Hi Reverser,
Today, I would like to share abit of my research regarding how you hide your windows API calls from static analysis.
Usually, When you want to interact with the windows operating system, then you need to call windows API either from user32.dll or kernel32.dll from your code such as MessageBoxA, GetCurrentProcessId, memcpy or any other API. If you specify the calls from your code, then the compiler will include the MessageBoxA or all the API’s needed in the import table in your PE. it would give ideas for the malware analyst on how the malware would behave.

What is Ordinals ?
Each function exported by a DLL is identified by a numeric ordinal and optionally a name. Likewise, functions can be imported from a DLL either by ordinal or by name. The ordinal represents the position of the function’s address pointer in the DLL Export Address table. It is common for internal functions to be exported by ordinal only. For most Windows API functions, only the names are preserved across different Windows releases; the ordinals are subject to change. Thus, one cannot reliably import Windows API functions by their ordinals.

Ordinal Number Formula
ordinal = ExportOrdinalTable [i] + OrdinalBase;
The ordinals might change on each release of the dll. We do not hardcode it in our code. We need to look up the ordinals by iterating the list and make a string comparison. This activity is counterproductive to our objective to hide the API name in our code since we need to make a string comparison during the lookup. We usually hardcode the name in our code in the encoded format. To solve this situation, or an encrypted string such as TWVzc2FnZUJveEE= is equal to MessageBoxA. So we need to decode or decrypt before we make the string comparison.
Hide My Calls
Below is the C code with the scenario that I want to call MessageBoxA from user32.dll, but I don’t want the MessageBoxA and user32.dll being listed in the import table and libraries.
#include <stdio.h>
#include "windows.h"
#include <iostream>
using namespace std;
typedef UINT(CALLBACK* MessageBoxA_RioAsmara)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
char base46_map[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
char* base64_decode(char* cipher) {
char counts = 0;
char buffer[4];
char* plain = (char*)malloc(strlen(cipher) * 3 / 4);
int i = 0, p = 0;
for (i = 0; cipher[i] != '\0'; i++) {
char k;
for (k = 0; k < 64 && base46_map[k] != cipher[i]; k++);
buffer[counts++] = k;
if (counts == 4) {
plain[p++] = (buffer[0] << 2) + (buffer[1] >> 4);
if (buffer[2] != 64)
plain[p++] = (buffer[1] << 4) + (buffer[2] >> 2);
if (buffer[3] != 64)
plain[p++] = (buffer[2] << 6) + buffer[3];
counts = 0;
}
}
plain[p] = '\0'; /* string padding character */
return plain;
}
DWORD FindNptProc(PDWORD npt, DWORD size, PBYTE base, LPCSTR proc)
{
INT cmp;
DWORD max;
DWORD mid;
DWORD min;
min = 0;
max = size - 1;
while (min <= max) {
mid = (min + max) >> 1;
cmp = strcmp((LPCSTR)(npt[mid] + base), proc);
printf("Check API Name %s on %d\n", (LPCSTR)(npt[mid] + base), mid);
if (cmp < 0) {
min = mid + 1;
}
else if (cmp > 0) {
max = mid - 1;
}
else {
return mid;
}
}
return -1;
}
PIMAGE_EXPORT_DIRECTORY GetExportDirectoryTable(HMODULE module)
{
PBYTE base; // base address of module
PIMAGE_FILE_HEADER cfh; // COFF file header
PIMAGE_EXPORT_DIRECTORY edt; // export directory table (EDT)
DWORD rva; // relative virtual address of EDT
PIMAGE_DOS_HEADER mds; // MS-DOS stub
PIMAGE_OPTIONAL_HEADER oh; // so-called "optional" header
PDWORD sig; // PE signature
// Start at the base of the module. The MS-DOS stub begins there.
base = (PBYTE)module;
mds = (PIMAGE_DOS_HEADER)module;
// Get the PE signature and verify it.
sig = (DWORD*)(base + mds->e_lfanew);
if (IMAGE_NT_SIGNATURE != *sig) {
// Bad signature -- invalid image or module handle
return NULL;
}
// Get the COFF file header.
cfh = (PIMAGE_FILE_HEADER)(sig + 1);
// Get the "optional" header (it's not actually optional for executables).
oh = (PIMAGE_OPTIONAL_HEADER)(cfh + 1);
// Finally, get the export directory table.
if (IMAGE_DIRECTORY_ENTRY_EXPORT >= oh->NumberOfRvaAndSizes) {
// This image doesn't have an export directory table.
return NULL;
}
rva = oh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);
return edt;
}
DWORD GetProcOrdinal(HMODULE module, LPCSTR proc)
{
PBYTE base; // module base address
PIMAGE_EXPORT_DIRECTORY edt; // export directory table (EDT)
PWORD eot; // export ordinal table (EOT)
DWORD i; // index into NPT and/or EOT
PDWORD npt; // name pointer table (NPT)
base = (PBYTE)module;
// Get the export directory table, from which we can find the name pointer
// table and export ordinal table.
edt = GetExportDirectoryTable(module);
// Get the name pointer table and search it for the named procedure.
npt = (DWORD*)(base + edt->AddressOfNames);
i = FindNptProc(npt, edt->NumberOfNames, base, proc);
if (-1 == i) {
// The procedure was not found in the module's name pointer table.
return -1;
}
// Get the export ordinal table.
eot = (WORD*)(base + edt->AddressOfNameOrdinals);
// Actual ordinal is ordinal from EOT plus "ordinal base" from EDT.
printf("EOT %d\n", eot[i]);
printf("Base %d\n", edt->Base);
return eot[i] + edt->Base;
}
char* convertStringToChar(string encrypted) {
char* cstr = new char[encrypted.length() + 1];
strcpy(cstr, encrypted.c_str());
return cstr;
}
int main(int argc, char* argv[])
{
LPCSTR procNameEncodedBase64;
HMODULE module = NULL;
LPCSTR moduleName;
DWORD ordinal;
moduleName = "dXNlcjMyLmRsbA==";
procNameEncodedBase64 = "TWVzc2FnZUJveEE="; // MessageBoxA
printf("Decoded %s \n", base64_decode(convertStringToChar(procNameEncodedBase64)));
if (NULL == LoadLibrary(base64_decode(convertStringToChar(moduleName)))) {
printf("Could not load library %s\n", base64_decode(convertStringToChar(moduleName)));
return EXIT_FAILURE;
}
module = GetModuleHandle(base64_decode(convertStringToChar(moduleName)));
if (NULL == module) {
printf("Couldn't get a handle to %s\n", base64_decode(convertStringToChar(moduleName)));
return EXIT_FAILURE;
}
ordinal = GetProcOrdinal(module, base64_decode(convertStringToChar(procNameEncodedBase64)));
if (-1 == ordinal) {
printf("Could not find ordinal for %s in %s\n", base64_decode(convertStringToChar(procNameEncodedBase64)), moduleName);
}
else {
printf("Found %s at ordinal %d\n", base64_decode(convertStringToChar(procNameEncodedBase64)), ordinal);
}
MessageBoxA_RioAsmara fn = (MessageBoxA_RioAsmara)GetProcAddress(module, MAKEINTRESOURCE(ordinal));
if (NULL == fn) {
printf("Found %s at ordinal %d\n", base64_decode(convertStringToChar(procNameEncodedBase64)), ordinal);
}
else {
printf("Execute API using Ordinal");
int a = fn(NULL,"MessageBoxA API was Called with Ordinal","Rio Says:",MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2);
}
return EXIT_SUCCESS;
}
Code Output

The above code allows us to enumerate each API ordinal index and call the API using the ordinal index. The pointer returned by the GetProcAddress needs to be cast to the MessageBoxA. In this case, I typedef it as MessageBoxA_RioAsmara structure so that you can call just like MessageBoxA
By using the above code then we can see that the MessageBoxA is not listed in the import table list

And the user32.dll is also not listed in the libraries because it is loaded dynamically

You also cannot find the user32.dll and MessageBoxA in the strings collection of the PEStudio except its based 64encoded


Additional
In my application code above, the solution that I took to evade the string from being extracted from the binary is using the base64 encoding. I see other technique being implemented by other malware creator is by implementing stack-based strings, and they combine it with word reversing to enhance the complexity (e.g. MessageBoxA become AxoMegasseM) during the static analysis