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

Day6-Basic buffer overflow

Lab 網址 前言 經歷了前面的許多基礎知識與工具介紹,終於來到了第一個漏洞與第一個 lab。而這個漏洞可以說是最簡單也最基本的,就是 Buffer Overflow。 介紹 首先,先來看一個簡單例子。假如一個程式宣告了一些變數及字串,記憶體中的狀態可能會是下圖的樣子: 如果對 buf 字串輸入正常大小的資料,例如這邊宣告的空間是 0x10(16 bytes),輸入 0x10 個 ‘A’,記憶體狀態可能會像下圖這樣: 然而,當使用一些不安全的函式來輸入數據時,例如 gets 函式,可能會產生問題。因為 gets 不會檢查輸入的長度,因此可能會覆蓋到 buf 後面的變數,甚至是 rbp 或 return address。以下例子顯示了覆蓋到後面變數 num3 和 num4 的情況: 如果對填入的值進行精確的編排,則可能成為如下所示的情況: 這就是最基本的 Buffer Overflow 例子。 Lab 查看以下程式原始碼 1#include<stdio.h> 2int main(){ 3 setvbuf(stdout, 0, 2, 0); 4 setvbuf(stdin, 0, 2, 0); 5 setvbuf(stderr, 0, 2, 0); 6 int a = 10; 7 printf("Please input your name: "); 8 char b[10]; 9 gets(b); 10 if(a == 0xdeadbeef){ 11 system("/bin/sh"); 12 } 13 printf("Hello, %s\n", b); 14 return 0; 15} 使用以下指令進行編譯 ...

2024-09-20 · 1 min · 213 words · YJK

Day5-How to use GDB

簡介 GDB 是 GNU Debugger 的縮寫,它是一個功能強大的 command line debug 工具,支援多種程式語言如 C、C++、Fortran 等等。當我們在進行開發時,如果程式在執行過程中發生 segmentation fault 或其他錯誤,GDB 可以幫助我們追蹤程式 crash 的原因。我們可以透過設置中斷點、檢查變數內容等,來針對特定問題進行 debug 改造? 原生的 GDB 有時候可能顯得過於簡單,缺少一些便利的視覺化工具或記憶體分析功能。因此,許多使用者會安裝外掛來補強 GDB 的功能,像是 pwndbg 或 peda 等外掛,可以讓我們更容易地看到 stack、register 等資訊,從而提高 debug 效率 使用 執行程式並設定中斷點: 在 GDB 中,我們可以使用 run 或 r 來執行程式,通常會先設定中斷點(break)在程式的某一個位置,例如 main 函式,這樣程式會在到達該點時停止執行,方便我們進行檢查。 ex: b main 在 main 函式處設置中斷點。 踩到中斷點後要繼續執行程式: 當程式停止於中斷點時,使用 continue 或 c 來繼續執行程式。 觀察 register: 當程式在中斷點停止後,我們可以使用 info 指令查看目前的 register 狀態、函式列表等資訊。例如,使用 info registers 來查看目前的 register 內容。 使用 x 指令可以檢查具體的記憶體位址,通過格式化選項來調整輸出,例如 x/2gx $rax 顯示從寄存器 rax 開始的兩個 giant word。 反組譯執行的程式: ...

2024-09-19 · 2 min · 226 words · YJK

Day4-pwntools & useful tools

前言 這篇文章整理了筆者在練習 CTF 題目時常用的工具,有些工具在後續也會頻繁使用。另外,如果你有興趣一起跟著文章進行練習,可以使用文中提供的 Docker 環境,裡面已經包含了後續教學中會使用到的工具,此 repo 包含後面的題目及環境 環境 Dockerfile F M R R R R R R R R R R R R R R R R U R R R R R R R R R R R R R R U # E E C R A U U U U U U U U U U U U U U U U S U U U U U U U U U U U U U U S X N M O I N N N N N N N N N N N N N N N N E N N N N N N N N N N N N N N E r P T D M N R R u O R T a y a a a a a a a p g g m e s e g c g c r e e e e e e e e e n S Y [ u A p e p p p p p p p i e e k c e c r i d i p m c c c c c c c c c r E P " b I t s t t t t t t t p m m d h d h o t t h h h h h h h h h o t O / u N - - - - - - - - 3 i o o o ~ ~ - o o o o o o o o o o h 2 I u n E g | g g g g g g g i i r - t c / c / r t e 2 N s t R e e e e e e e e i n n ' i ' l p l P f " " " " " " " " " T r u t u t t t t t t t n s s P % o w o w s s s d p i a e e s / : Y n s t t v e ' s n n n n ~ o o e y m n n n e [ s 2 J u m i i i i i i i t a a a r s u e d e g u u u f t p g d d r " b 2 K p i n n n n n n n a l l r m / d b d . r r r i h o e " " v / i . d n s s s s s s s l l l / i n o h g h b g c c c n o r l i u n 0 a i t t t t t t t l r t u t t / d e e e e n t h c s / 4 t m a a a a a a a s o u E l A t & t . b " e e r s e i l l l l l l l p e n n m l L p & p g i ~ ~ ~ h a a / s z l l l l l l l w c e / p o L s s d n / / / o n p ~ ~ b h e n c _ s t k = : : b i p P P o g . / i d - - - - - - - t o g s y _ ( / i t w w w k e i . . n " y y y y y y y o m a h P s A s / n n n n - ~ l n g g / , o p d d a e L g e g i d g g r / h i d d t t o g g l c r l - g s c L i t i t b d d u . e t b b i " i p c c i u u s t e s u ) t u t g b b n g a _ i i n - n e c c b r b o t w r h p h ~ / / / " d p a n n i D i n - c l y c o o e N u . u / g p a b " n i i " " s g m 6 - a l r / O b s b d w n i g t t , ] i s d u - g d p s d n P . h . b n g n e p h b l d i e s s u A c c i g e i l " r - t b t v t l S o o n d l ~ t h - o s m i g o y l S m m i b h / ~ e - u e a l z w n e o W / / t . e . / a " t r k i d s g e s k D p s . p a g . p ] e v e b p h e ' / : w c p y p d g ( 2 e k t f ' A n w y " / b d ) r y g g i L d u " g i b " i a + - l / L b a d n i p s s + d e e ' g p b i n u u m - e b / t / t i t i t d m v y e c p x ~ n t i o n u t t / w / ~ i ~ l a l e c p n P . t / s v s t s / a / d w . g . . - i m i s m e b n g d p g p m l k s . t g g d b y d i t i e h d c d b i " b n g c b y / / / ~ b i n i g r p s s c s / . n i n e d t s o u p g i t i n p u o h m d w i t t e m n d m o n t ~ t g p e _ o e d / - a - c n r b ~ . t w p e o - s g / g o k y n n a P d o t g f u w b l r h i i t n i s s o n g h g n y n e d i n n 3 b t e c r t p o c t y p a m t p t u h e x o r n d 3 i - f p f i u p t i p l y s t h f o i n l 3 e - v i r t u a l e n v docker-compose.yml ...

