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
| Register | Contains | Purpose |
|---|---|---|
| RCX | Input string pointer | String to parse |
| RDX | Format string pointer | Expected input format |
| R8 | RBP + 0x64 | First output variable address |
| R9 | RBP + 0x84 | Second 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
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
| Index | Address (base + i×4) | Value (hex) | Decimal |
|---|---|---|---|
| 0 | …1d0 | 0x0a | 10 |
| 1 | …1d4 | 0x02 | 2 |
| 2 | …1d8 | 0x0e | 14 |
| 3 | …1dc | 0x07 | 7 |
| 4 | …1e0 | 0x08 | 8 |
| 5 | …1e4 | 0x0c | 12 |
| 6 | …1e8 | 0x0f | 15 |
| 7 | …1ec | 0x0b | 11 |
| 8 | …1f0 | 0x00 | 0 |
| 9 | …1f4 | 0x04 | 4 |
| 10 | …1f8 | 0x01 | 1 |
| 11 | …1fc | 0x0d | 13 |
| 12 | …200 | 0x03 | 3 |
| 13 | …204 | 0x09 | 9 |
| 14 | …208 | 0x06 | 6 |
| 15 | …20c | 0x05 | 5 |
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
| Node | Address | Value (4 bytes) | id (4 bytes) | next (8 bytes) |
|---|---|---|---|---|
| node1 | …f050 | 0x212 | 1 | …f040 (node2) |
| node2 | …f040 | 0x1c2 | 2 | …f030 (node3) |
| node3 | …f030 | 0x215 | 3 | …f020 (node4) |
| node4 | …f020 | 0x393 | 4 | …f010 (node5) |
| node5 | …f010 | 0x3a7 | 5 | …f000 (node6) |
| node6 | …f000 | 0x200 | 6 | NULL (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) |
|---|---|
| 0x3a7 | 5 |
| 0x393 | 4 |
| 0x215 | 3 |
| 0x212 | 1 |
| 0x200 | 6 |
| 0x1c2 | 2 |
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.
