Timeline
Timeline
2025-06-24
init
This article introduces the detailed reverse engineering analysis process of the CSAPP BombLab experiment under the AArch64 architecture, covering environment setup, main function stack frame logic, and phase_0 to phase_2's assembly code analysis and problem-solving ideas.
Environment
win11 WSL2:

Environment setup
1 | git clone https://github.com/SJTU-IPADS/OS-Course-Lab.git |
terminal1
You can write the answers in ans.txt, so you don’t have to type the ones you’ve already solved again.
1 | qemu-aarch64-static -g 1234 ./bomb < ans.txt |
terminal2
1 | gdb-multiarch -ex "set architecture aarch64" -ex "target remote localhost:1234" -ex "file bomb" |
main
The main function has C code
1 |
|
It can be seen that read_line’s return value is passed as a parameter to phase_x

It can be seen that the main function calls read each time_line to read input, then calls phase_x function, then calls phase_defused to print information

Function start (stack frame setup):
1 | 400b10: a9bf7bfd stp x29, x30, [sp, #-16]! // Save frame pointer and return address to the stack, sp -= 16 |
Get the target buffer for fgets (x0), ready to call_IO_fgets:
The function is defined as follows:
1 | char *fgets(char *s, int size, FILE *stream); |
It can be seen that the first parameter is char* (x0), the second parameter is int (w1), and the third parameter is a struct pointer (x2)
1 | 400b18: f00004e0 adrp x0, 49f000 |
At this point, the function has read a line of string from the input to
0x4a2138address.
Processing fgets result: Traversing the string to find the newline character:
1 | 400b34: d2800000 mov x0, #0 // x0 = 0, as the string offset index |
If ‘\n’ is not found in the first 80 bytes, callexplode:
1 | 400b5c: 97ffffe6 bl 400af4 <explode> // No '\n', explode |
If a newline character is found\n, replace it with\0(end of string):
1 | 400b60: d0000501 adrp x1, 4a2000 |
Restore context before returning:
1 | 400b6c: d0000500 adrp x0, 4a2000 |

It can be seen that the phase_defused function reads a global variable value, then decrements it by one
Then loads a global address 0x464000+0x7c0 = 0x4647c0 as a parameter to call printf to print

And w1 stores the value of the initially decremented global variable, the address is 0x4a0000+ 80 = 0x4a0000 + 0x50 = 0x4a0050.

phase_0

1 | stp x29, x30, [sp, #-16]! |
- Function: Push x29 (frame pointer) and x30 (link register, return address) onto the stack. This step saves the caller’s frame pointer and return address for easy restoration later.
1 | mov x29, sp |
- Function: Assign the current stack pointer sp to the frame pointer x29, i.e., establish the new stack frame for the current function.
From now on, x29 points to the stack bottom of this function call, making it convenient to access local variables or pass parameters later.
Subsequently, the function first calls the read_int function to read an int value, stores the return value in w0, and then compares it with the value at 0x4a0000 + 0x54(84) = 0x4a0054 to see if they are equal. You can use the gdb examine command to view the value at this memory location:

It can be seen that it is 2022
Then the cmp instruction compares the input value (stored in x0) with 2022 (stored in x1). If they are not equal, it jumps to the line calling the explode function, so they must be equal. That is, the input value must be 2022
phase_1
phase_1 and phase_0 is close enough

It can be seen that the function loads the value at address 0x4a0000+0x58(88) = 0x4a0058 into x1, then calls strcmp. Its parameters should be x0 and x1, where x0 is our input value. That is to say, it compares our input string with the string at address 0x4a0058, and checks if the return value w0 is 0. If it is not 0, it jumps to the line that calls explode.

It should be noted that the address 0x4a0058 does not store a string, but the address of a string. Therefore, you must first read out this address, and then read the string at that string address.
The answer is: “Fault Tolerance: Reliable Systems from Unreliable Components.”
phase_2

The first line allocates 64 bytes of stack space, which can hold 64/8=8 64-bit values in total. The first line places x29 at sp, and x30 at sp+8.
Then it saves sp to the x29 register.
Then it places the values of the x19 and x20 registers at positions sp+16 and sp+24, namely
- sp —> x29
- sp+8 —> x30
- sp+16 —> x19
- sp+24 —> x20
Then x1 = sp + 0x20 = sp + 32, serving as the read_8_numbers’ second parameter, then calls read_8_numbers function

It can be seen that the function first allocates 0x20, or 32 bytes of stack space, sp = sp - 32, then stores x29 at sp+16, and x30 at sp+24.
Then it sets the stack frame pointer x29 to sp + 0x10, which is sp + 16.
Then it puts x1 into x2, x1 is the read_8_numbers’ second parameter
Then, x1 = x1 + 0x1c = x1 + 28
Store x1 into sp+8
x1 = x2 + 0x18 =x1 + 24
Store x1 into sp
Subsequently
x7 = x2+ 0x14
x6 = x2 + 0x10
x5 = x2+0xc
x4 = x2+ 0x8
x3 = x2+0x4
x1 is the value at address 0x464000+0x858, which is a pointer. It is obvious here that this is the starting address of an array. It can be seen that it is a formatted string, serving as the __second parameter of isoc99_scanf
1 | int sscanf(const char *str, const char *format, ...); |
The first parameter is x0, which has remained unchanged since returning from read_line.

After the function call ends, it compares the return value with 0x7. If it is less than or equal to, it explodes. Therefore, you must input 8 numbers.
After the function returns, the original stack structure is restored.
- sp —> x29
- sp+8 —> x30
- sp+16 —> x19
- sp+24 —> x20
- sp+32 —> the starting address of the array, a total of 8 numbers, which is also the address of the first number array[0]
- sp+36 —> array[1]
- sp+40 —> array[2]
- sp+44 —> array[3]
- sp+48 —> array[4]
- sp+52 —> array[5]
- sp+56 —> array[6]
- sp+60 —> array[7]
Exactly fills the 64 bytes initially allocated by phase_2

Then comes the judgment of the 8 input numbers. It is easy to see that the first and second numbers must both be 1.
Then comes a loop

x19 = sp+0x20 = sp+32, which is array[0]
x20 = sp+0x38 = sp+56, which is the termination condition, i.e., (56-32)/4=6, meaning it stops at the sixth element
First, b 0x4007d0 skips the increment of x19 and the comparison with x20 once
If x19 == x20, jump to phase_2+108 to end
Otherwise, first load x19 as an address into w0 (array[i]), and load the next one into w1 (array[i+1])
Let w0 = array[i] + array[i+1] + 4
w1 = array[i+2]
Compare if w0 equals w1; if equal, jump to phase_2+60, incrementing i
If not equal, it explodes
The first and second numbers must be 1
Then the third number is 1+1+4=6
The fourth number is 1+6+4=11
The fifth number is 6+11+4=21
The sixth number is 11+21+4=36
The seventh number is 21+36+4=61
The eighth number is 61+36+4=101
Therefore, the answer is
1 | 1 1 6 11 21 36 61 101 |