개요
해당 취약점은 다음 버전에서 패치되었다.
- macOS Tahoe 26.3

무엇이 바꼈나?
여러 파일들을 봐왔지만, 문자열 경로만 바꼈으며, 코드 로직은 거의 변함이 없었다.

이중에서 일반 사용자가 생성할 수 있을만한 /private/tmp/.AppleMiniSetupDidRun 파일을 주목해서 한번 봤다.

트리거 방법?
System Settings 앱에서 새로운 부계정을 생성한 다음에

fs_usage로 파일 사용을 추적해보면,
최초 부계정 로그인 할시에 mbsystemadministration 프로세스에서 /private/tmp/.AppleMiniSetupDidRun 파일을 생성하는 것을 볼 수 있다.
seo@seos-Virtual-Machine ~ % sudo fs_usage -w -f filesys | grep -iE "DidRunFLO|/tmp|var/run"
...
16:18:10.774192 stat64 [ 2] private/tmp/.AppleMiniSetupDidRun 0.000035 mbsystemadministration.48270
16:18:10.774455 open F=3 (_WC_T__________) private/tmp/.AppleMiniSetupDidRun 0.000160 mbsystemadministration.48270
16:18:10.774983 WrData[A] D=0x008799c7 B=0x1000 /dev/disk3s5 private/tmp/.AppleMiniSetupDidRun 0.000486 W mbsystemadminist.48270
mbsystemadministration에서 /tmp/.AppleMiniSetupDidRun 문자열을 xref해봤을때, -[MBSAServer writeMiniBuddyCookieForUser:completion:] 에서 해당 파일을 생성한다.
/tmp/.AppleMiniSetupDidRun 해당 파일이 없더라도 상관없이 생성 가능하며,
void __cdecl -[MBSAServer writeMiniBuddyCookieForUser:completion:](MBSAServer *self, SEL a2, unsigned int a3, id a4)
{
__int64 v4; // x19
void (__fastcall **v5)(id, __int64); // x20
__int64 _18; // [xsp+18h] [xbp+8h]
v4 = *(_QWORD *)&a3;
v5 = (void (__fastcall **)(id, __int64))objc_retain(a4);
sub_100019048(v4);
v5[2](v5, 1);
if ( ((_18 ^ (2 * _18)) & 0x4000000000000000LL) != 0 )
__break(0xC471u);
objc_release(v5);
}
void __fastcall sub_100019048(__int64 a1)
{
NSNumber *v1; // x20
NSArray *v2; // x21
void *v3; // x19
id v4; // x20
NSDictionary *v5; // x21
const __CFString *v6; // [xsp+0h] [xbp-40h] BYREF
void *v7; // [xsp+8h] [xbp-38h] BYREF
NSNumber *v8; // [xsp+10h] [xbp-30h] BYREF
v1 = objc_retainAutoreleasedReturnValue(+[NSNumber numberWithUnsignedInt:](&OBJC_CLASS___NSNumber, "numberWithUnsignedInt:", a1));
v8 = v1;
v2 = objc_retainAutoreleasedReturnValue(+[NSArray arrayWithObjects:count:](&OBJC_CLASS___NSArray, "arrayWithObjects:count:", &v8, 1));
v3 = -[NSArray mutableCopy](v2, "mutableCopy");
objc_release(v2);
objc_release(v1);
v4 = objc_retainAutoreleasedReturnValue(sub_100019170());
if ( v4 )
objc_msgSend(v3, "addObjectsFromArray:", v4);
v6 = CFSTR("uids");
v7 = v3;
v5 = objc_retainAutoreleasedReturnValue(
+[NSDictionary dictionaryWithObjects:forKeys:count:](
&OBJC_CLASS___NSDictionary,
"dictionaryWithObjects:forKeys:count:",
&v7,
&v6,
1));
-[NSDictionary writeToFile:atomically:](v5, "writeToFile:atomically:", CFSTR("/tmp/.AppleMiniSetupDidRun"), 0);
objc_release(v5);
objc_release(v4);
objc_release(v3);
}
id sub_100019170()
{
NSFileManager *v0; // x19
unsigned int v1; // w20
NSDictionary *v2; // x19
id v3; // x20
__int64 v4; // x0
id v5; // x21
__int64 _28; // [xsp+28h] [xbp+8h]
v0 = objc_retainAutoreleasedReturnValue(+[NSFileManager defaultManager](&OBJC_CLASS___NSFileManager, "defaultManager"));
v1 = -[NSFileManager fileExistsAtPath:](v0, "fileExistsAtPath:", CFSTR("/tmp/.AppleMiniSetupDidRun"));
objc_release(v0);
if ( v1 )
{
v2 = objc_retainAutoreleasedReturnValue(
+[NSDictionary dictionaryWithContentsOfFile:](
&OBJC_CLASS___NSDictionary,
"dictionaryWithContentsOfFile:",
CFSTR("/tmp/.AppleMiniSetupDidRun")));
v3 = objc_retainAutoreleasedReturnValue(-[NSDictionary objectForKey:](v2, "objectForKey:", CFSTR("uids")));
v4 = objc_opt_class(&OBJC_CLASS___NSArray);
if ( (objc_opt_isKindOfClass(v3, v4) & 1) != 0 )
v5 = objc_retain(v3);
else
v5 = nullptr;
objc_release(v3);
objc_release(v2);
}
else
{
v5 = nullptr;
}
if ( ((_28 ^ (2 * _28)) & 0x4000000000000000LL) != 0 )
__break(0xC471u);
return objc_autoreleaseReturnValue(v5);
}
-[NSDictionary writeToFile:atomically:] 메소드를 호출하여 아래와 같이 UID 키값이 있는 데이터를 쓸 것이다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "<http://www.apple.com/DTDs/PropertyList-1.0.dtd>">
<plist version="1.0">
<dict>
<key>uids</key>
<array>
<integer>501</integer>
</array>
</dict>
</plist>
하지만 mbsystemadministration 에서 수락받을 수 있는 XPC 클라이언트는 com.apple.private.mbsystemadministration 자격을 필요로 한다.
bool __cdecl -[MBSAServer listener:shouldAcceptNewConnection:](MBSAServer *self, SEL a2, id a3, id a4)
{
id v6; // x19
id v7; // x20
char v8; // w21
id v9; // x23
NSString *v10; // x24
id v11; // x23
id v12; // x23
OS_dispatch_queue *v13; // x24
id v14; // x23
void *v15; // x0
NSString *v16; // x22
_QWORD v18[5]; // [xsp+8h] [xbp-A8h] BYREF
id v19; // [xsp+30h] [xbp-80h] BYREF
id location; // [xsp+38h] [xbp-78h] BYREF
_QWORD block[5]; // [xsp+40h] [xbp-70h] BYREF
id v22; // [xsp+68h] [xbp-48h]
v6 = objc_retain(a3);
v7 = objc_retain(a4);
v8 = sub_100003FAC(CFSTR("com.apple.private.mbsystemadministration"), v7);
v9 = objc_retainAutoreleasedReturnValue(+[MBLogManager sharedManager](&OBJC_CLASS___MBLogManager, "sharedManager"));
if ( (v8 & 1) != 0 )
{
...
}
else
{
v16 = objc_retainAutoreleasedReturnValue(
+[NSString stringWithFormat:](
&OBJC_CLASS___NSString,
"stringWithFormat:",
CFSTR("<Security> Rejecting connection due to lack of entitlement")));
objc_msgSend(
v9,
"logType:inFunction:atLine:withString:",
1,
"-[MBSAServer listener:shouldAcceptNewConnection:]",
238,
v16);
objc_release(v16);
v15 = v9;
}
objc_release(v15);
objc_release(v7);
objc_release(v6);
return v8;
}
아래 링크를 통해 자격을 확인해봤을때 com.apple.private.mbsystemadministration 자격을 가진 프로세스는 딱 4개가 존재한다.
https://codecolor.ist/entdb/os/find?key=com.apple.private.mbsystemadministration&os=mac%2F26.2_25C56

