常见APK保护策略

1 背景

众所周知,Android APK有三种常用的保护策略:

  • Java代码混淆
  • 资源混淆
  • 签名校验

2 Java代码混淆

Java代码混淆就是为了保护Java源代码,对编译好的class文件进行混淆处理。 混淆就是对发布出去的程序进行重新组织和处理,通过一些工具,对函数名、变量名、类名、字段进行批量重命名。使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。

图1 Java代码混淆示例

混淆没法进行彻底还原,只能通过工具尽可能的还原,例如Jadx、JEB等。

图2 Jadx反混淆示例

3 资源混淆

资源混淆就是将资源名称与目录进行混淆,提高反编译的难度,同时也减小APK文件的大小;APK中的R文件就是混淆后的资源文件;进入R文件内部,可以看到很多无意义无规则的目录,这是混淆后的资源文件。

资源混淆的目的就是让你编译不回去。比如说我们用Android Killer不能成功编译回去APK时,就可以尝试使用MT管理器来修改单个dex文件。在图3中找到街机捕鱼达人的安装目录/data/app/com.prgame5.fish2.baidu-2/base.apk,然后可以使用Dex编辑器修改相关内容。

图3 使用MT管理器修改dex文件

4 签名校验

签名校验和完整性校验主要是针对于二次打包的检测防范措施,如果没有签名校验和完整性校验功能,应用可能被恶意攻击者二次打包,被盗版的风险大大增加,同时也可能进行任意代码修改。

一个普通的APK想要安装到手机上,必须要有签名,如果没有签名,手机上是安装不上去的(雷电模拟器可以把签名关掉),签名校验就是对比下APK的签名是不是一样,如果不一样说明被篡改了,就不能安装。

4.1 背景

通常分析APK的时候基本上都是先反编译,再修改,最后打包。

如果没有修改就直接打包后仍报错很可能是签名验证的问题,但是可能在Java层或者so层进行签名验证,亦或二者皆有之。

这时候就需要会用到签名三兄弟,如果搜不到需要尝试是不是文件校验。

签名三兄弟:getPackageManagergetPackageInfogetPackageName
使用示例: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所示。

图4 AndroidKiller反编译未修改APK后安装失败

第二步 利用DDMS查看闪退时的信息

出现这种情况有可能就是出现了签名校验,一打开就出现崩溃,说明签名校验在入口点Application或者入口页面onCreate()

利用包信息过滤出只有com.bug.bt的信息,查看DDMS的Logcat界面,如图5所示。

图5 查看程序闪退时的DDMS显示的Logcat信息

可以看到APK加载了一个.so文件和两个方法,分别是/data/app/com.bug.bt-1/lib/arm/libbug.socom.bug.bt.MainActivity.qian()com.bug.bt.MainActivity.onCreate()

第三步 Java层分析

使用Jadx打开霸哥磁力搜索.apk,查看MainActivity的Java代码,OnCreate()函数调用了qian()函数,具体的qian()函数的实现如下所示。

1
2
3
4
5
6
7
8
public void qian(int i) {
try {
if (getPackageManager().getPackageInfo(getPackageName(), 64).signatures[0].hashCode() != i) {
Toast.makeText(this, 1, 1).show();
}
} catch (PackageManager.NameNotFoundException e) {
}
}

其功能是获取包名管理,然后进行签名,计算hash值,不相等就使程序崩溃。
这是Java层验证签名的高明之处,也是很多人容易忽略的一点。

因此,我们可以在AndroidKiller中注释掉onCreate()方法中关于qian()的调用,打包并安装到模拟器中,如图6所示。

图6 注释掉qian()函数后的程序

虽然程序现在运行了,但界面却是一片白。而且onCreate()函数中还有native实现的bug()函数的调用,因此绕过Java层的签名后还得绕so层。

第四步 so层分析

