利用Frida打造ELF解析器
概述
在這個文章系列中,我們將為讀者展示Frida的無窮的可能性和強大的功能。簡單來說,Frida就是一個支持多種操作系統的動態檢測工具包,可以將Javascript或自己的程序庫注入到本地應用程序中。Frida通常用於hook和操縱各種函數。雖然當前可以從網上找到一些Frida教程,但是大部分都是介紹如何使用Frida的攔截器API的,即如何「攔截」目標函數調用。在這個系列中,我們要為讀者介紹的,不僅僅涉及如何hook函數,更包括如何通過控制程序的執行流程來完成各種炫酷的事情。
利用Frida打造ELF解析器
在本系列的第一部分中,將為讀者介紹如何使用Frida和Javascript編寫一個簡單而通用的EL解析器。對於這種技術,有許多適用和實用的場景,例如將代碼注入另一個進程時;但對於本文來說,我們這樣做只是為了好玩。
為了搭建基本的實驗環境,需要:
·運行Lollipop的Nexus 5
·最新版本的Frida
·PoC應用程序
我為這個項目構建的應用程序包括一個具有單個JNI函數的共享庫。這裡將用Frida來解析這個共享庫。之所以選用這個共享庫,是因為本系列的第二部分將要用它做一些事情。實際上,如果單純想要了解如何利用Frida解析程序的話,任何共享庫或可執行文件都是不錯的選擇。
打開並讀取ELF文件
為了解析我們的目標ELF文件,需要先打開該文件,並讀取其內容。為此,我們可以藉助於open和read系統調用來完成這兩個操作。
int open(const char *path, int oflag, … );
ssize_t read(int fd, void *buf, size_t count);
當使用Frida實現上述功能時,可以使用下列Frida API:
·Memory
·NativeFunction
·Module
在編寫相應的代碼時,我們不妨問自己:「如果用C語言的話,該如何編寫?」。 為了回答這個問題,我們執行下面的步驟。
·系統調用open的第一個參數是const char *,即文件路徑。對應的,這裡可以使用Memory.allocUtf8String(path)。
·為了調用open,我們需要獲取其原始地址,以便可以使用NativeFunction構造函數,從而獲取調用open的許可權。
·可以通過Frida的Module.findExportByName()來獲得open的地址。
·在調用open之後,系統會返回已打開的共享庫的文件描述符返。
現在,已經成功地打開了共享庫,下面,我們需要計算文件的大小,並根據該值讀取共享庫的內容。在這裡,我們通過調用fstat來獲取共享庫的大小。
int fstat(int fd, struct stat *buf);
fstat函數需要兩個參數
·第一個參數是一個文件描述符,前面的open調用將返回相應的文件描述符。
·第二個參數是一個指向已分配的stat struct的指針。
我們可以通過Frida的Memory.alloc()為stat struct分配內存。
調用fstat函數的具體步驟,請參考調用open的過程。
現在,我們需要從stat struct的st_size成員中讀取數據,以獲得共享庫的大小。
off_t st_size 對於常規文件,它表示文件大小,以位元組為單位。對於符號鏈接,它表示包含在符號鏈接中的路徑名的長度,以位元組為單位。
當我們在網上搜索off_t的大小相關知識時,只找到了以下內容:
blkcnt_t和off_t應該是有符號的整數類型。
鑒於此,同時,還知道到st_size成員在結構體0x30偏移處,我們可以使用Frida的Memory.readS32()來獲取其大小。
function getFileSize(fd) {
// TODO Get the actual size of this structure
var statBuff = Memory.alloc(500);
console.log("[+] struct stat --> " + statBuff.toString());
var fstatSymbol = getSymbolAddress("libc.so", "fstat");
console.log("[+] fstat --> " + fstatSymbol);
var fstat = new NativeFunction(fstatSymbol, "int", ["int", "pointer"]);
console.log("[+] Calling fstat() [!]");
if(fd > 0) {
var ret = fstat(fd, statBuff);
if(ret failed [!]");
}
}
console.log(hexdump(statBuff, {
offset: 0,
length: 20,
header: true,
ansi: true
}));
var size = Memory.readS32(statBuff.add(0x30))
if(size > 0) {
console.log("[+] size of fd --> " + size.toString());
return size;
} else {
return 0;
}
}
最後,我們可以使用為共享庫分配的內存(這段內存的大小是通過fstat獲得的)以及通過open調用返回的文件描述符來實現系統調用read的功能。
function openAndReadLibrary(library_path) {
library_path_ptr = Memory.allocUtf8String(library_path);
console.log("[+] path --> " + library_path_ptr.toString());
open = getSymbolAddress("libc.so", "open");
console.log("[+] open --> " + open.toString());
mOpen = new NativeFunction(open, "int", ["pointer", "int"]);
console.log("[+] Opening --> " + library_path);
var fd = mOpen(library_path_ptr, 0);
if(fd " + library_path);
}
console.log("[+] fd --> " + fd.toString());
var size = getFileSize(fd);
var read_sym = getSymbolAddress("libc.so", "read");
var read = new NativeFunction(read_sym, "int", ["int", "pointer", "long"]);
var rawElf = Memory.alloc(size);
if(read(fd, rawElf, size) " + size + " bytes [!]");
console.log(hexdump(rawElf, {
offset: 0,
length: 20,
header: true,
ansi: true
}));
return rawElf
}
下面是運行解析ELF的Frida腳本的輸出結果:
[+] Running elf parser [!]
[+] path --> 0xa05af7c8
[+] open --> 0xb6e491dd
[+] Opening --> /data/data/com.versprite.poc/lib/libnative-lib.so
[+] fd --> 55
[+] struct stat --> 0xa05b0520
[+] fstat --> 0xb6e6d154
[+] Calling fstat() [!]
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 1c b3 00 00 00 00 00 00 00 00 00 00 57 7c 02 00 ............W|..
00000010 ed 81 00 00 01 00 00 00 e8 03 00 00 e8 03 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 a0 25 00 00 00 00 00 00 00 10 00 00 00 00 00 00 .%..............
[+] size of fd --> 9632
[+] read --> 9632 bytes [!]
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
00000010 03 00 28 00
解析ELF文件
由於這裡只是為了演示,所以,我實現的ELF解析器的功能非常簡單:
·解析ELF頭
·解析程序頭表
·解析節頭表
作為Javascript的新手,當數據從共享庫中讀取到緩衝區後,我還不清楚該如何訪問和可視化這些數據。說得更精確些,是我發現DataView的時候。
「DataView視圖提供了一個低級介面,用於以ArrayBuffer形式對多種數值類型進行讀寫操作,而不用關心平台的位元組順序如何。」
首先,我需要處理ELF頭。截止寫作本文為止,只支持32位。對於那些不熟悉ELF頭結構的人,下面給出32位的具體定義。
#define EI_NIDENT 16
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
解析二進位格式文件時,我也喜歡使用010 Editor,因為它可以將以代碼方式進行的所有操作以可視化的形式完成。解析ELF頭實際上非常簡單,可以通過以下步驟完成。
由於我們的指針已經指向內存中的ELF文件,所以,可以使用Frida的Memory.readByteArray(),根據Elf32_Ehdr的大小,將ELF頭讀入緩衝區。然後,從這個緩衝區構造一個新的DataView實例。
DataView類提供了從指定索引讀取數據類型的方法。例如,如果我們想讀取Elf32_Ehdrstructure的e_type成員,我們可以這樣做:
var e_type = elfHeaderDataView.getInt32(0x10, true);
通過組合使用Memory.readByteArray()和DataViews,同樣也能夠處理ELF程序頭表和節頭表。你可以從下列地址找到這些結構的定義:
https://raw.githubusercontent.com/torvalds/linux/master/include/uapi/linux/elf.h
我將把這個練習留給讀者,同時,最終的ELF解析腳本可以從下面的地址找到。
https://github.com/VerSprite/engage/blob/master/js/elf_parser.js
下面是該腳本部分輸出結果。
[+] HEADERS -----------------------------
[+] e_type --> 2621443
[+] e_machine --> 40
[+] e_version --> 1
[+] e_entry --> 0
[+] e_phoff --> 52
[+] e_shoff --> 0x21b8
[+] e_flags --> 0x5000200
[+] e_ehsize --> 52
[+] e_phentsize --> 32
[+] e_phnum --> 8
[+] e_shentsize --> 40
[+] e_shnum --> 25
[+] e_shtrndx --> 24
[+] SEGMENTS -----------------------------
[+] segment --> 0x34 : PT_PHDR
[+] segment --> 0x54 : PT_LOAD
[+] segment --> 0x74 : PT_LOAD
[+] segment --> 0x94 : PT_DYNAMIC
[+] segment --> 0xb4 : PT_NOTE
[+] SECTIONS -----------------------------
[+] .note.gnu.build-id : 0x21e0
[+] s_addr --> 0x134
[+] s_offset --> 0x134
[+] s_size --> 0x24
[+] .dynsym : 0x2208
[+] s_addr --> 0x158
[+] s_offset --> 0x158
[+] s_size --> 0xf0
小結
在本文中,我們為讀者展示了在解析二進位格式時,如何藉助Frida幫我們處理一些非常重要的事情。在本系列的下一篇文章中,我們將進一步為讀者介紹Frida的其他神奇功能。
參考文獻:
https://www.frida.re/docs/javascript-api/#interceptor
https://www.frida.re/docs/javascript-api/#NativeFunction
https://www.frida.re/docs/javascript-api/#module
https://linux.die.net/man/3/open
https://linux.die.net/man/2/read
https://linux.die.net/man/2/fstat
https://raw.githubusercontent.com/torvalds/linux/master/include/uapi/linux/elf.h
https://www.sweetscape.com/010editor/
※LAZARUS向韓國安全智庫網站上植入ActiveX 0 day漏洞
TAG:嘶吼RoarTalk |