Hide API Call Strings with Ordinals

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s