android-anti-debug

Author Avatar
Xzhah 6月 20, 2022
  • 在其它设备中阅读本文章

安卓反调试

1. java层反调试

​ Android的android.os.Debug类提供了isDebuggerConnected()方法,用于检测是否有调试器挂载到程序上。首先会想到,能不能改了smali重新编译回去?问题在于app有签名校验,改了smali后不能再打开该app。所以一个比较好的方法是用frida进行hook。还看到有人思路是直接修改测试机的系统,把aosp的相关代码直接改为returen False。

2. native层反调试

2.1 调试端口检测

​ android_server的默认监听的端口号是23946,所以可以通过检测这个端口号来起到一定的反调试作用。在Linux系统中,/proc/net/tcp文件会记录一些连接信息,在启动android_server以后,文件中会多出一条23946端口的记录。这样可以起到检测调试的作用,当然如果调试指定了其他监听端口,本方法就无法检测了。

2.2 基于时间检测

​ 利用计时api,判断某段代码用时是否异常,如果某段代码用时明显长于正常值,则很可能是在调试当中。

2.3 基于文件检测

​ 可以去读取一些/proc目录下的文件。

​ 1. /proc/pid/status文件,在未调试的情况下,TracerPid这一项是0,调试状态下是调试进程的PID。/proc/pid/stat文件,在调试状态下,会有t字符的标志出现。/proc/pid/wchan文件,在调试状态下,里面的内容是ptrace_stop。

2.4 基于文件格式反调试

  1. 比如文件名是假的,需要用别的文件格式去解析。

    1. 文件数据被修改,可以正常运行但是无法被IDA等工具解析。

比如elf的文件头在系统运行时解析和工具解析是不一样的,这时候可以精心构造一个文件头,让readelf/IDA等工具解析失败。

2.5 ptrace

​ 因为Linux下每个进程同时只能被一个进程调试,linux 系统gdb等调试器,都是通过ptrace系统调用实现。所以app可以先ptrace自己,防止调试器附加。解决办法就是找到ptrace代码手工patch掉。或者可以在LD_PRELOAD里设置一个自定义的so,自己在so里实现一个fake_ptrace,相当于把ptrace给hook掉。

​ 以及看到网上有思路可以用ptrace附加zygote,去拦截zygote的fork子进程,从而定位到目标应用pid。然后拦截目标pid的系统调用,把ptrace_traceme这种相关参数改了,让ptrace失败。

2.6 断点扫描

​ 1.调试器的原理是向断点地址插入breakpoint的汇编指令,把原来的指令暂存到别处。那么就可以扫描本so内存中有没有breakpoint指令就好了。

​ 2.另一方面,breakpoint指令会使被调试进程发出信号SIGTRAP,调试器会截获linux系统给被调试进程的信号。由调试者来选择是否传递信号。但是SIGTRAP是个例外,因为通常的目标程序中不会出现breakpoint,因为这会使得程序自己奔溃。因此,当调试器遇到SIGTRAP信号时会认为是自己下的断点发出的。这样一来当调试器给这个breakpoint命令插入断点breakpoint后,备份的命令也是breakpoint,这样当继续执行时,调试器将备份指令恢复并执行,结果误以为备份后这个位置发出的SIGTRAP又是自己下的断点造成的,这样一来就会使得调试器的处理逻辑出现错误,不同的调试器会导致各种不同的问题。开发者可以在自己的signal handler函数里再把breakpoint指令替换为相关函数。

2.7 调试器错误理解

​ Arm架构的CPU却不仅仅只是运行Arm指令集,还会运行Thumb指令集,并且目前Android Studio已经将Thumb-2模式设定为默认NDK编译指令集,比Arm指令集还要优先。这是为什么?因为Thumb-2模式是Thumb16和Thumb32指令的混合执行,有更高的效率和代码密度,对于APP的运行效率和空间占用都有着更好的表现。

​ 但是这对于调试器来说并不是好事。Thumb16和Thumb32在opcode上没有冲突,只要一条条按照顺序去反汇编,就可以得到正确的Thumb指令。但是Arm指令集和Thumb指令集是会有冲突的,一条Thumb32指令是可以被理解为作用意义完全不同的另一条Arm指令的,甚至2条Thumb16指令可以被调试器误解为一条合法的Arm指令。

​ 而这两个模式的切换涉及跳转时候的地址数值,这个值可能是动态产生的,因此对编译器来说难以判断跳转后的代码是该理解为Thumb还是Arm。

2.8 多进程/线程

​ 这里意思是开一个守护线程,用来检测父线程是否被调试。算是反调的一种实现方式吧,原理还是以上检测原理。

2.9 进程名

​ 遍历进程,看有没有android-server之类的进程,来判断是否有调试器

2.10 利用调试器截获信号进行反调试

​ 调试器会截获系统给进程的信号,然后选择是否传递给进程。那其实可执行文件里就可以自己将一些流程放在信号处理函数中,从而达到检测调试目的。