Dylib基础

先掌握一些 macOS 下的前置知识:

otool查看装载指令

otool查看装载指令

LoadCommands基本的加载命令的数据结构如下:

struct load_command {
	uint32_t cmd;		/* type of load command  */
	uint32_t cmdsize;	/* total size of command in bytes */
};
...
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*/
};

“DYLD_INSERT_LIBRARIES”环境变量

在 macOS 中动态链接器在程序加载之前加载 DYLD_INSERT_LIBRARIES 此环境变量中指定的任何动态库,本质上是将动态库注入应用程序。写个例子测试一下:

// 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");
}
// 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”的限制

Untitled

如果有如下签名权限贼也可以绕过限制:

com.apple.security.cs.allow-dyld-environment-variables
com.apple.security.cs.disable-library-validation

DYLD_INSERT_LIBRARIES实例

MacOS Stored Credentials 文章中检测到 Microsoft Remote Desktop 并没有开启runtime利用DYLD_INSERT_LIBRARIES环境变量注入dylib,应用本身就有Keychain的访问权限直接能从钥匙串中检索密码:

Untitled

Dylib劫持

DYLIB HIJACKING ON OS X - Patrick Wardle 文章详细介绍 dylib 劫持,这里就不再赘述,总结如下:

  1. 对加载命令为 LC_LOAD_WEAK_DYLIB Dylib 进行劫持

    什么是弱引用?dyld 是macOS 上动态链接器,其运行原理在文章中有详细的描述。简单来说dylb在加载动态库时,如果动态库的加载命令为 LC_LOAD_WEAK_DYLIB 即使库缺失了也不会影响程序的加载和运行。

找到缺失库的路径并替换为恶意动态库:

Untitled

  1. 对搜索路径为 @rpath Dylib 进行劫持

@rpath 动态链接通过搜索路径列表定位动态库,而这个列表是嵌入在应用程序中,一般在LoadCommands 中的 LC_RPATH

Untitled

按顺序查找路径下缺失动态库时就可以将其位置替换为恶意动态库,因为加载程序将按顺序搜索这些路径,这样就完成了劫持:

Untitled

Dylib劫持的限制

  1. DYLD_INSERT_LIBRARIES一样存在 Hardened RuntimeLibrary Validation 签名选项的限制。

  2. 劫持 Dylib 的路径

    由于macOS的系统完整性保护(SIP),/usr/lib/* 或者 /System/Library/* 等受SIP保护下目录中的文件没权限修改,可以直接忽略(除非用户手动关闭SIP)。

Dylib劫持实例

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,又想允许任何库加载到我的地址空间中:

Untitled

因此,由于这种权限的存在,我们(理论上)可以绕过 Hardened Runtime,并将恶意库代码注入Zoom中(例如,在没有访问警告的情况下访问麦克风和摄像头)。在文章中提到 dylib proxying 方法,这种方法既隐蔽又持久,接下来简单介绍下 Dylib Proxying 技术。

Dylib Proxying

简而言之就是我们替换了目标应用程序所依赖的合法库文件,然后将应用程序发出的所有请求代理回原始库文件,由于加载程序将遵循其重新导出指令,因此不会抛出任何链接器错误。dylib proxying另一个好处是它不会损害二进制文件的代码签名证书(会破坏程序集的签名)。

Untitled

howtodo

  1. 设置LC_REEXPORT_DYLIB链接器指令

    使用 Xcode 向劫持者的dylib 项目添加几个链接器标志:-Xlinker,reexport_library,添加目标代理库的路径:

    Untitled

  2. 链接器标志生成一个嵌入 LC_REEXPORT_DYLIB 加载命令:

Untitled

  1. 修改LC_REEXPORT_DYLIB 为绝对路径

    dyld 加载程序将尝试直接从文件系统加载 LC_REEXPORT_DYLIB 的路径,如果路径为@rpath 等非绝对路径必然会失败。使用 install_name_tool 修改路径:

    install_name_tool -change @rpath/xxxlib /Applications/xxx.app/lib

    Untitled

自动化的实现

根据上面的总结,可以写个自动化程序快速帮我们在目标机器上查找到可注入&劫持Dylib的应用程序。

大概功能如下:遍历 /Applicaiotns/some.app 过滤掉存在 Hardened RuntimeLibrary Validation 的限制 ,如果有com.apple.security.cs.disable-library-validation 授权可以忽略Hardened Runtime。查找 weak@rpath 可以劫持动态库的路径(在sip保护之外的),如果没有可劫持的路径可以列出 dylib proxy

源码仓库:https://github.com/ac0d3r/dylibx

至于注入的 dylib 应该还有很多玩法,留到后面文章继续研究。

参考文章


Powered by Kali-Team