在macOS内存中运行可执行文件

作为一名安全研究员,我一直在研究恶意软件攻击设备时使用到的新兴技术。虽然Windows是最常见的目标,但是对于macOS(以前称为OS X),并不缺乏新兴的技术。在这篇文章中,我将讨论一些在macOS(直到Sierra)上执行多级攻击载荷的技术。

用于执行多级攻击载荷的常见技术是拥有一个可以从内存中载入可执行文件而不是从计算硬盘上载入的初始攻击载荷,它可以降低被检测的风险。一般来说,当尝试从macOS的内存中加载代码时,第一步是找到动态加载器dyld,以便于加载第二级攻击载荷。一旦你在内存中找到dyld,你可以解析它的Mach-O头来定位函数NSCreateObjectFileImageFromMemory(创建一个NSObjectFileImage)和NSLinkModule(链接库到当前进程和运行构造函数)来加载可执行文件。

深入了解dyld

你在MacOS执行一个动态链接的二进制文件时,内核会做的第一件事就是从二进制Mach-O的加载命令中检索动态加载器的位置,并加载它。因此,dyld是第一个被加载到进程的地址空间的Mach-O。内核通过二进制文件的vmaddr和ASLR slide来确定dyld的候选地址。然后内核将Mach-O的sections映射到第一个可用未分配的内存地址中,内存地址要大于或等于到候选地址。

如下所示,在Sierra之前的macOS版本中,dyld的vmaddr是0x7fff5fc00000(DYLD_BASE):

MacOS的10.10.5(Yosemite)

  1. $ otool -l /usr/lib/dyldx 
  2. /usr/lib/dyld: 
  3. Load command 0 
  4.       cmd LC_SEGMENT_64 
  5.     cmdsize 552 
  6.     segname __TEXT 
  7.       vmaddr 0x00007fff5fc00000 
  8. ... 

在内存中定位dyld是很容易的; 从DYLD_BASE开始搜索,找到的第一个Mach-O映像就是dyld。然后可以通过内存中的dyld地址减去DYLD_BASE来计算用于dyld的ASLR slide。确定ASLR slide对于解析符号很重要,因为符号表的nlist_64结构中的n_value包含需要被偏移的基地址。

在Sierra中,dyld变成了动态映射(vmaddr为0):

macOS 10.12.2 (Sierra)

  1. $ otool -l /usr/lib/dyld 
  2. /usr/lib/dyld: 
  3. Mach header 
  4. magic cputype cpusubtype caps    filetype ncmds sizeofcmds      flags 
  5.   0xfeedfacf 16777223          3  0x00           7    14       1696 0x00000085 
  6. Load command 0 
  7.     cmd LC_SEGMENT_64 
  8.   cmdsize 552 
  9.   segname __TEXT 
  10.     vmaddr 0x0000000000000000 
  11. ... 

这意味着,现在,在内存中已加载的可执行映像相邻处可以找到dyld,而不是DYLD_BASE中的第一个Mach-O映像。因为没有固定的基地址,我们现在不能再轻松计算ASLR slide。幸运的是,我们不再关心这个值,因为符号表的nlist_64结构的n_value现在包含了从dyld开始的偏移; 一旦你在内存中找到dyld的地址,你可以解析它的符号。我们将在下面的解析符号部分详细讨论这一点。

dyld在内存中的位置

那么我们如何在内存中搜索dyld呢?在地址空间中搜索特定映像的正确方法是递归地使用vm_region。然而,通过这种方法产生的shellcode是冗长和繁琐的。幸运的是,有一个选择; 如果我们发现一个系统调用,它接受一个指针并根据地址是否被映射返回不同的返回值,我们可以使用它。chmod系统调用就是这样。

