Day16-PLT to Libc

Lab 網址 前言 昨天我們示範了如何在沒有拿到 libc base 且程式是動態鏈結的情況下,透過 PLT 拿到 shell。不過那是因為程式一開始就有呼叫一次 system 函式,使得在編譯過程中產生了 GOT 和 PLT。如果沒有呼叫過該函式,是不是就無法被攻擊了呢?其實不是,因為我們可以透過 PLT 和 GOT 嘗試取得程式中的 libc base,並進行 ret2libc 攻擊。 plt & got leak libc 如之前所提,GOT 會存儲外部函式的地址。即便我們不知道具體的 libc base,也可以將某些函式的 GOT 地址作為參數,傳遞給可以輸出資訊的函式,如 puts,來洩露 libc 地址並計算出 libc base。具體上傳遞參數的過程會像這樣:puts(got_address)。 這裡需要注意,若程式的 RELRO 是 Partial,我們需要函式的地址被解析完成後才能使用。有些讀者可能會問:既然我們不知道 libc base,那要如何呼叫 puts 呢?實際上,我們可以直接呼叫 PLT,因為 PLT 會自動從 GOT 中取出地址,也就是 libc 內部的函式地址。如此一來,我們就能成功呼叫 puts 並取得 libc base,進而進行簡單的 ret2libc 攻擊。 Lab 查看以下原始碼: 1#include<stdio.h> 2#include<stdlib.h> 3#include<unistd.h> 4 5void pop_rdi(){ 6 __asm__("pop %rdi; ret;"); 7} 8 9int main(){ 10 setvbuf(stdout, 0, 2, 0); 11 setvbuf(stdin, 0, 2, 0); 12 setvbuf(stderr, 0, 2, 0); 13 char message[0x10]; 14 puts("Welcome to challenge!"); 15 puts("Leave a message: "); 16 read(0, message, 0x100); 17 return 0; 18} 使用以下指令進行編譯: ...

2024-09-30 · 2 min · 354 words · YJK

Day15-ret2plt

前言 昨天剛學完神奇的 one gadgets,今天我們將回到 PLT 表,介紹 ret2plt 的攻擊手法。如果沒有足夠的 gadgets 可供使用,並且無法 leak libc,我們可以將目標轉向 PLT。這種技巧通常被稱為 ret2plt。 ret2plt 顧名思義,ret2plt 就是將執行流程返回到 PLT 表上。那麼,為什麼這樣做可以取得 shell 呢?我們先來看幾個例子: write(1, "ret2plt", 7) 在 stack 上的狀況會是: puts("ret2plt") 在 stack 上的狀況會是: 這些例子中,實際上都是輸出 “ret2plt” 這個字串。如果有可以利用的 PLT 表,就可以用更短的 chain 完成,而不需要尋找過多可用的 gadgets。類似的: system("/bin/sh") 在 stack 上的狀況會是: 這代表我們可以先透過 gets 或其他可以寫入的 PLT 將資料寫進可寫區域,然後把該區域當作 system@plt 的參數,這樣就可以將 /bin/sh 或 sh 寫入並呼叫 system@plt。如此一來,便能開啟 shell。 簡單來說,ret2plt 是透過 binary 中已有的函數,先準備好參數,然後直接呼叫這些函數來達成攻擊目的。即使在無法 leak libc 的情況下,也能透過返回 PLT 表來使用該函數,這就是 ret2plt。 Lab 查看以下程式碼: ...

2024-09-29 · 2 min · 280 words · YJK

Day14-Other ret2libc(one gadgets)

