android反调试技术

检测调试相关文件android_server等

int SearchFile(std::string file_path)
{
    int ret = 0;
    // fork进程检测
    std::string command = "cat ";
    command.append(file_path.c_str());
    FILE *fd1 = popen(command.c_str(), "r");
    if(NULL != fd1){
        char m_buffer[10] = {0};
        if(fgets(m_buffer, sizeof(m_buffer), fd1)){
            return -1;
        }
        pclose(fd1);
    }

    // 直接打开
    int fd2 = open(file_path.c_str(), O_RDONLY);
    if(fd2 != -1){
        close(fd2);
        return -1;
    }

    //fd反查
    DIR *dir = opendir("/proc/self/fd/");
    if (dir != NULL) {
        struct dirent *entry = NULL;
        while ((entry = readdir(dir)) != NULL) {
            char buf[0x1000] = {0};
            char filePath[0x1000] = {0};
            snprintf(filePath, sizeof(filePath), "/proc/self/fd/%s", entry->d_name);
            readlinkat(AT_FDCWD, filePath, buf, 0x1000);
            if (NULL != strstr(buf, file_path.c_str())) {
                ret = -1;
                break;
            }
        }
        closedir(dir);
    }
    return ret;
}

这种检测方法很好绕过,直接修改文件名称即可。

检测特定的字段

调试状态下/proc/pid/stat 和/proc/pid/task/pid/stat的第三个字段会变成t

调试状态下/proc/pid/wchan 和/proc/pid/task/pid/wchan的值会变成ptrace_stop

/proc/pid/status文件的TracerPid的值为调试器进程的pid

检测android_server等调试器进程默认端口号

android_server进程默认监听的是23946端口,gdbserver默认监听的是27042端口
通过/proc/net/tcp文件获取正在被监听的端口

int SearchPort()
{
    int ret = 0;
    char m_buffer[0x1000] = {0};
    FILE *fd = fopen("/proc/net/tcp","r");
    if(NULL == fd){
        return -1;
    }
    while(fgets(m_buffer, sizeof(m_buffer), fd)){
        if( strstr(m_buffer, "5D8A") ||
            strstr(m_buffer, "69A2")){
            printf("check debug!\n");
            ret = -1;
            break;
        }
    }
    fclose(fd);
    return ret;
}

android 10之后/proc/net文件系统的访问权限实施了限制,可以直接建立socket连接来确认是否存在端口占用。

inotify监控文件

通过linux提供的inotify来监控文件/proc/pid/mem/proc/pid/pagemap是否被读写,防止一些dump操作。

检测指令间运行时间差

程序正常执行两天指令执行的时间差是很短的,如果被调试的话时间差会大大增加。

扫描内存断点

内存断点是通过修改程序指令实现的,通过遍历内存中的各个段并判断是否为bkpt指令可以发现是否存在内存断点。

bool check_break_point (uint32_t module_base)
{
    Elf32_Ehdr *elf_header;
    uint32_t program_offset, program_header;
    if (module_base == 0) {
        return false;
    }
    elf_header = (Elf32_Ehdr *) module_base;
    program_header = module_base + elf_header->e_phoff;
    for (int i = 0; i < elf_header->e_phnum; i++) {
        Elf32_Phdr *ph_t;
        ph_t = (Elf32_Phdr*)(program_header + i * sizeof(Elf32_Phdr));
        if ( !(ph_t->p_flags & 1) ) continue;
        program_offset = module_base + ph_t->p_vaddr;
        program_offset += sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) * elf_header->e_phnum;
        char *p = (char*)program_offset;
        for (int j = 0; j < ph_t->p_memsz; j++) {
            if(*p == 0x01 && *(p+1) == 0xde) {
                //thumb16
                return true;
            } else if (*p == 0xf0 && *(p+1) == 0xf7 && *(p+2) == 0x00 && *(p+3) == 0xa0) {
                //thumb32
                return true;
            } else if (*p == 0x01 && *(p+1) == 0x00 && *(p+2) == 0x9f && *(p+3) == 0xef) {
                //arm断点
                return true;
            }
            p++;
        }
    }
    return false;
}

主动抛出SIGTRAP异常反调试

程序主动抛出SIGTRAP异常信号,操作系统在得到SIGTRAP信号后会检查是否存在调试器,如果存在调试器会将信号先传递给调试器。这样程序中的异常处理程序就得不到执行,调试器就会陷入死循环。

//利用ida先截获信号的特性进行反调试
//异常处理函数
void my_signal(int sig)
{
    printf("not find debugger!\n");
}
int SetSignal()
{
    //设置异常对应的异常处理函数
    sighandler_t ret = signal(SIGTRAP, my_signal);
    if(SIG_ERR == ret){
        return -1;
    }
    //主动抛出异常
    raise(SIGTRAP);
    return 0;
}

bkpt指令反调试

