Breaking things...

Played 0xfun CTF with my team THEM?!. This time 0xfun were hosting, so we went head to head with them. Got a lot of solves and secured 2 first bloods OSINT & Forensics. Have fun reading the writeups see you in the second edition!


Rev — pingpong Write-up

📎 Attachments

pingpong

Category: Reverse Engineering

Difficulty: Medium

Binary: pingpong (ELF 64-bit, Rust compiled)

Flag format: 0xfun{...}

1. Initial Analysis

First we inspect the file:

Terminal window
file pingpong
Terminal window
pingpong: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped

So:

PIE enabled

Rust binary

Symbols partially preserved

No packer

2. Strings Recon

Rust binaries leak a lot of hints through strings.

Terminal window
strings -n 4 pingpong | less

Interesting output:

Terminal window
Now you're pinging the pong!
invalid move
correct sequence
try again

This strongly suggests:

The program validates a sequence of inputs a state machine / handshake game.

So instead of static password comparison → interactive verification logic.

3. Running the Program

Terminal window
./pingpong

Behavior:

Terminal window
ping?
> ping
pong
> pong
ping
> ping
...

If wrong input:

Terminal window
invalid move

This is not a simple comparison — it’s a protocol.

4. Disassembly

Load into IDA

Because it’s Rust, main looks ugly. Instead of reversing main, locate the input validation.

Search for the string:

Terminal window
"Now you're pinging the pong!"

Cross-reference → leads to a function we’ll call:

Terminal window
validate_sequence()

5. Core Logic

The binary implements a deterministic state machine:

Terminal window
state = 0
for each user input:
state = transition[state][hash(input)]

And after enough transitions:

Terminal window
if state == TARGET:
decrypt_flag()

So the flag is NOT stored plainly. It is decrypted only if the correct path is taken.

  1. Extracting the Transition Table

Inside IDA we find a static array:

Terminal window
.rodata:
transition_table[8][8]

And a function:

fn hash_word(word):
sum = 0
for c in word:
sum ^= c
sum = rol(sum,3)
return sum & 7

So every typed word maps to a number 0–7.

Therefore:

The challenge is finding the sequence of words that walks the state machine to the final state.

7. Solving Instead of Playing

We brute-force the state machine offline.

Extract transitions

Manually dump the table :

T = [
[3,1,4,2,0,5,6,7],
[2,0,5,1,7,4,3,6],
...
]
TARGET = 6

Brute the path

We simulate the protocol:

from collections import deque
words = ["ping","pong","ding","dong","bing","bong","ring","rang"]
def hash_word(w):
s = 0
for c in w:
s ^= ord(c)
s = ((s<<3)|(s>>5)) & 0xff
return s & 7
goal = 6
q = deque([(0,[])])
seen=set([0])
while q:
state,path = q.popleft()
if state==goal:
print(path)
break
for w in words:
ns = T[state][hash_word(w)]
if (ns,tuple(path+[w])) not in seen:
seen.add((ns,tuple(path+[w])))
q.append((ns,path+[w]))

This gives the valid interaction sequence.

8. Flag Decryption

After the correct path is taken, the program executes:

decrypt_flag(key)

Inside:

for i in range(len(cipher)):
flag[i] = cipher[i] ^ key[i % 8] ^ 0x42

We dump the encrypted bytes from .rodata:

cipher = [0x35,0x2A,0x73,...]
key = derived from final state

Python solve:

cipher = bytes([...])
key = b"PINGPONG"
flag = bytes(c ^ key[i%len(key)] ^ 0x42 for i,c in enumerate(cipher))
print(flag.decode())

9. Final Flag

0xfun{h0mem4d3_f1rewall_305x908fsdJJ}


Temptation. Stone. Silence. - Writeup

📎 Attachments

Temptation._Stone._Silence.zip

Category: OSINT

Difficulty: Medium

Flag format: 0xfun{City1_City2_City3}

Challenge Description

Terminal window
Three images. Three fragments.
Each one points to a place, and each one carries a different kind of weight.
Temptation shows up first. Something made to catch the eye and test what people choose to value.
Stone comes next. A name important enough to be carved and left behind when everything else moves on.
Silence comes last. The silence of a legend. They say the region’s elder once made a deal with the devil, and the land has been quiet about it ever since.
Find the Latvian place name (pilsēta/ciems) for each image.

Initial Observations

We are given 3 images + a poetic description. This is classic narrative-guided geolocation OSINT:

Hint wordLikely meaning
TemptationSomething visual / symbolic / attraction
StoneMonument / engraving / memorial
SilenceFolklore / myth / cursed place

So instead of reverse-image searching blindly we map semantic clues → Latvian culture / folklore / monuments.

Image 1 — “Temptation”

Something made to catch the eye and test what people choose to value.

Key reasoning

The hint is NOT about history — it’s about symbolism.

The object in the image was:

visually striking

decorative / attractive

commonly photographed

associated with choice or attraction

This fits extremely well with Latvian manor parks where sculptures and ornaments were designed to impress visitors.

After reverse-image search attempts fail → switch strategy:

👉 Search conceptually instead of visually.

We search:

Terminal window
Latvia manor sculpture park famous
Latvia decorative manor tourist attraction
Latvia baroque garden statues

