C2C CTF 2026 Quals
Writeup for all challenge I solve in the competition.
Forensics
Tattletale
Apparently I have just suspected that this
serizawabinary is a malware .. I was just so convinced that a friend of mine who was super inactive suddenly goes online today and tell me that this binary will help me to boost my Linux performance.Now that I realized something's wrong.
Note: This is a reverse engineering and forensic combined theme challenge. Don't worry, the malware is not destructive, not like the other challenge. Once you realized what does the malware do, you'll know how the other 2 files are correlated. Enjoy warming up with this easy one!
Author: aseng
For this challenge, we're provided with three files.
cron.aseng, which typically
/dev/input/event0records.serizawa, packed ELF binary.
and there is one file whatisthis.enc which I assume that's where the flag stored.
First, we should examine what serizawa binary is, so we can decompile it. Use Detect It Easy, we can know that the binary is packed with PyInstaller.

So, we can use PyInstxtractor to extract the original source code. After that, we got the serizawa.pyc that we can decompile use PyLingual here. After we got the original source code, now we kan know that is a keylogger and we can parse the cron.aseng so it can be readable.

Although its garbaged, but we can see the message. the flag encrypted in the whatisthis.enc, and its an .env file. The password used is pass:4_g00d_fr13nD_in_n33D . Then, we can decrypt it with openssl.

Its look like octal dump, because lots of 6 digit octal words. So, I convert that to be a readable string with:
then we can find the flag inside that env.

C2C{it_is_just_4_very_s1mpl3_l1nuX_k3ylogger_xixixi_haiyaaaaa_ez}
Log
My website has been hacked. Please help me answer the provided questions using the available logs!
Author: daffainfo
For this challenge, we are given a log from the WordPress service, the access.log and error.log. Also, to get the flag, we must answer several question.
What is the Victim's IP address?
To get the answer, we can look at the access.log at the first request. We can see that there is only 2 IP. Their local, 127.0.0.1, and possibly their public IP, 182.8.97.244 . We can confirm it by submit the IP and got the correct one.

What is the Attacker's IP address?
And when we scrolled down, we can also see other IP, 219.75.27.16 . Which the only one IP except the Victim IP address. So the it also the answer for Question 2.

How many login attempts were made?
The attacker seems to attempt some logins in the Wordpress.

So the answer for the next question is 5

Which plugin was affected?
Scrolled down little bit, we can also spot the plugin affected

What is the CVE ID?
Search on the internet, we can found the CVE related to the attack


Which tool and version were used to exploit the CVE?
And based on the search above, we can know that it cause an Unauthenticated SQL Injection. As we know, the popular SQL Injection tool is sqlmap, and we can search it on the log to get the detail version.


What is the email address obtained by the attacker?
And to know the email address, we can extract the injection from the log to recover a email pattern.


What is the password hash obtained by the attacker?
With some modified with previous script, we could also extract the password


When did the attacker successfully log in?
Back to the log, we can search the POST method to wp-login and get the timestamp


C2C{7H15_15_V3rY_345Y_5556747f102c}
React
One month ago, there was a massive attack on one of the popular JS frameworks, and it seems like my website was affected as well...
You can use Wireshark to analyze the PCAP file.
author: daffainfo
In this challenge, we will provide a packet capture. Also, we need to answer several questions to obtain the flag. For packet capture challenges, I usually use apackets to help me visualize the captured network.
What is the IP address of the attacker?

From that we know that the related IP from the incident is 192.168.56.103 and 192.168.56.103 .

And from that information, its strongly suggest that the attacker IP is 192.168.56.104

What is the IP address of the victim?
From previous question, we can know the vistim IP is 192.168.56.103

What tools did the attacker use first? (Lowercase)
Basically, the common tools for the attacker in first time is nmap, but we need to prove it, right?

Based on what we found, it scans many ports. This behavior is consistent with Nmap’s default SYN scan (-sS).

Which CVE ID was exploited by the attacker?
Based on the description, we know that this is a popular React CVE.

And from that, we know that its Next js. And some popular CVE from that is the latest who can get RCE, but we must confirm that.


We can confirm that this is the popular CVE called React2Shell.

What was the first command executed by the attacker?
From previous step, we know the first command executed is echo 123

Which Command and Control (C2) framework is being used?
To answer this question, we can back to the apackets result and find interesting requests


And from the article I found, It strongly suggests that the C2 is Trevor C2.

What password was used to interact with the C2 server?
I think this is the part which tricked me. Like there is no strong evidence to find where the attacker plant the client.py to connect to the C2 server. Find the TLS protocol, I think its a Weak RSA key, like this article. But calculating the mod, the factor is big and not possible to be cracked. So I move to other tools called RsaCtfTool.

And we find the key! So lets decrypt it and we can find the a.py

But it is an obfuscated code, so we must deobfuscate it because we know the obfuscation method
We can deobfuscate it with this script

And we successfuly get the source code and solve the question.

What was the first file accessed by the attacker?
Know the password, and based on the previous article in question 6, we can know the hidden command is inside the oldcss= and the response will send back to the C2 server use /images?guid= . So we can extract all the guid= content and oldcss= .


We can save it and use python script to automate the decryption
And based on the result, the first file accessed is /etc/passwd


What command or method was used by the attacker to establish persistence on the system?
From the previous screenshoot, we also know the answer.

Which MITRE ATT&CK technique corresponds to the persistence method used?
We can search based on what we found


C2C{r34C725h3Ll_f0r_7H3_W1n_e59d9f62dfa2}
FixClicked
One of my friend's device has been compromised due to clickfix campaign. Can you investigate it further? The traffic is originally can also be retrieved from
https://any.run/report/b55419bc7529bf574b4ba57b38c501a8ce2b0cd2b3ea66d19750a7d8e0c1796f/a5abf06f-112f-4072-9535-01006c3f955c.WARNING: YOU ARE ABOUT TO ANALYZE A REAL MALWARE. THIS MALWARE HAS HARMFUL CAPABILITY SO DON'T FORGET TO ANALYZE IT INSIDE THE VM. AUTHOR IS NOT RESPONSIBLE WHETHER IF THERE ARE ANY DESTRUCTIVE ACTION PERFORMED BY THIS MALWARE.
author: aseng
We provide any run reports that can be analyzed to answer several questions and determine whether to raise a flag.
Judging from the traffic, it looks like the victim copied the powershell command to their terminal. What's the domain that preserve the malicious powershell script?
From the given report, we can click Full Analysis to see the captured processes and windows. Looking at the HTTP request, we can see that the malicious powershell.exe is connected to http://trusteddevice.info. This confirms our suspicions.

It looks like the malicious powershell script is run silently without showing the console windows. What's the function name that responsible to do it?
We can click the process and see more information inside it.

And based on that information, we can know the function responsible is ThsBFKNQuLtnyKgwB

The malicious powershell script also does perform a self-decryption from an encrypted buffer from $OYBwgNyGIBxTBIKUL variable. What are the key and iv used during that process respectively? Wrap both components in hexadecimal format and underscore!
We can see the decryption process for the key and iv is by decode the base64 and xor-ing them with value 210.


Still referring with the previous question, what's the SHA256 of the decrypted buffer? Please do answer in hexadecimal format.
In the full script that we extracted, we can change the execution to save the file.


Diving to analyze the decrypted buffer which turns out to be an obfuscated executable, there can be spotted an uncompiled CSharp code which then later to be used for a potential process injection. What's the arbitrary function's name that responsible to perform this operation?
Back to the any run, we can trace the process captured and found the uncompiled CSharp

The function that corresponds to the questions is AzeroFloid.

What's the built-in main target process path executable that the code is trying to inject to? Provide the full path!
Back to the any run, we can see that the other process created with the parent process is powershell is the



What's the resource name (in the executable) that hold the encrypted buffer to be injected into that process?
Write the C# code to get the resource inside the binary we already save from previous powershell script.

And we now know the resource name

