這個視頻講的比較難,我花了比較長時間來分析,甚至一個點反復很多次,這也是在學PWN的過程中不可避免的,需要堅持和毅力
pwn3:
沒有system,通過ROP調用write的plt入口,執行write函數,并且將gots里的內容打印出來,泄露libc
遠程libc和本地不同。有些大的版本更新會使libc發生大變,所以不能用一個腳本。
改成本地。用一下命令可以看到libc的路徑
so.6只是軟連接,通過以下可以獲得真實路徑
這一題是真正需要我們自己泄露內容的題目。這就需要泄露的過程,但是一次不能把ROP鏈構造完。
先一次構造完試一下,在構造payload時后面應該加上system地址,但是現在并不知道執行完write返回的system地址。其實這里就可以填vulnerable_func函數的地址,這樣就會在執行完后返回read造成棧溢出
再通過payload,重新執行write,打印出想要的內容,程序再執行一次vulnerable_func函數
相當于程序執行流被我們劫持了,我們可以去到已知的地址任意一個地方,一會會返回這個函數,還有一次溢出。
下一步加write參數(三個),elf got表里的write對應表象。第一個參數在低地址,后面在高地址
這是第一次棧溢出,并且執行完之后會重新返回到有漏洞的地方,為了下一次溢出
到這里,我猜前段是為了在write寫入write的libc地址,然后讓程序自己把libc真實地址泄露出來
補充:
p32函數是將整數轉換為字節型數據,可以用u32逆轉換,再轉為16進制。這就是write在內存中的真實地址。p是pack打包,u是unpack解包
繼續找system和bin/sh的地址。這兩個都在libc中,要得到基地址,是用write真實地址-write在libc中的偏移。那么system的地址就是libc的基地址+system在libc中的偏移;bin/sh就是libc的基地址+第一個bin/sh在libc中的偏移
這是第一個payload,執行完后會回到vulnerable_func函數,這就要再次構造payload
就可以獲得shell
花式棧溢出:
棧中有很多棧幀,每個棧幀對應一個函數的執行狀態。但是在棧中所有的內容不都是棧幀,第一個是main函數,在其之前沒有棧幀,在這之前保存了什么內容?
找一道題,checksec,一個動態鏈接的程序
復習:
可以直接帶到這個ida中
白色背景直接存在于程序中,而粉紅色只存在于plt表象
start函數用于布置環境,沒有C語言;棧在棧幀之外存在的內容,如當前執行程序的名字
由于動態鏈接庫,libc start main函數先來到plt,第一次調用,先解析真實地址
ret到start函數末尾
這道題沒有說main函數,但是可以在ida中找到,地址4006D0
這題帶有Canary
這道題需要溢出的數據很大,得幾百
偵測到了緩沖區溢出,但是輸出的內容不一樣,就是因為打開了Canary保護
沒有這個保護,就直接向上溢出
雖然也是可以直接覆蓋,但是返回時先檢查Canary,如果和最開始放的值不同,就會觸發stack chk fail函數,輸出一些東西并且強行退出程序
補充? canary:
如何在ida里判斷是否開啟canary保護?
黃色區域這一行代碼就是對應的canary
對應匯編實現
把一個十六進制數mov到rax,再移到棧上。就是放置canary,在v4
最后原來canary的隨機數會和v4異或,值得是0,就是每一位都相等
匯編里有一個jnz指令,在之前的博客里講過
例題(smashes):
其實這道題就是上面分析的,ctrl+shifft+12,好像又一個flag
flag的意思是服務器上的flag所占用的位置
返回到原代碼
第10行寫入數據,第14行輸出一行“你好,請復寫flag”,第17行讀取輸入,如果什么都沒有輸入,會到LABEL_9,強行退出程序;如果是換行符,就會跳出程序。第22行存的數據就是之前flag內容,這會導致我們輸入的內容將服務器上真正的flag給復寫掉。如果是跳出程序,之后也會將flag清零。
就是說如果按照它的程序是肯定拿不到flag
補充:elf很小的時候,可以在編譯的時候通過某些選項,elf中的某些節和段會在虛擬內存中映射兩份
在600D40有一份,在0X400d20這里也有一份
data段被映射到了這兩個位置,就是即使600被復寫了,400還會有,寫的內容一樣
按理說應該不能棧溢出,但是已經寫好的payload里有一次
補充:這里的main函數沒有被特殊符號標記,使用了strip,使gdb辨別不出來有main函數。需要通過ida里函數地址
這里可能還有其它保護,致使沒法調試,這里老師遇到了點問題。可能是這道題的libc太老,而新的會把漏洞給修復掉
還是要學習一下這道題的知識
這里變成了0,應該就是漏洞被修復的位置
這行應該是程序名,所對應的字符串的地址,向上溢出到canary,觸發造成程序強行終止,在終止前會將程序名稱打印出來。然后取這個地址,到這個地方尋找程序絕對路徑名稱的字符串。把這個指向路徑名的地址篡改成指向flag的地址,報錯絕對路徑名,會把flag的值當作程序路徑名打印出來
棧遷移:
會碰到下面情況
長度不夠:到prev ebp就結束了
思路:把這個棧的內存區域轉移到另一個內存區域
只覆蓋ebp,還能劫持執行流。匯編后面兩個指令leave和ret,leave就是相當于mov esp,ebp和pop ebp,之后esp繼續抬高一個字長,ebp就會指向剛開辟的可控制的區域,就不在原來的棧里
第二個子函數后面也是這樣
先用ppt展示一下
最簡單的情況就是溢出的并不只是一個長度(不只ebp),還能向上溢出一些距離。任務就是要把棧遷移到另一個區域,控制了棧的兩個寄存器ebp,esp,就可以欺騙操作系統,控制到另一個區域。另一個區域有我們設計的gadget,能完成一次完整的攻擊
怎樣遷移呢?會有一個函數棧溢出,覆蓋上面的內容。函數最后使leave,ret,會將ebp篡改為想要的值。進行溢出時會pop ebp到惡意覆蓋的地址,還有esp。就需要依靠返回地址創造的gadget,可以leave或pop,將esp移到ebp。可以在新的空間內構造數據完成攻擊流程
這里可以發現ebp幫助esp標記了一個返回的位置,而且數據讀寫都在棧底。就是說不管怎樣,只要把esp控制了就行
大體是這樣,其實有很多種,比這難,聽著很乏味,需要看具體題目
例題 PWN3 X64:
拖入ida
跟上一題邏輯,漏洞一樣,就是變成了64位
構造兩次ROP,調用write,打印write的plt地址,再返回這里,再溢出,ROP,調用system,bin/sh。就是構造payload時傳參會有不同,參數用寄存器來存放
payload:
第一個payload調用write,并寫入got表。從ret addr開始,寫入三個參數,rdi,rsi,rdx,需要相應的gadget控制。
找到了rdi;rsi是r15 ret,寫入write got表地址,pop r15要寫入一些垃圾數據;第三個參數是向屏幕輸出的參數的最大長度,沒有rdx,只能賭運氣,就是rdx的值>8。回到vulner func
第二個payload,先rdi ret,然后bin/sh,最后system
格式化字符串漏洞:
以一個代碼為例
對于這個printf,輸出一些參數,%加數字加字母,這個就是格式化字符串
后面的一些給刪去,還是會輸出,就是printf函數的缺陷
有時候后方并沒有檢查格式化參數數量和printf后方接收到的參數數量是否一樣
補充:
這個命令是打開腳本的調試模式
調試運行一下,其中gcc -g可以帶著源碼
會顯示發送的數據和接受的數據,還按字長對齊了。每一步很清楚
調試一下上面代碼,這里出問題了,另舉例
printf輸出時,前面有幾個%x,后面有幾個參數,最好是整型,那么就會按照十六進制格式打印出來
但是把后面參數去掉,會打印出來一些奇怪的數據,對照一下
再沒有給任何參數的情況下,直接把棧上的內容打印了出來
這是為什么呢?在X86的情況下,printf棧幀,先向上數2個字長(ebp,ret addr),接著找自己的參數,例如%x%x%x%x,要向上找四個字長,就會跳過那兩個字長,向上四個字長的值作為參數,而這四個字長應該是由父函數放置(子函數的參數由父函數壓入)。
在call時如果后面沒有參數(a,b,c,d),高地址就沒有這四個值,但是父函數的內容還在。還是會向上找四個參數,而這現在就是父函數的內容,就會把父函數的一些地址給泄露出來。這是格式化字符串的基本原理
后面會更加深入的講格式化字符串,再往后就會講堆。