11

Is there any way to get the image base of an .exe without calling WinAPI functions (i.e. imported functions) so that it can't be easily viewed in a disassembler/debugger?

I've been thinking of declaring a global variable anywhere in code and reading its address in a loop backwards until e.g.MZ is found (obviously checking for NULL in the meantime). However, there may be a section which isn't readable, a string in .rdata which contains the MZ characters or some value (especially a function address or generally pointers) that contains the MZ bytes (0x4D, 0x5A) (and probably a few more "situations").

Have you any idea how to achieve that? Is it even possible?

3 Answers 3

8

I can think of a couple of ways of doing that

Scanning memory from EIP

You could easily get the EIP of your own code without calling any APIs. There are a few ways to achieve that using inline assembly, but the most common one is to include the following two instructions:

call $+5
pop eax

This works because call will push the next address (where pop eax is onto the stack) and then execute the instruction right after it (again, our pop eax). When pop eax is executed, it'll pop the address just pushed by call $+5 into a register.

After you got your EIP value you can easily scan backwards as you thought of doing, but this time starting at a position a lot closer to your image base.

Keeping in mind image bases are aligned to page boundaries, you can scan in PAGE_SIZE (4096 bytes) intervals.

Reading loaded modules from PEB

The Process Environment Block (also here) structure is accessible using a designated segment register (fs on 32 bit systems and gs on 64 bit systems) which stores the address of the Thread Information Block from which the PEB is reachable. Although most of the PEB is undocumented, in it is a lot of data relating to the operational aspects of the currently running process.

One such piece of information is the ImageBaseAddress, an undocumented (but consistent) field that holds the process's image base. If you're interested in the .exe's image base (opposed to a loaded DLLs image base), this will do.

If you want something more reliable you could use the Ldr member, which points to a PEB_LDR_DATA structure which points to a linked list of LDR_DATA_TABLE_ENTRYs, which lists all loaded modules, their addresses and a lot of other information about each loaded module. Your executable's image base is the DllBase field.

5
  • 2
    PEB.ImageBaseAddress will be easier than walking the Ldr structures.
    – josh poley
    Oct 18, 2018 at 19:10
  • @Nirlzr Thank you very much, that's a great idea! I can't however understand why I can read the eip in 0x1000 intervals. If you meant something like: while(*(WORD*)eip != 'ZM') (DWORD)eip -= PAGE_SIZE;, then what if the image base actually was 0x400000 and the eip was, say, 0x400ABC? Am I missing something?
    – Jason
    Oct 18, 2018 at 19:45
  • @joshpoley Cool. I thought there must be something like that but couldn't find it when skimming for it. I'll add that!
    – NirIzr
    Oct 18, 2018 at 19:46
  • @Jason You can easily align the address to a 4k boundary by performing eip & ~(4096-1)
    – NirIzr
    Oct 18, 2018 at 19:49
  • @Nirlzr Alright, I've already seen this technique with virtual addresses, just misunderstood you.
    – Jason
    Oct 18, 2018 at 19:50
5

If you use Visual C++, you can use the special symbol __ImageBase which points to image base of the current module. For example, here's code from VS2010 CRT source (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;
    }
}

At the top of the file there is code declaring this variable as 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) */

So it may be that the key is the .base section and not the variable name by itself.

1
4

To get your own image base in 32-bit code, you can do this:

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

which you can obviously obfuscate in multiple ways, such as by moving fs: into ds: temporarily, calculating "30h" and "8" by multiplying, etc.

64-bit code is gs:[60h] and +10h instead.

2
  • 1
    Might wanna mention that this is effectively the PEB/TEB method discussed in the accepted answer.
    – 0xC0000022L
    Oct 20, 2018 at 22:41
  • Definitely a more to-the-point and a practical take on that, though! +1
    – NirIzr
    Oct 20, 2018 at 22:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.