The real buffer to be injected seems to be a Donut Shellcode. What's the SHA256 of the donut shellcode? Answer in a hexadecimal format.
In the uncompiled CSharp we found, we can see the process injection is like
To know the actual code, we can use ILSpy or DNSpy. Because im use MacOS, im using ILSpy which is compatible with this. And I found some interesting code.
Then, we can see the whole decryption process from the resource to be a shellcode. I create a python script to implement that and get the shellcode.


The malware also created a windows shortcut file. Please provide only the filename of this shortcut without full path!
Back to the any run, we can know the shortcut file created is App.url


What's the full path of the file that is going to be executed from the shortcut? This file's attribute is set to hidden.
Clicked the detail from the previous provided screenshoot, we can know the file that is going to be executed from the shortcut


C2C{W31l_d0Ne_YoU_SucC3sSfUL1y_DE7on4Te_oNE_01_7He_ClICk1!x_m4lWAR3_CaMPAi9n_FrOm_R34l_AP7_6R0up_AsenG15hERE}
Web
corp-mail
Rumor said that my office's internal email system was breached somewhere... must've been the wind.
author: lordrukie x beluga
Solved by Claude Sonnet 4.5
The app is a fake corporate email system built with Flask. Somewhere in the database is an email from admin to mike.wilson with the subject "Confidential: System Credentials" — and the flag is in the body.
To read it, we need admin access. All /admin routes are blocked by a proxy. Here's how I get in anyway.
The attack chain is to Register -> Login -> Leak JWT via SSTI -> Forge admin token -> Bypass HAProxy -> Flag.

C2C{f0rm4t_str1ng_l34k5_4nd_n0rm4l1z4t10n_3dcd0b62728c}
Reverse
Bunaken
Can you help me to recover the flag?
author: vidner
Helped with ChatGPT 5.2
We're given a executable file and an encrypted flag.

When I tried to string the binary, it gave me a "Bun" string, indicating that it is a "Bun" binary. So, I decompile it use Bun Decompile and get the source code.
From that, key material is passed like this pattern:
Buffer.from(s(373, "rG]G"))
So, we can evaluate it to reveal the real string

In the code, there is a normalize step:
if key length is not 16/24/32 bytes, hash with SHA-256
take first 16 bytes
So actual AES key is:
SHA256("sulawesi")[:16]
Then, from the source code, we can know the encryption process is by:
Read flag text
Compress with zstd
Encrypt using AES-CBC with random 16-byte IV
Save IV || ciphertext
Base64 encode into flag.txt.bunakencrypted
With all of that information, now we can decrypt the flag.

C2C{BUN_AwKward_ENcryption_compression_obfuscation}
Pwn
ns3
It's not S3, but it's not such a simple server either. Or maybe it is?
author: msfir
Solved by GPT 5.2
This challenge is a small HTTP file server with two dangerous features:
GETcan read files (path,offset,size)PUTcan write files (path,offset)
There is a strict rate limit (10 requests / 60s), so the solve must stay low-noise.
Reading directories normally returns an empty body, so we cannot list files directly. The trick is to patch the running server process through /proc/self/mem (inside one keep-alive connection), then make / return directory entries.
After that:
Parse the directory listing
Find
flag-*Read the flag file

