buffer_overflow 第3章 筆記1 第一個shellcode

第三章  shellcode 

shellcode 也就是一隻程式,你需要這個程式做出什摸樣的動作。

shellcode 是用組合語言來編寫的,編寫完後轉換為相對應的opcode利用buffer_overflow 攻擊定位到EIP暫存器之後,在把流程跳到shellcode上,如此一來shellcode就會被執行。


在此之前,需要先了解DLL

DLL是動態函數連接庫,一個DLL裡面有好多微軟提供的API 函數。
當應用程式被執行加載時,如果該程式有使用到某個DLL裡面的API,應用程式就會載入該DLL,DLL可以使用應用程式的資源,應用程式可以使用該DLL的API。


那我們就寫一個打印出hello world 然後結束的程式,觀察一下用組合語言的角度會長什摸樣子。

<<<<用DEV  c++編譯>>>>
int main()
{
      printf("hello world\n");
      exit(0);
}


用OllyDbg打開:
框起來的為主程式:


可以在 004012C1看到 CALL <JMP.&msvcrt.printf> 代表他要呼叫printf,我們可以在 004012C1下中斷點按F2 在按F9 讓程式執行到中斷點,接著F7進入看看 printf 真正位置。

下方顯示了 77C1186A才是printf真正的位址。


可以在 004012CD看到 CALL <JMP.&msvcrt.exit> 代表他要呼叫exit,我們可以在 004012CD下中斷點按F2 在按F9 讓程式執行到中斷點,接著F7進入看看 exit 真正位置。







可以觀察到:
printf()函數的真正位址在 msvcrt.dll 內  位置為 77C1186A
exit()   函數的真正位址在 msvcrt.dll 內  位置為 77C09E7E

知道了函數的位址我們只要先把參數準備好,在呼叫函數位址就可以了



printf()需要用的參數 字串  " Hello, World!\n"  在 ASCII 碼表示為:

H        e        l         l        o        ,       空白     W        o        r         l        d       !        \n
[48]    [65]   [6C]   [6C]  [6F]   [2C]       [20]   [57]      [6F]   [72]    [6C]    [64]  [21]   [0A]



在組合語言角度中,呼叫某個函數如果有參數的話,會先利用push 指令將需要的參數推入堆疊,在用call指令呼叫函數來實現。




由於 32位下的暫存器為32位元 所以一次可以存4位元組,所以一次可以推入4位元組。
推入的順序,字串的頭也就是H [48] ...... 必須最後推入,

PUSH 0x210A0000

PUSH 0x6F726C64

PUSH 0x6D2C2057

PUSH 0x48656C6C
-------------------------------------------------------------------------------------------------------------

由於windows 是little endian 儲存數值 相反過來,所以變成

PUSH 0x00000A21

PUSH 0x646C726F

PUSH 0x57202C6F

PUSH 0x6C6C6548
---------------------------------------------------------------------------------------------------------------
由於printf() 接收的參數必需是一個指向字串的指標,也就是一個記憶體位置,並非字串本身所以 

PUSH 0x00000A21

PUSH 0x646C726F

PUSH 0x57202C6F

PUSH 0x6C6C6548

PUSH ESP                                    //執行完PUSH ESP此時堆疊最頂端的存放該字串頭的記憶體                                                                                                                                                       //  位置

------------------------------------------------------------------------------------------------------------------
再來 exit() 需要一個參數0 

xor eax,eax                              //讓eax 變0

-------------------------------------------------------------------------------------------------------------------
全部加起來:

PUSH 0x00000A21

PUSH 0x646C726F

PUSH 0x57202C6F

PUSH 0x6C6C6548

PUSH ESP 

MOV ECX ,  0x77C1186A                  //   把printf位置給 ecx

CALL ECX                                          //   呼叫printf

XOR   EAX , EAX                              //   讓eax 變0

PUSH  EAX                                        //   把推0推進堆疊頂端

MOV ECX,  77C09E7E                     //   把 exit位置放進 0x77

CALL ECX                                         //   呼叫 ecx 

---------------------------------------------------------------------------------------------------------------------


把上面程式碼轉換為OPCODE 型態:

使用:Immunity Debugger 外掛模組mona.py 轉換:
打開Immunity Debugger 輸入!mona


接著 在下方命令列輸入 !mona assemble -s 加上 剛剛的組合語言,每個指令前面用一個#號隔開:

!mona asseble -s  PUSH 0x00000A21 # PUSH 0x646C726F # PUSH 0x57202C6F # PUSH 0x6C6C6548# PUSH ESP # MOV ECX ,0x77C1186A # CALL ECX # XOR EAX,EAX  # PUSH EAX # MOV ECX,77c09e7e  # CALL ECX

按下ALT+L 叫出log data視窗


可以看到我們指令對照OPCODE的樣子
把他複製下來 我們接下來要來執行這個shellcode 。




把這個shellcode,放在DEV c++執行看看會不會印出Hellow, World,並且透過exit()離開



#include <cstdio>
using namespace std;

char shellcode []=
"\x68\x21\x0a\x00\x00"          //PUSH   0x00000A21
"\x68\x6f\x72\x6c\x64"          //PUSH    0x646C726F
"\x68\x6f\x2c\x20\x57"          //PUSH    0x57202C6F
"\x68\x48\x65\x6c\x6c"          //PUSH    0x6C6C6548
"\x54"                                   //PUSH    ESP   
"\xc7\xc1\x6a\x18\xc1\x77"    //MOV     ECX ,   0x77C1186A
"\xff\xd1"                                //CALL    ECX
"\x33\xc0"                               //XOR      EAX, EAX
"\x50"                                      //PUSH    EAX
"\xc7\xc1\x7e\x9e\xc0\x77"      //MOV      ECX,77c09e7e
"\xff\xd1";                               //CALL     ECX

typedef void (*FUNCPTR)();
int main()
{
        printf("<<shellcode  started >> \n");
 
        FUNCPTR fp = (FUNCPTR)shellcode;
        fp();

        printf("you can't see me because  exit() ");


        //system("pause"); 暫停沒用 exit()早就執行了
}


其中:
1.
定義了typedef void (*FUNCPTR) (); 函數  回傳值為void  ,之後把存放shellcode的陣列強制轉換為FUNCPTR 型態,執行了 fp()那一行 就代表進入到函數也就是執行了我們shellcode

2.
最後一行 printf("you can't see me because  exit() ") 沒辦法看到 因為shellcode中的exit() 已經把程式結束掉了

3.
在dev c++下會一閃而過因為沒有設定system("pause")讓程式暫停,為什摸不用system("pause")
原因很簡單,想一下我們的shellcode會先執行exit() ,程序早就結束了。

所以我們用cmd來執行 我們的這個Test_shellcode.exe


大功告成:


相關圖片










留言

這個網誌中的熱門文章

buffer_overflow 第1章 筆記 介紹

Buffer_overflow 第2章 筆記1 函數堆疊的故事

buffer_overflow 第3章 筆記6 拼出我們第二個shellcode。