2024-09-18 · 7 min · 1455 words · YJK

Day3-x86 asm

Introduction ASM 是 Assembly(組合語言)的縮寫。組合語言屬於低階語言,通常專為特定架構設計。相對於低階語言,高階語言更易於理解和使用。以下是兩者的比較: 層級 優點 缺點 範例 高階語言 易學易懂、除錯容易 執行效率較低、佔用記憶體較多 C、C++、Python 低階語言 執行效率高、速度快 相容性差、不易維護 組合語言、機器語言 學習組合語言的原因之一是許多程式語言在執行前會先編譯成機器語言。儘管近年來的 PWN 題目通常會提供程式碼,但如果要挖掘漏洞或檢測產品韌體,仍需要進行程式碼的逆向分析。因此,我們會將編譯好的程式反組譯成組合語言,以便閱讀和理解。以下是一個 C 程式編譯過程的流程圖: 你需要知道的 ASM Registers Flags Sections Instructions Stack frame Calling Convention Registers(暫存器) 暫存器是處理運算時暫存數值的地方,具有讀寫速度快的特性。根據資料型態大小,暫存器可以分為以下四種: QWORD: 64 bits, Quad Word DWORD: 32 bits, Double Word WORD: 16 bits BYTE: 8 bits 根據功能,暫存器又可以分為以下四種: 名稱 功用 通用暫存器 運算、計數 區段暫存器 指向記憶體區段 指標暫存器 指向堆疊或陣列 旗標暫存器 紀錄狀態(進位、溢位等) 通用暫存器 Register AX、BX、CX、DX、DI、SI、BP、SP R8 ~ R15 64 bit 前綴加 R,例如:RAX、RDI 前綴加 R,例如:R8、R9 32 bit 前綴加 E,例如:EAX、EDI;後綴加 D,例如:R8D、R9D 前綴加 E,例如:EAX、EDI;後綴加 D,例如:R8D、R9D 16 bit 無變化,例如:AX、DI;後綴加 W,例如:R8W、R9W 無變化,例如:AX、DI;後綴加 W,例如:R8W、R9W 8 bit 後綴加 X(X 可替換為 H/L),例如:AH、BL、DIL、BPL;後綴加 B,例如:R8B、R9B 後綴加 X(X 可替換為 H/L),例如:AH、BL、DIL、BPL;後綴加 B,例如:R8B、R9B 區段暫存器 名稱 作用 CS 指向 Code Segment DS 指向 Data Segment SS 指向 Stack Segment ES、FS、GS 指向 Data Segment(選擇性使用) 指標暫存器 名稱 作用 SP Stack Pointer,指向 stack 頂端 BP Base Pointer,指向 stack 的任何位置 IP 指向目前執行指令的地址 SI Source Index,指向資料來源 DI Destination Index,指向資料目的地 旗標暫存器 名稱 作用 CF Carry Flag:最高位元有進位或借位,CF = 1;否則為 0 PF Parity Flag:判斷運算後最低 8 位元的 1 的數量,奇數個 PF = 1;否則為 0 AF Auxiliary Flag:運算後第 3 位元產生進位或借位,AF = 1;否則為 0 ZF Zero Flag:運算後結果為 0,則 ZF = 1;否則為 0 SF Sign Flag:運算後,SF=1 表示負數;否則為 0 OF Overflow Flag:運算後結果溢位,OF=1;否則為 0 TF Trap Flag:用於 Debug,TF=1 時,每次執行一個指令 DF Direction Flag:字串運算,DF=0 從低位到高位;DF=1 反之 IF Interrupt Flag:IF=1 時,接受外部中斷;IF=0 則無法 語法格式 常見的格式有兩種:Intel 和 AT&T。如果有興趣,也可以使用 Compiler Explorer 將程式轉為 ASM 進行觀察。原則上,大家比較偏向使用 Intel 格式,因為它的語法接近正常的程式語言,且更易於閱讀。以下是兩種語法格式的比較: ...

2024-09-17 · 2 min · 408 words · YJK