iT邦幫忙

2023 iThome 鐵人賽

DAY 5
0

今天又回到正常的雜燴了,所以我們也繼續講 Pwn 吧~


上次提到,當一個程式(program)被載入到記憶體成為一個 process 時,作業系統會為其分配記憶體空間。在那張記憶體配置圖中,我們可以看到 process 被分配到 stack 和 heap 區域。

  • Heap:允許程式在執行過程中根據需要請求和釋放記憶體,通常用於存儲動態創建的對象、數據結構或其他資料。
  • Stack:用於管理程式執行過程中的函數調用和局部變數。

這兩個區域與原本已經預先配置好內容的區域(例如.bss 和.text 段)有所不同,它們是在程式運行期間根據程式的需求動態分配和使用的,也就是因為這個原因,前面才會說到他是較容易 Pwn 的地方,因為若程式沒寫好,就能利用這些漏洞,在程式運行時下惡意的 payload 實現入侵和控制系統的目標。

僅僅只是說 Heap 和 Stack 這兩個區域在程式執行期間允許動態分配和使用記憶體,且它們的管理和安全性容易受到攻擊,所以這些區域較容易 Pwn,而非是只有這兩部分才能 Pwn。

Basic 系列主要會談談如何在 Stack 上達成 buffer overflow(Stack-Based Buffer Overflow)。

到這邊是原先預計的 Basic(1) 結尾 www


前面講到 stack 是用於管理程式執行過程中的函數調用和局部變數,這是甚麼意思呢?

之前有說到程式會被編譯成組合語言,它看起來會像這樣

push   ebp
mov    ebp,esp
sub    esp,0x10

每一行稱為一個 instruction(指令),這是程式中最小執行單位,意思是 CPU 會一個 instruction 一個 instruction 從記憶體中讀取出來並執行。而 CPU 要麼知道要去記憶體哪邊抓 instruction 呢?這就要依靠 register(暫存器)了。

Register 是 CPU 中小型的、可快速存取的儲存區域,用於儲存和處理資料。其中有一個特殊的 register 它會指著下一條 CPU 要讀取的 instruction 位址,這個 register 的名稱在不同的 CPU 架構中有不同的名稱,其中在 x86 架構下稱為 EIP(x86-64 架構下稱為 RIP)

register 通常都是指標的形式,所以 EIP/RIP 的內容是儲存下一條 instruction 的位址的位址(就是指著下一條 instruction 的位址的意思)

由於惡意程式使用 x86 架構,因此本篇主要會此架構,然而現在 x86 架構逐漸被 x86-64 取代,所以可以的話會盡量補充一點。

不管是 x86 還是 x86-64 架構,基本的程式執行流程大致如下:

  1. CPU 從當前 EIP/RIP 儲存的值(instruction 的位址)讀取 instruction。
  2. CPU 執行該 instruction。
  3. 執行完畢後,EIP/RIP 會將儲存的值修改為下一條 instruction 的位址,通常是加上一個 instruction 的大小。
  4. 重複步驟一,繼續執行下一條 instruction。

看起來整個流程非常順暢,可喜可賀~
但還是有個問題~ 想想看如果這時候如果遇到函數呢?
當我們進入函數後,暫存器不會知道應該要加多少才能回到原本下一條 instruction 的位置,所以應該怎麼做呢?

在 x86 架構下會使用 stack 來儲存要傳遞的參數以及回傳地址

在 x86-64 不是使用 stack,因為它暫存器夠多(有本錢),因此是使用暫存器。

我們用實際程式來解釋遇到函數時會如何運作:

void func(int a, int b) {
    a + b;
}

int main(void) {
    int a = 2;
    int b = 3;
    func(a, b);
    return 0;
}

使用 gdb 反組譯 main,可以看到 main 的組語:

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000118d <+0>:     push   ebp
   0x0000118e <+1>:     mov    ebp,esp
   0x00001190 <+3>:     sub    esp,0x10
   0x00001193 <+6>:     call   0x11c0 <__x86.get_pc_thunk.ax>
   0x00001198 <+11>:    add    eax,0x2e5c
   0x0000119d <+16>:    mov    DWORD PTR [ebp-0x4],0x2        \\ int a = 2;
   0x000011a4 <+23>:    mov    DWORD PTR [ebp-0x8],0x3        \\ int b = 3;
   0x000011ab <+30>:    push   DWORD PTR [ebp-0x8]            \\ _
   0x000011ae <+33>:    push   DWORD PTR [ebp-0x4]            \\  |
   0x000011b1 <+36>:    call   0x117d <func>                  \\ func(a,b);
   0x000011b6 <+41>:    add    esp,0x8                        \\  |
   0x000011b9 <+44>:    mov    eax,0x0                        \\ _|
   0x000011be <+49>:    leave
   0x000011bf <+50>:    ret
End of assembler dump.

剛剛說到 stack 會用來儲存要傳遞的參數、回傳地址以及其他資訊,因此當一個函數要被調用前,我們會將參數、回傳地址等資訊先 push 到 stack 中。

