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


  • 數組越界

    數組越界,是剛開始學習編程時,就不斷被別人提醒的一個點,“相當可怕”。獲取不合理數值,造成程序異常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(函數調用框架),還是想扯點東西的,畢竟專門去了解了一堆,但是看網上人家都說現在都不用棧幀這些了,不知諸君想看不。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM