首页 > 代码库 > Bash玩转脚本4之搞一套完整的Android反编译与分包工具
Bash玩转脚本4之搞一套完整的Android反编译与分包工具
一、前言
正在搞IOS的微信支付和支付宝支付,焦头烂额之时,天上掉下来一个Android分包工具的需求,觉得还蛮有意思,其实之前一直想搞一个类似的东西,正好趁着这次机会实践一下。
[原文地址]
(http://blog.csdn.net/yang8456211/article/details/52513354 )
(先说清楚需求,这个分包工具要干什么)
从产品角度
拿到一个apk安装包,然后用这个包去生成n个包,这n个包需要有特定的标示,能够根据包的标示去收集信息,而且这个n个包彼此不能覆盖安装。
从技术角度
对于这个需求,关键点在于三个点
1. 怎么去生成n个包?
2. 怎么修改apk的标示?
3. 怎么使得这n个包不能覆盖安装?
二、Just do it
2.1 首先我们自己制作一个简单的apk包
这个apk包包含两个功能点:
- 获取一些包的基本信息,例如应用包名
- 获取一些Meta信息,用来区分我们所打的包
因为这篇文章主要在讲bash和apk打包,对于Android代码就不赘述了,贴出来参考。
(获取应用包名)
PackageInfo info = null;
try {
// 获取包名
info = this.getPackageManager()
.getPackageInfo(this.getPackageName(), 0);
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 当前版本的包名
pgName = info.packageName;
(获取Meta信息)
PackageManager pm = this.getPackageManager();
ApplicationInfo appInfo = null;
try {
appInfo = pm.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
} catch (NameNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// 读取meta的内容
msg.append(appInfo.metaData.getString("SDK_CHANNEL"));
这里我们读取了SDK_CHANNEL这个信息作为包的标示,需要在Android Manifest中配置好相关的Meta data。
<meta-data android:name="SDK_CHANNEL" android:value="天降正义" />
运行时截图
可以看到现在的包名是com.example.testmultipac,渠道是天降正义~
接下来我们就开始着手一个一个解决问题
三、开始处理我们的包
3.1 怎么去生成这个n个包?
想要生成多个包,必须涉及到需要把apk进行反编译,然后重新生成apk包的过程,因此我使用了apktool这个工具。
注:apktool 有1和2两个版本,两者语法有些许不同,在这里使用了apktool_2.1.0这个jar,当然你也可以使用apktool1;window可以直接调用apktool.bat的批处理,事实上也是调用了apktool.jar。
apktool指令:
解开apk包:
java -jar apktool d -f 输入的apk路径 -o 输出的文件夹路径
重新生成apk包:
java -jar apktool b 上一步解出的文件夹路径 -o 输出apk路径
注:这里使用的apktool2,因此有-o这个参数,apktool是没有的,请注意。
了解了apktool的指令后,我们便可以方便的实现apk的解包和组包,在组包的会后通过修改包的名称,就可以生成多个不同名的包了。
例如:
do_repac(){
outapk=$apkpacpath"/"$game_package_name".apk"
# apktool重新回包 以免apktool的一些临时改动
java -jar ./tool/apktool/$APKTOOL_JAR b $unpacpath -o $outapk
}
上面的outapk即为 $apkpacpath”/”$game_package_name”.apk”
game_package_name为当前包的包名。(如果能做到包名不同,则生成的apk的名字便不同)
3.2 怎么修改apk的标示?
目前例子来说,我们apk的标示是”SDK_CHANNEL”这个Meta字段,我们可以通过修改这个字段来实现我们对包的区分。当客户端对服务器发起请求的时候,带上这个字段,服务器便可以方便的知道是哪个包进行的请求了。
那我们要怎么修改这个字段呢?
在此我使用了sed,一个方便替换的文本的指令。
(想要见识sed对字符串的替换,可以看我的这篇文章)
Bash玩转脚本3之几个指令有趣的筛选京东评价
指令为:
sed -i ‘‘ "s~^.*<meta-data android:name=\"SDK_CHANNEL\".*~ <meta-data android:name=\"SDK_CHANNEL\" android:value=http://www.mamicode.com/""$game_channel"\"/>~g" $manifest
在此我稍作解释,如果有兴趣的朋友可以去google或者百度才能系统的学习。
1)sed "s~原字符串~新字符串~g" 文件路径
这是sed最基本的指令, “~”符号可以换成很多符号,三个”~”对应更换即可.
2)sed -i
通过 man sed
可以查到sed的基本指令(OSX 系统)
意思是不备份,直接在原文件上面进行操作。
注:在linux上可以直接使用 sed -i
,而在Unix上需要sed -i ""
3) ^.*<meta-data android:name=\"SDK_CHANNEL\".*
这一段是正则匹配,用于匹配到SDK_CHANNEL那一行,sed是一行一行进行扫描的,如果遇到能够匹配的就会进行替换。
4)"$game_channel"
这个是bash的取值,相当于一个变量,这里我是取game_channel这个变量
到现在,我们已经能够把包解出来,然后使用sed去修改里面的标示,看样子已经成功了一半了。
3.3 怎么使得这n个包不能覆盖安装?
涉及到这个问题,我们需要对Android的基本知识进行一个简单回顾。
Android通过什么来保证应用的唯一性?
答案是包名和签名。
- 如果两个apk,包名相同,签名也相同,则会根据versionCode的值来决定是否会覆盖,如果后一个apk的versionCode比较大,才能够覆盖安装。
- 如果包名相同,签名不同,则会被识别为不安全的应用,会给予提示,安装的结果会是后一个apk会删掉前一个apk,然后进行安装。
- 如果包名不同,签名相同,则代表着同一个开发者的应用,被识别为不同的应用。(看来这样就可以实现我们的不覆盖安装了)
在修改包名的时候,我同样是用了一系列指令。(下意识写的指令,并没有考虑到是否最好,如果有更好的指令可以给我留言)
old_pacname=`cat $manifest | grep "package=" | head -n 1 | awk -F ‘package=\"‘ ‘{print $2}‘ | awk -F ‘\"‘ ‘{print $1}‘ | xargs echo `
echo "==>"$old_pacname
sed -i ‘‘ "s~package=\""$old_pacname\""~package="\"$game_package_name\""~g" $manifest
讲一下思路:
1. 先把”package=”关键字的那一行抓出来(这里是为了抓包名)
2. 使用两次awk取出 package=后面的包名(得到当前的包名)
3. 当前的包名赋值给old_pacname
4 .使用sed替换旧的包名为新包名,新的包名为game_package_name这个变量的值。
至此我们已经弄清楚了其中的知识点,对于一些难点也有了一定处理办法,接下来我们便要开始做一个脚本去自动化实现Android的反编译和分包了。
3.4 来看看我们的流程
可以发现,在这个流程的最后一步,还有个签名的过程,那怎么进行签名呢?
3.5 签名过程
我们是通过jarsigner这个工具对apk进行签名的,如果不签名的应用可是无法安装的~
那个这个jarsigner是什么呢?其实这个是jdk自带的对jar包进行签名的工具,我们可以在安装的java指令的同级目录找到它。
通过直接输入jarsigner指令,可以得到提示
用法:
# 显示信息 签名文件 签名密码 生成apk 未签名apk alias
jarsigner -verbose -keystore $keystore_name -storepass $SIGN_PASS -signedjar $sign_apkname -digestalg SHA1 -sigalg MD5withRSA $unsign_apkname $SIGN_ALIAS
每个参数的意思注释都标志的很清楚了~
四、make Auto
下面讲讲脚本的思路
4.1 实现两个调用模式
第一种)所有的参数从外部传递调用,Mode为1时
第二种)读取本地的配置文件,Mode为0时,可以实现批处理生成多个包
功能说明:
help_info() {
cat << ENTER
============= Auto pac For game =============
Version: 1.0
Date: 20160907
Usage: Auto repackage For the game, modify package name and subchannel
e.g.:
Mode0: sh autopac.sh inputApkPath gamename
Mode1: sh autopac.sh inputApkPath gamename channel outputApkPath
inputApkPath: 待分包的apk路径
gamename: 游戏名称英文首字母小写
channel: 渠道号
outputApkPath: 完成输出apk的路径
============= Auto pac For game =============
ENTER
}
参数由外部输入就不赘述了,简单的赋值即可。
对于Mode=0时的读取配置文件一般是通过awk来实现的。
4.2 使用awk读取配置文件
例如配置文件的内容是这样
[package]
package1=d101
package2=d102
我们通过awk取出对应的d101,和102出来
read_config() {
inifile=$1 #$1为配置文件的位置
_readIni=`awk -F ‘=‘ ‘$1~/‘package[d]*‘/{print $2}‘ $inifile`
echo $_readIni
}
稍微解释一下:
1. awk -F ‘=’ 为按照’=’,进行字符串分割
2. package[d]*为正则表达式,用来匹配package1,package2这种类型的一行,所以配置文件中可以有很多个package
3. {print $2} 就是打印出用’=’分割的第二个字符串,即’=’号后面的内容。
(假如我的标示不止1个怎么办?)
例如我的标示有三个,channel、subchannel、payway分别对应着渠道号、子渠道、支付渠道,那这个时候怎么办呢?
我们可以把配置文件写成这样:
package1=d101:subchannel101:1
package1=d102:subchannel102:2
然后在按照上面的做法,我们取出了 ‘=’ 右边的值,例如d101:subchannel101:1
我们便可以通过bash的字符串分割来做:
game_payway=${1##*:}
temp=${1%:*}
game_subchannel=${temp##*:}
game_channel=${temp%:*}
这样便可以取出payway等值了,关于bash的字符串分割,可以自行百度
基本的东西都讲完了,我们来看看最终的效果:
五、实现效果
以mode为1的批处理为例子:
1)配置文件配置了4个不同channel的包:
[package]
package1=yingxiongbuxiu:英雄不朽
package2=wushiyidao:午时已到
package3=laidianyinyue:来点音乐
package4=ronghuohexin:熔火核心
2)执行脚本:
sh autopac.sh ./TestMultiPac.apk mszl
TestMutiPac.apk就是我们最开始生成的一个apk,mszl为游戏的名称
3)执行效果:
看到已经生成了4个包:
4)安装效果:
如图生成了5个包
随便选取一个包的效果
源码地址:
https://github.com/yang8456211/AutoPacAndroid
杨光(atany)原创,转载请注明博主与博文链接,未经博主允许,禁止任何商业用途。
博文地址:http://blog.csdn.net/yang8456211/article/details/52513354
博客地址:http://blog.csdn.net/yang8456211
本文遵循“署名-非商业用途-保持一致”创作公用协议
Bash玩转脚本4之搞一套完整的Android反编译与分包工具