Lab 網址 前言 昨天的內容利用了 libc 進行攻擊,不過實際上我們使用的 gadgets 長度既不算長也不算短。而且在之前的題目中,我們常常隨意填充 rbp,那麼 rbp 真的沒有用嗎?事實上,它是有作用的。像是 one gadget 這個工具,就會使用到 rbp,而且它能使 gadgets 的長度最短,因為它只需要控制 rbp 和 8 個 byte 的 return address 即可。 One Gadget 在函式庫中,有些函式會呼叫 execve('/bin/sh', argv, envp),簡單來說就是可以拿來開啟 shell。像是 system(cmd) 可能會執行 fork() + execve('/bin/sh', ["sh","-c",cmd], environ)。因此,如果跳到 system 的某個中段,最終可能會執行 execve('/bin/sh', argv, environ)。 不過,雖然這樣說,直接跳過去不一定會成功,但通常有很多個 one gadget,所以多試幾個總會有成功的可能。如果你想進一步了解工具的運作方式,或者想手動尋找 one gadget,建議參考 david942j 在 HITCON CMT 2017 的議程 david942j - 一發入魂 One Gadget RCE。 使用 使用以下指令安裝 one gadget 工具: 1gem install one_gadget 安裝完成後,只需要針對 libc 找出可用的 one gadget,指令如下: ...

2024-09-28 · 2 min · 414 words · YJK

Day13-How to change libc & ret2libc

前言 昨天介紹了 ROP,當程式使用靜態鏈結時,我們可以利用更多 gadgets 組合出 ROP chain。那麼,當程式為動態鏈結時,還有其他方法能夠取得 shell 嗎?其實有一種方法與 libc.so 有關,這種技術被稱作 ret2libc。 How to change libc 首先,由於每個人的環境和遠端環境可能不同,因此操作系統與 libc 版本也不盡相同。我們會希望在本地測試的程式與遠端題目盡量一致,所以我們通常會更換 libc 和動態鏈結器。以往題目不常附帶 libc,這時可能需要自己從 libc database 等網站找資料。然而,有些題目(像筆者的 lab)會提供包含題目完整環境的 Dockerfile。我們可以按照以下步驟將題目環境架設起來: 執行 docker-compose up -d 執行 docker ps 並記下容器的 ID 使用 docker exec -it [ID] /bin/bash 進入容器 使用 ldd 確認 libc 路徑與檔名 使用 docker cp [ID]:[檔案路徑] . 將檔案從容器中複製到本地 使用 ls -al 會發現 ld-linux-x86-64.so.2 其實是連結到另一個檔案,因此需要再多複製一次 接下來,我們需要使用 patchelf 來修改 ELF 檔案。下載 patchelf 後,執行以下命令進行 patch: ...

2024-09-27 · 4 min · 794 words · YJK

Day12-Basic ROP

Lab 網址 前言 昨天的 shellcode 是在記憶體區段有執行與寫入權限的情況下才可以執行,那如果程式啟用了 NX 保護,且沒有任何變數的記憶體區段有執行權限呢?這就要提到今天的內容:ROP (Return Oriented Programming)。 static or dynamic linking 觀察以下程式碼,並使用底下的編譯參數分別編譯: 1#include<stdio.h> 2int main(){ 3 printf("Hello World!\n"); 4 return 0; 5} 1gcc test.c -static -o static 2gcc test.c -o dynamic 透過各種資訊查看兩者差異,這裡使用 ls、file 和 ASM 的方式比較。 ls -al static dynamic 可以發現 static 檔案的大小明顯大上許多。 file static dynamic 可以看到一個是 static linking,另一個是 dynamic linking。 ...

2024-09-26 · 3 min · 545 words · YJK

Day11-Shellcode

