• Android反调试笔记


    1)代码执行时间检测

    通过取系统时间,检测关键代码执行耗时,检测单步调试,类似函数有:time,gettimeofday,clock_gettime.

    也可以直接使用汇编指令RDTSC读取,但测试ARM64有兼容问题。

    time_t t1, t2;
    time (&t1);
    /* Parts of Important Codes */
    time (&t2);
    if (t2 - t1 > 2) {
        puts ("debugged");
    }
    

    2)检测 procfs 文件系统变化

    进程的状态信息能通过 procfs 系统反馈给用户空间,调试会使进程状态发生变化:

    char file [PATH_LEN];
    char line [LINE_LEN];
    
    snprintf (file, PATH_LEN-1, "/proc/%d/status", pid);	
    FILE *fp = fopen (file, "r"); 
    while (fgets (line, LINE_LEN-1, fp)) {
    		if (strncmp (line, "TracerPid:", 10) == 0) {
    			if (0 != atoi (&line[10])) {
    				/* encrypt random .TEXT code */
    			}
    			break;
    		}
    }
    fclose (fp);
    

    类似可以检测的接口还有:

    /proc/pid/status
    /proc/pid/task/pid/status
    /proc/pid/stat
    /proc/pid/task/pid/stat
    /proc/pid/wchan
    /proc/pid/task/pid/wchan
    

    3)利用信号机制

    ARM程序下断点,调试器完成两件事:

    1. 保存目标地址处指令
    2. 将目标地址处指令替换成断点指令
    指令集指令
    Arm 0x01, 0x00, 0x9f, 0xef
    Thumb 0x01, 0xde
    Thumb2 0xf0, 0xf7, 0x00, 0xa0

    当命中断点时,系统产生SIGTRAP信号,调试器收到信号后完成下面操作:

    1. 恢复断点处原指令
    2. 回退被跟踪进程的当前PC

    这时当控制权回到被调试程序时,正好执行断点位置指令。这就是 ARM 平台断点的基本原理。

    可以看到,断点是通过处理 SIGTRAP 信号来实现的,假如我们自己注册 SIGTRAP 的信号处理函数,并在程序中主动执行中断指令触发中断。

    在中断处理函数中,NOP 掉断点指令,程序可正常执行。但在调试状态下,调试器遇到断点指令时,会去恢复原先指令,由于不是调试器下的断点,所以恢复会失败,而调试器会继续第2步操作,回退PC寄存器,程序会在此处无限循环。

    4)软件断点检测

    断点会替换内存中原有指令,因此通过检测内存中的断点指令,可以检测调试:

    #include <stdlib.h>
    #include <stdio.h>
    #include <elf.h>
    #include <string.h>
    #include <unistd.h>
    #include <dlfcn.h>
    
    void checkBreakPoint ();
    unsigned long getLibAddr (const char *lib);
    
    #define LOG_TAG "ANTIDBG_DEMO"
    
    #include <android/log.h>
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    
    int main ()
    {
    	dlopen ("./libdemo.so", RTLD_NOW);
    	sleep (60);
    	checkBreakPoint ();
    
    	return 0;
    }
    
    unsigned long getLibAddr (const char *lib) 
    {
    	puts ("Enter getLibAddr");
    	unsigned long addr = 0;
    	char lineBuf[256];
    	
    	snprintf (lineBuf, 256-1, "/proc/%d/maps", getpid ());
    	FILE *fp = fopen (lineBuf, "r");
    	if (fp == NULL) {
    		perror ("fopen failed");
    		goto bail;
    	}
    	while (fgets (lineBuf, sizeof(lineBuf), fp)) {
    		if (strstr (lineBuf, lib)) {
    			char *temp = strtok (lineBuf, "-");
    			addr = strtoul (temp, NULL, 16);
    			break;
    		}
    	}
    bail: 
    	fclose(fp);
    	return addr;
    }
    
    void checkBreakPoint ()
    {
    	int i, j;
    	unsigned int base, offset, pheader;
    	Elf32_Ehdr *elfhdr;
    	Elf32_Phdr *ph_t;
    
    	base = getLibAddr ("libdemo.so");
    	if (base == 0) {
    		LOGI ("getLibAddr failed");
    		return;
    	}
    
    	elfhdr = (Elf32_Ehdr *) base;
    	pheader = base + elfhdr->e_phoff;
    
    	for (i = 0; i < elfhdr->e_phnum; i++) {
    		ph_t = (Elf32_Phdr*)(pheader + i * sizeof(Elf32_Phdr)); // traverse program header
    
    		if ( !(ph_t->p_flags & 1) ) continue;
    		offset = base + ph_t->p_vaddr;
    		offset += sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) * elfhdr->e_phnum;
    		
    		char *p = (char*)offset;
    		for (j = 0; j < ph_t->p_memsz; j++) {
    			if(*p == 0x01 && *(p+1) == 0xde) {
    				LOGI ("Find thumb bpt %p", p);
    			} else if (*p == 0xf0 && *(p+1) == 0xf7 && *(p+2) == 0x00 && *(p+3) == 0xa0) {
    				LOGI ("Find thumb2 bpt %p", p);
    			} else if (*p == 0x01 && *(p+1) == 0x00 && *(p+2) == 0x9f && *(p+3) == 0xef) {
    				LOGI ("Find arm bpt %p", p);
    			}
    			p++;
    		}
    	}
    }
    

    5)inotify 文件系统监控

    inotify 是一个内核用于通知用户态文件系统变化的机制,当文件被访问,修改,删除等时用户态可以快速感知。

    1.使用 inotify_init() 初始化一个 inotify 实例并返回文件描述符,每个文件描述符都关联了一个事件队列:

    int fd = inotify_init ();
    

    2.拿到这个文件描述符后下一步就告诉内核,哪些文件发生哪些事件时你得通知我,通过函数 inotify_add_watch 实现:

    int wd = inotify_add_watch (fd, path, mask);
    

    第一个参数即 inotify_init 返回的文件描述符,path 表示关注的目标路径,可以是文件目录等。mask 表示关注的事件的掩码,如 IN_ACCESS 代表访问,IN_MODIFY 代表修改等

    相应的,可以通过 inotify_rm_watch 来删除一个watch:

    int ret = inotify_rm_watch (fd, wd);
    

    这样,每当监视的文件发生变化时,内核便给 fd 关联的事件队列里面塞一个文件事件。文件事件用一个 inotify_event 结构表示,可以通过 read 来读取:

    struct inotify_event {
            __s32           wd;             /* watch descriptor */
            __u32           mask;           /* watch mask */
            __u32           cookie;         /* cookie to synchronize two events */
            __u32           len;            /* length (including nulls) of name */
            char            name[0];        /* stub for possible name */
    };
    
    size_t len = read (fd, buf, LEN);
    
    • 通过监视 /proc/pid/maps 文件的打开事件,可防针对 360 加固的 dump 脱壳

    • 文件变化与事件触发非必然联系,例如 /proc/pid/status 中的 TracerPid 值在被调试时是变化的,但其变化没有事件发生,原因未知可能与 inofity 的内核实现有关

    6)ptrace()

    ptrace() 是 Linux 的一个系统调用,也是 Linux 下 gdb 等调试器实现的基础。它提供了 Linux 下一个进程跟踪另一个进程寄存器、内存等的能力。

    由于 ptrace() 到一个线程后,任何信号都将导致线程STOP 并将控制权交由调用者 ,因此如果利用每个线程只能有一个ptrace跟踪 来防止附加的话,需要处理好这个关系:

    while (waitpid (g_childPid, &stat, 0) ) {
    	if (WIFEXITED (stat) || WIFSIGNALED(stat)) {
    		XXX_DEBUG_LOG ("waitpid : child died
    ");
    		exit (11);
    	}
    	ptrace (PTRACE_CONT, g_childPid, NULL, NULL);
    }
    

    7)多进程的反调试实现

    写了份 demo : GITHUB

  • 相关阅读:
    andorid jar/库源码解析之Butterknife
    JavaScript DOM 鼠标拖拽
    JavaScript JSON 与 AJAX
    JavaScript DOM 事件模型
    JavaScript DOM 样式操作
    JavaScript DOM 常用尺寸
    JavaScript 日期与计时器
    JavaScript DOM 基础
    JavaScript 数组
    JavaScript 对象拷贝
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864005.html
Copyright © 2020-2023  润新知