LeHACK 2025 z80 Explorer

The Game Boy’s CPU is very similar to Intel’s but much simpler. Loading the ROM into an emulator/disassembler that supports Game Boy binaries makes this challenge straightforward.


Kaddate |

Difficulty : Medium
Category : Reversing
Ctf : LeHACK 2025


The file command indicates that our target is a Game Boy ROM image (256 Kbit, Rev.00):

~ $ file z80_explorer.gb 
z80_explorer.gb: Game Boy ROM image (Rev.00) [ROM ONLY], ROM: 256Kbit

Since the console is very old, the architecture might be very simple to understand. I looked for tools to emulate and disassemble the ROM.
- I decided to go for pyboy as the emulator.
- And GhidraBoy for Ghidra, which is an extension that loads Game Boy ROMs correctly.

Running the Game

When launched in PyBoy, the game shows only a skull icon and neither responds to input nor progresses—effectively a “zombie” process.

ef4026acceac565ed2f287dd453e5f9c.png

Ghidra FTW

GhidraBoy correctly imports the ROM. The function list is minimal, suggesting almost no game logic beyond simple memory operations.

f53faca7c2063f80143b17fb7b45133a.png

Entrypoint

The decompiled entry function is hard to read. I switched to the raw disassembly to trace what it actually does.

864e089310205ab177aa1f58ddbb769f.png

Identifying a memcpy‑like Routine

The first function called by the entry point (the function FUN_0172), is interesting.

0eef8c40d39e7644b98454fc9b0f3a91.png

It calls another function (FUN_01b7), where the first argument seems to be an harcoded memory address. This could indicate that this function might write or read to registers within the SoC used by the gameboy.

The first call of the function FUN_01b7 has the following arguments :
- 0x9000
- Some pointer to probably a data stream
- and a pointer to some data, probably an integer.

Here is it's disassembly :

2e76ca13ae899222c7b42e672db8546a.png

We can see that the code is fairly simple, nevertheless the registers are not renamed to their parameters id. While looking at some documentation, I noticed that the gameboy actually has no default ABI. So I have to check what the caller does to understand how the parameters are passed.

To do this I Checked this documentation that shows the differents registers of the CPU and their sizes : https://gbdev.io/pandocs/CPU_Instruction_Set.html

To do this, I have looked at this documentation which shows the differents registers of the CPU and their sizes.

Now looking at the disassembly of the caller, we can see 3 LD intructions to DE, HL and BC registers, before the call to the function FUN_01b7.

99ad518a5dfed5f749e27bfd3ac8423b.png

So the caller basically put theses values into the following arguments :
- HL = 0x9000
- BC = pointer to 0x360
- DE = pointer to some data

Now looking back at the code of the function FUN_01b7, it is fairly easy to understand what's happening :

2e76ca13ae899222c7b42e672db8546a.png

The function simply load the data pointed by DE to the address stored into HL. The register BC serving as the amount of byte to copy.

The function signature is something like that :

void __asm memcpy(byte * dst, byte * src, uint8_t * len)

The calling convention would be HL→dst, DE→src, BC→length.

Suspecting this routine draws graphics, I patched one CALL FUN_01B7 to a NOP. Re‑running the ROM in PyBoy resulted in a blank screen, confirming that FUN_01B7 handles rendering.

ec53677af3264223c1d66838f7e9b090.png

Identifying unused code

Looking a bit more at Ghidra's listing, I could see something was off. The function contains some garbage data at its end, with no cross references. This is unlikely to be data because data and code are commonly placed in seperated memory regions.

33454bdb1f10e2bb3859a7f018d8ce7b.png

Disassembling this data clearly shows some unused code.

1dec618886e465c997540c710f1a091b.png

This portion of code, executes 2 calls to the memcpy function, using the same addresses than the two previous calls, likely replacing what's on the screen once executed. The ez trick to execute theses calls is just to patch the RET by a NOP

3b066d135b9c5937e9aeb774a51441e4.png

Let's execute the ROM again now :

9fddc0551e19ab7da03705bf434f4be2.png

And here is the flag :)