Post

UT CTF 2025

Write up UT-CTF 2025

UT CTF 2025

Ostrich Algorithm

image image

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ử:

image

image

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 đó.

image

image

image

utflag{d686e9b8f13bef2a3078c324ceafd25d}

Maps

image image

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:

image image

Để 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}

image

Safe Word

image image

1 challenge ELF64.

image image

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.

image

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:

image

Đây là t sẽ ứng với giá trị v4 mới là 64h

image

–> ý 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 image

utflag{1_w4nna_pl4y_hypix3l_in_c}

This post is licensed under CC BY 4.0 by the author.

Trending Tags