有没有办法在不调用任何 WinAPI 函数(例如 GetModuleHandle)的情况下获取我自己的图像库?

逆向工程 视窗 聚乙烯 可执行 可执行程序
2021-07-06 04:13:55

有什么方法可以在.exe不调用 WinAPI 函数(即导入的函数)情况下获取图像库,以便在反汇编器/调试器中无法轻松查看?

我一直在考虑在代码中的任何位置声明一个全局变量,并在循环中向后读取其地址,直到MZ找到eg (显然NULL在此期间进行检查)。但是,可能存在不可读的部分,.rdata其中包含MZ包含MZ字节 ( 0x4D, 0x5A)字符或某些值(尤其是函数地址或通常的指针)的字符串(可能还有一些“情况”)。

你知道如何实现这一目标吗?甚至有可能吗?

3个回答

我可以想到几种方法来做到这一点

从 EIP 扫描内存

EIP无需调用任何 API即可轻松获取自己的代码。使用内联汇编有几种方法可以实现这一点,但最常见的一种是包含以下两条指令:

call $+5
pop eax

这是有效的,因为call将推送下一个地址(在pop eax堆栈上的位置),然后在它之后立即执行指令(再次,我们的pop eax)。pop eax被执行时,它会弹出只是推地址call $+5到寄存器。

获得EIP价值后,您可以按照自己的想法轻松地向后扫描,但这一次从离您的图像库更近的位置开始。

请记住,图像库与页面边界对齐,您可以按PAGE_SIZE(4096 字节)间隔进行扫描

从 PEB 读取加载的模块

进程环境块(也此处)结构是可访问的使用指定的段寄存器(fs在32位的系统和gs在64位系统),其存储所述地址线程信息块从该PEB可达。尽管大部分 PEB 都没有记录,但其中包含大量与当前正在运行的流程的操作方面相关的数据。

其中一条信息是ImageBaseAddress,一个未记录(但一致)的字段,用于保存进程的映像库。如果您对.exe的映像库(与加载的 DLL 映像库相对)感兴趣,则可以这样做。

如果你想要更可靠的东西,你可以使用Ldr成员,它指向一个PEB_LDR_DATA指向LDR_DATA_TABLE_ENTRYs链表结构,它列出了所有加载的模块、它们的地址以及关于每个加载模块的许多其他信息。您的可执行文件的映像库是该DllBase字段。

如果您使用 Visual C++,您可以使用__ImageBase指向当前模块的图像库的特殊符号例如,这是来自 VS2010 CRT 源代码 ( pesect.c):

BOOL __cdecl _IsNonwritableInCurrentImage(
    PBYTE pTarget
    )
{
    PBYTE                 pImageBase;
    DWORD_PTR             rvaTarget;
    PIMAGE_SECTION_HEADER pSection;

    pImageBase = (PBYTE)&__ImageBase;

    __try {
        //
        // Make sure __ImageBase does address a PE image.  This is likely an
        // unnecessary check, since we should be running from a normal image,
        // but it is fast, this routine is rarely called, and the normal call
        // is for security purposes.  If we don't have a PE image, return
        // failure.
        //
        if (!_ValidateImageBase(pImageBase))
        {
            return FALSE;
        }

        //
        // Convert the targetaddress to a Relative Virtual Address (RVA) within
        // the image, and find the corresponding PE section.  Return failure if
        // the target address is not found within the current image.
        //
        rvaTarget = pTarget - pImageBase;
        pSection = _FindPESection(pImageBase, rvaTarget);
        if (pSection == NULL)
        {
            return FALSE;
        }

        //
        // Check the section characteristics to see if the target address is
        // located within a writable section, returning a failure if yes.
        //
        return (pSection->Characteristics & IMAGE_SCN_MEM_WRITE) == 0;
    }
    __except (GetExceptionCode() == STATUS_ACCESS_VIOLATION)
    {
        //
        // Just return failure if the PE image is corrupted in any way that
        // triggers an AV.
        //
        return FALSE;
    }
}

在文件的顶部有代码将此变量声明为extern

#if defined (_WIN64) && defined (_M_IA64)
#pragma section(".base", long, read)
__declspec(allocate(".base"))
extern IMAGE_DOS_HEADER __ImageBase;
#else  /* defined (_WIN64) && defined (_M_IA64) */
extern IMAGE_DOS_HEADER __ImageBase;
#endif  /* defined (_WIN64) && defined (_M_IA64) */

因此,关键可能是.base部分而不是变量名本身。

要在 32 位代码中获得自己的图像库,您可以执行以下操作:

mov eax, fs:[30h]
mov eax, [eax+8]

您显然可以通过多种方式进行混淆,例如通过将 fs: 移动到 ds: 临时,通过乘法计算“30h”和“8”等。

64 位代码是 gs:[60h] 和 +10h。