[pwn基礎]Pwntools學習

VxerLee暱稱已被使用發表於2022-06-21

[pwn基礎]Pwntools學習

Pwntools介紹

Pwntools是一個非常著名的CTF框架漏洞利用開發庫,可以讓使用者快速的編寫exp

它擁有本地執行遠端連線讀寫shellcode生成ROP鏈構建ELF解析符號洩漏等眾多強大的功能。

Pwntools安裝

因為他是個python庫,所以直接用pip來管理安裝即可。

#提前安裝pip
sudo apt-get install python3-pip
#安裝pwntools
pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple

測試是否安裝成功

image-20220620180907115

Pwntools常用模組和函式

Pwntools分為兩個模組,一個是pwn,簡單的用from pwn import *就能把所有子模組和一些常用的系統庫匯入當前名稱空間中,是專門為了CTF比賽優化的。

另外一個模組是pwnlib,它適合開發成產品,根據自己需要來匯入不同的子模組。

  • pwnlib.adb: 安卓adb
  • pwnlib.asm: 彙編和反彙編
  • pwnlib.constans: 包含各種體系結構和作業系統中的系統呼叫號常量(來自標頭檔案),constants.linux.i386.SYS_stat
  • pwnlib.context: 設定執行環境
  • pwnlib.dynelf: 利用資訊洩漏遠端解析函式
  • pwnlib.encoders: 對shellcode進行編碼,如encoders.encoder.null('xxx')
  • pwnlib.elf: 操作ELF可執行檔案和共享庫
  • pwnlib.fmtstr: 格式化字串利用工具
  • pwnlib.gdb: 除錯,配合gdb使用
  • pwnlib.libcbd: libc資料庫,入libcdb.search_by_build_id('xxx')
  • pwnlib.log: 日誌記錄管理,比如log.info('hello')
  • pwnlib.memleak: 記憶體洩漏工具,將洩漏的記憶體快取起來,可作為Payload
  • pwnlib.qume: QEMU模擬相關,一般用來模擬不同架構的指令或執行程式
  • pwnlib.rop: ROP利用工具,包括rop,srop等
  • pwnlib.runner: 執行Shellcode,例如:run_assembly('mov eax,SYS_exit;int 0x80;')
  • pwnlib.shellcraft: Shellcode生成器
  • pwnlib.tubes: scokets、ssh、程式管道通訊
  • pwnlib.utils: 一些實用小工具,比如CRC計算,cyclic字串生成等

pwnlib.tubes模組學習

tubes模組是主要用來通訊的模組,應該是pwn題中用的最廣泛的互動方式,他主要有下面4中通訊方式。

  1. pwnlib.tubes.process: 程式通訊
  2. pwnlib.tubes.serialtube: 串列埠通訊
  3. pwnlib.tubes.sock: socket套接字通訊
  4. pwnlib.tubes.ssh: SSH連線通訊

tubes.process

這裡我一直好奇這個程式通訊他是怎麼弄的,為什麼我用程式碼p=process('./mydemo'),然後就可以用send和recv對程式進行傳送,然後看了他原始碼註釋 Spawns a new process, and wraps it with a tube for communication.,他應該是用了比較hack的方法,自己模擬了系統載入本地程式變成程式的操作,並且封裝了一層管道通訊在上面,這樣我們就可以通過send、recv來和他建立的程式來進行通訊了,所以就給我們創造了無數可能,比如我之前文章[二進位制漏洞]棧(Stack)溢位漏洞 Linux篇中裡面的題目scanf只能輸入ASCII碼,這樣我們無法構造一個地址Payload,而有了tubes.process則可以輕鬆做到。

#include <stdio.h>

void hack()
{
    printf("Hack Success!!!!\n");
}

int main()
{
    printf("Hello,Please Start Hack!\n");
    char buf[20]={0};
    scanf("%s",buf);
    printf("Your input:%s\n",buf);
    int i;
    for(i=0;i<sizeof(buf);i++)
    {
        printf("0x%x,",buf[i]);
    }
    return 0;
}
gcc hack2.c -m32 -fno-stack-protector -z noexecstack -o hack2