mbsystemadministration 프로세스를 디버깅하여 -[MBSAServer writeMiniBuddyCookieForUser:completion:] 에 브레이크포인트를 잡으면, XPC통신으로 타 프로세스에 와 통신하는 것을 볼 수 있고
seo@seos-Virtual-Machine ~ % sudo lldb
(lldb) process attach --name mbsystemadministration --waitfor
Process 1126 stopped
* thread #1, stop reason = signal SIGSTOP
frame #0: 0x000000019ae7932c dyld`metadata_visitor::Visitor::resolveRebase(metadata_visitor::ResolvedValue const&) const + 4
dyld`metadata_visitor::Visitor::resolveRebase:
-> 0x19ae7932c <+4>: ldr x9, [x9]
0x19ae79330 <+8>: cbz x9, 0x19ae7935c ; <+52>
0x19ae79334 <+12>: ldrb w10, [x0, #0x18]
0x19ae79338 <+16>: tbz w10, #0x0, 0x19ae79368 ; <+64>
Target 0: (mbsystemadministration) stopped.
Executable binary set to "/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/mbsystemadministration".
Architecture set to: arm64e-apple-macosx-.
(lldb) c
Process 1126 resuming
Process 1126 stopped
* thread #2, queue = 'com.apple.NSXPCConnection.user.com.apple.mbsystemadministration.1113', stop reason = breakpoint 1.1
frame #0: 0x0000000100e60ed8 mbsystemadministration`___lldb_unnamed_symbol_10000ced8
mbsystemadministration`___lldb_unnamed_symbol_10000ced8:
-> 0x100e60ed8 <+0>: pacibsp
0x100e60edc <+4>: stp x20, x19, [sp, #-0x20]!
0x100e60ee0 <+8>: stp x29, x30, [sp, #0x10]
0x100e60ee4 <+12>: add x29, sp, #0x10
Target 0: (mbsystemadministration) stopped.
(lldb) bt
* thread #2, queue = 'com.apple.NSXPCConnection.user.com.apple.mbsystemadministration.1113', stop reason = breakpoint 1.1
* frame #0: 0x0000000100e60ed8 mbsystemadministration`___lldb_unnamed_symbol_10000ced8
frame #1: 0x000000019b26bdc4 CoreFoundation`__invoking___ + 148
frame #2: 0x000000019b26bc4c CoreFoundation`-[NSInvocation invoke] + 424
frame #3: 0x000000019cabd4d0 Foundation`__NSXPCCONNECTION_IS_CALLING_OUT_TO_EXPORTED_OBJECT__ + 16
frame #4: 0x000000019d5ee460 Foundation`-[NSXPCConnection _decodeAndInvokeMessageWithEvent:reply:flags:] + 1208
frame #5: 0x000000019d5efde0 Foundation`message_handler_message + 88
frame #6: 0x000000019ca833ac Foundation`message_handler + 152
frame #7: 0x000000019aec26f0 libxpc.dylib`_xpc_connection_call_event_handler + 112
frame #8: 0x000000019aec0f80 libxpc.dylib`_xpc_connection_mach_event + 1488
frame #9: 0x000000019b03eb1c libdispatch.dylib`_dispatch_client_callout4 + 16
frame #10: 0x000000019b04139c libdispatch.dylib`_dispatch_mach_msg_invoke + 464
frame #11: 0x000000019b02d350 libdispatch.dylib`_dispatch_lane_serial_drain + 332
frame #12: 0x000000019b042104 libdispatch.dylib`_dispatch_mach_invoke + 472
frame #13: 0x000000019b02d350 libdispatch.dylib`_dispatch_lane_serial_drain + 332
frame #14: 0x000000019b02dff8 libdispatch.dylib`_dispatch_lane_invoke + 440
frame #15: 0x000000019b038474 libdispatch.dylib`_dispatch_root_queue_drain_deferred_wlh + 292
frame #16: 0x000000019b037d6c libdispatch.dylib`_dispatch_workloop_worker_thread + 692
frame #17: 0x000000019b1dde4c libsystem_pthread.dylib`_pthread_wqthread + 292
(lldb)
/System/Library/CoreServices/Setup Assistant.app/Contents/MacOS/Setup Assistant 에서 XPC 연결을 시도한다.
(lldb) po [NSXPCConnection currentConnection]
<NSXPCConnection: 0xc44c4c280> connection from pid 1113 on mach service named com.apple.mbsystemadministration
(lldb) shell ps -A | grep 1113
1113 ?? 0:01.67 /System/Library/CoreServices/Setup Assistant.app/Contents/MacOS/Setup Assistant -MiniBuddyYes
1380 ttys001 0:00.01 /bin/sh -c ps -A | grep 1113
1382 ttys001 0:00.00 grep 111
정리하자면, XPC 연결을 하는데에 앞서 클라이언트는 /System/Library/CoreServices/Setup Assistant.app/Contents/MacOS/Setup Assistant, 서버는 /System/Library/CoreServices/Setup Assistant.app/Contents/Resources/mbsystemadministration이 되겠다.
해당 XPC 서버는 com.apple.private.mbsystemadministration 자격을 갖춘 클라이언트만 수용하고 있고, 보통은 listener:shouldAcceptNewConnection: 메소드에서 자격이 필요한지 안한지 확인할 수 있다.
이제 클라이언트 프로세스인 Setup Assistant 를 확인해보자.
일반 사용자가 터미널에서 Setup Assistant 를 실행만하면, applicationDidFinishLaunching: → writeMiniBuddyCookieForUser:completion: 메소드가 순서대로 호출되어 mbsystemadministration XPC 서버에게 /tmp/.AppleMiniSetupDidRun 파일 생성을 요청한다.
void __cdecl -[MacBuddyAppDelegate applicationDidFinishLaunching:](MacBuddyAppDelegate *self, SEL a2, id a3)
{
//...
{
v48 = objc_retainAutoreleasedReturnValue(+[MBSAConnection sharedConnection](&OBJC_CLASS___MBSAConnection, "sharedConnection"));
objc_msgSend(
v48,
"writeMiniBuddyCookieForUser:completion:",
-[MacBuddyAppDelegate miniBuddyTargetUID](self, "miniBuddyTargetUID"),
&stru_100283028);
objc_release(v48);
}
objc_release(v8);
objc_release(v7);
if ( ((_58 ^ (2 * _58)) & 0x4000000000000000LL) != 0 )
__break(0xC471u);
objc_release(v6);
}
실제로 실행해서 확인해보면, /private/tmp 경로에 .AppleMiniSetupDidRun 파일이 생성되있다.
seo@seos-Virtual-Machine ~ % open /System/Library/CoreServices/Setup\ Assistant.app
seo@seos-Virtual-Machine ~ % ls -la /private/tmp
total 8
drwxrwxrwt 10 root wheel 320 7월 12 20:44 .
drwxr-xr-x 6 root wheel 192 7월 12 18:47 ..
-rw-r--r-- 1 root wheel 374 7월 12 20:44 .AppleMiniSetupDidRun
...

악용 가능성
root 권한 소유의 파일을 /private/tmp/.AppleMiniSetupDidRun 에 심볼릭 링크로 걸어둔다면, 해당 파일을 아래 내용과 같은 형식으로 변조할 수 있다. uid 값을 제외하고는 완전히 파일 내용을 제어할 수는 없기 때문에 파급력이 크진 않다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "<http://www.apple.com/DTDs/PropertyList-1.0.dtd>">
<plist version="1.0">
<dict>
<key>uids</key>
<array>
<integer>(UID)</integer>
</array>
</dict>
</plist>