1 背景
众所周知,Android APK有三种常用的保护策略:
- Java代码混淆
- 资源混淆
- 签名校验
2 Java代码混淆
Java代码混淆就是为了保护Java源代码,对编译好的class文件进行混淆处理。 混淆就是对发布出去的程序进行重新组织和处理,通过一些工具,对函数名、变量名、类名、字段进行批量重命名。使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。
混淆没法进行彻底还原,只能通过工具尽可能的还原,例如Jadx、JEB等。
3 资源混淆
资源混淆就是将资源名称与目录进行混淆,提高反编译的难度,同时也减小APK文件的大小;APK中的R文件就是混淆后的资源文件;进入R文件内部,可以看到很多无意义无规则的目录,这是混淆后的资源文件。
资源混淆的目的就是让你编译不回去。比如说我们用Android Killer不能成功编译回去APK时,就可以尝试使用MT管理器来修改单个dex文件。在图3中找到街机捕鱼达人的安装目录/data/app/com.prgame5.fish2.baidu-2/base.apk,然后可以使用Dex编辑器修改相关内容。
4 签名校验
签名校验和完整性校验主要是针对于二次打包的检测防范措施,如果没有签名校验和完整性校验功能,应用可能被恶意攻击者二次打包,被盗版的风险大大增加,同时也可能进行任意代码修改。
一个普通的APK想要安装到手机上,必须要有签名,如果没有签名,手机上是安装不上去的(雷电模拟器可以把签名关掉),签名校验就是对比下APK的签名是不是一样,如果不一样说明被篡改了,就不能安装。
4.1 背景
通常分析APK的时候基本上都是先反编译,再修改,最后打包。
如果没有修改就直接打包后仍报错很可能是签名验证的问题,但是可能在Java层或者so层进行签名验证,亦或二者皆有之。
这时候就需要会用到签名三兄弟,如果搜不到需要尝试是不是文件校验。
签名三兄弟:getPackageManager、getPackageInfo、getPackageName
使用示例:context.getPackageManager().getPackageinfo(context.getPackageName(), 64).signatures[0].hashCode()
4.2 霸哥磁力搜索
4.2.1 基本思路
此处比较高明的签名验证的地方
- Java层:Toast.makeText(this, 1, 1).show();
- so层:签名三兄弟签名验证
【注】Toast.makeText(context, text, duration).show();才是正确的使用示例,其中context是上下文对象,text是显示的内容,duration是显示的时间,而APK利用传入错误的参数来使程序崩溃。
因此,就需要在Java层和so层绕过签名验证的逻辑。
4.2.2 具体的绕过操作
第一步 安装AndroidKiller反编译未修改的APK到模拟器
首先,使用AndroidKiller编译霸哥磁力搜索.apk,安装到模拟器中查看软件正常运行效果,如图4所示。
第二步 利用DDMS查看闪退时的信息
出现这种情况有可能就是出现了签名校验,一打开就出现崩溃,说明签名校验在入口点Application或者入口页面onCreate()。
利用包信息过滤出只有com.bug.bt的信息,查看DDMS的Logcat界面,如图5所示。
可以看到APK加载了一个.so文件和两个方法,分别是/data/app/com.bug.bt-1/lib/arm/libbug.so、com.bug.bt.MainActivity.qian()和com.bug.bt.MainActivity.onCreate()。
第三步 Java层分析
使用Jadx打开霸哥磁力搜索.apk,查看MainActivity的Java代码,OnCreate()函数调用了qian()函数,具体的qian()函数的实现如下所示。
1 | public void qian(int i) { |
其功能是获取包名管理,然后进行签名,计算hash值,不相等就使程序崩溃。
这是Java层验证签名的高明之处,也是很多人容易忽略的一点。
因此,我们可以在AndroidKiller中注释掉onCreate()方法中关于qian()的调用,打包并安装到模拟器中,如图6所示。
虽然程序现在运行了,但界面却是一片白。而且onCreate()函数中还有native实现的bug()函数的调用,因此绕过Java层的签名后还得绕so层。
第四步 so层分析
使用IDA打开AndroidKiller反编译的霸哥磁力搜索.apk的lib/armeabi/libbug.so文件,经过尝试后发现是静态注册方法。
然后导入jni.h头文件,修改函数的参数类型和名称,最终发现Java_com_bug_bt_MainActivity_bug()函数调用的getSignHashCode()函数才是最关键因素。
1 | jint __fastcall getSignHashCode(JNIEnv *env, jobject obj) |
接着,我们使用010 Editor把BLX exit给NOP掉,绕过so层的exit()函数即将其十六进制改为NOP NOP即可(即00 00 00 00),如图7所示。
【注】直接使用IDA进行NOP是没用的。
在修改好libbug.so文件之后,一定要记得刷新,不然不能将修改好的so文件打包进APK,如图8所示。
最后,通过AndroidKiller签名打包安装发现已经大功告成了。
4.2.3 其他绕过so层的思路
so层可以有多种绕过签名验证的策略
- 把BLX exit给NOP掉
- 把BL getSignHashCode给NOP掉
- 把BEQ loc_D12修改为BNQ loc_D12,即D0改成D1
4.3 书旗小说
4.3.1 基本思路
通过DDMS查看反编译但未修改的APK的报错信息,进而查找签名的蛛丝马迹,接着修改签名函数的返回值,最终即可绕过签名。
4.3.2 具体的绕过操作
第一步 DDMS查看报错信息
首先,使用AndroidKiller反编译APK,再打包后安装到模拟器中,结果发现程序一直闪退。
查看从打开程序到程序崩溃后DDMS的Logcat界面,发现killProcess()和handleToken()的函数调用,如图10所示。
handleToken()调用了killProcess()函数,猜测是没有通过签名校验而杀死进程。
第二步 Jadx查看程序运行流程
使用Jadx打开APK,搜索killProcess后发现对应的函数调用关系,如图11所示。
点进去后发现具体的Java层签名验证逻辑,如下所示。
1 | public static void checkSigAsync(final Context context) { |
我们发现签名验证失败后会调用try里面的Process.killProcess(Process.myPid());,所以程序才会闪退。因此,我们只需要使handleToken()的返回值为true即可。
第三步 AndroidKiller修改handleToken()函数
使用AndroidKiller搜索killProcess,会发现有多处调用,我们这里只需要对ShuqiApplication$3.smali中的handleToken()函数进行修改即可,如图12所示。
最终,程序能够正常运行,如图13所示。
5 参考文献
[1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019.
[2]https://blog.csdn.net/freeking101/article/details/106735467
[3]https://blog.csdn.net/freeking101/article/details/106742734
[4]https://blog.csdn.net/freeking101/article/details/106747417
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。