1. 程式人生 > >【匯編雜項】關於_高級語言中 數組越界與匯編中 棧溢出的_聯系的思考

【匯編雜項】關於_高級語言中 數組越界與匯編中 棧溢出的_聯系的思考

edi bits 關註 學習 type 控制 方便 設計 滿足

  • 數組越界

    數組越界,是剛開始學習編程時,就不斷被別人提醒的一個點,“相當可怕”。獲取不合理數值,造成程序異常or操作計算機重要內存,造成威脅。。。原因是什麽呢?數組在匯編中以棧機制實現,其中內存分配的機制與數組越界的風險有很大關系。今天做個小實驗,來簡單探討下這個。

  • 代碼

    先展示問題代碼

1 #include<stdio.h>
2 int main(){
3     int a[3]={0,1,2};
4     for(int i=0;i<=3;i++){
5         a[i]=0;
6         printf("
test"); 7 } 8 return 0; 9 }

    

    諸君很容易看出,第4行for循環內的結束條件設置的顯然有問題,數組a長度位3,顯然下標只能到2,而循環中卻做了一個對所謂a[3]的賦0操作,這就是常說的數組下標越界問題。

    看一下,這個問題代碼給我們帶來了什麽樣的麻煩。。。

    我編譯出可執行文件,運行。。。瞬間屏幕被 “test” 字符串填滿。。。

技術分享圖片

    僅僅兩三秒,就不知做了多少次循環了,計算機運算就是快/xyx/xyx/xyx,展示下頁長。。。

技術分享圖片

    

    顯然,由於這個數組越界的問題,我們陷入了死循環,(瘋狂ctrl+c,終於停了,如圖)

技術分享圖片

  • 思考

    那。。。為什麽會死循環?

    匯編語言裏找問題,用gcc拿出中間匯編文件,查看匯編代碼(沒有采用什麽O1/O2的優化編譯,所以以下仍含有棧幀的概念)。

 1         .file   "test.c"
 2         .intel_syntax noprefix
 3         .section        .rodata
 4 .LC0:
 5         .string "test"
 6         .text
 7         .globl  main
 8         .type   main, @function
9 main: 10 .LFB0: 11 .cfi_startproc 12 push rbp 13 .cfi_def_cfa_offset 16 14 .cfi_offset 6, -16 15 mov rbp, rsp 16 .cfi_def_cfa_register 6 17 sub rsp, 16 18 mov DWORD PTR [rbp-16], 0 19 mov DWORD PTR [rbp-12], 1 20 mov DWORD PTR [rbp-8], 2 21 mov DWORD PTR [rbp-4], 0 22 jmp .L2 23 .L3: 24 mov eax, DWORD PTR [rbp-4] 25 cdqe 26 mov DWORD PTR [rbp-16+rax*4], 0 27 mov edi, OFFSET FLAT:.LC0 28 mov eax, 0 29 call printf 30 add DWORD PTR [rbp-4], 1 31 .L2: 32 cmp DWORD PTR [rbp-4], 3 33 jle .L3 34 mov eax, 0 35 leave 36 .cfi_def_cfa 7, 8 37 ret 38 .cfi_endproc 39 .LFE0: 40 .size main, .-main 41 .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)" 42 .section .note.GNU-stack,"",@progbits

    

    (雜魚懶得刪了,全貼,這是intel風格64位匯編代碼,帶諸君看)

    開始分析,第11行到38行就是main函數了,.cfi_startproc和.cfi_endproc是調用框架指令,用來標記這是一段函數(這裏涉及調用框架,諸君有興趣自行探索)。

    

    12行起到16行:一系列操作據說是函數調用框架的規範步驟,成為前序,就是為了調用main函數和函數返回的正常做的工作,這裏不做深究。

    

    我們看17行,將rsp(棧頂寄存器)減了16字節,這就是為下面的數組及變量開辟空間。接著四步就可以看出一次錄入了 a[0],a[1],a[2],i 的值。接著jmp無條件跳轉到 L2段。

    !!!L2裏就涉及到for循環的控制了,我們開始接近問題的本源了。cmp 操作數,用來比較 [rbp-4] (我們知道這裏放的是變量i) 與3的大小,接著jle(什麽jump when less or equal,差不多這樣),若結果為小於等於則跳轉到L3。

    L3內,上來就把我們的i的值取了出來(因為後面依據下標取數組元素要用到),接著cdqe是將32為寄存器拓展為64為寄存器rax。我們就從出問題的時間點來排查,假設這時i的值已為3(即下標已經越界了),可是到了26行時 i 的值又被賦為了0,這一步其實對應 c 文件裏for循環中 a[i] = 0; 這一步,但是這裏由於棧幀中內存的分配導致越界後操作到了 i 的值。可想而知,程序的邏輯是for循環到 i==4(i<=3) 時結束,而每次 i一到 3 又被我們重置為 0,for循環又如何停止???所以就死循環了唄。

    (附一張main棧幀的簡圖,方便諸君理解)

技術分享圖片

    沒錯,分析到這裏基本就沒什麽問題了,可是學習不止於此。。。

  • 拓展

    既然我們知道死循環是由於 i 變量被非法篡改了,導致無法滿足 i>3 的截止條件,那麽我們可不可以“將錯就錯”,使 i 的值被非法篡改為滿足條件的值(比如4)

    即 a[i]=0; ==> a[i]=4; 那麽匯編就變為 mov DWORD PTR [rbp-16+rax*4],4 。(雖然沒啥意義,但從側面印證了,的確是 i 的值被篡改導致問題)

  • 思考

    幸而這裏只是一個不那麽緊要的變量被改,導致這個小小的程序出錯。然而更多時候,這樣的問題威脅更大:堆棧溢出!這是緩沖區溢出中危害較大的一種了,原理就是我們設計的程序並沒有對接受的數據做長度的檢查,導致該程序分配到的內存空間(棧區/緩沖區)放不下這麽多內容,從而,這些數據被寫入到其他不合理的內存空間,比如上圖中返回地址,一旦被修改,下一條被執行的保不齊就是一條shellcode,系統被別人拿了特權;又或者惡意導致計算機宕機。。。(懂得不多,差不多也就了解到寫。意在引起諸君對棧溢出的關註,無論是以後走安全,還是走馬農,有良好的“意識”)

  • 扯閑篇

    關於CFI(函數調用框架),還是想扯點東西的,畢竟專門去了解了一堆,但是看網上人家都說現在都不用棧幀這些了,不知諸君想看不。。。

【匯編雜項】關於_高級語言中 數組越界與匯編中 棧溢出的_聯系的思考