- 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
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
# 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
# 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
# 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
# By address
disas -s 0x00001620
# By function name
disas -n Foo.Bar
# By ObjC method
disas -n "+[YDFileChecker asmSyscallFunction:]"
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 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)
# 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 0x00007fff36d99fb5
memory read --format instruction --count 5 0x10463d970
mem read 0x00007ffee5f99610 -f d
(lldb) section
[0x0000010462c000-0x00000107744000] 0x0003118000 MyApp`__TEXT
[0x00000107744000-0x00000107d48000] 0x0000604000 MyApp`__DATA
/* removed sections for brevity */
mem find -s "youtube" -- 0x00000107744000 0x00000107d48000
memory read -c100 0x10793362c
(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.
//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
help watchpoint set
watchpoint list
watch del 1
watchpoint set variable file_exists
po file_exists = NO
watchpoint set variable completionHandler
watchpoint set expression -w write -- "+[YDFileChecker checkFileExists]" + 32
watchpoint set expression -- $arg1
watchpoint set expression -w read_write -- $arg1
error: sending gdb watchpoint packet failed
settings show target.run-args
settings show target.env-vars
echo "settings set target.x86-disassembly-flavor intel" >> ~/.lldbinit
settings set target.process.extra-startup-command QSetLogging:bitmask=LOG_ALL;
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
}
}
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
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.
(lldb) po lyftClass()
<lyftClass: 0x60c000051dc0>
expression let $lyft = rusty.lyftClass()
po $lyft.app_version()
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.
(lldb) po lyftClass.uri
"https://my.url/"
(lldb) po lyftClass.google()
42
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
}
}
(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()
(lldb) expression let $b = lyftClass(mickey: "zer", mouse: 500)
(lldb) po $b
<lyftClass: 0x60c000284470>
(lldb) po $b.mickey
"zer"
(lldb) po $b.mouse
500
(lldb) expression let $a = lyftClass(mickey: "wow")
(lldb) po $a
<lyftClass: 0x60000009b580>
(lldb) po $a.mickey
"wow"
(lldb) po $a.mouse
100
@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;
}
(lldb) exp Box *$b = [Box new];
I could have easily written:
(lldb) exp Box *$a = [[Box alloc]init];
(lldb) po $a->length
1
(lldb) po $b
<Box: 0x100777d50>
(lldb) po (Box*)$b
<Box: 0x100777d50>
(lldb) exp $b->height = 20.0
(double) $8 = 20
(lldb) po $b->height
20
(lldb) po $b.volume
20
- (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];
(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
(lldb) expression BOOL $myflag = YES
(lldb) print $myflag
(BOOL) $myflag = NO
(lldb) expression $myflag = YES
(BOOL) $7 = YES
(lldb) print $myflag
(BOOL) $myflag = YES
--> 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
(lldb) po $simpleBlock()
[1136:66563] hello from a block!
(lldb) po $simpleBlock // get pointer to Block
(void (^)()) $simpleBlock = 0x00000001025a9900
(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
......
(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
po void (^$fakeBlock)(int, NSURLCredential * _Nullable) =^(int a, NSURLCredential *b) {NSLog(@"hello. Original enum was set to %d", a);}
po $fakeBlock(2,0)
(lldb) expression
1 dispatch_sync(dispatch_get_global_queue(0,0),
^(){ printf("Hello world\n"); });
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
(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
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"
(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"
(lldb) malloc_info --type 0x1c0010480
[+][+][+][+] The string is in the heap! [+][+][+][+]
(lldb) memory read 0x00000001c0010460
(lldb) e (void)free($str)
(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
(lldb) po (char*) message
"AAAA"
(lldb) po message
"AAAA"
(lldb) p message
(char *) $5 = 0x0000000100000fa9 "AAAA"
(lldb) p *message
(char) $1 = 'A'
(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)
(lldb) expr PAS_RESULT $a
(lldb) po $a
<nil>
(lldb) p $a
(PAS_RESULT) $a = 0
(lldb) exp $a = 2
(PAS_RESULT) $0 = 2
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)
Make sure you add the $
sign before a variable. Else you will hit:
error: warning: got name from symbols: b
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;
}
(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')
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 *
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
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;
}
>>> 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
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'
>>> 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)
script import sys; print(sys.version)
lldb attach -p $(ps x | grep -i -m1 debugger_challenge | awk '{print $1}')
// 'debugger_challenge' is app name
command source <file_path>/lldb_script.txt
command script import <file_path>/lldb_python.py
- Kill xcode
- Run iOS app in the simulator
- lldb attach -p $(ps x | grep -i -m1 debugger_challenge | awk '{print $1}')
log enable gdb-remote packets
settings set prompt \-\>
exp (void)NSLog(@"😀foobar woobar");
// on a real iOS device, you don't need to caflush
for this to appear in console.app
>>> if byteOrder == lldb.eByteOrderLittle:
... pass
... elif byteOrder == lldb.eByteOrderBig:
... print("big endian")
... byteOrder.reverse()
>>> print(lldb.eByteOrderLittle)
4
>>> print(lldb.eByteOrderBig)
1
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
(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
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]
(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]
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>
)
(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
(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
(lldb) po [$tabs barTintColor]
nil
(lldb) e (void)[$tabs setBarTintColor: [UIColor lightGrayColor]]
0x0000000108ea9e30
(lldb) caflush
(lldb) e (void)[$tabs setBarTintColor: [UIColor greenColor]]
(lldb) caflush
(lldb) search UINavigationBar
(lldb) e id $nc = (id)0x113d960e0
(lldb) po [$nc setBarTintColor: [UIColor greenColor]]
(lldb) caflush
(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
- 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.
(lldb) pviews
(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
(lldb) fvc --view=0x7fc2c4410970
Found the owning view controller.
<MYAPP.ydHomeVC: 0x7fc2c443d850>
(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
<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
<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>>
(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 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
(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
(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]
^
;
(lldb) expression -l objective-c -o -- id $my_hello = [hello_from_objc new]
expression -lobjc -O -- [$my _shortMethodDescription]
(lldb) expression -l swift -o -- let $myHello = HelloClass()
(lldb) expression -l swift -o -- $myHello.hello()
(lldb) settings set target.language swift
Apple's System Integrity Protection
$ sudo lldb -n Finder
(lldb) process attach —name "Finder"
/* fails if you don't disable Rootless */
"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
).
(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
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.
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. */
}
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.
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)
@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;
}
# 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
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 shipped with debugserver
. Previous jailbreaks had lots of manual steps to get the correct debugserver
onto the device.
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.
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
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/>
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/
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
(lldb) lookup jail
****************************************************
2 hits in: MYAPP
****************************************************
-[RSADeviceInfo jailBreak]
-[RSADeviceInfo setJailBreak:]
(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
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 🐝🐝.
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.
https://github.com/DerekSelander/LLDB
dclass -o my_app
(lldb) search RSADeviceInfo
<RSADeviceInfo: 0x1d019e780>
(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;)
(lldb) po [0x1d019e780 DeviceName]
Security iPhone 8
(lldb) po [0x1d019e780 DeviceModel]
iPhone
(lldb) po [0x1d019e780 jailBreak]
0x0000000000000005 // very jailbroken
(lldb) settings set target.language objc
(lldb) exp RSADeviceInfo *$rsa = (id)[[RSADeviceInfo alloc] init]
(lldb) po $rsa
<RSADeviceInfo: 0x1c819ddc0>
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
I found an app that was using a phone number validator. This was publicly available from: https://github.com/iziz/libPhoneNumber-iOS
I downloaded the app via the AppStore and then extracted it from a jailbroken device.
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.
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
.
[+] script to dump classes from: https://github.com/DerekSelander/LLDB
dclass -m libPhoneNumber_iOS
Dumping classes
************************************************************
NBPhoneNumberUtil
NBPhoneNumberDesc
(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"
http://lldb.llvm.org/man/lldb.html
https://www.inovex.de/blog/lldb-patch-your-code-with-breakpoints/
https://rderik.com/blog/scanning-a-process-memory-using-lldb/
https://www.objc.io/issues/19-debugging/lldb-debugging/
https://lldb.llvm.org/use/python-reference.html#using-the-python-api-s-to-create-custom-breakpoints
https://www.theiphonewiki.com/wiki/Kernel_Syscalls#List_of_system_calls_from_iOS_6.0_GM_
https://opensource.apple.com/source/xnu/xnu-4570.1.46/bsd/kern/syscalls.master
https://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1
https://reverse.put.as/2019/11/19/how-to-make-lldb-a-real-debugger/
https://gist.github.com/alanzeino/82713016fd6229ea43a8
https://github.com/DerekSelander/LLDB
https://swifting.io/blog/2016/02/19/6-basic-lldb-tips/
https://www.nesono.com/sites/default/files/lldb%20cheat%20sheet.pdf
https://gist.github.com/ryanchang/a2f738f0c3cc6fbd71fa
https://www.bignerdranch.com/blog/xcode-breakpoint-wizardry/
https://www.objc.io/issues/19-debugging/lldb-debugging/
https://lldb.llvm.org/python-reference.html
https://www.unvanquished.net/\~modi/code/include/x86\_64-linux-gnu/sys/ptrace.h.html
http://www.vantagepoint.sg/blog/89-more-android-anti-debugging-fun
https://knight.sc/debugging/2018/08/15/macos-kernel-debugging.html
http://www.secretmango.com/jimb/Whitepapers/ptrace/ptrace.html
https://azeria-labs.com/arm-data-types-and-registers-part-2/