Time: 2025.12.02-04 (onsite)
My first time in Riyadh,KSA 🇸🇦 it's also my first time abroad, and surprisingly, it’s for a CTF. Life has a strange way of unfolding. Grateful to God . Only God and I know what it took to reach this moment. Literally packed my bags, thought I'm a world traveler now. Arrived in Riyadh, Saudi Arabia with team ===___=== (yeah thats really my team name haha..).
Three days of sleepless nights, lots of soft drinks, and questionable life choices later we somehow managed to get 12th place . Not too shabby for someone who barely knows how to use Google Maps. GG to my teammates who actually know what they're doing.But the sad part, After receiving my visa, I asked for support in my country,companies and from my university, but I was turned away. No one helped me not even with a single penny. Some of them literally laughed at me. I cried many times during this journey, carrying nothing but faith and determination. There were moments when I nearly gave up, but my friends gave me hope: Sayed, Sadif, Ajwad, and Adeeb. So firstly, I want to thank Uzbekistan 🇺🇿 and Turan Security Company. I will never forget you for standing by me when no one else did.


day1
Que?
Note: This one was the only problem that i'm able to solve with my friend @chatgpt throughout this event , After returned into hotel I was looking at this challenge and at first it felt complex—lots of math, custom crypto, big primes. But once I slowed down, the mistake jumped out. Both secrets (k and r) are multiplied by the same small 24-bit mask. When the protocol computes C1 = k · r⁻¹ mod p, that mask cancels out completely. So the “128-bit secret” isn’t really 128 bits anymore. The bigger facepalm? Aaron encrypts the flag before the protocol even finishes. No authentication. No confirmation. Just straight AES-ECB with a deterministic key and a known flag format. That makes offline guessing trivial.
Tampering Detection System
AES-GCM

Some code by me for a better understanding. (I prefer $E_0$ while it’s $J_0$ in the figure.)
1 | from sage.all import GF |
GCM Routine
As always, we need to recover (H, E0) first. We have:
tag = GHASH(H, C) + E0tag2 - tag1 = GHASH(H, C2) - GHASH(H, C1) = GHASH(H, xor(C2, C1))
Then we can find the roots of the polynomial on GF, which includes H. We don’t know (ct1, ct2) but for AES-GCM we have xor(ct1, ct2) = xor(pt1, pt2), and xor(C1, C2) = 0_128||xor(pt1, pt2)||0_64||0_64.
With H, we can solve E0 = tag_flag - GHASH(H, C_flag), thus enabling us to forge a valid tag for any ciphertext.
AAD Construction
In the second part we’re given a decryption oracle, requiring truncating length and aad. Our target is to recover ct and decrypt the flag.
One may notice that each ct corresponds to a unique aad, which can be solved from a linear equation on GF. Consider we have recovered ct[:i], we can bruteforce ct[:i+1] = ct[:i] + ch and find the real ch by checking the corresponding aad, thus recovering ct byte by byte.
1 | z0 = GF(2)["z0"].gen() |
With a long enough ct, we can decrypt our flag now.
day2
doloRSitAmet
All about Sample Strategy
Clearly, 4 equations are not enough to solve the whole system, but we can ‘glue’ some variables by setting certain seeds. For instance we can fix 1,2 showing up together, which leads to a new variable.
At first I tried to split them into 3 parts like [(0), (1,2,3), (4)].
1 | Seed 15901 gives [0, 1, 2, 3, 4] |
With 4 equations we can obtain a linear equation of the form $a \cdot x + b \cdot y + c = 0 \pmod{n}$. It’s indeed solvable with LLL, but we need some (too much) luck to get 2 small variables. This costed me a lot of time.
Then I realized if we glue 4 variables together, there’s only 2 variables left in 2 equations, and we can obtain a univariate polynomial. Just do it twice:
1 | Seed 15901 gives [0, 1, 2, 3, 4] |
With 2 polynomials, we can solve the first variable by gcd. This gives us another linear equation with 2 variables (the first sentence and the flag sentence) but much smaller (there’s only 32 bytes unknown in the flag sentence), so we just LLL and get the flag.
limeade
A juicy challenge, for real.
Structure Analysis
To put it briefly, Lime includes permutation (Roll) and substitution (Squeeze) funcion. And Jucier essentially constructs a 2-round SPN cipher with each round using the same key:
- permuted (lime[0]) key addition
- 16 times substitution (lime[1]) and permutation (lime[2])
- permuted (lime[3]) key addition
- 16 times substitution (lime[4]) and permutation (lime[5])
- permuted (lime[6]) key addition
Boomerang Style Attack
We are provided Encryption/Decryption Oracle with additional fault injection on the key. But first let’s think about what we can do with xor fault injection.
An interesting observation is that $P \oplus K = (P \oplus 1) \oplus (K \oplus 1)$, so if we inject 1 bit fault on both the key and the plaintext at the same position, it shows no difference after the first round of encryption. But with the second key addition, the fault will be injected and the final ciphertext goes completely different.
1 | Encryption |
Why don’t we do it again? This time let’s inject on both the key and the ciphertext, and do the decryption.
1 | = P0+perm[0](1) → P0'' |
This means a lot: now we have a pair of $(P_0, P_0’’)$ which only have 1 bit difference after the first round of encryption. Let take another step forward…
1 | Encryption |
Finally, we manage to obtain a ciphertext with 1 bit difference compared to the original one. Based on this we can construct a distinguisher with our additional fault injection:
- Encrypt a random plaintext with the original key.
- Inject 1 bit additional fault on the i-th bit of the key.
- Obtain the magic ciphertext with 1 bit fault injected.
If success, the additional fault on the i-th bit equals to a 1 bit xor fault. For most parts of the key, this implies the i-th bit is 0 (with high probability). Injection on the MSB may always cause 1 bit fault so we need to bruteforce it instead.
(*)ella
No need for any paper, it’s just parity statistics.
Sadly i couldn't able to solve this chall .
day3
NoisyChannel
Our Solution
Well this problem has the less solution among the problems i have able to solved , I got 3rd blood in it just after the r3kapig. So It turns out that randbytes is implemented with getrandbits, with MT19937 as the prng core.
1 | # random.py |
Our target is to find a kernel vector that can be used for any random seeds. If this is even possible, it implies we are dealing with a non-full-rank linear system. Therefore, we can randomly generate sets of data, concatenate them, and then extract a non-trivial vector from its left kernel space as the answer.
(*)LCC LLC
Here comes a McEliece challenge.
Decode Failure Oracle
Core functions below.
The server uses McEliece to generate access tokens, and our target is to forge a valid token with admin privilege.
1 | class McEliece(BinaryGoppaCode): |
There’s not so much we can do actually, since we’re only provided with some partly controllable tokens (even without the public key). So sadly i couldn't able to solve this one :") oh i also solved 2 forensics and 1 rev challenge as well..
In Saudi Arabia
Still can’t believe we did it, my teammates are reewnat, tanskit from uzbekistan and kruknight from Egypt. Got some luck too I guess .
![]() |
|---|
Met so many old and new friends here, enjoyed.meet with some dream teams *0xA, r3kapig, bios, hacksec etc
But 3-day competition is really tiring and we didn't have so much or energy to travel around. Will do more exploitation in the city if I come next year.




