溢出漏洞原理及利用

本文是作者学习过程的笔记整理而来的,如有错误,还请见谅!

接上一篇


  • 溢出漏洞原理及利用
  • 适用性更强的MessageBox


适用性更强的MessageBox

之前我们写的MessageBox,是直接在调试器中找到的地址.当模块使用动态加载基址的时候,我们重启系统或者在其他机器上运行的时候,函数地址就改变了.

为了程序在其他系统通用,需要动态获取MessageBoxA和EixtProcess函数的地址.

思路就是:

使用FS寄存器找到当前TEB,得到0x30处的PEB,通过PEB中0xC0出的位置找到PEB_LDR_DATA的结构体指针,使用其中的三个模块信息链表寻找kernel32.dll,然后找到dll中导出的GetProcAddress函数的地址,使用GetProcAddress函数获取LoadLibrary函数的地址

有了这两个函数,其他函数就都可以获取了.

一般三个链表顺序如图(第一个都为ntdll.dll):

skywaling 内存溢出监控 内存溢出漏洞利用_shellcode


通过三个链表查看加载模块信息,代码:

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;
}

运行效果:

skywaling 内存溢出监控 内存溢出漏洞利用_链表_02