一、序

無論是逆向分析還是漏洞利用,我所理解的攻防博弈無非是二者在既定的某一階段,以高維的方式進行對抗,并不斷地升級維度。比如,逆向工程人員一般會選擇在Root的環(huán)境下對App進行調試分析,其是以root的高權限對抗受沙盒限制的低權限;在arm64位手機上進行root/越獄時,ret2usr利用技術受到PXN機制的約束,廠商從修改硬件特性的高維度進行對抗,迫使漏洞研究者提高利用技巧。

下文將在Android逆向工程方面,分享鄙人早期從維度攻擊的角度所編寫的小工具。工具本身可能已經(jīng)不能適應現(xiàn)在的攻防,“授人以魚不如授人以漁”,希望能夠給各位讀者帶來一些思路,構建自己的分析利器。

二、正

0x00 自定義Loader

早期Android平臺對SO的保護采用畸形文件格式和內容加密的方式來對抗靜態(tài)分析。隨著IDA以及F5插件地不斷完善和增多,IDA已經(jīng)成為了逆向人員的標配工具。正因如此,IDA成為了畸形文件格式的對抗目標?;畏绞綇臏p少文件格式信息到構造促使IDA加載crash的變化正應證了這一點。對此,鄙人研究通過重建文件格式信息的方式來讓IDA正常加載。

在完成編寫修復重建工具不久之后,鄙人在一次使用IDA的加載bin文件時,猛然意識到畸形文件格式的對抗目標是IDA對ELF文件的加載的默認loader。既然防御的假象和維度僅僅在于默認loader,那么以自定義的loader加載實現(xiàn)高維攻擊,理論是毫無敵手的。

那如何來實現(xiàn)IDA自定義loader呢?

以Segment加載的流程對ELF文件進行解析,獲取和重建Section信息(參看上面所說貼子)。
把文件信息在IDA中進行展示,直接調用對應的IDAPython接口

實現(xiàn)加載bin文件的py代碼見文末github鏈接,直接放置于IDA/loaders目錄即可。由于早期少有64位的安卓手機,加載腳本僅支持arm 32位格式,有興趣讀者可以改寫實現(xiàn)全平臺通用。不同ndk版本所編譯文件中與動態(tài)加載無關的Section不一定存在,注釋相應的重建代碼即可。

0x01 Kernel Helper

以APP分析為例,對于加固過的應用通常會對自身的運行環(huán)境進行檢測。比如: 檢測自身調試狀態(tài),監(jiān)控proc文件等。相信各位讀者有各種奇淫技巧來繞過,早期鄙人構建hook環(huán)境來繞過。從維度的角度,再來分析這種對抗。對于APP或者bin文件而言,其僅運行于受限的環(huán)境中,就算exp提權后也只是權限的提升和對內核有一定的訪問控制權。對于Android系統(tǒng)而言,逆向人員不僅能夠拿到root最高權限,而且還可以修改系統(tǒng)的所有代碼。從攻防雙方在運行環(huán)境的維度來看,“魔”比”道“高了不只三丈,防御方猶如板上魚肉。而在代碼維度,防御方擁有源代碼的控制權,攻防處于完全劣勢。隨著代碼混淆和VMP技術的運用,防御方這塊魚肉越來越不好"啃"。

對于基于linux的安卓系統(tǒng)而言,進程的運行環(huán)境和結構是由內核來提供和維護的。從修改內核的維度來對抗,能達到一些不錯的效果。下文將詳述在內核態(tài)dump目標進程內存和系統(tǒng)調用監(jiān)控。

1. 內存DUMP

對內核添加一些自定義功能時,通??梢圆捎脙群蓑寗觼韺崿F(xiàn)。雖然一部分Android手機支持驅動ko文件加載,但內核提供的其他工具則不一定已經(jīng)編譯到內核,在后文中可以看到。nexus系列手機是谷歌官方所支持的,編譯刷機都比較方便,推薦使用。

S1. 編譯內核