正常情況下:無法輸入0x01、0x02、0x03這種資料。

image-20220620190740108

而用tubes.process則可以用send傳送原始十六進位制資料。

#匯入pwntools模組
from pwn import *
context(arch = 'i386',os='linux')
p = process("./hack2")

#顯示程式執行的第一條回顯
print(p.recv())

#利用pipe管道傳送帶 十六進位制的資料
p.sendline(b'AAAA'+b'\x01\x02\x03\x04')

#回顯結果
print(p.recvline())
print(p.recvline())

image-20220620191153963

所以[二進位制漏洞]棧(Stack)溢位漏洞 Linux篇文章的題,用tubes.process搞起來就方便多了。

列印程式裝載起始地址。

p = process("./hack")
imageBase = p.libs()["/home/ubuntu/hack"]

遠端的話使用如下命令:

conn = remote('exploitme.example',31337)
conn.recv()
conn.sendline('test')

pwnlib.context(執行環境)

這個模組主要是用來設定程式執行時的環境,比如目標是什麼CPU架構,多少位數,什麼平臺,是否開啟日誌等等。

#架構32位X86,平臺Linux
context(arch='i386',os='linux')
#設定tmux分屏
context.terminal['tmux','splitw','-h']
#開啟日誌資訊
context.log_level = 'debug'

CPU架構如下:

architectures = _longest({
        'aarch64':   little_64,
        'alpha':     little_64,
        'avr':       little_8,
        'amd64':     little_64,
        'arm':       little_32,
        'cris':      little_32,
        'i386':      little_32,
        'ia64':      big_64,
        'm68k':      big_32,
        'mips':      little_32,
        'mips64':    little_64,
        'msp430':    little_16,
        'powerpc':   big_32,
        'powerpc64': big_64,
        'riscv':     little_32,
        's390':      big_32,
        'sparc':     big_32,
        'sparc64':   big_64,
        'thumb':     little_32,
        'vax':       little_32,
        'none':      {},
    })
transform = [('ppc64', 'powerpc64'),
             ('ppc', 'powerpc'),
             ('x86-64', 'amd64'),
             ('x86_64', 'amd64'),
             ('x86', 'i386'),
             ('i686', 'i386'),
             ('armv7l', 'arm'),
             ('armeabi', 'arm'),
             ('arm64', 'aarch64')]

位數:

    big_32    = {'endian': 'big', 'bits': 32}
    big_64    = {'endian': 'big', 'bits': 64}
    little_8  = {'endian': 'little', 'bits': 8}
    little_16 = {'endian': 'little', 'bits': 16}
    little_32 = {'endian': 'little', 'bits': 32}
    little_64 = {'endian': 'little', 'bits': 64}

平臺:

 oses = sorted(('linux','freebsd','windows','cgc','android','baremetal'))

pwnlib.elf(ELF檔案操作)

pwnlib.elf模組還是挺實用的,雖然linux下有<elf.h>標頭檔案可以用來解析ELF檔案,但是很多程式碼都要自己實現,這個模組就解決了這些實現,可以進行符號查詢、虛擬記憶體、檔案偏移、修改和儲存二進位制等等。

from pwnlib.elf import ELF
#構造類
elf = ELF('./hack_dyn')

#架構,位數,平臺
print("---------------------------------------------")
print("[+]架構:{0} 位數:{1} 系統:{2}".format(elf.arch,elf.bits,elf.os))
print("")
#列印裝載地址
print("[*]裝載地址:",hex(elf.address))
#列印GOT表
print("[*]GOT表:")
for kv in elf.got.items():
    print(kv)
#列印PLT表
print("")
print("[*]PLT表:")
for kv in elf.plt.items():
    print(kv)
print("[*]hack函式偏移:",hex(elf.symbols['hack']))
print("---------------------------------------------")

可以看到列印出了GOT表、PLT表、符號表中hack函式的偏移,其中裝載地址為0是因為這是個動態連結程式,裝載地址不確定,改成靜態編譯就能顯示裝載地址。

image-20220621142628339

靜態連結

image-20220621142802198

