UT CTF 2025
Write up UT-CTF 2025
Ostrich Algorithm
Challenge ELF64, chạy thử không in ra gì cả.
Mở tab String trong IDA thấy có chuỗi utflag{ nên xref tới thử:
Chương trình thực hiện in flag nhưng bị chặn bởi vòng lặp so sánh –> patch code để bỏ qua bước kiểm tra đó.
utflag{d686e9b8f13bef2a3078c324ceafd25d}
Maps
1 challenge ELF64.
Challenge cung cấp 1 file chal thực hiện mã hoá và 1 file output.txt chứa flag sau khi bị mã hoá.
Mở = IDA chương trình call hàng nghìn hàm:
Để reverse khá căng –> để ý thấy output sau khi chạy chall và output của file output.txt giống nhau ở 35 chữ số đầu tiên –> chứng tỏ 7 ký tự đầu của flag (utflag{) sẽ ứng với 35 chữ số đó –> nó mã hoá lần lượt từng ký tự của flag và in ra màn hình.
Để tìm flag mình sẽ nhập lần lượt 33 ký tự khác nhau trong phạm vi ascii và tạo 1 dictionary để lưu lại 5 chữ số mã hoá ứng với từng ký tự –> sau khi thu thập đủ sẽ ánh xạ cipher trong output.txt với dictionary để giải mã flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
enc_map = {}
ascii_chars = [chr(i) for i in range(32, 127)]
for i in range(0, len(ascii_chars), 33):
chars = "".join(ascii_chars[i:i+33])
io = process("./chal")
io.recvuntil(b"Transform!")
io.sendline(chars.encode())
output = io.recvall().decode().strip().split("\n")
enc_string = output[-1]
enc_val = [enc_string[j:j+5] for j in range(0, len(enc_string), 5)]
for char, enc in zip(chars, enc_val):
enc_map[enc] = char
io.close()
cipher = "4934849349493674935749360493664940249346493534935849348493574936549351493644937449348493464936449365493744935349360493464935449364493574935749374493494935349358493594935449404"
flag = ""
for i in range(0, len(cipher), 5):
enc_chunk = cipher[i:i+5]
flag += enc_map.get(enc_chunk, "?")
print(flag)
utflag{shouldve_used_haskell_thonk}
Safe Word
1 challenge ELF64.
Tổng quan hàm main yêu cầu nhập flag, thực hiện khởi tạo mảng arr với rất nhiều phần tử sau đó duyệt lần lượt 33 ký tự của flag và thực hiện kiểm tra với mảng arr.
Hàm sub_5555555551FC nhận tham số là phần tử arr tại index [256 * v4 + s[i]] và kiểm tra giá trị đó có theo format 0xC358[v4_value]6A hay cụ thể như sau với ký tự u ứng với giá trị v4 mới là 0Bh:
Đây là t sẽ ứng với giá trị v4 mới là 64h
–> ý nghĩa của 3 lệnh đó chính là push giá trị v4 vào stack rồi lấy ra gán cho eax và cập nhật lại v4.
–> byte thứ 2 của arr[index] sẽ được gán cho v4 để tiếp tục duyệt.
Ví dụ:
1
2
3
4
5
6
7
8
9
10
11
v4 = 91 = 0x5b
s[0] = 'u' --> index = v4 * 256 + 'u' = 0x5b * 0x100 + 0x75
--> arr[0x5b75] = 0xc3580b6a
--> v4 = 0x0b
s[1] = 't' --> index = 0x0b * 0x100 + 0x74
--> arr[0x0b74] = 0xc358646a
--> v4 = 0x64
... ...
tương tự cho các ký tự còn lại
Tóm lại các ký tự của flag phải làm cho phần tử arr tại index trở thành 1 hàm thực thi với các lệnh push, pop, retn –> brute-force 33 ký tự sao cho thoả yêu cầu trên –> có nhiều input thoả mãn chương trình vì nó có thể lặp lại cho v4 –> vì thế mình sẽ dùng backtracking để tránh trường hợp lặp lại ký tự.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import re
def assign_arr(file_path):
pattern = re.compile(r'arr\[(\d+)\] = (0x[0-9A-Fa-f]+|\d+)LL;')
arr = [0] * 32768
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
match = pattern.search(line)
if match:
index = int(match.group(1))
value = int(match.group(2), 0)
arr[index] = value
return arr
arr = assign_arr('arr.txt')
def solve(arr):
v4 = 91
s = []
seen_states = set()
def backtrack(index):
nonlocal v4
if index >= 33:
return True
original_v4 = v4
for s_i in range(20, 128):
arr_index = 256 * v4 + s_i
if arr_index < len(arr):
val = arr[arr_index]
# format: 0xC358XX6A
if (val & 0xFF) == 0x6A and ((val >> 16) & 0xFF) == 0x58 and ((val >> 24) & 0xFF) == 0xC3:
next_v4 = (val >> 8) & 0xFF
if next_v4 not in seen_states:
seen_states.add(next_v4)
s.append(chr(s_i))
print(f'arr[{hex(arr_index)}] = {hex(val)} => v4 = {hex(next_v4)}, s[{hex(index)}] = {chr(s_i)}')
v4 = next_v4
if backtrack(index + 1):
return True
seen_states.remove(next_v4)
s.pop()
v4 = original_v4
return False
if backtrack(0):
return ''.join(s)
else:
print("Not found!")
return None
print(solve(arr))
Để ý thấy v4 phải khớp với format của giá trị của phần tử arr 0xc358[v4]6a 
utflag{1_w4nna_pl4y_hypix3l_in_c}



















