先掌握一些 macOS 下的前置知识:
macho
Mach-O (Mach Object 的缩写)是 IOS/macOS 下的原生可执行文件格式,可用于可执行文件、共享库、动态加载的代码等等。
dylib
也被称为动态共享库、共享对象或动态链接库,简单来说 dylib
就是用于动态链接的库。
Load commands
Mach-O文件由头(Header)、装载指令(Load Commands)和数据(Data)组成。这些加载命令在Mach-O文件加载解析时,被内核加载器或者动态链接器调用,这些加载命令指定了二进制文件的布局和链接特性(内存布局、主线程的初始执行状态、依赖的动态链接的名称等)。
MachOView查看装载指令
otool查看装载指令
LoadCommands基本的加载命令的数据结构如下:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
cmd 字段代表当前加载命令的类型。
cmdsize 字段代表当前加载命令的大小。
加载命令类型的定义在 mach-o/loader.h 中,以 LC 为前缀的;所有的这些加载命令由系统内核加载器直接使用,或由动态链接器处理。其中几个常见的加载命令有LC_SEGMENT
、LC_LOAD_DYLINKER
、LC_LOAD_DYLIB
、LC_MAIN
、LC_CODE_SIGNATURE
、LC_ENCRYPTION_INFO
等。这里重点关注下 dylib 的加载,当cmd类型是LC_LOAD_DYLIB
、LC_LOAD_WEAK_DYLIB
与LC_REEXPORT_DYLIB
时,统一使用 dylib_command 结构体表示:
...
struct dylib_command {
uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
LC_REEXPORT_DYLIB */
uint32_t cmdsize; /* includes pathname string */
struct dylib dylib; /* the library identification */
};
...
struct dylib {
union lc_str name; /* library's path name */
uint32_t timestamp; /* library's build time stamp */
uint32_t current_version; /* library's current version number */
uint32_t compatibility_version; /* library's compatibility vers number*/
};
在 macOS 中动态链接器在程序加载之前加载 DYLD_INSERT_LIBRARIES
此环境变量中指定的任何动态库,本质上是将动态库注入应用程序。写个例子测试一下:
example.dylib
// clang -shared -framework Foundation example_dylib.m -o libexample.dylib
#import <Foundation/Foundation.h>
static void __attribute__((constructor)) initialize(void){
NSLog(@"insert_dylib i'm here");
}
hello.m
// clang -framework Foundation hello.m -o hello
#import <Foundation/Foundation.h>
int main(int argc, char **argv) {
@autoreleasepool {
NSLog(@"HelloWorld!");
}
}
指定 libexample.dylib
并运行 $ DYLD_INSERT_LIBRARIES=./libexample.dylib ./hello
libexample.dylib 被提前加载执行,运行结果如下:
$ DYLD_INSERT_LIBRARIES=./libexample.dylib ./hello
2022-11-20 21:06:06.397 hello[17898:473695] insert_dylib i'm here
2022-11-20 21:06:06.398 hello[17898:473695] HelloWorld!
“DYLD_INSERT_LIBRARIES”
的限制设置SUID位限制
$ sudo chmod +s hello
$ DYLD_INSERT_LIBRARIES=./libexample.dylib ./hello
2022-11-20 21:07:07.369 hello[17898:473695] HelloWorld!
强化运行时(Hardened Runtime)
SIP&Hardened Runtime 通过防止某些类型的攻击(如代码注入、动态链接库 (DLL) 劫持和进程内存空间篡改)来保护软件的运行时完整性。
Hardened Runtime选项指为0x10000(runtime)
,可以使用 codesign
命令的 --option=runtime
来添加。
使用 codesign -dvvv xxx
查看MachO签名信息,如图中的应用就不会加载&运行注入的dylib:
如果有如下签名权限贼也可以绕过限制:
com.apple.security.cs.allow-dyld-environment-variables
com.apple.security.cs.disable-library-validation
库验证签名(Library Validation)
验证每个加载dylib的签名,需要与主可执行文件相同的密钥进行签名的才能通过。
Library Validation选项为**flags=0x2000(library-validation)
,**可以使用 codesign
命令的 --option=library
来添加。``
CS_RESTRICT代码签名标志
这通常是在 Apple 平台二进制文件加载期间使用csops系统调用的CS_OPS_MARKRESTRICT操作动态设置的 。
CS_RESTRICT选项为 **flags=0x800(restrict)
,**可以使用 codesign
命令的 --option=0x800
来添加。
可以使用CSOps 实用程序来查询进程的代码签名状态:
$ csops -status 361csops -status [pid]
PID: 361 -> Code Signing Status: 26015b11
# CS_RESTRICT标志为 0x800,在编号 0x26015b11 中设置。我们可以通过 使用简短的 bash 单行代码在两个值之间执行AND运算来快速验证这一点。
$ echo $(([##16]0x26015b11 & 0x800))
800
DYLD_INSERT_LIBRARIES
实例MacOS Stored Credentials 文章中检测到 Microsoft Remote Desktop 并没有开启runtime
利用DYLD_INSERT_LIBRARIES
环境变量注入dylib,应用本身就有Keychain
的访问权限直接能从钥匙串中检索密码:
DYLIB HIJACKING ON OS X - Patrick Wardle 文章详细介绍 dylib 劫持,这里就不再赘述,总结如下:
对加载命令为 LC_LOAD_WEAK_DYLIB
Dylib 进行劫持
什么是弱引用?dyld 是macOS 上动态链接器,其运行原理在文章中有详细的描述。简单来说dylb在加载动态库时,如果动态库的加载命令为 LC_LOAD_WEAK_DYLIB
即使库缺失了也不会影响程序的加载和运行。
找到缺失库的路径并替换为恶意动态库:
@rpath
Dylib 进行劫持@rpath
动态链接通过搜索路径列表定位动态库,而这个列表是嵌入在应用程序中,一般在LoadCommands 中的 LC_RPATH
:
按顺序查找路径下缺失动态库时就可以将其位置替换为恶意动态库,因为加载程序将按顺序搜索这些路径,这样就完成了劫持:
和 DYLD_INSERT_LIBRARIES
一样存在 Hardened Runtime
和 Library Validation
签名选项的限制。
劫持 Dylib 的路径
由于macOS的系统完整性保护(SIP),/usr/lib/*
或者 /System/Library/*
等受SIP保护下目录中的文件没权限修改,可以直接忽略(除非用户手动关闭SIP
)。
Zoom本地安全漏洞#2获取麦克风与摄像头权限的代码注入 虽然应用程序存在Hardened Runtime
,通过 codesign -d --entitlements :- /Applications/zoom.us.app/
导出zoom应用的授权项发现又存在 [com.apple.security.cs.disable-library-validation](<https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation>)
权限,这表示zoom应用既想要 Hardened Runtime
,又想允许任何库加载到我的地址空间中:
因此,由于这种权限的存在,我们(理论上)可以绕过 Hardened Runtime
,并将恶意库代码注入Zoom中(例如,在没有访问警告的情况下访问麦克风和摄像头)。在文章中提到 dylib proxying 方法,这种方法既隐蔽又持久,接下来简单介绍下 Dylib Proxying
技术。
简而言之就是我们替换了目标应用程序所依赖的合法库文件,然后将应用程序发出的所有请求代理回原始库文件,由于加载程序将遵循其重新导出指令,因此不会抛出任何链接器错误。dylib proxying
另一个好处是它不会损害二进制文件的代码签名证书(会破坏程序集的签名)。
howtodo
设置LC_REEXPORT_DYLIB
链接器指令
使用 Xcode 向劫持者的dylib 项目添加几个链接器标志:-Xlinker,reexport_library
,添加目标代理库的路径:
链接器标志生成一个嵌入 LC_REEXPORT_DYLIB
加载命令:
修改LC_REEXPORT_DYLIB
为绝对路径
dyld
加载程序将尝试直接从文件系统加载 LC_REEXPORT_DYLIB
的路径,如果路径为@rpath
等非绝对路径必然会失败。使用 install_name_tool
修改路径:
install_name_tool -change @rpath/xxxlib /Applications/xxx.app/lib
根据上面的总结,可以写个自动化程序快速帮我们在目标机器上查找到可注入&劫持Dylib的应用程序。
大概功能如下:遍历 /Applicaiotns/some.app
过滤掉存在 Hardened Runtime
和 Library Validation
的限制 ,如果有com.apple.security.cs.disable-library-validation
授权可以忽略Hardened Runtime
。查找 weak
和 @rpath
可以劫持动态库的路径(在sip保护之外的),如果没有可劫持的路径可以列出 dylib proxy
。
源码仓库:https://github.com/ac0d3r/dylibx
至于注入的 dylib 应该还有很多玩法,留到后面文章继续研究。