$ cat writeup.md…
$ cat writeup.md…
HackTheBox
Task: ARM embedded system reads 8-digit passcode via UART, uses it as LCG seed to generate addresses into W25Q128 SPI flash, reads scattered flag bytes. Solution: reverse engineer LCG parameters from binary, brute-force 10^8 seed space with known-plaintext 'HTB{' prefix check against flash dump.
Our years undercover in the art dealing world have paid off, and we have compromised the inner circle. The coordinates of the stolen treasures are contained within a mysterious device that we now have clearance to access. We managed to dump its flash memory and extract the embedded software running on it, and tap its input channel with a trigger-based capturing device in order to monitor passcode input. Its secrets must be retrieved!
Three files provided:
embedded_software — ARM 32-bit ELF binary (Raspberry Pi), linked with wiringPi library, not strippedflash_memory_dump.bin — 16 MB W25Q128 SPI flash chip dumpinput_channel_trace.sal — Saleae Logic 8 capture of UART input channel (internal .sal format containing digital-0.bin and meta.json)Goal: recover the secret (flag) stored scattered across the flash memory.
The embedded_software file is an ARM 32-bit ELF binary for Raspberry Pi. It imports functions from libwiringPi.so (SPI and serial I/O) and libc.so.6. Source file names visible in debug info: main2.c, memory_access.c.
main — orchestrates the entire flow:
wiringPiSPISetup(0, 0x1e8480)serialOpen("/dev/ttyS0", 38400) (0x9600 = 38400 decimal)unsigned long via strtoul(buffer, NULL, 10) — this is the passcode/seednext_in_seqW25Q128_init(0) to configure SPI channelget_UniqieID() to read 8-byte unique ID from flash via SPI command 0x4Bd2 66 b4 21 83 51 30 2crandom_generator() three times to construct a 24-bit flash address, reads 1 byte from flash, outputs it via putchar()random_generator — Linear Congruential Generator (LCG):
next_in_seq = next_in_seq * 0x41c64e6d + 0x0008042a (32-bit overflow)
return next_in_seq % 16777215 (mod 0xFFFFFF)
get_secret — SPI flash read:
0x03 (Read Data) with 3-byte addressAddress construction (per flag character):
rng1 = random_generator()
rng2 = random_generator()
rng3 = random_generator()
addr = (rng1 & 0xFF0000) | (rng2 & 0xFF00) | (rng3 & 0xFF)
The .sal file is a zip containing Saleae Logic 2 internal format files. Meta.json indicated: Saleae Logic 8, 50 MHz digital sample rate, 1 digital channel, 3.54 second capture. The internal binary format uses undocumented RLE-encoded transition data, making reliable UART decoding at 38400 baud difficult without the proprietary software. This led to the brute-force approach.
Key insight: the passcode is an 8-digit decimal number (00000000–99999999 = 10^8 possibilities). The LCG is fully deterministic given the seed. The flag format HTB{...} provides a known-plaintext check — we can verify the first 4 characters against the flash dump to filter candidates.
flash[addr] matches H, T, B, { respectively#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> int main() { FILE *f = fopen("flash_memory_dump.bin", "rb"); if (!f) { perror("open flash"); return 1; } unsigned char *flash = malloc(16777216); fread(flash, 1, 16777216, f); fclose(f); for (uint32_t seed = 0; seed < 100000000; seed++) { uint32_t state = seed; // Check first 4 chars: H, T, B, { char expected[] = "HTB{"; int match = 1; for (int c = 0; c < 4 && match; c++) { state = state * 0x41c64e6d + 0x0008042a; uint32_t rng1 = state % 16777215; state = state * 0x41c64e6d + 0x0008042a; uint32_t rng2 = state % 16777215; state = state * 0x41c64e6d + 0x0008042a; uint32_t rng3 = state % 16777215; uint32_t addr = (rng1 & 0xFF0000) | (rng2 & 0xFF00) | (rng3 & 0xFF); if (flash[addr] != expected[c]) match = 0; } if (!match) continue; // Found HTB{ prefix — generate full 49-char flag printf("FOUND seed=%u\n", seed); state = seed; printf("Flag: "); for (int i = 0; i < 49; i++) { state = state * 0x41c64e6d + 0x0008042a; uint32_t rng1 = state % 16777215; state = state * 0x41c64e6d + 0x0008042a; uint32_t rng2 = state % 16777215; state = state * 0x41c64e6d + 0x0008042a; uint32_t rng3 = state % 16777215; uint32_t addr = (rng1 & 0xFF0000) | (rng2 & 0xFF00) | (rng3 & 0xFF); printf("%c", flash[addr]); } printf("\n"); free(flash); return 0; } printf("Not found\n"); free(flash); return 1; }
Compiled with cc -O3 -o brute brute.c and ran in under 1 second:
FOUND seed=73261522
Flag: HTB{REDACTED}
The passcode entered via UART was 73261522.
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar