Day20-Stack 題目練習

Lab 網址 前言 今天大致來到 Stack 部分的尾聲,前面的內容我們討論了幾個經典的 Stack 漏洞,講解了如何進行攻擊並編寫 exploit,也介紹了不少實用的工具。今天,我們將實際練習一道程式碼量較大的題目,這是我在 HITCON 社團攤位時設計的練習題目。 Lab 查看以下原始碼: 1#include<stdio.h> 2#include<stdlib.h> 3#include<unistd.h> 4int choice,num; 5char *product[3] = { 6 "shirt\n", 7 "sticker\n", 8 "tissue\n" 9}; 10 11void buy(){ 12 printf("We have 3 products:\n"); 13 for(int i=0; i<3; i++){ 14 printf("%d. %s", i+1, product[i]); 15 } 16 printf("Which one do you want to buy? "); 17 scanf("%d", &choice); 18 printf("You have bought %s\n", product[choice-1]); 19 printf("How many do you want to buy? "); 20 scanf("%d", &num); 21 return; 22} 23 24char address[0x20]; 25int main(){ 26 setvbuf(stdout, 0, _IONBF, 0); 27 setvbuf(stdin, 0, _IONBF, 0); 28 printf("This is HackerSir!\n"); 29 printf("Welcome to the shop!\n"); 30 printf("1. Buy\n"); 31 printf("2. Exit\n"); 32 printf("Your choice: "); 33 scanf("%d", &choice); 34 if(choice==1){ 35 buy(); 36 printf("Please leave your address: "); 37 scanf("%s", address); 38 printf(address); 39 }else{ 40 printf("Goodbye!\n"); 41 exit(0); 42 } 43 char last[0x10]; 44 printf("\nleave your last message to our club: "); 45 read(0, last, 0x30); 46 return 0; 47} 使用以下指令進行編譯: ...

2024-10-04 · 3 min · 464 words · YJK

Day19-Shellcode Bonus-ORW

Lab 網址 前言 今天我們將介紹一些簡單的知識,並在最後提供一個練習 Lab。看完前面的內容,大家可能會想:「如果我們可以調用 execve,那是否就能拿到 shell 呢?」這確實沒錯,但還有一種方法可以防止使用者調用 execve 系統呼叫 (syscall),這就是 seccomp 的機制。 Seccomp (secure computing mode) seccomp 是 Linux 核心用來禁用特定系統呼叫的機制,透過 Seccomp BPF 可以設定對某些 syscall 的過濾規則。例如,可以限制 execve,甚至 open、read、write 等等。 但在實作這些規則後,逆向工程的過程可能變得不直觀且困難理解。為了簡化分析,one gadget 工具的作者還開發了一個工具,叫做 seccomp-tools,非常適合這類情況。 seccomp-tools 筆者提供的環境中已經安裝了該工具 這個工具能分析程式的 seccomp 規則,並將結果轉換成直觀的 if-else 形式的 pseudo code。這讓我們能輕鬆看出程式允許或限制了哪些 syscall,非常適合處理複雜的 seccomp 規則。 使用方式如下: 1seccomp-tools dump ./[binary] Lab 查看以下原始碼: 1#include <stdio.h> 2#include <sys/mman.h> 3#include <unistd.h> 4#include "seccomp-bpf.h" 5 6void apply_seccomp() { 7 struct sock_filter filter[] = { 8 VALIDATE_ARCHITECTURE, 9 EXAMINE_SYSCALL, 10 ALLOW_SYSCALL(read), 11 ALLOW_SYSCALL(write), 12 ALLOW_SYSCALL(open), 13 KILL_PROCESS, 14 }; 15 16 struct sock_fprog prog = { 17 .len = (unsigned short)(sizeof(filter) / sizeof(struct sock_filter)), 18 .filter = filter, 19 }; 20 21 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { 22 perror("Seccomp Error"); 23 exit(1); 24 }; 25 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { 26 perror("Seccomp Error"); 27 exit(1); 28 }; 29} 30char shellcode[0x100]; 31int main() { 32 setvbuf(stdin, 0, 2, 0); 33 setvbuf(stdout, 0, 2, 0); 34 setvbuf(stderr, 0, 2, 0); 35 unsigned long addr = (unsigned long)&shellcode & ~0xfff; 36 mprotect((void *)addr, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE); 37 apply_seccomp(); 38 printf("I add new rule to prevent you from using system().\n"); 39 printf("Give me shellcode: "); 40 read(0, shellcode, 0x100); 41 printf("Overflow me: "); 42 char buffer[0x10]; 43 gets(buffer); 44 printf("Bye!\n"); 45 return 0; 46} 使用以下指令進行編譯: ...

2024-10-03 · 3 min · 446 words · YJK

Day18-Stack Pivoting

Lab 網址 前言 如果我們沒有辦法有足夠的長度或方法 leak libc 或是構建 ROP chain,還有什麼攻擊方式嗎?其實有的,就是將 stack 直接移到其他區塊並執行事先寫在指定區域的 chain。將 stack 遷移到其他地方的方式就叫做 Stack Pivoting 或 Stack Migration。 Stack Pivoting Stack Pivoting 是將 ROP chain 分次寫在指定區域,最後將 stack 遷移過去執行。Stack 由 rsp 控制,因此我們需要控制 rsp,主要利用以下的關鍵指令: leave ; ret 實際執行了以下兩個動作: leave -> mov rsp , rbp ; pop rbp ; ret -> pop rip; 由於在每次的 buffer overflow 中,rbp 是可以控制的,所以我們可以利用 rbp 加上這個指令來控制 rsp。這些 gadgets 在程式中幾乎都可以找到,因為它們通常用於 function 結束時恢復上一個 function 的 stack frame。 範例 以下範例展示如何將 rsp 遷移到另一個區塊: ...

2024-10-02 · 2 min · 364 words · YJK

Day17-ret2csu

前言 經過了這麼多天的 Lab,今天的內容就輕鬆一下,只講原理,不提供練習 Lab 囉! 如果大家有注意看前兩天的程式碼,應該會發現我都寫了一個 pop rdi 指令進去。這是因為現在較新的編譯器版本不再包含 __libc_csu_init,因此傳遞參數變得相對困難。不過,在有這個函式的情況下,會有更多傳遞參數的 gadgets 可以使用,甚至可以直接用一大段 __libc_csu_init 來設定參數並取得 shell。這種技巧被稱為 ret2csu。 __libc_csu_init __libc_csu_init 是編譯器在編譯時會自動加入的函式,用來初始化 libc 函式庫。由於大部分程式都會使用 libc 函式,這個函式原則上一定會存在(不過現今新版編譯器已不再編入這個函式)。 這是 __libc_csu_init 的部分內容: 仔細觀察可以發現,最底下的這段程式碼非常適合用來放參數以及控制流程。 使用方式 我們可以控制 rbp、rbx、r12、r13、r14、r15,並跳至 gadgets 開頭。對應一下就會發現,r13 和 r14 可以用來控制 rsi、rdx 等暫存器。這樣就能解決很多程式中找不到相關 gadgets 來控制參數的問題。 此外,我們還可以透過 r15 和 rbx 指定任意記憶體位置。接著將 rbx 和 rbp 分別指定為 0 和 1,在呼叫完後確保 rbx == rbp == 1,此時 jne 不會生效,我們就可以繼續使用後面的一連串 pop 指令,最終達成任意 ROP 攻擊。

2024-10-01 · 1 min · 62 words · YJK

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