Dynamic Analysis

LLDB is fully integrated with XCode since XCode 5, so if you are an iOS developer you’ve already get familiar with it as a daily job. We can attach to the running process from XCode or Terminal, for this post I will run LLDB from the terminal and attach to running app on the jailbroken device. Assume you already setup debugserver on jailbroken device in Prerequisites section.

Start debugserver

Let SSH to the jailbroken device, for my case, it would be like this: ssh ipse_home , for your case it would be ssh root@your_device_ip_address . You might need to key in when password prompts (default password is alpine ). If you would like to debug on jailbroken device often, you can create an SSH shortcut in your ~/.ssh/config file like this (more details you can reference this post for how to setup) then you can SSH via shortcut instead. You also can SSH Passwordless login using SSH Keygen if you manage to SSH more often.

Host ipse_home

HostName 192.168.1.113

User root

Figure 9: SSH Shortcut

After SSH into the jailbroken device, we will use debugserver to attach to the app and listen for connections from other machines then from our laptop we will launch LLDB to connect to debugserver for remote debugging. Let’s do step by step.

From Terminal SSH into the device, then run this command: debugserver 0.0.0.0:1234 -w "Executable file"

Executable file is the process name we want to attach (this value you need to get from Info.plist file).

is the process name we want to attach (this value you need to get from file). 0.0.0.0 is the IP range allows for other connections to connect (if you know the IP address of remote debugging machine then please specify that for secure).

is the IP range allows for other connections to connect (if you know the IP address of remote debugging machine then please specify that for secure). 1234 is port number (you can define any if you want).

is port number (you can define any if you want). -w argument is abbreviation of --waitfor which will wait for process to launch (if not launched yet) or attach immediately if it’s been already launched.

iPhone-SE:~ root# debugserver 0.0.0.0:1234 -w REDACTED

debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.57..2

for arm64.

Waiting to attach to process REDACTED...

Figure 10: Start debugserver

Let assume you already configured the jailbroken device using Burp Suite proxy, the app was not started yet then start above debugserver command. Whenever you see the output Waiting to attach to process , it’s time to launch our app from the jailbroken device and you can see from Terminal it will print out new message Listening to port 1234 for a connection from 0.0.0.0... which means that it attached successfully and waiting for a connection to debug.

iPhone-SE:~ root# debugserver 0.0.0.0:1234 -w REDACTED

debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.57..2

for arm64.

Waiting to attach to process REDACTED...

Listening to port 1234 for a connection from 0.0.0.0...

Figure 11: debugserver attached process

Launch LLDB

We will use LLDB as a standalone debugger for this post instead of LLDB debugger through XCode debugging feature (XCode console pane). So let have some fun with LLDB command line, why not?

Now let the fun begin. Open another tab of Terminal and run lldb command.

MBP# lldb

(lldb)

Figure 12: Start LLDB standalone

From lldb prompt, type platform select remote-ios then process connect connect://192.168.1.113:1234 to attach into our app (process) via debugserver (just to remember 192.168.1.113 is the IP address of the jailbroken device). If we can attach to the process successfully, you will see this log in Terminal:

(lldb) platform select remote-ios

Platform: remote-ios

Connected: no

SDK Path: "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/13.3.1 (17D50) arm64e"

SDK Roots: [ 0] "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/13.3.1 (17D50) arm64e"

SDK Roots: [ 1] "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/11.3.1 (15E302)"

SDK Roots: [ 2] "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/11.0 (15A372)"

SDK Roots: [ 3] "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/12.4 (16G77)"

Process 19596 stopped

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP

frame #0: 0x000000018104e3b0 libsystem_c.dylib`strlen + 48

