Advanced Imports Obfuscation

7 minute read

OverView

Imports are a great place to look at when you need to identify ‘quickly’ if the file is suspicious “and that’s one of the indicators that AVs also make decisions by looking at”, So we always notice malware authors try to resolve the needed malicious APIs in runtime using “GetProcAddress” and “GetModuleHandle” APIs, But these functions also get flagged sometimes. So why not to make it harder for AVs, Let’s implement our own functions that will get the job done by just parsing our file’s PE Structures.

Note:
I will mention how you can recognize the use of this technique as a malware analyst
by explaining with Source code and assembly code.

Some Needed Structures

As I said we will heavily depend on PE Structures So here is the structure for some of the needed Structures.

The contents of the structures are copied from MSDN and other non-official documentation.

struct PEB_LDR_DATA
{
  ULONG Length;
  BOOLEAN Initialized;
  HANDLE SsHandle;
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
  PVOID EntryInProgress;
  BOOLEAN ShutdownInProgress;
  HANDLE ShutdownThreadId;
};

struct PEB
{
  BOOLEAN InheritedAddressSpace;
  BOOLEAN ReadImageFileExecOptions;
  BOOLEAN BeingDebugged;
  union
  {
    BOOLEAN BitField;
    struct
    {
      BOOLEAN ImageUsesLargePages : 1;
      BOOLEAN IsProtectedProcess : 1;
      BOOLEAN IsImageDynamicallyRelocated : 1;
      BOOLEAN SkipPatchingUser32Forwarders : 1;
      BOOLEAN IsPackagedProcess : 1;
      BOOLEAN IsAppContainer : 1;
      BOOLEAN IsProtectedProcessLight : 1;
      BOOLEAN SpareBits : 1;
    };
  };
  HANDLE Mutant;
  PVOID ImageBaseAddress;
  PEB_LDR_DATA* Ldr;
  //...
};

struct UNICODE_STRING
{
  USHORT Length;
  USHORT MaximumLength;
  PWCH Buffer;
};
  
struct LDR_DATA_TABLE_ENTRY
{
  LIST_ENTRY InLoadOrderLinks;
  LIST_ENTRY InMemoryOrderLinks;
  union
  {
    LIST_ENTRY InInitializationOrderLinks;
    LIST_ENTRY InProgressLinks;
  };
  PVOID DllBase;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  //...
};

Let’s start with the simple one

GetModuleHandle Implementation “Source Code”

This will be one Argument function that holds the name of the DLL which we need to get its base address.

HMODULE WINAPI hlpGetModuleHandle(LPCWSTR sModuleName);

At start, we need to get the address of the “PEB” Structure which is where we could start.

#ifdef _M_IX86 
  PEB * ProcEnvBlk = (PEB *) __readfsdword(0x30);
#else
  PEB * ProcEnvBlk = (PEB *)__readgsqword(0x60);
#endif

The PEB->LDR is a Structure that Contains a linked list that holds info about loaded modules, and the LDR->InMemoryOrderModuleList is a pointer to the start of this Linked list.

PEB_LDR_DATA * Ldr = ProcEnvBlk->Ldr;  
LIST_ENTRY * ModuleList = &Ldr->InMemoryOrderModuleList;
LIST_ENTRY *  pStartListEntry = ModuleList->Flink; 

So we can easily loop over each entry on the list, get its LDR_DATA_TABLE_ENTRY, and compare the name of the module with our argument(desired module name), If there is a match, the job is done.

for (LIST_ENTRY *  pListEntry  = pStartListEntry;     
                   pListEntry != ModuleList;        
                   pListEntry  = pListEntry->Flink) 
{
    
    // get current Data Table Entry
    LDR_DATA_TABLE_ENTRY * pEntry = (LDR_DATA_TABLE_ENTRY *) ((BYTE *) pListEntry - sizeof(LIST_ENTRY));

    // check if the module is found and return its base address
    if (lstrcmpiW(pEntry->BaseDllName.Buffer, sModuleName) == 0)
      return (HMODULE) pEntry->DllBase;
  }

GetModuleHandle Implementation “Assembly”

As explained earlier the function is one argument function, and you can also notice how the function started with getting the address of the PEB Structure.

Error loading

So the next step is to get the LDR offset inside the PEB Structure, then get the InMemoryOrderModuleList offset inside LDR structure.

And that’s what happened here

mov     edi, large fs:30h  // x86 build
...
mov     edi, [edi+0Ch]
add     edi, 14h

Then our loop for testing the held module name with our argument takes the rest of the job.

Error loading

Easy right?!! hopes to continue like so.

GetProcAddress Implementation “Source Code”

This will be two arguments function, the first is the base address of the module returned by the previous “GetModuleHandle” function, and the second is the name of the API that needed to be resolved from this dll.

As we are going to start parsing the dll from its base address we need to obtain the addresses of its main headers and it’s IMAGE_EXPORT_DIRECTORY what is this?!!

