Skip to main content

Command Palette

Search for a command to run...

Debugging with lldb - part 1

Binary Analysis 101

Published
20 min read
G

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:

  1. set a breakpoint to the beginning of the routine and see what happens, step by step
  2. 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...