Debugging with lldb - part 1
Binary Analysis 101
Math guy who's into Cryptography, into iOS/MacOS development, and obviously into hacking/pentesting. Writing stuff in C/C++/ObjectiveC/Swift/Python/Assembly.
Abstract
In the previous article (MachO Binary Analysis with objdump), we have followed step-by-step the compilation of an Objective C program. We have seen all the steps that lead from the code to the Assembly. Now it's time to see what happens at the assembly level.
We will use LLDB, the XCode debugger. It's gonna be a challenge for me as well, for I am more versed in GDB.
Debugging with LLDB
Previously we used a 'dry' version of the 'myNumber' program - I just added some printouts in the code to simplify the comprehension of the assembly code.
We will work with the following program:
//
// main.m
// DebugMe
//
// Created by Gabriel Biondo on 24/03/2022.
//
#import <Foundation/Foundation.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define STRING_FORMAT @"%@"
#define START_MSG @"INITIALISING RANDOMNESS"
#define GENERATE @"GENERATING RANDOM NUMBER"
#define MAXIMUM 123
@interface myNumber: NSObject
@property int value;
- (Boolean) isPerfectSquare;
- (int) nearestPerfectSquare;
- (Boolean) isPrime;
- (void) randomInit;
@end
@implementation myNumber
- (void) randomInit {
NSLog(STRING_FORMAT, START_MSG);
srand(time(0));
NSLog(STRING_FORMAT, GENERATE);
int num = rand() % MAXIMUM;
NSLog(@"Generated number: %i", num);
self.value = num;
}
- (Boolean) isPerfectSquare{
NSLog(@"Checking perfect square");
double num = (double)self.value;
double sqr = sqrt(num);
int squareRoot = (int) sqr;
return (squareRoot*squareRoot == self.value);
}
- (int) nearestPerfectSquare {
NSLog(@"Looking for perfect squares");
int nearest = 0;
if ([self isPerfectSquare]) {
nearest = self.value;
} else {
double num = (double)self.value;
double sqr = sqrt(num);
int low = (int) sqr;
int hi = low + 1;
int lowq = low * low;
int hiq = hi * hi;
int deltaLow = self.value - lowq;
int deltaHi = hiq - self.value;
if (deltaLow < deltaHi) {
NSLog(@"The nearest square is the lowest one");
nearest = lowq;
}
if (deltaHi < deltaLow) {
NSLog(@"The nearest square is the highest one");
nearest = hiq;
}
if (deltaHi == deltaLow){
NSLog(@"The given number is exactly in the middle of two perfect squares: %i and %i. Returning the lowest", lowq,hiq);
nearest = lowq;
}
}
NSLog(@"Returning the value");
return nearest;
}
- (Boolean) isPrime{
NSLog(@"Checking primality");
Boolean result = TRUE;
if (self.value > 2){
double num = (double)self.value;
double sqr = sqrt(num);
int threshold = 1 + (int) sqr;
for (int i=2; i<=threshold; i++){
if ((self.value % i) == 0) {
result = FALSE;
}
}
} else {
result = FALSE;
}
return result;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
myNumber * m = [myNumber new];
myNumber * n = [myNumber new];
myNumber * o = [[myNumber alloc] init];
myNumber * p = [[myNumber alloc] init];
myNumber * q = [[myNumber alloc] init];
n.value = 144;
m.value = 155;
o.value = 20;
p.value = 73;
if ([n isPerfectSquare]) {
NSLog(@"%i is a perfect square", n.value);
} else {
NSLog(@"%i is not a perfect square", n.value);
}
if ([m isPerfectSquare]) {
NSLog(@"%i is a perfect square", m.value);
} else {
NSLog(@"%i is not a perfect square", m.value);
}
int k = [m nearestPerfectSquare];
NSLog(@"The nearest square to %i is %i", m.value, k);
int h = [n nearestPerfectSquare];
NSLog(@"The nearest square to %i is %i", n.value, h);
int j = [o nearestPerfectSquare];
NSLog(@"The nearest square to %i is %i", o.value, j);
int i = [p nearestPerfectSquare];
NSLog(@"The nearest square to %i is %i", p.value, i);
if ([p isPrime]) {
NSLog(@"%i is prime", p.value);
} else {
NSLog(@"%i is not prime", p.value);
}
if ([m isPrime]) {
NSLog(@"%i is prime", m.value);
} else {
NSLog(@"%i is not prime", m.value);
}
[q randomInit];
}
return 0;
}
Typical output of this program is as follows:
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] 144 is a perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] 155 is not a perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Looking for perfect squares
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square is the lowest one
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Returning the value
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square to 155 is 144
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Looking for perfect squares
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Returning the value
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square to 144 is 144
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Looking for perfect squares
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square is the lowest one
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Returning the value
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square to 20 is 16
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Looking for perfect squares
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking perfect square
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square is the highest one
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Returning the value
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] The nearest square to 73 is 81
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] Checking primality
2022-03-30 10:40:42.775 myNumberExtended[97845:5242525] 73 is prime
2022-03-30 10:40:42.776 myNumberExtended[97845:5242525] Checking primality
2022-03-30 10:40:42.776 myNumberExtended[97845:5242525] 155 is not prime
2022-03-30 10:40:42.776 myNumberExtended[97845:5242525] INITIALISING RANDOMNESS
2022-03-30 10:40:42.776 myNumberExtended[97845:5242525] GENERATING RANDOM NUMBER
2022-03-30 10:40:42.776 myNumberExtended[97845:5242525] Generated number: 58
We attach lldb to a program simply by launching lldb myNumberExtended. Easy as π.
Working with breakpoints
The very first thing I used to do in GDB was to set a breakpoint for the main routine - if I emulate this here, I obtain:
(lldb) target create "myNumberExtended"
Current executable set to '/Users/gbiondo/EXP312/Debugging/myNumberExtended' (x86_64).
(lldb) breakpoint set -name main
Breakpoint 1: 10 locations.
(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 10
1.1: where = myNumberExtended`main, address = myNumberExtended[0x00000001000039f0], unresolved, hit count = 0
1.2: where = Foundation`-[NSBlockOperation main], address = Foundation[0x00007ff801229338], unresolved, hit count = 0
1.3: where = Foundation`-[NSThread main], address = Foundation[0x00007ff8012403c5], unresolved, hit count = 0
1.4: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = Foundation[0x00007ff801240eef], unresolved, hit count = 0
1.5: where = Foundation`-[NSInvocationOperation main], address = Foundation[0x00007ff80125580b], unresolved, hit count = 0
1.6: where = Foundation`-[NSOperation main], address = Foundation[0x00007ff801256098], unresolved, hit count = 0
1.7: where = Foundation`-[NSFilesystemItemMoveOperation main], address = Foundation[0x00007ff8012955b7], unresolved, hit count = 0
1.8: where = Foundation`-[NSDirectoryTraversalOperation main], address = Foundation[0x00007ff8012d9492], unresolved, hit count = 0
1.9: where = Foundation`-[_NSBarrierOperation main], address = Foundation[0x00007ff8013a6e9c], unresolved, hit count = 0
1.10: where = Security`Security::OSXCode::main(), address = Security[0x00007ff802617780], unresolved, hit count = 0
Ten different locations for a breakpoint are not what I want. Let's delete them and find a better approach. Here it seems there's no "dot notation", but "tick notation", hence I proceed as follows:
(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n]
All breakpoints removed. (1 breakpoint)
(lldb) breakpoint set myNumberExtended`main
error: invalid combination of options for the given command
This looks odd, at a first glance: if i use the breakpoint command, I receive an error. I googled a little bit to find the solution - here one needs to use the _regexp-break command:
(lldb) help _regexp-break
Set a breakpoint using one of several shorthand formats. Expects 'raw' input (see 'help raw-input'.)
Syntax:
_regexp-break <filename>:<linenum>:<colnum>
main.c:12:21 // Break at line 12 and column 21 of main.c
_regexp-break <filename>:<linenum>
main.c:12 // Break at line 12 of main.c
_regexp-break <linenum>
12 // Break at line 12 of current file
_regexp-break 0x<address>
0x1234000 // Break at address 0x1234000
_regexp-break <name>
main // Break in 'main' after the prologue
_regexp-break &<name>
&main // Break at first instruction in 'main'
_regexp-break <module>`<name>
libc.so`malloc // Break in 'malloc' from 'libc.so'
_regexp-break /<source-regex>/
/break here/ // Break on source lines in current file
// containing text 'break here'.
The command is shortened with the letter b. Now we can do:
(lldb) b myNumberExtended`main
Breakpoint 2: where = myNumberExtended`main, address = 0x00000001000039f0
So far, so good. Let's execute it and see what happens:
(lldb) r
Process 69433 launched: '/Users/gbiondo/EXP312/Debugging/myNumberExtended' (x86_64)
Process 69433 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x00000001000039f0 myNumberExtended`main
myNumberExtended`main:
-> 0x1000039f0 <+0>: push rbp
0x1000039f1 <+1>: mov rbp, rsp
0x1000039f4 <+4>: sub rsp, 0x50
0x1000039f8 <+8>: mov dword ptr [rbp - 0x4], 0x0
Target 0: (myNumberExtended) stopped.
The breakpoint has been hit. Now it would be good to set breakpoints for other subroutines. I recently discovered this page (GDB to LLDB command map) that maps GDB commands to LLDB - saving me big headaches. There i find this shortcut:
This one finds debug symbols:
(lldb) image lookup -r -n <FUNC_REGEX>
This one finds non-debug symbols:
(lldb) image lookup -r -s <FUNC_REGEX>
so I work as follows:
(lldb) image lookup -r -n randomInit
1 match found in /Users/gbiondo/EXP312/Debugging/myNumberExtended:
Address: myNumberExtended[0x0000000100003680] (myNumberExtended.__TEXT.__text + 0)
Summary: myNumberExtended`-[myNumber randomInit]
(lldb) image lookup -r -n isPerfectSquare
1 match found in /Users/gbiondo/EXP312/Debugging/myNumberExtended:
Address: myNumberExtended[0x0000000100003710] (myNumberExtended.__TEXT.__text + 144)
Summary: myNumberExtended`-[myNumber isPerfectSquare]
(lldb) image lookup -r -n nearestPerfectSquare
1 match found in /Users/gbiondo/EXP312/Debugging/myNumberExtended:
Address: myNumberExtended[0x00000001000037a0] (myNumberExtended.__TEXT.__text + 288)
Summary: myNumberExtended`-[myNumber nearestPerfectSquare]
(lldb) image lookup -r -n isPrime
1 match found in /Users/gbiondo/EXP312/Debugging/myNumberExtended:
Address: myNumberExtended[0x00000001000038e0] (myNumberExtended.__TEXT.__text + 608)
Summary: myNumberExtended`-[myNumber isPrime]
Obviously I can add a breakpoint for isPrime as done before:
(lldb) b myNumberExtended`-[myNumber isPrime]
Breakpoint 3: where = myNumberExtended`-[myNumber isPrime], address = 0x00000001000038e0
or i can set a breakpoint at a memory address (here I do it for randomInit):
(lldb) breakpoint set -a 0x0000000100003680
Breakpoint 4: where = myNumberExtended`-[myNumber randomInit], address = 0x0000000100003680
choose your bane and go ahead with all remaining subroutines. At the end of the process you will end up with something like:
(lldb) breakpoint list
Current breakpoints:
2: name = 'main', module = myNumberExtended, locations = 1, resolved = 1, hit count = 1
2.1: where = myNumberExtended`main, address = 0x00000001000039f0, resolved, hit count = 1
3: name = '-[myNumber isPrime]', module = myNumberExtended, locations = 1
3.1: where = myNumberExtended`-[myNumber isPrime], address = 0x00000001000038e0, unresolved, hit count = 0
4: address = myNumberExtended[0x0000000100003680], locations = 1
4.1: where = myNumberExtended`-[myNumber randomInit], address = 0x0000000100003680, unresolved, hit count = 0
5: name = '-[myNumber isPerfectSquare]', module = myNumberExtended, locations = 1
5.1: where = myNumberExtended`-[myNumber isPerfectSquare], address = 0x0000000100003710, unresolved, hit count = 0
6: name = '-[myNumber nearestPerfectSquare]', module = myNumberExtended, locations = 1
6.1: where = myNumberExtended`-[myNumber nearestPerfectSquare], address = 0x00000001000037a0, unresolved, hit count = 0
Note that the first execution of the program changed the status of the first breakpoint from unresolved to resolved, and the hit count has been incremented to 1.
If we run the program now, it will stop at each subroutine. To continue just type c and hit return (or just hit return, if you already typed c-return once. The key return hit alone repeats the last command). You will see something like:
(lldb) r
Process 72978 launched: '/Users/gbiondo/EXP312/Debugging/myNumberExtended' (x86_64)
Process 72978 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x00000001000039f0 myNumberExtended`main
myNumberExtended`main:
-> 0x1000039f0 <+0>: push rbp
0x1000039f1 <+1>: mov rbp, rsp
0x1000039f4 <+4>: sub rsp, 0x50
0x1000039f8 <+8>: mov dword ptr [rbp - 0x4], 0x0
Target 0: (myNumberExtended) stopped.
(lldb) c
Process 72978 resuming
Process 72978 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
frame #0: 0x0000000100003710 myNumberExtended`-[myNumber isPerfectSquare]
myNumberExtended`-[myNumber isPerfectSquare]:
-> 0x100003710 <+0>: push rbp
0x100003711 <+1>: mov rbp, rsp
0x100003714 <+4>: sub rsp, 0x30
0x100003718 <+8>: mov rax, rdi
Target 0: (myNumberExtended) stopped.
(lldb) c
Process 72978 resuming
2022-03-29 13:57:12.271864+0100 myNumberExtended[72978:4841122] Checking perfect square
2022-03-29 13:57:12.272346+0100 myNumberExtended[72978:4841122] 144 is a perfect square
Process 72978 stopped
I added some spaces to help reading the output, obtaining three blocks. The control flow enters the first block when invoking main, and when the isPerfectSquare routine gets invoked, the control flow passes to the second block. Once we hit c, the output is presented, the routine exits, and the control flow returns to main.
Here's the list of breakpoints of the program:
(lldb) breakpoint list
Current breakpoints:
2: name = 'main', module = myNumberExtended, locations = 1, resolved = 1, hit count = 2
2.1: where = myNumberExtended`main, address = 0x00000001000039f0, resolved, hit count = 2
3: name = '-[myNumber isPrime]', module = myNumberExtended, locations = 1, resolved = 1, hit count = 2
3.1: where = myNumberExtended`-[myNumber isPrime], address = 0x00000001000038e0, resolved, hit count = 2
4: address = myNumberExtended[0x0000000100003680], locations = 1, resolved = 1, hit count = 1
4.1: where = myNumberExtended`-[myNumber randomInit], address = 0x0000000100003680, resolved, hit count = 1
5: name = '-[myNumber isPerfectSquare]', module = myNumberExtended, locations = 1, resolved = 1, hit count = 6
5.1: where = myNumberExtended`-[myNumber isPerfectSquare], address = 0x0000000100003710, resolved, hit count = 6
6: name = '-[myNumber nearestPerfectSquare]', module = myNumberExtended, locations = 1, resolved = 1, hit count = 4
6.1: where = myNumberExtended`-[myNumber nearestPerfectSquare], address = 0x00000001000037a0, resolved, hit count = 4
Breakpoint 6 served us well - let's give it some rest:
(lldb) breakpoint delete 6
1 breakpoints deleted; 0 breakpoint locations disabled.
Far from being the masters of the breakpoints, we now have a fair understanding of how to use them.
Disassembling
Disassembling the whole program can be quite useless - also disassembling the whole main routine produces a lot of code. Remember: the final objective of these efforts would be doing reverse engineering, so we want to make our lives easier... and we have to consider the fact that - at least until now - we are not able to write any assembly :)!
Let's start by disassembling one routine, for instance nearestPerfectSquare. We proceed as follows:
(lldb) disassemble -n nearestPerfectSquare
myNumberExtended`-[myNumber nearestPerfectSquare]:
myNumberExtended[0x100003710] <+0>: push rbp
myNumberExtended[0x100003711] <+1>: mov rbp, rsp
myNumberExtended[0x100003714] <+4>: sub rsp, 0x50
myNumberExtended[0x100003718] <+8>: mov rax, rdi
myNumberExtended[0x10000371b] <+11>: lea rdi, [rip + 0x98e] ; @"Looking for perfect squares"
myNumberExtended[0x100003722] <+18>: mov qword ptr [rbp - 0x8], rax
myNumberExtended[0x100003726] <+22>: mov qword ptr [rbp - 0x10], rsi
myNumberExtended[0x10000372a] <+26>: mov al, 0x0
myNumberExtended[0x10000372c] <+28>: call 0x100003cda ; symbol stub for: NSLog
myNumberExtended[0x100003731] <+33>: mov dword ptr [rbp - 0x14], 0x0
myNumberExtended[0x100003738] <+40>: mov rdi, qword ptr [rbp - 0x8]
myNumberExtended[0x10000373c] <+44>: mov rsi, qword ptr [rip + 0x4a75] ; "isPerfectSquare"
myNumberExtended[0x100003743] <+51>: call qword ptr [rip + 0x8b7] ; (void *)0x0000000000000000
myNumberExtended[0x100003749] <+57>: cmp al, 0x0
myNumberExtended[0x10000374b] <+59>: je 0x10000376a ; <+90>
myNumberExtended[0x100003751] <+65>: mov rdi, qword ptr [rbp - 0x8]
myNumberExtended[0x100003755] <+69>: mov rsi, qword ptr [rip + 0x4a54] ; "value"
myNumberExtended[0x10000375c] <+76>: call qword ptr [rip + 0x89e] ; (void *)0x0000000000000000
myNumberExtended[0x100003762] <+82>: mov dword ptr [rbp - 0x14], eax
myNumberExtended[0x100003765] <+85>: jmp 0x10000385a ; <+330>
myNumberExtended[0x10000376a] <+90>: mov rdi, qword ptr [rbp - 0x8]
myNumberExtended[0x10000376e] <+94>: mov rsi, qword ptr [rip + 0x4a3b] ; "value"
myNumberExtended[0x100003775] <+101>: call qword ptr [rip + 0x885] ; (void *)0x0000000000000000
myNumberExtended[0x10000377b] <+107>: cvtsi2sd xmm0, eax
myNumberExtended[0x10000377f] <+111>: movsd qword ptr [rbp - 0x20], xmm0
myNumberExtended[0x100003784] <+116>: movsd xmm0, qword ptr [rbp - 0x20] ; xmm0 = mem[0],zero
myNumberExtended[0x100003789] <+121>: sqrtsd xmm0, xmm0
myNumberExtended[0x10000378d] <+125>: movsd qword ptr [rbp - 0x28], xmm0
myNumberExtended[0x100003792] <+130>: cvttsd2si eax, qword ptr [rbp - 0x28]
myNumberExtended[0x100003797] <+135>: mov dword ptr [rbp - 0x2c], eax
myNumberExtended[0x10000379a] <+138>: mov eax, dword ptr [rbp - 0x2c]
myNumberExtended[0x10000379d] <+141>: add eax, 0x1
myNumberExtended[0x1000037a0] <+144>: mov dword ptr [rbp - 0x30], eax
myNumberExtended[0x1000037a3] <+147>: mov eax, dword ptr [rbp - 0x2c]
myNumberExtended[0x1000037a6] <+150>: imul eax, dword ptr [rbp - 0x2c]
myNumberExtended[0x1000037aa] <+154>: mov dword ptr [rbp - 0x34], eax
myNumberExtended[0x1000037ad] <+157>: mov eax, dword ptr [rbp - 0x30]
myNumberExtended[0x1000037b0] <+160>: imul eax, dword ptr [rbp - 0x30]
myNumberExtended[0x1000037b4] <+164>: mov dword ptr [rbp - 0x38], eax
myNumberExtended[0x1000037b7] <+167>: mov rdi, qword ptr [rbp - 0x8]
myNumberExtended[0x1000037bb] <+171>: mov rsi, qword ptr [rip + 0x49ee] ; "value"
myNumberExtended[0x1000037c2] <+178>: call qword ptr [rip + 0x838] ; (void *)0x0000000000000000
myNumberExtended[0x1000037c8] <+184>: sub eax, dword ptr [rbp - 0x34]
myNumberExtended[0x1000037cb] <+187>: mov dword ptr [rbp - 0x3c], eax
myNumberExtended[0x1000037ce] <+190>: mov eax, dword ptr [rbp - 0x38]
myNumberExtended[0x1000037d1] <+193>: mov dword ptr [rbp - 0x44], eax
myNumberExtended[0x1000037d4] <+196>: mov rdi, qword ptr [rbp - 0x8]
myNumberExtended[0x1000037d8] <+200>: mov rsi, qword ptr [rip + 0x49d1] ; "value"
myNumberExtended[0x1000037df] <+207>: call qword ptr [rip + 0x81b] ; (void *)0x0000000000000000
myNumberExtended[0x1000037e5] <+213>: mov ecx, eax
myNumberExtended[0x1000037e7] <+215>: mov eax, dword ptr [rbp - 0x44]
myNumberExtended[0x1000037ea] <+218>: sub eax, ecx
myNumberExtended[0x1000037ec] <+220>: mov dword ptr [rbp - 0x40], eax
myNumberExtended[0x1000037ef] <+223>: mov eax, dword ptr [rbp - 0x3c]
myNumberExtended[0x1000037f2] <+226>: cmp eax, dword ptr [rbp - 0x40]
myNumberExtended[0x1000037f5] <+229>: jge 0x10000380f ; <+255>
myNumberExtended[0x1000037fb] <+235>: lea rdi, [rip + 0x8ce] ; @"The nearest square is the lowest one"
myNumberExtended[0x100003802] <+242>: mov al, 0x0
myNumberExtended[0x100003804] <+244>: call 0x100003cda ; symbol stub for: NSLog
myNumberExtended[0x100003809] <+249>: mov eax, dword ptr [rbp - 0x34]
myNumberExtended[0x10000380c] <+252>: mov dword ptr [rbp - 0x14], eax
myNumberExtended[0x10000380f] <+255>: mov eax, dword ptr [rbp - 0x40]
myNumberExtended[0x100003812] <+258>: cmp eax, dword ptr [rbp - 0x3c]
myNumberExtended[0x100003815] <+261>: jge 0x10000382f ; <+287>
myNumberExtended[0x10000381b] <+267>: lea rdi, [rip + 0x8ce] ; @"The nearest square is the highest one"
myNumberExtended[0x100003822] <+274>: mov al, 0x0
myNumberExtended[0x100003824] <+276>: call 0x100003cda ; symbol stub for: NSLog
myNumberExtended[0x100003829] <+281>: mov eax, dword ptr [rbp - 0x38]
myNumberExtended[0x10000382c] <+284>: mov dword ptr [rbp - 0x14], eax
myNumberExtended[0x10000382f] <+287>: mov eax, dword ptr [rbp - 0x40]
myNumberExtended[0x100003832] <+290>: cmp eax, dword ptr [rbp - 0x3c]
myNumberExtended[0x100003835] <+293>: jne 0x100003855 ; <+325>
myNumberExtended[0x10000383b] <+299>: lea rdi, [rip + 0x8ce] ; @"The given number is exactly in the middle of two perfect squares: %i and %i. Returning the lowest"
myNumberExtended[0x100003842] <+306>: mov esi, dword ptr [rbp - 0x34]
myNumberExtended[0x100003845] <+309>: mov edx, dword ptr [rbp - 0x38]
myNumberExtended[0x100003848] <+312>: mov al, 0x0
myNumberExtended[0x10000384a] <+314>: call 0x100003cda ; symbol stub for: NSLog
myNumberExtended[0x10000384f] <+319>: mov eax, dword ptr [rbp - 0x34]
myNumberExtended[0x100003852] <+322>: mov dword ptr [rbp - 0x14], eax
myNumberExtended[0x100003855] <+325>: jmp 0x10000385a ; <+330>
myNumberExtended[0x10000385a] <+330>: lea rdi, [rip + 0x8cf] ; @"Returning the value"
myNumberExtended[0x100003861] <+337>: mov al, 0x0
myNumberExtended[0x100003863] <+339>: call 0x100003cda ; symbol stub for: NSLog
myNumberExtended[0x100003868] <+344>: mov eax, dword ptr [rbp - 0x14]
myNumberExtended[0x10000386b] <+347>: add rsp, 0x50
myNumberExtended[0x10000386f] <+351>: pop rbp
myNumberExtended[0x100003870] <+352>: ret
myNumberExtended[0x100003871] <+353>: nop word ptr cs:[rax + rax]
myNumberExtended[0x10000387b] <+363>: nop dword ptr [rax + rax]
If we take a look at the Objective-C code, we immediately see that this routine gets the value of the property value and finds the nearest square to it.
This is interesting because this way we can learn where a return value is stored.
We have two possibilities:
- set a breakpoint to the beginning of the routine and see what happens, step by step
- set a breakpoint somewhere before the end of the routine and see the values of the registers.
Once again, here we assume that we have no talent at all and we do not know any assembly - so let's go for the easiest option.
We can set a breakpoint at the address 0x100003848, just before the NSLog instruction is called and examine the registers. From the Objective C code we know that the first object on which the method is invoked has the property value set to 155 (or 0x9B). This value is between 1212=144 (0X90) and 1313=169 (0xA9). The nearest square is obviously 144.
The situation should be as follows:
(lldb) br s -a 0x100003861
Breakpoint 4: where = myNumberExtended`-[myNumber nearestPerfectSquare] + 337, address = 0x0000000100003861
(lldb) br list
Current breakpoints:
3: address = myNumberExtended[0x0000000100003868], locations = 1 Options: disabled
3.1: where = myNumberExtended`-[myNumber nearestPerfectSquare] + 344, address = 0x0000000100003868, unresolved, hit count = 4
4: address = myNumberExtended[0x0000000100003861], locations = 1
4.1: where = myNumberExtended`-[myNumber nearestPerfectSquare] + 337, address = 0x0000000100003861, unresolved, hit count = 0
Now if we run the program, the execution will stop once the RIP (instruction pointer - we'll come back on this later) hits the value 0x0000000100003848. In fact:
(lldb) r
Process 4310 launched: '/Users/gbiondo/EXP312/Debugging/myNumberExtended' (x86_64)
2022-03-30 14:05:50.875159+0100 myNumberExtended[4310:5367748] Checking perfect square
2022-03-30 14:05:50.875445+0100 myNumberExtended[4310:5367748] 144 is a perfect square
2022-03-30 14:05:50.875456+0100 myNumberExtended[4310:5367748] Checking perfect square
2022-03-30 14:05:50.875463+0100 myNumberExtended[4310:5367748] 155 is not a perfect square
2022-03-30 14:05:50.875470+0100 myNumberExtended[4310:5367748] Looking for perfect squares
2022-03-30 14:05:50.875477+0100 myNumberExtended[4310:5367748] Checking perfect square
2022-03-30 14:05:50.875483+0100 myNumberExtended[4310:5367748] The nearest square is the lowest one
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x0000000100003861 myNumberExtended`-[myNumber nearestPerfectSquare] + 337
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x100003861 <+337>: mov al, 0x0
0x100003863 <+339>: call 0x100003cda ; symbol stub for: NSLog
0x100003868 <+344>: mov eax, dword ptr [rbp - 0x14]
0x10000386b <+347>: add rsp, 0x50
Target 0: (myNumberExtended) stopped.
We give the command n (short for thread step-over) twice, reaching the address -[myNumber nearestPerfectSquare] + 344 and obtain:
(lldb) n
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003863 myNumberExtended`-[myNumber nearestPerfectSquare] + 339
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x100003863 <+339>: call 0x100003cda ; symbol stub for: NSLog
0x100003868 <+344>: mov eax, dword ptr [rbp - 0x14]
0x10000386b <+347>: add rsp, 0x50
0x10000386f <+351>: pop rbp
Target 0: (myNumberExtended) stopped.
(lldb) n
2022-03-30 14:10:34.213710+0100 myNumberExtended[4310:5367748] Returning the value
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003868 myNumberExtended`-[myNumber nearestPerfectSquare] + 344
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x100003868 <+344>: mov eax, dword ptr [rbp - 0x14]
0x10000386b <+347>: add rsp, 0x50
0x10000386f <+351>: pop rbp
0x100003870 <+352>: ret
Target 0: (myNumberExtended) stopped.
Now it is time to examine the value of registers:
(lldb) register read
General Purpose Registers:
rax = 0xe62a2c872238004d
rbx = 0x00000001000c8060
rcx = 0x000000010080b040
rdx = 0x0000000000000000
rdi = 0x000000010080b040
rsi = 0x000000010080b040
rbp = 0x00007ff7bfeff860
rsp = 0x00007ff7bfeff810
r8 = 0x00007ff7bfeff5c0
r9 = 0x0000000000000040
r10 = 0x0000000000000000
r11 = 0x0000000000000246
r12 = 0x00000001000903a0 dyld`_NSConcreteStackBlock
r13 = 0x00007ff7bfeff978
r14 = 0x0000000100003990 myNumberExtended`main
r15 = 0x000000010007c010 dyld`dyld4::sConfigBuffer
rip = 0x0000000100003868 myNumberExtended`-[myNumber nearestPerfectSquare] + 344
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
Observe that RIP, the Instruction Point Register, points to the next instruction to be ran. Remembering that we know no assembly at all, we take another step in the debug and see how the values of the registers change:
(lldb) s
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x000000010000386b myNumberExtended`-[myNumber nearestPerfectSquare] + 347
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x10000386b <+347>: add rsp, 0x50
0x10000386f <+351>: pop rbp
0x100003870 <+352>: ret
0x100003871 <+353>: nop word ptr cs:[rax + rax]
Target 0: (myNumberExtended) stopped.
(lldb) register read
General Purpose Registers:
rax = 0x0000000000000090
rbx = 0x00000001000c8060
rcx = 0x000000010080b040
rdx = 0x0000000000000000
rdi = 0x000000010080b040
rsi = 0x000000010080b040
rbp = 0x00007ff7bfeff860
rsp = 0x00007ff7bfeff810
r8 = 0x00007ff7bfeff5c0
r9 = 0x0000000000000040
r10 = 0x0000000000000000
r11 = 0x0000000000000246
r12 = 0x00000001000903a0 dyld`_NSConcreteStackBlock
r13 = 0x00007ff7bfeff978
r14 = 0x0000000100003990 myNumberExtended`main
r15 = 0x000000010007c010 dyld`dyld4::sConfigBuffer
rip = 0x000000010000386b myNumberExtended`-[myNumber nearestPerfectSquare] + 347
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
We observe that the only two registers that have changed are RAX and obviously RIP. Furthermore, RAX contains exactly the value we are looking for (0x90) - we can conjecture that this register will contain the return value for the subroutine. If this is true, we may want to change it in a way that contains a number we decide - let's say 0x1723, or 5923.
To do so, it would suffice to launch the command:
(lldb) register write rax 0x1723
(lldb) register read rax
rax = 0x0000000000001723
and see the printout in the main program.
We hit n a couple of times to return to the main program (the ret instruction):
(lldb) s
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x000000010000386f myNumberExtended`-[myNumber nearestPerfectSquare] + 351
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x10000386f <+351>: pop rbp
0x100003870 <+352>: ret
0x100003871 <+353>: nop word ptr cs:[rax + rax]
0x10000387b <+363>: nop dword ptr [rax + rax]
Target 0: (myNumberExtended) stopped.
(lldb) n
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003870 myNumberExtended`-[myNumber nearestPerfectSquare] + 352
myNumberExtended`-[myNumber nearestPerfectSquare]:
-> 0x100003870 <+352>: ret
0x100003871 <+353>: nop word ptr cs:[rax + rax]
0x10000387b <+363>: nop dword ptr [rax + rax]
myNumberExtended`-[myNumber isPrime]:
0x100003880 <+0>: push rbp
Target 0: (myNumberExtended) stopped.
(lldb) s
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003b28 myNumberExtended`main + 408
myNumberExtended`main:
-> 0x100003b28 <+408>: mov dword ptr [rbp - 0x3c], eax
0x100003b2b <+411>: mov rdi, qword ptr [rbp - 0x18]
0x100003b2f <+415>: mov rsi, qword ptr [rip + 0x467a] ; "value"
0x100003b36 <+422>: call qword ptr [rip + 0x4c4] ; (void *)0x00007ff812cde040: objc_msgSend
Target 0: (myNumberExtended) stopped.
(lldb) register read
General Purpose Registers:
rax = 0x0000000000001723
rbx = 0x00000001000c8060
rcx = 0x000000010080b040
rdx = 0x0000000000000000
rdi = 0x000000010080b040
rsi = 0x000000010080b040
rbp = 0x00007ff7bfeff8c0
rsp = 0x00007ff7bfeff870
r8 = 0x00007ff7bfeff5c0
r9 = 0x0000000000000040
r10 = 0x0000000000000000
r11 = 0x0000000000000246
r12 = 0x00000001000903a0 dyld`_NSConcreteStackBlock
r13 = 0x00007ff7bfeff978
r14 = 0x0000000100003990 myNumberExtended`main
r15 = 0x000000010007c010 dyld`dyld4::sConfigBuffer
rip = 0x0000000100003b28 myNumberExtended`main + 408
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
Hitting a few times n until we reach myNumberExtended`main + 442, and then hitting it again, we obtain:
2022-03-30 14:31:40.787665+0100 myNumberExtended[4310:5367748] The nearest square to 155 is 5923
Process 4310 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003b4f myNumberExtended`main + 447
myNumberExtended`main:
-> 0x100003b4f <+447>: mov rdi, qword ptr [rbp - 0x20]
0x100003b53 <+451>: mov rsi, qword ptr [rip + 0x4666] ; "nearestPerfectSquare"
0x100003b5a <+458>: call qword ptr [rip + 0x4a0] ; (void *)0x00007ff812cde040: objc_msgSend
0x100003b60 <+464>: mov dword ptr [rbp - 0x40], eax
Target 0: (myNumberExtended) stopped.
So, our conjecture was right.
Conclusions
This is a very long article. The idea behind it is to show how lldb can be used, and to ignite some itch for assembly programming.
I will come back on these kind of exercises - I find them quite funny :)
'til next time...