為了讓內核支持驅動ko文件的加載,在make memuconfig配置內核選項時,以下勾選:

[*] Enable loadable module support 
    次級目錄所有選項

編譯步驟參看谷歌官方提供的內核編譯步驟。

S2. 驅動代碼

linux系統(tǒng)支持多種驅動設備,這里采用最簡單的字符設備來實現(xiàn)。與其他操作系統(tǒng)類似,linux驅動程序也分為入口和出口。在module_init入口中,對字符設備進行初始化,創(chuàng)建/dev/REHelper字符設備。文末代碼采用傳統(tǒng)的方式對字符設備進行注冊,也可直接使用misc的方式。字符設備的操作方式通過注冊file_operations回調實現(xiàn),其中ioctl函數(shù)比較靈活,滿足實現(xiàn)需求。

定義command ID:

#define CMD_BASE 0xC0000000 #define DUMP_MEM (CMD_BASE + 1) #define SET_PID (CMD_BASE + 2)

構建dump_request參數(shù):

struct dump_request{ 
    pid_t pid; //目標進程 
    unsigned long addr; //目標進程dump起始地址
    ssize_t count; //dump的字節(jié)數(shù) char __user *buf; //用戶空間存儲buf };

在ioctl中實現(xiàn)分支:

case DUMP_MEM:  
    target_task = find_task_by_vpid(request->pid); //對于用戶態(tài),進程通過進程的pid來標示自身;在內核空間,通過pid找到對應的進程結構task_struct
    if(!target_task){
        printk(KERN_INFO "find_task_by_vpid(%d) failed\n", request->pid);
        ret = -ESRCH;        return ret;
    }
    request->count = mem_read(target_task->mm, request->buf, request->count, request->addr);     //進程的虛擬地址空間同樣由內核進程管理,通過mm_struct結構組織

memread其實是對memrw函數(shù)的封裝,mem_rw能夠讀寫目標進程,簡略流程:

static ssize_t mem_rw(struct mm_struct *mm, char __user *buf,  
            size_t count, unsigned long addr, int write)
{
    ssize_t copied;    char *page;

    ...

    page = (char *)__get_free_page(GFP_TEMPORARY); // 獲取存儲數(shù)據(jù)的臨時頁面

    ...    while (count > 0) {        int this_len = min_t(int, count, PAGE_SIZE);          // 將寫入數(shù)據(jù)從用戶空間拷貝到內核空間
        if (write && copy_from_user(page, buf, this_len)) {
            copied = -EFAULT;            break;
        }         // 對目標進程進行讀或寫操作,具體實現(xiàn)參看內核源碼
        this_len = access_remote_vm(mm, addr, page, this_len, write);         // 將獲取到的目標進程數(shù)據(jù)從內核拷貝到用戶空間
        if (!write && copy_to_user(buf, page, this_len)) {
            copied = -EFAULT;            break;
        }
         ...             
    }
    ...
}

內核驅動部分的dump功能實現(xiàn),接著只需在用戶空間訪問驅動程序即可。

// 構造ioctl參數(shù)request.pid = atoi(argv[1]);  
request.addr = 0x40000000;  
request.buf = buf;  
request.count = 1000;// 打開內核驅動int fd = open("/dev/REHelper", O_RDWR);  
// 發(fā)送讀取命令ioctl(fd, DUMP_MEM, &request);  
close(fd);

S3. 測試

文末代碼中,dump_test為目標進程,dump_host通過內核驅動獲取目標進程的數(shù)據(jù)。insmod和dump_host以root權限運行即可。

2. 系統(tǒng)調用監(jiān)控

通常情況下,APP通過動態(tài)鏈接庫libc.so間接的進行系統(tǒng)調用,直接在用戶態(tài)hook libc.so的函數(shù)即可實現(xiàn)監(jiān)控。而對于靜態(tài)編譯的bin文件和通過svc匯編指令實現(xiàn)的系統(tǒng)調用,用戶態(tài)直接hook是不好處理的。道理很簡單,系統(tǒng)調用由內核實現(xiàn),hook也應該在內核。

linux系統(tǒng)的系統(tǒng)調用功能統(tǒng)一存在syscall表中,syscall表通常編譯放在內核映像的代碼段,修改syscall表需要修改內核頁屬性,感興趣的讀者可以找到linux rootkit方面的資料。本文對系統(tǒng)調用監(jiān)控的實現(xiàn),采用內核從2.6支持的probe功能來實現(xiàn),選用的最重要原因是:通用性。在不同abi平臺通過匯編實現(xiàn)系統(tǒng)調用的讀者應該知道,不同abi平臺的系統(tǒng)調用功能號并不一定相同,這就意味其在syscall表中的數(shù)組索引是不一致的,還需要額外的判定,實現(xiàn)并不優(yōu)雅。

linux內核提供了kprobe、jprobe和kretprobe三種方式。限于篇幅,僅介紹利用jprobe實現(xiàn)系統(tǒng)調用監(jiān)控。感興趣的讀者可以參看內核Documentation/kprobes.txt文檔以及samples目錄下的例子。

S1. 編譯選項

為了能夠支持probe功能,需在上述開啟驅動ko編譯選項的基礎上勾選kprobe選項。如果沒有開啟內核驅動選項,是不會有kprobes(new)選項的

General setup --->  
    [*] Kprobes(New)

S2. 驅動代碼

以監(jiān)控sys_open系統(tǒng)調用為例。首先,在module_init函數(shù)中對調用register_jprobes進行注冊。注冊信息封裝在struct jprobe結構中。

static struct jprobe open_probe = {  
    .entry          = jsys_open,    //回調函數(shù)
    .kp = {
        .symbol_name    = "sys_open", //系統(tǒng)調用名稱
    },
};

由于系統(tǒng)調用為所有進程提供服務,不加入過濾信息會造成監(jiān)控信息過多?;卣{函數(shù)的聲明和被監(jiān)控系統(tǒng)調用的聲明一致。

asmlinkage int jsys_open(const char *pathname, int flags, mode_t mode){  
pid_t current_pid = current_thread_info()->task->tgid;  
// 從當前上下文中獲取進程的pid

 // monitor_pid初始化-1,0為全局監(jiān)控。if(!monitor_pid || (current_pid == monitor_pid)){  
    printk(KERN_INFO "[open] pathname %s, flags: %x, mode: %x\n", 
        pathname, flags, mode);
}

jprobe_return();  
return 0;  
}

對monitor_pid的設置通過驅動的ioctl來設置,參數(shù)簡單直接設置。

case SET_PID:  
    monitor_pid = (pid_t) arg;

S3. 測試

文末代碼bin_wrapper和ptrace_trace均為靜態(tài)編譯,bin_wrapper通過設置監(jiān)控對ptrace_trace的進行監(jiān)控。內核prink的打印信息通過cat /proc/kmsg獲取,輸出類似如下:

<6>[34728.283575] REHelper device open success!  
<6>[34728.285504] Set monitor pid: 3851  <6>[34728.287851] [openat] dirfd: -100, pathname /dev/__properties__, flags: a8000, mode: 0  <6>[34728.289348] [openat] dirfd: -100, pathname /proc/stat, flags: 20000, mode: 0  <6>[34728.291325] [openat] dirfd: -100, pathname /proc/self/status, flags: 20000, mode: 0  <6>[34728.292016] [inotify_add_watch]: fd: 4, pathname: /proc/self/mem, mask: 23  <6>[34729.296569] PTRACE_PEEKDATA: [src]pid = 3851 --> [dst]pid = 3852, addr: 40000000, data: be919e38

三、尾

本文介紹了鄙人對攻防的維度思考,以及從維度分析來實現(xiàn)的早期工具的部分介紹。希望能夠給各位讀者帶來一些幫助和思考。限于鄙人水平,難免會有疏漏或者錯誤之處,敬請各位指出,謝謝。

四、附