(writeup) TAMUctf 2024
Admin Panel
leak canary, libc –-> ret2libc

gigem{l3ak1ng_4ddre55e5_t0_byp4ss_s3cur1t1e5!!}
Janky

analyse
- chương trình cho ta thực thi shellcode khi và chỉ khi bypass hàm validate()
- ở hàm validate() sẽ check xem trong shellcode mình có chữ 'j' hay không
shellcode có chữ 'j' chỉ có thể lệnh jump
jmp, jz, jne, …
shellcode
- ta sẽ viết shellcode lần 1 chỉ có lệnh jump
- jump như thế nào thì một khi đang thực thi shellcode, ta có thể jump tuỳ ý, thì ta jump làm sao setup thanh ghi gọi sys_read thôi
- shellcode lần 2 cứ execve bình thường
get flag

gigem{jump1ng_thr0ugh_h00p5}
Rift

- chall về fmtstr
- idea:
- thoát loop: ow $rbp-0x4 = 0
- chain pop_rdi -> binsh -> system để ret2libc

quên lưu flag =))
Confinement

analyse
- chương trình sẽ thực thi shellcode cho mình với điều kiện tiến trình con đang hoạt động (hàm fork())
- thấy rằng là seccomp chỉ cho phép mỗi exit_group nên nghĩ đến hướng brute force flag
vì có lưu flag trong biến bss
- và khả năng cao phải brute từng bit
vì nếu brute byte khá là khoai (từ 0x20 đến 0x7f)
brute force
- ta sẽ tạo 2 vòng lặp, 1 cho từng byte, 1 cho từng bit

jump vào shellcode
do trên stack có địa chỉ exe
nên sẽ pop vào 1 thành ghi nào đó đỡ
r trừ ra base
cộng ra địa chỉ FLAG
rồi dùng phép AND (&) để so sánh bit 1 hoặc 0
đưa kết quả vào $rdi rồi call hàm exit_group (sys_num = 0xe7)
nếu ret trả về 1 thì thoả mãn
đồng thời đưa bit dịch trái theo bit_offset (0 đến 7)
lấy kết quả trả về cho vào hàm chr()
gặp NULL (kết thúc chuỗi flag) sẽ break
get flag

gigem{3xf1l_5ucc3ss!}
Five

analyse
- bài này shellcode 5 byte nên nghĩ ngay đến call read 1 lần nữa rồi get_shell

- để setup đủ register thì cần setup cả 4 thanh ghi
$rax=0
$rdi=0
$rsi=addr_shellcode
$rdx=size
xor rdi,rdi (2 byte)
push rdx (1 byte)
pop rsi (1 byte)
syscall (2 byte)
- thấy là $rdi đang chứa biến môi trường nên đoán là server sẽ không có (chỉ là kinh nghiệm)
- nên bỏ phép
xor rdi, rdi
đi thì syscall thành công trên server
- vậy local k ăn shell được =)))
- thế shellcode chỉ cần 4 byte là đủ
get flag

gigem{if_you_used_syscall_read_pls_tell_nhwn_how_you_did_it}
Good Emulation

analyse
- vì đây là file kiến trúc ARM nên exploit thông thường hơi khó
- có thể coi blog về ARM tui có viết
- BUG BOF khá rõ:
gets(buf);
- thường mấy bài này sẽ tận dụng mấy thanh ghi thôi
ROP chain
- trước khi nhập buf sẽ ỉn ra
/proc/self/maps
của tiến trình hiện tại (là chương trình này)
- ta sẽ setup ROPchain cho 4 thanh ghi
$r7 giống $rax là syscall number = 0x3b
$r0 giống $rdi
$r1 giống $rsi
$r2 giống $rdx
syscall trong arm là svc
get flag

gigem{q3mu_wh4t_th3_fl1p}
Shrink

analyse
- option1 chỉ là in ra
- option2 sẽ dựa vào len để read
- option3 để tăng len rồi thêm byte '!' vào cuối chuỗi
- BUG ở option3, tăng lượng vừa đủ để ret2win
get flag

gigem{https://i.redd.it/sayk4pi4ood81.png}
Index

analyse
- thấy rằng có hàm win() nên nghĩ đến ret2win
- mà option1 có hàm read() đọc size có 99 thôi, memset() tận 100
- option2 là in ra ouput và lưu trên stack (biến tmp)
- lờ mờ đoán được phải làm gì đó đụng đến strcpy() tránh NULL byte để copy full chuỗi
- thực ra có BUG interger overflow khi chọn index

get_message()
- nhận đối số $a1 trả về từ get()
- nhân 100 rồi check phải nằm trong đoạn này

interger overflow
- BUG ở chỗ nhân với 100 (ai mượn :v)
- ta sẽ lấy số cao nhất cho kiểu unsigned long

18446744073709551615
- rồi bỏ đi 2 số cuối
- tăng thêm 1 là
184467440737095517
để khi nhân 100 nó sẽ bị ngu ngu

đây là khi bị IOF
không nằm trong phạm vi MESSAGE, MESSAGE+100 hay MESSAGE+200
thế là ta chỉ việc nối chuỗi rồi dùng option2 cho lên stack
rồi option3 exit sẽ return
tăng giảm payload để return chuẩn nha
get flag

gigem{wh0_put_m4th_1n_my_pwn}
Super Lucky

analyse
- thông qua source ta thấy BUG OOB khá rõ –-> leak primitive (21 times)
- ta sẽ leak libc thông qua puts@got
- phân tích hoạt động:
nhập là "%lu" nhưng output là "%d" (4 byte)
–-> leak 2 lần để được full 8 byte
rand
- ở chall này có lẽ không thể load libc vào để predict rand từ srand được (do seed được gen từ /dev/urandom)
- vì ta hoàn toàn có thể leak tuỳ ý nên BUG sẽ nằm trong chính source code của rand
https://elixir.bootlin.com/glibc/glibc-2.28/source/stdlib/stdlib.h#L423
https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L160
https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L146
https://elixir.bootlin.com/glibc/glibc-2.35/source/stdlib/random_r.c#L352
https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random_r.c#L370
- ta sẽ quan tâm 2 giá trị là fptr và rptr
DEBUG
- ta có công thúc tính như source trên:
- ta sẽ reseeding nhằm mục đích debug

result sẽ bằng val dịch phải 1
val bằng *fptr (0x3e01511e) + *rptr (0x991539b1) rồi lấy 4 byte (uint32_t)

- vậy ta đã predict thành công output của rand
- và để ý luôn
*fptr += (uint32_t) *rptr;
sau khi rand() sẽ cập nhật dữ liệu tại *fptr

- ta chỉ cần 7 giá trị dword (4 byte) cho lần guess, nên sẽ padding lần leak cho đủ 21 lần
- vì lí do
khó hiểu nào đó mà khi pwninit libc thì file patched không tìm thấy unsafe_state nên sẽ gdb cả file gốc lẫn file patched để tìm offset

offset unsafe_state = 0x1ba740
- check khi leak ở randtbl :

get flag

gigem{n0_on3_exp3ct5_the_l4gg3d_f1b0n4cc1}