為了要讓所有呼叫(使用)此函數的人都知道要從什麼順序讀取參數,所以依據不同的架構、作業系統會定義出一套標準的流程,包含參數如何處理、回傳值如何處理,函式如何進行呼叫等,這套流程稱為 Calling Convention(呼叫慣例)。

關於 stack 可以參考這篇文章

以我們例子的呼叫慣例會將參數由右而左 push 進 stack 中,最後則 push 回傳地址。

我們實際執行一遍 func(a, b)給大家看:

func 語句由 +30 到 +44 這五個 instruction 組成,
因此我們將斷點設置在 main+30 的位置,結果如下(已省略部分資訊)

gdb-peda$ b *main + 30
Breakpoint 1 at 0x11ab: file fun.c, line 12.
gdb-peda$ run
[----------------------------------registers-----------------------------------]
    :
    :
EIP: 0x565561ab (<main+30>:     push   DWORD PTR [ebp-0x8])
    :
[-------------------------------------code-------------------------------------]
    :
   0x565561a4 <main+23>:        mov    DWORD PTR [ebp-0x8],0x3
=> 0x565561ab <main+30>:        push   DWORD PTR [ebp-0x8]
   0x565561ae <main+33>:        push   DWORD PTR [ebp-0x4]
   0x565561b1 <main+36>:        call   0x5655617d <func>
   0x565561b6 <main+41>:        add    esp,0x8
   0x565561b9 <main+44>:        mov    eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffd0e8 --> 0xf7fc2ab0 --> 0xf7da122d ("GLIBC_PRIVATE")
0004| 0xffffd0ec --> 0x1
0008| 0xffffd0f0 --> 0x3
0012| 0xffffd0f4 --> 0x2
0016| 0xffffd0f8 --> 0x0
0020| 0xffffd0fc --> 0xf7da57c5 (add    esp,0x10)
0024| 0xffffd100 --> 0x1
0028| 0xffffd104 --> 0xffffd1b4 --> 0xffffd31c ("your path")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, main () at fun.c:12
12              func(a,b);

我們剛剛在 main 的位址 +30 的地方下了斷點,這個意思是說我們剛結束了 +23 的執行,在準備要執行 +30 的部分暫停下來了(所以現在所有的 stack、register 狀態都是 +23 結束後,而非 +30,因此 EIP 此時還指著 +30,等到 +30 執行完後才會指向 +33)

補充說明:上面 disas main 出來的結果 +30 的位置是 0x000011ab,這時候還沒執行,而到了正式執行時我們的位置會依據當時的情況分配,因此不一定是 0x000011ab(像這次就是:0x565561ab),所以若我們斷點下 *0x000011ab 可能會出現讀取不到的錯誤,不能這樣下。

接下來我們可以使用 ni(next instruction)跳到下一個 instruction(+33)暫停(就是執行 +30 那行的意思)

補充說明:若只下 n 則是會以高階語言的語句為單位,例如我們在這邊下 n,就會執行完構成 func(a, b) 的五個 instruction 直接到 +49

gdb-peda$ ni
[----------------------------------registers-----------------------------------]
    :
ESP: 0xffffd0e4 --> 0x3
EIP: 0x565561ae (<main+33>:     push   DWORD PTR [ebp-0x4])
    :
[-------------------------------------code-------------------------------------]
   0x5655619d <main+16>:        mov    DWORD PTR [ebp-0x4],0x2
   0x565561a4 <main+23>:        mov    DWORD PTR [ebp-0x8],0x3
   0x565561ab <main+30>:        push   DWORD PTR [ebp-0x8]
=> 0x565561ae <main+33>:        push   DWORD PTR [ebp-0x4]
   0x565561b1 <main+36>:        call   0x5655617d <func>
   0x565561b6 <main+41>:        add    esp,0x8
   0x565561b9 <main+44>:        mov    eax,0x0
   0x565561be <main+49>:        leave
[------------------------------------stack-------------------------------------]
0000| 0xffffd0e4 --> 0x3
    :
    :
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x565561ae      12              func(a,b);

我們在 stack 的部分可以看到參數 b 的值被 push 上去了。
此外,多介紹一個 register:ESP,他指著 stack 的最頂端,因此我們可以看到存著 0x3 內容的位址被存到 ESP 裡了。

再繼續往下執行:

gdb-peda$ ni
[----------------------------------registers-----------------------------------]
    :
ESP: 0xffffd0e0 --> 0x2
EIP: 0x565561b1 (<main+36>:     call   0x5655617d <func>)
    :
[-------------------------------------code-------------------------------------]
   0x565561a4 <main+23>:        mov    DWORD PTR [ebp-0x8],0x3
   0x565561ab <main+30>:        push   DWORD PTR [ebp-0x8]
   0x565561ae <main+33>:        push   DWORD PTR [ebp-0x4]
=> 0x565561b1 <main+36>:        call   0x5655617d <func>
   0x565561b6 <main+41>:        add    esp,0x8
   0x565561b9 <main+44>:        mov    eax,0x0
   0x565561be <main+49>:        leave
   0x565561bf <main+50>:        ret
