# UlisseCTF 2025 - Forensics
## AWAWA

This challage gave me a .jpg file.

Everything looked unsuspicous.
Also nothing with `exiftool`

`foremost` and `binwalk` also had nothing to extract :cry:

Let's try `steghide`. First time, I looked at description and thought that "AWAWA." was the passphrase but it not :cry:

Then, I tried with no passphrase and it worked. The description made me confused.

So, we have the wav file. Let's open it with `Audacity` and use `Spectrogram` for the flag

**FLAG: UlisseCTF{St3g0_M4st3r}**
## Internet of Lighthouse

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

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

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.

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.

**FLAG: UlisseCTF{WAITITSALLMQTTALWAYSHASBEEN}**
## Self Quote

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

Thử bỏ vô HxD

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`

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


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

**FLAG: UlisseCTF{M4yb3_Zippy_w4s_4_b3tter_n4m3}**
# Misc
## POV


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()
```
