1164 words
6 minutes
DamCTF - 2025

This was my first CTF with “CTF Academy”, mentored by Shellphish. I’m so happy to have played alongside everyone! We solved 14 out of 18 challenges and finished 21st overall. WP everybody !!

They release the official writeups for every chall… But I just want to write one for memorize. some people die of nostalgia XD.

rev/It’s data, not data#

Challenge detail#

(you may want to do Is it data or data? first)
nc itsdatanotdata.chals.damctf.xyz 39531
attached

Binary analysis#

The program gonna take our input to gen a weird string. Then ask us to make it become “episode3,57min34sec” in 100 step. The program will expect a number from user to execute command.
After reverse we got the command table like this:

NumberExplain
15set “G” at cursor
24set “q” at cursor
57set “W” at cursor
19set “c” at cursor
22set ”;” at cursor
2str[cursor] += 3
3str[cursor] += 3
7set “7” at str[7]
9swap str[cursor] with str[cursor - 1]
8swap str[cursor] with str[cursor + 2]

Everytime you use those command, the step += 1.
There are 2 commands that allow you to move the cursor:

CommandExplain
rmove cursor to right
lmove cursor to left

These 2 commands don’t count as 1 step.
Because we only have 100 steps. We need a GOOD string by finding the good init number and let the program give us a good string. Script (shout out to Elvis):

from pwn import *

def get_score(rnd_string):
    score = 0
    target_string = "episode3,57min34sec"
    for i, c in enumerate(rnd_string):
        if c.lower() == target_string[i]:
            score += 1
    
    return score

if __name__ == "__main__":
    max_score = 0
    good_string = ""
    best_seed = 0
    
    for seed in range(0, 0xFFFFFFFF):
        p = process("./chal")
        p.sendline(str(seed).encode())
        p.recvuntil(b"Current: ")
        rnd_string = p.recvline()[:-1]
        rnd_string = rnd_string.decode()
        score = get_score(rnd_string)
        if score > max_score:
            print("Found new string '%s' , seed %d with score %d" % (rnd_string, seed, score))
            max_score = score
            good_string = rnd_string
            best_seed = seed
        else:
            print("Best seed: %d" % best_seed)
        
        p.close()

# int: 2244
# episode3,57min34sec
# etnsOle`,5?}Iz)2Cb@

Good enough, now we just need to tweak the string. Those steps:

7
8
r
r
8
r
r
r
9
l
l
l
l
l
l
l
8
7
r
r
r
9
l
l
l
8
7
3
3
3
3
r
r
3
3
r
r
24
3
3
3
3
l
l
3
r
r
r
19
2
2
r
24
3
3
3
r
3
3
r
r
24
2
3
l
2
3
r
r
2
r
19
r
r
24
3
r
19
2
2
r
r
24
3
3
r
19
2
3
3

rev/Lost in the ZCL#

Challenge detail#

Little Timmy decided to simplify the flag printing, and has deployed an update to the ESPs. Once again Timmy has lost the source, can you get him the flag again?

Note: this is intended as a pt2 of RISCy Business, but the challenges are not interdependent attached

PCAP analysis#

Load the file in Wireshark and get nothing. Look at [Zigbee Network Layer Data]. We can see the payload is encrypted. Zigbee use AES-128 to encrypt the traffic, the default key is:

Zigbee Trust Center Link Key (ZigbeeAlliance09):
5A 69 67 42 65 65 41 6C 6C 69 61 6E 63 65 30 39

Add this key, Edit -> Preferences -> Protocols -> Zigbee -> Edit. After decrypt, we can see the payload.
I can see some packet with Cluster: OTA Upgrade (0x0019), lets get all the packet that has “Cluster: OTA Upgrade (0x0019)”.

Add this key to Wireshark. We get some traffic with data is some ascii string. Since we are playing damCTF, so I gonna search for some “dam{” or “64:61:6d:7b” in hex. And we found the flag.

Or you can use ZigbeeOtaExtractor. In Wireshark, apply this filter zbee_aps.cluster == 0x0019 then File -> Export Packet Dissections -> As Json. Then use ZigbeeOtaExtractor and find for a flag.

compujuckel
/
ZigbeeOtaExtractor
Waiting for api.github.com...
00K
0K
0K
Waiting...

misc/Mode Sleuth#

Challenge detail#

Software defined radio is so fun! I just recorded a bunch of planes near me, but I think someone messed with the data. Can you find the plane that is not supposed to be there?

Flag format: dam{}

Note: must be all caps, owner name should be full name including any spaces Example: dam{N249BA_BOEING AIRCRAFT HOLDING CO_24309}

attached

File analysis#

The challenge detail talk about plane. The 1st value has 112-bits, the 3rd is 56-bits. First bytes of 112-bits is 0x8D (10001101), first 5-bits = 17, next 3-bits = 5, 17 - 5 is the DF (Downlink Format) and CA (Transponder capability). Seem like we are dealing with ADS-B and ADS-B Mode S.

BitExplain
1-5DF - Downlink Format
6-8CA - Transponder capability
9-32ICAO - ICAO aircraft address
33-88ME - Message, extended squitter
(33-37)TC - Type code (optional)
89-112PI - Parity/Interrogator ID

ICAO will contain plane’s info, we can search through it. Write a simple script that parse all the 112-bits value, get ICAO then search. Most of them are civil planes… but there is one plane with ICAO AC82EC. It’s NASA plane, so this must be the one we are finding for. I use this web to get the info, and we have the flag.

misc/breach#

Challenge detail#

hack the mainframe, choom. eddies for days.

ssh chal@breach.chals.damctf.xyz

pw: chalworksnow

attached

Binary analysis#

The program give us a matrix[10][10] and 2 command:

 74  63  63  2D  73  73  20  6C  63  2D
 3B  74  66  74  3B  6C  67  66  20  73
 74  67  67  74  61  20  3B  73  6C  2D
 67  52  64  6C  61  3B  73  20  66  73
 61  61  61  2D  6C  66  67  63  63  3B
 67  64  66  61  52  67  20  67  63  63
 66  64  64  52  74  2D  73  20  52  74
 6C  63  6C  20  20  67  66  6C  66  3B
 3B  64  2D  3B  6C  61  2D  61  61  20
 63  52  6C  74  64  66  3B  20  6C  52

 [63][66][20][3b][74] NEUTRALIZE MALWARE
 [67][20][67][3b][6c][74][3b][20] DATAMINE: COPY MALWARE

I chose some random pos to get better understand. x denotes row, y denotes column.At first x = 0, if we select the pos at y (0, y) then next time we will have to select pos on the y column, so on and so on,… e.g: if we select (0, 2) at first, then we need to selec pos in 3rd column for next value.
The output is quite interesting:

BUFFER: [74 74 63 63 74 74 74 74 ]
BREACH FAILED. ERROR:
bash: line 1: ttccttttccc: command not found

Hmm why is the ttccttttccc: command not found here. Lets load it to our fav decompiler.
The program check our buffer with [63 66 20] and [67 20 67 3b]. If correct, it makes the program hang (WTF ??).

Then I found a interesting place, the program call os_exec_Command(). So the program will decode your input from hex to ascii and pass it to os_exec_Command(). Thats why the ttccttttccc: command not found appeared.

I try to create a string “ls -R”. And the output is:

BUFFER: [6C 73 20 2D 52 ]

DEBUG: AXIS: y
BREACH FAILED. ERROR:
.:
alft
altf
atfl
falt
flat
latf

./alft:

./altf:
flag

./atfl:

./falt:

./flat:

./latf:

Good good, now we just need to create a string like cd altf; cat flag