Android反调试笔记
2016-12-29 23:46
246 查看
转自:https://my.oschina.net/cve2015/blog/734381
也可以直接使用汇编指令RDTSC读取,但测试ARM64有兼容问题。
类似可以检测的接口还有:
保存目标地址处指令
将目标地址处指令替换成断点指令
当命中断点时,系统产生SIGTRAP信号,调试器收到信号后完成下面操作:
恢复断点处原指令
回退被跟踪进程的当前PC
这时当控制权回到被调试程序时,正好执行断点位置指令。这就是 ARM 平台断点的基本原理。
可以看到,断点是通过处理 SIGTRAP 信号来实现的,假如我们自己注册 SIGTRAP 的信号处理函数,并在程序中主动执行中断指令触发中断。
在中断处理函数中,NOP 掉断点指令,程序可正常执行。但在调试状态下,调试器遇到断点指令时,会去恢复原先指令,由于不是调试器下的断点,所以恢复会失败,而调试器会继续第2步操作,回退PC寄存器,程序会在此处无限循环。
1.使用 inotify_init() 初始化一个 inotify 实例并返回文件描述符,每个文件描述符都关联了一个事件队列:
2.拿到这个文件描述符后下一步就告诉内核,哪些文件发生哪些事件时你得通知我,通过函数 inotify_add_watch 实现:
第一个参数即 inotify_init 返回的文件描述符,path 表示关注的目标路径,可以是文件目录等。mask 表示关注的事件的掩码,如 IN_ACCESS 代表访问,IN_MODIFY 代表修改等
相应的,可以通过 inotify_rm_watch 来删除一个watch:
这样,每当监视的文件发生变化时,内核便给 fd 关联的事件队列里面塞一个文件事件。文件事件用一个 inotify_event 结构表示,可以通过 read 来读取:
通过监视
文件变化与事件触发非必然联系,例如
由于 ptrace() 到一个线程后,任何信号都将导致线程STOP 并将控制权交由调用者 ,因此如果利用每个线程只能有一个ptrace跟踪 来防止附加的话,需要处理好这个关系:
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程序下断点,调试器完成两件事:保存目标地址处指令
将目标地址处指令替换成断点指令
指令集 | 指令 |
---|---|
Arm | 0x01, 0x00, 0x9f, 0xef |
Thumb | 0x01, 0xde |
Thumb2 | 0xf0, 0xf7, 0x00, 0xa0 |
恢复断点处原指令
回退被跟踪进程的当前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\n"); exit (11); } ptrace (PTRACE_CONT, g_childPid, NULL, NULL); }
7)多进程的反调试实现
写了份 demo : GITHUB相关文章推荐
- Android的调试原理--学习笔记
- android 学习笔记(五) 调试相关 5.2 在android真机中建一个ssh服务器
- Android 学习笔记——利用JNI技术在Android中调用、调试C++代码
- Android 学习笔记——利用JNI技术在Android中调用、调试C++代码
- Android 学习笔记——利用JNI技术在Android中调用、调试C++代码
- Android的调试原理--学习笔记
- Android调试笔记
- android WIFI/BT调试笔记
- Android开发笔记:显示调试信息
- Android开发学习笔记6--安卓程序调试方法
- android 学习笔记(五)调试相关 5.3 在android获取root权限
- Android的调试原理--学习笔记
- android jni ndk-gdb调试笔记
- Android 学习笔记——利用JNI技术在Android中调用、调试C++代码
- Android笔记之:App调试的几个命令的实践与分析
- android 学习笔记(五) 调试相关 5.1 android使用wifi进行程序调试
- android 学习笔记(五)调试相关 5.6 AndroidApp定位和规避内存泄露方法研究
- android 零星调试笔记
- android TP驱动移植调试笔记
- Android笔记:真机调试无法输出Log 信息的问题