Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android反調試筆記

Android反調試筆記

編輯:關於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\n");
        exit (11);
    }
    ptrace (PTRACE_CONT, g_childPid, NULL, NULL);
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved