skip to content
Pocket Change

From Assembly to Pseudocode: How I Defused Bomb Phases 5 & 6

/ 6 min read

Context

Architecture 1001: x86-64 Assembly is an OpenSecurityTraining2 course that teaches the basic concepts and describes the hardware that assembly code deals with and common assembly instructions. It’s a free course that I consider very well done :) You can find it here.

Introduction

At the end of the course there is a binary bomb lab game where you try finding the right phrase to pass each phase with a total of 6 phases. Fun challenge! I definitely struggled with phase 6.

Phase 5

00007ff7`72a124a7 4c8d8d84000000   lea     r9, [rbp+84h]
00007ff7`72a124ae 4c8d4564         lea     r8, [rbp+64h]
00007ff7`72a124b2 488d15679d0000   lea     rdx, [bomb!`string' (7ff772a1c220)]
00007ff7`72a124b9 488b8da0010000   mov     rcx, qword ptr [rbp+1A0h]
00007ff7`72a124c0 e801eeffff       call    bomb!@ILT+705(sscanf) (7ff772a112c6)
00007ff7`72a124c5 8985a4000000     mov     dword ptr [rbp+0A4h], eax
00007ff7`72a124cb 83bda400000002   cmp     dword ptr [rbp+0A4h], 2
00007ff7`72a124d2 7d05             jge     bomb!phase_5+0x79 (7ff772a124d9)
00007ff7`72a124d4 e8ddeeffff       call    bomb!@ILT+945(explode_bomb) (7ff772a113b6)

For sscanf

RegisterContainsPurpose
RCXInput string pointerString to parse
RDXFormat string pointerExpected input format
R8RBP + 0x64First output variable address
R9RBP + 0x84Second output variable address

So we get the addresses below for R8 and R9 to see the output and track it. So it takes in 2 integers we entered and verifies the count.

0:000> r r8,r9
r8=000000b3f2eff884 r9=000000b3f2eff8a4

phase_5 loop in WinDbg
dd rcx

00007ff7`72a1f1d0  0000000a 00000002 0000000e 00000007
00007ff7`72a1f1e0  00000008 0000000c 0000000f 0000000b
00007ff7`72a1f1f0  00000000 00000004 00000001 0000000d
00007ff7`72a1f200  00000003 00000009 00000006 00000005

rcx being the array, loaded with lea rcx, [bomb!n1+0x20] and read as [rcx+rax*4] because each entry is a 4-byte int.

Visualised as the following

IndexAddress (base + i×4)Value (hex)Decimal
0…1d00x0a10
1…1d40x022
2…1d80x0e14
3…1dc0x077
4…1e00x088
5…1e40x0c12
6…1e80x0f15
7…1ec0x0b11
8…1f00x000
9…1f40x044
10…1f80x011
11…1fc0x0d13
12…2000x033
13…2040x099
14…2080x066
15…20c0x055
handwritten pseudocode notes

Translating this piece of code into pseudocode

i = 0;
input1 = input1 & 15;
item2 = 0;

while (input1 != 15) {
    i++;
    input1 = array[input1];
    item2 = input1 + item2;
}

if (i != 15 || item2 != input2)
    explode_bomb();

My first handwritten version (photo above) used while (i != 15). That’s wrong: the loop only stops when input1 reaches 15, and i is only checked after the loop ends.

I also fixed the final if. The bomb explodes if either check fails, so it’s if (i != 15 || item2 != input2).

The & 15 keeps only the last hex digit of the input. The array has exactly 16 entries (index 0–15), so this makes sure the index can never go outside it. A side effect is that any number ending in hex 5 works too, so 21 115 passes as well.

So reversing that will be looking at array above at starting at decimal 15 which is index 6. And doing that until the counter is equal 15 so:

index   value
6       15
14      6
2       14
1       2
10      1
0       10
8       0
4       8
9       4
13      9
11      13
7       11
3       7
12      3
5       12

Adding all these values results in 115.

So input1 is 5 and we check the result of item2 which is 115 so the input is 5 115

Phase 6

1. Reading the input

00007ff7`72a12602 488d5548             lea     rdx, [rbp+48h]
00007ff7`72a12606 488b8de0010000       mov     rcx, qword ptr [rbp+1E0h]
00007ff7`72a1260d e8c0eaffff           call    bomb!@ILT+205(read_six_numbers) (7ff772a110d2)

First part shows that phase6 takes in 6 numbers and checks you entered exactly six numbers.

2. No duplicates

00007ff7`72a1267d 486385c4000000       movsxd  rax, dword ptr [rbp+0C4h]
00007ff7`72a12684 48638de4000000       movsxd  rcx, dword ptr [rbp+0E4h]
00007ff7`72a1268b 8b4c8d48             mov     ecx, dword ptr [rbp+rcx*4+48h]
00007ff7`72a1268f 394c8548             cmp     dword ptr [rbp+rax*4+48h], ecx
00007ff7`72a12693 7505                 jne     bomb!phase_6+0xea (7ff772a1269a)
00007ff7`72a12695 e81cedffff           call    bomb!@ILT+945(explode_bomb) (7ff772a113b6)

Here it compares each input in the list with other so the list must be unique

3. Finding the struct

0:000> dd 00007FF772A1F000 L18
00007ff7`72a1f000  00000200 00000006 00000000 00000000
00007ff7`72a1f010  000003a7 00000005 72a1f000 00007ff7
00007ff7`72a1f020  00000393 00000004 72a1f010 00007ff7
00007ff7`72a1f030  00000215 00000003 72a1f020 00007ff7
00007ff7`72a1f040  000001c2 00000002 72a1f030 00007ff7
00007ff7`72a1f050  00000212 00000001 72a1f040 00007ff7
NodeAddressValue (4 bytes)id (4 bytes)next (8 bytes)
node1…f0500x2121…f040 (node2)
node2…f0400x1c22…f030 (node3)
node3…f0300x2153…f020 (node4)
node4…f0200x3934…f010 (node5)
node5…f0100x3a75…f000 (node6)
node6…f0000x2006NULL (end of list)

4. Putting the nodes in my order

00007ff7`72a126c1 488b4508             mov     rax, qword ptr [rbp+8]
00007ff7`72a126c5 48894528             mov     qword ptr [rbp+28h], rax
00007ff7`72a126c9 c785e400000001000000 mov     dword ptr [rbp+0E4h], 1
00007ff7`72a126d3 eb0e                 jmp     bomb!phase_6+0x133 (7ff772a126e3)
00007ff7`72a126d5 8b85e4000000         mov     eax, dword ptr [rbp+0E4h]
00007ff7`72a126db ffc0                 inc     eax
00007ff7`72a126dd 8985e4000000         mov     dword ptr [rbp+0E4h], eax
00007ff7`72a126e3 486385c4000000       movsxd  rax, dword ptr [rbp+0C4h]
00007ff7`72a126ea 8b448548             mov     eax, dword ptr [rbp+rax*4+48h]
00007ff7`72a126ee 3985e4000000         cmp     dword ptr [rbp+0E4h], eax
00007ff7`72a126f4 7d0e                 jge     bomb!phase_6+0x154 (7ff772a12704)
00007ff7`72a126f6 488b4528             mov     rax, qword ptr [rbp+28h]
00007ff7`72a126fa 488b4008             mov     rax, qword ptr [rax+8]
00007ff7`72a126fe 48894528             mov     qword ptr [rbp+28h], rax
00007ff7`72a12702 ebd1                 jmp     bomb!phase_6+0x125 (7ff772a126d5)
00007ff7`72a12704 486385c4000000       movsxd  rax, dword ptr [rbp+0C4h]
00007ff7`72a1270b 488b4d28             mov     rcx, qword ptr [rbp+28h]
00007ff7`72a1270f 48894cc578           mov     qword ptr [rbp+rax*8+78h], rcx
00007ff7`72a12714 eb94                 jmp     bomb!phase_6+0xfa (7ff772a126aa)

This is looping through linked list and finds the node at that position and saves a pointer to it.

The step counter j is at [rbp+0E4h].

output of linked list:

0:000> dd 7ff772a1f000 L18
00007ff7`72a1f000  00000200 00000006 00000000 00000000
00007ff7`72a1f010  000003a7 00000005 72a1f000 00007ff7
00007ff7`72a1f020  00000393 00000004 72a1f010 00007ff7
00007ff7`72a1f030  00000215 00000003 72a1f020 00007ff7
00007ff7`72a1f040  000001c2 00000002 72a1f030 00007ff7
00007ff7`72a1f050  00000212 00000001 72a1f040 00007ff7

It’s unchanged: the next pointers still go 1 -> 2 -> … -> 6, because this loop only saves pointers to the nodes and doesn’t modify them.

Visualising a node in memory as:

node at ...f040:
  [addr + 0]  value   (4 bytes)   0x1c2
  [addr + 4]  id      (4 bytes)   2
  [addr + 8]  next    (8 bytes)   ...f030

5. Relinking the list

00007ff7`72a12716 b808000000           mov     eax, 8
00007ff7`72a1271b 486bc000             imul    rax, rax, 0
00007ff7`72a1271f 488b440578           mov     rax, qword ptr [rbp+rax+78h]
00007ff7`72a12724 48894508             mov     qword ptr [rbp+8], rax
00007ff7`72a12728 488b4508             mov     rax, qword ptr [rbp+8]
00007ff7`72a1272c 48894528             mov     qword ptr [rbp+28h], rax
...
00007ff7`72a12753 486385c4000000       movsxd  rax, dword ptr [rbp+0C4h]
00007ff7`72a1275a 488b4d28             mov     rcx, qword ptr [rbp+28h]
00007ff7`72a1275e 488b44c578           mov     rax, qword ptr [rbp+rax*8+78h]
00007ff7`72a12763 48894108             mov     qword ptr [rcx+8], rax
00007ff7`72a12767 488b4528             mov     rax, qword ptr [rbp+28h]
00007ff7`72a1276b 488b4008             mov     rax, qword ptr [rax+8]
00007ff7`72a1276f 48894528             mov     qword ptr [rbp+28h], rax
00007ff7`72a12773 ebc7                 jmp     bomb!phase_6+0x18c (7ff772a1273c)

This rewires the list from 1→6 into the order you typed, for example 5 4 3 1 6 2.

Output of linked list from my first attempt, when I tried just reversing the order (6 5 4 3 2 1). Following the next pointers gives 6 -> 5 -> 4 -> 3 -> 2 -> 1, and the final check below explodes straight away because 0x200 is not bigger than or equal to 0x3a7:

0:000> dd 7ff772a1f000 L18
00007ff7`72a1f000  00000200 00000006 72a1f010 00007ff7
00007ff7`72a1f010  000003a7 00000005 72a1f020 00007ff7
00007ff7`72a1f020  00000393 00000004 72a1f030 00007ff7
00007ff7`72a1f030  00000215 00000003 72a1f040 00007ff7
00007ff7`72a1f040  000001c2 00000002 72a1f050 00007ff7
00007ff7`72a1f050  00000212 00000001 00000000 00000000

6. The final check

00007ff7`72a127ac 488b4528             mov     rax, qword ptr [rbp+28h]
00007ff7`72a127b0 488b4008             mov     rax, qword ptr [rax+8]
00007ff7`72a127b4 488b4d28             mov     rcx, qword ptr [rbp+28h]
00007ff7`72a127b8 8b00                 mov     eax, dword ptr [rax]
00007ff7`72a127ba 3901                 cmp     dword ptr [rcx], eax
00007ff7`72a127bc 7d05                 jge     bomb!phase_6+0x213 (7ff772a127c3)
00007ff7`72a127be e8f3ebffff           call    bomb!@ILT+945(explode_bomb) (7ff772a113b6)

It compares each value to the next one, and each has to be bigger than or equal to the next.

Solving it

Value (sorted biggest → smallest)id (what you type)
0x3a75
0x3934
0x2153
0x2121
0x2006
0x1c22

The answer would be: 5 4 3 1 6 2

Definitely was a great exercise, and I am looking forward to applying these concepts to deepen my reverse engineering and malware analysis skills.