macOS Library Injection
dyld的代码是开源的,可以在https://opensource.apple.com/source/dyld/找到,也可以使用类似https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz的URL下载tar文件。
Dyld进程
查看Dyld如何在二进制文件中加载库:
macOS Dyld ProcessDYLD_INSERT_LIBRARIES
这类似于Linux上的LD_PRELOAD。它允许指示一个进程将从路径加载特定库(如果启用了环境变量)。
这种技术也可以用作ASEP技术,因为每个安装的应用程序都有一个名为"Info.plist"的属性列表,允许使用名为LSEnvironmental
的键分配环境变量。
库验证
即使二进制文件允许使用**DYLD_INSERT_LIBRARIES
**环境变量,如果二进制文件检查要加载的库的签名,它将不会加载自定义内容。
为了加载自定义库,二进制文件需要具有以下授权之一:
或者二进制文件不应该具有强化运行时标志或库验证标志。
您可以使用codesign --display --verbose <bin>
检查二进制文件是否具有强化运行时,检查**CodeDirectory
中的标志运行时,例如:CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
**
如果使用与二进制文件相同的证书签名,也可以加载库。
找到一个关于如何(滥用)利用此功能并检查限制的示例:
macOS Dyld Hijacking & DYLD_INSERT_LIBRARIESDylib劫持
请记住以前的库验证限制也适用于执行Dylib劫持攻击。
与Windows一样,在MacOS中,您也可以劫持dylibs以使应用程序执行任意 代码(实际上,从普通用户这样做可能不可能,因为您可能需要TCC权限才能写入.app
包并劫持库)。
然而,MacOS应用程序加载库的方式比Windows更受限制。这意味着恶意软件开发人员仍然可以使用此技术进行隐蔽,但是滥用此技术以提升权限的可能性要低得多。
首先,更常见的是发现MacOS二进制文件指示库的完整路径。其次,MacOS从不在 $PATH 文件夹中搜索库。
与此功能相关的主要代码部分位于ImageLoader.cpp
中的**ImageLoader::recursiveLoadLibraries
**中。
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.cpp
的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
配置为@rpath:Mach-O二进制文件可以具有**
LC_RPATH
和LC_LOAD_DYLIB
命令。根据这些命令的值**,库将从不同目录加载。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
利用这种功能进行 权限提升 的方式是在罕见情况下,由 root 执行的 应用程序 正在 查找 一些 库,而攻击者具有写权限的某个文件夹中存在该库。
一个很好的 扫描工具,用于查找应用程序中的 缺失库 是 Dylib Hijack Scanner 或 CLI 版本。 关于这种技术的一个带有技术细节的不错的 报告 可以在 这里 找到。
示例
macOS Dyld Hijacking & DYLD_INSERT_LIBRARIESDlopen 劫持
请记住,执行 Dlopen 劫持攻击时也适用 先前的库验证限制。
来自 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
)。
$DYLD_LIBRARY_PATH
LC_RPATH
CWD
(如果不受限制)$DYLD_FALLBACK_LIBRARY_PATH
/usr/local/lib/
(如果不受限制)/usr/lib/
如果名称中没有斜杠,有两种方法可以进行劫持:
如果任何
LC_RPATH
是 可写的(但会检查签名,因此您还需要二进制文件是不受限制的)如果二进制文件是 不受限制的,那么可以从 CWD 中加载内容(或滥用其中提到的环境变量之一)
当路径 看起来像一个框架 路径(例如
/stuff/foo.framework/foo
),如果在启动时设置了$DYLD_FRAMEWORK_PATH
,dyld 将首先在该目录中查找 框架部分路径(例如foo.framework/foo
)。接下来,dyld 将尝试使用 提供的路径(对于相对路径,使用当前工作目录)。最后,对于旧的二进制文件,dyld 将尝试一些回退。如果在启动时设置了$DYLD_FALLBACK_FRAMEWORK_PATH
,dyld 将搜索这些目录。否则,它将搜索/Library/Frameworks
(在 macOS 上,如果进程是不受限制的),然后在/System/Library/Frameworks
中搜索。
$DYLD_FRAMEWORK_PATH
提供的路径(对于相对路径,如果不受限制,使用当前工作目录)
$DYLD_FALLBACK_FRAMEWORK_PATH
/Library/Frameworks
(如果不受限制)/System/Library/Frameworks
如果是框架路径,劫持的方式是:
如果进程是 不受限制的,可以滥用从 CWD 开始的 相对路径 和提到的环境变量(即使在文档中没有提到进程是否受限制,DYLD_* 环境变量会被移除)
当路径 包含斜杠但不是框架路径(即完整路径或指向 dylib 的部分路径),dlopen() 首先在(如果设置了)
$DYLD_LIBRARY_PATH
中查找(使用路径的叶子部分)。接下来,dyld 将尝试使用 提供的路径(对于相对路径,仅对于不受限制的进程使用当前工作目录)。最后,对于旧的二进制文件,dyld 将尝试一些回退。如果在启动时设置了$DYLD_FALLBACK_LIBRARY_PATH
,dyld 将在这些目录中搜索,否则,dyld 将在/usr/local/lib/
中查找(如果进程是不受限制的),然后在/usr/lib/
中查找。
$DYLD_LIBRARY_PATH
提供的路径(对于相对路径,如果不受限制,使用当前工作目录)
$DYLD_FALLBACK_LIBRARY_PATH
/usr/local/lib/
(如果不受限制)/usr/lib/
如果名称中有斜杠而不是框架,劫持的方式是:
如果二进制文件是 不受限制的,那么可以从 CWD 或
/usr/local/lib
中加载内容(或滥用其中提到的环境变量之一)
检查路径
让我们使用以下代码检查所有选项:
// 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;
}
如果您编译并执行它,您可以看到每个库未成功搜索的位置。此外,您可以过滤FS日志:
sudo fs_usage | grep "dlopentest"
相对路径劫持
如果一个特权二进制应用程序(比如一个SUID或一些拥有强大权限的二进制文件)正在加载一个相对路径库(例如使用@executable_path
或@loader_path
),并且禁用了库验证,那么可能会将二进制文件移动到攻击者可以修改相对路径加载的库的位置,并滥用它来向进程注入代码。
修剪 DYLD_*
和 LD_LIBRARY_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
这两个环境变量对于suid和sgid二进制文件设置为null**。
如果针对OSX,可以从同一文件的**_main
**函数中调用此函数:
#if TARGET_OS_OSX
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);
并且这些布尔标志在代码中的同一文件中设置:
#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
# 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
区块 __RESTRICT
与段 __restrict
__RESTRICT
与段 __restrict
gcc -sectcreate __RESTRICT __restrict /dev/null hello.c -o hello-restrict
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-restrict
强化运行时
在钥匙串中创建一个新证书,并使用它来签署二进制文件:
# Apply runtime proetction
codesign -s <cert-name> --option=runtime ./hello
DYLD_INSERT_LIBRARIES=inject.dylib ./hello #Library won't be injected
# Apply library validation
codesign -f -s <cert-name> --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-name> inject.dylib
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed
# Apply CS_RESTRICT protection
codesign -f -s <cert-name> --option=restrict hello-signed
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed # Won't work
请注意,即使有用标志**`0x0(none)`**签名的二进制文件,当执行时也可以动态地获得**`CS_RESTRICT`**标志,因此这种技术在其中不起作用。
您可以使用以下命令检查进程是否具有此标志(获取csops here):
csops -status <pid>
参考资料
最后更新于