ntust_pwn

2019-03-17

這學期的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段),然後再把rdxrsi歸零,

把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
2
3
4
5
為什麼不能直接放字串呢?因為~

int system(const char *command)

他是要記憶體位置

但這邊要注意一下,這裡不同於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