247CTF - The Flag Bootloader

Can you unlock the secret boot sequence hidden within our flag bootloader to recover the flag?

Analysis

1
2
❯ file flag.com
flag.com: DOS/MBR boot sector

This is a 512-byte boot sector image. Using xxd to view the hex data:

1
2
3
4
00000000: eb21 b800 10cd 16c3 60b4 0e8a 043c 0074  .!......`....<.t
00000010: 0eb7 00b3 07cd 1080 fe24 7403 46eb ec61 .........$t.F..a
...
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.

The 55 aa signature at the end confirms it is a standard boot sector.

Boot Sector Fundamentals

When a computer powers on and completes the Power-On Self-Test (POST), the BIOS reads the first sector (512 bytes) of the disk into physical memory between 0x7C00 and 0x7DFF. It then sets the CPU’s Instruction Pointer (IP) to 0x7C00 to begin execution.

Therefore, the base address of this program in memory is 0x7C00. When performing static analysis or debugging, any hardcoded physical addresses must have 0x7C00 subtracted to find their corresponding file offset.

Static Analysis and Memory Mapping

Analyzing the assembly code reveals two critical memory locations:

1. Flag Ciphertext Area (0x7DAA)

Calculate the file offset:
$$0x7DAA - 0x7C00 = 0x01AA$$
Looking at the hex dump at 0x01AA:

1
000001a0: 6420 636f 6465 210a 0d00 3234 3743 5446  d code!...247CTF

The ciphertext indeed begins here, with the first 7 bytes representing the 247CTF{ prefix.

2. Input Buffer (0x7DEC)

Calculate the file offset:
$$0x7DEC - 0x7C00 = 0x01EC$$
The region of null bytes before the 55 aa signature is used directly as a buffer for keyboard input.

Core Logic Deconstruction

Focusing on sub_1016A in IDA (the primary decryption logic):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
seg000:016B  mov  bx, 7DECh      ; BX points to the input buffer
seg000:016E mov si, 7DAAh ; SI points to the start of the ciphertext
seg000:0171 add si, 7 ; Skip "247CTF{"

; Validate the first input character and decrypt
seg000:0174 mov al, 4Bh ; 'K'
seg000:0176 xor al, 0Ch ; AL = 0x4B ^ 0x0C = 0x47 ('G')
seg000:0178 cmp [bx], al ; Check if input[0] == 'G'
seg000:017A jnz loc_1027C ; Jump to failure if not equal

; Decrypt ciphertext using the validated char in AL
seg000:017E xor [si], al ; XOR decryption
seg000:0180 inc si
seg000:0181 xor [si], al ; Each input char decrypts TWO bytes
seg000:0183 inc bx
seg000:0184 inc si

Logic Summary:

  1. The program requires a 16-character unlock code.
  2. Each character is validated via arithmetic/logical operations (XOR, ADD, SUB).
  3. Validated characters serve as XOR keys to decrypt the subsequent ciphertext region.
  4. Each key character decrypts 2 bytes of ciphertext.

Solution Script

We can simulate the assembly operations to recover the unlock code and then use it to XOR the ciphertext bytes.

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
#!/usr/bin/env python3

def solve():
# 1. Recover the 16-character unlock code
unlock_chars = [
0x4B ^ 0x0C, 0x53 ^ 0x06, 0x58 - 0x01, 0x62 - 0x29,
0x68 ^ 0x23, 0x4B ^ 0x00, 0x62 - 0x1E, 0x4D - 0x0B,
0x45 ^ 0x0D, 0x10 ^ 0x28, 0x58 ^ 0x1D, 0x7A ^ 0x28,
0x65 - 0x13, 0x33 ^ 0x07, 0x25 ^ 0x15, 0x4C + 0x0C
]

unlock_code = "".join(chr(c) for c in unlock_chars)
print(f"[*] Unlock Code: {unlock_code}")

# 2. Extract encrypted payload (32 bytes from offset 0x1B1)
# This corresponds to memory 0x7DB1 (0x7DAA + 7)
payload_hex = "77 21 67 30 60 35 0c 0c 78 79 2e 2e 20 72 70 75 29 2b 00 5c 21 70 62 63 65 60 07 0d 06 02 3b 3b"
encrypted_bytes = bytes.fromhex(payload_hex)

# 3. Perform XOR decryption
decrypted_chars = []
for i, byte in enumerate(encrypted_bytes):
key = unlock_chars[i // 2]
decrypted_chars.append(chr(byte ^ key))

print(f"[+] Flag: 247CTF{{{ ''.join(decrypted_chars) }}}")

if __name__ == "__main__":
solve()
247CTF{0f2e7b5532eed627ac8dd501723962cc}