Dylib Injection

0x00 前言

通过学习《macOS Control Bypasses》,这里对于dylib注入的方法做列举和总结,输出一个文档。以下内容不一定准确,欢迎大家提建议,发表你的看法。

0x01 Dylib注入方式

  • 环境变量注入
    • DYLD_INSERT_LIBRARIES 注入
  • 动态注入
    • Dylib Hijacking(动态库劫持)
    • Dylib Proxying(动态库代理)
    • dlopen劫持
  • 进程注入
    • Mach Task Port注入

1x01 DYLD_INSERT_LIBRARIES 注入

通过设置环境变量 DYLD_INSERT_LIBRARIES 来在程序启动前注入dylib,该方法存在一定限制:

  • 对于设置了SUID位的二进制文件无效
  • 包含__RESTRICT/__restrict分段的二进制文件无效
  • 开启SIP状态并签名的情况下
    • 对启用Hardened Rutime/CS_RESTRICT的应用无效
    • 对启用了library validation的应用无效
      • 注入的dylib需要有正确的签名
    • 如果包含以下两种entitlement可以注入
      • com.apple.security.cs.allow-dyld-environment-variables
      • com.apple.security.cs.disable-library-validation

使用方法:DYLD_INSERT_LIBRARIES=example.dylib ./hello

1x02 Dylib Hijacking

利用动态加载器(dyld)加载共享库时的特性进行注入,限制条件同上。

  • LC_LOAD_WEAK_DYLIB命令在加载动态库时如果找不到文件会继续运行,不会抛出异常
  • LC_RPATH会指定搜索目录,可以定义多个
    • LC_LOAD_DYLIB/LC_LOAD_WEAK_DYLIB/LC_LOAD_UPWARD_DYLIB指定的路径如果是@rpath/xxx.dylib,程序在加载时会按顺序依次在LC_PATH中搜寻

此时就存在两种场景:

  • 应用使用LC_LOAD_WEAK_DYLIB命令但实际dylib不存在时,可以放置自己的dylib
  • @rpath搜索路径顺序中,如果前面的路径不存在dylib,可以在前面路径放置自己的dylib

1x03 Dylib Proxying

dylib劫持的另一种利用方式,LC_REEXPORT_DYLIB可以以代理的方式加载dylib,导出相关的函数,由此可以在编译注入的dylib时,通过重命名原始dylib并让自己的dylib指向真实dylib,将被劫持的dylib以代理模式重新导出,避免程序崩溃,限制条件同上。

// 编译dylib,适配current_version和compatibility_version,这个具体的值需要自行更改
// -reexport_library 是以代理的方式加载存在的dylib,防止程序崩溃,也就是LC_REEXPORT_DYLIB
gcc -dynamiclib -current_version 1.0 -compatibility_version 1.0 -framework Foundation hijack.m -Wl,-reexport_library,"hijacked_file_path" -o hijack.dylib
// 默认情况代理加载的dylib是以@rpath的形式加载的,修改为绝对路径的形式
install_name_tool -change @rpath/xxxx.dylib "/xxx/xxx/hijacked_file_path" hijack.dylib

1x04 dlopen劫持

当应用使用dlopen函数且未指定完整路径时,dyld会搜索不同路径。

搜索路径顺序:

  • $LD_LIBRARY_PATH (如果设置)
  • $DYLD_LIBRARY_PATH (如果设置)
  • 当前工作目录
  • $DYLD_FALLBACK_LIBRARY_PATH (如果设置)
  • $DYLD_FALLBACK_LIBRARY_PATH (如果未设置)
    • $HOME/lib
    • /usr/local/lib
    • /usr/lib
    • 当前目录

限制条件:

  • 如果二进制文件设置了 setuid/setgid 位或者签名包含 entitlements,所有环境变量都会被忽略
  • 对于受限制的二进制文件,只能使用完整路径调用 dlopen
  • /usr/lib 目录受 SIP 保护,即使 root 用户也无法写入

1x05 Mach Task Port注入

Mach是macOS的微内核,负责基本任务如调度、线程管理、硬件接口、虚拟内存和进程间通信,使用task作为资源共享的最小单位,一个task可以包含多个线程,通信通过单向通道和端口(port)进行,存在一个特殊的端口是task port,获得task port的SEND权限可以完全控制目标进程,可以读写虚拟内存,创建/停止线程等。

task port访问限制:

  • 开启SIP

    • 如果应用有com.apple.security.get-task-allow权限,同用户级别的进程可以访问其task port

    • 有com.apple.system-task-ports权限可以访问任何进程的task port(仅限Apple自身应用)

    • 非Apple自身应用且未启用hardened runtime时,root可以获取其task port

注入shellcode步骤:

  • 使用task_for_pid获取目标进程task port的SEND权限
  • 在目标进程分配内存并写入shellcode
  • 创建远程线程执行shellcode

注入dylib步骤:

  • 需要先将Mach线程提升为POSIX线程
  • 使用pthread_create_from_mach_thread创建线程
  • 使用dlopen加载dylib
  • 处理线程的双重特性(Mach和POSIX)

关键代码示例(不能直接拿来用):

// 1. 获取目标进程的task port  
task_t remoteTask;  
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);  

// 2. 在远程进程分配内存  
mach_vm_address_t remoteStack64;  
mach_vm_address_t remoteCode64;  
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);  
kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);  

// 3. 写入shellcode  
kr = mach_vm_write(remoteTask, remoteCode64, (vm_offset_t)shellcode, CODE_SIZE);  

// 4. 设置内存权限  
kr = vm_protect(remoteTask, remoteCode64, CODE_SIZE, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);  
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);  

// 5. 创建远程线程执行shellcode  
thread_act_t remoteThread;  
x86_thread_state64_t remoteThreadState64;  
thread_create_running(remoteTask, x86_THREAD_STATE64,  
                     (thread_state_t)&remoteThreadState64,  
                     x86_THREAD_STATE64_COUNT,  
                     &remoteThread);  

0x02 注入维持思路

  • insert_dylib InsertDylib

    • 这两款工具本质上一样的,后者是论坛@QiuChenly在前者的基础上增加了一些功能
    • 工具会在被注入的程序上插入一个LC_LOAD_DYLIB指令,修复相关数据,让程序在运行时主动去加载指定的dylib
    • 如果加上--weak会插入LC_LOAD_WEAK_DYLIB指令
    • 检测到签名也可以移除签名
  • 环境变量注入方式

    • Info.plist文件中添加环境变量信息

    • <key>LSEnvironment</key>
      <dict>
      <key>DYLD_INSERT_LIBRARIES</key>
      <string>/Applications/xxxx.app/Contents/xxxx.dylib</string>
      </dict>
      
    • 此方法需要移除程序的签名,需要刷新Info.plist的缓存

      • codesign --remove-signature /Applications/xxxx.app/Contents/MacOS/xxxx
      • /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/ Support/lsregister -f /Application/xxxx.app
  • Dylib劫持或者代理方式

    • 本身就是会自动加载

0x03 总结

SIP开启情况下,最好的方法就是insert_dylib和环境变量维持的方式,但是需要删除签名,在某些存在校验的应用上会无效,还有就是如果有程序自带帮助程序/系统扩展的XPC服务,有可能会部分功能失效,因为XPC服务可能会校验请求的客户端(如果没有校验那它有被XPC攻击的可能,那你研究研究没准儿能搞出个CVE)。

SIP关闭,那你随意玩耍,注入基本就不是大问题了,此状态下也可以试试frida,frida的原理应该就是通过进程注入的方式在程序中注入javascript引擎,然后再用进程间通信机制来操作执行代码。