常见反调试整理

参考链接

http://www.evil0x.com/posts/26301.html

ptrace me

ptrace me , 占住调试的坑,让第三方工具无法调试。
解决就是Hook ptrace 过滤掉 ptrace me 的操作。

时间相关反调试

  1. 通过计算某部分代码的执行时间差来判断是否被调试。
  2. 在Linux内核下可以通过timegettimeofday,或者直接通过sys call 来获取当前时间。
  3. 还可以通过自定义SIGALRM信号来判断程序运行是否超时。

检测关键文件

/proc/pid/status、/proc/pid/task/pid/status

TracerPid字段写入调试进程的pid

/proc/pid/stat、/proc/pid/task/pid/stat

调试状态下/proc/pid/stat、/proc/pid/task/pid/stat文件中第二个字段是t(T):

/proc/pid/wchan、/proc/pid/task/pid/wchan

若进程被调试,也会往/proc/pid/wchan、/proc/pid/task/pid/wchan文件中写入ptrace_stop

检测端口号

android_server默认监听23946端口,
可以通过检测/proc/net/tcp文件,或者直接system执行命令netstat -apn等

检测android_server、gdb、gdbserver

可以通过打开/proc/pid/cmdline、/proc/pid/statue等文件来获取进程名。当然,这种检测方法非常容易绕过――直接修改android_server、gdb、gdbserver的名字即可。

signal

在反调试中最常见的要数SIGTRAP信号了,SIGTRAP原本是调试器设置断点时发出的信号,为了能更好的理解SIGTRAP信号反调试,先让我们看看一下调试器设置断点的原理:

和x86架构类似,arm架构下调试器设置断点先要完成两件事:

  1. 保存目标地址上的数据
  2. 将目标地址上头几个字节替换成arm/thumb下的breakpoint指令

Arm架构下各类指令集breakpoint机器码如下:

指令集 Breakpoint机器码(little endian)
Arm 0x01, 0x00, 0x9f, 0xef
Thumb 0x01, 0xde
Thumb2 0xf0, 0xf7, 0x00, 0xa0

调试器收到SIGTRAP信号后,会继续完成以下几件事:

  1. 在目标地址上用原来的指令替换之前的breakpoint指令
  2. 回退被跟踪进程的当前pc值

如果我们在程序中间插入一条breakpoint指令,而不做其他处理的话,操作系统会用原来的指令替换breakpoint指令,然而这个breakpoint是我们自定义插入的,该地址上并不存在原指令,所以操作系统就跳过这个步骤,进入下一步回退pc值,即breakpoint的前一条指令。这就造成了无限循环,达到反调试的效果。

char dynamic_ccode[] = {0x1f,0xb4, //push {r0-r4}
                        0x01,0xde, //breakpoint
                        0x1f,0xbc, //pop {r0-r4}
                        0xf7,0x46};//mov pc,lr

char *g_addr = 0;

void my_sigtrap(int sig){

    char change_bkp[] = {0x00,0x46}; //mov r0,r0
    memcpy(g_addr+2,change_bkp,2);
    __clear_cache((void*)g_addr,(void*)(g_addr+8)); // need to clear cache
    LOGI("chang bpk to nop/n");

}

void anti4(){//SIGTRAP

    int ret,size;
    char *addr,*tmpaddr;

    signal(SIGTRAP,my_sigtrap);

    addr = (char*)malloc(PAGESIZE*2);

    memset(addr,0,PAGESIZE*2);
    g_addr = (char *)(((int) addr + PAGESIZE-1) & ~(PAGESIZE-1));

    LOGI("addr: %p ,g_addr : %p/n",addr,g_addr);

    ret = mprotect(g_addr,PAGESIZE,PROT_READ|PROT_WRITE|PROT_EXEC);
    if(ret!=0)
    {
        LOGI("mprotect error/n");
        return ;
    }

    size = 8;
    memcpy(g_addr,dynamic_ccode,size);

    __clear_cache((void*)g_addr,(void*)(g_addr+size)); // need to clear cache

    __asm__("push {r0-r4,lr}/n/t"
            "mov r0,pc/n/t"  //此时pc指向后两条指令
            "add r0,r0,#4/n/t"//+4 是的lr 地址为 pop{r0-r5}
            "mov lr,r0/n/t"
            "mov pc,%0/n/t"
            "pop {r0-r5}/n/t"
            "mov lr,r5/n/t" //恢复lr
    :
    :"r"(g_addr)
    :);

    LOGI("hi, i'm here/n");
    free(addr);

}

在代码中主动触发breakpoint指令,然后在自定义SIGTRAP handle中将breakpoint替换成nop指令,于是程序可以正常执行完毕。

Linker

可使用 r_debug-r_brk 来触发异常,其原理即是用到了linker中一些调试特性

struct r_debug {
    int32_t r_version;
    link_map_t* r_map;
    void (*r_brk)(void);
    int32_t r_state;
    uintptr_t r_ldbase;
};  

r_debug是以静态变量的形式存在于linker中,其初始化代码如下:\
static r_debug _r_debug = {1, NULL, &rtld_db_dlactivity, RT_CONSISTENT, 0};

在初始化时,r_debug中的r_brk函数指针被初始化成了rtld_db_dlactivity函数,该函数只是一个空的桩函数:

/*
 * This function is an empty stub where GDB locates a breakpoint to get notified
 * about linker activity.  It canʼt be inlined away, can't be hidden.
 */
extern "C" void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity() {
}

没调试下,该函数即为空函数,而在调试状态下会将该函数的内容改写为相应指令集的breakpoint指令。
所以先注册自己的signal函数处理breakpoint异常(SIGTRAP),然后在运行时调用该函数,即可触发自定义SIGTRAP的接管函数。
而动态调试时,SIGTRAP会先被调试器接收,这样不仅能迷惑调试器,还能在自定义接管函数中做一些tricky的事。

利用Inotify机制防止dump

防止dump
利用Inotify机制,对/proc/pid/mem和/proc/pid/pagemap文件进行监视。inotify API提供了监视文件系统的事件机制,可用于监视个体文件,或者监控目录。具体原理可\
参考1:http://man7.org/linux/man-pages/man7/inotify.7.html \
参考2: http://burningcodes.net/%E9%80%9A%E8%BF%87inotify%E5%AE%9E%E7%8E%B0%E5%8F%8D%E8%B0%83%E8%AF%95/

IsDebuggerConnected 反 jdb 调试

直接 hook libdvm.so _Z25dvmDbgIsDebuggerConnectedv 返回 0 就行。
– 注意: am start -D -n / 挂起运行时 dvmIsDebuggerConnected 需要先放行。

_Z12dvmDbgActivev 反 jdb 调试

jdb 调试后过调用 _Z12dvmDbgActivev 上层函数,此函数被引用,修改此函数直接引发异常,可起到反调试。

解决是用该函数的正常二进制字节恢复被 hook 的地方即可。

发表评论