JUN0.DEV
JUN0.DEV

BombLab - Binary Bomb Writeup

Published on
  • avatarJunyoung Yang
GitHubNeibce/SysSoft-LABS-2024System Software(059) LAB 과제 풀이
  • Defusing A Binary Bomb
  • Solving the given binary through disassembly and GDB debugging
  • Writeup

1. Phase 1

image
Looking at phase_1, I could guess that a string comparison was being done through strings_not_equal. From test and jne, I could also see that the bomb would explode if the return value was not 0.

image
Before calling strings_not_equal, I checked the value loaded into rsi with lea. The value was at 0x2bd0, and printing it showed the answer: "You can Russia from land here in Alaska."

2. Phase 2

image
First, rsp was moved into rsi, then read_six_numbers was called. After that, the code checked whether the value at rsp was less than 0 and jumped to explode_bomb if it was. From this, I guessed that read_six_numbers received the address of an array as its second argument and filled it with six input numbers. After disassembling read_six_numbers, I saw sscanf, and before the sscanf call, lea instructions set arguments using offsets from rsi in multiples of 4. That confirmed the guess. With a quick JavaScript check, I found that the first number only had to be greater than or equal to 0.

image
For the remaining numbers, I interpreted the assembly and found a loop where rbx went from 1 to 5. The passing condition was arr[rbx - 1] + ebx == arr[rbx]. In other words, the values had to increase by 1, 2, 3, 4, 5. The input 0 1 3 6 10 15 passed.

3. Phase 3

image
image
I first wanted to know the input format used by sscanf inside phase_3, so I printed the value at 0x2c26, which was loaded into rsi. It showed that the input format was "%d %c %d". From the lea instructions before sscanf, I could see that the character was stored at rsp + 0xf, the first integer at rsp + 0x10, and the last integer at rsp + 0x14.

image
The first input value n1 was used to jump through a jump table, like a switch-case statement.
Checking the jump table gave the following values. 

image

 

image

 

image
n1posvaluegoto (value+0x2c40)
00x2c400xffffe76c0x13ac
10x2c440xffffe78e0x13ce
20x2c480xffffe7b00x13f0

image
Starting from 0x13ac, I found that the last input value had to equal 0x254(596) to avoid the explosion through the je 0x14a0 branch.

image
At 0x14a0, the input character had to match al. Since 0x78(120) had been moved into eax at 0x13ac, the character had to be x. I ignored the other jump table cases and entered 0 x 596, which passed.

4. Phase 4

I checked the value of rsi right before sscanf and found that the input format was two integers, %d %d. image
The first integer n1 was stored at rsp, and the second integer n2 was stored at rsp + 4.

image showed that the first integer had to be less than or equal to 0xE(14).

image
After analyzing the following code, I found that func4(n1, 0, 14) had to return 4, and n2 also had to be 4.

When I analyzed func4, it looked like a recursive binary-search-style function.
image
image
It was hard to tell immediately which n1 would make the function return 4, so I reimplemented the function in C and tested values from 1 to 14. When n1 was 2, the function returned 4. Therefore, 2 4 passed.

5. Phase 5

image
After analyzing phase_5, I found that the input string length had to be 6. The function took each input character, kept only the lower 4 bits, used that value as an index into the string at 0x2c60, and stored the selected character in an array. Then it compared the result with the string devils at 0x2c2f using strings_not_equal.

The string at 0x2c60 was "maduiersnfotvbylSo you think...", but because the index was input_str_ptr[rax] & 0xf, only the first 16 characters mattered. To make devils, I needed characters at indices 0x2 0x5 0xC 0x4 0xF 0x7. Since only the lower 4 bits had to match, I checked the ASCII table and entered BELDOG, which passed.

6. Phase 6

image
From this part, I could see that six integers were being read into an array at r13(rsp).

image
While reading the code in order, I found that the bomb exploded if any value in the input array was greater than 6. I also found a nested loop that compared values while incrementing rbp and r13. In other words, the phase exploded if there were duplicate input values. This meant that the inputs had to be unique.

image
Continuing the analysis, I found another loop. It increased r12, which stored the input array address, and replaced each element with 7 - original value.

image
After that, I found another nested loop. The hint from the lecture slide said Phase 6 was about linked lists and structs. Also, the first lea at 0x16be loaded 0x204230 into rdx, and I had annotated that location as node1 in GDB. So I guessed it was related to a struct. When I inspected 0x204230, I saw image, so I continued with the assumption that it was a struct.

Inside the second loop, the code accessed 0x204230 + 8, then accessed the address stored there. That looked like a linked list, so I followed 0x204230 + 8, *(0x204230 + 8), *(*(0x204230 + 8) + 8), and so on. This showed that it was a linked list with six nodes.

node+0+8node+0+8
10x148(328)0x20424040x2ba(698)0x204270
20x16a(362)0x20425050x327(807)0x204110
30x358(856)0x20426060x2d4(724)0x0

image
This part took each value from the array that had been transformed with 7 - input, found the corresponding node, and stored that node address in a new array at rsp + 32.

image
After the loop, the code updated the second field of each node, which was the address of the next node, according to the order stored in the array at rsp + 32.

image
There was one more loop after that. It checked the nodes in order, and the bomb did not explode only when the value at +0 of each node was greater than the value of the next node. Using the values I found earlier, the nodes had to be ordered as node3, 5, 6, 4, 2, 1. Since the code had already applied 7 - input, the answer was 4 2 1 3 5 6.

7. Secret Phase

First, I needed to find how to execute secret_phase. I disassembled the functions called from main and found that phase_defused contained logic related to secret_phase.

image
From this part, I saw that the value at 0x5555557586 had to be 6. To understand what that value meant, I set a breakpoint at phase_defused + 30. After passing Phase 1, the value became 1; after passing Phase 2, it became 2, and so on. This showed that the code ran only after all six phases were defused.

image
The code then called sscanf. To see where the input in rdi came from, I ran the command shown in the image, and confirmed that it was the same input used in Phase 4.

image

The command showed that the code checked one more string after the Phase 4 input, and that value was stored at rsp + 0x10, passed through r8.


image
Here, I saw that the string at rsp + 0x10 was compared with image. This meant that the condition for entering secret_phase was adding DrEvil after the Phase 4 input.

image

Then I disassembled secret_phase. It read one line and used strtol to parse a decimal number. If the parsed value minus 1 was greater than 0x3E8(1000), the bomb exploded.
image

After that, the code called fun7(0x555555758150, input_num), and exploded if the return value was not 7.

The value of n1 was image(36), so I disassembled fun7.

image
fun7 worked as follows. If arg1 was greater than the input value, it returned 2 * fun7(*(rdi + 8), input_num). If it was smaller, it returned 2 * fun7(*(rdi + 16), input_num) + 1. If it was equal, it returned 0. To make the final return value 7, the 2 * fun7(...) + 1 path had to be taken three times: 2 * (2 * (2 * 0 + 1) + 1) + 1 = 7. Therefore, the input value had to equal *(*(*(rdi + 16) + 16) + 16).

image

That value was 1001, so the answer was 1001.