Debugger Commands Po Call Frame Variable Continue
The LLDB Debugger
- Quickstart
- Attach
- Finding variables
- Getting started
- Disassemble
- Registers
- Print and Expression
- Breakpoints
- Memory
- Scripting
- Watchpoint
- Settings
- lldb with Swift
- lldb with Objective C
- lldb with Objective-C Blocks
- lldb with C code
- Read Pointer Array
- Cast
- Stripped Binary, decayed pointers and memory writes
- Find, read and amend variable inside Parent Frame
- Structs
- Symbols
- Advanced
- Endians
- stdout
- Playing with the User Interface
- Facebook's Chisel
- Thread Pause / Thread Exit
- help lldb by setting the language
- lldb & rootless
- lldb bypass Certificate Pinning
- lldb on Jailbroken iOS device
- lldb bypass iOS Jailbreak detections
- lldb inspect third party SDK
- lldb lifting code iOS app
- lldb references
Quickstart
It is hard to do things quickly in lldb
unless you have set up an lldbinit
file.
cat ~/.lldbinit # Great scripts from https://github.com/DerekSelander/LLDB command script import ~/LLDB/lldb_commands/dslldb.py # Facebook's chisel command script import /usr/local/opt/chisel/libexec/fbchisellldb.py # Personal commands command script import python_lldb_scripts.py # Alias command alias -h "Run a command in the UNIX shell." -- yd_shell platform shell command alias yd_thread_beautify settings set thread-format "thread: #${thread.index}\t${thread.id%tid}\n{ ${module.file.basename}{ ` ${function.name-with-args} \n " command alias yd_register_beautify register read -f d command alias yd_smoke exp let $z = 5 command alias yd_swift settings set target.language swift command alias yd_objc settings set target.language objc command alias yd_c settings set target.language c command alias yd_stack_vars frame variable --no-args command alias yd_connect process connect connect://localhost:6666
Attach
# Load debugger lldb # Launch code from within lldb (lldb) process launch c_playground # Create target from within lldb (lldb) target create patched.bin # Create a target lldb --wait-for patched.bin (lldb) target create patched.bin # Attach to running app lldb -n open_app
Finding variables
# Frame frame info # Print variables in the Frame frame variable -A -T # Get pointer to variables inside the Frame fr v -L # Show the current thread's call stack bt # Move to another Frame to find variables frame select 1
Getting started
# Threads thread list # Brief list of attached Libraries image list -b # Sections of all loaded code image dump sections # Sections of a Module image dump sections myApp # Symbols of a Module image dump symtab myApp # Symbols of all loaded code (BAD IDEA) image dump symtab # Lookup help help image lookup # Lookup a Debug Symbol image lookup -r -n YDClass # Lookup non-debug symbols image lookup -r -s YDClass # Lookup Address image lookup -a 0x1000016a0 # Search for Object on Heap search -r 0x0000000100610570
Disassemble
# By address disas -s 0x00001620 # By function name disas -n Foo.Bar # By ObjC method disas -n "+[YDFileChecker asmSyscallFunction:]"
Registers
Argument | Register | x86_64 | arm64 |
---|---|---|---|
Return | - | RAX | - |
First | arg1 | RDI | x0 |
Second | arg2 | RSI | x1 |
Third | arg3 | RDX | x2 |
Fourth | arg4 | RCX | x3 |
Fifth | arg5 | R8 | x4 |
Sixth | arg6 | R9 | x5 |
Syscalls | - | syscall | x16 |
Print and Expression
# Print a register with useful aliase po $arg2 # Hex to Decimal p/d 0x1a // (int) $2 = 26 # Create char * and persist it ( the $ symbol ) po char * $new # Check for substring in a register po $new = (char *) strnstr ((char *)$rsi , "Info.plist", (int)strlen((char *) $rsi )) # Create NSString from Selector exp NSString * $myMethod = NSStringFromSelector(_cmd) # Get Selector po NSSelectorFromString($meth)
Breakpoints
# Getting the options help breakpoint set # Options to add script to Breakpoint help break command add # Delete all breakpoints b delete # List b list # Breakpoint on symbol name b syscall # Breakpoint on fullname breakpoint set -F access # Breakpoint on fullname in a single Module breakpoint set -F access -s libsystem_kernel.dylib # Breakpoint on Name and give the breakpoint a name b -n task_get_exception_ports -N fooName --auto-continue true # Breakpoint on Address ( gdb syntax ) b *0x1000016ce # Breakpoint on ObjC Class Method b "+[YDFileChecker foobar:]" # Breakpoint on Function, name the breakpoint and set condition br set -b "+[YDFileChecker foobar:]" -N fooName -c " $arg1 == 0x33" # Breakpoint on Address with name (lldb syntax ) br s -a 0x1000016ce -N fooName # Break on Register value ( SVC calls ) b set -N fooName --auto-continue true -c $x16==26 # Break on Register holding Info.plist substring br s -n syscall -c '(char *) strnstr((char *)$rsi, "Info.plist", (int)strlen((char *) $rsi)) != NULL' # Breakpoint on Selector breakpoint set --selector URLSession:didReceiveChallenge:completionHandler: # Breakpoint on Selector in Module breakpoint set --selector blah:blah: -s playModule # Regex Breakpoint on Selector ( good for Swift ) rb Foo.handleBarChallenge -s playModule -N fooName # Breakpoint naming breakpoint set --selector blah:blah: -s objc_play -N fooName # Breakpoint condition br mod -c $arg2 == "URLSession:didReceiveChallenge:completionHandler:" fooName # Break on exact ObjC Method b "-[MyUser name:]" # Breakpoint on completionHandler b -[YDURLSessionDel URLSession:didReceiveChallenge:completionHandler:] # Regex Breakpoint rb '\-\[UIViewController\ ' rb '\-\[YDUser(\(\w+\))?\ ' breakpoint set --func-regex=. --shlib=objc_play # Python script when Breakpoint fires (lldb) breakpoint command add -s python fooName Enter your Python command(s). Type 'DONE' to end. print( "[!]found it" ) DONE # Callback to Python function when Breakpoint hits (lldb) breakpoint command add -F ydscripts.YDHelloWorld fooName # Add & continue Python script when Breakpoint fires (lldb) breakpoint command add -s python fooName print lldb.frame.register[ "rsi" ].value lldb.frame.register[ "rsi" ].value = "1" print( "[*]new value set." ) thread = frame.GetThread() process = thread.GetProcess() process.Continue() DONE # Breakpoint all code inside a function (lldb) script >>> for a in range(0x1000016bc, 0x1000016d1): ... lldb.target.BreakpointCreateByAddress(a)
Memory
Read the string that is pointed to by a char* pointer
memory read 0x00007fff36d99fb5
Read five instructions after address
memory read --format instruction --count 5 0x10463d970
Read memory and print in format Decimal
mem read 0x00007ffee5f99610 -f d
Get start and end of search
(lldb) section [0x0000010462c000-0x00000107744000] 0x0003118000 MyApp`__TEXT [0x00000107744000-0x00000107d48000] 0x0000604000 MyApp`__DATA /* removed sections for brevity */
Find String in memory range
mem find -s "youtube" -- 0x00000107744000 0x00000107d48000
Read 100 bytes from address
memory read -c100 0x10793362c
Find every "B" character. Stop after 5 matches.
(lldb) mem find -s "B" -c 5 -- 0x00000100000000 0x00000100004000 data found at location: 0x10000063b 0x10000063b: 42 2e 64 79 6c 69 62 00 00 00 00 00 00 26 00 00 B.dylib......&.. data found at location: 0x100003f60 0x100003f60: 42 00 00 00 61 00 00 00 62 00 00 00 6f 00 00 00 B...a...b...o... 0x100003f70: 6f 00 00 00 6e 00 00 00 00 00 00 00 0a 5b 2a 5d o...n........[*] no more matches within the range.
Scripting
//See how many times a C function is called when running an iOS app. breakpoint set -n getenv breakpoint modify --auto-continue 1 breakpoint command add 1 po (char *)$arg1 // telling lldb how to cast $arg1 DONE continue
Watchpoint
Help
help watchpoint set
watchpoint list
watchpoint list
watchpoint delete
watch del 1
watchpoint on Global variable
watchpoint set variable file_exists
Once it stops
po file_exists = NO
watchpoint on frame variable
watchpoint set variable completionHandler
watchpoint on address in function
watchpoint set expression -w write -- "+[YDFileChecker checkFileExists]" + 32
watchpoint on register
watchpoint set expression -- $arg1
watchpoint on register
watchpoint set expression -w read_write -- $arg1
Delete some watchpoints, if you see this error
error: sending gdb watchpoint packet failed
Settings
show target.run-args
settings show target.run-args
show target.env-vars
settings show target.env-vars
Add setting to lldbinit file
echo "settings set target.x86-disassembly-flavor intel" >> ~/.lldbinit
Logging
settings set target.process.extra-startup-command QSetLogging:bitmask=LOG_ALL;
lldb with Swift
class lyftClass { static let request_number = 1 static let uri = "https://my.url/" let app_version = "app_99" func facebook() -> Int { return 96 } static func google() -> Int { return 42 } }
Invoking code from a Framework or the main application ?
If you are invoking code from a Swift dynamic framework, make sure to tell lldb about the Framework. Below is why...
(lldb) exp let $a = RustyAppInfo() error: <EXPR>:3:10: error: use of unresolved identifier 'RustyAppInfo' let $a = RustyAppInfo() ^~~~~~~~~~~~ (lldb) expr -- import rusty_nails (lldb) exp let $a = RustyAppInfo() // success. now you have a class instant.
lldb knew I was trying to write swift code. In an iOS app, where you have Swift and Objective-C code, I always find it useful to type:
(lldb) settings set target.language swift
Connect to app via lldb
I liked to put a breakpoint on the the AppDelegate class. If you let the app load, the context of Swift is automagically lost by lldb.
Print your class
(lldb) po lyftClass() <lyftClass: 0x60c000051dc0>
Create a class instance
expression let $lyft = rusty.lyftClass()
Invoke a member function from instance class
po $lyft.app_version()
Try and print a Class member
po $lyft.request_number error: <EXPR>:3:1: error: static member 'uri' cannot be used on instance of type 'lyftClass'
This fails as it is a Static class member and not accessible to the instantiated class.
Print a Class member
(lldb) po lyftClass.uri "https://my.url/"
Print Class function member
(lldb) po lyftClass.google() 42
Invoke Swift Class with Initializers
class lyftClass { static let url = "https://my.url/" let version = "app_99.0" let mickey: String let mouse: Int init(mickey: String, mouse: Int) { self.mickey = mickey self.mouse = mouse } convenience init(mickey: String){ self.init(mickey: mickey, mouse: 100) } func facebook_string() -> String { return "jibber jabber" } static func google_int() -> Int { return 42 } }
Create a class instance
(lldb) expression let $a = lyftClass() error: <EXPR>:3:10: error: cannot invoke initializer for type 'lyftClass' with no arguments <EXPR>:3:10: note: overloads for 'lyftClass' exist with these partially matching parameter lists: (mickey: String, mouse: Int), (mickey: String) let $a = lyftClass()
Invoke a function from instant class
(lldb) expression let $b = lyftClass(mickey: "zer", mouse: 500) (lldb) po $b <lyftClass: 0x60c000284470> (lldb) po $b.mickey "zer" (lldb) po $b.mouse 500
Create Class with convenience initializer
(lldb) expression let $a = lyftClass(mickey: "wow") (lldb) po $a <lyftClass: 0x60000009b580> (lldb) po $a.mickey "wow" (lldb) po $a.mouse 100
lldb with Objective C
Classes
@import Foundation; @interface Box:NSObject { double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box } @property(nonatomic, readwrite) double height; // Property -(double) volume; @end @implementation Box @synthesize height; -(id)init { self = [super init]; length = 1.0; breadth = 1.0; return self; } -(double) volume { return length*breadth*height; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Box *box1 = [[Box alloc]init]; // Create box1 object of type Box double volume = 0.0; // Store the volume of a box here // box 1 specification box1.height = 5.0; // volume of box 1 volume = [box1 volume]; NSLog(@"Volume of Box1 : %f", volume); } return 0; }
Create a new Class Instance
(lldb) exp Box *$b = [Box new]; I could have easily written: (lldb) exp Box *$a = [[Box alloc]init];
Print an initialised Class variable
Print the Class pointer
(lldb) po $b <Box: 0x100777d50> (lldb) po (Box*)$b <Box: 0x100777d50>
Set a Class variable
(lldb) exp $b->height = 20.0 (double) $8 = 20 (lldb) po $b->height 20
Access Instance Method
Invoke Instance Method with several parameters
- (void)getVersion:(int*)num1 minor:(int*)num2 patch:(int*)num3; (lldb) e SampleClass *$sample = [[SampleClass alloc]init]; (lldb) po $sample <SampleClass: 0x10040ae70> (lldb) exp [$sample getVersion:&a minor:&b patch:&c];
NSString to NSData and back
(lldb) exp @import Foundation (lldb) exp NSString* $str = @"hello string"; (lldb) po $str hello string (lldb) exp NSData* $data = [$str dataUsingEncoding:NSUTF8StringEncoding]; (lldb) po $data <74657374 73747269 6e67> (lldb) po (NSData*) $data <74657374 73747269 6e67> (lldb) exp NSString* $newStr = [[NSString alloc] initWithData:$data encoding:NSUTF8StringEncoding]; (lldb) po $newStr hello string
Booleans
(lldb) expression BOOL $myflag = YES (lldb) print $myflag (BOOL) $myflag = NO (lldb) expression $myflag = YES (BOOL) $7 = YES (lldb) print $myflag (BOOL) $myflag = YES
lldb with Objective-C Blocks
Write Block
--> remember, Blocks are on the Stacks. So if your debugger moves around and you created a Block it won't be available ( if you changed Frames / Stacks ) (lldb) exp 1 void (^$simpleBlock)(void) = ^{ 2 (void)NSLog(@"hello from a block!"); 3 }; 4
Call Block
(lldb) po $simpleBlock() [1136:66563] hello from a block!
Get Pointer to Block
(lldb) po $simpleBlock // get pointer to Block (void (^)()) $simpleBlock = 0x00000001025a9900
While Loop inside a Block
(lldb) expression 1 void (^$helloWhile)(int) = 2 ^(int a) { 3 while(a <10) { 4 printf("Hello %d\n", a); 5 a++; 6 }}; (lldb) po $helloWhile(2) Hello 2 Hello 3 Hello 4 ......
Add two numbers with a Block
(lldb) expression 1 int (^$add)(int, int) = 2 ^(int a, int b) { return a+b; } (lldb) p $add(3,4) (int) $0 = 7 (lldb) po $add 0x0000000101424110 (lldb) p $add (int (^)(int, int)) $add = 0x0000000101424110
Void Block
po void (^$fakeBlock)(int, NSURLCredential * _Nullable) =^(int a, NSURLCredential *b) {NSLog(@"hello. Original enum was set to %d", a);} po $fakeBlock(2,0)
Use Global Dispatch Block
(lldb) expression 1 dispatch_sync(dispatch_get_global_queue(0,0), ^(){ printf("Hello world\n"); });
Calling the Block with a Name
A more complicated example that gives the Block a name so it can be called like a function. (lldb) exp 1 double (^$multiplyTwoValues)(double, double) = 2 ^(double firstValue, double secondValue) { 3 return firstValue * secondValue; 4 }; 5 (lldb) po $multiplyTwoValues(2,4) 8 (lldb) exp double $result (lldb) p $result (double) $result = 0 (lldb) exp $result = $multiplyTwoValues(2,4) (double) $1 = 8 (lldb) po $result 8
Get the syntax
(lldb) expression Enter expressions, then terminate with an empty line to evaluate: 1 void(^$remover)(id, NSUInteger, BOOL *) = ^(id string, NSUInteger i,BOOL *stop){ 2 NSLog(@"ID: %lu String: %@", (unsigned long)i, string); 3 }; 4 (lldb) p $remover (void (^)(id, NSUInteger, BOOL *)) $remover = 0x00000001021a4110 (lldb) exp [oldStrings enumerateObjectsUsingBlock:$remover] ID: 0 String: odd ID: 1 String: raygun ID: 2 String: whoop whoop 3 String: doctor pants
lldb with C code
malloc / strcpy
Create a malloc char array, copy with strcpy, and free.
(lldb) e char *$str = (char *)malloc(8) (lldb) e (void)strcpy($str, "munkeys") (lldb) e $str[1] = 'o' (lldb) p $str (char *) $str = 0x00000001c0010460 "monkeys"
Warm-up - getenv
(lldb) e const char *$home = NULL (lldb) p *$home error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory (lldb) e $home = getenv("HOME") (const char *) $3 = 0x00007ffeefbff8d2 "/Users/foobar" (lldb) po $home "/Users/foobar" (lldb) p $home (const char *) $home = 0x00007ffeefbff8d2 "/Users/foobar"
Examine
(lldb) malloc_info --type 0x1c0010480 [+][+][+][+] The string is in the heap! [+][+][+][+] (lldb) memory read 0x00000001c0010460
Free
(lldb) e (void)free($str)
Print Bool
(lldb) po (bool) result <object returned empty description> (lldb) p result (bool) $2 = true (lldb) p/x result (bool) $0 = 0x01 (lldb) exp result = false (bool) $1 = false (lldb) p/x result (bool) $2 = 0x00 (lldb) p/t result (bool) $4 = 0b00000000 (lldb) exp result = true (bool) $5 = true (lldb) p/t result (bool) $6 = 0b00000001
Print Char Array
(lldb) po (char*) message "AAAA" (lldb) po message "AAAA" (lldb) p message (char *) $5 = 0x0000000100000fa9 "AAAA" (lldb) p *message (char) $1 = 'A'
Struct initialize
(lldb) expr struct YD_MENU_ITEMS $menu = {.menu_option = "a", .description = "all items"}; (lldb) expr struct VERSION_INFO $b error: typedef 'VERSION_INFO' cannot be referenced with a struct specifier (lldb) expr VERSION_INFO $b (lldb) p $b (VERSION_INFO) $b = (Major = 0, Minor = 0, Build = 0)
Enum initialize
(lldb) expr PAS_RESULT $a (lldb) po $a <nil> (lldb) p $a (PAS_RESULT) $a = 0 (lldb) exp $a = 2 (PAS_RESULT) $0 = 2
Cast return types
The flexibility of void *
is great. If you don't know how to cast the return handle you can point it to void
.
(lldb) exp (void*) getCurrentVersion(&$b); (void *) $2 = 0x0000000000000000 (lldb) p $b (VERSION_INFO) $b = (Major = 4, Minor = 6, Build = 13)
Banana Skins
Make sure you add the $
sign before a variable. Else you will hit:
error: warning: got name from symbols: b
Read Pointer Array
Source code
void foo_void ( float *input ) { printf("Pointer: %p.\n", input); <-- breakpoint here } int main ( void ) { float tiny_array[4]; tiny_array[0] = 1.0; tiny_array[1] = 2.0; tiny_array[2] = 3.0; tiny_array[3] = 4.0; foo_void ( tiny_array ); return 0; }
Solution
(lldb) fr v -L 0x00007ffeefbff4c8: (float *) input = 0x00007ffeefbff4f0 (lldb) script Python Interactive Interpreter. To exit, type 'quit()', 'exit()'. >>> ptr = lldb.frame.FindVariable('input') >>> print(ptr.GetValue()) // this prints the value NOT the offset 0x00007ffeefbff4f0 >>> print(ptr.GetType()) float * >>> print(ptr.GetLoadAddress()) 140732920755400 >>> ptr_type = ptr.GetType().GetPointeeType() >>> print(ptr_type) float >>> ptr_size_type = ptr_type.GetByteSize() >>> print(ptr_size_type) 4 >>> for i in range (0, 4): ... offset = ptr.GetValueAsUnsigned() + i * ptr_size_type ... val = lldb.target.CreateValueFromAddress("temp", lldb.SBAddress(offset, lldb.target), ptr_type) ... print(offset, val.GetValue()) ... (140732920755440, '1') (140732920755444, '2') (140732920755448, '3') (140732920755452, '4')
Cast
In a stripped binary - you can get a value from a register - as you will know the register position from documentation. But you won't have a variable symbol name and will probably need to help lldb with the Type. For example:
# same information in `input` and `arg1` # debug build (lldb) fr v -L 0x00007ffeefbff4c8: (int *) input = 0x00007ffeefbff4f0 (lldb) mem read 0x00007ffeefbff4f0 -c 16 0x7ffeefbff4f0: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 (lldb) script >>> ptr = lldb.frame.FindVariable( 'input' ) >>> print(ptr.GetType()) int *
What happens if I don't have debug symbols ?
>>> ptr = lldb.frame.FindRegister( "arg1" ) >>> print(ptr.GetType()) unsigned long >>> ptr.GetNumChildren() 1
Were you expecting it to have 4 children? lldb doesn't even know it is a pointer to a single integer or array of integers. At this point you are stuck, unless you help lldb.
>>> options = lldb.SBExpressionOptions() // help lldb by casting to int * >>> val = lldb.frame.EvaluateExpression("(int *) $arg1", options) >>> print(val) (int *) $0 = 0x00007ffeefbff4f0 >>> print(val.GetType()) int *
Stripped Binary, decayed pointers and memory writes
Source code
void foo_void (int *input) { printf( "Pointer: %p.\n " , input); } int main (void) { int tiny_array[4]; tiny_array[0] = 1; tiny_array[1] = 2; tiny_array[2] = 3; tiny_array[3] = 4; foo_void (tiny_array); return 0; }
When the breakpoint fires - I want to modify tiny_array[3]
. First, I will cast the register value to the type you expect:
>>> options = lldb.SBExpressionOptions() >>> ptr = lldb.frame.EvaluateExpression( "(int *) $arg1 " , options) >>> print(ptr) (int *) $0 = 0x00007ffeefbff4f0 >>> print(ptr.GetType()) int * >>> ptr_type = ptr.GetType().GetPointeeType() int >>> ptr_size_type = ptr_type.GetByteSize() 4 >>> offset = ptr.GetValueAsUnsigned() + 3 * ptr_size_type 140732920755452 >>> error = lldb.SBError() >>> new_int_as_bytes = str( '\xFF\x00\x00\x00' ) >>> result = lldb.process.WriteMemory(offset, new_int_as_bytes, error) >>> if not error.Success() or result != len(new_int_as_bytes): ... print('SBProcess.WriteMemory() failed! ') >>> offset = ptr.GetValueAsUnsigned() + 3 * ptr_size_type >>> print(lldb.target.CreateValueFromAddress("temp", lldb.SBAddress(offset, lldb.target), ptr_type)) (int) temp = 255 >>> for i in range (0, 4): ... offset = ptr.GetValueAsUnsigned() + i * ptr_size_type ... val = lldb.target.CreateValueFromAddress("temp", lldb.SBAddress(offset, lldb.target), ptr_type) ... print(offset, val.GetValueAsUnsigned()) (140732920755440, ' 1 ') (140732920755444, ' 2 ') (140732920755448, ' 3 ') (140732920755452, ' 32 ') >>> exit (lldb) mem read 0x00007ffeefbff4f0 -c 16 0x7ffeefbff4f0: 01 00 00 00 02 00 00 00 03 00 00 00 ff 00 00 00
Find, read and amend variable inside Parent Frame
Source code
void foo_void ( float *input ) { printf( "Pointer: %p.\n " , input); <-- Breakpoint here } int main ( void ) { float tiny_array[4]; tiny_array[0] = 1.0; tiny_array[1] = 2.0; tiny_array[2] = 3.0; tiny_array[3] = 4.0; foo_void ( tiny_array ); return 0; }
Solution
>>> print(lldb.frame.GetFunctionName()) foo_void >>> print(lldb.frame.get_parent_frame().GetFunctionName()) main >>> f = lldb.thread.GetFrameAtIndex(1) >>> ptr = f.FindVariable( 'tiny_array' ) >>> print(ptr) (float [4]) tiny_array = (1, 2, 3, 4) >>> print(ptr.GetChildAtIndex(1)) (float) [1] = 2 >>> print(ptr.AddressOf()) (float (*)[4]) &tiny_array = 0x00007ffeefbff540 >>> print(ptr.AddressOf().GetType()) float (*)[4] >>> print(ptr.TypeIsPointerType()) False >>> print(ptr.GetNumChildren()) 4 >>> print(ptr.GetLoadAddress()) 140732920755520 >>> ptr_type = ptr.AddressOf().GetType() >>> print(ptr_type) float (*)[4] >>> pointee_type = ptr_type.GetPointeeType() >>> print(pointee_type) float [4] >>> print(pointee_type.GetByteSize()) 16 >>> for i in range (0, ptr.GetNumChildren()): ... offset = ptr.GetLoadAddress() + i * (pointee_type.GetByteSize() / ptr.GetNumChildren()) ... print(offset, str(ptr.GetChildAtIndex(i))) ... (140732920755520, '(float) [0] = 1' ) (140732920755524, '(float) [1] = 2' ) (140732920755528, '(float) [2] = 3' ) (140732920755532, '(float) [3] = 4' ) >>> error = lldb.SBError() >>> result = ptr.GetChildAtIndex(i).SetValueFromCString( '0xFF' , error) >>> print(ptr.GetChildAtIndex(3)) (float) [3] = 255
Structs
C code:
// https://stackoverflow.com/questions/38251944/lldb-python-api-sbaddress-constructor-error struct Foo { int a; int b; }; void bar_void ( void *input ) { printf("Pointer: %p.\n", input); // BREAKPOINT HERE } int main ( void ) { struct Foo my_foo = { 111, 222 }; bar_void ( &my_foo ); return 0; }
LLDB commands:
(lldb) fr v -L 0x00007ffeefbff528: (void *) input = 0x00007ffeefbff550 (lldb) mem read 0x00007ffeefbff4f0 -c 16 0x7ffeefbff4f0: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 (lldb) script >>> ptr_type = lldb.target.FindFirstType('Foo').GetPointerType() >>> print(ptr_type) struct Foo * >>> print(type(ptr_type)) <class 'lldb.SBType'> >>> root = lldb.target.CreateValueFromAddress("root", lldb.SBAddress(0x00007ffeefbff538, lldb.target), ptr_type) >> print(root) (Foo *) root = 0x00007ffeefbff550 >>> root.GetValue() '0x00007ffeefbff550' >>> root.GetChildAtIndex(0).GetValue() '111' >>> root.GetChildAtIndex(1).GetValue() '222'
Symbols
>>> process = lldb.debugger.GetSelectedTarget().GetProcess() >>> target = debugger.GetSelectedTarget() >>> target = lldb.debugger.GetSelectedTarget() >>> module = target.GetModuleAtIndex(0) >>> print(type(module)) <class 'lldb.SBModule'> >>> for symbol in module: ... name = symbol.GetName() ... saddr = symbol.GetStartAddress() ... print(name, saddr)
Advanced
Check versions ( python, lldb )
script import sys; print(sys.version)
Launch
lldb attach -p $(ps x | grep -i -m1 debugger_challenge | awk '{print $1}')
// 'debugger_challenge' is app name
Import lldb script
command source <file_path>/lldb_script.txt
Import Python script
command script import <file_path>/lldb_python.py
lldb command line ( no XCode )
- Kill xcode - Run iOS app in the simulator - lldb attach -p $(ps x | grep -i -m1 debugger_challenge | awk '{print $1}')
Watch Packets ( caution )
log enable gdb-remote packets
Custom prompt
settings set prompt \-\>
Print with NSLog
exp (void)NSLog(@"πfoobar woobar");
// on a real iOS device, you don't need to caflush
for this to appear in console.app
Endians
>>> if byteOrder == lldb.eByteOrderLittle: ... pass ... elif byteOrder == lldb.eByteOrderBig: ... print("big endian") ... byteOrder.reverse() >>> print(lldb.eByteOrderLittle) 4 >>> print(lldb.eByteOrderBig) 1
stdout
If you use lldb --wait-for
or lldb -attach
you are attaching after a decision on where to send stdout
was made. For example:
// NSLog only sent to Console.app when you attach ./objc_playground_2 ps -ax lldb -p 3668 (lldb) exp @import Foundation (lldb) exp (void)NSLog(@"hello"); (lldb) c Process 3668 resuming < you can see the output to NSLog when you open console.app >
But you can control stdout
.
$) lldb (lldb) target create my_playground (lldb) process launch (lldb) exp @import Foundation (lldb) exp (void)NSLog(@"hello"); 2018-12-13 10:14:09.638801+0000 objc_playground_2[2776:61771] hello
Playing with the User Interface
lldb - print all View Controllers connected to current hierarchy
(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
lldb - Recursive description of current view
po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
UILabel change text
Find the ID of the UIlabel after running the recursiveDescription
command above.
(lldb) e id $myLabel = (id)0x104ec9370 (lldb) po $myLabel <MyApp.CustomUILabel: 0x104ec9370; baseClass = UILabel; frame = (0 0; 287 21); text = 'Boring default text...'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1d4291b70>> (lldb) po [$myLabel superview] <UIStackView: 0x104ec8f70; frame = (56 0; 287 88); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x1d443a620>> (lldb) p (int)[$myLabel setText:@"Bye World"] Nothing will happen. You need to refresh the screen or continue the app. (lldb) e (void)[CATransaction flush]
Change background Color
(lldb) e id $myView2 = (id)0x104f474e0 (lldb) v <UIView: 0x104f474e0; frame = (0 0; 375 603); autoresize = RM+BM; layer = <CALayer: 0x1d0239c20>> (lldb) e (void)[$myView2 setBackgroundColor:[UIColor blueColor]] (lldb) caflush // this is the Chisel alias for e (void)[CATransaction flush]
TabBar
po [[UIWindow keyWindow] rootViewController] e id $rootvc = (id)0x7fb9ce868200 expression -lobjc -O -- [`$rootvc` _shortMethodDescription] expression (void)[$rootvc setSelectedIndex:1] caflush expression (void)[$rootvc setSelectedIndex:0] caflush (lldb) po [$rootvc selectedViewController] <tinyDormant.YDJediVC: 0x7fb9cd613a80> (lldb) po [$rootvc viewControllers] <__NSArrayM 0x600001038810>( <tinyDormant.YDJediVC: 0x7fb9cd613a80>, <tinyDormant.YDMandalorianVC: 0x7fb9cd41f1c0> )
Part 1 : UITabBarController add a tab
(lldb) po [[UIWindow keyWindow] rootViewController] <UITabBarController: 0x7fdf0f036000> (lldb) e id $tbc = (id)0x7fdf0f036000 (lldb) po $tbc <UITabBarController: 0x7fdf0f036000> (lldb) po [$tbc description] <UITabBarController: 0x7fdf0f036000> // METHOD 1 (lldb) e Class $sithVcClass = (Class) objc_getClass("tinyDormant.YDSithVC") (lldb) e id $sithvc = (id)[$sithVcClass new] (lldb) po $sithvc <tinyDormant.YDSithVC: 0x7fb9cd426880> // METHOD 2 e id $newClass = (id)class_createInstance($sithVcClass, 100); (lldb) po [$tbc viewControllers] <__NSArrayM 0x6000029fc930>( <tinyDormant.YDJediVC: 0x7fdf0ef194e0>, <tinyDormant.YDMandalorianVC: 0x7fdf0ed23c50> ) // Create mutable array (lldb) e NSMutableArray *$listofvcontrollers = (NSMutableArray *)[$tbc viewControllers] // Add and Delete and View Controller from the array (lldb) po [$listofvcontrollers addObject:$sithvc] (lldb) po [$listofvcontrollers removeObjectAtIndex:0] // Print the array (lldb) po $listofvcontrollers <__NSArrayM 0x600001c32580>( <tinyDormant.YDMandalorianVC: 0x7fa476e15c40>, <tinyDormant.YDSithVC: 0x7fa476d033d0> ) (lldb) po [$tbc setViewControllers:$listofvcontrollers] nil
Part 2 : UITabBarController beautify
(lldb) search UITabBar <UITabBar: 0x7fa476e16be0; frame = (0 618; 375 49); autoresize = W+TM; gestureRecognizers = <NSArray: 0x60000082b690>; layer = <CALayer: 0x600000678b40>> (lldb) e id $tabs = (id)0x7fa476e16be0 (lldb) po [$tabs items] <__NSArrayI 0x600000826580>( <UITabBarItem: 0x7fae2f6164c0>, <UITabBarItem: 0x7fae2f6195a0>, <UITabBarItem: 0x7fae2f502380> selected ) (lldb) e int $sithIndex = [$listofvcontrollers indexOfObject:$sithvc] (lldb) po $sithIndex 2 (lldb) po [[[$tabs items] objectAtIndex:$sithIndex] setBadgeValue:@"99"]; (lldb) e UIImage *$sithimage = [UIImage imageNamed:@"approval"]; (lldb) e [[[$tabs items] objectAtIndex:$sithIndex] setImage:$sithimage] nil (lldb) caflush
Part 3 : UITabBarController add tint color
(lldb) po [$tabs barTintColor] nil (lldb) e (void)[$tabs setBarTintColor: [UIColor lightGrayColor]] 0x0000000108ea9e30 (lldb) caflush (lldb) e (void)[$tabs setBarTintColor: [UIColor greenColor]] (lldb) caflush
Part 3 : UINavigationBar add Right-Sided Button
(lldb) search UINavigationBar (lldb) e id $nc = (id)0x113d960e0 (lldb) po [$nc setBarTintColor: [UIColor greenColor]] (lldb) caflush
Push a new ViewController
(lldb) po [[UIWindow keyWindow] rootViewController] <UINavigationController: 0x105074a00> (lldb) e id $rootvc = (id)0x105074a00 (lldb) po $rootvc <UINavigationController: 0x105074a00> (lldb) e id $vc = [UIViewController new] (lldb) po $vc <UIViewController: 0x1067116e0> (lldb) e (void)[$rootvc pushViewController:$vc animated:YES] (lldb) caflush
Advanced : Wiring a more complicated U.I.
- Find a UITabBarController in memory
- Create a new UINavigationController
- Create a new UIViewController
- Connect the UIViewController to the UINavigationController
- Create a new array of UINavigationControllers
- Check the View Hierarchy to ensure it is wired correctly
(lldb) search UITabBarController <UITabBarController: 0x7fa3b0029600> (lldb) search UINavigationController <UINavigationController: 0x7fa3b0813600> (lldb) po id $nc = [[UINavigationController alloc] initWithRootViewController:$sithvc] (lldb) po $nc <UINavigationController: 0x7fa3af80a000> (lldb) search UINavigationController <UINavigationController: 0x7fa3af81a200> <UINavigationController: 0x7fa3b0813600> (lldb) po [$nc2 viewControllers] <__NSSingleObjectArrayI 0x600002005570>( <tinyDormant.YDSithVC: 0x7fa3af704430> ) (lldb) e Class $sithVcClass = (Class) objc_getClass("tinyDormant.YDSithVC") (lldb) e id $sithvc = (id)[$sithVcClass new] (lldb) po $sithvc <tinyDormant.YDSithVC: 0x7fa3af704430> // NOW I NEED TO ADD IT... (lldb) po [[UIWindow keyWindow] rootViewController] <UITabBarController: 0x7fa3b0029600> (lldb) e id $tbc = (id)0x7fa3b0029600 (lldb) po $tbc <UITabBarController: 0x7fa3b0029600> (lldb) e NSMutableArray *$listofvcontrollers = (NSMutableArray *)[$tbc viewControllers] (lldb) po $listofvcontrollers <__NSArrayM 0x600002cb89f0>( <tinyDormant.YDJediVC: 0x7fa3af727240>, <UINavigationController: 0x7fa3b0813600> ) (lldb) po [$listofvcontrollers addObject:$nc] (lldb) po $listofvcontrollers <__NSArrayM 0x600002cb89f0>( <tinyDormant.YDJediVC: 0x7fa3af727240>, <UINavigationController: 0x7fa3b0813600>, <UINavigationController: 0x7fa3af80a000> ) (lldb) po [$tbc setViewControllers:$listofvcontrollers] (lldb) search UITabBar <UITabBar: 0x7fa476e16be0; frame = (0 618; 375 49); autoresize = W+TM; gestureRecognizers = <NSArray: 0x60000082b690>; layer = <CALayer: 0x600000678b40>> (lldb) e id $tabs = (id)0x7fa476e16be0 (lldb) e UIImage *$sithimage = [UIImage imageNamed:@"UIBarButtonSystemItemFastForward"] (lldb) e (void)[[[$tabs items] objectAtIndex:$sithIndex] setImage:$sithimage] nil (lldb) caflush
WARNING - careful with copy and paste of text into lldb. I spent hours trying to work out why one the above commands was not working.
Facebook's Chisel
Read all Objects on a screen
Change a Swift View border
(lldb) pvc <UINavigationController 0x7fa47905fa00>, state: appeared, view: <UILayoutContainerView 0x7fa47862e9c0> | <DELETE_PROV_PROFILE_MACHINE.ydHomeVC 0x7fa47861dcc0>, state: appeared, view: <UIView 0x7fa4786327a0> (lldb) expr -l objc -- @import UIKit (lldb) border -c red -w 1.0 0x7fa4786327a0 (lldb) border -c red -w 5.0 0x7fa4786327a0
Find View Controller (fvc)
(lldb) fvc --view=0x7fc2c4410970 Found the owning view controller. <MYAPP.ydHomeVC: 0x7fc2c443d850>
hide a View
(lldb) pvc The current UIViewController that you want to hide… <UIViewController 0x1067116e0>, state: appearing, view: <UIView 0x10b707740> lldb) hide 0x10b707740
Show a hidden ViewControlller
var $window: UIWindow? $window = UIWindow(frame: UIScreen.main.bounds) let $mainViewController = ydHiddenVC() window?.rootViewController = $mainViewController $window?.makeKeyAndVisible() https://medium.com/@Dougly/a-uiviewcontroller-and-uiviews-without-storyboard-swift-3-543096e78f2
UILabel fun
<UIButtonLabel: 0x7f826bd2b090; frame = (0 3; 56 20.5); text = 'Submit'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003dc3ed0>> | | | | | <UILabel: 0x7f826be15710; frame = (148.5 12; 78.5 20.5); text = 'Feedback'; (lldb) mask 0x7f826bd2b090 (lldb) unmask 0x7f826bd2b090 (lldb) border -c yellow -w 2.0 0x7f826be15710 (lldb) border 0x7f826be15710
Cast from Swift to Objective-C object
<UILabel: 0x7f826bd2e8c0; frame = (33 10.5; 302 20); text = 'General feedback'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003db0e10>> (lldb) expression id $alien = (id)0x7f826bd2e8c0 // UILabel Object was created in Swift but you need access in Objective-C (lldb) po $alien <UILabel: 0x7f826bd2e8c0; frame = (33 10.5; 302 20); text = 'General feedback'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003db0e10>> (lldb) exp (void*)[$alien setText:@"odd"] (void *) $11 = 0x0000000107116010 You won't see anything until you.. (lldb) caflush (lldb) po $alien <UILabel: 0x7fd0b36444a0; frame = (172 12; 31 20.5); text = 'odd'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000031c8820>>
Demangle Swift ViewController Names
(lldb) search UIView -m myFramework // observe the de-mangled Swift name <myFramework.PageViewController: 0x7f826bf0c980> (lldb) search UIViewController -m myFramework // Great for Swift _TtC8myFramework18PageViewController * [0x00007f826bf0c980] (lldb) search -r 0x7f826bf0c980 // Now get all references to ViewController
Thread Pause / Thread Exit
Thread ID: dec:2264260 hex: 0x228cc4 Thread ID: dec:2264261 hex: 0x228cc5 shark: 0 jelly: 0 shark: 1 jelly: 1 shark: 2 jelly: 2 shark: 3 jelly: 3 shark: 4 jelly: 4 Program ended with exit code: 0
Output
(lldb) settings set thread-format "thread: #${thread.index}\t${thread.id%tid}\n{ ${module.file.basename}{`${function.name-with-args}\n" (lldb) thread list Process 3106 stopped thread: #1 0x1659a libsystem_kernel.dylib`__ulock_wait * thread: #2 0x165f7 objc_playground_2`hello_world(voidptr=0x0000000100633f50) thread: #3 0x165f2 libsystem_kernel.dylib`__workq_kernreturn thread: #4 0x165f4 libsystem_kernel.dylib`__workq_kernreturn thread: #5 0x165f8 objc_playground_2`hello_world(voidptr=0x0000000100634370) (lldb) exp NSTimeInterval $blockThreadTimer = 2 (lldb) exp [NSThread sleepForTimeInterval:$blockThreadTimer] (lldb) c Process 49868 resuming [+]Tiger Shark: thread ID: 0x14a075 [+]Lemon Shark: thread ID: 0x14a07a Tiger Shark: 0 Tiger Shark: 1 Tiger Shark: 2 Tiger Shark: 3 Tiger Shark: 4 Lemon Shark: 5 Lemon Shark: 6 Lemon Shark: 7 Lemon Shark: 8 Lemon Shark: 9
help lldb by setting the language
I hit this error too many times!
(lldb) e id $my_hello = [hello_from_objc new] error: <EXPR>:3:3: error: consecutive statements on a line must be separated by ';' id $my_hello = [hello_from_objc new] ^ ;
Tell lldb you are using Objective-C
(lldb) expression -l objective-c -o -- id $my_hello = [hello_from_objc new]
print all instance methods available to you..
expression -lobjc -O -- [$my _shortMethodDescription]
Set the expression language to Swift explicitly:
(lldb) expression -l swift -o -- let $myHello = HelloClass()
Set the expression language to Swift and call a function
(lldb) expression -l swift -o -- $myHello.hello()
Swift:
(lldb) settings set target.language swift
lldb & rootless
Apple's System Integrity Protection
$ sudo lldb -n Finder (lldb) process attach —name "Finder" /* fails if you don't disable Rootless */
lldb bypass Certificate Pinning
Bypass overview
"Do I trust the server, before sending data?". You will often find that question in iOS and Android app code. It refers to certificate pinning
.
The below script overwrites the answer to that question. The bypass requires a debugger (lldb
) a scripting language (python
) and writing values in memory (registers
).
Result
(lldb) br s -a 0x1000013ae -N fooName Breakpoint 2: where = objc_play`-[YDURLSessionDel URLSession:didReceiveChallenge:completionHandler:] + 334 at main.m:19:5, address = 0x00000001000013ae (lldb) c Process 48838 resuming πStart πChallenged on: www.google.com πCert chain length: 3 (lldb) yd_bypass_urlsession // run custom Python LLDB script [*]URLSession trust bypass started [*]Original of NSURLSessionAuthChallengeDisposition: (unsigned long) rsi = 0x0000000000000002 [!]NSURLSessionAuthChallengeDisposition set to Cancel. [*]PATCHING result: pass πHTTP Response Code: 200 πfinish
Background
App's often used a completionHandler
with Apple's NSURLSession
on iOS and macOS when deciding whether to start a network request
.
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
The above line of code is typical of an app that has implemented Certificate Pinning
and has decided to stop the network request from being sent.
Find the Needle in the Haystack
If you ingested the executable file into a disassembler like Hopper, you could find the assembly instruction
to patch out the answer.
Hopper had a really nice pseudo code flow of: URLSession:didReceiveChallenge:completionHandler:
:
/* @class YDURLSessionDel */ -(void)URLSession:(void *)arg2 didReceiveChallenge:(void *)arg3 completionHandler:(void *)arg4 { var_30 = [[arg3 protectionSpace] serverTrust]; [[arg3 protectionSpace] host]; NSLog(cfstring___m__); SecTrustGetCertificateCount(var_30); NSLog(cfstring___m__); (*(arg4 + 0x10))(arg4, 0x2, 0x0, arg4); return; }
We care about the line: (*(arg4 + 0x10))(arg4, 0x2, 0x0, arg4);
.
In assembly, that is this instruction:
0x1000016ce <+174>: call qword ptr [rcx + 0x10]
Sure enough, if you set a breakpoint
on this instruction
:
(lldb) po $arg1 <__NSStackBlock__: 0x7000050deba8> signature: "v24@?0q8@"NSURLCredential"16" invoke : 0x7fff30e47a04 (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFHTTPCookieStorageUnscheduleFromRunLoop) copy : 0x7fff30d3b7ed (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFURLCredentialStorageCopyAllCredentials) dispose : 0x7fff30d3b825 (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFURLCredentialStorageCopyAllCredentials) (lldb) po $arg2 2 (lldb) po $arg3 <nil> (lldb) po $arg4 <__NSStackBlock__: 0x7000050deba8> signature: "v24@?0q8@"NSURLCredential"16" invoke : 0x7fff30e47a04 (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFHTTPCookieStorageUnscheduleFromRunLoop) copy : 0x7fff30d3b7ed (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFURLCredentialStorageCopyAllCredentials) dispose : 0x7fff30d3b825 (/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork`CFURLCredentialStorageCopyAllCredentials)
What is the 2
value in the second register (arg2
)? If a server and connection was trusted or not, the result was often this value:
typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) { NSURLSessionAuthChallengeUseCredential = 0, /* Use the specified credential, which may be nil */ NSURLSessionAuthChallengePerformDefaultHandling = 1, /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, /* The entire request will be canceled; the credential parameter is ignored. */ NSURLSessionAuthChallengeRejectProtectionSpace = 3, /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */ }
Breakpoint and script
Most of the effort and skill was placing a breakpoint.
(lldb) br s -a 0x1000013ae -N fooName
You could then - now you have named the breakpoint - add instructions to the breakpoint OR you could invoke a Python script from the command line.
I choose to invoke my own Python script so it was simple to re-use this code on other apps. The main lines of the script were:
frame = exe_ctx.frame disposition = frame.FindRegister("rsi") if disposition.unsigned == 2: print "[!]NSURLSessionAuthChallengeDisposition set to Cancel." result = frame.registers[0].GetChildMemberWithName('rsi').SetValueFromCString("0x1", error) messages = {None: 'error', True: 'pass', False: 'fail'} print ("[*]PATCHING result: " + messages[result])
The trick was frame = exe_ctx.frame
. If you didn't have this context - from https://lldb.llvm.org/use/python-reference.html - you would get stuck for hours / days.
Try, try and try again
Like most bypass code, I tried multiple ideas. I removed the details of failed ones for brevity. If you care, essentially they were:
- Set
completionHandler
to NULL - Overwrite the instruction with no operation ( a
NOP instruction
) - Passing a
NULL Objective-C block
- Passing a fake
Objective-C block
- Drop the
(NSURLAuthenticationChallenge *)challenge
( failed as a lot of code depends on this challenge)
Source
@interface YDURLSessionDel : NSObject <NSURLSessionDelegate> @end @implementation YDURLSessionDel - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{ SecTrustRef trust = [[challenge protectionSpace] serverTrust]; NSLog(@"πChallenged on: %@", [[challenge protectionSpace] host]); NSLog(@"πCert chain length: %ld", (long)SecTrustGetCertificateCount(trust)); completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL); } @end int main(void) { @autoreleasepool { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSURL *url = [NSURL URLWithString:@"https://www.google.com"]; YDURLSessionDel *del = [[YDURLSessionDel alloc] init]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSLog(@"π @property waitsForConnectivity default: %hhd", config.waitsForConnectivity); config.waitsForConnectivity = YES; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:del delegateQueue:nil]; NSLog(@"π start"); NSURLSessionDataTask *task = [session dataTaskWithRequest: request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { if(error.code == -999) NSLog(@"π Bypass failed. Connection: %@ ( %ld)", [error localizedDescription], (long)error.code); } if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSLog(@"π HTTP Response Code: %ld", (long)[(NSHTTPURLResponse *)response statusCode]); } dispatch_semaphore_signal(semaphore); }]; [task resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"π finish"); } return 0; }
lldb on Jailbroken iOS device
# macOS - install iProxy brew install usbmuxd # for lldb over USB access iproxy 6666 6666 & # For SSH over USB access iproxy 2222 22 & # SSH onto jailbroken device ssh -p 2222 root@localhost # get process ID ps -ax | grep -i my_app # invoke lldb debugserver localhost:6666 -a my_app # Hijack the app, before it starts debugserver localhost:6666 --waitfor my_app # start lldb from Terminal on macOS $ lldb (lldb) process connect connect://localhost:6666 (lldb) thread list
Checkra1n Jailbreak
An SSH server is deployed on port 44 on localhost only.
iproxy 4444 44 & ssh -p 4444 root@localhost
Although SSH is installed by default, you need scp
for file copying. I installed openSSH
via Cydia
.
Electra Jailbreak
Electra shipped with debugserver
. Previous jailbreaks had lots of manual steps to get the correct debugserver
onto the device.
Electra app will not open
If you have a full Apple iOS developer license, you can code-sign ad-hoc
apps to last one year. If the Electra app
won't open, you can re-code sign the ipa file
. One way to achieve this:
- Open
Cydia Impactor
- Select
\Device\InstallPackage
- Find the
Electra.ipa
file - When prompted by
Cydia Impactor
enter your Apple ID. - Do NOT enter your password. Go to https://appleid.apple.com/ and generate a
APP-SPECIFIC PASSWORD
Now Electra
will work for another year.
Electra specifics
When you select Tweaks
, Electra runs the debug-server
from a different path:
# Tweaks enabled /Developer/usr/bin/debugserver localhost:6666 -a 794 # Tweaks disabled /usr/bin/debugserver localhost:6666 -a 794
Terminted due to Code Signing Error
You need to change the entitlements
inside the app bundle. Specifically: <key>get-task-allow</key>
:
security cms -D -i embedded.mobileprovision | grep -i -A 1 "get" <key>get-task-allow</key> <true/>
References
https://github.com/dmayer/idb/wiki/How-to:-ssh-via-usb https://kov4l3nko.github.io/blog/2018-05-25-my-experience-with-lldb-and-electra-jb/
lldb bypass iOS Jailbreak detections
Connect debugger
Set your iOS debugserver to wait for the app to be opened.
ssh onto Jailbroken devices Install the app on JB device root# /Developer/usr/bin/debugserver localhost:6666 -v DEBUG=1 -waitfor MYAPP // on JB device ssh session OPEN THE APP now the debugserver is waiting for a connection $) LLDB_SDK=ios lldb // from macOS machine (lldb) process connect connect://localhost:6666
Find the target
(lldb) lookup jail **************************************************** 2 hits in: MYAPP **************************************************** -[RSADeviceInfo jailBreak] -[RSADeviceInfo setJailBreak:]
Attack the Setter
(lldb) b -[RSADeviceInfo setJailBreak:] Breakpoint 2: where = MYAPP`-[RSADeviceInfo setJailBreak:], address = 0x00000001033fe1fc (lldb) c Process 1874 resuming Process 1874 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 2.1 frame #0: 0x00000001033fe1fc MYAPP` -[RSADeviceInfo setJailBreak:] (lldb) p (char *) $arg2 (char *) $2 = 0x00000001034bb104 "setJailBreak:" (lldb) p (char *) $arg3 (char *) $4 = 0x0000000000000005 <no value available> (lldb) p (int) $arg3 (int) $5 = 5. // this is the value register write $arg3 0 // put the value 0 (clean?) in the setter
Attack the getter
Kill the app and start the whole process of connecting again.
(lldb) b -[RSADeviceInfo jailBreak] Breakpoint 1: where = MYAPP`-[RSADeviceInfo jailBreak], address = 0x0000000100b821ec Process 1957 stopped (lldb) step until the return register is set $x0 on a physical iOS device frame #0: 0x0000000100b821f8 MYAPP` -[RSADeviceInfo jailBreak] + 12 MYAPP`-[RSADeviceInfo jailBreak]: -> 0x100b821f8 <+12>: ret (lldb) po (int) $x0 5 (lldb) register write $x0 0 (lldb) p (int) $x0 0 ππ Success ππ.
Summary
The getter
example is a little more work; you have to place the breakpoint
, step
until it sets the return register
and then modify the return value. All of that can be automated. Changing the Setter
is normally a one-time only call.
lldb inspect third party SDK
Get helper lldb scripts
https://github.com/DerekSelander/LLDB
Dump classes
dclass -o my_app
search classes on Heap
(lldb) search RSADeviceInfo <RSADeviceInfo: 0x1d019e780>
Inspect interesting Methods
(lldb) methods 0x1d019e780 <RSADeviceInfo: 0x1d019e780>: in RSADeviceInfo: Properties: @property (retain) NSString* Timestamp; (@synthesize Timestamp = Timestamp;) @property (retain) NSString* HardwareID; (@synthesize HardwareID = HardwareID;) @property (retain) NSString* SIM_ID; (@synthesize SIM_ID = SIM_ID;) @property (retain) NSString* PhoneNumber; (@synthesize PhoneNumber = PhoneNumber;) @property (retain) RSAGeoLocationInfo* GeoLocation; (@synthesize GeoLocation = GeoLocation;) @property (retain) NSString* DeviceModel; (@synthesize DeviceModel = DeviceModel;)
Invoke instance methods
(lldb) po [0x1d019e780 DeviceName] Security iPhone 8 (lldb) po [0x1d019e780 DeviceModel] iPhone (lldb) po [0x1d019e780 jailBreak] 0x0000000000000005 // very jailbroken
Create a class
(lldb) settings set target.language objc (lldb) exp RSADeviceInfo *$rsa = (id)[[RSADeviceInfo alloc] init] (lldb) po $rsa <RSADeviceInfo: 0x1c819ddc0>
lldb lifting code ( iOS app )
This article was written to show:
- a framework can be ripped out of an iOS app
- you can invoke Objective-C code without even importing the Modules
Find the target app
I found an app that was using a phone number validator. This was publicly available from: https://github.com/iziz/libPhoneNumber-iOS
Extract the App
I downloaded the app via the AppStore and then extracted it from a jailbroken device.
Extract the Framwork
Inside the app bundle, copy the entire libPhoneNumber-iOS
framework. Ignore the fact the Framwork is codesigned. xCode will resign the Framework later, when you create a fresh app.
Xcode
Create a new hello world project and drag in your lifted Framework.
The "lifted" code inside an iOS app was thinned. Don't try and run this on a simulator. Run the app in debug mode and connect lldb
.
Dump the classes
[+] script to dump classes from: https://github.com/DerekSelander/LLDB dclass -m libPhoneNumber_iOS Dumping classes ************************************************************ NBPhoneNumberUtil NBPhoneNumberDesc
attach lldb
(lldb) exp id $a = [NBPhoneNumberUtil new] (lldb) po $a <NBPhoneNumberUtil: 0x1c1c6b580> (lldb) expression -lobjc -O -- [$a _shortMethodDescription] // dumps all available Class, Properties and Instance Methods (lldb) exp NSString *$b = @"497666777000" (lldb) exp NSString *$nn = nil (lldb) exp NSNumber *$cc = (NSNumber *)[$a extractCountryCode:$b nationalNumber:&$nn] // using the github page, find how to invoke a method via Objective C, then apply it via lldb (lldb) p $cc (__NSCFNumber *) $cc = 0xb0000000000002c3 (long)49 (lldb) p $nn (__NSCFString *) $nn = 0x00000001c4227be0 @"7666777000"
lldb references
Man page
http://lldb.llvm.org/man/lldb.html
Patching code
https://www.inovex.de/blog/lldb-patch-your-code-with-breakpoints/
LLDB-Python script with excellent examples
https://rderik.com/blog/scanning-a-process-memory-using-lldb/
UI manipulation
https://www.objc.io/issues/19-debugging/lldb-debugging/
Custom breakpoints
https://lldb.llvm.org/use/python-reference.html#using-the-python-api-s-to-create-custom-breakpoints
ASM arm instructions
https://armconverter.com/
iOS Syscalls
https://www.theiphonewiki.com/wiki/Kernel_Syscalls#List_of_system_calls_from_iOS_6.0_GM_
iOS Syscalls
https://opensource.apple.com/source/xnu/xnu-4570.1.46/bsd/kern/syscalls.master
Debugger technical background
https://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1
Breakpoint commands
https://developer.apple.com/library/archive/documentation/General/Conceptual/lldb-guide/chapters/C3-Breakpoints.html
Hardware Breakpoints
https://reverse.put.as/2019/11/19/how-to-make-lldb-a-real-debugger/
watchpoints
https://www.raywenderlich.com/books/advanced-apple-debugging-reverse-engineering/v3.0/chapters/8-watchpoints#toc-chapter-011-anchor-001
Fun cheat sheet
https://gist.github.com/alanzeino/82713016fd6229ea43a8
Inspiration
https://github.com/DerekSelander/LLDB
Multi-line lldb commands
https://swifting.io/blog/2016/02/19/6-basic-lldb-tips/
lldb cheatsheet
https://www.nesono.com/sites/default/files/lldb%20cheat%20sheet.pdf
some lldb commands
https://gist.github.com/ryanchang/a2f738f0c3cc6fbd71fa
great lldb overview
https://www.bignerdranch.com/blog/xcode-breakpoint-wizardry/
more lldb info
https://www.objc.io/issues/19-debugging/lldb-debugging/
lldb | python References
https://lldb.llvm.org/python-reference.html
ptrace Reference
https://www.unvanquished.net/\~modi/code/include/x86\_64-linux-gnu/sys/ptrace.h.html
ptrace Anti-debugging
http://www.vantagepoint.sg/blog/89-more-android-anti-debugging-fun
ptrace Anti-debugging
https://knight.sc/debugging/2018/08/15/macos-kernel-debugging.html
ptrace enum values
http://www.secretmango.com/jimb/Whitepapers/ptrace/ptrace.html
anti-debug code samples
https://gist.github.com/joswr1ght/fb8c9f4f3f9a2feebf7f https://www.theiphonewiki.com/wiki/Bugging\_Debuggers
lldb and Blocks
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
Different ARM and x86 Registers
https://azeria-labs.com/arm-data-types-and-registers-part-2/
petersonthetooday.blogspot.com
Source: https://github.com/rustymagnet3000/lldb_debugger
0 Response to "Debugger Commands Po Call Frame Variable Continue"
Post a Comment