asm(address,assembly) # 彙編指令assembly插入ELF的address地址處,需要使用save函式來儲存
bss(offset) # 返回.bss段加上offset後的地址
checksec() # 檢視檔案開啟的安全保護
disable_nx() # 關閉NX
disasm(address,n_bytes) # 返回地址address反彙編n位元組的字串
offset_to_vaddr(offset) # 將偏移offset轉換為虛擬地址
vaddr_to_offset(address) # 從虛擬地址address轉換為檔案偏移
read(address,count) # 從虛擬地址address讀取count個位元組的資料
write(address,data) # 在虛擬地址address寫入data
section(name) # 獲取name段的資料
debug() # 使用gdb.debug()進行除錯

pwnlib.asm(彙編模組)

這是個很強大的模組,可以進行彙編和反彙編,通常用來開發Shellcode的時候非常有用。

可以用pwnlib.context先設定CPU架構位元組序位數。

asm()函式進行彙編,用disasm()函式進行反彙編

from pwnlib.asm import *
#彙編
print(asm('mov eax, 0'))
print(asm('mov ebx, 1'))
print(asm('add eax, ebx'))
print(asm('mov eax, SYS_execve'))
print(asm('nop'))

image-20220621150013820

disasm()反彙編

from pwnlib.asm  import *
from pwnlib.util.fiddling import *
#反彙編
print(disasm(unhex('E007BFA9E20FBFA9E417BFA9E61FBFA9E827BFA9FA6FBFA9FC77BFA9FE0F1FF8C81580D2010000D440050035881580D2010000D41F040071C1040054000080D261FCFF10021880D2E3031FAA080780D2010000D4E003F837FF4300D1481680D2010000D4E00B00B9881B80D2E4230091E3031FAAE2031FAAE1031FAA200280D20024A0F2010000D4E00B00B9FF43009100020035000080D241FAFF300201A0D2E3031FAA080780D2010000D4E40300AAC81B80D2000080D2230080D2E5031FAAA20080D2010094D20100A0F2010000D4FE0741F8FC77C1A8FA6FC1A8E827C1A8E61FC1A8E417C1A8E20FC1A8E007C1A8FD7BBEA9010000142C010000000000000000000000000000000000000000000000000000AA0400000000F1FF'),arch='aarch64',bits=64))

當彙編反彙編,其他架構平臺時候,記得要安裝對應的Binutils,安裝教程:https://docs.pwntools.com/en/stable/install/binutils.html

image-20220621151710861

image-20220621151821857

反編譯效果,真的很強大!

image-20220621151843674

pwnlib.shellcraft(Shellcode生成器)

這個模組可以用來生成Shellcode程式碼,這種模組簡直太愛了,他可以生成aarch64、arm、thumb、mips、i386、amd64、powerpc架構的shellcode程式碼,基本上的架構都有了。

生成Shellcode程式碼。

>>> from pwn import *
>>> print(shellcraft.i386.nop())
    nop
   #生成了一個x86架構平臺的nop

接下來生成一個Android手機開啟/data/local/tmp/test.txt的Shellcode,這模組太強大了。

#設定CPU架構  aarch64
#設定系統平臺 android
print(shellcraft.aarch64.android.open('/data/local/tmp/test.txt'))

image-20220621153632963

還能配合asm輸出不同格式shellcode,實在是太方便、太好用了!

from pwn import *

shellcode = shellcraft.aarch64.android.open('/data/local/tmp/test.txt')
print("輸出字串格式Shellcode:")
print(asm(shellcode,arch='aarch64',bits=64,os='android'))
print("")
print("輸出十六進位制格式Shellcode:")
print(asm(shellcode,arch='aarch64',bits=64,os='android').hex())

image-20220621154304667

官方的例子。

from pwnlib.shellcraft import *
context.clear()
context.arch = 'amd64'
sc = 'push rbp; mov rbp, rsp;'
sc += shellcraft.echo('Hello\n')
sc += 'mov rsp, rbp; pop rbp; ret'
solib = make_elf_from_assembly(sc, shared=1)
subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib}, universal_newlines = True)
'Hello\nWorld\n'

pwnlib.util(小工具)

這個模組是一些常用的功能函式,比如之前用到過的unhex就來自這模組,除此之外還有packing、hashes、net、misc、sh_string、cyclic等函式。

