Dyld 进程
查看 Dyld 如何在二进制文件中加载库:
macOS Dyld Process DYLD_INSERT_LIBRARIES
这类似于 Linux 上的 LD_PRELOAD 。它允许指示即将运行的进程从路径加载特定库(如果环境变量已启用)。
此技术也可以作为 ASEP 技术 使用,因为每个安装的应用程序都有一个名为 "Info.plist" 的 plist,允许使用名为 LSEnvironmental
的键 分配环境变量 。
自 2012 年以来,Apple 大幅减少了 DYLD_INSERT_LIBRARIES
的功能。
查看代码并 检查 src/dyld.cpp
。在函数 pruneEnvironmentVariables
中,您可以看到 DYLD_*
变量被移除。
在函数 processRestricted
中设置了限制的原因。检查该代码,您可以看到原因包括:
macho 二进制文件中存在 __RESTRICT/__restrict
部分。
使用以下命令检查二进制文件的 权限 :codesign -dv --entitlements :- </path/to/bin>
在更新版本中,您可以在函数 configureProcessRestrictions
的第二部分找到此逻辑。然而,在较新版本中执行的是该函数的 开始检查 (您可以删除与 iOS 或模拟相关的 if,因为这些在 macOS 中不会使用)。
库验证
即使二进制文件允许使用 DYLD_INSERT_LIBRARIES
环境变量,如果二进制文件检查要加载的库的签名,它也不会加载自定义库。
为了加载自定义库,二进制文件需要具有 以下任一权限 :
或者二进制文件 不应 具有 强化运行时标志 或 库验证标志 。
您可以使用 codesign --display --verbose <bin>
检查二进制文件是否具有 强化运行时 ,检查 CodeDirectory
中的 runtime 标志,如:CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
如果库 使用与二进制文件相同的证书签名 ,您也可以加载该库。
找到一个示例,了解如何(滥用)此功能并检查限制:
macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES Dylib 劫持
请记住,先前的库验证限制也适用于 执行 Dylib 劫持攻击。
与 Windows 一样,在 MacOS 中,您也可以 劫持 dylibs 使 应用程序 执行 任意 代码 (实际上,从普通用户的角度来看,这可能不可行,因为您可能需要 TCC 权限才能写入 .app
包并劫持库)。
然而,MacOS 应用程序 加载 库的方式 比 Windows 更受限制 。这意味着 恶意软件 开发人员仍然可以使用此技术进行 隐蔽 ,但能够 滥用此技术以提升权限的可能性要低得多 。
首先,更常见 的情况是 MacOS 二进制文件指示要加载的库的完整路径 。其次,MacOS 从不在 $PATH 的文件夹中搜索库。
与此功能相关的 主要 代码部分在 ImageLoader::recursiveLoadLibraries
中,位于 ImageLoader.cpp
。
macho 二进制文件可以使用 4 种不同的头命令 来加载库:
LC_LOAD_DYLIB
命令是加载 dylib 的常用命令。
LC_LOAD_WEAK_DYLIB
命令的工作方式与前一个相同,但如果未找到 dylib,执行将继续而不会出现错误。
LC_REEXPORT_DYLIB
命令代理(或重新导出)来自不同库的符号。
LC_LOAD_UPWARD_DYLIB
命令在两个库相互依赖时使用(这称为 向上依赖 )。
然而,有 2 种类型的 dylib 劫持 :
缺失的弱链接库 :这意味着应用程序将尝试加载一个不存在的库,配置为 LC_LOAD_WEAK_DYLIB 。然后,如果攻击者在预期加载的位置放置了一个 dylib 。
链接是“弱”的事实意味着即使未找到库,应用程序仍将继续运行。
与此相关的 代码 在 ImageLoaderMachO::doGetDependentLibraries
函数中,lib->required
仅在 LC_LOAD_WEAK_DYLIB
为 true 时为 false
。
在二进制文件中查找弱链接库 (稍后您将看到如何创建劫持库的示例):
otool -l </path/to/bin> | grep LC_LOAD_WEAK_DYLIB -A 5 cmd LC_LOAD_WEAK_DYLIB cmdsize 56 name /var/tmp/lib/libUtl.1.dylib (offset 24) time stamp 2 Wed Jun 21 12:23:31 1969 current version 1.0.0 compatibility version 1.0.0
Copy * **配置为 @rpath**:Mach-O 二进制文件可以具有 **`LC_RPATH`** 和 **`LC_LOAD_DYLIB`** 命令。根据这些命令的 **值**,**库** 将从 **不同目录** 加载。
* **`LC_RPATH`** 包含用于通过二进制文件加载库的一些文件夹的路径。
* **`LC_LOAD_DYLIB`** 包含要加载的特定库的路径。这些路径可以包含 **`@rpath`**,将由 **`LC_RPATH`** 中的值 **替换**。如果 **`LC_RPATH`** 中有多个路径,将使用所有路径来搜索要加载的库。例如:
* 如果 **`LC_LOAD_DYLIB`** 包含 `@rpath/library.dylib`,而 **`LC_RPATH`** 包含 `/application/app.app/Contents/Framework/v1/` 和 `/application/app.app/Contents/Framework/v2/`。这两个文件夹将用于加载 `library.dylib`**。** 如果库在 `[...]/v1/` 中不存在,攻击者可以将其放置在那里以劫持在 `[...]/v2/` 中加载库,因为遵循 **`LC_LOAD_DYLIB`** 中路径的顺序。
* **在二进制文件中查找 rpath 路径和库**:`otool -l </path/to/binary> | grep -E "LC_RPATH|LC_LOAD_DYLIB" -A 5`
<div data-gb-custom-block data-tag="hint" data-style='info'>
**`@executable_path`**:是包含 **主可执行文件** 的 **目录** 的 **路径**。
**`@loader_path`**:是包含 **Mach-O 二进制文件** 的 **目录** 的 **路径**,该文件包含加载命令。
* 当在可执行文件中使用时,**`@loader_path`** 实际上与 **`@executable_path`** 相同。
* 当在 **dylib** 中使用时,**`@loader_path`** 给出 **dylib** 的 **路径**。
</div>
滥用此功能以 **提升权限** 的方式是在 **应用程序** 由 **root** 执行时,**查找** 在攻击者具有写权限的某个文件夹中的 **库** 的罕见情况。
<div data-gb-custom-block data-tag="hint" data-style='success'>
一个很好的 **扫描器** 用于查找应用程序中的 **缺失库** 是 [**Dylib 劫持扫描器**](https://objective-see.com/products/dhs.html) 或 [**CLI 版本**](https://github.com/pandazheng/DylibHijack)。\
关于此技术的详细技术报告可以在 [**这里**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x) 找到。
</div>
**示例**
<div data-gb-custom-block data-tag="content-ref" data-url='macos-dyld-hijacking-and-dyld_insert_libraries.md'>
[macos-dyld-hijacking-and-dyld\_insert\_libraries.md](macos-dyld-hijacking-and-dyld\_insert\_libraries.md)
</div>
## Dlopen 劫持
<div data-gb-custom-block data-tag="hint" data-style='danger'>
请记住,**先前的库验证限制也适用于** 执行 Dlopen 劫持攻击。
</div>
来自 **`man dlopen`**:
* 当路径 **不包含斜杠字符**(即它只是一个叶名称)时,**dlopen() 将进行搜索**。如果 **`$DYLD_LIBRARY_PATH`** 在启动时设置,dyld 将首先 **在该目录中查找**。接下来,如果调用的 mach-o 文件或主可执行文件指定 **`LC_RPATH`**,则 dyld 将 **在这些** 目录中查找。接下来,如果进程是 **不受限制的**,dyld 将在 **当前工作目录** 中搜索。最后,对于旧二进制文件,dyld 将尝试一些后备方案。如果 **`$DYLD_FALLBACK_LIBRARY_PATH`** 在启动时设置,dyld 将在 **这些目录** 中搜索,否则,dyld 将在 **`/usr/local/lib/`** 中查找(如果进程不受限制),然后在 **`/usr/lib/`** 中查找(此信息来自 **`man dlopen`**)。
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(如果不受限制)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/`(如果不受限制)
6. `/usr/lib/`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
如果名称中没有斜杠,则有 2 种方式进行劫持:
* 如果任何 **`LC_RPATH`** 是 **可写的**(但签名会被检查,因此为此您还需要二进制文件不受限制)
* 如果二进制文件是 **不受限制的**,然后可以从 CWD 加载某些内容(或滥用提到的环境变量之一)
</div>
* 当路径 **看起来像框架** 路径(例如 `/stuff/foo.framework/foo`)时,如果 **`$DYLD_FRAMEWORK_PATH`** 在启动时设置,dyld 将首先在该目录中查找 **框架部分路径**(例如 `foo.framework/foo`)。接下来,dyld 将尝试 **按原样使用提供的路径**(使用当前工作目录进行相对路径)。最后,对于旧二进制文件,dyld 将尝试一些后备方案。如果 **`$DYLD_FALLBACK_FRAMEWORK_PATH`** 在启动时设置,dyld 将在这些目录中搜索。否则,它将搜索 **`/Library/Frameworks`**(在 macOS 上,如果进程不受限制),然后是 **`/System/Library/Frameworks`**。
1. `$DYLD_FRAMEWORK_PATH`
2. 提供的路径(如果不受限制,使用当前工作目录进行相对路径)
3. `$DYLD_FALLBACK_FRAMEWORK_PATH`
4. `/Library/Frameworks`(如果不受限制)
5. `/System/Library/Frameworks`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
如果是框架路径,劫持的方式将是:
* 如果进程是 **不受限制的**,滥用 **来自 CWD 的相对路径** 和提到的环境变量(即使文档中没有说明,如果进程受限,DYLD\_\* 环境变量会被移除)
</div>
* 当路径 **包含斜杠但不是框架路径**(即到 dylib 的完整路径或部分路径)时,dlopen() 首先在(如果设置) **`$DYLD_LIBRARY_PATH`** 中查找(使用路径的叶部分)。接下来,dyld **尝试提供的路径**(使用当前工作目录进行相对路径(但仅适用于不受限制的进程))。最后,对于旧二进制文件,dyld 将尝试后备方案。如果 **`$DYLD_FALLBACK_LIBRARY_PATH`** 在启动时设置,dyld 将在这些目录中搜索,否则,dyld 将在 **`/usr/local/lib/`** 中查找(如果进程不受限制),然后在 **`/usr/lib/`** 中查找。
1. `$DYLD_LIBRARY_PATH`
2. 提供的路径(如果不受限制,使用当前工作目录进行相对路径)
3. `$DYLD_FALLBACK_LIBRARY_PATH`
4. `/usr/local/lib/`(如果不受限制)
5. `/usr/lib/`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
如果名称中有斜杠且不是框架,则劫持的方式将是:
* 如果二进制文件是 **不受限制的**,然后可以从 CWD 或 `/usr/local/lib` 加载某些内容(或滥用提到的环境变量之一)
</div>
<div data-gb-custom-block data-tag="hint" data-style='info'>
注意:没有配置文件来 **控制 dlopen 搜索**。
注意:如果主可执行文件是 **set\[ug]id 二进制文件或具有权限的代码签名**,则 **所有环境变量都将被忽略**,只能使用完整路径([检查 DYLD\_INSERT\_LIBRARIES 限制](macos-dyld-hijacking-and-dyld\_insert\_libraries.md#check-dyld\_insert\_librery-restrictions)以获取更详细的信息)。
注意:Apple 平台使用“通用”文件来组合 32 位和 64 位库。这意味着没有 **单独的 32 位和 64 位搜索路径**。
注意:在 Apple 平台上,大多数操作系统 dylibs 被 **组合到 dyld 缓存中**,并且在磁盘上不存在。因此,调用 **`stat()`** 以预检操作系统 dylib 是否存在 **将不起作用**。然而,**`dlopen_preflight()`** 使用与 **`dlopen()`** 相同的步骤来查找兼容的 mach-o 文件。
</div>
**检查路径**
让我们使用以下代码检查所有选项:
```c
// gcc dlopentest.c -o dlopentest -Wl,-rpath,/tmp/test
#include <dlfcn.h>
#include <stdio.h>
int main(void)
{
void* handle;
fprintf("--- No slash ---\n");
handle = dlopen("just_name_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}
fprintf("--- Relative framework ---\n");
handle = dlopen("a/framework/rel_framework_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}
fprintf("--- Abs framework ---\n");
handle = dlopen("/a/abs/framework/abs_framework_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}
fprintf("--- Relative Path ---\n");
handle = dlopen("a/folder/rel_folder_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}
fprintf("--- Abs Path ---\n");
handle = dlopen("/a/abs/folder/abs_folder_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}
return 0;
}
如果你编译并执行它,你可以看到每个库被搜索但未成功找到的位置 。此外,你可以过滤文件系统日志 :
Copy sudo fs_usage | grep "dlopentest"
相对路径劫持
如果一个 特权二进制文件/应用程序 (如 SUID 或某些具有强大权限的二进制文件)正在 加载相对路径 库(例如使用 @executable_path
或 @loader_path
)并且 禁用库验证 ,攻击者可能会将二进制文件移动到一个位置,在那里攻击者可以 修改相对路径加载的库 ,并利用它在进程中注入代码。
修剪 DYLD_*
和 LD_LIBRARY_PATH
环境变量
在文件 dyld-dyld-832.7.1/src/dyld2.cpp
中,可以找到函数 pruneEnvironmentVariables
,该函数将删除任何 以 DYLD_
开头 和 LD_LIBRARY_PATH=
的环境变量。
它还将特定地将环境变量 DYLD_FALLBACK_FRAMEWORK_PATH
和 DYLD_FALLBACK_LIBRARY_PATH
设置为 null ,适用于 suid 和 sgid 二进制文件。
如果目标是 OSX,该函数会从同一文件的 _main
函数中调用,如下所示:
Copy #if TARGET_OS_OSX
if ( ! gLinkContext . allowEnvVarsPrint && ! gLinkContext . allowEnvVarsPath && ! gLinkContext . allowEnvVarsSharedCache ) {
pruneEnvironmentVariables (envp , & apple);
这些布尔标志在代码中的同一文件中设置:
Copy #if TARGET_OS_OSX
// support chrooting from old kernel
bool isRestricted = false ;
bool libraryValidation = false ;
// any processes with setuid or setgid bit set or with __RESTRICT segment is restricted
if ( issetugid () || hasRestrictedSegment (mainExecutableMH) ) {
isRestricted = true ;
}
bool usingSIP = ( csr_check (CSR_ALLOW_TASK_FOR_PID) != 0 );
uint32_t flags;
if ( csops ( 0 , CS_OPS_STATUS , & flags , sizeof (flags)) != - 1 ) {
// On OS X CS_RESTRICT means the program was signed with entitlements
if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {
isRestricted = true ;
}
// Library Validation loosens searching but requires everything to be code signed
if ( flags & CS_REQUIRE_LV ) {
isRestricted = false ;
libraryValidation = true ;
}
}
gLinkContext . allowAtPaths = ! isRestricted;
gLinkContext . allowEnvVarsPrint = ! isRestricted;
gLinkContext . allowEnvVarsPath = ! isRestricted;
gLinkContext . allowEnvVarsSharedCache = ! libraryValidation || ! usingSIP;
gLinkContext . allowClassicFallbackPaths = ! isRestricted;
gLinkContext . allowInsertFailures = false ;
gLinkContext . allowInterposing = true ;
这基本上意味着,如果二进制文件是 suid 或 sgid ,或者在头文件中有 RESTRICT 段,或者它是用 CS_RESTRICT 标志签名的,那么 !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache
为真,环境变量将被修剪。
请注意,如果 CS_REQUIRE_LV 为真,则变量不会被修剪,但库验证将检查它们是否使用与原始二进制文件相同的证书。
检查限制
SUID & SGID
Copy # Make it owned by root and suid
sudo chown root hello
sudo chmod +s hello
# Insert the library
DYLD_INSERT_LIBRARIES = inject.dylib ./hello
# Remove suid
sudo chmod -s hello
Section __RESTRICT
with segment __restrict
Copy gcc -sectcreate __RESTRICT __restrict /dev/null hello.c -o hello-restrict
DYLD_INSERT_LIBRARIES = inject.dylib ./hello-restrict
加固运行时
在钥匙串中创建一个新证书,并使用它来签署二进制文件:
Copy # Apply runtime proetction
codesign -s < cert-nam e > --option=runtime ./hello
DYLD_INSERT_LIBRARIES = inject.dylib ./hello #Library won't be injected
# Apply library validation
codesign -f -s < cert-nam e > --option=library ./hello
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed #Will throw an error because signature of binary and library aren't signed by same cert (signs must be from a valid Apple-signed developer certificate)
# Sign it
## If the signature is from an unverified developer the injection will still work
## If it's from a verified developer, it won't
codesign -f -s < cert-nam e > inject.dylib
DYLD_INSERT_LIBRARIES = inject.dylib ./hello-signed
# Apply CS_RESTRICT protection
codesign -f -s < cert-nam e > --option=restrict hello-signed
DYLD_INSERT_LIBRARIES = inject.dylib ./hello-signed # Won't work
请注意,即使有二进制文件带有标志 0x0(none)
,它们在执行时也可以动态获得 CS_RESTRICT
标志,因此此技术在它们上将无法工作。
您可以使用 (获取 csops 这里 ) 检查一个进程是否具有此标志:
然后检查标志 0x800 是否启用。
参考文献