在程序中malloc申请内存硬编码存放bkpt指令,然后设置异常处理函数来恢复程序执行。如果程序被调试的话操作系统会先将异常信号传递给调试器,异常处理函数就得不到执行,程序无法恢复执行陷入死循环。

//利用bkpt指令反调试
uint32_t pBkpt;
//SIGTRAP信号处理函数
void my_sigtrap(int sig)
{
    //将bkpt指令覆盖填充为arm模式的mov pc,r1指令,执行此指令后返回
    *((uint32_t*)pBkpt) = 0xE1A0F001;
}
int SetBkpt()
{
    pBkpt = (uint32_t)malloc(0x10);
    if(NULL == pBkpt){
        return -1;
    }
    if(mprotect((void*)((uint32_t)pBkpt / PAGE_SIZE * PAGE_SIZE), PAGE_SIZE,PROT_READ|PROT_WRITE|PROT_EXEC)){
        return -1;
    }

    *((uint32_t*)pBkpt) = 0xE1200070;    //填充arm模式的bkpt指令
    signal(SIGTRAP, my_sigtrap);         //设置SIGTRAP异常处理函数

     __asm__ volatile(
            "mov r0, %[pBkpt]\n\t"
            "mov r1,pc\n\t"             //保存返回地址
            "BX r0\n\t"                 //执行bkpt指令
            :                           //在内联汇编代码中修改的变量列表
            :[pBkpt]    "r"(pBkpt)      //在内联汇编代码中使用的变量列表
            : "cc", "r0","r1","pc"      //在内联汇编中使用的寄存器列表
            );
    return 0;
}

rtld_db_dlactivity

linker的rtld_db_dlactivity函数在非调试模式下为空函数,如果程序被调试则会被修改为对应的断点指令,可以对此函数进行检测。

ptrace反调试

因为一个程序只能被一个调试进程调试,所以有其他进程ptrace的话就会失败。可以创建一个守护进程,并通过ptarce轮询附加主进程,如果失败则证明主进程被调试。

int ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
if(ret){
    return -1;
}
return 0;

双进程反调试

  • 可以通过主进程fork子进程,然后子进程ptarce调试父进程并监控父进程所有的线程(ptrace 附加的单位是线程),同时父进程通过进程间通讯(信号量等)检测子进程是否存活,这样其他进程就无法ptrace调试主进程。
    针对上述方法可以ptrace注入子进程并在子进程中调用ptrace PTRACE_DETACH 父进程解除保护,这样就可以正常调试父进程了。针对这种方法可以对双进程保护逻辑进行优化,子进程 ptrace父进程的所有线程,但是需要保留一个守护线程。此守护线程ptrace 附加子进程,这样子进程与父进程都无法被ptrace。https://blog.csdn.net/u011247544/article/details/78248427https://xz.aliyun.com/t/9815

反ida静态分析

  • 因为ida使用的递归下降的反汇编引擎,而这种引擎有个最大的缺点就是无法识别间接跳转指令。在函数中穿插间接跳转指令可以反ida F5分析
int OrderObscure()
{
    __asm__ volatile( \
    "mov r0,pc \n\t" \
    "adds r0,0xc \n\t" \
    "push {r0} \n\t" \
    "pop {r0} \n\t" \
    "bx r0 \n\t"
    :::"r0" \
    );
    
    printf("is ok");
    return 0;
}

上面的代码在函数中使用的间接跳转指令bx r0,ida查看此汇编代码bx r0后的指令是没有识别的。

IDA F5也是无法正确分析的

但是还是ida还是可以正确查看整个函数对应的反汇编代码的

因为ida无法同时解析arm与thumb指令,所以进一步的在代码中硬编码穿插thumb指令可以使ida连汇编代码也无法解析。

//1. 利用代码中穿插BX R0间接跳转指令
//2. 利用代码中混合使用thumb和arm指令
int OrderObscure()
{
    __asm__ volatile( \
    "mov r0,pc \n\t" \
    "adds r0,0xd \n\t" \
    "push {r0} \n\t" \
    "pop {r0} \n\t" \
    "bx r0 \n\t"


    //thhub指令
    ".byte 0x00 \n\t" \
    ".byte 0xBF \n\t" \
    \
    ".byte 0x78 \n\t" \
    ".byte 0x46 \n\t" \
    \
    ".byte 0x02 \n\t" \
    ".byte 0x30 \n\t" \
    \
    ".byte 0x00 \n\t" \
    ".byte 0x47 \n\t" \
    :::"r0" \
    );
    //nop
    //mov r0,pc
    //adds r0,2
    //bx r0

    printf("is ok");
    return 0;
}

ida查看对应的反汇编代码,发现无法正确解析thumb指令集的汇编代码以及后续的代码。

posted @ 2022-10-10 16:33  怎么可以吃突突  阅读(954)  评论(0编辑  收藏  举报