This document discusses methods for jailbreaking iOS devices by exploiting vulnerabilities to escalate privileges and disable security mitigations. It summarizes 3 key steps:
1) Using CVE-2016-7637 and CVE-2016-7661 to perform privilege escalation to root level access via a man-in-the-middle attack on the com.apple.iohideventsystem port.
2) Using CVE-2016-7644, a use-after-free bug, to gain the kernel task port, allowing control of the kernel.
3) Overwriting ipc_port data structures to fake the kernel task port and obtain full kernel write privileges, bypassing security mechanisms like AMCC
2. Who am I
• An iOS hacking enthusiast
• A first-grade high school student at Waseda University
High School
3. Summary
• Escalation to root
• Escalation to kernel
• Disabling mitigations for post exploitations
This talk explains the following 3 Methods
4. The bugs
• Those bugs are found by Ian beer who is at
googleprojectzero
• CVE-2016-7637
- Broken kernel mach port name uref handling on iOS/MacOS can lead to privileged
port name replacement in other processes
• CVE-2016-7644
- XNU kernel UaF due to lack of locking in set_dp_control_port
• CVE-2016-7661
- MacOS/iOS arbitrary port replacement in powerd
5. Attack vector
• CVE-2016-7637 and CVE-2016-7661 to privilege
escalation to root
• CVE-2016-7644 to gain the kernel task port
• Applying patches to disable a bunch of mitigations
7. CVE-2016-7637
• The bug is basically a mistake of an buffer overflow
checking (but not buffer overflow bug in
ipc_right_copyout)
8. CVE-2016-7637
• What’s the meaning of “pegging”?
• Suppose it is to prevent a sort of buffer overflow and
wrap around to 0
• But the concept of “pegging” is hardly used in xnu
• Can we exploit it?
9. Mach IPC system
• The ports targeting on this exploit are related to ipc_entry
Source: “Through the mach portal”, ianbeer
https://bugs.chromium.org/p/project-zero/issues/attachment?aid=280146
10. CVE-2016-7637
• The point is that
UREFS count is exceeded at 0xFFFE
and send “overflow” message to the target port
• The next UREFS count being supposed to be 0xFFFF will
result in still retaining the UREFS count being 0xFFFE
• So it’s promoted to an out-of-sync vulnerability
• Let’s take a look at the inside of this exploit in the next
slide
11. CVE-2016-7637
• The applied technique is sending 0x10000 messages (of
the same send right) to the target port
• The messages are made to be freed in the process of
mach_msg_server (sending invalid messages)
• Spraying those malicious messages to target port’s UREFS
and they will be freed after they are counted to UREFS
• This cause the target port being freed!
• Let’s reallocate there and take control of the target port
12. CVE-2016-7637
• There’s a strategy to mitigate for the reallocation of a port
and using it (like Use-After-Free)
• “ipc_entry” has an entry of generation number (in
“ie_bits”)
• Generation number entry consists of 6bits bit field.
• Generation number will be checked on the userspace/
kernelspace boundary
• Incrementing generation number program is below
13. CVE-2016-7637
• Generation number is up to 64 (not overlapping)
• So we need a primitive that allows us to loop
generation number around to match the generation
number at 64th reallocating
• Exploiting reliably, the target port needs to locate at the
approximately middle of the freelist
14. ipc_entry freelist
• It’s a simple LIFO list.
• Though the value indicating the next node isn’t an
address but an index of “is_table”
• Unlinking the entry from freelist, old head becomes our
next node
15. CVE-2016-7637
• This topic is to enhance the reliability for this exploit
• Sending N messages (reallocating and freed soon) for the
sake of target port to be down the freelist
• After that, sending 62 loops of 2N messages to increment
target port’s generation number
16. CVE-2016-7637
• Review this exploit
• Carry out the UREFS bug
• Sending N messages
• Sending 62 loop of 2N messages
• Target port’s generation number will have been matched
17. What’s suitable for the
target port?
• We need a send right for that port
• The kernel ports can’t be consumed since kernel-owned
ports are looked up each time (e.g. bad setting for
generation number)
• launchd is a great service
18. launchd
• com.apple.iohideventsystem can be accessed inside the
sandbox and approved to have send right
• Thanks to insecurities of Apple,
com.apple.iohideventsystem receives the task port from
client of it
• Man-In-The-Middling the target port to capture the task
port gives us root task port
19. Task port
• Task port is assigned per task
• Task port can be obtained by task_for_pid though this API
is so restricted
• If we have the task port, we can do anything on its
process.
20. CVE-2016-7661
• Powerd is a client of com.apple.iohideventsystem
• Powerd runs as root
• Crashing powerd process (CVE-2016-7661) brings the
target port to receive powerd’s task port
22. ipc_port
• ipc_entry has a reference to ipc_port (ie_object)
• ipc_entry has only reference for ipc_port object
• It’s maintained in zones which allocated by zone
allocator
23. CVE-2016-7644
• This vulnerability is a sort of Use-After-Free
• set_dp_control_port is only called from root task port so
we can’t use this bug without previous exploiting
24. CVE-2016-7644
• Threads can race to see the same value for
dynamic_pager_control_port and release it
• ipc_port_release_send decrements the reference count
io_references
26. CVE-2016-7644
• MIG has the feature named no-more-senders
notification to notify us if there’s no ip_srights by
ipc_port_release_send
• The total count of senders ip_srights is decremented as
well as the reference count
• So we’ll use the advantage to notice when it reaches to 0
38. zalloc
• zalloc is a system call that assigns zones corresponding
to the size
• zalloc have a local freelist per zone
• zones are freed by memory pressures or
mach_force_zone_gc
39. CVE-2016-7644
• There are zones for ipc_port as I said before
• We allocate a ram_mb*20 number of ports (early ports)
• And we alllocate 20 of ports (middle ports)
• Finally, we allocate 5000 of ports (late ports)
• Not forget to prepare stashed port (corresponding to
portB) for all of ports we allocated
40. CVE-2016-7644
• Causes the bug for each middle ports
• Destroy the stashed ports
• Eventually we’ve made ipc_entries point into dangling
port pointers
41. CVE-2016-7644
• Make the page be able to reallocate other kind of
zones(currently it’s for ipc_ports) to capture the kernel
port
• We here use another technique to gain kernel task port
42. Fake ipc_port
• Use dangling ipc_ports to retrieve or write ip_context
• Overlapping the zone we targeted on using ool_ports
• ool_ports will misunderstand ipc_ports members
Source: “Through the mach portal”, ianbeer
https://bugs.chromium.org/p/project-zero/issues/
attachment?aid=280146
43. CVE-2016-7644
• We are aiming to get the kernel task port
• The kernel task port are supposed to be allocated at
bootstrapping kernel which is probably at the first page of
ipc_ports
• And we can get/set the ip_context of ipc_ports (with
mach_port_{set|get}_context)
44. CVE-2016-7644
• Rewrite the every ip_context of dangling ipc_ports to the addresses
which are around the middle of first ipc_ports’ page
(This is CORE technique of our Jailbreak)
• There is a kernelspace to userspace conversion function
which converts “port address” to “port object”
• Using this conversion, once our dangling ipc_ports’ ipc_context will be
rewritten to the address of kernel task ports, the overlapped ool_ports
(whose host_port) becomes kernel_task port with some probability!
• As a result, the ool_ports can be used for receiving kernel_task_port
from userspace tasks.
This enables our userspace task to manipulate kernel memory in any way!
45. Process handling in xnu
• Process handling implementation in xnu is similar to one
in BSD
• There is a great deal of benefits to compromise a couple
of values in the exact structure
46. Privilege Escalation
• Most exploits in various platforms likely use this sort of
technique
• “proc” structure is provided by per process
• “allproc” variable holds a single linked list for every
process’s “proc”
• Rewriting flags and credentials inside “proc” structure!
47. “proc” structure
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
pid_t p_pid; /* Process identifier. (static)*/
void * task; /* corresponding task (static)*/
struct proc * p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid; /* process's parent pid number */
pid_t p_pgrpid; /* process group id of the process (LL)*/
uid_t p_uid;
gid_t p_gid;
uid_t p_ruid;
.....
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */
.....
uint32_t p_csflags; /* flags for codesign (PL) */
.....
• At first glance, we just rewrite the values “p_uid” and “p_gid”
• But these fields are’t used inside the kernel process maintaining
system
• The real one is inside the “p_ucred” structure!
48. “kauth_cred_t”
• “typedef struct ucred *kauth_cred_t;”
• “ucred” structure is the original credential maintainer
• So we should do is to copy a highly privileged credential
to our process
49. p_csflags
• Flags for codesign
• Just a 32bit bitfield
• Attributes are defined in bsd/sys/codesign.h
• Editing several values to allow/deny options
50. p_csflags
• flag |= CS_PLATFORM_BINARY|CS_INSTALLER|
CS_GET_TASK_ALLOW
• This allows us to obtain a task from another process
• flag &= ~(CS_RESTRICT|CS_KILL|CS_HARD);
• Omitting complicated options
51. • Until 2 months ago, it was called KPP but now it is called
KTRR or AMCC
• The most annoying mitigation for jailbreakers
• Based on arm’s TrustZone technology
• As of xnu-4570.1.46, it became partially open-sourced
(there seems no sync_handler implementation)
AMCC
52. AMCC
• There are 4 privilege level being established in Trustzone
• EL0 - User-space programs are running here
• EL1- Kernel and iBoot(Bootloader)
• EL2 - Unused in iOS
• EL3 - AMCC/KTRR
53. AMCC
• AMCC’s strategy is the kernel regions with read-only or
read|exec-only permissions to be guaranteed unmodified
• If these regions are determined as an invalid region,
AMCC causes kernel panic
• Bypass the regions checking loop so that giving us to
write anywhere
54. AMCC
• In theory, it is inevitable
• Though in practice, it is not inevitable
• Let’s get started into arm abyss!
55. AMCC
• AMCC needs to retrieve several system registers to know
the kernel space states or user space states
• We can set these registers if we have kernel execution
56. System Registers
• TTBR1_EL1
The TTBR1_EL1 controls the base address of translation
table 1
• CPACR_EL1
The CPACR_EL1 controls access to floating-point, and
Advanced SIMD functionality from EL0, EL1, and EL3.
The flag register “CPACR_EL1.FPEN” called “NEON”
determines if it traps
58. sync_handler
• It’s the core of AMCC
• Supposed it checks KTRR regions integrity and registers
integrity
• If there’s invalid pages it will trigger kernel panic
• It sets CPACR_EL1 to not to trap FPU instruction
59. Watchtower
• It will be called by IRQ instruction
• Its source is located at osfmk/arm64/locore.s
• Restoring a bunch of register and CPACR_EL1 to trap any
instruction in EL0 and EL1
60. The solution
• Referred to @qwertyoruiopz Yalu102 jailbreak
• 1st, load dummy Translation Table Base address to
TTBR1_EL1
• 2nd, hit CPACR_EL1 not to be trapped by EL3
(but it triggers check on EL3 now)
• 3rd, load fake Translation Table Base address to TTBR1_EL1
• 4th, executes “tlbi vmalle1” to invalidate all stage 1 translations
• And patching instruction touching cpacr to nop
62. Shadowmapping technique
• gVirtbase - virtual address of translation table entries
• gPhybase - physical address of translation table entries
• Both of them is stored in pmap structure
• The technique is replacing original Translation Table Entry
to our fake Translation Table Entry so that giving us write
permission to executable pages
63. Problem
• TTBR_EL1 will be reset to 0 on sleeping
• There are 2 kinds of sleeping
• Idle sleep
sleeping when cpu is idle
• Deep sleep
sleeping when the screen has been black out for more than
30 seconds
• The state of them is stored in “struct
cpu_data>interrupt_handler”
64. AMFI
• Apple Mobile File Integrity
• Exist as a daemon and a kext
• The reason why it is targeted by attacker is that its kext
audits the binary’s entitlements, code signing and
MAC(Mandatory Access Control) policy
65. PE_i_can_has_debugger
• Patching a1 for the function to always return true
• If the function always returns true, many of the checks in
the kernel will turn off
66. Disabling sandbox
• MAC policies are stored at
“mac_policy_conf->mac_ops”
• These policies needs to be disable for
jailbreaking
• Just rewrite those pointers to be null
67. LwVM
• Light-weight Volume Manager
• LwVM wraps GPT
• _mapForIO traps the root partition writing
• Partition object which mark it lock by a flag is held on
heap
• Removing this flag to write anywhere in the root partition
68. Remount rootfs
• mac_mount prevents mounting root partition as r/w
• Bypassing this protection is as the same doing as we did
in LwVM
• vnode(checked every mounting on) have a flag
• Remove that flag and remounting root partition
69. References
• Through the mach portal (by ian beer)
• yalu102 (by Luca Todesco)
• iOS 10 Kernel Heap Revisited (by Stefan Esser)
• Mac OS X and iOS Internals (by Jonathan Levin)