Actually, we have three ways to call a function inside a DLL Using:

  • Name
  • Ordinal
  • Address

Both the “Name & Ordinal” is translated to “Addresses”.

But all the three are stored somewhere pointed to by a pointer inside that IMAGE_EXPORT_DIRECTORY as you can see in the following structure.

public struct IMAGE_EXPORT_DIRECTORY
    {
        public UInt32 Characteristics;
        public UInt32 TimeDateStamp;
        public UInt16 MajorVersion;
        public UInt16 MinorVersion;
        public UInt32 Name;
        public UInt32 Base;
        public UInt32 NumberOfFunctions;
        public UInt32 NumberOfNames;
        public UInt32 AddressOfFunctions;     // RVA from base of image
        public UInt32 AddressOfNames;     // RVA from base of image
        public UInt32 AddressOfNameOrdinals;  // RVA from base of image
    }

So here’s what we need.

IMAGE_DOS_HEADER * pDosHdr = (IMAGE_DOS_HEADER *) pBaseAddr;
IMAGE_NT_HEADERS * pNTHdr = (IMAGE_NT_HEADERS *) (pBaseAddr + pDosHdr->e_lfanew);
IMAGE_OPTIONAL_HEADER * pOptionalHdr = &pNTHdr->OptionalHeader;
IMAGE_DATA_DIRECTORY * pExportDataDir = (IMAGE_DATA_DIRECTORY *) (&pOptionalHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
IMAGE_EXPORT_DIRECTORY * pExportDirAddr = (IMAGE_EXPORT_DIRECTORY *) (pBaseAddr + pExportDataDir->VirtualAddress);

We will start by resolving the previously mentioned pointers to the exported functions.

  DWORD * pEAT = (DWORD *) (pBaseAddr + pExportDirAddr->AddressOfFunctions);
  DWORD * pFuncNameTbl = (DWORD *) (pBaseAddr + pExportDirAddr->AddressOfNames);
  WORD * pOrdTbl = (WORD *) (pBaseAddr + pExportDirAddr->AddressOfNameOrdinals);

Now we can loop over all the exported functions looking for the one we are interested in.

    for (DWORD i = 0; i < pExportDirAddr->NumberOfNames; i++) {
      char * sTmpFuncName = (char *) pBaseAddr + (DWORD_PTR) pFuncNameTbl[i];
  
      if (strcmp(sProcName, sTmpFuncName) == 0) {
        // If found, get the function virtual address = RVA + BaseAddr
        pProcAddr = (FARPROC) (pBaseAddr + (DWORD_PTR) pEAT[pHintsTbl[i]]);
        break;
      }
    }

We can also do the same using ordinals.

if (((DWORD_PTR)sProcName >> 16) == 0) {
    WORD ordinal = (WORD) sProcName & 0xFFFF; // convert to WORD
    DWORD Base = pExportDirAddr->Base;      // get first ordinal number

    // check if the ordinal is out of scope
    if (ordinal < Base || ordinal >= Base + pExportDirAddr->NumberOfFunctions)
      return NULL;

    // If found, get the function virtual address = RVA + BaseAddr
    pProcAddr = (FARPROC) (pBaseAddr + (DWORD_PTR) pEAT[ordinal - Base]);
  }

That may looks working fine for you, but actually there is an important check that needs to be done before returning the result of the function address.

DLLs have some exported functions in their “IMAGE_EXPORT_DIRECTORY” but they actually don’t belong to them, instead, they are pointing to another dll, In this case, the returned pointer will be a string in the following format library.function.

So we have two checks here needed to be done:

  • Is returned address is out of this DLL
  • Is the other DLL actually loaded in our process memory

The first check can easily be done by checking if the returned address is in the address space of the DLL.

if ((char *) pProcAddr >= (char *) pExportDirAddr && 
    (char *) pProcAddr < (char *) (pExportDirAddr + pExportDataDir->Size))

If this is the case we can recursively call our implemented “GetModuleHandle” & “GetProcAddress” to resolve the “LoadLibrary” API to Load the needed DLL to our Procces’ memory space, then resolve the needed function.

pLoadLibraryA = (LoadLibrary_t) hlpGetProcAddress(hlpGetModuleHandle(L"KERNEL32.DLL"), "LoadLibraryA");
//...
pProcAddr = hlpGetProcAddress(hLoadedLibrary, sNeededFunction);

GetProcAddress Implementation “Assembly Code”

We can see ebp has the base address of the DLL and the offsets from that address is the addresses of the previously mentioned structures.

Error loading

After that, you will enter a loop that tries to find the needed function from the DLL’s Export table.

You can notice that by looking at the values of the registers during the loop. you will notice your argument(the function needed) is stored in a register(or maybe in the stack) and another register that keeps updating each round of the loop with different exported function from the table, and both of them is compared with each other.

Error loading

In the case of the function being out of the DLL, you will notice a string operation on the output which is previously mentioned to be in the format library.function to split both of them and recursively loads the library and gets the function address in the same way.

Error loading

Error loading