# Reverse engineer a swift binary

When you compile your high-level Swift code, it gets turned into raw machine instructions. As developers, we rarely look at this level, but it's a fascinating world for anyone interested in security, performance, or just pure curiosity.

In this post, we are going to write a tiny "login" program in Swift, disassemble it, and find the *exact* instruction that decides if our password is right or wrong.

Then, we are going to change that instruction to let us in with *any* password.

### Part 1: The Swift Program

First, our highly secure, un-hackable login program, `login.swift`:

```bash
// login.swift

// Our login function with a very secret password
func login(pass: String) -> Bool {
    pass == "right-password"
}

// Get the password from the command line
// Note: CommandLine.arguments[0] is the program name
// CommandLine.arguments[1] is our first argument
let result = login(pass: CommandLine.arguments[1])

// Print success or failure
print(result ? "success" : "failure")
```

Let's compile it and run it to make sure it works as expected.

```bash
$ swiftc login.swift -o login

# Test with the wrong password
$ ./login wrong
failure

# Test with the right password
$ ./login right-password
success
```

It works perfectly. Now, let's break it.

### Part 2: Disassembly

We will use `objdump` to disassemble the executable, turning its machine code back into (semi) readable ARM64 assembly.

```bash
$ objdump -D login
```

This produces a *lot* of output, which can be intimidating. But we are looking for two specific functions:

1. `_main`: This is the main entry point of our script.
    
2. `_$s5loginAA4passSbSS_tF`: This is our `login(pass: String) -> Bool` function after Swift "mangles" its name.
    

### Part 3: Finding the Password in the Code

Let's look at our `login` function's assembly first (`_$s5loginAA4passSbSS_tF`). Tucked inside, we see it loading some data:

```bash
00000001000007a4 <_$s5loginAA4passSbSS_tF>:
...
1000007c8: 90000000    adrp    x0, 0x100000000
1000007cc: 9124d000    add     x0, x0, #0x934
...
1000007f8: 94000036    bl      0x1000008d0 ; This calls the string comparison function
...
100000810: 12000000    and     w0, w0, #0x1 ; Result is 1 (true) or 0 (false)
...
10000081c: d65f03c0    ret                  ; Return
```

The `add x0, x0, #0x934` instruction is loading the address of our hardcoded password. How do we know? If we look at the `__TEXT,__cstring` (string) section of the dump, we find this at that *exact* address:

```bash
0000000100000934 <__cstring+0x10>:
100000934: 68676972    ; "righ"
100000938: 61702d74    ; "t-pa"
10000093c: 6f777373    ; "sswo"
100000940: 0a006472    ; "rd"
```

The program literally has `"right-password"` stored inside it. This function compares the input password to this string and returns `1` (true) or `0` (false) in the `w0` register.

### Part 4: The Branch

Now, let's look at `_main`. This is where the *decision* to print "success" or "failure" is made, based on the return value from our `login` function.

```bash
0000000100000628 <_main>:
...
100000668: 9400004f    bl    0x1000007a4 <_$s5loginAA4passSbSS_tF> ; CALLS OUR LOGIN FUNCTION
10000066c: aa0003e8    mov   x8, x0          ; Moves the 1 or 0 result into register x8 (w8)
...
1000006bc: 39400108    ldrb  w8, [x8]        ; Loads the boolean result (1 or 0) into w8
1000006c0: 36000188    tbz   w8, #0x0, 0x1000006f0 <_main+0xc8>
1000006c4: 14000001    b     0x1000006c8 <_main+0xa0>
```

This is it. This is the whole security model.

* `1000006c0: 36000188 tbz w8, #0x0, 0x1000006f0`
    

`tbz` stands for "Test Bit and Branch if Zero". This instruction does the following:

1. Looks at register `w8` (which holds our `1` for success or `0` for failure).
    
2. Tests bit `#0` of that register.
    
3. If that bit is **Zero** (meaning the password was wrong), it branches (jumps) to address `0x1000006f0`.
    

What is at `0x1000006f0`? It is the code that loads and prints "failure".

If the bit is **Not Zero** (i.e., it's `1`, meaning success), it *doesn't* branch. It just falls through to the next instruction at `0x1000006c4`, which takes it to the code that prints "success".

### Part 5: The Patch: Bypassing the Check

So, how do we bypass this? We just need to stop that conditional branch from *ever* happening. We can "neuter" the instruction by overwriting it with a `nop` (No-Operation) instruction.

The `nop` instruction in ARM64 is `d503201f`. It literally does nothing.

By replacing the `tbz` instruction with a `nop`, the code will *always* fall through to the "success" block, no matter what the password is.

**Here is our plan:**

1. **Find the bytes:** The `tbz` instruction is `36000188`. On a little-endian system (like Apple Silicon), this is stored in the file as `88 01 00 36`.
    
2. **Replace the bytes:** The `nop` instruction is `d503201f`. This is stored as `1f 20 03 d5`.
    
3. **Use a hex editor** (like `hexedit`) to find `88 01 00 36` and replace it with `1f 20 03 d5`.
    

```bash
# 1. Open the file in the hex editor
$ hexedit login

# 2. Press Ctrl+S to search.
#    Search for hex bytes: 88 01 00 36

# 3. Type over it with the new bytes: 1f 20 03 d5
#    (The text will turn red)

# 4. Press Ctrl+X to exit, and 'y' to save.
```

### Part 6: The Final Step (macOS Security)

If we try to run `./login` now, it will hang or crash. We have modified the binary, which breaks its "code signature". macOS sees the file has been tampered with and refuses to run it.

We need to apply a new, ad-hoc signature to tell macOS we trust this new version.

```bash
$ codesign -f -s - login
```

### The Result

Now, we run our patched app.

```bash
$ ./login anything-i-want
success

$ ./login blahblah
success

$ ./login 1234
success
```

We have successfully bypassed the password check by changing just 4 bytes in the compiled executable.

This is, of course, a very simple example, but it demonstrates the fundamentals of binary patching and reverse engineering. It also shows why hardcoding secrets in your client-side applications is never a good idea!
