• windbg分析Kernel32.dll导出表


    写在前面的话

    继续上篇,在获得了Kernel32.dll基址的基础上,分析它的导出表结构;

    对PE结构不太熟悉的同学,可以参考看雪论坛里的一篇帖子:https://bbs.pediy.com/thread-224265.htm

    零、思路说明

    分析之前,要明确我们的目的是,为了能在程序里获得某些API的地址;

    I)   遍历导出名称表(下面统称为ENT),匹配到需要的函数【匹配过程中,设置一个自增的变量,这样找到后,变量里就是该函数在ENT里的下标】

    II)  根据导出序号表(下面统称EOT),结合下标,找到该函数在导出地址表(EAT)里的索引

    III) 然后,从EAT里取出来这个索引处的地址,就是我们要搜寻的函数的地址了;

    一、分析Kernel32.dll在文件中的结构

    由于是在win10_64位上进行的分析;不得不提的一点:

    I)  如果程序是32位的,那么程序会链接C:WindowsSysWOW64kernel32.dll

    II) 如果程序是64位的,那么程序会链接C:WindowsSystem32kernel32.dll

    这一点,也可以通过分析基址开始80处的偏移得知;大家可以自行去分析,上篇获得基址后,db 看下内存,然后和这两个文件对比下内容就明白了;

    这里,就以32位程序为例,进行分析,那么自然,我们首先需要在010里看下kernel32.dll的结构;

    对应的文件偏移是:

     这里,再次看下导出表的数据结构体(40Byte)

    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics;
        DWORD   TimeDateStamp;
        WORD    MajorVersion;
        WORD    MinorVersion;
        DWORD   Name; // DLL的名称地址
        DWORD   Base; // 索引基数
        DWORD   NumberOfFunctions; // 函数地址表大小
        DWORD   NumberOfNames; // 函数名表大小 == 函数序号表大小
        DWORD   AddressOfFunctions;     // 函数地址表——首地址
        DWORD   AddressOfNames;         // 函数名表——首地址
        DWORD   AddressOfNameOrdinals;  // 函数序号表——首地址
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

    00 00 00 00
    CE 65 47 74【时间戳】
    00 00      【主版本】
    00 00      【次版本】 ]

    B6 45 08 00【DLL名称地址】
    01 00 00 00【索引基数】

    3B 06 00 00【函数地址表大小】
    3B 06 00 00【函数名表大小 == 函数序号表大小】

    68 07 08 00【函数地址表——首地址】
    54 20 08 00【函数名表——首地址】
    40 39 08 00【函数序号表——首地址】

    至此,我们可以得出以下几个结论:

    I)   扩展头里的导入表区段偏移RVA:00080740,距离PE头位置0x168

    II)  都是函数名导入

    III)  EAT_Offset:80768 - 80740 = 0x28

          ENT_Offset:82054 - 80740 = 0x1914

          EOT_Offset:83940 - 80740 = 0x3200

     下面的图,可以帮助大家理解这些偏移的含义,以及如何根据这些偏移找到运行时,载入内存后的对应位置;

    0. 根据BaseAddr+0x168,我们可以找到ExportStart RVA

    1. 根据BaseAddr + ExportStart_RVA + EAT/ENT/EOT_Offset就能知道真正载入内存后的EAT/ENT/EOT位置了;

    二、分析Kernel32.dll在内存中的结构

    根据上面的分析,其实,我们已经知道了偏移量,接下来,事情就简单了,同样的,用windbg附加打开一个进程,方法和上一篇帖子一致;

    我们要做的,就是根据我们计算的偏移,算出真正的3张表的地址,并进行解析;

    ExportStart_RVA = 0x80740;

    拿一个举例,就第1个吧,RVA+基址,看下

    这个函数是AcquireSRWLockExclusive,ENT里的第0号元素;那看下EOT[0]的内容:

    EOT[0] = 0x03;也就是说,这个函数的地址,在EAT[0x3]里;那看下EAT[3]的内容:

    EAT[3] = 0x08463a,这是RVA,那么函数地址就是BaseAddr + 0x08463A = 7536463a 

     

    看到这,说实话,有点怀疑人生了,这不是个字符串吗,我们不是在看EAT吗,不应该是函数地址了吗?

    这里,不得不再说下Kernel32.dll的奇葩之处,它会“转发”别的dll的导出函数,就像我们在调试器中见识到的那样的导入表结构:

    为了方便大家进一步看清楚EAT的正常的真实情况,这里,就再次分析一个别的函数LoadLibraryExA,

    套路也是一样,ENT->EOT->EAT,EAT里存放的是RVA,因为一个一个去在ENT里匹配LoadLibraryExA实在是太繁琐,而且,在上面的分析中,

    相信大家都已经理解了ENT和EOT以及EAT的相互间的关系;这里,就直接在LoadPE里找到这个函数的RVA,我们看下:

    RVA = 159A0,然后,我们在windbg里用基址加上这个偏移,对齐进行反汇编,看下是不是和我们想的一致:

    结论得到了验证,我们也可以在调试器中,进行再次确认,Ctrl + G,输入LoadLibraryExA,如下:

    注意看那个地址,这也从侧面证明,我们对EAT的解析是成功的;

    三、编写代码,实现加载user32.dll

        char srcLoadLibraryExA[] = "LoadLibraryA";
        char srcuser32[] = "user32.dll";
        int kernel32Addr = GetKernel32Base(); // 上一篇中已经写过了
        int EATAddr = 0;
        int ENTAddr = 0;
        int EOTAddr = 0;
    
        _asm {
            push eax;
            push ebx;
            mov eax, [kernel32Addr]; // ExportStart_RVA
            mov eax, [eax + 0x168]; // ExportStart_RVA
            add eax, kernel32Addr; // ExportStart_VA
            mov ebx, eax;
            add ebx, 0x28; // EAT
            mov EATAddr, ebx;
            mov ebx, eax;
            add ebx, 0x1914;
            mov ENTAddr, ebx; // ENT
            mov ebx, eax;
            add ebx, 0x3200;
            mov EOTAddr, ebx; // EOT
            pop ebx;
            pop eax;
        }
    
        _asm {
            push eax;
            push ebx;
            push ecx;
            push esi;
            push edi;
            xor ebx, ebx;
            mov eax, 0x63B;
            cld;
    
        _ENT_FIND:
            mov ecx, 13;
            mov esi, ENTAddr; // ENT RVA
            mov esi, [esi + 4 * ebx];
            add esi, kernel32Addr; // ENT VA
            lea edi, srcLoadLibraryExA;
            repe cmpsb;
            je _ENT_OK;
            inc ebx;
            dec eax;
            cmp eax, 0;
            jg _ENT_FIND;
            jmp _ENT_END;
    
        _ENT_OK:
            mov ecx, EOTAddr; // EOT Number
            mov ecx, [ecx + 2 * ebx];
            and ecx, 0xFFFF;
            mov esi, EATAddr; // EAT Address RVA
            mov esi, [esi + 4 * ecx];
            add esi, kernel32Addr; // EAT Address VA
    
            lea eax, srcuser32;
            push eax;
            call esi;
    
        _ENT_END:
            pop edi;
            pop esi;
            pop ecx;
            pop ebx;
            pop eax;
        }

    至此,对Kernel32.dll的导出表解析便告一段落了;

  • 相关阅读:
    docker镜像文件导入与导出,支持批量
    配置和启动Kubernetes服务
    在CentOS 7 上安装docker
    安装CentOS7精简版后的配置工作
    Docker镜像加速
    docker命令不需要敲sudo的方法
    建立时间和保持时间(setup time 和 hold time)
    时序收敛:基本概念
    GitHub: Windows 下的简单使用
    K-means算法和矢量量化
  • 原文地址:https://www.cnblogs.com/Reginald-S/p/8763130.html
Copyright © 2020-2023  润新知