Advanced Imports Obfuscation
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.
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.
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.
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.
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.