- Published on
No Hack No CTF 2025 - Reverse challenges
Introduction
No Hack No CTF 2025
, organized by ICEDTEA
, held a jeopardy-style CTF from July 5th to 7th. Our team initially placed 5th, then climbed to 3rd, and finally finished 4th ;) We managed to solve 4 out of 5 reverse challenges. More information about this CTF can be found here. 
flag checker

import struct
def sub1189(x):
# Convert float or IEEE754 bit-pattern to int
if isinstance(x, float):
return int(x)
return int(struct.unpack('<f', struct.pack('<I', x & 0xffffffff))[0])
def sub119d(x):
return x & 0xffffffff
def rol8(x, n):
x &= 0xff
return ((x << n) & 0xff) | (x >> (8 - n))
def ror8(x, n):
x &= 0xff
return ((x >> n) | ((x << (8 - n)) & 0xff)) & 0xff
def sub11dc(x, n):
return rol8(x, n)
def sub120d(x, n):
return ror8(x, n)
# Initialize result array
res = [None] * 46
# Apply each constraint to determine characters
res[0] = next(c for c in range(256) if sub1189(c * 3.1415) == 0xf5)
res[1] = 0x48
res[2] = (sub11dc(sub1189(0x419cf5c3), 3) - 0x4a) & 0xff
res[3] = (((sub119d(0x411b4673) >> 16) & 0xff) + 0x28) & 0xff
res[4] = next(c for c in range(256) if (sub1189(c * 2.5) + 0x48) & 0xff == c)
res[5] = (sub120d(sub1189(0x420fe9e1), 1) - 0x27) & 0xff
res[6] = (sub1189(0x41316c22) * 2 + 0x5f) & 0xff
res[7] = (((sub119d(0x42607127) >> 8) & 0xff) + 2) & 0xff
res[8] = next(c for c in range(256) if (sub11dc(sub1189(c * 1.5), 2) - 0x12) & 0xff == c)
res[9] = (sub1189(0x416c902e) // 3 + 0x5b) & 0xff
res[0xa]= (sub119d(0x40b37b4a) + 0x29) & 0xff
res[0xb]= next(c for c in range(256) if (sub1189(c * 7.76999998) % 5 + 0x2e) & 0xff == c)
res[0xc]= (sub120d(sub1189(0x42b16c22), 4) - 0x18) & 0xff
res[0xd]= (sub1189(0x41082681) + 0x2b) & 0xff
res[0xe]= (((sub119d(0x41f66b86) >> 24) & 0xff) + 0x1e) & 0xff
res[0xf]= (sub11dc(sub1189(0x413313aa), 2) + 0x37) & 0xff
res[0x10]= (sub1189(0x406c902e) * 3 + 0x27) & 0xff
res[0x11]= (sub120d(sub1189(0x3f800000), 1) - 0x50) & 0xff
res[0x12]= (sub1189(0x40a00000) + 0x67) & 0xff
res[0x13]= ((sub119d(0x3f800000) >> 24) & 0xff) + 0x20
res[0x14]= (sub11dc(sub1189(0x40c00000), 1) + 0x5a) & 0xff
res[0x15]= ((sub1189(0x40e00000) + 0x14) << 2) & 0xff
res[0x16]= ((sub1189(0x41000000) >> 1) + 0x6b) & 0xff
res[0x17]= (((sub119d(0x40000000) >> 16) & 0xff) + 0x34) & 0xff
res[0x18]= (sub120d(sub1189(0x40400000), 3) + 0x14) & 0xff
res[0x19]= (sub1189(0x40a00000) % 7 + 0x2c) & 0xff
res[0x1a]= ((sub1189(0x40c80000) + 0x31) * 2) & 0xff
res[0x1b]= (((sub119d(0x3fc00000) >> 8) & 0xff) + 0x67) & 0xff
res[0x1c]= (sub11dc(sub1189(0x41100000), 4) - 0x31) & 0xff
res[0x1d]= (sub1189(0x41200000) // 5 + 0x6e) & 0xff
res[0x1e]= (((sub119d(0x3f000000) >> 24) & 0xff) - 0xf) & 0xff
res[0x1f]= (sub120d(sub1189(0x40000000), 2) - 0x17) & 0xff
res[0x20]= ((sub1189(0x41100000) % 10 + 0x2e) * 2) & 0xff
res[0x21]= (sub119d(0x3fa00000) + 0x37) & 0xff
res[0x22]= (sub11dc(sub1189(0x40b00000), 5) - 0x41) & 0xff
res[0x23]= (sub1189(0x40d00000) + 0x2a) & 0xff
res[0x24]= (sub120d(sub1189(0x40f00000), 6) + 0x54) & 0xff
res[0x25]= ((sub1189(0x41080000) & 3) + 0x33) & 0xff
res[0x26]= (((sub119d(0x40000000) >> 16) & 0xff) + 0x72) & 0xff
res[0x27]= (sub11dc(sub1189(0x408ccccd), 1) + 0x59) & 0xff
res[0x28]= (sub1189(0x40d33334) * 5 + 0x19) & 0xff
res[0x29]= (((sub119d(0x3f800000) >> 8) & 0xff) + 0x69) & 0xff
res[0x2a]= (sub120d(sub1189(0x400ccccd), 3) + 0x2f) & 0xff
res[0x2b]= (sub1189(0x4013d70a) + 0x6c) & 0xff
res[0x2c]= (sub11dc(sub1189(0x406ccccd), 7) - 0x4c) & 0xff
res[0x2d]= 0x7d
# Join and print the flag
flag = ''.join(chr(c) for c in res)
print(flag)
NHNC{jus7_s0m3_c00l_flo4t1ng_p0in7_0p3ra7ion5}
b@by_r3v3rs3

from Crypto.Cipher import AES
# Dane z modułu WASM
encrypted = b'\x06F\x88\x88"\xbb\x1d\x1d\x9d\xd4T6M\xc1\xefB' \
b'\xf9\xeb\xd2\xd8\xd4\x93\x13\xc3@wr\x88\xe2\xeaIG' \
b'\x91?\xe5\x9f<S]\xd2{\xbc&\xec\xc3)\xa9\x10'
# Klucz i IV (16 bajtów każdy), pobrane z zakodowanych danych w module
key = b"secretkey???!!!~" # 16-bajtowy klucz AES
iv = b"Hi_I_am_iv_owo!!" # 16-bajtowy wektor inicjalizacyjny
# Deszyfrujemy w trybie AES-CBC (AES-128) – otrzymamy tekst jawny (flagę)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(encrypted)
# Odrzucamy ewentualne wypełnienie i konwertujemy na tekst
# (zakładamy wypełnienie PKCS#7; w przykładzie są 8 bajtów 0x08 na końcu)
pad_len = plaintext[-1]
flag = plaintext[:-pad_len].decode('utf-8', errors='ignore')
print("Flaga:", flag)
encrypted: the original 48-byte encrypted string (from the WASM module data).
key, iv: the AES key and IV extracted from the module's strings.
Decryption: AES-128 in CBC mode decrypts the data.
Padding: removes PKCS#7 padding (the last byte indicates how many padding bytes were added).
NHNC{3@sy_R3v3rs3_f0r_fr33_fl@9!!!!!!!!}
Yep Another Snake Game


The name of the task suggests that it's a snake
game. We can confirm this by running the code in a web browser:


The important parts of this script are as follows:
func flag1():
return "he_tol" + "d_"
func flag2():
var flag = "where_is_my_snake"
var flag100 = "100_score_correct"
if flag == "whale120":
return flag100
elif 1337 == 1337:
var a1 = 123
a1 ^= 100
if 1 == 1:
return "me" + "_n" + "ot_to"
func flag3():
var test = 1337
test += 1
test += 3
if test == 0:
return "user_root" + ":pa"
elif test == 3:
return flag1()
else:
return "_use"
func flag4():
var meow = 100
for i in range(100):
meow += i
if meow:
return "_che" + "at_eng" + "ine"
else:
return "my_fir" + "st" + "_game" + "_hackIng"
func Score(x):
get_node("Score").clear()
if x >= 1333337:
get_node("Score").add_text(flag1() + flag2() + flag3() + flag4())
else:
get_node("Score").add_text(str("Score : ", x))
Now, we can just replace func
with def
, remove the var
keyword and at the end add print(flag1() + flag2() + flag3() + flag4())
. Then, just save it as a python
file and run it to get the flag:
NHNC{he_told_me_not_to_use_cheat_engine}
Encrypted(?

This task seemed difficult at first—there was a lot of code and many checks to analyze. I approached it a bit differently. I didn’t even need encoded examples, just the encoded flag that we received with the challenge. Besides that, there was a binary that encoded a file. If the file contained the string "AA", the letter "A" in the first position was always encoded the same way, and the letter "A" in the second position was also always encoded the same way, although it differed from the first one. The first letter was independent of the other. Since the flag file flag2_enc.txt
contained 507 bytes, it was possible to map 507 positions and then restore them. It worked!!! As you know, I really like bruteforce ;)
from pwn import *
import threading
context.log_level = 'warning'
context.update(arch='x86_64', os='linux')
context.terminal = ['wt.exe','wsl.exe']
BINARY_NAME="./encrypter"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
zmapowana_tablica={}
mapped_bytes = []
mapping_all = {}
for length in range(1, 508): # od 1 do 507 bajtów
mapped_bytes_n = []
for j in range(0x20, 0x7f): # tylko pisemne znaki ASCII
data = bytes([0x41] * (length - 1) + [j])
with open('plik.txt', 'wb') as f:
f.write(data)
p = process([binary.path, 'plik.txt'])
p.recvall(timeout=2)
p.close()
with open('plik.txt', 'rb') as f:
encrypted_bytes = f.read(length)
mapped_bytes_n.append((j, encrypted_bytes[-1:])) # interesuje nas ostatni bajt
mapping_all[(length, j)] = encrypted_bytes[-1:]
# Wyświetl mapowanie dla danej długości
print(f"\n--- Mapowanie dla długości {length} bajtów ---")
for j, b in mapped_bytes_n:
print(f"{length}th: {j:02x} -> {b.hex()}")
# Na końcu wypisz całość
print("\n=== Pełne mapowanie wszystkich długości ===")
for (length, j), b in mapping_all.items():
print(f"{length}th: {j:02x} -> {b.hex()}")
# Odczytaj zakodowany plik
with open('flag2_enc.txt', 'rb') as f:
encrypted_data = f.read()
# Odwrotne mapowanie: (length, zaszyfrowany_bajt) -> oryginalny_bajt
reverse_mapping = {}
for (length, orig_byte), enc_byte in mapping_all.items():
reverse_mapping[(length, enc_byte)] = orig_byte
# Dekodowanie bajtów
decoded = []
for idx, enc_byte in enumerate(encrypted_data):
length = idx + 1 if idx + 1 <= 507 else 507 # dla pozycji >507 używaj długości 507
key = (length, bytes([enc_byte]))
if key in reverse_mapping:
decoded.append(reverse_mapping[key])
else:
decoded.append(ord('?')) # znak zapytania jeśli nie znaleziono
# Zamień na string
decoded_str = ''.join(chr(b) for b in decoded)
print("Odkodowany tekst:")
print(decoded_str)
Bonus
Sometimes these binaries are simply no longer available, but occasionally you can find them on GitHub, so you can download and test them. Download binaries.zip