地址:https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653580160&idx=1&sn=ae96bf7a9576e76e69d97bd22c3ac1e0&chksm=84b3b987b3c43091e256655b14e0be250cb670341c8133340bec1069bc992873e94ef1ddee36&scene=0&xtrack=1#rd
Android app反调试与代码保护的一些基本方案
原创: jackycao 腾讯Bugly 今天
| 导语 本文介绍Android app代码(java + ndk)的反调试的方法和保护代码增加逆向难度的一些基本方法。
ps:反调试并不能完全阻止逆向行为,只是在长期的攻防战中给破解人员不断的增加逆向难度。
Java:
(1)Proguard
借助Android studio的proguard工具,对Java代码分别进行压缩(Shrink)、优化(Optimize)、混淆(Obfuscate)、检查(Veirfy)。
压缩(Shrink):去掉代码中无用的类、函数方法和字段。
优化(Optimize):对Android的可执行文件dex进行优化,去掉无用指令。
混淆(Obfuscate):用毫无意义的字段对代码的类名、函数名、变量名重命名,比如用a, b, c这种。
检查(Veirfy):对混淆后的代码进行检查。
经过Proguard后,代码程序依然可以重新组织和处理,处理后的程序逻辑与之前完全一致,而混淆后的代码即便反编译后依然很难阅读。同时,在混淆过程中对于一些不影响正常运行的信息将永久丢失,这些信息的丢失使得程序更加难以理解。
同时,Proguard还可以控制对某个类混淆,以及对某个类的某些函数方法混淆。
下图是一张混淆前和混淆后的对比图:
混淆前:
混淆后:
(2)isDebuggerConnected
Android Debug类提供isDebuggerConnected函数,函数原型如下:
在VMDebug类里的isDebuggerConnected的具体实现在ndk程序里。
这里暂且不跟进该函数,总之,isDebuggerConnected函数用于检测此刻是否有调试器挂载到程序上,如果返回值为true则表示此刻被调试中。用法很简单,如下:
(3)android:debuggable属性
在Android的AndroidManifest.xml清单文件的application节点下加入android:debuggable="false"属性,使程序不能被调试。在Java程序代码里也可检测该属性的值,如下:
NDK:
(1)ptrace函数
Linux内核的ptrace函数原型:
ptrace可以允许A进程控制B进程,并且A进程可以检查和修改B进程的内存和寄存器。但是一个进程只能被一个进程调试,所以根据这个特点,可以让进程自己ptrace自己,传入的request设置为PTRACE_TRACEME,程序被自己附加调试后,其他的调试操作就会失败了。
(2)文件节点检测
一旦程序处于被调试状态,Linux会向进程的节点写入数据,比如/proc//status内容中的TracePid会写入调试进程的pid,如果TracePid的值不为0,就表明进程处于被调试状态了。
此外,通用的检测逻辑还有检测调试的端口号,Linux的文件节点/proc/net/tcp会记录着正在运行的进程的本地的端口号,调试工具IDA的默认的调试端口是23946,通过读取/proc/net/tcp内容,检测是否有23946,如果找到了就表明进程处于被调试状态了。
(3)Inotify
Linux的Inotify用于检测文件系统变化。它可以检测单个文件,也可以检测整个目录。
逆向最常做的一件事就是dump 内存,使用dd命令(或者如果使用gdb的话为gcore命令),dump掉/proc//mem或/proc//mpas或/proc//pagemap的内容。
这里,就可以使用Inotify API对上述三个文件监控,如果有发现打开、读写操作,极大概率就是进程正在被破解。
(4)so文件hash值检测
so文件在被JNI_Onload加载后,so文件的函数的指令是固定的,若被调试器挂载,下了断点后指令会发生改变(断点地址会被改写为bkpt指令),计算内存中加载的so的hash值,进行校验检测函数是否被修改或被下断点即可判断出是否被调试状态。
(5)时间差检测
一个取巧的方法,正常情况下,一段程序在两条代码之间的时间差是很短的,而对于调试程序来说,单步调试中的程序两条代码之间的时间差会比较大,检测两条代码之间的时间差,可以大概率判断程序是否被调试。
Resource资源文件:
Android资源文件经常被恶意篡改,植入各种广告,插件,严重影响了Android app生态平衡。
APK签名检测
Android SDK中有apk 签名检测的方法,Framework的PackageManager类提供了getPackageInfo()函数,函数原型:
第二个参数传入GET_SIGNATURES时,返回对象的signature字段就是签名信息,计算其hash值,前后对比hash值。实际可用的两种方案:
(1)在本地Java代码里进行校验,不一致则强退应用;
(2)把签名信息发到服务器后台,服务器后台记录着正确的签名信息,比对后不一致则返回一个错误给错误。
上述即为对于Android app的反调试,代码保护的一些基本策略。