pwn 常用指令以及概念紀錄
前置概念
little endian
: 高位優先big endian
: 低位優先- 逆向工具使用
- hooking: 在運行(通常)時把原函數以新函數取代,以改變程式行為。
- Stack Frame
- 為實現 Activation Record instance (ARI) 的機制
- You should know about the binary format and the internal detail
保護
Pwntool 的 pwn checksec
指令,可以簡單測啟用哪些保護
pwn checksec /bin/sh
透過checksec
下去檢測有哪些保護啟用
gdb-peda$ checksec
輸出結果
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
可以看到 NX 啟動,表示可能需要透過 ROP 的方式下去進行利用, 也可能是透過其他漏洞如格式化字串。
另外參見 漏洞緩解技術
利用方法
- Heap
- Format String
- Windows
- SEH
- VEH
- Linux 請見 ELF 章節
- Stack Smash
x86 32 bit ROP
@plt
ret pop*2
parameter1
parameter2
@plt
ret pop*1
parameter1
下面兩個 parameter 要 pop 掉,如果後面還有 gadget 的話
工具
反組譯查看函數
objdump -M intel -d ./echo | less
透過 gdb 檢查流程
gdb ./echo
載入 gdb peda,不過現在比較建議使用 gef 還不錯
source /usr/share/peda/peda.py
註: 如 ida pro 與 ghidra 在某些情況下可能無法得出可以讀的程式碼,可以嘗試不同反編譯工具。
GEF 可以參考手冊 Home - GEF - GDB Enhanced Features documentation
符號與區段
顯示符號表
nm ./a.out
顯示區段
readelf -x .text ./a.out
objdump -s -j .text ./a.out
其他指令參見逆向。
/proc/self/maps
pwntools 可以透過 Process.libs()
來取得 /proc/self/maps
,或許在除錯 PIE 的時候有用
注: 其他關於 proc 相關的可以參見文檔,想說怎麽不過設計在 proc 中
https://docs.pwntools.com/en/stable/util/proc.html
Shellcode
產生 shellcode
- 參見 msfvenom
- Pwntools
gef
的 shellcode 指令
shellcode search arm
shellcode get 904
pwntools
我們可以使用 pwntools 的 pwn
一行產生 shellcode
pwn shellcraft sh | pwn disasm
熟悉 pwntools 方便編寫 shellcode ,如構造一個 echo
的 shellcode 如下
from pwnlib import shellcraft
shellcraft.echo('hello world!')
如果需要填充用 flat()
即可,比方說填充 buffer 有用,或者說前面需要擺放 /bin/sh
時可以用來填充一定長度。
from pwn import *
flat({0: 'ABC', 5: 'CBA'}, filler = b' ', length = 10)
參見官方文檔
大部分 shellcode 都必須編寫 ROP chain,可以採用 pwntools 的 ROP 輔助編寫
如果需要產生 jmp 的 shellcode,pwntools 的 asm()
是可以使用 label 的,這部分因為實際上 asm 只是去透過 gnu as
進行處理,
因此我們可以寫出如下的 code
asm("""
loop:
nop
nop
nop
jmp loop
""")
補齊
如16位元/bin///sh
採用多個/
來避免生成的shellcode含有\0
字元
from pwn import *
context.arch='amd64'
pwn.asm('mov rax, 0x732f2f2f6e69622f')
pwn.asm('mov rax, 0x732f6e69622f')
Shellcode: Nop Sled
透過nop
指令增加shellcode的命中機率,
在shellcode之前的記憶體放入大量的nop
。
除錯
- 系統呼叫
int 0x80
沒有作用時,請檢查呼叫後的EAX
是否有變化,代表參數可能輸入錯誤 - 傳入
\0
或\n
導致沒辦法完整讀取 payload,可以嘗試放到後面。 - read syscall 如果遇到填充問題,嘗試填滿 buffer,包含前一次的 read。
- 如果有的時候 syscall 失效可能需要處理 alignment 0x10
ELF
利用方法
ret2csu
.fini
與 .fini_array
,執行順序依序為 .fini_array[1]
, .fini_array[0]
, .fini
- 在 main 結束後被呼叫
call QWORD PTR [rbp+rbx*8+0x0]
- 利用條件: 已知
.fini_array
地址,並且複寫內容 - 例題
- fini_array loop: pwnable.tw 3x17
參見
ASLR繞過
利用漏洞找到 Return Address,並且找到相關如 libc
、.text
、stack
的 Base 用於利用。
利用方法
- ret2text
- ret2shellcode
- ret2syscall
- ret2libc
- ret2reg
- BROP: Blind ROP
- ret2_dl_runtime_resolve
- SROP: Sigreturn Oriented Programming
- ret2vDSO: Virtual Dynamically-linked Shared Object
- JOP: Jump-oriented programming
- COP: Call-oriented programming
無法確定 libc 記憶體
無法確定 libc 記憶體,或者有啟用 ASLR 的時候,根據情況
- 可讀
/proc
並且輸出/proc/self/maps
可以得出記憶體映射
- 可寫
/proc/self/mem
可以覆蓋.text
請參見 Pwn学习笔记20:其他一些技术
pwntool 設定 base address
在 leak address 之後為了方便使用,會設置 base address
e = ELF('./a.out')
e.address = 0x400000
並且在使用 ROP 的時候會需要配置 base adderss
base = 0x400000
rop = ROP(e, base)
任意寫
絕對 Virtual Address
bss, data 這幾個 section 所屬的 segment 是可以任意寫
stack pivoting 到可控地址上。
-
ebp -> esp : leave; ret
-
例題
- pwnable.tw 3x17
相對 Virtual Address
-
stack 相對寫入: 可以編寫 ROP,覆蓋地址
-
heap 相對寫入
-
例題
- pwnable.tw calc: stack 相對寫入
技巧
PIE 取得 base address
https://tianstcht.github.io/pwntools的使用技巧/
如果是透過 ld.so 請透過以下指令
c.libs()[c.cwd + c.argv[3].decode(‘utf8’).strip(’.’)]
除錯技巧
採用GDB Script來避免重複的動作, 如:
- 啟動gdb時啟動peda
- peda
start
- 一些script來找到目的位置。
為了檢驗payload是否正常運作而不斷重複以上步驟, 將導致效率低下。
如果採用 pwntools ,可以直接在呼叫 gdb.attach() 時執行指令,來增加效率 。1
反向除錯
參見除錯
查詢 libc 地址
在 gdb 可以透過以下指令查詢 libc 地址,以及連結的 libc
info proc mapping
參見[Why does ldd
and (gdb) info sharedlibrary
show a different library base address?]
在 python script 切換到 interact
因為大多數在解題的時候,會針對特定程式的 function 寫一個 python function 進行控制,部分題目可能不能輸入 ascii,可以在我們腳本中使用輔助送出結構化的封包。
import code
# ...
def do_something(a):
c.sendline(a)
code.interact(local=locals())
查詢特定版本的 glibc
可以透過 此網站 來找特定版本的 glibc
計算 offset
- pwntools 有提供 cyclic_find(),可以輔助計算 offset
指定 libc
pwntools 可以透過此方式指定使用的 libc,有 hooking 的意味,請務必注意 lib 前面的 ./
。
c = process('echo2', env={'LD_PRELOAD': './libc-2.23.so.x86_64'})
# 請務必修改 libc 名稱
c = process('echo2', env={'LD_LIBRARY_PATH': '.'})
shell 可以透過此指令
LD_PRELOAD=/tmp/libc-2.23.so.x86_64 ./a.out
LD_LIBRARY_PATH=. ./a.out
採用 LD_LIBRARY_PATH
時請務必修改 libc 的名詞,並且透過 ldd
檢查連結的 library。
mv libc_32.so.6 libc.so.6
LD_LIBRARY_PATH=. ldd ./seethefile
輸出會出現以下訊息,代表已經透過該目錄的 libc
libc.so.6 => ./libc.so.6
如果不修改名詞會導致載入系統上的 libc
問題
- 遇到
Inconsistency detected by ld.so
暫時沒有找到解法,請找訊相同版本的ld.so
- 請務必透過 ldd 檢查是否載入正確的 lib
參考
指定 ld.so
關於 pwn 的時 libc 的坑的解決辦法
- 透過 docker 處裡環境問題
- 執行 libc 找到對映的 ld.so ,執行
./libc.so
來獲取版本號,並且下載 deb 包,
請參考 Handling SO Hell in CTF Pwn - BrieflyX’s Base
或者在 pwntool 之下,則可以參考以下方法。
pwn_file="./ld-2.29.so --library-path ./lib/ ./babyheap"
p = process(pwn_file.split())
參見於文章提到 starctf2019 的方法
注意: ELF 如果為 x86 ,則 ld, libc 都要為 x86,反之為 x64 都要採用 x64 版本的 ld, libc
採用此方法載入時如果要找到基準位置請改成
c.libs()[c.cwd + c.argv[3].decode('utf8').strip('.')]
危險函數
read(0, buf, (uint) -1)
這個觸發條件必須是 32 bit EFL 與buf+size
之後不會蓋到 kernel space,也因此在 64 bit 的系統載入 32 bit 可以觸發這個漏洞,在 32bit 運行 32bit ELF 因為unit
大小相加後,會與 kernel space 相疊導致ok_access()
無法通過,參見 2manypkts write-up (Nuit du Hack CTF Quals 2017) ,這個漏洞在 x86_64 運行 x86 ELF 與 aarch64 運行 arm32 ELF 皆可觸發。
參見
CTF PWN