22 KiB
macOS库注入
☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 YouTube 🎥
- 你在一家网络安全公司工作吗?你想在HackTricks中看到你的公司广告吗?或者你想获得PEASS的最新版本或下载PDF格式的HackTricks吗?请查看订阅计划!
- 发现我们的独家NFT收藏品The PEASS Family
- 获取官方PEASS和HackTricks周边产品
- 加入💬 Discord群组 或 Telegram群组 或 关注我在Twitter上的🐦@carlospolopm。
- 通过向hacktricks repo 和hacktricks-cloud repo 提交PR来分享你的黑客技巧。
{% hint style="danger" %} dyld的代码是开源的,可以在https://opensource.apple.com/source/dyld/找到,并且可以使用类似https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz的URL下载tar文件。 {% endhint %}
DYLD_INSERT_LIBRARIES
这是一个以冒号分隔的动态库列表,在指定程序之前加载。这使您可以通过加载一个临时的动态共享库,其中只包含新模块,来测试用于平面命名空间映像中使用的现有动态共享库的新模块。请注意,这对使用动态共享库构建的二级命名空间映像没有任何影响,除非还使用了DYLD_FORCE_FLAT_NAMESPACE。
这类似于Linux上的LD_PRELOAD。
这种技术也可以用作ASEP技术,因为每个安装的应用程序都有一个名为"Info.plist"的plist文件,允许使用名为LSEnvironmental
的键来分配环境变量。
{% hint style="info" %}
自2012年以来,Apple已大大降低了DYLD_INSERT_LIBRARIES
的权限。
转到代码并检查src/dyld.cpp
。在函数pruneEnvironmentVariables
中,您可以看到DYLD_*
变量被删除。
在函数processRestricted
中,设置了限制的原因。检查该代码,您可以看到原因是:
- 二进制文件是
setuid/setgid
- 在macho二进制文件中存在
__RESTRICT/__restrict
部分。 - 软件具有没有
com.apple.security.cs.allow-dyld-environment-variables
权限或com.apple.security.cs.disable-library-validation
权限的强化运行时权限。 - 使用以下命令检查二进制文件的权限:
codesign -dv --entitlements :- </path/to/bin>
- 如果库与二进制文件使用不同的证书签名
- 如果库和二进制文件使用相同的证书签名,这将绕过先前的限制
- 具有权限**
system.install.apple-software
和system.install.apple-software.standar-user
**的程序可以在不要求用户输入密码的情况下安装由Apple签名的软件(特权升级)
在更新的版本中,您可以在函数**configureProcessRestrictions
的第二部分找到此逻辑。但是,在较新的版本中执行的是函数的开始检查**(您可以删除与iOS或模拟相关的if语句,因为这些在macOS中不会使用)。
{% endhint %}
您可以使用codesign --display --verbose <bin>
检查二进制文件是否具有强化运行时权限,并检查**CodeDirectory
中的运行时标志,例如:CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
**
在以下位置找到有关如何(滥用)使用此技术并检查限制的示例:
{% content-ref url="../../macos-dyld-hijacking-and-dyld_insert_libraries.md" %} macos-dyld-hijacking-and-dyld_insert_libraries.md {% endcontent-ref %}
Dylib劫持
{% hint style="danger" %} 请记住,执行Dylib劫持攻击时,先前的限制也适用。 {% endhint %}
与Windows一样,在MacOS中,您也可以劫持dylib以使应用程序执行任意代码。
然而,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
函数中,当LC_LOAD_WEAK_DYLIB
为true时,lib->required
只有在false
时才为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_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`
{% hint style="info" %}
**`@executable_path`**:是包含**主可执行文件**的目录的**路径**。
**`@loader_path`**:是包含包含加载命令的Mach-O二进制文件的**目录**的**路径**。
* 在可执行文件中使用**`@loader_path`**时,它实际上与**`@executable_path`**相同。
* 在**dylib**中使用**`@loader_path`**时,它给出了**dylib**的**路径**。
{% endhint %}
滥用此功能来**提升特权**的方式是,在以**root**身份执行的**应用程序**中,寻找某个**库**时,攻击者具有写权限的某个**文件夹**。
{% hint style="success" %}
一个很好的用于查找应用程序中**缺少库**的**扫描工具**是[**Dylib Hijack Scanner**](https://objective-see.com/products/dhs.html)或[**CLI版本**](https://github.com/pandazheng/DylibHijack)。
关于这种技术的一个带有技术细节的很好的**报告**可以在[**这里**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x)找到。
{% endhint %}
**示例**
{% content-ref 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)
{% endcontent-ref %}
## 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`**)。
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(如果无限制)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/`(如果无限制)
6. `/usr/lib/`
{% hint style="danger" %}
如果名称中没有斜杠,有两种方法可以进行劫持:
* 如果任何**`LC_RPATH`**是**可写的**(但是签名会被检查,所以为此还需要二进制文件是无限制的)
* 如果二进制文件是**无限制的**,然后可以从CWD加载某些内容(或滥用上述环境变量之一)
{% endhint %}
* 当路径**看起来像一个框架路径**时(例如`/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`
{% hint style="danger" %}
如果是框架路径,劫持的方式是:
* 如果进程是**无限制的**,滥用CWD的**相对路径**和上述环境变量(即使在文档中没有提到如果进程受限制,DYLD\_\*环境变量会被删除)
{% endhint %}
* 当路径**包含斜杠但不是框架路径**时(即完整路径或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/`
{% hint style="danger" %}
如果名称中有斜杠而不是框架,则劫持它的方法是:
* 如果二进制文件是**无限制的**,则可以从CWD或`/usr/local/lib`加载内容(或滥用其中一个环境变量)
{% endhint %}
{% hint 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平台上,大多数操作系统dylib都**合并到dyld缓存中**,并且不存在于磁盘上。因此,调用**`stat()`**来预先检查操作系统dylib是否存在**不起作用**。但是,**`dlopen_preflight()`**使用与**`dlopen()`**相同的步骤来查找兼容的mach-o文件。
{% endhint %}
**检查路径**
让我们使用以下代码检查所有选项:
```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;
}
如果您编译并执行它,您可以看到每个库未成功搜索的位置。此外,您可以过滤文件系统日志:
sudo fs_usage | grep "dlopentest"
删除DYLD_*
和LD_LIBRARY_PATH
环境变量
在文件dyld-dyld-832.7.1/src/dyld2.cpp
中,可以找到函数**pruneEnvironmentVariables
,它将删除任何以DYLD_
和LD_LIBRARY_PATH=
**开头的环境变量。
对于suid和sgid二进制文件,它还会将环境变量**DYLD_FALLBACK_FRAMEWORK_PATH
和DYLD_FALLBACK_LIBRARY_PATH
设置为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
段
The __RESTRICT
section is a segment in macOS that is used to restrict access to certain libraries and prevent unauthorized processes from injecting code into them. This section is specifically designed to enhance the security of macOS by preventing privilege escalation attacks through library injection.
The __restrict
segment, on the other hand, is a specific area within the __RESTRICT
section that contains code and data that are restricted from modification or injection. This segment is heavily protected by macOS to ensure the integrity and security of the libraries it contains.
By leveraging the __RESTRICT
section and the __restrict
segment, macOS can effectively mitigate the risks associated with library injection attacks, making it more difficult for malicious actors to exploit vulnerabilities and gain unauthorized access to sensitive system resources.
__RESTRICT
部分与__restrict
段
__RESTRICT
部分是macOS中的一个段,用于限制对某些库的访问,并防止未经授权的进程向其注入代码。该部分专门设计用于增强macOS的安全性,通过防止通过库注入进行权限提升攻击。
另一方面,__restrict
段是__RESTRICT
部分中的一个特定区域,其中包含受限制的代码和数据,禁止进行修改或注入。macOS对该段进行了严格保护,以确保其中包含的库的完整性和安全性。
通过利用__RESTRICT
部分和__restrict
段,macOS可以有效地减轻与库注入攻击相关的风险,使恶意行为者更难利用漏洞并未授权地访问敏感系统资源。
gcc -sectcreate __RESTRICT __restrict /dev/null hello.c -o hello-restrict
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-restrict
强化运行时
在钥匙串中创建一个新的证书,并使用它对二进制文件进行签名:
{% code overflow="wrap" %}
# 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
{% endcode %}
{% hint style="danger" %}
请注意,即使有用标志**0x0(none)
签名的二进制文件,当执行时它们也可以动态地获得CS_RESTRICT
**标志,因此这种技术在它们上面不起作用。
您可以使用以下命令检查进程是否具有此标志(获取csops here):
csops -status <pid>
然后检查是否启用了标志位0x800。 {% endhint %}
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- 你在一家网络安全公司工作吗?想要在HackTricks中宣传你的公司吗?或者想要获取PEASS的最新版本或下载PDF格式的HackTricks吗?请查看订阅计划!
- 发现我们的独家NFT收藏品The PEASS Family
- 获取官方PEASS和HackTricks周边产品
- 加入💬 Discord群组 或 Telegram群组,或者关注我在Twitter上的🐦@carlospolopm。
- 通过向hacktricks repo 和hacktricks-cloud repo 提交PR来分享你的黑客技巧。