C2C{1INUx_1iL3_sY57eM_lS_QuiT3_MIND_BL0wlnG_l5N't_1T_e788d535e7cc?}
Misc
Jinjail
Pyjail? No, this is JinJail!
author: daffainfo
Solved with Claude Sonnet 4.5
This challenge is basically jinja2 ssti jail with a strict waf.
In app.py, they render my input as template, and they expose numpy into jinja globals. waf blocks a lot of obvious stuff (eval, exec, ctypes, quotes, /, +, -, etc), so normal payloads die.
my payload:
{{numpy.testing.extbuild.os.system(numpy.testing.extbuild.os.sep~numpy.testing.extbuild.sys.copyright[9].join(dict(fix=1,help=1)))}}
how i used it:
i pivoted from numpy.testing.extbuild to get os + sys.
i made dict(fix=1,help=1) so i can get keys fix and help without quotes.
i used sys.copyright[9] as /, then .join(...) to build fix/help.
os.sep ~ ... adds leading /, final command becomes /fix/help.
os.system(...) executes it.
/fix is suid root binary (fix.c). if arg is help, it does setuid(0) and cats /root/flag.txt.

C2C{damnnn_i_love_numpy_4d32b40b19d4}
Crypto
AIC gachapon
The factory must grow!! Originium rolls only
author: azuketto
Solved by ChatGPT 5.2
This challenge is a slot-machine style ASP.NET app. Goal: recover the hidden redeem code, then call /api/redeem to get flag.
GET /api/recent/30gives recent tick framesPOST /api/redeemtakes{ "tickId": long, "code": int }
The app does not return the redeem code directly, but it leaks many RNG outputs in each frame (sampleInts, etc).
Backend uses one long-lived System.Random instance for everything. Each tick consumes RNG in a fixed order, including:
16 times
Next(int.MaxValue)(this is the big leak)then later
Next(RedeemMax)for secret redeem code
Because System.Random is predictable, those leaked values are enough to recover internal state and predict future outputs.
Exploit idea
Pull frames from
/api/recent/30.Use leaked
sampleIntsto reconstructSystem.Randomstate (mod2^31-1, 55-word state, with small rounding noise).Once state is known, compute the RNG output at redeem position for a target tick.
Send
tickId+ predictedcodeto/api/redeem.

C2C{11d7f3bda057}
Blockchain
tge
i dont understand what tge is so all this is very scuffed, but this all hopefully for you to warmup, pls dont be mad
author: hygge
Solved by Gemini 3
Goal
Reach Setup.isSolved() by making tge.userTiers(player) == 3.
Contract Setup
Setupdeploys:TokenandTGETGE supplies: tier1=
15, tier2=35, tier3=50
Player receives exactly
15tokens.enableTge(bool)is public inSetup, so the player can toggle TGE state.
Vulnerability
TGE.upgrade() uses this gate:
require(preTGEBalance[msg.sender][tier] > preTGESupply[tier], "not eligible");
This is intended to compare user snapshot balance vs total snapshot supply. But preTGEBalance is not a real snapshot:
preTGESupplyis snapshotted once when TGE is first turned off.preTGEBalancekeeps increasing whenever_mintis called duringisTgePeriod(even after snapshot).
So after snapshot, a user can mint tier 2/3 during reopened TGE and make preTGEBalance[user][tier] > preTGESupply[tier] true trivially.
Exploit
approve(tge, 15)buy()-> gets tier 1 NFT/slot.enableTge(false)-> triggers one-timepreTGESupplysnapshot.enableTge(true)-> reopens TGE.upgrade(2):burns tier1, mints tier2
mint updates
preTGEBalance[user][2] += 1preTGESupply[2]was snapshotted as0, so1 > 0passes.
upgrade(3):same pattern for tier3 (
preTGESupply[3] == 0)user reaches tier 3, challenge solved.


Convergence
Convergence....
author: chovid99
Solved by Gemini 3
Goal
Make Setup.isSolved() return true.
In this challenge, that means making challenge.ascended() become a non-zero address.
Key Idea
The solver in main.py uses a very direct path:
Register as seeker with
registerSeeker().Build one
truthpayload (same format asagreement):SoulFragment[]bytes32uint32address invoker/binderaddress witness
Put 11 fragments, each with
100 etheressence.Total essence =
1100 ether.Setup.bindPact()allows each fragment up to100 ether, so this passes.
Call
Setup.bindPact(truth)to store the chronicle seal.Call
Challenge.transcend(truth).
Why This Works
transcend() only checks:
sender is a registered seeker,
payload seal exists in
setup.chronicles,invoker == msg.sender,witness == msg.sender,total fragment essence
>= 1000 ether.
It does not require offerDestiny, harvestSouls, or achieveConvergence first. So one valid chronicled payload with enough essence is enough to ascend.
Result
After transcend, ascended is set to player address, and Setup.isSolved() returns true.


Last updated