無論是逆向分析還是漏洞利用,我所理解的攻防博弈無非是二者在既定的某一階段,以高維的方式進(jìn)行對抗,并不斷地升級維度。比如,逆向工程人員一般會(huì)選擇在Root的環(huán)境下對App進(jìn)行調(diào)試分析,其是以root的高權(quán)限對抗受沙盒限制的低權(quán)限;在arm64位手機(jī)上進(jìn)行root/越獄時(shí),ret2usr利用技術(shù)受到PXN機(jī)制的約束,廠商從修改硬件特性的高維度進(jìn)行對抗,迫使漏洞研究者提高利用技巧。
下文將在Android逆向工程方面,分享鄙人早期從維度攻擊的角度所編寫的小工具。工具本身可能已經(jīng)不能適應(yīng)現(xiàn)在的攻防,“授人以魚不如授人以漁”,希望能夠給各位讀者帶來一些思路,構(gòu)建自己的分析利器。
早期Android平臺(tái)對SO的保護(hù)采用畸形文件格式和內(nèi)容加密的方式來對抗靜態(tài)分析。隨著IDA以及F5插件地不斷完善和增多,IDA已經(jīng)成為了逆向人員的標(biāo)配工具。正因如此,IDA成為了畸形文件格式的對抗目標(biāo)。畸形方式從減少文件格式信息到構(gòu)造促使IDA加載crash的變化正應(yīng)證了這一點(diǎn)。對此,鄙人研究通過重建文件格式信息的方式來讓IDA正常加載。
在完成編寫修復(fù)重建工具不久之后,鄙人在一次使用IDA的加載bin文件時(shí),猛然意識(shí)到畸形文件格式的對抗目標(biāo)是IDA對ELF文件的加載的默認(rèn)loader。既然防御的假象和維度僅僅在于默認(rèn)loader,那么以自定義的loader加載實(shí)現(xiàn)高維攻擊,理論是毫無敵手的。
那如何來實(shí)現(xiàn)IDA自定義loader呢?
以Segment加載的流程對ELF文件進(jìn)行解析,獲取和重建Section信息(參看上面所說貼子)。 把文件信息在IDA中進(jìn)行展示,直接調(diào)用對應(yīng)的IDAPython接口
實(shí)現(xiàn)加載bin文件的py代碼見文末github鏈接,直接放置于IDA/loaders目錄即可。由于早期少有64位的安卓手機(jī),加載腳本僅支持arm 32位格式,有興趣讀者可以改寫實(shí)現(xiàn)全平臺(tái)通用。不同ndk版本所編譯文件中與動(dòng)態(tài)加載無關(guān)的Section不一定存在,注釋相應(yīng)的重建代碼即可。
以APP分析為例,對于加固過的應(yīng)用通常會(huì)對自身的運(yùn)行環(huán)境進(jìn)行檢測。比如: 檢測自身調(diào)試狀態(tài),監(jiān)控PRoc文件等。相信各位讀者有各種奇淫技巧來繞過,早期鄙人構(gòu)建hook環(huán)境來繞過。從維度的角度,再來分析這種對抗。對于APP或者bin文件而言,其僅運(yùn)行于受限的環(huán)境中,就算exp提權(quán)后也只是權(quán)限的提升和對內(nèi)核有一定的訪問控制權(quán)。對于Android系統(tǒng)而言,逆向人員不僅能夠拿到root最高權(quán)限,而且還可以修改系統(tǒng)的所有代碼。從攻防雙方在運(yùn)行環(huán)境的維度來看,“魔”比”道“高了不只三丈,防御方猶如板上魚肉。而在代碼維度,防御方擁有源代碼的控制權(quán),攻防處于完全劣勢。隨著代碼混淆和VMP技術(shù)的運(yùn)用,防御方這塊魚肉越來越不好”啃”。
對于基于linux的安卓系統(tǒng)而言,進(jìn)程的運(yùn)行環(huán)境和結(jié)構(gòu)是由內(nèi)核來提供和維護(hù)的。從修改內(nèi)核的維度來對抗,能達(dá)到一些不錯(cuò)的效果。下文將詳述在內(nèi)核態(tài)dump目標(biāo)進(jìn)程內(nèi)存和系統(tǒng)調(diào)用監(jiān)控。
對內(nèi)核添加一些自定義功能時(shí),通常可以采用內(nèi)核驅(qū)動(dòng)來實(shí)現(xiàn)。雖然一部分Android手機(jī)支持驅(qū)動(dòng)ko文件加載,但內(nèi)核提供的其他工具則不一定已經(jīng)編譯到內(nèi)核,在后文中可以看到。nexus系列手機(jī)是谷歌官方所支持的,編譯刷機(jī)都比較方便,推薦使用。
為了讓內(nèi)核支持驅(qū)動(dòng)ko文件的加載,在make memuconfig配置內(nèi)核選項(xiàng)時(shí),以下勾選:
[*] Enable loadable module support 次級目錄所有選項(xiàng)編譯步驟參看谷歌官方提供的內(nèi)核編譯步驟。
linux系統(tǒng)支持多種驅(qū)動(dòng)設(shè)備,這里采用最簡單的字符設(shè)備來實(shí)現(xiàn)。與其他操作系統(tǒng)類似,linux驅(qū)動(dòng)程序也分為入口和出口。在module_init入口中,對字符設(shè)備進(jìn)行初始化,創(chuàng)建/dev/REHelper字符設(shè)備。文末代碼采用傳統(tǒng)的方式對字符設(shè)備進(jìn)行注冊,也可直接使用misc的方式。字符設(shè)備的操作方式通過注冊file_Operations回調(diào)實(shí)現(xiàn),其中ioctl函數(shù)比較靈活,滿足實(shí)現(xiàn)需求。
定義command ID:
#define CMD_BASE 0xC0000000 #define DUMP_MEM (CMD_BASE + 1) #define SET_PID (CMD_BASE + 2)構(gòu)建dump_request參數(shù):
struct dump_request{ pid_t pid; //目標(biāo)進(jìn)程 unsigned long addr; //目標(biāo)進(jìn)程dump起始地址 ssize_t count; //dump的字節(jié)數(shù) char __user *buf; //用戶空間存儲(chǔ)buf };在ioctl中實(shí)現(xiàn)分支:
case DUMP_MEM: target_task = find_task_by_vpid(request->pid); //對于用戶態(tài),進(jìn)程通過進(jìn)程的pid來標(biāo)示自身;在內(nèi)核空間,通過pid找到對應(yīng)的進(jìn)程結(jié)構(gòu)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); //進(jìn)程的虛擬地址空間同樣由內(nèi)核進(jìn)程管理,通過mm_struct結(jié)構(gòu)組織memread其實(shí)是對memrw函數(shù)的封裝,mem_rw能夠讀寫目標(biāo)進(jìn)程,簡略流程:
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); // 獲取存儲(chǔ)數(shù)據(jù)的臨時(shí)頁面 ... while (count > 0) { int this_len = min_t(int, count, PAGE_SIZE); // 將寫入數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間 if (write && copy_from_user(page, buf, this_len)) { copied = -EFAULT; break; } // 對目標(biāo)進(jìn)程進(jìn)行讀或?qū)懖僮鳎唧w實(shí)現(xiàn)參看內(nèi)核源碼 this_len = access_remote_vm(mm, addr, page, this_len, write); // 將獲取到的目標(biāo)進(jìn)程數(shù)據(jù)從內(nèi)核拷貝到用戶空間 if (!write && copy_to_user(buf, page, this_len)) { copied = -EFAULT; break; } ... } ...}內(nèi)核驅(qū)動(dòng)部分的dump功能實(shí)現(xiàn),接著只需在用戶空間訪問驅(qū)動(dòng)程序即可。
// 構(gòu)造ioctl參數(shù)request.pid = atoi(argv[1]); request.addr = 0x40000000; request.buf = buf; request.count = 1000;// 打開內(nèi)核驅(qū)動(dòng)int fd = open("/dev/REHelper", O_RDWR); // 發(fā)送讀取命令ioctl(fd, DUMP_MEM, &request); close(fd);文末代碼中,dump_test為目標(biāo)進(jìn)程,dump_host通過內(nèi)核驅(qū)動(dòng)獲取目標(biāo)進(jìn)程的數(shù)據(jù)。insmod和dump_host以root權(quán)限運(yùn)行即可。
通常情況下,APP通過動(dòng)態(tài)鏈接庫libc.so間接的進(jìn)行系統(tǒng)調(diào)用,直接在用戶態(tài)hook libc.so的函數(shù)即可實(shí)現(xiàn)監(jiān)控。而對于靜態(tài)編譯的bin文件和通過svc匯編指令實(shí)現(xiàn)的系統(tǒng)調(diào)用,用戶態(tài)直接hook是不好處理的。道理很簡單,系統(tǒng)調(diào)用由內(nèi)核實(shí)現(xiàn),hook也應(yīng)該在內(nèi)核。
linux系統(tǒng)的系統(tǒng)調(diào)用功能統(tǒng)一存在syscall表中,syscall表通常編譯放在內(nèi)核映像的代碼段,修改syscall表需要修改內(nèi)核頁屬性,感興趣的讀者可以找到linux rootkit方面的資料。本文對系統(tǒng)調(diào)用監(jiān)控的實(shí)現(xiàn),采用內(nèi)核從2.6支持的probe功能來實(shí)現(xiàn),選用的最重要原因是:通用性。在不同abi平臺(tái)通過匯編實(shí)現(xiàn)系統(tǒng)調(diào)用的讀者應(yīng)該知道,不同abi平臺(tái)的系統(tǒng)調(diào)用功能號并不一定相同,這就意味其在syscall表中的數(shù)組索引是不一致的,還需要額外的判定,實(shí)現(xiàn)并不優(yōu)雅。
linux內(nèi)核提供了kprobe、jprobe和kretprobe三種方式。限于篇幅,僅介紹利用jprobe實(shí)現(xiàn)系統(tǒng)調(diào)用監(jiān)控。感興趣的讀者可以參看內(nèi)核Documentation/kprobes.txt文檔以及samples目錄下的例子。
為了能夠支持probe功能,需在上述開啟驅(qū)動(dòng)ko編譯選項(xiàng)的基礎(chǔ)上勾選kprobe選項(xiàng)。如果沒有開啟內(nèi)核驅(qū)動(dòng)選項(xiàng),是不會(huì)有kprobes(new)選項(xiàng)的
General setup ---> [*] Kprobes(New)以監(jiān)控sys_open系統(tǒng)調(diào)用為例。首先,在module_init函數(shù)中對調(diào)用register_jprobes進(jìn)行注冊。注冊信息封裝在struct jprobe結(jié)構(gòu)中。
static struct jprobe open_probe = { .entry = jsys_open, //回調(diào)函數(shù) .kp = { .symbol_name = "sys_open", //系統(tǒng)調(diào)用名稱 },};由于系統(tǒng)調(diào)用為所有進(jìn)程提供服務(wù),不加入過濾信息會(huì)造成監(jiān)控信息過多。回調(diào)函數(shù)的聲明和被監(jiān)控系統(tǒng)調(diào)用的聲明一致。
asmlinkage int jsys_open(const char *pathname, int flags, mode_t mode){ pid_t current_pid = current_thread_info()->task->tgid; // 從當(dāng)前上下文中獲取進(jìn)程的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的設(shè)置通過驅(qū)動(dòng)的ioctl來設(shè)置,參數(shù)簡單直接設(shè)置。
case SET_PID: monitor_pid = (pid_t) arg;文末代碼bin_wrapper和ptrace_trace均為靜態(tài)編譯,bin_wrapper通過設(shè)置監(jiān)控對ptrace_trace的進(jìn)行監(jiān)控。內(nèi)核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本文介紹了鄙人對攻防的維度思考,以及從維度分析來實(shí)現(xiàn)的早期工具的部分介紹。希望能夠給各位讀者帶來一些幫助和思考。限于鄙人水平,難免會(huì)有疏漏或者錯(cuò)誤之處,敬請各位指出,謝謝。
https://github.com/ThomasKing2014/ReverseTinytoolDemo
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注