- Published on
CTF Write-up: BlackBox (misc)
- Authors
- Name
- speep
- @x86nullptr
- reverse engineer
BlackBox (misc) – CTF Write-up
Challenge Overview
We’re given a 8.5kb file named firmware.elf, and told generically the flag is hidden somewhere within.
- A short and sweet solve, this was a personal speed record. We missed first blood by one or two teams.
- Target is an 8-bit AVR firmware file (discovered to be
atmega328) - Rather than pull the ELF apart, we dumped it straight to raw bytes for quick analysis
- XOR cribbed the flag using a known plaintext attack
Initial Reconnaissance
Using the file command, target is confirmed to be an unstripped, 8-bit AVR firmware ELF:
~ λ file firmware.elf
firmware.elf: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, with debug_info, not stripped
Running strings gives some more insight:
~ λ strings firmware.elf
GCC: (GNU) 7.3.0
/home/jenkins/workspace/avr-gcc-staging/...
atmega328p
...
This tells us two things:
- Firmware was compiled using the
avr-gcctoolchain - Target microcontroller unit is
atmega328p, common in many Arduino boards
With an embedded background, we expect string literals (like a flag) would be baked into flash (.text / PROGMEM) or EEPROM. We will inspect these regions in hex, while discarding the SRAM sections (.data / .bss) during static analysis.
Static Analysis
The bytes from the .text section were extracted using IDA’s hex editor. Expecting simple obfuscation, we pasted those bytes into CyberChef and got to work.
Input:
940C 0068 940C 0068 E3F1 E6E6 E3F1 F1DE
94CD FAD6 D694 D6FA C8CA FA96 94D6 D5C8
96C9 91FA C1D7 94D0 CACB C3FA D794 D2C8
D791 D8C0 0000 2411 BE1F EFCF E0D8 ...
To get started, we must first apply a few operations (or filters) to the byte array.
Operation 1: Swap endianness
Recall that the target MCU is atmega328p, where instructions are 16-bits wide and stored little-endian in flash. This means the low and high bytes must be swapped. We apply Swap_endianness('Hex', 2), where 2 is our word length in bytes.
| Recipe | Output |
|---|---|
Swap_endianness('Hex', 2) | 0c 94 68 00 0c 94 68 00 f1 e3 e6 e6 f1 e3 de f1 .. |
Operation 2: From Hex
We parse the above output as hex bytes delimited by the space char. If there were to exist a cleartext string literal in the PROGMEM section, which is to say not encrypted, encoded, or obfuscated by any means, it would be visible in the output window. This is clearly not the case.
| Recipe | Output |
|---|---|
Swap_endianness('Hex', 2) | 0c 94 68 00 0c 94 68 00 f1 e3 e6 e6 f1 e3 de f1 .. |
From_Hex('space') | ãææñãÞñÍÖúÖúÖÊÈúÖÈÕÉú×ÁÐËÊúÃ... |
Operation 3: XOR Cribbing
We again parse the above output, this time trying our first simple decryption routine: dragging a known plaintext, or a crib, across the bytes (ciphertext) while iterating XOR keys. Naturally our chosen crib is TFCCTF{, the guaranteed flag prefix.
This small (a priori) knowledge drastically reduces our search space. Instead of searching the entire keyspace ${0, 1}^n$ for an $n$-byte key, we can narrow it by excluding any key that doesn’t immediately yield our crib when partially applied via $P = C ⊕ K$.
CyberChef allows us to do this very thing via the operation XOR_Brute_Force(1, 100, 0, 'TFCCTF{') where 1 is the (initial) key length, 100 is the sample length, and 0 is the offset.
Even with key length $n = 1$, we reduced the keyspace from $2^8 = 256$ candidates (0x00 - 0xFF) to just one: 0xA5.
| Recipe | Output |
|---|---|
Swap_endianness('Hex', 2) | 0c 94 68 00 0c 94 68 00 f1 e3 e6 e6 f1 e3 de f1 .. |
From_Hex('space') | ãææñãÞñÍÖúÖúÖÊÈúÖÈÕÉú×ÁÐËÊúÃ... |
XOR_Brute_Force(1, 100, 0, 'TFCCTF{') | Key = a5: ÃÂ¥TFCCTF{Th1s_1s_som3_s1mpl3_4rdu1no_f1rmw4re}¥¥ |
Key Takeaways
- Don’t overcomplicate it. This was an especially fast solve because we did not write a single line of code nor bother with any complicated AVR diassembly tools.
- CyberChef is a very powerful tool, and it has more features than you think; even if you don’t crib with a known plaintext, you could at least scan the byte array for text entropy. Thanks GCHQ.
- Time is of the essence. This challenge was released in the second wave, and we might’ve gotten first blood points if we didn’t wait so long to attempt them. This was completed in only a few minutes while waiting on an SMT solver.