Notice: As noted by @Antelox this is just a loader that drops LokiBot, not LokiBot itself.


Loki is a credential harvester bot sold in Russian underground forums and black markets.

Loki as advertised


For this blogpost, the goal will be to defeat/patch Loki's anti measures to be able to properly analyze malicious behavior.


Initial triage

File hashes for the sample I'll be using:

MD5: 09D2E274F1F50AB81105A3A6B9BE34CF
SHA-1: 04AD370BFE1A0AFA273568EE18F8C14BD8E612DC
SHA-256: D7AAAFB88B91A937D1EF8BCAA97F88A13545364269F510A95DAB4A72B68A4313

The sample has a pretty low detection ratio (8/58) as of now (29/03/2017) on VirusTotal.

TrID report:

Win32 Executable Delphi generic (37.4%)
Windows screen saver (34.5%)
Win32 Executable (generic) (11.9%)
Win16/32 Executable Delphi generic (5.4%)
Generic Win/DOS Executable (5.2%)

PEiD report:

PEiD BobSoft Mini Delphi -> BoB / BobSoft

The sample reports a compilation timestamp of 1991-03-05 11:25:56, this is most likely faked.

Average entropy for the sample is 6.77, so we will have to investigate a little further to know if it is packed or not.

Obfuscation / Anti


Loki uses cryptic-looking strings like "BwOjhICsHS", "js7X6kq2Bh" and "LU81txhLQMvIQfqmNA2Q1c5LsEH55". They look like it might be obfuscated data but they are actually used as direct arguments for GetModuleHandle and GetProcAddress. Of course, these calls return NULL.

Anti#1: Pointless loops

