NYU hacknight, april 6, 2016

37
Tracing Objective-C (Hacking in context) Mikhail Sosonkin

Transcript of NYU hacknight, april 6, 2016

Tracing Objective-C(Hacking in context)

Mikhail Sosonkin

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/

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

Before we begin...

Let’s cover some basics.

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

Let’s explorer how Objective-C

calls methods

@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)

The Plan

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 1 - allocate a page

mmap(NULL,

4096,

PROT_READ | PROT_WRITE,

MAP_ANON | MAP_PRIVATE,

-1,

0);

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