Guessed arguments:
arg[0]: 0x2
arg[1]: 0x3
[------------------------------------stack-------------------------------------]
0000| 0xffffd0e0 --> 0x2
0004| 0xffffd0e4 --> 0x3
    :
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x565561b1      12              func(a,b);

我們可以看到參數 a 也被 push 進去了。
那接下來 +36 這行的 call 意思是呼叫一個函式,若我們想跟進去則是使用 si

補充說明:同樣也有 s,如同上面介紹的 n,只是他會進入函式

gdb-peda$ si
[----------------------------------registers-----------------------------------]
    :
ESP: 0xffffd0dc --> 0x565561b6 (<main+41>:      add    esp,0x8)
EIP: 0x5655617d (<func>:        push   ebp)
    :
[-------------------------------------code-------------------------------------]
   :
=> 0x5655617d <func>:   push   ebp
   0x5655617e <func+1>: mov    ebp,esp
   0x56556180 <func+3>: call   0x565561c0 <__x86.get_pc_thunk.ax>
   0x56556185 <func+8>: add    eax,0x2e6f
   0x5655618a <func+13>:        nop
[------------------------------------stack-------------------------------------]
0000| 0xffffd0dc --> 0x565561b6 (<main+41>:     add    esp,0x8)
0004| 0xffffd0e0 --> 0x2
0008| 0xffffd0e4 --> 0x3
    :
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
func (a=0x2, b=0x3) at fun.c:5
5       {

這時我們可以看到,原先應該要被執行的下一行(+41)被 push 進去了
接下來我們一直下 ni,直到遇到 ret:

gdb-peda$ ni
[----------------------------------registers-----------------------------------]
    :
ESP: 0xffffd0dc --> 0x565561b6 (<main+41>:      add    esp,0x8)
EIP: 0x5655618c (<func+15>:     ret)
    :
[-------------------------------------code-------------------------------------]
   0x56556185 <func+8>: add    eax,0x2e6f
   0x5655618a <func+13>:        nop
   0x5655618b <func+14>:        pop    ebp
=> 0x5655618c <func+15>:        ret
   0x5655618d <main>:   push   ebp
   0x5655618e <main+1>: mov    ebp,esp
   0x56556190 <main+3>: sub    esp,0x10
   0x56556193 <main+6>: call   0x565561c0 <__x86.get_pc_thunk.ax>
[------------------------------------stack-------------------------------------]
0000| 0xffffd0dc --> 0x565561b6 (<main+41>:     add    esp,0x8)
0004| 0xffffd0e0 --> 0x2
0008| 0xffffd0e4 --> 0x3
    :
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x5655618c      7       }

ret 執行函式的返回操作,他會將 stack pop 到 EIP,然後再讓 CPU 執行,這時候 CPU 就會依據 EIP 所指的位置繼續執行,我們就能成功的回到原先的地方了

gdb-peda$ ni
[----------------------------------registers-----------------------------------]
    :
ESP: 0xffffd0e0 --> 0x2
EIP: 0x565561b6 (<main+41>:     add    esp,0x8)
    :
[-------------------------------------code-------------------------------------]
   0x565561ab <main+30>:        push   DWORD PTR [ebp-0x8]
   0x565561ae <main+33>:        push   DWORD PTR [ebp-0x4]
   0x565561b1 <main+36>:        call   0x5655617d <func>
=> 0x565561b6 <main+41>:        add    esp,0x8
   0x565561b9 <main+44>:        mov    eax,0x0
   0x565561be <main+49>:        leave
   0x565561bf <main+50>:        ret
   0x565561c0 <__x86.get_pc_thunk.ax>:  mov    eax,DWORD PTR [esp]
[------------------------------------stack-------------------------------------]
0000| 0xffffd0e0 --> 0x2
0004| 0xffffd0e4 --> 0x3
    :
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x565561b6 in main () at fun.c:12
12              func(a,b);   

那麼若是我們能將返回地址覆蓋成我們自己想要的東西呢?
是不是繞過原本程式執行的流程,讓程式隨我們操作了呢~
明天(沒意外的話)我們將實際利用有漏洞的程式,實作在 Stack 上達成 buffer overflow(Stack-Based Buffer Overflow)。


上面打太多廢話了,趕快進入主題吧~
今天的主題是:

雜燴菜


source:老家河南網(?
每天都被嘴,好嘛我這次認真介紹~
雜燴菜,河南也簡稱燴菜,大鍋菜,是河南人最愛吃的菜品之一。作法就是把白菜、冬粉、豆腐、肉丸子等放在一起,再加上薑、蔥、香菜以及其它佐料熬成一鍋。
可以了吧,內容應該有昨天的三分之一了吧


上一篇
Day4 大雜燴之鮑魚熊掌 - 工控:OT 環境
下一篇
Day6 大雜燴之紅燒雜燴 - Pwn:Basic(4)
系列文
雜七雜八大雜燴,資安技術大亂鬥30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言