Day22-Heap 名詞介紹

前言 在昨天的文章中,我們提到 Heap 涉及到許多關鍵名詞與概念,如 chunk 和 bin。由於這些名詞在後續討論 malloc 流程時會高度關聯,因此今天我們先對這些重要名詞做一些簡單介紹,為後續的學習打好基礎。 chunk chunk 是 glibc 在進行記憶體管理時使用的資料結構。例如,當你呼叫 malloc 分配記憶體時,實際上得到的是一塊 chunk。chunk 的最小大小是 SIZE_T (unsigned long int,8 byte) 的四倍。chunk 包含兩部分:chunk header(由 prev_size 和 size 組成)以及 user data,malloc 返回的指標實際上是指向 user data 的位址。 當一塊 chunk 被 free 掉後,它會被加入一個名為 bin 的 linked list 中。根據大小,chunk 可以分為三類:allocated chunk、free chunk 和 top chunk。 allocated chunk allocated chunk 的特殊之處在於當前一塊 chunk 是 free 狀態時,allocated chunk 的 prev_size 欄位會存儲上一塊 chunk(包含 header)的大小。此外,allocated chunk 的 size 欄位還包含了三個 flag: ...

2024-10-06 · 2 min · 320 words · YJK

Day21-Heap 簡介

前言 昨天我們已經結束了 stack 相關的內容,今天要開始介紹 PWN 中另一個非常重要的部分:Heap。相較於 stack,heap 涉及更多基礎知識,是一個需要深入了解的重要環節。 What is Heap? 在程式執行過程中,為了更有效率地分配記憶體空間,會使用動態記憶體配置(Dynamic Memory Allocation)。不同的使用場景會使用不同的記憶體分配器,例如:glibc 使用的 ptmalloc、firefox 的 jemalloc 以及 chrome 的 tcmalloc。而我們所說的 heap,就是這些分配器取得的一塊連續的虛擬記憶體空間。我們接下來的討論主要集中在 glibc 所使用的記憶體分配器上。 malloc malloc 是用來分配記憶體的函數,基本原則是需要多少分配多少。這樣可以提升程式的記憶體分配效率,避免不必要的空間浪費。其實,malloc 的運作過程相當複雜,但若簡單整理,可以理解為:如果分配的 size $<$ 128KB,系統會呼叫 brk 來進行配置;相反,若 size $\ge$ 128KB,則會使用 mmap 進行分配。 main arena 雖然如果分配的 size 小於 128KB 會通過 brk 來向 kernel 申請空間,但實際上並不只分配請求的空間,系統會直接給予 132KB 的 heap 段,這段記憶體被稱為 main arena。 以下是 GLIBC 2.35 關於 struct malloc_state 的程式碼: 1struct malloc_state 2{ 3 /* Serialize access. */ 4 __libc_lock_define (, mutex); 5 6 /* Flags (formerly in max_fast). */ 7 int flags; 8 9 /* Set if the fastbin chunks contain recently inserted free blocks. */ 10 /* Note this is a bool but not all targets support atomics on booleans. */ 11 int have_fastchunks; 12 13 /* Fastbins */ 14 mfastbinptr fastbinsY[NFASTBINS]; 15 16 /* Base of the topmost chunk -- not otherwise kept in a bin */ 17 mchunkptr top; 18 19 /* The remainder from the most recent split of a small request */ 20 mchunkptr last_remainder; 21 22 /* Normal bins packed as described above */ 23 mchunkptr [NBINS * 2 - 2]; 24 25 /* Bitmap of bins */ 26 unsigned int binmap[BINMAPSIZE]; 27 28 /* Linked list */ 29 struct malloc_state *next; 30 31 /* Linked list for free arenas. Access to this field is serialized 32 by free_list_lock in arena.c. */ 33 struct malloc_state *next_free; 34 35 /* Number of threads attached to this arena. 0 if the arena is on 36 the free list. Access to this field is serialized by 37 free_list_lock in arena.c. */ 38 INTERNAL_SIZE_T attached_threads; 39 40 /* Memory allocated from the system in this arena. */ 41 INTERNAL_SIZE_T system_mem; 42 INTERNAL_SIZE_T max_system_mem; 43}; 可以看到這裡有 flag、last_remainder、fastbin 和 bins 等欄位,這些名詞將在後續內容中詳細說明。 ...

2024-10-05 · 2 min · 321 words · YJK

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