如果路径指针在进程分配的地址空间的之外,chmod返回EFAULT; 如果路径不存在,则返回ENOENT。因此,我们可以使用以下函数来找到dyld:

  1. int find_macho(unsigned long addr, unsigned long *base) { 
  2.     *base = 0; 
  3.     while(1) { 
  4.         chmod((char *)addr, 0777); 
  5.         if(errno == 2 && /*ENOENT*/ 
  6.           ((int *)addr)[0] == 0xfeedfacf /*MH_MAGIC_64*/) { 
  7.             *base = addr; 
  8.             return 0; 
  9.         }  
  10.         addr += 0x1000; 
  11.     }   
  12.     return 1; 

在Sierra之前的macOS版本中,可以这样做:

  1. unsigned long dyld; 
  2. if(find_macho(DYLD_START, &dyld)) return 

在Sierra,我们必须调用find_macho两次:一次找到已加载的二进制,一次找到dyld:

  1. unsigned long binary, dyld; 
  2. if(find_macho(EXECUTABLE_BASE_ADDR, &binary)) return 1; 
  3. if(find_macho(binary + 0x1000, &dyld)) return 1; 

解析符号

找到dyld的字符串表可以通过解析其加载命令,并使用以下代码(基址是内存中的dyld地址)查找符号表以及内存中的linkedit和text segments 来完成:

  1. lc = (struct load_command *)(base + sizeof(struct mach_header_64)); 
  2. for(int i=0; i<((struct mach_header_64 *)base)->ncmds; i++) { 
  3.     if(lc->cmd == 0x2/*LC_SYMTAB*/) { 
  4.         symtab = (struct symtab_command *)lc; 
  5.     } else if(lc->cmd == 0x19/*LC_SEGMENT_64*/) { 
  6.         switch(*((unsigned int *)&sc->segname[2])) { //skip __ 
  7.         case 0x4b4e494c:    //LINK 
  8.             linkedit = (struct segment_command_64 *)lc; 
  9.             break; 
  10.         case 0x54584554:    //TEXT 
  11.             text = (struct segment_command_64 *)lc; 
  12.             break;  
  13. }  
  14. }  
  15. lc = (struct load_command *)((unsigned long)lc + lc->cmdsize); 

上面的代码略过内存中的mach_header_64结构,然后遍历各种加载命令结构。我们保存LC_SYMTAB的地址和与__LINKEDIT和__TEXT段相关的两个LC_SEGMENT_64命令。使用指向这些结构体的指针,我们现在可以计算内存中的字符串表的地址:

  1. unsigned long file_slide = linkedit-> vmaddr - text-> vmaddr - linkedit-> fileoff;  
  2. strtab =(char *)(base + file_slide + symtab-> stroff); 

要遍历字符串表,我们需要遍历符号表的nlist_64结构。每个nlist_64结构体包含一个到字符串表(n_un.n_strx)的偏移量:

  1. char * name = strtab + nl [i] .n_un.n_strx; 

并不是使用传统的哈希算法来匹配字符串表中的符号名,我写了一个帮助脚本(symbolyzer.py)生成唯一offset/ int对,通过以下方式识别:

  1. $ nm / usr / lib / dyld | cut -d“”-f3 | 排序| uniq | 蟒蛇symbolyzer.py 
  2. $ nm /usr/lib/dyld | cut -d" " -f3 | sort | uniq | python symbolyzer.py 
  3. ... 
  4. _NSCreateObjectFileImageFromFile[25] = 0x466d6f72 
  5. ... 

symbolyzer.py的代码(https://github.com/CylanceVulnResearch/osx_runbin/blob/master/symbolyzer.py)可以在GitHub上找到这里。

正如你可以看到,NSCreateFileImageFromMemory的offset / int对是25&0x466d6f72。这意味着如果我们字符串表中的给定字符串索引为25,并且它等于0x466d6f72,则我们发现了一个匹配。在我们的匹配对中包含一个逻辑终止符NULL就可以匹配所有符号字符串。

加载可执行文件

在MacOS的内存中加载代码的最常见的方法就是在macOS bundle上调用NSCreateObjectFileImageFromMemory,随后调用NSLinkModule,然后调用NSAddressOfSymbol来查找已知函数。 “The Mac Hacker’s Handbook”中为NSLinkModule指出:“目前的实现仅限用于插件的Mach-O MH_BUNDLE类型。” dyld的头文件进一步说明“NSObjectFileImage只能用于MH_BUNDLE文件”。经过快速证实,这是真的; 如果文件类型的mach_header_64结构不是MH_BUNDLE(0x8),NSCreateObjectFileImageFromMemory会失败。幸运的是,使用以下两行代码可以很容易改变结构的文件类型:

  1. int type = ((int *)binbuf)[3]; 
  2. if(type != 0x8) ((int *)binbuf)[3] = 0x8; //change to mh_bundle type 

然后我们可以在Mach-O映象内解析NSLinkModule返回的NSModule对象来找到入口点; 在Sierra中,Mach-O映象从偏移0x48变为0x50。通过迭代该映像的加载命令,我们可以找到LC_MAIN命令并获取入口点的偏移量。只需将此偏移量添加到Mach-O基址中即可得到入口点:

  1. unsigned long execute_base = *(unsigned long *)((unsigned long)nm + 0x50); 
  2. struct entry_point_command *epc;   
  3. if(find_epc(execute_base, &epc)) {  //loops over commands and finds LC_MAIN 
  4. fprintf(stderr, "Could not find entry point command.\n"); 
  5. goto err; 
  6. int(*main)(intchar**, char**, char**) = (int(*)(intchar**, char**, char**))(execute_base+ epc->entryoff); 
  7. char *argv[]={"test"NULL}; 
  8. int argc = 1; 
  9. char *env[] = {NULL};   
  10. char *apple[] = {NULL}; 
  11. return main(argc, argv, env, apple); 

这篇文章的所有的POC代码(https://github.com/CylanceVulnResearch/osx_runbin)都可以在GitHub上找到。

1. http://phrack.org/issues/64/11.html

2. http://www.blackhat.com/presentations/bh-dc-09/Iozzo/BlackHat-DC-09-Iozzo-let-your-mach0-fly-whitepaper.pdf

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

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/152532.html<

(0)
运维的头像运维
上一篇2025-03-14 01:26
下一篇 2025-03-14 01:28

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注