---
# System prepended metadata

title: UlisseCTF 2025 - Forensics
tags: [CTF]

---

# UlisseCTF 2025 - Forensics
## AWAWA
![image](https://hackmd.io/_uploads/B1HuVSxAJg.png)

This challage gave me a .jpg file. 
![image](https://hackmd.io/_uploads/SyLhEHlCkg.png)
Everything looked unsuspicous.

Also nothing with `exiftool`
![image](https://hackmd.io/_uploads/B1syBrxA1l.png)

`foremost` and `binwalk` also had nothing to extract :cry: 
![image](https://hackmd.io/_uploads/BkvmBBgRye.png)

Let's try `steghide`. First time, I looked at description and thought that "AWAWA." was the passphrase but it not :cry: 
![image](https://hackmd.io/_uploads/SyVtrHgAke.png)

Then, I tried with no passphrase and it worked. The description made me confused.
![image](https://hackmd.io/_uploads/HkMCSBxCyg.png)

So, we have the wav file. Let's open it with `Audacity` and use `Spectrogram` for the flag
![image](https://hackmd.io/_uploads/Hk_NIreR1x.png)

**FLAG: UlisseCTF{St3g0_M4st3r}**


## Internet of Lighthouse
![image](https://hackmd.io/_uploads/BySgwNxRke.png)

This challenge gave me a pcap file. Let's open it with Wireshark

![image](https://hackmd.io/_uploads/H1hUPNlAkg.png)

It has TCP and HTTP/JSON protocol, I checked TCP streams

![image](https://hackmd.io/_uploads/H10KDNeA1x.png)

It contains 67 streams, and the differences are the `timestamp` and `onoff`. First, I checked the `onoff`, it changed between `0` and `1`. So I thought it like a binary string, and extracted it from 67 streams.

`1001011011011110101110110000100010101110010010011111111011101111101`

Convert it to ASCII but nothing happens. So I paid attention to `timestamp`. I realized that some adjacent streams had same `timestamp`, it was bettwen 1 -> 4 adjacent streams, so It could basically be Morse code.

So I splited it into the cluster with same `timestamp`, 1 is `.`, 0 is `-`

The result:
```
.-- .- .. - .. - ... .- .-.. .- .. -- --.- - - .- .-.. .-- .- -.-- ... .... .- ... -... . . -.
```

I decoded it, ya saw some English words but not all correct.
"WAITITS" and "ALWAYSHASBEEN" maybe is correct.
![image](https://hackmd.io/_uploads/SygqFEg0yl.png)

Hmm, the division was not correct somewhere in the middle, I checked again with `timestamp`, sometimes it increased `5` but sometimes it just increased `1` (There only one bruh). I focused on the time changed `5`, if the timestamp difference is none or `1`, it didn't push any " ", else pushed " ".

So the result changed a little bit in the middle.

![image](https://hackmd.io/_uploads/S1VVMrl0Je.png)

**FLAG: UlisseCTF{WAITITSALLMQTTALWAYSHASBEEN}**

## Self Quote
![image](https://hackmd.io/_uploads/BkeHJneCkx.png)

Bài này cho 1 file .data nên trước tiên kiểm tra thử extension thật sự của nó là gì.
Check bằng `file` thì nó vẫn không ra

![image](https://hackmd.io/_uploads/BkOhynxRkx.png)

Thử bỏ vô HxD

![image](https://hackmd.io/_uploads/BknCy3l0kx.png)

Thấy có dòng JFIF, có thể là dạng bài fix Magic Byte
Trước tiên tìm về magic byte của JPEG
https://en.wikipedia.org/wiki/List_of_file_signatures

https://stackoverflow.com/questions/48669812/how-do-i-read-and-compare-single-bytes-from-jpeg-file-in-c

Nhưng mà khi thay đổi các bytes này cho đúng thì chẳng có gì xảy ra cả, có thể đây không phải magic byte của JPEG

Có thể magic byte này ban đầu đã là một cái extension khác, thử search với 4 byte đầu `FF 06 00 00`

![image](https://hackmd.io/_uploads/SkxeQneCJe.png)

Nó có 1 bài Wiki về dạng Snappy (Compression), sửa lại cho đúng magic byte của cái này thôi
![image](https://hackmd.io/_uploads/H1WQ7ne01x.png)
![image](https://hackmd.io/_uploads/ry04mhlA1e.png)

Tìm công cụ giải nén dạng này thì có 2 nguồn

https://github.com/berdav/snappy-fox

https://github.com/kubo/snzip


Dùng `snappy-fox` trích xuất file jpg
![image](https://hackmd.io/_uploads/SyJMv3xAJl.png)

**FLAG: UlisseCTF{M4yb3_Zippy_w4s_4_b3tter_n4m3}**

# Misc
## POV
![image](https://hackmd.io/_uploads/HyyE53lR1e.png)
![image](https://hackmd.io/_uploads/SyoG5hgAJg.png)

Mà mỗi lần load lại trang là một cái hình khác nhau. Tải hết hình về thôi.

`Doing a GIF by subdiving the noise in 60 imaged`

Ý tưởng như trên, cách tải ảnh thì xem hàm `download_all_images` dưới file python
```
ffmpeg -framerate 60 -i image%d.png -t 10 -filter:v "format=yuv420p,loop=loop=-1:size=60" output.mp4
```

Bài này có cách khác là xor mấy ảnh với nhau, writeup lời giải bên dưới
```python=
from PIL import Image
import requests
from time import sleep
import numpy as np
import glob

OUT_FOLDER = "./images/"

ENDPOINT = "http://pov.challs.ulisse.ovh:8888/api/stream"

def download_all_images():
    files_set = set()
    
    while(True):
        resp = requests.get(ENDPOINT)
        if "Too many requests" in resp.text:
            sleep(0.1)
            continue
        
        data = resp.content
        
        filename = resp.headers["Content-Disposition"].split('"')[1]
        if filename not in files_set:
            files_set.add(filename)
            with open(OUT_FOLDER + filename, "wb") as f:
                f.write(data)
                print(f"Downloaded {filename}")
                print(f"{60 - len(files_set)} remaining")
        if len(files_set) == 60:
            return
        
        sleep(1)
        
def main():
    
    download_all_images()
    
    image_files = sorted(glob.glob('images/*.png'))  
    assert len(image_files) == 60, f"Expected 60 images, found {len(image_files)}"

    # Open the first image and convert to binary array
    base_img = Image.open(image_files[0]).convert('1')  # convert to 1-bit
    xor_result = np.array(base_img, dtype=bool)

    # Iterate over the rest and compute XOR
    for file in image_files[1:]:
        img = Image.open(file).convert('1')
        xor_result ^= np.array(img, dtype=bool)

    # Convert boolean array back to uint8 (0 or 255)
    output_array = xor_result.astype(np.uint8) * 255

    # Create and save final XOR image
    final_image = Image.fromarray(output_array, mode='L')
    final_image.save('xor_result.png')

if __name__ == "__main__":
    main()
```
![image](https://hackmd.io/_uploads/HyXfp3lCJx.png)