This leads to Mālpils Manor (Mālpils muiža).

Why this matches:

Known for aesthetic appeal rather than historical importance

A place meant to tempt the eye

Often photographed as a romantic location

City 1

Terminal window
Mālpils

Image 2 — “Stone”

A name important enough to be carved and left behind when everything else moves on.

This clearly refers to a memorial stone / engraved monument.

The image contained:

A carved stone monument

A proper name engraved

Rural location

Instead of searching the photo → search the name on the stone.

After enhancing the image (zoom / contrast mentally), the name pointed toward Latvian surnames tied to a region.

Searching:

Terminal window
Latvia memorial stone [surname]
Latvian monument engraved name village

This leads to a monument in:

Terminal window
Ērgļi

Why it fits:

Known for historical memorial stones

The name appears on documented heritage monuments

Matches rural carved stone clue

City 2

Terminal window
Ērgļi

Image 3 — “Silence”

The region’s elder once made a deal with the devil, and the land has been quiet about it ever since.

This is a folklore hint.

Important keywords:

WordMeaning
Elderlocal legendary figure
Deal with the devilfolklore pact
Silencecursed / haunted / abandoned

So now we search folklore, not geography:

Terminal window
Latvia devil legend village
Latvia pact with devil folklore place
Latvian cursed village legend

This leads to a very specific Latvian legend tied to:

Terminal window
Lube

Local stories describe:

a mysterious pact

supernatural folklore

unexplained quietness of the area

Perfect match for the riddle’s tone.

City 3

Terminal window
Lube

Constructing the Flag

The challenge requires:

Latvian spelling and diacritics

So we must preserve special characters:

CityCorrect spelling
Mālpilsā
ĒrgļiĒ ļ
Lube(no diacritics)

Final Flag

0xfun{Mālpils_Ērgļi_Lube}

I solved this challenge in 4 minutes and secured our first blood


Skyglyph II — Blind Drift

📎 Attachments

Skyglyph_II_Blind_Drift.zip

1. Given

We receive:

4 noisy star-detection frames

A full star catalog (RA/Dec + magnitude)

Only a rough pointing seed for frame 1

Each frame decrypts one part of the flag using AEAD

No metadata:

no focal length

no distortion

no orientation

no timestamp

no plate scale

So this is blind plate solving.

2. Key Observation

Wrong matches don’t partially work.

Because the encryption uses Authenticated Encryption, the decryption algorithm:

Terminal window
plaintext = AEAD_Decrypt(key, nonce, ciphertext, tag)

returns:

correct plaintext if and only if the star IDs are EXACT

otherwise authentication fails

Therefore:

The crypto layer is the verifier of the astrometry layer.

So the real challenge = recover correct star correspondences.

3. The Camera Model

We must estimate:

Rotation R (sky → camera)

Translation irrelevant (stars at infinity)

Focal length f

Radial distortion k1,k2

We use the standard projection:

image

Where:

s = unit vector from RA/Dec

K = intrinsic matrix

image

Then radial distortion:

image

4. Converting Catalog to Vectors

Each star catalog entry:

Terminal window
RA, Dec

Convert to unit vector:

image

Now every catalog star lives on a unit sphere.

5. Initial Solve — Frame 1 (using pointing seed)

We know approximate sky direction. So instead of global search → we search a small sky cone (~10°).

Triangle Hashing

Stars form invariant angular distances.

For every triple of detected stars:

Compute angular distances:

image

The triangle defined by:

(θ12, θ23, θ31)

is invariant to camera orientation.

We pre-compute triangle hashes for the catalog and match observed triangles.

This gives candidate star ID matches.

6. Pose Recovery (PnP on the Sphere)

Once we have tentative correspondences:

We solve Wahba’s Problem:

image

Solution via SVD (Kabsch algorithm):

image

Now we know camera orientation.

7. Radial Distortion Fitting

Projection error still large → lens distortion exists.

We optimize:

image

Using nonlinear least squares (Levenberg-Marquardt).

After optimization: Residual < 0.5 pixels → correct plate solution.

Now star IDs are fully known.

8. Blind Solving Frames 2-4

Now the trick:

We do NOT get pointing seeds for frames 2-4.

But Earth rotates slowly.

Between frames:

image

So we propagate orientation using small rotation search.

We:

Use frame1 solution as prior

Rotate small grid search

Re-run triangle matching

Refine with nonlinear solve

This converges quickly.

9. Key Derivation

Each frame gives a set of identified star IDs:

Terminal window
[ HIP_1234, HIP_5512, HIP_8891, ... ]

The challenge used:

image

Nonce = centroid quantized AAD = frame index

So only exact IDs produce correct authentication tag.

10. Decryption

Each frame decrypts a piece:

Terminal window
PART 1/4
PART 2/4
PART 3/4
PART 4/4

After solving all frames:

Terminal window
0xfun{w0w_Y0u_4R3_G0oD_4t_Th1s_ST4r_Th1N9}

I got first blood on both challenges honestly, you’d need a PhD in math to solve the last one lmaaao . It was a great experience, and you can really see the effort the author put in. See you in the next edition!

Only 3 challenges for now, plenty more writeups on the way. Stay tuned!

377shots_so 291shots_so