# 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)