使用IDA打开AndroidKiller反编译的霸哥磁力搜索.apk的lib/armeabi/libbug.so文件,经过尝试后发现是静态注册方法
然后导入jni.h头文件,修改函数的参数类型和名称,最终发现Java_com_bug_bt_MainActivity_bug()函数调用的getSignHashCode()函数才是最关键因素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
jint __fastcall getSignHashCode(JNIEnv *env, jobject obj)
{
void *v4; // r7
struct _jmethodID *v5; // r0
jclass v6; // r0
struct _jmethodID *v7; // r0
jobject v8; // r6
jobject v9; // r6
jclass v10; // r0
struct _jfieldID *v11; // r0
jobject v12; // r0
jobject v13; // r6
jclass v14; // r0
struct _jmethodID *v15; // r0
jint result; // r0
jobject v17; // [sp+8h] [bp-20h]
struct _jmethodID *v18; // [sp+Ch] [bp-1Ch]

v4 = ((*env)->GetObjectClass)(env);
v5 = (*env)->GetMethodID(env, v4, "getPackageManager", "()Landroid/content/pm/PackageManager;");
v17 = (*env)->CallObjectMethod(env, obj, v5);
v6 = (*env)->GetObjectClass(env, v17);
v18 = (*env)->GetMethodID(env, v6, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
v7 = (*env)->GetMethodID(env, v4, "getPackageName", "()Ljava/lang/String;");
v8 = (*env)->CallObjectMethod(env, obj, v7);
(*env)->GetStringUTFChars(env, v8, 0);
v9 = (*env)->CallObjectMethod(env, v17, v18, v8, 64);
v10 = (*env)->GetObjectClass(env, v9);
v11 = (*env)->GetFieldID(env, v10, "signatures", "[Landroid/content/pm/Signature;");
v12 = (*env)->GetObjectField(env, v9, v11);
v13 = (*env)->GetObjectArrayElement(env, v12, 0);
v14 = (*env)->GetObjectClass(env, v13);
v15 = (*env)->GetMethodID(env, v14, "hashCode", "()I");
result = (*env)->CallIntMethod(env, v13, v15);
if ( result != 226776851 )
exit(0);
return result;
}

接着,我们使用010 Editor把BLX exit给NOP掉,绕过so层的exit()函数即将其十六进制改为NOP NOP即可(即00 00 00 00),如图7所示。
【注】直接使用IDA进行NOP是没用的

图7 使用010 Editor把BLX exit给NOP掉

在修改好libbug.so文件之后,一定要记得刷新,不然不能将修改好的so文件打包进APK,如图8所示。

图8 刷新

最后,通过AndroidKiller签名打包安装发现已经大功告成了。

图9 成功绕过APK签名验证

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所示。

图10 通过DDMS发现可疑函数调用

handleToken()调用了killProcess()函数,猜测是没有通过签名校验而杀死进程

第二步 Jadx查看程序运行流程

使用Jadx打开APK,搜索killProcess后发现对应的函数调用关系,如图11所示。

图11 发现对应包中的函数调用

点进去后发现具体的Java层签名验证逻辑,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void checkSigAsync(final Context context) {
GlobalTaskScheduler.m12798Rt().mo16976a(10001, 1, new GlobalTaskScheduler.AbstractC2795c() {
/* class com.shuqi.app.ShuqiApplication.C31683 */

@Override // com.shuqi.android.p215a.GlobalTaskScheduler.AbstractC2795c
public boolean handleToken(int i, int i2) {
try {
if (-1936262660 == context.getPackageManager().getPackageInfo(context.getPackageName(), 64).signatures[0].hashCode()) {
return true;
}
Process.killProcess(Process.myPid());
return true;
} catch (Exception e) {
System.exit(-1);
return true;
}
}

@Override // com.shuqi.android.p215a.GlobalTaskScheduler.AbstractC2795c
public int getMaxStep() {
return 0;
}
});
}

我们发现签名验证失败后会调用try里面的Process.killProcess(Process.myPid());,所以程序才会闪退。因此,我们只需要使handleToken()的返回值为true即可。

第三步 AndroidKiller修改handleToken()函数

使用AndroidKiller搜索killProcess,会发现有多处调用,我们这里只需要对ShuqiApplication$3.smali中的handleToken()函数进行修改即可,如图12所示。

图12 修改handleToken()函数的返回值

最终,程序能够正常运行,如图13所示。

图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。

×

喜欢就点赞,疼爱就打赏