바이너리의 권한을 확인하려면: codesign -dv --entitlements :- </path/to/bin>
더 최신 버전에서는 이 논리를 configureProcessRestrictions 함수의 두 번째 부분에서 찾을 수 있습니다. 그러나 최신 버전에서 실행되는 것은 함수의 시작 검사입니다(이것은 macOS에서 사용되지 않으므로 iOS 또는 시뮬레이션과 관련된 if를 제거할 수 있습니다).
Library Validation
바이너리가 DYLD_INSERT_LIBRARIES 환경 변수를 사용하도록 허용하더라도, 바이너리가 로드할 라이브러리의 서명을 확인하면 사용자 정의 라이브러리를 로드하지 않습니다.
또는 바이너리가 강화된 런타임 플래그 또는 라이브러리 검증 플래그를 가지지 않아야 합니다.
바이너리가 강화된 런타임을 가지고 있는지 확인하려면 codesign --display --verbose <bin>을 사용하여 **CodeDirectory**에서 플래그 런타임을 확인하세요: CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
바이너리와 같은 인증서로 서명된 라이브러리를 로드할 수도 있습니다.
이것을 (악용)하는 방법과 제한 사항을 확인하려면:
Dylib Hijacking
이전 라이브러리 검증 제한 사항이 Dylib 하이재킹 공격을 수행하는 데에도 적용됩니다.
Windows와 마찬가지로 MacOS에서도 dylibs를 하이재킹하여 애플리케이션이 임의의코드를 실행하도록 만들 수 있습니다(사실 일반 사용자에게는 TCC 권한이 필요할 수 있으므로 .app 번들 내에서 쓰기 위해 하이재킹된 라이브러리를 사용할 수 없을 수 있습니다).
그러나 MacOS 애플리케이션이 라이브러리를 로드하는 방식은Windows보다 더 제한적입니다. 이는 악성 소프트웨어 개발자가 여전히 이 기술을 은폐를 위해 사용할 수 있지만, 권한 상승을 악용할 가능성은 훨씬 낮습니다.
우선, MacOS 바이너리가 로드할 라이브러리의 전체 경로를 지정하는 것이 더 일반적입니다. 둘째, MacOS는 절대 경로에서 라이브러리를 검색하지 않습니다.
이 기능과 관련된 주요 부분은 **ImageLoader::recursiveLoadLibraries**에 있습니다 ImageLoader.cpp.
macho 바이너리가 라이브러리를 로드하는 데 사용할 수 있는 4가지 다른 헤더 명령이 있습니다:
LC_LOAD_DYLIB 명령은 dylib를 로드하는 일반적인 명령입니다.
LC_LOAD_WEAK_DYLIB 명령은 이전 명령과 유사하지만, dylib가 발견되지 않으면 오류 없이 실행이 계속됩니다.
LC_REEXPORT_DYLIB 명령은 다른 라이브러리의 기호를 프록시(또는 재수출)합니다.
LC_LOAD_UPWARD_DYLIB 명령은 두 라이브러리가 서로 의존할 때 사용됩니다(이를 _상향 의존성_이라고 합니다).
그러나 dylib 하이재킹에는 2가지 유형이 있습니다:
누락된 약한 연결 라이브러리: 이는 애플리케이션이 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
* **@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>
이 기능을 악용하여 **권한을 상승시키는 방법**은 **루트**에 의해 실행되는 **애플리케이션**이 **공격자가 쓰기 권한을 가진 폴더에서 라이브러리를 찾고 있는** 드문 경우에 해당합니다.
<div data-gb-custom-block data-tag="hint" data-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)에서 찾을 수 있습니다.
</div>
**Example**
<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 Hijacking
<div data-gb-custom-block data-tag="hint" data-style='danger'>
이전 라이브러리 검증 제한 사항이 **Dlopen 하이재킹 공격을 수행하는 데에도 적용됩니다**.
</div>
**`man dlopen`**에서:
* 경로에 **슬래시 문자가 포함되지 않으면**(즉, 단순한 리프 이름인 경우) **dlopen()이 검색을 수행합니다**. **`$DYLD_LIBRARY_PATH`**가 시작 시 설정된 경우, dyld는 먼저 **해당 디렉토리**를 **확인합니다**. 다음으로, 호출된 macho 파일이나 주 실행 파일이 **`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 플랫폼에서 대부분의 OS dylibs는 **dyld 캐시에 결합되어** 있으며 디스크에 존재하지 않습니다. 따라서 OS dylib가 존재하는지 사전 확인하기 위해 **`stat()`**를 호출하는 것은 **작동하지 않습니다**. 그러나 **`dlopen_preflight()`**는 호환 가능한 mach-o 파일을 찾기 위해 **`dlopen()`**과 동일한 단계를 사용합니다.
</div>
**Check paths**
다음 코드를 사용하여 모든 옵션을 확인해 보겠습니다:
```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;
}
만약 당신이 그것을 컴파일하고 실행하면 각 라이브러리가 어디에서 실패했는지 검색된 위치를 볼 수 있습니다. 또한, FS 로그를 필터링할 수 있습니다:
sudofs_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=**를 제거합니다.
또한 suid 및 sgid 이진 파일에 대해 DYLD_FALLBACK_FRAMEWORK_PATH 및 DYLD_FALLBACK_LIBRARY_PATH 환경 변수를 null로 설정합니다.
#ifTARGET_OS_OSX// support chrooting from old kernelbool isRestricted =false;bool libraryValidation =false;// any processes with setuid or setgid bit set or with __RESTRICT segment is restrictedif ( 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 entitlementsif ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {isRestricted =true;}// Library Validation loosens searching but requires everything to be code signedif ( 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**가 true가 되고 환경 변수는 제거됩니다.
CS_REQUIRE_LV가 true인 경우, 변수는 제거되지 않지만 라이브러리 검증은 원래 이진 파일과 동일한 인증서를 사용하고 있는지 확인합니다.
제한 사항 확인
SUID & SGID
# Make it owned by root and suidsudochownroothellosudochmod+shello# Insert the libraryDYLD_INSERT_LIBRARIES=inject.dylib./hello# Remove suidsudochmod-shello
# Apply runtime proetctioncodesign-s<cert-name>--option=runtime./helloDYLD_INSERT_LIBRARIES=inject.dylib./hello#Library won't be injected# Apply library validationcodesign-f-s<cert-name>--option=library./helloDYLD_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'tcodesign-f-s<cert-name>inject.dylibDYLD_INSERT_LIBRARIES=inject.dylib./hello-signed# Apply CS_RESTRICT protectioncodesign-f-s<cert-name>--option=restricthello-signedDYLD_INSERT_LIBRARIES=inject.dylib./hello-signed# Won't work
이진 파일이 0x0(none) 플래그로 서명되어 있더라도, 실행될 때 동적으로 CS_RESTRICT 플래그를 가질 수 있으므로 이 기술은 그들에 대해 작동하지 않습니다.