I played iHack CTF 2025 solo and finished as the winner. These are three of the challenges I solved and the evidence that moved each one forward.
Read the Rules
This was a warm-up web challenge. The clue was the title itself.
I opened the /rules page, viewed the HTML source, and searched for comments. The flag was in a comment that the rendered page did not display.
The useful habit is to inspect what the browser received before reaching for a scanner. HTML comments, JavaScript bundles, source maps, and hidden form fields often reveal more than the visible page.
Hail Caesar
The challenge supplied two Base64 values. Decoding them was only the first layer.
I decoded both values in CyberChef. One result contained scrambled JavaScript. The structure was close enough to JavaScript that a character shift was worth testing. After reversing the shift, I had a small decryptor. Running that logic against the other decoded value produced the flag.
The helper reduced the test to one operation:
function decrypt(ch, increment) {
return String.fromCharCode(ch.charCodeAt(0) + increment);
}
let output = '';
output += decrypt('h', 0);
console.log(output);
The original encoded values are not included here, so this section documents the route rather than providing a fully reproducible solver.
From Mars
The challenge presented a file with a .jpg extension. An image viewer was not the right first test. I checked the file structure with binwalk:
binwalk challenge.jpg
The output showed an embedded Windows executable. After extracting it, I checked printable strings:
strings extracted.exe
One string referred to martian_transmission.c, which supported the idea that I was looking at a compiled challenge binary rather than image data.
Finding the transformation
I opened the executable in Ghidra and followed main. It called a function named process_binary_sequence.
At the call site, I identified three inputs: an array of integers, a shift value of 7, and a padding value of 13. The array lived elsewhere in the binary, so I copied the values from Ghidra and checked how the function transformed each element.
The transformation subtracted the index multiplied by the shift, then subtracted the padding. The compiled code kept the result to one byte.
Rebuilding it in Python
Rather than debug the executable, I reproduced that operation:
data = [
0x76, 0x7c, 0x7c, 0x7c, 0x94, 0xab, 0x9d, 0x91, 0xb5, 0xc7, 0xcd, 0xcd,
0xce, 0xec, 0xf9, 0xfe, 0xf8, 0xe1, 0x115, 0x114, 0x102, 0x135, 0x136,
0x13c, 0x14e, 0x16a, 0x0
]
shift = 7
padding = 13
flag = "".join(
chr((value - index * shift - padding) & 0xFF)
for index, value in enumerate(data)
if value != 0
)
print(flag)
The & 0xFF matters because several constants exceed 0xFF. Masking the result reproduces the single-byte wraparound performed by the compiled program.
A debugger offered a shorter route. I could have stopped execution after process_binary_sequence returned and inspected the output buffer. Reimplementing the function took longer, but it left me with a solver I could explain and rerun.