#用的最多的應該是pack函式了吧
p8(0) #打包1位元組
b'\x00'
p32(0xdeadbeef) #32位最常用的,打包4位元組
b'\xef\xbe\xad\xde'
p64(0xdeadbeef)
b'\xef\xbe\xad\xde\x00\x00\x00\x00'
#可設定大小端序
>>> p32(0xdeadbeef,endian='little')
b'\xef\xbe\xad\xde'
>>> p32(0xdeadbeef,endian='big')
b'\xde\xad\xbe\xef'
#解包
unpack(b'\xaa\x55',16,endian='little')
'0x55aa'
u32('\xaa\x55\x00\x00')
21930
u64('\xaa\x55\x00\x00\x00\x00\x00\x00')
21930


#生成溢位字串(cyclic)
cyclic(20)
b'aaaabaaacaaadaaaeaaa'
cyclic(20, alphabet=string.ascii_uppercase) #全大寫
b'AAAABAAACAAADAAAEAAA'  
cyclic(20, n=8)		    #8字元對齊
b'aaaaaaaabaaaaaaacaaa' 
cyclic(20, n=2)         #2字元對齊
b'aabacadaeafagahaiaja' 
cyclic(alphabet = "ABC", n = 3)#設定成ABC對齊
b'AAABAACABBABCACBACCBBBCBCCC'
context.cyclic_alphabet = "ABC" #全域性修改
cyclic(10)
b'AAAABAAACA'
#查詢偏移
cyclic_find('daaa')
12
cyclic_find(0x61616162)
4

#unhex
unhex('0102030405060708')
b'\x01\x02\x03\x04\x05\x06\x07\x08'

pwnlib.rop

rop利用模組,包括rop,srop等。

現在的exploit是越來越難,一般起手題都得是NX開啟的,ROP這種以前都能出400分題的技術現在也就出50-100分題了非常慘,也許跟這個工具簡化了ROP過程有關係?「誤」

先簡單回顧一下ROP的原理,由於NX開啟不能在棧上執行shellcode,我們可以在棧上佈置一系列的返回地址與引數,這樣可以進行多次的函式呼叫,通過函式尾部的ret語句控制程式的流程,而用程式中的一些pop/ret的程式碼塊(稱之為gadget)來平衡堆疊。其完成的事情無非就是放上/bin/sh,覆蓋程式中某個函式的GOT為system的,然後ret到那個函式的plt就可以觸發system('/bin/sh')。由於是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果沒有開啟ASLR,可以直接使用ret2libc技術)

好,這樣來看,這種技術的難點自然就是如何在棧上佈置返回地址以及函式引數了。而ROP模組的作用,就是自動地尋找程式裡的gadget,自動在棧上部署對應的引數。

from pwn import *

elf = ELF('ropasaurusrex')
rop = ROP(elf)
rop.read(0, elf.bss(0x80))
rop.dump()
# ['0x0000:        0x80482fc (read)',
#  '0x0004:       0xdeadbeef',
#  '0x0008:              0x0',
#  '0x000c:        0x80496a8']
str(rop)
# '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'

使用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) : 給出一個地址,反解析出符號

參考文章:

http://unbelievable.cool/2021/07/25/pwntools學習/#pwnlib-asm (pwntools學習)

http://brieflyx.me/2015/python-module/pwntools-intro/ (Exploit利器--Pwntools)

http://www.leonlist.top/2020/09/02/pwn基本工具-pwntools/ (pwn基本工具-pwntools)

https://xuanxuanblingbling.github.io/ctf/pwn/2020/12/13/getshell3/ (Getshell遠端:真·RCE 正連?反連?不連?)

https://github.com/Gallopsled/pwntools (pwntools原始碼)

最後大家可以多看看pwntools原始碼去熟悉熟悉,每個模組的功能,他註釋一般有demo寫的還是非常詳細的。

PWN菜雞小分隊

最後感謝大家的閱讀,本菜雞也是剛學,文章中如有錯誤請及時指出。

大家也可以來群裡罵我哈哈哈,群裡有PWN、RE、WEB大佬,歡迎交流

img