reverse reverse

GRID_WAVE

hofill CSCG25
reverse avr gdb qemu ripemd160

Flag

dach2025{eada28984ece76ce258f354d1a87c140a2062347}

Summary

Reversing an AVR binary to get the flag byte by byte using GDB under QEMU.

Details

To run and debug the AVR binary, we use qemu-system-avr:

qemu-system-avr -M uno -bios GRID_WAVE_4X8_LangleyMicros.vr -nographic \
                -serial tcp::5678,server=on,wait=off \
                -s -S

The -s -S flags open a GDB server on port 1234. Connect with:

avr-gdb -nx -ex 'target remote :1234' GRID_WAVE_4X8_LangleyMicros.vr

The serial interface is available via telnet localhost 5678.

Getting clean disassembly from Ghidra was unreliable for AVR, so we used avr-objdump instead:

avr-objdump -D -m avr GRID_WAVE_4X8_LangleyMicros.vr > caca.asm

Running this YARA ruleset against the binary reveals it uses RIPEMD-160. This was useful context but not strictly necessary to solve it.

The key function is a comparison loop at 0x8a4:

; r24:r25 - Pointer to A (our input hash)
; r22:r23 - Pointer to B (the expected hash)
; r20:r21 - Length (20 bytes for RIPEMD-160)
 8a4:  fb 01     movw  r30, r22
 8a6:  dc 01     movw  r26, r24
 8a8:  04 c0     rjmp  .+8       ; jump to 0x8b2
 8aa:  8d 91     ld    r24, X+   ; load byte from our input
 8ac:  01 90     ld    r0, Z+    ; load byte from expected (Z++)
 8ae:  80 19     sub   r24, r0   ; compare
 8b0:  21 f4     brne  .+8       ; bail if not equal
 8b2:  41 50     subi  r20, 0x01 ; decrement length
 8b4:  50 40     sbci  r21, 0x00
 8b6:  c8 f7     brcc  .-14      ; loop

At 0x8ac, r0 is loaded with the expected hash byte just before comparison. Setting a breakpoint here and reading $r0 after each iteration gives us the target hash one byte at a time.

Method 1 — GDB automation

(gdb) define pipi
Type commands for definition of "pipi".
End with a line saying just "end".
>ni
>printf "%x", $r0
>set $PC2=0x8ac
>end

Run pipi 20 times to get all 20 bytes of the expected RIPEMD-160 hash.

GDB and AVR process memory were out of sync when trying to read the Z register’s memory directly — had to read $r0 after each ld instruction instead.

Method 2 — Oracle bruteforce

Send progressively longer prefixes of the correct hash as input. Each time the comparison reaches a new byte position, $r0 will contain the next expected byte (at the moment the comparison fails).

LAUNCHCODE> eada28984ece76ce258f354d1a87c140a2062347
Success
[!] Activating GRIDWAVE...
dach2025{eada28984ece76ce258f354d1a87c140a2062347}

Resources

DNXSS-over-HTTPS Little Cluster