Make Debugging Great Again (on Liber* Jailbroken devices)
A quick tutorial on how to get debugserver working without crashing any programs
Jonathan Levin, (@Morpheus______), http://newosxbook.com/ - 02/28/2018
About
Apple's hardening of iOS 11.x is formidable, but no match if you have kernel read/write primitives. The QiLin toolkit gets around most of them by directly patching the various protections in kernel memory.
Getting debugging to work again is a bit tricky, owing to intercepting process startup. There are two different approaches to this problem. One, which I think is unwise at best, is hooking launchd with all sorts of injected code. The other, is a set of CLIs, which - a bit inconvenient - are certainly far better in terms of maintaining system stability and zero disruption.
Platformize
The first challenge is getting debugserver over the hurdle of task_conversion_eval. I described this problem in MOXiI Volume I and III (particularly, in the QiLin writeup). Even with task_for_pid-allow, you still need to be marked as a platform binary to be able to get the target task port of some other process, if its binary, too, is platform. Note, this is platform binary, and not platform-application (which you need to begin with in order to execute past the sandbox's platform profile).
I tweeted the following image about this:
As you can see, before platformizing, debugserver is "unable to start the exception thread". This is because it needs the victim task's exception ports, which it can't get for lack of being a platform binary. Along comes platformize, which is a super simple binary to compile whose source and binary you can get on the QiLin page. What platformize does is call QiLin's spawnAndPlatformize, which - once in possession of the kernel task port - can access kernel memory and patch the necessary bit (TF_PLATFORM in the task's tf_flags, at offset 0x3a8 if I recall correctly), and that's that. It spawns debugserver suspended, patches, and lets it continue.
debugserver needs to be resigned with the following entitlements:
platform-application: so it can runtask_for_pid-allow: so it can get the task portcom.apple.system-task-ports: so it can get Apple binary task ports too
seatbelt-profiles from debugserver.
For the impatient, that means the last entitlements of your debugserver should look like this:
.. <key>run-unsigned-code</key> <true/> <key>com.apple.system-task-ports</key> <true/> <key>task_for_pid-allow</key> <true/> <key>platform-application</key> <true/>
Bypassing Code signing..
Doing the above will get you debugging and the task port, but you'll still see a peculiar behavior - your target will die as soon as you let it run with a break point, even if the breakpoint itself was not hit. Observe the following:
root@Phontifex-3 (~) # lldb -p 9295
(lldb) process attach --pid 9295
Process 9295 stopped
* thread #1: tid = 0x2f0658, 0x00000001826b8170 CoreFoundation`__CFStrHashEightBit2, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00000001826b8170 CoreFoundation`__CFStrHashEightBit2
CoreFoundation`__CFStrHashEightBit2:
-> 0x1826b8170 <+0>: movz w8, #0x406, lsl #16
0x1826b8174 <+4>: movk w8, #0x401
0x1826b8178 <+8>: movz w9, #0x103, lsl #16
0x1826b817c <+12>: movk w9, #0x301
Executable module set to "/var/containers/Bundle/Application/3F21FB14-547F-4CA0-AE28-CD89A0471619/News.app/News".
Architecture set to: arm64-apple-ios.
(lldb) b xpc_connection_send_message_with_reply_sync
Breakpoint 1: where = libxpc.dylib`xpc_connection_send_message_with_reply_sync, address = 0x0000000182363e2c
(lldb) c
Process 9295 resuming
Process 9295 stopped
* thread #1: tid = 0x2f0658, 0x0000000182361ee0 libxpc.dylib`_xpc_connection_reply_callout + 60, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=50, address=0x182361ee0)
frame #0: 0x0000000182361ee0 libxpc.dylib`_xpc_connection_reply_callout + 60
libxpc.dylib`_xpc_connection_reply_callout:
-> 0x182361ee0 <+60>: add w0, w21, #1 ; =1
0x182361ee4 <+64>: mov x1, x19
0x182361ee8 <+68>: bl 0x182367a60 ; _xpc_ktrace_pid1
0x182361eec <+72>: ldp x29, x30, [sp, #32]
(lldb) c
Process 9295 resuming
Process 9295 exited with status = 0 (0x00000000) Terminated due to code signing error
You might want to take a minute to reflect on this behavior . Note that EXC_BAD_ACCESS was generated at 0x182361ee0, whereas the breakpoint was at 0x182363e2c. So.. why? (answer below)
run-unsigned-code was supposed to take care of this, and you can also fix this in other ways, but the simplest method is to flip GET_TASK_ALLOW (0x4) in the code signing flags. So a third QiLin tool , csflags now does this on any pid of your choice:
root@Phontifex-3 (~) # ps -ef | grep News$
501 9320 1 0 11:21AM ?? 0:04.50 /var/containers/Bundle/Application/3F21FB14-547F-4CA0-AE28-CD89A0471619/News.app/News
root@Phontifex-3 (~) # /jb/csflags 9320 0x4
2018-05-09 11:22:12.587 csflags[9335:3085328] STATUS: Got 64-bit kernel. Great
2018-05-09 11:22:12.594 csflags[9335:3085328] STATUS: Loaded The QiLin Toolkit for Darwin 17.2.0 Darwin Kernel Version 17.2.0: Fri Sep 29 18:14:50 PDT 2017; root:xnu-4570.20.62~4/RELEASE_ARM64_T8010 iPhone9,1 - Phontifex-3
...
-- Current CS Flags of process (@0xffffffe0068ca738): 0x0
-- process CS Flags @0xffffffe0068ca738 set to 0x4 (RC: 0)
VNODE INFO :
My blob is @0xffffffe003b49200
BLOB CS FLAGS: 0x3000024
BLOB CS FLAGS NOW: 0x3000024
RC: 0
root@Phontifex-3 (~) # lldb -p 9320
(lldb) process attach --pid 9320
b xpc_connection_send_message_with_replProcess 9320 stopped
* thread #1: tid = 0x2f12a9, 0x00000001821f4bc4 libsystem_kernel.dylib`mach_msg_trap + 8, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00000001821f4bc4 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
-> 0x1821f4bc4 <+8>: ret
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x1821f4bc8 <+0>: movn x16, #0x1f
0x1821f4bcc <+4>: svc #0x80
0x1821f4bd0 <+8>: ret
Executable module set to "/var/containers/Bundle/Application/3F21FB14-547F-4CA0-AE28-CD89A0471619/News.app/News".
Architecture set to: arm64-apple-ios.
(lldb) b xpc_connection_send_message_with_reply
Breakpoint 1: where = libxpc.dylib`xpc_connection_send_message_with_reply, address = 0x0000000182360b34
(lldb) b xpc_connection_send_message_with_reply_sync
Breakpoint 2: where = libxpc.dylib`xpc_connection_send_message_with_reply_sync, address = 0x0000000182363e2c
(lldb) c
Process 9320 resuming
Process 9320 stopped
* thread #1: tid = 0x2f12a9, 0x0000000182360b34 libxpc.dylib`xpc_connection_send_message_with_reply, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000182360b34 libxpc.dylib`xpc_connection_send_message_with_reply
libxpc.dylib`xpc_connection_send_message_with_reply:
-> 0x182360b34 <+0>: stp x26, x25, [sp, #-80]!
0x182360b38 <+4>: stp x24, x23, [sp, #16]
0x182360b3c <+8>: stp x22, x21, [sp, #32]
0x182360b40 <+12>: stp x20, x19, [sp, #48]
(lldb) po $x0
{ name = com.apple.UIKit.KeyboardManagement.hosted, listener = false, pid = 9088, euid = 501, egid = 501, asid = 0 }>
(lldb) c
Process 9320 resuming
And you have debugging again :-)
Another option: fake sign with get-task-allow.
Q&A
Advertisement
After a wonderful training in NYC last week, there's another MOXiI training set for July 9th-13th - this time on the west coast. Also, if you're coming to BlackHat - we have the followup to MOXiI - applied *OS Security/Insecurity - in a special two day session. I'll show a lot more QiLin there.
* - Answer - the breakpoint invalidated the entire page (0x182360000-0x182363fff, 16K on ARM64) because it overwrites the code.