前言 從今天開始,內容會變得更加困難。之前的題目可能有留一個後門函式,讓你可以直接開啟 shell,或是使用 system 函式來方便後續的操作。然而,現實中基本不會有這種情況,所以我們需要學習更多漏洞利用的方法,才能更符合實際的需求。 Shellcode 根據之前提到的編譯過程,經過簡化後,最終會變成下圖所示的形式: 簡單來說,最後真正執行的部分是機器碼,而這正是本章節要教授的內容:Shellcode。之所以稱為 Shellcode,是因為我們透過編寫 Assembly 程式碼,將參數寫入指定的位置,並最終呼叫 syscall,來達成我們的目標,例如開啟 shell 或讀取特定檔案。 Syscall syscall 即為 System Call,是用來與 Kernel 進行溝通的呼叫。在 CTF 中,常見的有以下兩種: execve("/bin/sh", NULL, NULL) open、read、write 這些可以分別用來啟動 shell 或進行任意的檔案讀寫。這個網站可以幫助查詢呼叫各種 syscall 時應該將各個暫存器設置為什麼值,這與前面提到的 Calling Convention 是相關的。 如何編寫 Shellcode? 編寫 Shellcode 有幾種方法,以下是幾個選項: 自己寫 Assembly,並透過 pwntools 進行轉換。 從 shellcode database 中找到需要的 Shellcode。 使用 pwntools 中的 shellcraft,記得指定 context.arch。 Lab 查看以下程式原始碼: 1#include<stdio.h> 2#include <unistd.h> 3#include <sys/mman.h> 4char shellcode[0x100]; 5int main(){ 6 setvbuf(stdout, 0, _IONBF, 0); 7 setvbuf(stdin, 0, _IONBF, 0); 8 setvbuf(stderr, 0, _IONBF, 0); 9 unsigned long addr = (unsigned long)&shellcode & ~0xfff; 10 mprotect((void *)addr, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE); 11 printf("Give me shellcode: "); 12 read(0, shellcode, 0x100); 13 printf("Overflow me: "); 14 char buffer[0x10]; 15 gets(buffer); 16 printf("Bye!\n"); 17 return 0; 18} 使用以下指令進行編譯: ...

2024-09-25 · 2 min · 257 words · YJK

Day10-GOT Hijacking

前言 前一天我們介紹了 GOT 和 Lazy Binding 的機制,今天要介紹的是如何利用這些機制進行攻擊,也就是 GOT Hijacking。 簡介 由於 Lazy Binding 的機制,GOT 是可寫的。因此,如果能夠覆蓋 GOT 的值,那麼下次呼叫該函數時,就可以控制即將執行的函數指針。這種情況通常出現在陣列未驗證輸入範圍、記憶體越界(即所謂的 out of bounds,oob)或是透過格式化字串(format string)將特定值寫入 GOT 的情況。 Lab 1#include<stdio.h> 2#include<stdlib.h> 3 4int backdoor(const char *arg){ 5 system("/bin/sh"); 6} 7 8long long value[4]; 9 10int main(){ 11 setvbuf(stdin, 0, 2, 0); 12 setvbuf(stdout, 0, 2, 0); 13 setvbuf(stderr, 0, 2, 0); 14 long long index; 15 for(int i = 0; i < 4; i++){ 16 puts("Enter a index to store a value: "); 17 scanf("%lld", &index); 18 puts("Enter a value: "); 19 scanf("%lld", &value[index]); 20 } 21 if(value[0] != 123){ 22 puts("CHECK FAILED\n"); 23 exit(0); 24 } 25 if(value[1] != 456){ 26 puts("CHECK FAILED\n"); 27 exit(0); 28 } 29 if(value[2] != 789){ 30 puts("CHECK FAILED\n"); 31 exit(0); 32 } 33 if(value[3] != 101112){ 34 puts("CHECK FAILED\n"); 35 exit(0); 36 } 37 puts("CHECK PASSED\n"); 38 return 0; 39} 使用以下指令進行編譯(注意,預設編譯會是 Partial RELRO): ...

2024-09-24 · 2 min · 243 words · YJK

Day9-Lazy binding & GOT

簡介 如果大家經常使用 file 指令來查看檔案資訊,應該會經常看到像下面這樣的一大串訊息: 你會注意到其中有一個資訊是 dynamically linked,這表示這是一個動態鏈結的程式。動態鏈結意味著程式在執行時,會從外部函式庫載入一些函式,例如常見的 printf、scanf 等。 Lazy Binding Lazy binding 是動態鏈結程式的一種機制。當程式包含一個或多個函式庫時,不一定會使用到所有的函式。換句話說,有些函式庫中的函式可能永遠不會被執行。 Lazy binding 的機制是在程式首次呼叫某個函式時,系統才會查找該函式的位置,並將其填入 GOT 表(Global Offset Table)。後續的呼叫則會直接從 GOT 表中取得函式的位址。那什麼是 GOT 表呢? GOT 前面提到,函式庫中的函式位址是在載入時才決定的,因此在編譯階段無法得知這些函式的具體位址。GOT 表儲存了這些函式的指標,而程式在一開始執行時,並不會直接存取 GOT 表,而是先透過 PLT 表(Procedure Linkage Table)中的 offset。 例如,在下方的反編譯程式碼中,我們看到在 main 函式中呼叫了 printf@plt,而 printf@plt 最終會跳轉到 printf@GLIBC_2.2.5,也就是實際在 libc 中的 printf 函式。 攻擊? 大家可能會認為,這樣通過外部函式呼叫應該不會有什麼問題。但仔細想一想,事情或許並非如此。如果 PLT 表或 GOT 表可以被修改或重寫,那這樣的機制就不再安全了。 例如,將 printf 函式的指標改寫成 system 或者後門函式,甚至控制傳入的參數,這樣的情境可能會導致嚴重的安全漏洞。而這部分的內容將會在明天探討,也就是 GOT Hijacking 的攻擊技術。

