nyu hacknight, april 6, 2016
TRANSCRIPT
Security Researcher at SYNACK
Working on low level emulation with QEMU and iPhone automation.
Graduate of Polytechnic University
a.k.a Polytechnic Institute of New York University
a.k.a New York University Polytechnic School of Engineering
a.k.a New York University Tandon School of Engineering
Getting started with iOS
- Get iPhone 5s- Swappa
- Apply Jailbreak- Install OpenSSH via Cydia- Use tcprelay to SSH over USB
- Start exploring- Debugserver
- Objective-c: Phrack 0x42- http://phrack.org/issues/66/4.html
- https://github.com/iosre/iOSAppReverseEngineering- https://nabla-c0d3.github.io/blog/2014/12/30/tcprelay-multiple-devices/
Where are the vulns?!
Jailbreaks are basically chains of memory corruptions going all the way down to the kernel.
Memory corruption - just won’t go away! That’s what a lot of CTFs seem to be focusing on.History thereofMemory Errors
Beg, borrow and steal
Finding vulnerabilities
Fuzzing (AFL, Many frameworks)
Code reading (SourceInsight, Understand)
Dynamic/Static analysis (Qira, Panda)Code coverage is important for this type
ARM64 Registers
31 General purpose registers
X0 … X30 or W0 … W30
X31 - (zr) The Zero register
X30 - (lr) Procedure Link Register (RIP)
X29 - (fp) Frame pointer (RBP)
X18 - Reserved on iOS
ARM64 Instructions
Conditional Branches
B.EQ, B.NE, TBNZ (Test bit and Branch if Nonzero), etc.
Unconditional Branches
B, RET, SVC
Conditional Select
CSEL W9, W9, W10, EQ
“W9 = EQ?W9:W10”
Calling Convention
On ARM64:
X0 … X8 Contain function parameters
X16 has the system call number
Positive for Posix
Negative for Mach Ports
0x80000000 for thread_set_self
SVC 0x80; jumps to kernel
Syscall numbers
OSX:
0x01000000 - mach ports
0x02000000 - Posix
0x03000003 - pthread_set_self
IOS
0x00000000 and below - mach ports
0x00000000 and above - Posix
0x80000000 - pthread_set_self
More details
See the iOS ABI Function Call Guide
@interface TestObject : NSObject { }
-(void)print;
@end
@implementation TestObject
-(void)print { NSLog(@"Test Object"); }
@end
…
TestObject* obj = [TestObject alloc]; [obj print];
__text:0000000100000DB0 mov rsi, cs:classRef_TestObject
__text:0000000100000DB7 mov rdi, cs:selRef_alloc
__text:0000000100000DBE mov [rbp+var_38], rdi
__text:0000000100000DC2 mov rdi, rsi
__text:0000000100000DC5 mov rsi, [rbp+var_38]
__text:0000000100000DC9 call _objc_msgSend
__text:0000000100000DCE mov [rbp+var_18], rax
__text:0000000100000DD2 mov rax, [rbp+var_18]
__text:0000000100000DD6 mov rsi, cs:selRef_print
__text:0000000100000DDD mov rdi, rax
__text:0000000100000DE0 call _objc_msgSend
[obj print];
objc_msgSend(obj, “print”);
-[TestObject print](obj, “print”);
id objc_msgSend(id self, SEL op, ...)
void __cdecl -[TestObject print]
(struct TestObject *self, SEL)
Steps
1. Allocate a page - a jump page
2. Set objc_msgSend readable and writable
3. Copy bytes from objc_msgSend
4. Check for branch instructions in preamble
5. Modify objc_msgSend preamble
6. Set jump page to readable and executable
7. Set objc_msgSend readable and executable
Repository: https://github.com/nologic/objc_trace
Step 2 - Set objc_msgSend readable and writable
mach_port_t self_task = mach_task_self();
vm_protect(self_task, (vm_address_t)o_func, 4096, true, VM_PROT_READ | VM_PROT_WRITE) != KERN_SUCCESS
vm_protect(self_task, (vm_address_t)o_func, 4096, false, VM_PROT_READ | VM_PROT_WRITE) != KERN_SUCCESS
set_maximum
Step 3 - Copy bytes from objc_msgSend
*t_func = *(jump_page());
// save first 4 32bit instructions
// original -> trampoline
instruction_t* orig_preamble = (instruction_t*)o_func;
for(int i = 0; i < 4; i++) {
t_func->inst [i] = orig_preamble[i];
t_func->backup[i] = orig_preamble[i]; }
Step 3 - Types
typedef uint32_t instruction_t;
__attribute__((naked)) void d_jump_patch() {
__asm__ __volatile__(
"ldr x16, #8;\n"
"br x16;\n"
".long 0;\n" // place for jump address
".long 0;\n" ); }
Step 3 - Jump page
__attribute__((naked)) void d_jump_page() {
__asm__ __volatile__(
"B INST1;\n" // placeholder for original instructions
"B INST2;\n"
"B INST3;\n"
"B INST4;\n"
Step 3 - Copy bytes from objc_msgSend
__text:18DBB41C0 EXPORT _objc_msgSend
__text:18DBB41C0 _objc_msgSend
__text:18DBB41C0 CMP X0, #0
__text:18DBB41C4 B.LE loc_18DBB4230
__text:18DBB41C8 loc_18DBB41C8
__text:18DBB41C8 LDR X13, [X0]
__text:18DBB41CC AND X9, X13, #0x1FFFFFFF8
Step 4 - Check for branch instructions in preamble
CMP B.LE LDR AND LDR BR #Addr64 LDR BR #Addr64 LDR BR #Addr64
PC
Jump page:
objc_msgSend+16objc_msgSend+##
Step 4 - Check for branch instructions in preamble
(lldb) x/10i 0x0000000104ef4000 ; Jump page 0x104ef4000: 0xf100001f cmp x0, #0 0x104ef4004: 0x540000ed b.le 0x104ef4020 0x104ef4008: 0xf940000d ldr x13, [x0] 0x104ef400c: 0x927d75a9 and x9, x13, #0x1fffffff8 ; Jump patch 1 0x104ef4010: 0x58000050 ldr x16, #8 0x104ef4014: 0xd61f0200 br x16 0x104ef4018: 0x964efbd0 bl 0xfe2b2f58 0x104ef401c: 0x00000001 .long 0x00000001 ; unknown opcode ; Jump patch 2
0x104ef4020: 0x58000050 ldr x16, #80x104ef4024: 0xd61f0200 br x16
Step 4 - Working with instructions
typedef struct { uint32_t offset : 26; uint32_t inst_num : 6;} inst_b;…
instruction_t inst = t_func->inst[i];inst_b* i_b = (inst_b*)&inst;inst_b_cond* i_b_cond = (inst_b_cond*)&inst;
if(i_b->inst_num == 0x5) { // unconditional branch
Step 5 - Modify objc_msgSend preamble
Think back to d_jump_patch
(lldb) x/10i 0x1964efbc0 ; objc_msgSend-> 0x1964efbc0: 0x58000050 ldr x16, #8 ; <+8> 0x1964efbc4: 0xd61f0200 br x16 0x1964efbc8: 0x04eeb730 .long 0x04eeb730 ; unknown opcode 0x1964efbcc: 0x00000001 .long 0x00000001 ; unknown opcode 0x1964efbd0: 0xa9412d2a ldp x10, x11, [x9, #16] 0x1964efbd4: 0x0a0b002c and w12, w1, w11 0x1964efbd8: 0x8b0c114c add x12, x10, x12, lsl #4
Step 5 - Jump patch destination__attribute__((naked)) id objc_msgSend_trace(id self, SEL op) { __asm__ __volatile__ (
"stp fp, lr, [sp, #-16]!;\n"
"mov fp, sp;\n"
"sub sp, sp, #(10*8 + 8*16);\n"
"stp q0, q1, [sp, #(0*16)];\n"
...
"stp x0, x1, [sp, #(8*16+0*8)];\n"
..
"BL _hook_callback64_pre;\n"
"mov x9, x0;\n"
// Restore all the parameter registers to the initial state.
"ldp q0, q1, [sp, #(0*16)];\n"
...
"ldp x0, x1, [sp, #(8*16+0*8)];\n"
// Restore the stack pointer, frame pointer and link register
"mov sp, fp;\n"
"ldp fp, lr, [sp], #16;\n"
"BR x9;\n" // call the original ); }
Step 6 - Set jump page to readable and executable
// set permissions to exec
if(mprotect((void*)t_func, 4096, PROT_READ | PROT_EXEC) != 0) {
perror("Unable to change trampoline permissions to exec");
return NULL;
}
Step 7 - Set objc_msgSend readable and executable
mach_port_t self_task = mach_task_self();
vm_protect(self_task, (vm_address_t)o_func, 4096, true, VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS
vm_protect(self_task, (vm_address_t)o_func, 4096, false, VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS
The end game!
void* hook_callback64_pre(id self, SEL op, void* a1, … ) { // get the important bits: class, method char* classname = (char*) object_getClassName( self ); char* opname = (char*) op; // print some useful info. fprintf(output, "%016x: [%s %s (", pthread_self(), classname, (char*)opname);
int printParam = 0; for(int i = 0; i < namelen; i++) { if(opname[i] == ':') { fprintf(output, "%p ", getParam(printParam, a1, a2, a3, a4, a5)); } }
return original_msgSend;}
Done! Let’s execute.
On the command line:
iPhone:~ root# DYLD_INSERT_LIBRARIES=libobjc_trace.dylib /Applications/Maps.app/Maps
objc_msgSend function substrated from 0x197967bc0 to 0x10065b730, trampoline
0x100718000
000000009c158310: [NSStringROMKeySet_Embedded alloc ()]
000000009c158310: [NSSharedKeySet initialize ()]
000000009c158310: [NSStringROMKeySet_Embedded initialize ()]
000000009c158310: [NSStringROMKeySet_Embedded init ()]
000000009c158310: [NSStringROMKeySet_Embedded initWithKeys:count: (0x0 0x0 )]
000000009c158310: [NSStringROMKeySet_Embedded setSelect: (0x1 )]
000000009c158310: [NSStringROMKeySet_Embedded setC: (0x1 )]
000000009c158310: [NSStringROMKeySet_Embedded setM: (0xf6a )]
000000009c158310: [NSStringROMKeySet_Embedded setFactor: (0x7b5 )]
Attaching to a process
iPhone:~ root# ./debugserver *:1234 --attach=DamnVulnerableIOSApp
MacBook-Pro-2:~ nl$ lldb(lldb) process connect connect://127.0.0.1:3234 Process 2826 stopped* thread #1: tid = 0x463b0, 0x0000000196c9f0c0 libsystem_kernel.dylib`__psynch_mutexwait + 8, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x0000000196c9f0c0 libsystem_kernel.dylib`__psynch_mutexwait + 8libsystem_kernel.dylib`__psynch_mutexwait:-> 0x196c9f0c0 <+8>: b.lo 0x196c9f0d8
The Challenge
1. Make function tracing work for another architecture:a. ARMv7, x86_64, PPC, etc.
2. Make function tracing work from shell code.a. ShellCC might be of help
Where to learn about security?
- iOS Reverse Engineering Book- https://seccasts.com/- http://www.opensecuritytraining.info/- https://www.corelan.be- youtube for conference- Security meetups
- Just practice- Read/follow walkthroughs
- follow the reddits:- netsec- reverseengineering- malware- lowlevel- blackhat- securityCTF- rootkit- vrd