Emulator

- 10 mins read

WorldWideCTF 2025: rev/Emulator

Team: TroJeun

Challenge Details:

  • Points: 500
  • Category: Mobile
  • Author: em07robot

Description

“Inside an emulator, reality bends—only shadows find the hidden truth.”

We are provided with a large zstd-compressed file:

└─$ zstd -d chall_dist.zst                                                           
chall_dist.zst      : 10778972160 bytes  

Initial Analysis

After decompression, we get an Android Virtual Device (AVD) directory structure:

└─$ tar -tvf chall_dist
drwxrwxr-x em07robot/em07robot        0 2025-07-10 17:16 chall.avd/
-rw-r--r-- em07robot/em07robot 69206016 2025-07-10 17:16 chall.avd/cache.img
-rw-r--r-- em07robot/em07robot  1966149 2025-07-10 17:16 chall.avd/encryptionkey.img.qcow2
-rw------- em07robot/em07robot        0 2025-07-10 17:16 chall.avd/bootcompleted.ini
-rw-rw-r-- em07robot/em07robot     1245 2025-07-10 17:16 chall.avd/config.ini
-rw-rw-r-- em07robot/em07robot       18 2025-07-10 17:16 chall.avd/quickbootChoice.ini
-rw-rw-r-- em07robot/em07robot     4227 2025-07-10 17:16 chall.avd/hardware-qemu.ini
drwxr--r-- em07robot/em07robot        0 2025-07-10 17:16 chall.avd/snapshots/
<..SNIP..>
-rw-rw-r-- em07robot/em07robot       116 2025-07-10 17:16 chall.ini
-rw-rw-r-- em07robot/em07robot  939493689 2025-07-10 17:10 chall.zst.bk
-rw-rw-r-- em07robot/em07robot        76 2025-07-10 17:13 chal

This appears to be an Android emulator reverse engineering challenge. The first step is to set up and run the emulator using the Android Command Line Tools.

Nim Yong Un

- 8 mins read

WorldWideCTF 2025: rev/Nim Yong Un

Challenge Description

Our agents captured some North Korean military software. Your task: find the correct launch code!

Approach & Solution

What We’re Dealing With

I got my hands on this Windows PE binary that was asking for a 42-character flag. Right off the bat, I could tell this wasn’t going to be your typical reverse engineering challenge when I threw some random input at it.

bilingual

- 20 mins read

DownUnderCTF 2025: rev/bilingual

image

Challenge Description

Two languages are better than one!

Regards, FozzieBear (cybears)

Approach & Solution

We are given this script bilingual.py:

DATA = "eNrtfQt8k0XW96RNei8p0mBBxIDBFhAoTXUrpZp........."
import argparse, base64, ctypes, zlib, pathlib, sys
PASSWORD = "cheese"
FLAG = "jqsD0um75+TyJR3z0GbHwBQ+PLIdSJ+rojVscEL4IYkCOZ6+a5H1duhcq+Ub9Oa+ZWKuL703"
KEY = "68592cb91784620be98eca41f825260c"
HELPER = None

def decrypt_flag(password):
    A = "utf-8"
    flag = bytearray(base64.b64decode(FLAG))
    buffer = (ctypes.c_byte * len(flag)).from_buffer(flag)
    key = ctypes.create_string_buffer(password.encode(A))
    result = get_helper().Decrypt(key, len(key) - 1, buffer, len(buffer))
    return flag.decode(A)

def get_helper():
    global HELPER
    if HELPER:
        return HELPER
    data = globals().get("DATA")
    if data:
        dll_path = pathlib.Path(__file__).parent / "hello.bin"
        if not dll_path.is_file():
            with open(dll_path, "wb") as dll_file:
                dll_file.write(zlib.decompress(base64.b64decode(data)))
        HELPER = ctypes.cdll.LoadLibrary(dll_path)
    else:
        0
    return HELPER

def check_three(password):
    return check_ex(password, "Check3")

def check_four(password):
    return check_ex(password, "Check4")

