实践还少,暂且没遇到,不过先来打个预防针,熟悉下。
首先什么是反调试:防止程序被调试,保护代码。一种加固手段。
万事无绝对,不能阻止,但能缓解,让破解者消费精力。一般的,该手段会与加壳并用。
如何来反调试呢?大方向有两类:检测,攻击。
1. 检测
检测程序是否被调试,若是的话做出一些“反”措施,例如退出,跳到其他位置等。
2. 攻击
让调试器崩溃,阻止调试。
1. 一般的,大多通过另启进程或线程。
基础知识:一个进程最多只能被一个进程ptrace:调试状态下,linux向/proc/pid/status写入一些信息,其中的tracerpid即为调试进程的pid。
于是,反调试策略:自己trace自己来防止其他进程附加进来调试:
逆向方法:在jni_load前将对应的增加调试方法去掉/nop掉。
2. 检测tracerpid值,若不为0则代表被调试,于是可以选择退出:
为了更好的加固,一般的会增加线程机制循环监测该值。
逆向方法:以debug模式启动,在jni_load处下断点,找到以上检测tracerpid值的函数将其nop掉;或者静态分析jni_load找到以上方法将其去掉。
【以上两者都是在jni_load函数处做文章,why?---需要了解下加载机制。。:不仅要知其然!更要知其所以然!】
3.检测android_server端口。
一般的,调试时启动android_server后,默认的监听端口是23946(可以在启动android_server后看到监听的是哪个端口号)。故可以检测 该端口号来反调试。
[在使用android_server时,不要看一些别人的破解思路使用的一些信息例如端口,应该根据自己的情况先做好判断!]
使用的一些命令:cat /proc/net/tcp[检测连接信息,发现端口。注意这里的表示是16进制需要转化为10进制来看]
图
next,netstat -apn[看端口监听状态]
反调试手段:若此时特定端口23946[监听端口号]为监听状态则退出:
逆向方法:换端口。
[需要熟悉下windows命令行一些命令..]
4.检测android_server关键字及文件目录:
运行时,进程名会写在/proc/[pid]/cmdline文件中,若在该文件中检测到该文件名则说明被调试。或者在特定目录下检测android_server文件,该文件一般放在/data/或者/data/local/tmp目录下,若存在则程序退出。
使用如下来获得pid,然后获得进程名,如下图
图
检测代码:
逆向方法:改名字,放在其他目录下。
5.检测在调试状态下的软件断点。
需要了解下调试器下断点调试的原理。
下断点利用的是ptrace函数(ptrace(2) - Linux manual page )。调试器下断点时主要做两件事:保存目标地址的数据;将目标地址的头几个字节替换为break point指令。当命中断点时触发breakpoint,向系统发送sigtrap信号,调试器收到后会回退被跟踪进程的当前pc值,当控制权回到原进程时,pc指向断点所在位置。(调试器工作原理及断点原理又是博大的一部分,之后另起一篇吧)
[另附安卓虚拟机调试http://www.droidsec.cn/android虚拟机调试器原理与实现/ ]
故凡调试策略为:遍历so文件中的可执行字段,查找是否出现breakpoint指令。
首先读取elf文件在内存中的地址:
然后检测有无arm, thumb, thumb2的断点指令,若有则kill程序。
逆向方法:一般这些函数在jni_onload,com_java_**函数中调用,将其nop掉。
第六种:使用Inotify对文件进行监控
我们知道在我们的动态调试的过程中,一般会查看调试进程的虚拟地址空间或者是dump内存,这时候就会涉及到对于文件的读写以及打开的权限,这时候如果能够检测它们的变化,这样岂不是很美妙?
然而偏巧的是在Linux下inotify就可以实现对文件系统事件的打开,读写的监管。如果通过Inotify监管这些,收到事件的变化,那我们就Kill掉进程。这里介绍下面主要会用到的几个常用的api:
1. inotify_init:用于创建一个 inotify 实例的系统调用,并返回一个指向该实例的文件描述符。Int fd=inotify_init();
2. inotify_add_watch:增加对文件或者目录的监控,并指定需要监控哪些事件。
int wd =inotify_add_watch(fd,path,mask);
这里path表示你要监听的文件目录,mask表示监听事件掩码,就是监听读了写了还是打开,删除等等。
3. read:读取包含一个或者多个事件信息的缓存。
主要是用来读取监听目录文件事件发生变化时的事件队列。
4. inotify_rm_watch:从监控列表中移出监控项目。
这个很好理解就是删除一个watch。
5. select(intnfds,fd_set* readset, fd_set* writeset, fe_set* exceptset, struct timeval* timeout):
select的第一个参数是文件描述符集中要被检测的比特数, 这个值必须至少比待检测的最大文件描述符大1;参数readfds指定了被读监控的文件描述符集。
这里由于篇幅要求,介绍的不详细,读者可以自行百度。
接着看实现部分:
第七种:调试时候代码执行时间差异检测
我们知道如果在程序执行的时候关键代码的前后的时间差异肯定是要比在动态调试的时候对应的关键代码的前后的时间差异要小的多,因此我们可以获取系统的时间,计算前后的差异,如果超出一般正常情况下的设定值,我们就认为此处的代码正在被调试,这时候就可以选择退出。或者是做出一些其他的事,逻辑比较简单,那以下是代码关键点的实现部分:
当然这里最重要的是获取系统的时间,获取系统时间的办法有很多,网上也有很多,可以自行查阅,这里介绍一种:
达到的效果是让程序退出,起到反调试的作用。
当然这个方法可以放在一个关键的逻辑地方,这里起到的效果就是检测到当前程序正在被调试,那我们下一步可以可以就像上面说的让程序退出。当然更高级的方法是不让它退出,读者可以自己YY。
第八种:Dalvik虚拟机内检测调试器函数
由于Dalvik自带这些内部检测调试器的代码,大致上是在被调试以后,改变那个调试器的状态字段,既然反调试就是第一步想办法检测出它正在被调试,第二步是真正的“反”,我们可以不动声色的去“反”,读者可以想办法YY,这里不多叙述。
总结篇:
在这里看到的反调试都是以检测手段为主,也是比较常用的思路,当然也是一种低级反调试,因为容易爆露反调试点,只有在做反调试系统的时候这些手段交杂多用,才能给逆向者增加一定的成本开销。
原文链接:https://blog.csdn.net/feibabeibei_beibei/article/details/60956307