libsystem_c.dylib`strlen:

-> 0x18104e3b0 <+48>: ldr q0, [x1, #0x10]!

0x18104e3b4 <+52>: uminv.16b b1, v0

0x18104e3b8 <+56>: fmov w2, s1

0x18104e3bc <+60>: cbnz w2, 0x18104e3b0 ; <+48>

Target 0: (REDACTED) stopped.

Figure 13: LLDB attached to process

In case lldb shows the SDK Path error or something, you might double-check if your device iOS version exists in XCode iOS DeviceSupport or not. You can refer this great post to troubleshoot if issues.

You need to type these 2 above commands everytime you want to debug application in lldb prompt, to save time, LLDB allow to config commands can be loaded when lldb start. You just need to copy those 2 commands and put in ~/.lldbinit will do. Here is my sample ~/.lldbinit file:

## .lldbinit start ###

command alias rr register read

command alias rw register write

command alias mr memory read

command alias mw memory write

command alias il image list -o -f

command alias eoc expression -l objc -O --



platform select remote-ios

process connect connect://192.168.1.113:1234

Figure 14: ~/.lldbinit

You can define lldb command alias here also, and it will take effect in lldb prompt. But there will be a problem here if you are an iOS developer because this file will also be applied for LLDB in XCode, which means when you debug an application in XCode it also load this init file and will cost you some more time to launch the application. To separate the configuration between LLDB standalone and LLDB of XCode, you can create a new ~/.lldbinit-Xcode file and put your commands there just for XCode usage. Whenever you debug application via XCode, it will check if ~/.lldbinit-Xcode file if exists then XCode will load this file instead of ~/.lldbinit .

Set breakpoints

Now, lldb attached to the app process and waiting for us to debug, you might notice that the app is hanging on the jailbroken device because the process is being interrupted. The debug process would be the same as we debug in XCode except in XCode you debug through each line of code, here you debug each line of assembly instruction, and you examine registers instead of variables. Before examining registers, let set a breakpoint first. Go back to Hopper Disassembler, go to address 0x100038000 of -[BackendController init] method, we need to set the breakpoint at this address to see if application will invoke this method or not and examine value of URL string passed to the registers (in Hopper Disassembler, press G then put in address value to navigate to). In LLDB, to set breakpoint at an address we can type: breakpoint set --address 0x100038000 , or br s -a 0x100038000 for short then enter.

(lldb) breakpoint set --address 0x100038000

warning: failed to set breakpoint site at 0x100038000 for breakpoint 1.1: error: 0 sending the breakpoint request

Breakpoint 1: address: 0x100038000

Figure 15: LLDB set breakpoint failed

But look at that warning, it failed to set breakpoint at 0x100038000 . Why??? The reason it’s failed because of ASLR (Address Space Layout Randomization). ASLR is a memory-protection process for operating systems (OSes) that guards against buffer-overflow attacks by randomizing the location where system executables are loaded into memory.. We need to calculate the real address when the process is running to set a breakpoint (we need to re-calculate real address every time we start the app). Real Address = ASLR shift + Hopper Address , let find out missing part ASLR Shift .

From lldb prompt, type image list -o processName then enter, the result in the console is the ASLR shift (please note that the value might not the same for you as it’s a random number).

(lldb) image list -o REDACTED

[ 0] 0x00000000045bc000

(lldb)

Figure 16: ASLR Shift

My case ASLR Shift = 0x00000000045bc000 , so Real Address = ASLR shift + Hopper Address = 0x00000000045bc000+0x100038000 = 0x1045F4000 . Let set breakpoint again: br s -a 0x00000000045bc000+0x100038000 or br s -a 0x1045F4000 then enter.

(lldb) br s -a 0x00000000045bc000+0x100038000

Breakpoint 2: where = REDACTED`___lldb_unnamed_symbol1321$$REDACTED + 112, address: 0x00000001045F4000

Figure 17: Set breakpoint successfully

We just set breakpoint successfully, now let the process continue to run by typing: continue or c then enter. It will run the app for a second then you will see it will stop again in console, IT HITS THE BREAKPOINT!!!!

(lldb) c

Process 19596 resuming

Process 19596 stpped

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1

frame #0: 0x00000001045f4000 REDACTED`___lldb_unnamed_symbol1321$$REDACTED + 112

REDACTED`___lldb_unnamed_symbol1321$$REDACTED

-> 0x1045f4000 <+112>: add x2, x2, #0x170 ; =0x170

0x1045f4004 <+116>: bl 0x1058dcae4 ; symbol stub for: objc_msgSend

0x1045f4008 <+120>: mov x21, x0

0x1045f400c <+124>: adrp x8, 7810

Target 0: (REDACTED) stopped.

(lldb)

Figure 18: LLDB hits breakpoint

Examine registers

You can see the process stopped at our breakpoint (at address 0x1045F4000 ) at the instruction add x2, x2, #0x170 . Let have a look again in Hopper Disassembler for these instructions:



...

100037fdc adrp x8, #0x101f09000

100037fe0 ldr x0, [x8, #0x3c8] ; objc_cls_ref_BackendHttpClient,__objc_class_BackendHttpClient_class

100037fe4 adrp x8, #0x101eb7000

100037fe8 ldr x20, [x8, #0xb88] ; "alloc",

100037fec mov x1, x20

100037ff0 bl imp___stubs__objc_msgSend ; objc_msgSend

100037ff4 adrp x8, #0x101eba000

100037ff8 ldr x1, [x8, #0x50] ; "initWithBaseURL:",

100037ffc adrp x2, #0x1019c5000

100038000 add x2, x2, #0x170 ; @"

100038004 bl imp___stubs__objc_msgSend ; objc_msgSend

100038008 mov x21, x0

10003800c adrp x8, #0x101eba000

100038010 ldr x1, [x8, #0x58] ; "setHttpClient:",

100038014 mov x0, x19

100038018 mov x2, x21

10003801c bl imp___stubs__objc_msgSend ; objc_msgSend

100038020 mov x0, x21

... -[BackendController init]:...100037fdc adrp x8, #0x101f09000100037fe0 ldr x0, [x8, #0x3c8] ; objc_cls_ref_BackendHttpClient,__objc_class_BackendHttpClient_class100037fe4 adrp x8, #0x101eb7000100037fe8 ldr x20, [x8, #0xb88] ; "alloc", @selector (alloc)100037fec mov x1, x20100037ff0 bl imp___stubs__objc_msgSend ; objc_msgSend100037ff4 adrp x8, #0x101eba000100037ff8 ldr x1, [x8, #0x50] ; "initWithBaseURL:", @selector (initWithBaseURL:)100037ffc adrp x2, #0x1019c5000100038000 add x2, x2, #0x170 ; @" https://redacted-backend.redacted.com/ 100038004 bl imp___stubs__objc_msgSend ; objc_msgSend100038008 mov x21, x010003800c adrp x8, #0x101eba000100038010 ldr x1, [x8, #0x58] ; "setHttpClient:", @selector (setHttpClient:)100038014 mov x0, x19100038018 mov x2, x2110003801c bl imp___stubs__objc_msgSend ; objc_msgSend100038020 mov x0, x21...

Figure 19: Instructions explanation

I put comment for those instructions, the add x2, x2, #0x170 can be translated as x2 = x2 + #0x170 = #0x1019c5000 + #0x170 = 0x1019c5170 , this is the address of https://redacted-backend.redacted.com/ (you can look back above Figure 4) and Hopper Disassembler is smart enough to find out and put comment at the end of this instruction. So after executing this instruction, we expect value of x2 is https://redacted-backend.redacted.com/ . Let type next or n then enter to execute this instruction.

Now to check value of register x2 , you can type po $x2 (po is print object) or register read x2 , you will see register x2 now holding address that contains string https://redacted-backend.redacted.com/



Process 19596 stopped

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1

frame #0: 0x00000001045f4004 REDACTED`___lldb_unnamed_symbol1321$$REDACTED + 116

