美文网首页
TracerPid反调试

TracerPid反调试

作者: Sharkchilli | 来源:发表于2020-11-06 22:57 被阅读0次

    前言

    在很多逆向分析的时候,我们看字符串信息经常能看到TracerPid等关键字,这是一个反调试常用的手段。这次我们就来实现这个检测TracerPid的反调试。

    实验

    使用ps查看我们运行的包pid

    ps | grep "com.shark.jnireflect"
    
    u0_a81    27178 182   915256 42908 ffffffff 4008673c S com.shark.jnireflect
    

    查看本程序的/proc/{PID}/status文件

    cat /proc/27178/status
    
    Name:   hark.jnireflect /*进程的程序名*/
    State:  S (sleeping)
    Tgid:   27178  /*线程组号*/
    Pid:    27178  /*进程pid process id*/
    PPid:   182  /*父进程的pid parent processid*/
    TracerPid:      0  /*跟踪进程的pid*/
    Uid:    10081   10081   10081   10081
    Gid:    10081   10081   10081   10081
    FDSize: 256
    Groups: 50081
    VmPeak:   915256 kB
    VmSize:   885044 kB
    VmLck:         0 kB
    VmPin:         0 kB
    VmHWM:     42936 kB
    VmRSS:     42908 kB
    VmData:    16076 kB
    VmStk:       136 kB
    VmExe:        20 kB
    VmLib:     49292 kB
    VmPTE:       152 kB
    VmSwap:        0 kB
    Threads:        11
    SigQ:   1/12274
    SigPnd: 0000000000000000
    ShdPnd: 0000000000000000
    SigBlk: 0000000000001204
    SigIgn: 0000000000000000
    SigCgt: 00000002000094f8
    CapInh: 0000000000000000
    CapPrm: 0000000000000000
    CapEff: 0000000000000000
    CapBnd: fffffff000000000
    Cpus_allowed:   f
    Cpus_allowed_list:      0-3
    voluntary_ctxt_switches:        109
    nonvoluntary_ctxt_switches:     371
    

    我注释了一些我们常看到的信息,其他的对于我们来说不那么重要

    TracerPid就是调试此进程的pid,在同一时刻只能有一个进程调试本进程

    调试

    使用ida进行调试后在查看TracerPid

    Name:   hark.jnireflect
    State:  t (tracing stop)
    Tgid:   27178
    Pid:    27178
    PPid:   182
    TracerPid:      27519
    Uid:    10081   10081   10081   10081
    Gid:    10081   10081   10081   10081
    FDSize: 256
    Groups: 50081
    VmPeak:   915256 kB
    VmSize:   885044 kB
    VmLck:         0 kB
    VmPin:         0 kB
    VmHWM:     43332 kB
    VmRSS:     43332 kB
    VmData:    16076 kB
    VmStk:       136 kB
    VmExe:        20 kB
    VmLib:     49292 kB
    VmPTE:       152 kB
    VmSwap:        0 kB
    Threads:        11
    SigQ:   1/12274
    SigPnd: 0000000000000000
    ShdPnd: 0000000000000000
    SigBlk: 0000000000001204
    SigIgn: 0000000000000000
    SigCgt: 00000002000094f8
    CapInh: 0000000000000000
    CapPrm: 0000000000000000
    CapEff: 0000000000000000
    CapBnd: fffffff000000000
    Cpus_allowed:   f
    Cpus_allowed_list:      0-3
    voluntary_ctxt_switches:        119
    nonvoluntary_ctxt_switches:     373
    

    可以看到TracerPid有了值,并且State变成了t状态

    使用ps查看调试进程是什么

    ps |grep 27519
    
    root      27519 27074 10488  8696  ffffffff 00000000 S ./android_server
    

    可以看到就是我们的android_server程序

    当我们使用IDA attach程序的时候,在/proc/{PID}/status文件的TracerPid字段会写入调试程序的PID

    也就是说使用TracerPid反调试的原理就是检测这个字段是否为0,为0说明没有被调试,不为0说明正在被调试,检测调试器直接退出就可以达到反调试的效果

    代码实现

    #include <jni.h>
    #include <string>
    #include "antidebug.h"
    
    #define NULL 0
    #define CHECK_TIME 10
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "shark chilli", __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "shark chilli", __VA_ARGS__)
    
    pthread_t id_anti_debug = NULL;
    
    void readStatus() {
        FILE *fd;
        char filename[128];
        char line[128];
        pid_t pid = syscall(__NR_getpid);
        LOGI("PID : %d", pid);
        sprintf(filename, "/proc/%d/status", pid);// 读取proc/pid/status中的TracerPid
        while (1) {
            fd = fopen(filename, "r");
            while (fgets(line, 128, fd)) {
                if (strncmp(line, "TracerPid", 9) == 0) {
                    //TracerPid值
                    int status = atoi(&line[10]);
                    LOGI("########## status = %d, %s", status, line);
                    fclose(fd);
                    syscall(__NR_close, fd);
                    if (status != 0) {
                        LOGI("########## FBI WARNING ##########");
                        LOGI("######### FIND DEBUGGER #########");
                        kill(pid, SIGKILL);
                        return;
                    }
                    break;
                }
            }
            sleep(CHECK_TIME);
        }
    }
    
    void checkAnti() {
        LOGI("Call readStatus...");
        readStatus();
    }
    
    void anti_debug() {
        LOGI("Call anti_debug...");
        //创建线程
        if (pthread_create(&id_anti_debug, NULL, (void *(*)(void *)) &checkAnti, NULL) != 0) {
            LOGE("Failed to create a debug checking thread!");
            exit(-1);
        };
        //线程分离 释放资源
        pthread_detach(id_anti_debug);
    }
    
    extern "C" JNIEXPORT void JNICALL
    Java_com_shark_tracerpidapp_MainActivity_checkTracerPid(
            JNIEnv *env,
            jobject /* this */) {
        anti_debug();
    }
    

    上面的代码我在java层的MainActivity的onCreate中调用了
    这里开启一个线程在线程中一直去查询自己status文件中TracerPid的值,当TracerPid不为0的时候说明有调试,我们调用kill将其杀死就可以了

    使用ida进行调试,断下后输出如下

    11-06 14:17:47.283 14196-14196/com.shark.tracerpidapp I/shark chilli: Call anti_debug...
    11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: Call readStatus...
    11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: PID : 14196
    11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:17:57.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:18:07.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:18:17.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:18:27.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:18:37.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    

    可以看到在没调试前我们的日志一直在循环输出,说明一直在循环检测TracerPid
    将ida运行

    11-06 14:19:17.303 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
    11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 13967, TracerPid: 13967
    11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## FBI WARNING ##########
    11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ######### FIND DEBUGGER #########
    

    我们的ida直接退出了,说明成功了!

    这里我们并没有进入断点后就退出,是在执行的时候才退出的
    我们来改进一下

    
    void readStatus() {
        FILE *fd;
        char filename[128];
        char line[128];
        pid_t pid = syscall(__NR_getpid);
        LOGI("PID : %d", pid);
        sprintf(filename, "/proc/%d/status", pid);// 读取proc/pid/status中的TracerPid
        if (fork() == 0) {
            while (1) {
                fd = fopen(filename, "r");
                while (fgets(line, 128, fd)) {
                    if (strncmp(line, "TracerPid", 9) == 0) {
                        int status = atoi(&line[10]);
                        LOGI("########## status = %d, %s", status, line);
                        fclose(fd);
                        syscall(__NR_close, fd);
                        if (status != 0) {
                            LOGI("########## FBI WARNING ##########");
                            LOGI("######### FIND DEBUGGER #########");
                            kill(pid, SIGKILL);
                            return;
                        }
                        break;
                    }
                }
                sleep(CHECK_TIME);
            }
        } else {
            LOGE("fork error");
        }
    }
    

    很简单就是改成了在子进程里面跑我们的反调试逻辑.
    运行使用ps查看应用

    root@hammerhead:/ # ps |grep "shark"
    u0_a82    14700 182   915140 42784 ffffffff 4008673c S com.shark.tracerpidapp
    u0_a82    14721 14700 875236 28968 c01b9228 40085e84 S com.shark.tracerpidapp
    

    可以看到有两个进程了

    我们再次使用IDA进行调试

    image.png

    选择父进程


    image.png

    直接闪退

    这里有个问题我们的子进程是可以被调试的,也就是可以在子进程中绕过调试或者直接杀死子进程
    再来改进

    void readStatus() {
        FILE *fd;
        char filename[128];
        char line[128];
        pid_t pid = syscall(__NR_getpid);
        LOGI("PID : %d", pid);
        sprintf(filename, "/proc/%d/status", pid);//读取/proc/pid/status中的TracerPid
        if (fork() == 0) {
            int pt = ptrace(PTRACE_TRACEME, 0, 0, 0); //子进程反调试
            if (pt == -1)
                exit(0);
            while (1) {
                fd = fopen(filename, "r");
                while (fgets(line, 128, fd)) {
                    if (strncmp(line, "TracerPid", 9) == 0) {
                        int status = atoi(&line[10]);
                        LOGI("########## status = %d, %s", status, line);
                        fclose(fd);
                        syscall(__NR_close, fd);
                        if (status != 0) {
                            LOGI("########## FBI WARNING ##########");
                            LOGI("######### FIND DEBUGGER #########");
                            kill(pid, SIGKILL);
                            return;
                        }
                        break;
                    }
                }
                sleep(CHECK_TIME);
            }
        } else {
            LOGE("fork error");
        }
    }
    

    上面加上了这段关键代码

     int pt = ptrace(PTRACE_TRACEME, 0, 0, 0); //子进程反调试
            if (pt == -1)
                exit(0);
    

    我们在子进程中使用ptrace,将请求类型设为PTRACE_TRACEME,表示让父进程跟踪自己,而进程在同一时间,只能被一个调试器调试或者跟踪,所以这里就是一个父进程,一个子进程,子进程通过读取父进程的/proc/{PID}/status文件保护父进程不被调试,同时让父进程跟踪自己,保护自己不被调试,如果ptrace失败,说明有调试器已经在调试自己,直接退出

    运行,使用cat查看子进程TracerPid

    root@hammerhead:/ # ps |grep "shark"
    u0_a82    15097 182   915132 42776 ffffffff 4008673c S com.shark.tracerpidapp
    u0_a82    15117 15097 875236 29060 c01b9228 40085e84 S com.shark.tracerpidapp
    root@hammerhead:/ # cat /proc/15097/status |grep "TracerPid"
    TracerPid:      0
    root@hammerhead:/ # cat /proc/15117/status |grep "TracerPid"
    TracerPid:      15097
    root@hammerhead:/ #
    

    上面可以看到,我们的子进程确实被父进程调试了

    image.png

    使用ida调试后效果如上图

    引用

    TracerPid反调试实现与逆向

    Linux 编程中的API函数和系统调用的关系

    相关文章

      网友评论

          本文标题:TracerPid反调试

          本文链接:https://www.haomeiwen.com/subject/xbxzvktx.html