2024-09-23 · 1 min · 60 words · YJK

Day8-format string bug

What is a Format String? 在 C 語言中,printf 和 scanf 是兩個常用的函式,它們透過格式化字串來處理參數。例如: scanf("%s", s); printf("Hello, %s\n", s); 這兩個例子會讀取格式化字串,並將 %s 解析後替換為傳入的參數。 Format String Vulnerability 如果在撰寫程式時未遵循安全方式,例如使用 printf(buf) 直接傳入變數而非格式化字串,這樣的漏洞可能會導致我們可以洩漏變數或 stack 的殘值。因此,有機會取得如 PIE base、libc base 和 canary 的值,甚至可能修改任意變數或記憶體中的值。如果能成功修改記憶體的值,便能改變程式的執行流程,例如往後會提到的 GOT Hijacking 攻擊。 Format String 格式 格式 %[parameter][flags][field width][.precision][length]type Parameter 可忽略 n$ 表示顯示第 n 個參數 printf("%3$d %2$d %1$d\n", 1, 2, 3); 輸出結果為 3 2 1 Type d/i、u:整數 x/X:十六進位 o:八進位 c、s:字元、字串 p:指標 n:可寫入變數 寫入資料 printf("Hello%n\n", &a); 此時 a=5,因為 Hello 字串佔了 5 個字元 Lab 本章節沒有提供 Lab,但接下來的內容會將此主題應用於實際的 Lab 中,例如 leak 可以繞過各種保護機制的資訊或更改任意記憶體的值。大家可以期待之後的實作內容。 ...

2024-09-22 · 1 min · 91 words · YJK

Day7-ret2code (ret2func)

Lab 網址 前言 經過了前一天的 lab,相信大家對於 buffer overflow 的原理已經有了更多的理解。既然我們能覆蓋判斷式中的值,那麼是否也能覆蓋 return address,讓程式跳到其他位置呢?這正是我們今天要介紹的攻擊技術:ret2code,又稱為 ret2func 或 ret2win。 介紹 回顧之前的 stack 內容,當使用 gets 讀取 buf 字串時,可以持續覆蓋記憶體,甚至可能覆蓋到 rbp 和 return address 例如,如果我們想跳到名為 shell 或 win 的函數,可以嘗試將 stack 覆蓋成如圖所示的狀態。當程式執行到 return address 時,它就會跳轉到我們覆蓋的位置。 雖然在現實情況下,程式中不太可能直接有開啟 shell 的 function,但在一些初學者的 CTF 題目中,這類控制程式執行流程(control flow)的題目仍然存在。在進入實作之前,我們要了解這種攻擊的前提條件是:需要關閉 PIE 保護,因為我們必須知道確切要跳轉的 function 位置。 Lab 查看以下程式原始碼: 1#include<stdio.h> 2 3void shell(){ 4 system("/bin/sh"); 5} 6 7int main(){ 8 setvbuf(stdout, 0, 2, 0); 9 setvbuf(stdin, 0, 2, 0); 10 setvbuf(stderr, 0, 2, 0); 11 char b[10]; 12 puts("Send me a message: "); 13 gets(b); 14 return 0; 15} 使用以下指令進行編譯: ...

2024-09-21 · 2 min · 386 words · YJK