REDACTED`___lldb_unnamed_symbol1321$$REDACTED

-> 0x1045f4004 <+116>: bl 0x1058dcae4 ; symbol stub for: objc_msgSend

0x1045f4008 <+120>: mov x21, x0

0x1045f400c <+124>: adrp x8, 7810

0x1045f4010 <+128>: ldr x1, [x8, #0x58]

Target 0: (REDACTED) stopped.

(lldb) po $x2

https://redacted-backend.redacted.com/ (lldb) nextProcess 19596 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1frame #0: 0x00000001045f4004 REDACTED`___lldb_unnamed_symbol1321$$REDACTED + 116REDACTED`___lldb_unnamed_symbol1321$$REDACTED-> 0x1045f4004 : bl 0x1058dcae4 ; symbol stub for: objc_msgSend0x1045f4008 : mov x21, x00x1045f400c : adrp x8, 78100x1045f4010 : ldr x1, [x8, #0x58]Target 0: (REDACTED) stopped.(lldb) po $x2

x2 = 0x0000000105f81170 @" (lldb) register read x2x2 = 0x0000000105f81170 @" https://redacted-backend.redacted.com /"

Figure 20: Examine register x2

Change registers value

As we are in debug mode, to change value of register is trivial. We want to downgrade https to http protocol so we need to change value of register x2 from https://redacted-backend.redacted.com/ to http://redacted-backend.redacted.com/ , let do it:

First, let create new http://redacted-backend.redacted.com/ string by: expression @"http://redacted-backend.redacted.com/" or e @"http://redacted-backend.redacted.com/" , it will spit out new string created with address of that string in memory. As below, 0x00000001c0c63740 is address of new string for my case:

(lldb) expression @"http://redacted-backend.redacted.com/"

(__NSCFString *) $5 = 0x00000001c0c63740 @"http://redacted-backend.redacted.com/"

(lldb)

Figure 21: Create a new string

Next, we need to set register x2 to hold new value by: register write x2 0x00000001c0c63740 and examine its value again, we can see x2 now reflected new value.

(lldb) register write x2 0x00000001c0c63740

(lldb) po $x2

http://redacted-backend.redacted.com/



(lldb)

Figure 22: Change register x2 to new value

Finally, just type c to resume application so it will continue to run with new URL endpoint, the process no longer hit the breakpoint and look into Hopper Disassembler we can see all of http://redacted-backend.redacted.com/ endpoints are shown in HTTP History tab with requests and responses details, MISSION COMPLETED!!!

Figure 23: Hopper Disassembler can see applications requests & responses

I see the light — Photo by Elias Maurer on Unsplash

Final thought

SSL Pinning only work with https protocol, so to downgrade to http requests we can bypass easily

protocol, so to downgrade to requests we can bypass easily REDACTED server supports both http and https protocol so the app works normally when downgraded to http

and protocol so the app works normally when downgraded to For servers that do not support http protocol, this kind SSL Pinning bypass will not work, but we can have our proxy to redirect http to https so it will work (client send http request to proxy -> proxy rewrite http to https request and send to the server)

protocol, this kind SSL Pinning bypass will not work, but we can have our proxy to redirect to so it will work (client send request to proxy -> proxy rewrite to request and send to the server) LLDB is very powerful!! Play with registers is an advanced skill we can learn. You can do anything as long as you can attach a debugger into the running app.

This app doesn’t have jailbreak and anti-debug detection, so we can do whatever we want without limitation. If you want to secure the app, you need to think about employing this detection in your codes.

Further readings

I hope you find this post helpful. Please follow and connect me on Twitter (@ReverseThatApp) to be notified on my upcoming posts ^_^