溢出漏洞原理及利用
本文是作者学习过程的笔记整理而来的,如有错误,还请见谅!
接上一篇
- 溢出漏洞原理及利用
- 适用性更强的MessageBox
适用性更强的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;
}
运行效果: