Timeline

2025-06-24

init


Environment

Win11 WSL2:

Hardware Environment

Environment setup:

1
2
3
git clone https://github.com/SJTU-IPADS/OS-Course-Lab.git
cd OS-Course-Lab/Lab0
sudo apt-get install qemu-user gdb-multiarch

Terminal 1 — write answers to ans.txt so you don’t have to retype previously solved ones:

1
qemu-aarch64-static -g 1234 ./bomb < ans.txt

Terminal 2:

1
gdb-multiarch -ex "set architecture aarch64" -ex "target remote localhost:1234" -ex "file bomb"

main

The main function has C source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include "phases.h"
#include "utils.h"

int main() {
char* input;
printf("Type in your defuse password!\n");

input = read_line();
phase_0(input);
phase_defused();

input = read_line();
phase_1(input);
phase_defused();

input = read_line();
phase_2(input);
phase_defused();

input = read_line();
phase_3(input);
phase_defused();

input = read_line();
phase_4(input);
phase_defused();

input = read_line();
phase_5(input);
phase_defused();

printf("Congrats! You have defused all phases!\n");
return 0;
}

We can see that the return value of read_line is passed as an argument to phase_x.

image-20250629225404452

As shown, main calls read_line to read input each time, then calls phase_x, then calls phase_defused to print information.

image-20250629225341441

Function prologue (stack frame setup):