def check_ex(password, func):
    GetIntCallbackFn = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_wchar_p)
    class CallbackTable(ctypes.Structure):
        _fields_ = [("E", GetIntCallbackFn)]
    @GetIntCallbackFn
    def eval_int(v):
        return int(eval(v))
    table = CallbackTable(E=eval_int)
    helper = get_helper()
    helper[func].argtypes = [ctypes.POINTER(CallbackTable)]
    helper[func].restype = ctypes.c_int
    return helper[func](ctypes.byref(table))

def check_two(password):
    @ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
    def callback(i):
        return ord(password[i - 3]) + 3
    return get_helper().Check2(callback)

def check_one(password):
    if len(password) != 12:
        return False
    return get_helper().Check1(password) != 0

def check_password(password):
    global PASSWORD
    PASSWORD = password
    checks = [check_one, check_two, check_three, check_four]
    result = True
    for check in checks:
        result = result and check(password)
    return result

def main():
    parser = argparse.ArgumentParser(description="CTF Challenge")
    parser.add_argument("password", help="Enter the password")
    args = parser.parse_args()
    if check_password(args.password):
        flag = decrypt_flag(args.password)
        print("Correct! The flag is DUCTF{%s}" % flag)
        return 0
    else:
        print("That is not correct")
        return 1

if __name__ == "__main__":
    sys.exit(main())

When running this on Linux, I encountered an error due to an invalid ELF header. This suggests that hello.bin is not a native Linux binary, but rather a Windows DLL or some other non-ELF format. The script attempts to load it using ctypes.cdll.LoadLibrary, which confirms it’s expecting a shared library (DLL) to call functions . This behavior is evident in the get_helper function.

Neon_Deceit

- 11 mins read

Neon Deceit

image

15 solves | Reverse

Challenge Description

In the neon-lit underbelly of the city, even your tools are programmed to betray you. Trust nothing… the lies are embedded in the code.


Initial Analysis

Starting with the binary from R3CTF, I ran it to see what happens:

➜  neon_deceit ./neon_deceit 
hello world
➜  neon_deceit 

That’s weird—a simple “hello world” program that’s 400 KB? Something’s definitely not right here.

➜  neon_deceit ls -l neon_deceit
-rwxrwxrwx 1 user user 407640 Jul  4 15:40 neon_deceit
➜  neon_deceit 

A basic hello world program shouldn’t be nearly half a megabyte. Time to dig into the decompilation and see what’s really going on.

Dna

- 30 mins read

smileyCTF2025 :

rev/DNA

image

Description

deoxy ribo nucleic acid deoxy meaning without oxygen ribo meaning the 5-carbon sugar backbone nucleic meaning of the nucleus acid meaning proton donor

Solution

Initial Recon

We start by looking at the challenge directory:

➜  dna ls
main.cpython-310.pyc  vm.dna

We can see that this is a virtual machine challenge, and the Python code has been compiled with Python 3.10 into a .pyc file ,Before diving into the challenge, let’s take a moment to understand what a virtual machine (VM) is.

brainrot

- 3 mins read

tamu2025 - rev Challenge: brainrot

Description

This challenge involves reverse engineering a custom “brain” simulation to extract a flag. The brain operates on a set of neurons and performs transformations using a combination of hashing, rotation, and matrix operations. The goal is to deduce the input that produces the required outputs.

Solution

The solution involves implementing the brain simulation in Python and using the Z3 solver to reverse the transformations. Below is the code and explanation:

otp

- 2 mins read

tamu2025 - rev Challenge: otp

Description

This challenge involves reverse engineering and cryptographic analysis to extract keys and decrypt an encrypted flag. The solution uses GDB scripting to automate the extraction of keys from memory frames and applies XOR decryption to retrieve the original flag.

Solution

GDB Script: ExtractKeys

The following GDB script automates the extraction of keys from memory frames:

import gdb
import re

