GreyCTF 2025
Formula739137
MISC | Finals

So we are given with this nice image of a Mercedes W16.
To begin with, let's first understand that a PNG file is made up of chunks.
Here are some common chunk types that are good to know:
IHDR - Image header
IDAT - Image data
IEND - Image end marker
Every chunk follows a unified structure:
Running PNG-Check on the file reveals suspicious IDAT chunks of 9 bytes at the end of the image, as shown below.
Let's extract these chunks and take a quick look.
First Sus Chunk Offset = 0x160f2d = 1445677
1445677 - 4 = 1445673 (to include the length since the offset is where the Type string is detected)
For easier viewing, let's just list it in 21 bytes:
Data length = 9Then
Total chunk size = 4 + 4 + 9 + 4 = 21 bytes
It is observed that the flag is found by concatenating the last byte of each chunk from offset 0x0000508e onwards.
After solving the challenge, I was told that there was an easter egg in the challenge, and here you go:
ββ$ grey{m3rced3s_1s_my_f4v_team!}
Reversing 101
EZPZ | Qualifier
The given binary was decompiled and analyzed, revealing three primary functions:
Function a : A length function that calculates the size of the input.
Function b : A key generation function that produces the internal RC4 key.
Function c : The RC4 encryption/decryption function.
The overall flow is as follows: the input is first processed by Function a to check that its length is exactly 5 characters, then encrypted by Function c using the key generated from Function b. The result is then compared against the encrypted byte array:
To obtain the return value of Function b using GDB, follow these steps:
Launch GDB with the binary:
gdb ./chalSet a breakpoint at the start of Function B:
b *0x00401217runs until Function B returns:
finishprint the returned key value:
Note:
On x86 (32-bit), the return value is stored in the
eaxregister.On x86-64 (64-bit), the return value is stored in
rax.
Then decrypt the encrypted byte array by running the code below.
Notsus.exe
Forensics | Qualifier
Insert Guessy forensics challenge description here
We are given a password-protected ZIP file that contains two files:
not_sus.exeflag.txt.yorm
Initial attempts to brute-force the ZIP using pkcrack didnβt work. So, I shifted focus to another common ZIP attack: the known-plaintext attack.
A great reference for this type of attack is this write-up from IEEE VIC-3 CTF 2024.
The idea behind a known-plaintext attack on ZIP files is that you know some part of the original content, and you can use that to recover the decryption key.
In this case, the .yorm file type was unfamiliar, so I focused on the not_sus.exe instead.
Most Windows executables start with a typical DOS MZ header containing the following:
0x00
0x4D 0x5A
"MZ" signature β Magic number for DOS/Windows executables.
0x02
0x90 0x00
Bytes on last page β 0x0090 = 144 bytes
0x04
0x03 0x00
Pages in file β File is 3 * 512 = 1536 bytes total.
0x06
0x00 0x00
Relocation entries β Number of relocation entries (0 here).
0x08
0x04 0x00
Header size β In 16-byte paragraphs. 0x0004 = 4 * 16 = 64 bytes
0x0A
0x00 0x00
Minimum extra paragraphs needed β Typically zero.
0x0C
0xFF 0xFF
Maximum extra paragraphs needed β 0xFFFF indicates βas much memory as availableβ.
0x0E
0x00 0x00
Initial SS (stack segment) β Relative to the load segment (0 here).
Using this known header as plaintext, I ran a known-plaintext ZIP attack using bkcrack
After decrypting the ZIP, we now have:
not_sus.exeflag.txt.yormβ contents look like gibberish, likely encrypted or encoded.
Running not_sus.exe through Detect It Easy (DIE) reveals that itβs a PyInstaller-packed Python executable.

We can unpack the executable with PyInstxtractor and decompiling the obtained .pycwith PyLingual
Looks like another RC4.
Recover the flag :
ββ$ grey{this_program_cannot_be_run_in_dos_mode_hehe}
SGRPC
Web | Qualifier
Can't get hacked if they can't reach it.
This challenge revolves around a gRPC server written in Go, exposing two main RPC methods via the Flag service:
HelloGetFlag
The server also includes a custom implementation of gRPC Server Reflection with some restrictions enforced in customreflect.go.
Based on the provided source code, the GetFlag RPC requires three specific conditions to be met in the request in order to get the flag.
To identify the required fields, we can enumerate using available gRPC Server Reflection. After some trial-and-error :
Base64 decoding the first file descriptor reveals it's a protoset for gRPC, but itβs not valid.
So letβs try decoding it manually to see whatβs actually inside.
With this, we can reconstruct the .proto file ourselves, which we can then use to generate a valid protoset.
ββ$ grey{r3fl3ct_th3_sch3m4}
Meowware
Rev | Qualifier | DidNotSolve
We were given two files: client (an executable) and client.pcap (containing encrypted traffic).
When we run the binary, it produces no output:
From this output, we can see that:
The binary uses x86-64 architecture (AMD64, Intel 64-bit)
It's statically linked
It's stripped
By using strace to trace the system calls, we were able to identify that:
The binary is packed with a custom upx (notice the "upX" and "upx"
memfdnames)It unpacks itself during runtime
It creates in-memory files (
memfd_create), writes unpacked content to them, and executes them viammap
Since we know the binary dumps the unpacked executable into in-memory files, we can try to dump the memory during runtime to obtain the unpacked binary.
From the strace output, we saw:
Start address: 0x400000
Size: 1062920 bytes = 0x103000 in hex
Last updated