1
2
400b10: a9bf7bfd    stp x29, x30, [sp, #-16]!    // Save frame pointer and return address to stack, sp -= 16
400b14: 910003fd mov x29, sp // Set new frame pointer x29 = sp

Get the destination buffer (x0) for fgets, preparing to call _IO_fgets:

The function is defined as:

1
char *fgets(char *s, int size, FILE *stream);

The first parameter is char* (x0), the second is int (w1), and the third is a struct pointer (x2).

1
2
3
4
5
6
7
8
9
10
400b18: f00004e0    adrp x0, 49f000
400b1c: f946d000 ldr x0, [x0, #3488]
400b20: f9400002 ldr x2, [x0] // FILE *stream

400b24: 52800a21 mov w1, #0x51 // w1 = 81, read at most 81 bytes

400b28: d0000500 adrp x0, 4a2000
400b2c: 9104e000 add x0, x0, #0x138 // x0 = 0x4a2000 + 0x138, x0 is the destination buffer

400b30: 94004a78 bl 413510 <_IO_fgets> // Call _IO_fgets(buffer, 81, stream)

At this point, the function has read one line of input into the address 0x4a2138.

Processing fgets result: iterating through the string looking for newline:

1
2
3
4
5
6
7
8
9
10
11
12
13
400b34: d2800000    mov x0, #0                   // x0 = 0, string offset index

// Reconstruct x2 = buffer base
400b38: d0000502 adrp x2, 4a2000
400b3c: 9104e042 add x2, x2, #0x138 // x2 = buffer

400b40: 38626801 ldrb w1, [x0, x2] // w1 = buffer[x0]
400b44: 34000141 cbz w1, 400b6c // If w1 == 0, end of string, jump to return
400b48: 7100283f cmp w1, #0xa // Check if it's newline '\n'
400b4c: 540000a0 b.eq 400b60 // It's newline, jump to remove it
400b50: 91000400 add x0, x0, #1 // x0++, continue checking next char
400b54: f101401f cmp x0, #0x50 // Check at most 0x50 bytes (80 bytes)
400b58: 54ffff41 b.ne 400b40 // Not at limit, continue loop

If ‘\n’ is not found in the first 80 bytes, call explode:

1
400b5c: 97ffffe6    bl 400af4 <explode>          // No '\n', explode

If newline \n is found, replace it with \0 (string terminator):

1
2
3
400b60: d0000501    adrp x1, 4a2000
400b64: 9104e021 add x1, x1, #0x138 // x1 = buffer
400b68: 3820c83f strb wzr, [x1, w0, sxtw] // buffer[x0] = 0 (wzr is the zero register)

Restore before returning:

1
2
3
4
5
400b6c: d0000500    adrp x0, 4a2000
400b70: 9104e000 add x0, x0, #0x138 // x0 = buffer, as return value

400b74: a8c17bfd ldp x29, x30, [sp], #16 // Restore frame pointer and return address, sp += 16
400b78: d65f03c0 ret // Return

image-20250629225645707

We can see phase_defused reads a global variable value, then decrements it.

Then loads a global address 0x464000+0x7c0 = 0x4647c0 as an argument to call printf.

image-20250630125413000

And w1 holds the value of the global variable that was decremented earlier, at address 0x4a0000 + 80 = 0x4a0000 + 0x50 = 0x4a0050.

image-20250630125539188

phase_0

image-20250624203514602

1
stp x29, x30, [sp, #-16]!
  • Purpose: Push x29 (frame pointer) and x30 (link register, return address) onto the stack. This saves the caller’s frame pointer and return address for later restoration.
1
mov x29, sp
  • Purpose: Assign the current stack pointer sp to the frame pointer x29, establishing the current function’s new stack frame.

From now on, x29 points to the bottom of this function call’s stack frame, convenient for accessing local variables or passing parameters later.

Next, the function calls read_int to read an int value, stores the return value in w0, then compares it with the value at 0x4a0000 + 0x54(84) = 0x4a0054 to check equality. Use gdb’s examine command to view this memory location:

image-20250624204122130

We can see it’s 2022.

Then the cmp instruction compares the input value (stored in x0) with 2022 (stored in x1). If not equal, jump to the line calling explode, so they must be equal. The input must be 2022.

phase_1

phase_1 is similar to phase_0.

image-20250624205525968

The function loads the value at address 0x4a0000+0x58(88) = 0x4a0058 into x1, then calls strcmp. Its parameters should be x0, x1, where x0 is our input value. This means it compares our input string with the string at address 0x4a0058, checking if the return value w0 is 0. If not 0, jump to the line calling explode.

image-20250624205829889

Note that address 0x4a0058 does not store the string itself, but the address of the string. So you need to first read this address, then read the string at that string address.

Answer: “Fault Tolerance: Reliable Systems from Unreliable Components.”

phase_2

image-20250629223737226

The first line allocates 64 bytes of stack space, enough for 64/8=8 64-bit values. The first line stores x29 at sp, x30 at sp+8.

Then saves sp to the x29 register.

Then stores x19, x20 registers at sp+16, sp+24, i.e.:

  • sp —> x29
  • sp+8 —> x30
  • sp+16 —> x19
  • sp+24 —> x20

Then x1 = sp+0x20 = sp+32, as the second parameter of read_8_numbers, then calls read_8_numbers.

image-20250629224840291

The function first allocates 0x20 (32 bytes) of stack space: sp = sp-32, then stores x29 at sp+16, x30 at sp+24.

Then sets frame pointer x29 to sp+0x10 (sp+16).

Then puts x1 into x2 — x1 is read_8_numbers’s second parameter.

Then x1 = x1+0x1c = x1+28, stores x1 at sp+8; x1 = x2+0x18 = x1+24, stores x1 at sp.

Then:

  • x7 = x2+0x14
  • x6 = x2+0x10
  • x5 = x2+0xc
  • x4 = x2+0x8
  • x3 = x2+0x4

x1 is the value at address 0x464000+0x858, a pointer — clearly the start of an array, a format string, used as __isoc99_scanf’s second parameter:

1
int sscanf(const char *str, const char *format, ...);

The first parameter is x0, which has remained unchanged since returning from read_line.

image-20250630140224444

After the function call, compare the return value with 0x7. If less than or equal, explode — so you must input 8 numbers.

After the function returns, restore the original stack structure:

  • sp —> x29
  • sp+8 —> x30
  • sp+16 —> x19
  • sp+24 —> x20
  • sp+32 —> array start address, 8 numbers, also the address of 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 filling the 64 bytes that phase_2 initially allocated.

image-20250630142117435

Then the 8 input numbers are checked. It’s easy to see the first and second numbers must both be 1.

Then a loop:

image-20250630142644664

x19 = sp+0x20 = sp+32, i.e. array[0].

x20 = sp+0x38 = sp+56, termination condition: (56-32)/4=6, stop at the 6th element.

First b 0x4007d0 skips one increment of x19 and comparison with x20.

If x19 == x20, jump to phase_2+108 to end.

Otherwise, first load x19 as address into w0 (array[i]), load the next into w1 (array[i+1]).

Let w0 = array[i] + array[i+1] + 4.

w1 = array[i+2].

Compare w0 == w1. If equal, jump to phase_2+60, increment i++.

If not equal, explode.

The first and second numbers must be 1.

So the third number is 1+1+4=6.

Fourth: 1+6+4=11.

Fifth: 6+11+4=21.

Sixth: 11+21+4=36.

Seventh: 21+36+4=61.

Eighth: 61+36+4=101.

So the answer is:

1
1 1 6 11 21 36 61 101

phase_3