class ExtractKeys(gdb.Command):
    def __init__(self):
        super(ExtractKeys, self).__init__("extract_keys", gdb.COMMAND_USER)

    def parse_gdb_line(self, line):
        """
        Extracts byte values from a single line of GDB output.
        Example input: "0x7ffe603d0100: 0x45 0x65 0x41 0x15 0x57 0xc0 0xdb 0xda"
        Returns: ["45", "65", "41", "15", "57", "c0", "db", "da"]
        """
        match = re.search(r":\s+((?:0x[0-9a-f]{2}\s*)+)", line)
        if match:
            return re.findall(r"0x([0-9a-f]{2})", match.group(1))
        return []

    def parse_gdb_output(self, gdb_output):
        """
        Parses the entire GDB output to extract key bytes.
        Returns a single hex string.
        """
        key_bytes = []
        for line in gdb_output.split("\n"):
            key_bytes.extend(self.parse_gdb_line(line))
        return "".join(key_bytes)

    def invoke(self, arg, from_tty):
        start_frame, end_frame = 4, 1003
        with open("keys.txt", "w") as key_file:
            for frame_id in range(start_frame, end_frame + 1):
                try:
                    gdb.execute(f"frame {frame_id}", to_string=True)
                    key_output = gdb.execute(f"x/59bx key", to_string=True)
                    key_hex_string = self.parse_gdb_output(key_output)
                    if key_hex_string:
                        key_file.write(key_hex_string + "\n")
                except gdb.error:
                    print(f"[-] Skipping frame {frame_id} (No key found)")
                    continue
        print("[✔] All keys extracted to keys.txt")

ExtractKeys()

Decryption Process

The decryption process involves loading the encrypted flag and the extracted keys, then applying XOR decryption in reverse order:

xorox

- 1 min read

tamu2025 - rev Challenge: xorox

Description

This challenge involves reverse engineering a binary to determine the required input that produces the desired output. The solution involves XOR operations and understanding the binary’s constants and register values.

Solution

The following Python script demonstrates the solution:

import struct

# Constants from the binary
constant = [
    0x2a8c7f3acdf36ffb,  # First 8 bytes of the constant
    0x8cc2eef32660caaa,  # Next 8 bytes
    0xefa1fd61d7a3b592,  # Next 8 bytes
    0xa9ddc2d22a90025e   # Last 8 bytes
]

# YMM7 register values from GDB (converted to 4x 64-bit integers)
ymm7 = [
    0x1eca2043bfc01980,
    0xd386a3ba753fbe9f,
    0x87d5cc1688d185ea,
    0xd4aebbb741cf3001
]

def qwords_to_bytes(qwords):
    return b''.join(struct.pack('<Q', q) for q in qwords)

constant_bytes = qwords_to_bytes(constant)
ymm7_bytes = qwords_to_bytes(ymm7)

required_input = bytes(a ^ b for a, b in zip(constant_bytes, ymm7_bytes))

flag = b"gigem" + required_input

print("Raw bytes:", flag)

# Try to decode as ASCII (some bytes may not be printable)
try:
    print("ASCII:", flag.decode('ascii'))
except UnicodeDecodeError:
    print("Contains non-ASCII bytes")

Flag

The flag for this challenge is:

Rev/gateway

- 8 mins read

FIRST CHALLENGE: Gateway

Challenge Description

Malakar has ensnared you with a dark spell, banishing you to the depths of the Nether world. Escape hinges on recalling the ancient enchantments of your forefathers. Wield their arcane power to shatter the Aether gateways and reclaim your freedom. Only the correct incantation—32 bytes of mystical precision—will unlock the path back to the mortal realm. Can you decipher the spell and blast through the barriers of this infernal trap?

Rev/imagepro

- 2 mins read

Impossimaze Writeup

Challenge Overview

  • Name: Impossimaze
  • Difficulty: Medium
  • Category: Reverse Engineering / Exploitation

Initial Analysis

Challenge Description

The challenge presents a seemingly simple ncurses-based program where the player navigates through a maze-like interface. The goal is to uncover a hidden flag by understanding the program’s intricate mechanics.

Key Observations

  1. The program uses ncurses library for terminal-based interaction
  2. Allows movement using arrow keys
  3. Displays terminal dimensions
  4. Contains a specific hidden mechanism when terminal is exactly 13x37

Reverse Engineering Approach

Code Breakdown

The main function reveals several interesting characteristics: