這學期的pwn又開始了,跟上次不同的是這次以x64為主要平台,算是比較真實一點了
Lab0
這題是訓練你使用pwntools
主要是先有一個問題讓你輸入3735928559(0xdeadbeef)
之後會有一千個隨機的算術問答,要你在90秒內完成
就降,阿解法也很簡單,用eval
就可解決了
阿不過題目會多print一個= ?
記得要把它recv掉
payload長這樣:
或是智障一點的解法是,把他的alarm
改成isnan
然後一個一個算XD
這裡一個注意的地方是上面的0xdeadbeef
要用p32
而不是p64
,被這個表到
因為0xdeadbeef
總共4個bytes,用p64
它會把後面補完8 bytes送出去,這樣在if就會出錯了
執行的畫面:
Lab1
看source code:
執行:
嘗試overflow它:
用gdb去看他的offset:
可以看到雖然RIP還是指向main
但實際上我們已經可以控制RIP了
因為它crash的原因是ret
的部份,
而ret
就是pop rip
所以只要知道stack那個gadget的offset就算控制rip了
或是可以套calling convention的概念,將rbp + 8 = rip,因為rbp也是能控制的,
上面是rbp的offset下面是stack的
rbp的部份還要再加4bytp才能蓋滿rbp,所以20+4 後面的bytes就是開始蓋return address了
跟直接看stack的offset是一樣的
exploit code:
Lab2
source code:
可以看到他有一個strlen擋住,上面的shell function有也有一個int allows = 0;
,所以也不能直接跳到function上面。
前面的問題可以用\0
做null byte截斷,跟strcmp一樣,後面的問題就是不要跳到function就好了,直接跳到它執行shell的那一段。
第一部還是先用gdb找offset:
然後很順利的找到offset
把agaa
拿去對offset得到的是23,strlen的問題解決了,再來是看我們要跳到哪
objdump它:
這裡可以看到,我們要跳的地方是0x4006c7
為什麼呢?
在說這個之前先大概寫一下execve
這個function的使用方式
#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
然後看一下overflow完後registers的狀態
因為跟這張圖可以知道,我們overflow的時候已經把rbp的位置蓋爛了,
而參數擺放的位置都是以rbp為機準下去做相對位置的計算,所以rdx, rsi是廢掉的不能用了
rdx, rsi對應到execv的參數就是char *const argv[],char *const envp[]
那其實影響不大,因為後面兩個參數不是必要的,
我們用execv("/bin/sh")
還是可以拿到shell,
而第一個參數是以rip為基準下去擺的,
當我們控制rip到0x4006c7
的時候rip還是好的,
所以這一條指令lea rdi,[rip+0x11a]
還是可以成功的導到/bin/sh
,
那怎麼知道rdi就是/bin/sh
呢?除了看source code之外,透過radare2也可以:
兩個問題都解決之後就是exploit code了:
Lab3
這題在教怎麼return to shellcode address,
直接看source code:
和checksec:
大概知道就是,我們可以把shellcode放在message,然後在第二個輸入的時候overflow他
跳到shellcode的位址執行shellcode(因為沒開NX)
然後payload大概是這樣子:
Lab4
source code:
題目有給我們可以任意改寫got的
簡單來說,我們可以把shellcode寫進第一個read的記憶體內,然後在scanf給puts_got的address
在第二個read給name的 address 的內容,這樣在下一行printf("Thank you")
就會被替換成shellcode執行
payload:
Lab5
這一題拿到後,我們一樣先checksec
嘗試執行它:
可以看到是一隻很簡單的程式
那我們用objdump看他的got table看看
可以發現他沒有用到 got table, 這邊有兩個結論,因為透過剛剛的執行發現他是有用到printf或是puts的function的
代表有兩個可能,一個是他用static的方式把libc的function包進ELF裡面,另一個是他自幹一個printf/puts的function,
前者的可能性大一點(誰沒事會自幹一個printf),如果是static的compile方式,
那就可以用ROPgadget
這個工具去找他有哪些可利用的指令來構成我們要的syscall,
由於他有開stack canary,為保起見我們進去看他的main是不是真的有開,
下面是我找的一個真的有開stack canary的function
下面是rop的main function
可以看到有stack canary的function在做完calling convention之後還會再塞一個值在return address之前
但rop的main function卻沒有,所以可以判定我們可以透過main 進行overflow的動作。
那rop chain要怎麼構呢?
借一張圖來說明:
詳細的syscall可以參考這裡:
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
就如上面所說,我們用ROPgadget
看看他有哪些碎片能用
ROPgadget --binary rop > ropgadget
可以用grep等等的指令方便我們尋找是否有需要的指令
基本上就如同上面的圖一樣
我們先讓rdi
指向bss
區段, 然後把/bin/sh
放進rsi
裡面
再把rsi
的值放進rdi
指向的記憶體位置(也就是bss段),然後再把rdx
和rsi
歸零,
把rax改成0x3b後叫syscall
來執行
就構成execve(/bin/sh)
了
exploit code:
P.S 仔細讀了一下後才知道原來rdi
要吃記憶體位置….
59 sys_execve const char *filename const char *const argv[] const char *const envp[]
那個星號rrrrrr, 我還傻傻的以為放字串進去也可以(對就是那些註解在嘗試的)。
Lab6
這一題考驗的是ret2plt,其實相對比較簡單…
先看source code :
checksec:
可以看到有一個local buffer 和 global buffer, 只要沒開PIE我們可以知道global buffer的位址
然後我們還發現了他有用system
這個function, 有了去年木棍大大的教課,可以直接想到如何構exploit code
我們先用第一個global buffer 寫入/bin/sh
然後在第二個local buffer進行overflow跳到system
參數就放global buffer的位址,就達到system('/bin/sh')
這個的功能了
1 | 為什麼不能直接放字串呢?因為~ |
但這邊要注意一下,這裡不同於x86的地方是,argument是存放在rdi
而不是stack
內,因此我們還要進 objdump 裡面看有沒有pop rdi
,這樣放進stack裡面
的/bin/sh
才會被pop到rdi上,pop rdi
的opcode是5f
幸運的是,我在objdump內有發現這個opcode, 而且後面正好接著return
global buffer 也可以在objdump內輕鬆找到(突然覺得PIE很重要了吧)
再來就是取得他overflow的offset, 由於我的gdb怪怪的,所以換到ubnutu來做
啊這台沒有peda的套件,所以會比較醜一點,不過不會影響到結果
0x6161616861616167
= haaagaaa, 考量到little endian我們取小的gaaa
= 24
exploit code
#Lab7
checksec:
GOT table:
有NX, 沒PIE和stack canary 第一時間猜測可以構ROP去串
但題目寫ret2libc,所以除了ROP 之外,我們可以透過buffer overflow
return to libc 從而構出一個system("/bin/sh")
source code:
看完source code之後發現好像更簡單,他直接幫你leak出libc address,而且連libc檔案都給了
所以可以不用透過buffer overflow去leak,但這樣感覺太簡單了,所以我自己的作法應該會預設
程式不給你leak libc address而且沒給你libc的檔案
這跟去年木棍的pwn basic會很像,但因為是x64架構,所以做法上來說還是會有不一樣的地方
我們先看看這支程式有沒有湊齊我們所需要的gadget
看起來是沒有,官方的解答是從libc裡面找到需要的gadget,因為有libc的檔案
所以可以很輕易的得到libc base和offset,所以直接拼ROP chain 構syscall
但如前面所說,覺得這樣不夠好玩
所以這邊應該是,我們用buffer overflow去leak出puts的
libc address然後透過libc的檔案取得puts的address
把這兩個相減就會是libc base
同樣方式透過libc檔案取得的system
和/bin/sh
兩個address
再去跟同個檔案內的puts address相減就可以取得offset
把libc base加上offset就可以得到對應的address從而構出system(“/bin/sh”)
用寫的感覺有點不清楚,這邊圖文解說
首先先動態分析出他的offset是56
然後可以透過gdb得到他的libc位置
如果是要用遠端的話則要用題目給的libc
用pwntools的ELF
的function去得到這些address
有了libc位置後,我們還需要能leak出current libc address,
前面說了用buffer overflow可以做到,所以我們來做一次
前面我們用400000
去糖塞他,我只需要一個input就夠了
因為x64跟x86還是有點不一樣,他的第一個參數是放在rdi這個暫存器
所以我們要先找到pop rdi
這個opcode前面說了是5f
,但除了5f
之外
我們下一個opcode要是c3
才能讓我們繼續控rip
那實際執行下來就是這樣:
我們還需要recv去接這個input
並且跳回main去執行我們第二次的overflow, 這邊我是跳回libc_start_main
這邊就直接付exploit code了
這邊說一下recv puts current libc address 那部分
雖然x64的記憶體位置長度是8 bytes, 但他不一定會用滿8 bytes,
我自己測試後發現puts 的 libc address 是6 bytes
於是我recv前六個bytes, 再把前面補0就是puts 的 libc address