take a jailbreak -stunning guards for ios jailbreak- by kaoru otsuka
TRANSCRIPT
Who am I
• An iOS hacking enthusiast
• A first-grade high school student at Waseda University High School
Summary
• Escalation to root
• Escalation to kernel
• Disabling mitigations for post exploitations
This talk explains the following 3 Methods
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
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
CVE-2016-7637
This vulnerability can be applied to MITM attack and leads us to gain a root task port.
CVE-2016-7637
• The bug is basically a mistake of an buffer overflow checking (but not buffer overflow bug in ipc_right_copyout)
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?
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
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
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
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
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
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
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
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
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
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
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.
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
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
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
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
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
no-more-senders• ipc_port_release_send drops one reference and one
send right for that port
• ipc_notify_no_senders sends port->nsrequest port when port becomes no-more-senders
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
ie_object
portA
1 reference
0 send right
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB
Making a reference for that port
2 references
1 send right
portB has send right to portA though doesn’t receive from portA
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB3 references
2 send right
portC
PortC will be the receiver of no-more-senders notification
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB3 references
2 send right
portC
Trigger that bug!
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB2 references
1 send right
portC
Trigger that bug!
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB1 references
0 send right
portC
Trigger that bug!
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB1 references
0 send right
portC
send no-more-
We could get the notification so we won
the race!
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
portB1 references
0 send right
portC
Then, what will happen if the portB will be
destroyed?
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
portA
ie_object
0 references
0 send right
portC
Then, what will happen if the portB will be
destroyed?
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entry
ipc_entries
ie_object
0 references
0 send right
What’s ie_object pointing to?
The ipc_entry->ie_object becomes a dangling pointer!
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
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
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
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
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
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)
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!
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
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!
“proc” structurestruct 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!
“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
p_csflags
• Flags for codesign
• Just a 32bit bitfield
• Attributes are defined in bsd/sys/codesign.h
• Editing several values to allow/deny options
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
• 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
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
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
AMCC
• In theory, it is inevitable
• Though in practice, it is not inevitable
• Let’s get started into arm abyss!
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
System Registers
• TTBR1_EL1The TTBR1_EL1 controls the base address of translation table 1
• CPACR_EL1The 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
The loopEL0
FPU Execution
EL3 Executes
sync_handler
EL0 or EL1 IRQ Execution
EL3 Executes
Watchtower
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
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
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
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
Problem• TTBR_EL1 will be reset to 0 on sleeping
• There are 2 kinds of sleeping
• Idle sleep sleeping when cpu is idle
• Deep sleepsleeping when the screen has been black out for more than 30 seconds
• The state of them is stored in “struct cpu_data>interrupt_handler”
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
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
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
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
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
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)