Just beside the cryptic strings, Loki has a lot of (15 that I've seen) small loops that do nothing but decrease a register a big amount of times. This can be either to mess with analysts or to enforce a timeout in automatic analysis tools like AV sandboxes (just like PE Cloak does).

Anti#2: Big memory allocations

Loki does allocate very big memory sections and writes useless data to them. That is another AV sandbox evasion trick, as AV's sandboxes can not allocate such a large memory space. It writes the memory with junk data because on most modern operative systems the memory is not actually allocated until you use it.

Here's how Loki does it:

              mem = VirtualAlloc_0(0, 544844866u, 0x3000u, 4u);// Allocate a lot of mem
              if ( mem )
                j = 1;
                  k = j + 1;
                  if ( j >= 0xFFFFFFFF )
                  remainder = k % 0x45;
                  if ( k % 0x45 > 0xFF )
                  if ( __CFADD__(j, mem) )
                  mem[j++] = remainder;  // Write memory with useless data
                while ( j != 544840799 );
                v20 = 1163247917;       // Some more useless looping
                while ( v20 );
                VirtualFree_0(mem, 0, 0x8000u); // Free the memory

As you may guess this just fills the memory with sequential bytes from 0x00 to 0x44.

Anti#3: Obscure instructions / calls

  • Loki does operations that will most likely break in a sandbox. For this sample, I've observed the StrokeAndFillPath(NULL).

  • Executes useless asm instructions like xchg ecx, ecx or mov ecx, ecx. I'm betting that's trying to break x86 emulation (i.e: more sandbox evasion).

  • Keeps calling rdtsc until the following condition is met:

  timestamp = __rdtsc();
while ( (unsigned int)timestamp > HIDWORD(timestamp) );

This last loop may be trying to detect stubbing of the rdtsc instruction.

Anti#4: Checking the foreground window

Loki tries to detect human activity by constantly checking the foreground window. If the foreground window does not change Loki will enter an endless loop where it only calls Sleep and GetForegroundWindow

Anti#5: Scrambled functions

One thing that pops up when opening the Loki sample in IDA is how many E-filled strings it does report.

DATA:00482C84 00000009 C EEEEE�`O                              
DATA:00482CC3 00000007 C EEEEEEE                               
DATA:00482CE6 00000005 C EEEEE                                 
DATA:00482CF6 00000005 C EEEEP                                 
DATA:00482D03 0000000D C EEEEEEEEEEEEE                         
DATA:00482D33 00000007 C EEEEE_f                               
DATA:00482D61 00000007 C EEEEEjd                               
DATA:00482D76 00000009 C EEEEEEVj\\                            
DATA:00482D84 00000006 C EEEEE^                                
DATA:00482D8F 00000007 C EEEEEEE                               
DATA:00482DA2 00000006 C EEEEEW                                
DATA:00482DB9 0000000F C EEEEEEEEEEEEjs^                       
DATA:00482DCD 00000008 C EEEEEEEE                              
DATA:00482DE0 00000005 C EEEEY                                 
DATA:00482DF5 00000007 C EEEEEEE                               
DATA:00482E2F 00000007 C EEEEEjc                               
DATA:00482E53 0000000C C EEEEEEEEEEEE                          
DATA:00482E93 00000009 C EEEEEEEEE                             
DATA:00482EA1 00000008 C EEEEEEEE                              
DATA:00482EC0 0000000C C EEEEEE����_                           
DATA:00482ECD 00000008 C EEEEEEEE                              
DATA:00482EF1 00000006 C EEEEEE                                                                

And that's not even the 10% of them.

Turns out the E strings (or 0x45 values) are used to separate pieces of a function, which are connected with jmp instructions.

0x45 value filling the space between the function

This breaks static analysis most of the time, as a disassembler will, most likely, interpret the data between the 0x45 values as some other ASCII values.

Lucky for us we can reconstruct this function in IDA by creating a function (P and C keys).

IDA makes it easy to unscramble the function

Sadly, the result is not very... nice.

Anti#6: Virtualization detection with CPUID

Loki uses the cpuid instruction to detect virtualization.

Of course, this comparison is not immediate. It first moves the data on the stack a couple of times so you get dizzy. But, in the end, it gets to comparing. This sample compares against KVMKVMKVM, Microsoft HV, prl hyperv & XenVMMXenVMM. If any of this comparisons succeeds the function will return 1, in the contrary, the function will return 0.

VM hypervisor comparisons

I am running VMWare (cpuid string is vmwarevmware), so checks failed as there is no comparison for VMWare (there might be on some other samples).

After hypervisor comparison, Loki moves to CPU vendor comparisons. Funny thing is it checks against the same blacklist (the hypervisor blacklist), so all checks failed again.

Are we done yet? Of course not!

Anti#7: SSE instructions support

Now Loki does this:

mov     eax, 1
movd    xmm0, eax             ; Moves the one into xmm0
xor     eax, eax              ; Zeroes eax 
movd    eax, xmm0             ; Recovers the 1 back into eax
mov     [esp+4+arg_1C], eax   ; Writes the value
cmp     [esp+4+arg_1C], 1     ; Checks it's still one
jnz     badVMorCPUdetected    ; Crash if check failed

This dumb code checks for SSE instruction support, as the xmm0 register was introduced by the SSE standard in 1999.

Anti#8: Dynamically resolved functions

If any of the previous checks fails Loki will jump to a specific address, else, it will start resolving DLL functions. Loki's DLL resolution is not straightforward, as everything else is, but if we explore the inner-most function setting breakpoints in all possible ret statements we can easily check that it is returning the address of VirtualAlloc on its first call.

If we observe some more calls to this function we can gather that the function takes a parameter. This parameter is used to identify the function to resolve. Successive calls will resolve CreateProcessW, GetThreadContext, SizeOfResource, GetFileAttributes, SetFileAttributes, SetFilePointer, WideCharToMultiByte, VirtualFree, GetModuleHandleA, SetThreadContext, LoadResource, ResumeThread, FindResourceW, TerminateProcess, GetCommandLineW, ExitProcess, GetModuleFileNameW, WriteFile, CreateFileW, CloseHandle, GetFileSize, ReadFile, CreateDirectoryW, DeleteFileW, GetTempPathW, CopyFileW, GetSystemDirectoryW, CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, ReadProcessMemory, WriteProcessMemory, VirtualAllocEx, Sleep, CreateThread, TerminateThread (Phew! That's a long list).

But that's only for kernel32, the process is repeated for shell32, user32, advapi32and ntdll.

The most remarkable functions resolved from these DLLs are:


  • ShellExecute


  • CallWindowProcW
  • MessageBoxA


  • RegSetValueEx


  • ZwUnmapViewOfSection
  • ZwResumeThread
  • ZwMapViewOfSection
  • ZwCreateSection

It is also remarkable that at the end of the function resolution process for advapi32 it tries to resolve a last function with identifier 1584B9A5h. If said function is correctly resolved Loki will call ExitProcess. Else, it calls VirtualAlloc again, but don't get too excited, it is to store more resolved functions from ntdll.

After that the outer-most function returns, so we can easily mark that functions and inner ones as function resolvers.

Anti#9: Checks process names lenghts

Loki iterates over process names and checks if there is any whose name length is bigger or equal to 64. If so, Loki exits. This is most likely because malware analysts often name malware samples after their SHA-256, which is exactly 64 characters in length. Therefore, if a process name with that length is found it may be a SHA-256 and it may be a sample under analysis, so better exit. Anyway, this anti is easily countered naming your sample something short.

Loki checking process names lenghts

Anti#10: Say my name (aka checking self-name)

Lokis checks the executale's path against a list of hard-coded values. In my particular sample this list was:

0018FA2C  00 00 00 00 00 00 00 00 73 00 61 00 6E 00 64 00  ........s.a.n.d.  
0018FA3C  62 00 6F 00 78 00 00 00 6D 00 61 00 6C 00 77 00  b.o.x...m.a.l.w.  
0018FA4C  61 00 72 00 65 00 00 00 73 00 61 00 6D 00 70 00  a.r.e...s.a.m.p.  
0018FA5C  6C 00 65 00 00 00 00 00 76 00 69 00 72 00 75 00  l.e.....v.i.r.u.  
0018FA6C  73 00 00 00 73 00 65 00 6C 00 66 00 2E 00 00 00  s...s.e.l.f.....  

To retrieve the path of the executable Loki uses GetModuleHandleA. To make the comparison a custom strstr-like function is used, meaning that if the executable's path contains any of the blacklisted items the check will succeed.

Loki checking a blacklisted item

Also, Loki writes the strings to memory in the most ridiculous fashion possible, by pushing and popping individual letters.

Writting the 'u' from 'virUs'

Anti#11: Checking running processes

Loki checks running processes names against a blacklist. For my particular sample the blacklist was:

0018F9DC  00 00 3D 00 00 00 00 00 70 00 72 00 6F 00 63 00  ..=.....p.r.o.c.  
0018F9EC  65 00 78 00 70 00 36 00 34 00 2E 00 65 00 78 00  e.x.p.6.4...e.x.  
0018F9FC  65 00 00 00 70 00 72 00 6F 00 63 00 6D 00 6F 00  e...p.r.o.c.m.o.  
0018FA0C  6E 00 36 00 34 00 2E 00 65 00 78 00 65 00 00 00  n.6.4...e.x.e...  
0018FA1C  70 00 72 00 6F 00 63 00 6D 00 6F 00 6E 00 2E 00  p.r.o.c.m.o.n...  
0018FA2C  65 00 78 00 65 00 00 00 6F 00 6C 00 6C 00 79 00  e.x.e...o.l.l.y.  
0018FA3C  64 00 62 00 67 00 2E 00 65 00 78 00 65 00 00 00  d.b.g...e.x.e...  
0018FA4C  70 00 72 00 6F 00 63 00 65 00 78 00 70 00 2E 00  p.r.o.c.e.x.p...  
0018FA5C  65 00 78 00 65 00 00 00 77 00 69 00 6E 00 64 00  e.x.e...w.i.n.d.  
0018FA6C  62 00 67 00 2E 00 65 00 78 00 65 00 00 00 00 00  b.g...e.x.e.....  

Anti#12: Is Being Debugged

Loki checks the IsBeingDebugged flag from the PEB. If it finds the process is being debugged it generates an access fault.

Loki checking PEB debug flag

Anti#13: You can never be too sure

Loki calls the Is Being Debugged (#12) 4 times (through execution of my sample) and CPUID (#5) twice.

Anti#14: Check if AV software is running

Loki checks if anti-virus software is running by comparing running process names.

Loki checking 'avp.exe'

My Loki sample checks for avp.exe (Kaspersky), avgcsrva.exe (AVG), bdagent.exe + bdwtxag.exe (BitDefender) and dwengine.exe (Dr. Web).

If Loki finds any of these AV products it writes a 1 to esp+28 and esp+30, which probably uses later to decide if execution should continue.

Malicious activity will be analyzed in a separate blogpost.