有没有办法在不调用WinAPI函数(即导入函数)的情况下获得.exe的映像库,以使其在反汇编程序/调试器中不易于查看?

我一直在思考在代码中的任何地方声明一个全局变量,并向后循环读取其地址,直到例如找到了MZ(同时正在检查NULL)。但是,可能存在不可读的部分,.rdata中的字符串包含MZ字符或某些值(尤其是函数地址或一般的指针),其中包含MZ字节(0x4D0x5A)(可能还有更多) “情况”)。

您知道如何实现这一目标吗?可能吗?

#1 楼

我可以想到几种方法来实现此目的

从EIP扫描内存

您可以轻松地获得自己代码的EIP,而无需调用任何API。使用内联汇编有几种方法可以实现此目的,但是最常见的一种方法是包括以下两条指令:

地址(call在堆栈上的位置),然后紧接其后执行指令(同样是我们的pop eax)。当执行pop eax时,它将pop eax刚刚推送的地址弹出到寄存器中。

请紧记图像基与页面边界对齐,可以按call $+5(4096字节)的间隔进行扫描。

读取加载的模块来自PEB

可使用指定的段寄存器(32位系统上的EIP和64位系统上的PAGE_SIZE)访问过程环境块(也在此处)结构,该寄存器存储线程信息块的地址, PEB可达。尽管大多数PEB都是未记录的,但其中有许多与当前正在运行的流程的操作方面有关的数据。保留了流程的图像基础。如果您对fs的映像库(与已加载的DLL映像库相反)感兴趣,可以这样做。

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

评论


PEB.ImageBaseAddress比遍历Ldr结构更容易。

–乔希·波利(Josh Polary)
18-10-18在19:10

@Nirlzr非常感谢,这是个好主意!但是我不明白为什么我可以以0x1000的间隔读取eip。如果您的意思是这样的:while(*(WORD *)eip!='ZM')(DWORD)eip-= PAGE_SIZE ;,如果图像基数实际上是0x400000而eip是0x400ABC,该怎么办?我想念什么吗?

–詹森
18-10-18在19:45



@joshpoley酷。我以为一定有类似的东西,但略读时找不到。我将添加!

– NirIzr
18-10-18在19:46

@Jason您可以通过执行eip&〜(4096-1)轻松地将地址与4k边界对齐

– NirIzr
18-10-18在19:49



@Nirlzr好吧,我已经看过这种使用虚拟地址的技术,只是误解了您。

–詹森
18-10-18在19:50

#2 楼

如果使用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: br />
#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部分,而不是变量名本身。

评论


+1酷!我找不到任何官方文档,最近的是:blogs.msdn.microsoft.com/oldnewthing/20041025-00/?p=37483太糟糕了,它特定于Visual C!

– NirIzr
18-10-18在21:22



#3 楼

要使用32位代码获得自己的图像库,可以执行以下操作: ds:暂时通过乘以此类推来计算“ 30h”和“ 8”。

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

评论


可能要提及的是,这实际上是公认的答案中讨论的PEB / TEB方法。

– 0xC0000022L♦
18-10-20在22:41

不过,绝对是更切合实际的做法! +1

– NirIzr
18-10-20在22:43