问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
安卓逆向-反调试与绕过反调试的几种姿势
移动安全
反调试在代码保护中扮演着很重要的角色,虽然不能完全阻止攻击者,但是还是能加大攻击者的时间成本,一般与加壳结合使用。 反调试可以分为两类:一类是检测,另一类是攻击,前者是去想各种办法去检测程序是否在被调试,如果正在被调试的话做出一些“反”的举措,比如退出等等,后者是采用攻击的方法,就是想办法让调试器不能正常工作或者是让调试器崩溃,从而阻止它,本章主要讲解检测方面的知识。
前言 -- ```php 反调试在代码保护中扮演着很重要的角色,虽然不能完全阻止攻击者,但是还是能加大攻击者的时间成本,一般与加壳结合使用。 反调试可以分为两类:一类是检测,另一类是攻击,前者是去想各种办法去检测程序是否在被调试,如果正在被调试的话做出一些“反”的举措,比如退出等等,后者是采用攻击的方法,就是想办法让调试器不能正常工作或者是让调试器崩溃,从而阻止它,本章主要讲解检测方面的知识。 ``` 一、实战详解分析-关键文件检测 --------------- ### 1、IDA案例思路分析 反调试通俗意思就是,程序挂起后突然出现八个F:FFFFFFFF或者在java层运行程序一直运行不起来等情况! 第一个反调试会检测android\_server,文件名检测!来分析下android\_server源文件  接下来分析下filecheck,用IDA打开  拖入后,IDA反编译  这是编译可执行文件,那么和so文件有什么区别呢? SO文件是可以找到JNI\_onload,那么编译可执行文件在IDA如何找逻辑所在处呢? 可看到**main函数的入口函数**,在Exports搜索start:   进来后看到main函数,还有了BL libc\_init指令,这时候看看有几个参数?  如果这个函数的参数超过了四个以上(>4),跳转的地址就得用其他的寄存器来替代,libc\_init当它这里没有被代替的时候,往下找最大的寄存器只出现了R3为止,没有出现R4以上的寄存器,那么就可以猜测传入的个数的参数为:0-3就是四个参数!TAB查看伪C代码:  双击main方法  这样就是编译可执行程序所要找的main函数 就算在重新打开IDA反编译,还是直接进入main函数处,那么得知道如何进入main函数的地方 开始分析,TAB键打开伪c代码  隐藏类型后:  check()检查,执行下面的循环if,判断if需要执行检查check,双击check进入  v0 = opendir("/data/local/tmp"); opendir打开/data/local/tmp目录给V0,这是文件指针 result = getpid(); getpid给result。getpid是当前进程的ID。 v2 = result; result给V2 就是说当V0不为空的时候,要执行while里面的循环逻辑, V3=readdir(v0); readdir()返回参数dir 目录流的下个目录进入点。返回值:成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回NULL. v4 = v3 == 0; 拿V3和0对比 v5 = (v3 + 19); 计算v3+19 if ( v4 ): 判断V3=readdir(v0);是否为null,为空结束循环 如果/data/local/tmp目录下有android\_server就会直接kill结束进程,这就是一个文件反调试逻辑思路。 ### 2、案例思路源码分析 过掉文件检查不难,直接改名字即可!开始分析下面的案例  C语言从这个main函数开始执行,首先对数组的开始一个声明,主要关心check函数,  check就是检测android\_server文件的!和之前理解的是一样的,定义了一个字符串指针指向目录/data/local/tmp,然后定义一个文件dir操作,用opendir打开tmp目录,打开后获取pid,接下来判断有没有打开,如果打开不为空(!=)就打开成功下面的while条件代码,currentDir有定义了一个指针,当读取的文件指针不为空(null),就继续往下读取。 **遍历一个文件时,如果你的指针指向的下一个位不为空,意味着你的指针的后面还有文件。** ### 3、Android底层详解 上传文件名为android\_server和filecheck可执行文件**(android\_server文件在IDA的dbgsrv目录下)** ```php adb push C:\test\filecheck data/local/tmp adb push C:\test\android_server data/local/tmp ```  成功上传到底层,可执行文件 为filecheck赋最高权限 ```php chmod 777 filecheck ```  执行filecheck文件  我们看到执行失败,报处kliied 为android\_server改名测试  可以看到改名后,filecheck正常执行 二、实战详解分析-调试端口检测 --------------- ### 1、源码分析调试端口检测  打开checkTCP.c文件开始分析 直接分析check方法  首先C文件中执行定义字符数组char buf\[0x1000\]={0};,然后执行命令cat /proc/net/tcp |grep :5D8A",这里grep :5D8A就是只查找存在5D8A字符的那一行,在/proc/net/tcp文件中5D8A代表端口的意思,5D8A是十六进制,转为十进制就是23946   如果存在23946这个端口就进入while循环,执行kill  ### 2、IDA分析调试端口检测 找到可执行文件  使用IDA打开,找到main函数,开始分析  如何找到main函数 ```php 1、左边列表中 2、在Exports搜索start,双击进入找到main双击进入 ``` 找到main函数后,查看伪C代码  双击sub\_728  这里的伪C代码和前面分析的c语言源码是一样的 ### 3、Android底层详解 上传可执行文件,上传android\_server文件 ```php adb push C:\test\checkTCP data/local/tmp adb push C:\test\android_server data/local/tmp ```  为两个文件赋执行权限  **测试没有启动23946端口状态** 执行checkTCP文件  没有输出,完成程序执行 **测试启动23946端口状态** 执行android\_server,启动23946端口  打开一个新窗口,执行checkTCP文件  我们看到输入了cat /proc/net/tcp |grep :5D8A结果,并killed了checkTCP程序 三、实战详解分析-进程名称检测 --------------- ### 1、源码分析进程名称检测 找到c语言源码  开始分析源码  直接从main函数开始分析,看到coursecheck方法   sprintf(filename, "/proc/%d/status", pid); 意思就是获取(getpid)pid值放到%d里面,然后可读的形式filename打开(r)操作,就是获取此时进程程序的一个状态。 FILE \*fd=fopen(filename,"r"); if(fd!=NULL) 开打一个文件fd,如果打开成功就进行while循环  fgets是大小,(line,bufsize,fd)指向指针的地方, if(strstr(line,"TracerPid")!=NULL); strstr会判断有没有TracerPid字符串 int statue =atoi(&line\[10\]); atoi会截取字符串前十个字符,意思就是检测TracerPid:,这个冒号后一位置不等于0的话,就表示TracerPid发生改变,就意味着在调试。 下面还对android\_server进行检测,如果有着杀死进程。 ### 2、IDA分析进程名称检测 在实战中进程遇到进程名称检测,开始分析关于进程检测的可执行文件 在main函数中找到coursecheck方法  双击sub\_794进入  getpid获取程序的pid,然后fopen指向命令,如果指向成功就进行检测TracerPid值,这里只是v6,这里v6在IDA识别为未定义的值。 ### 3、Android底层详解 上传进程检测可执行文件及android\_server文件 ```php adb push C:\test\BubbleSort data/local/tmp adb push C:\test\android_server data/local/tmp ```  赋权BubbleSort文件 ```php chmod 777 BubbleSort ```  执行程序  打开一个新窗口,查看BubbleSort文件运行状态 ```php ps | grep BubbleSort ```  11032是进程名称 查看该进程下的status文件 ```php cat /proc/11032/status ```  执行命令后就会遍历程序里面的内容,如果有TracerPid则不等于null,进入if,atoi检测前十位就是TracerPid:,判断TracerPid:后面的内容,如果为0就不进入下一个if,如果不为0就进入if继续执行。 那么现在让他发生变化,需要使用IDA! 先运行android\_server启动,先给android\_server,并不用默认端口启动 ```php mv android_server test ./test -p12345 ```  在电脑端进行端口转发 ```php adb forward tcp:12345 tcp:12345 ```  完成后,打开IDA,连接本地的12345端口  配置IDA连接  配置IDA选项  继续ps查询BubbleSort程序 ```php ps | grep BubbleSort ```  根据进程名称11536查看进程文件夹中的status内容  可以看到这时候TracerPid内容不为0了,为11389,发生了变化  源码提示如果检测到TracerPid的值不等于0对程序进程打印然后杀死进程。 如何过滤掉?首先他要获取TracerPid,返回值为R0,那么修改R0或者注释即可,或者刷机修改系统内容,或者把TracerPid修改回去都可以 进程名称检测: ```php 当动态调试的时候找到TracerPid赋值的地方,手动把它赋值修改为0即可。 ``` 四、实战详解分析-轮循检查技术 --------------- **简述:**轮循检查主要通过**safe\_attach函数**和**handle\_events函数**来实现的,轮询检测反调试技术基于**循环检测进程的状态,判断当前进程是否在被调试**。 **优点:**实现比较简单 **缺点:**系统资源消耗大 **原理:**读取进程的/proc/\[pid\]/status文件,通过该文件得到调试,当前进程的调试器(检测调试器的\[pid\]) **实现:**通过status文件内的TracerPid字段的值判断当前进程或线程是否正在被调试。 **status文件中各字段简述:** Name:进程名称 State:进程状态 Tgid:一般进程的名称 Pid:一般进程的ID,它的值和getpid函数的返回值相等 PPid:父进程的ID TracerPid:实现调试功能的进程ID,值为0表示当前进程未被调试。 **绕过反调试检测的方案:** 1、动态调试时修改TracerPid字段值为0, 2、修改内核,让TracerPid字段值为负值 ### 1、源码分析-轮循检测技术  打开main.cpp  从main函数入口分析,main函数中调用了anti\_debugger()方法,anti\_debugger有调用了anti\_debugger\_thread方法,anti\_debugger\_thread中有调用了check\_debugger方法 下面详细分析check\_debugger中的内容  fopen中f是file,就是打开文件操作的意思,fopen打开path,path是上面传入path路径的256并以rt模式打开,如果打开不为空(!=null),就执行while循环,fgets就是打开指向的fp文件,if通过strncmp函数进行判断,使用line参数和TRACERPID参数进行对比,最多比较前 TRACERPID\_LEN个字节, 如line参数和TRACERPID参数内容一致,进入if判断,if中tracerPid为0直接打印,还有if (!tracerPid)不为0就返回flase!! 这就是简单的一个检查遍历然后根据不同的结果返回不同的值。 ### 2、IDA分析轮循检测技术 上传轮循检测的可执行文件poll\_anti\_debug,并赋执行权限 ```php adb push C:\test\poll_anti_debug data/local/tmp chmod 777 poll_anti_debug ```  目前上传了检测调试程序,还需要上传调试程序  上传debugger调试程序,并赋权 ```php adb push C:\test\debugger data/local/tmp chmod 777 debugger ```  运行检测调试程序poll\_anti\_debug  一直在循环打印TracerPid: 0 我们开启debugger程序,来模拟我们现在正在调试,将pid值设置为poll\_anti\_debug程序的pid  我们看到poll\_anti\_debug检测到TracerPid的值发生了变化。 这就是轮循检测。 五、实战详解分析-self-debugging反调试 -------------------------- ### 1、原理简述 **原理:** Self的英文意思是自己,顾名思义,self-debugging就是通过调试自身检测出是否被调试。 父进程创建一个子进程,通过子进程调试父进程。 **特点:** 非常实用、高效率的实时反调试技术 **优点(可以作为受保护进程的主流反调试方案):** 消耗的系统资源较少 几乎不影响受保护的进程性能 可以轻易地阻止其他进程调试受保护的进程 **缺点:** 实现比较复杂 **实现:** 核心ptrace函数 进程的信号机制 **注意:** 进程暂停状态比较多 **暂停状态** **signal-delivery-stop状态:**调试器和被调试进程之间的关系 **group-stop状态(难):**sigcont信号 同时满足两个条件:进程/线程处于被调试状态;被调试进程/线程收到了暂停信号-->重置为0 sigstop sigtstp sigttin sigttou **syscall-stop状态** **ptrace-event-stop状态** **反-反调试方法:** 让父进程不fork 把while函数循环去掉 不能调试父进程,但是可以调试子进程,配合双IDA调试,挂起子进程 **下图可以明确表示出self-debugging反调试特点:**  fork是一个函数,fork函数fork出一个子进程来调试自己,那么别的函数就无法进程调试了,那么通过调试fork出来的子进程从而调试父进程。 **同一时刻,一个进程只能被一个进程附加(调试)** 用的比较少,还没进程名称检查用的多 ### 2、self-debugging反调试案例详解 将测试文件debugger和self-debugging上次到测试机 ```php adb push C:\test\debugger data/local/tmp adb push C:\test\self-debugging data/local/tmp ```  上传成功后进行赋权 ```php chmod 777 debugger self-debugging ```  找个进程调试,我们选择nfc ```php ps | grep com. ```  nfc进程的PID是3251,我们通过debugger程序开始调试 ```php ./debugger 3251 ```  前面见过如果一个进程被调试,他的TracerPid会发生什么变化?查看下: ```php cat /proc/3251/status ```  此时TracerPid变化为5574,5574是什么呢 ```php ps | grep 5574 ```  可以看到5574是debugger程序的PID,那么结论就是,TracerPid会变成调试器的PID 运行self-debugging:  可以看到main pid为5636是主进程的PID child pid为5637是子进程的PID 这是我在用debugger程序去调试self-debugging测试一下 ```php ./debugger 5636 ```  ```php PTRACE_ATTACH: Operation not permitted ``` 不允许操作,可以看到这就是验证了之前讲的,一个进程只能被一个进程附加(调试)。那么怎么办呢 **如果一个主进程不能被附加,就附加他的子进程即可。** ```php ./debugger 5637 ```  这时候就可以了,这就是self-debugging反调试的流程和原理,绕过self-debugging直接附加在他的子进程即可。 六、实战详解分析-Java层反调试 ----------------- ```php JDWP协议 JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和被调试的 Java 虚拟机(target vm)之间的通信协议 安卓程序动态调试条件(两个满足之一) 1、在AndroidMainfest.xml中,application标签下,Android:debuggable=true 2、系统默认调试,在bulid.prop(boot.img),ro.debugable=1 Android SDK中有android.so.debug类提供了一个isDebuggerConnected方法,用于判断JDWP调试器是否正在工作。 ``` java层反调试基于以上两个原理执行 ### 1、静态分析-实战案例 jadx开打测试apk  找到Oncreate  isDebuggerConnercted方法,用于判断JDWP调试器是否正在工作。  Debug.isDebuggerConnected()获取一个值进行比较,如果为真,就进行加载 loadLibrary();库。  所以会根据isDebuggerConnected进行一个判断,只要符合条件成立就执行if里面的逻辑,不成立不加载加载 loadLibrary库。这就是在java层进行反调试。也能用来保护代码。 **那如何绕过判断JDWP调试器呢?** 使用AndroidKiller反编制apk安装包:  遇到加壳先不管,加壳后期会教,主要用该APK熟悉java层反调试的逻辑,以及如何修改的思路。  AndroidKiller反编译后,直接工程搜索: isDebuggerConnected  通过工程搜索找到smile代码处,那么可看到有判断条件,绕过的方法很多,将if-nez修改为if-eqz,或者删除,注释或者该判断条件等等。 七、实战案例分析 -------- ### 案例一:静态分析-AntiDebug  **拿到一个apk第一步先去查壳,第二步adroidkiller查看没有签名,然后就可以逆向破解逻辑了** jadx反编译antiDebug.apk,并查询onCreate方法   System.loadLibrary("antidebug");直接加载antidebug,那么说明逻辑在so库里面,ida分析 找到so文件,使用IDA打开   滴入IDA后,第一步搜索静态注册找到jni\_OnLoad函数  查看伪C代码  两个赋值之后进行if判断,if判断用了或运算符,就是括号里面四个参数有一个成立if条件成立  那么只要找到四个参数的返回值进行修改,让他们返回值整理翻一个一个假即可 **anti\_time()方法分析** 1)先查anti\_time(),双击函数名称进入  首先定义结构体类型,时间类型timeval, 定义v0=getpid,然后调用了同一个函数gettimefday传入两个不同的值。 v1=tv.tv\_sec - v3.tv\_sec;:通过传入不同的参数调用tv\_sec做差值获取到v1,如果v1小于等于1返回0,否则就kill进程。这就是一个简单的时间测试,脱壳也会在之后教学。 **anti\_breakpoint()方法分析**  目的是想要返回值不触发,只需要里面函数的返回值都为0 即可,来分析一下逻辑,这里很多if嵌套,if在嵌套while等等,那么最终不执行return 1,可以在很多if中进行修改判断条件即可,方法很多。 **anti\_pthread()方法分析**  pthread\_self创建子线程 pipe(&pipefd); pipe是管道的意思,意思是是实现进程通信。 pthread\_create创建线程,传入四个参数,查看第三个参数:anti\_thread  最后都是return 0这里就是获取一个线程。 ### 案例二:动态分析 上面通过静态的方式简单熟悉了代码,现在通过动态分析反调试如何绕过该方法。 #### 1、环境调试 **1、安装apk** ```php adb install C:\test\AntiDebug.apk ```  **2、上传android\_server** 利用adb将android\_server文件传送到真机下指定的文件目录内(android\_server文件在IDA\\dbgsrv目录下) ```php adb push C:\test\android_server data/local/tmp ```  可以去android目录下面去看看是否成功 ```php adb devices adb shell su cd /data/local/tmp/ ls -al ```  将android\_server改名为test,并赋予777权限给test ```php mv android_server test chmod 777 test ```  **3、启动环境** 然后直接运行test,设置端口为22222 ```php ./test -p22222 ```  进行将22222端口转发到电脑端 ```php adb forward tcp:22222 tcp:22222 ```  **4、挂起程序** 在jadx中打开apk,MainActivity.xml中有标出  ```php com.qianyu.antidebug.MainActivity ``` 挂起程序 ```php adb shell am start -D -n com.qianyu.antidebug/.MainActivity ```  安卓启动的端口是23946 开启后再次打开另外一个CMD窗口,将安卓上面的23946端口转发到win电脑上 ```php adb forward tcp:23946 tcp:23946 ```  接下接下来就是要调试APK安装到真机上 ```php adb install E:\IDA7.0\test\javandk1.apk ```  **5、配置DDMS**  **6、配置IDA**  然后勾选三项配置   然后F9进入run状态  加载so文件 ```php jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600 ```  再次run  libantidebug.so就加载进来了,这样就可以开始IDA动态分析了 #### 2、动态调试分析 在Modules找到libantidebug.so的JNI-onload:  在JNI\_onload下断点  F9执行  可以看到红色变为蓝色后,程序就运行到了下断点出。 此时进来后看到状态寄存器T=1:  说明他这里面都是thumb模式指令。  0 2 4 6 8 A,都是每次增加两位 接下来进行动静结合深入理解 通过解压apk,在libs下找到so库文件,丢入IDA打开JNI\_onload,并按F5查看伪c代码   这四个函数和反调试有关,来找一个函数位置,复制到汇编代码  接下来和动态一起从上往下依次分析,R3是什么  直接F8过来,R3是GetEnv,继续步入  可看到BLX R3就是源码中的GetEnv,if条件如果Env获取成功的话就会执行后面的三个函数  通过前面就知道,这里三个函数会在这里进行反调试,如何跳过不执行呢? 同步PC寄存器  接下来吧三个函数NOP掉即可!F2修改为00后,F2保存  操作完成后可以看到三个函数就没有了。 查看下registerNative,双击进入    此时BL R5里面有四个参数,遇到registerNative共四个参数,只需要看第三个参数即可,这里有R5寄存器有四个参数,可以跳转到R5查看:F4  这时候F4跳转过来后,查看第三个参数R2点击箭头进入  可以看到识别地址后什么也没有,前面是没有分析该函数源码,查看源码  在c文件中jni\_onload充当什么作用呢? main入口函数: 在java层要使用loadLibrary加载so库,loadLibrary会便利so库,就是以main做为入口函数。 所以分析的C文件是要从JNI\_Onload这里开始分析:  可以看到JNINativeMethod nativeMethod\[\]={};,这里为空,什么都没做!返回为空。那么这时候就绕过了反调试保护。 八、总结 ---- ```php 本章节从静态调试和动态调试出发,详解解读了文件检测、端口检测、进程名称检测、定时轮询检测、self-debugging反调试,JDWP协议反调试。 关键文件检测:对特定目录下的特定文件名称进行检测,如:android_server等,如果存在,将结束程序。 调试端口检测:对正在运行的特定端口进行检测,如:23946等,如果存在,将结束程序。 进程名称检测:对正在运行的特定进程名称进行检测,如:android_server等,如果存在,将结束程序。 轮循检测:启动一个循环,定时对TracerPid的值等其他特点进行检测,如果发生被调试的特征的变化,将结束进程。 self-debugging反调试:利用一个进程只能被调试一次的特点,创建一个子进程用来对自身进行调试,让其他调试程序无法调试。 JDWP协议反调试:通过一个isDebuggerConnected方法,用于判断JDWP调试器是否正在工作,如果存在,将结束程序。 ```
发表于 2021-10-19 18:04:09
阅读 ( 9693 )
分类:
漏洞分析
0 推荐
收藏
1 条评论
miracles
2022-06-01 15:06
能不能分享一下项目的案例
请先
登录
后评论
请先
登录
后评论
嗯嗯呐
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!