What Year Is It? VB6 Payload Crypter

Last year, researchers identified new crimeware, Loki-Bot, which steals data and login credentials. Loki-Bot is generally distributed through malicious spam, and is difficult to identify without getting into the malware. Loki-Bot crimeware targets Windows, in contrast to the recent Android banking ransomware, Loki Bot. While the crimeware is not as prevalent in the wild, it has some unique and differentiating characteristics

Loki-Bot’s crypter is especially interesting and unique because it utilizes Visual Basic 6.0 to load multiple stages of shellcode to deliver the Loki-Bot payload. We saw an interesting sample highlighted by @DissectMalware on Twitter and decided to take a closer look for ourselves. We’ll walk through Loki-Bot’s crypter functionality, the first and second stage shellcodes, the payload, and then provide some thoughts on stopping these kinds of attacks and what we can expect to see next.


Loki-Bot Crypter Functionality

The main Visual Basic compiled binary uses a raw pointer access technique to jump into the first stage shellcode which then calls the second-stage shellcode that is executed off of the stack. The first stage shellcode disables data execution prevention (DEP) to make the stack executable, and uses jmp esp to jump into the stage 2 shellcode to allow it to begin execution. Stage 2 then sets up persistence, decrypts the payload, and executes the payload contents by using process hollowing. In this case the payload is Loki-Bot, crimeware designed to steal private information from a system.  Loki-Bot’s functionality has already been covered in detail elsewhere, so we will instead focus on the mode of compromise and its anti-reversing engineering tactics.


Delivery Context

This particular malware sample began its life as an executable with an RTF exploit. Generally an RTF exploit is a specially crafted file that exploits vulnerabilities in the Rich Text Format parser of an application like Microsoft Word or Adobe Acrobat in order to gain code execution on the victim. The malware can then use this initial code execution to begin its exploit chain. Usually, crafted files are spread as attachments in phishing emails, which was the case in this sample. Once the malware gets code execution on the host it then downloads jazz.exe from the link below.

Source: hxxp://tpreiastephenville.com/jazz.exe

Reference: https://twitter.com/DissectMalware/status/983640543828856832

Sha256: a66f989e58ada2eff729ac2032ff71a159c521e7372373f4a1c1cf13f8ae2f0c


PE Description

The binary was compiled with VB6.0 professional/enterprise and so contains normal x86asm with a dependency on msvbvm60.dll. Stage 1 makes specific use of the visual basic runtime DLL to make dll calls to other libraries.


First Stage Shellcode

The first stage shellcode exists within the VB6 portion of the malware, which we’ll refer to as the crypter.  The first stage shellcode exists in the “rentegninger” sub module. The original sub module is then partially overwritten with obfuscated shellcode. The “Remanipulation8” public function is called from Load_Form(). This function manipulates the list of values of the Virtual Function Table returned by the “Me” reference to the form.

Set var_2 = Me


How to Get the Shellcode Entrypoint

Stage 1 overwrites one of the variables in the compiled Visual Basic 6 code to point to an offset in the middle of the “rentegninger” submodule. The pointer is a hard coded integer that is calculated with division and a square root  as shown below.

var_17 = 0x1526C77
new_value = var_17 \ CLng(Sqr((25)))
new_value = 0x43AF4B



Utilizing StrPtr to Access Addresses

In the code example below the pointer to the Me value points to the beginning of the Virtual Function Table of the class that Me points to. In this case the Class is a Form. The offset 0x2B0 is actually the function “Show”. The pointer to the show function is overwritten by the entry point of the shellcode which is 0x43AF4B. Then you can easily call “Form.Show” and call into your shellcode. An example of this is located in the Appendix.


var_num1 = StrPtr(var_2) + 2B0h
ReplacePtr(var_num1, new_value)



This value is later called in the “Remanipulation8” function as call dword ptr [eax+2B0h]. It treats this call as a method of the Me object. DispCallFunc .



String-to-Stack Method

The crypter uses a common trick to get the strings that are inline with the assembly onto the stack. When a call is made, the next address gets pushed onto the stack as the return destination address. A disassembler will try to disassemble the string even though it never gets executed.



Using Shell_NotifyIcon

The first stage shellcode uses the Shell_NotifyIcon in a non-standard way. It passes an address off the stack that does not resemble a proper PNOTIFYICONDATA struct. The Windows API still processes the events as if they were normal. As you can see below, the Shell_TrayWnd icon is junk data for this process. It is called twice where NIM_ADD and NIM_DELETE are used.



Utilizing the PEB Loader and DllFunctionCall

The first stage shellcode uses a common technique among other Windows shellcode to get a reference to DllFunctionCall by utilizing the Process Environment Block (PEB). The PEB is a data structure provided to every running process, and can be used to gain information about that process such as environment variables, image base addresses and DLL imports. This shellcode contains a PEB loader routine that gets a reference to msvbvm60.dll  and then finds the offset of DllFunctionCall at 0x8D560CEC. Once it has the correct offset to DllFunctionCall, it can then use it to load Windows APIs so that it can make calls to them. More information on the PEB can be found here.

In a nutshell, the PEB is a linked list of offset values and the string names of the desired functions. You can linearly traverse the linked list, check for the desired function and save its offset if found.



Disabling Data Execution Prevention (DEP)

The function ZwSetInformationProcess can be called with parameters -1 and 0x22 to turn off DEP for the process. DEP was originally intended to prevent programs from executing code on the stack, however DEP can also cause normal programs to crash without any notifications. Giving the program the ability to turn off DEP for itself allows the malware to avoid unknown crashes while also allowing malware authors to execute shellcode explicitly on the Stack. Microsoft Support provides additional details about DEP.




Decoding the Shellcode

The stage 1 shellcode then utilizes JMP ESP to jump into that stack at the offset where the buffer for stage 2 shellcode was allocated. The stage 2 shellcode that was initially loaded onto the stack undergoes an initial pass of XOR decoding with an immediate value of 0x510473D1.




Second Stage Shellcode


Sandbox Evasion



The stage 1 shellcode executes CPUID to detect if it’s being run in a virtual environment. If it is running in a virtual environment, it exits. If the stage 1 shellcode is not running in a virtual environment, then it continues execution normally. As detailed elsewhere, the malware sets the EAX register to 1, calls CPUID and then checks the 31st bit of the ECX register by applying a bitmask. If the 31st bit is 0 it knows it’s being run in a virtual environment.

Subsequently, the stage 1 shellcode calls the sleep function. Sleeping for prolonged durations of time is one evasion technique used by malware to subvert detection in sandboxed environments which are usually constrained by resource allocation to not run any given sample for longer than some established period of time, such as 30 seconds. Thus for the first 30 seconds of the program’s lifetime, it is benign and might fool some sandboxing environments.


Anti-Debugging using NtYieldExecution

The ntdll function NtYieldExecution or its kernel32 equivalent SwitchToThread function allows the current thread to allocate the rest of the execution time, and allows the next scheduled thread to execute. If no threads are scheduled to execute or when the system is busy (and will not allow such a switch to occur), the ntdll NtYieldExecution() function returns a STATUS_NO_YIELD_PERFORMED (0x40000024) status code, which causes the kernel32 SwitchToThread function to return zero. When an application is being debugged, the act of single-stepping through the code causes debug events to occur and often results in no yield being allowed to occur. However, this is a hopelessly unreliable method of detecting the presence of a debugger because this method will also detect the presence of a thread that is running with high priority. An example of this code can be found here.


for (int i = 0; i < 0x20; i++)
	if (NtYieldExecution() != STATUS_NO_YIELD_PERFORMED)



Check for Adapters and Windows

The stage 2 shellcode calls VirtualAllocEX to populate a new memory region with GetAdaptersInfo. It checks the offset +10Ch  of the struct for the Description. It then calls EnumWindows to check if the window has an empty string, most likely an attempt to detect execution within some sandbox.


Persistence and Process Hollowing

The stage 2 shellcode takes two routes. During the first route, the shellcode sets up persistent mechanisms with schtasks.exe. It then decrypts the payload with Xor and RC4 during the second route, creating a suspended process of itself and then hollows it out with the payload’s contents. Each route is explained below.


Route 1 (Persistence)

The stage 2 shellcode’s first route will acquire the hardcoded strings APPDATA=, TEMP=, and copied.exe in order to place a copy of itself in the %APPDATA% and %TEMP% locations as %AppData%\\Roaming\\copied.exe. Once the path is acquired, it will create a scheduled task to copy and run copied.exe using ShellExecuteA.

schtasks.exe" /Create /SC MINUTE /TN "Startup Key" /TR "%AppData%\\Roaming\\copied.exe"
schtasks.exe "/run /tn \"Startup Key\""


If that fails it will try again by adding "\" /RU SYSTEM"

schtasks.exe" /Create /SC MINUTE /TN "Startup Key" /TR "%AppData%\\Roaming\\copied.exe" /RU SYSTEM


It will also set the registry startup run key with:

schtasks.exe /Create /SC HOURLY /MO 12 /TN \"Startup Key\" /TR \"reg add \"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\" /v \"\\\"\"Startup Key\"\\\"\" /f /t REG_SZ /d \"\\\"\""


Route 2 (Process Hollowing)

The stage 2 shellcode’s second route focuses on decrypting the executable payload into memory and then hollowing out a child process to execute the payload. “Wee2" is the marker on the stack in the shellcode and in the file that denotes the beginning of  the copy operation. The format is Wee2<length of payload><key>.


Decrypting the Payload with Key

The key is stored in the shellcode after the marker and size in the shellcode. Its length is 0x100h, and is shown below.


00000000: 9944 4203 c046 b4f2 38dd 33ed 0281 473a  .DB..F..8.3...G:
00000010: 1c76 67d8 43bc d9c6 000f 58c2 c9f7 280e  .vg.C.....X...(.
00000020: 9fec 49ac 0bef bb56 8386 7d96 4c2a 4de3  ..I....V..}.L*M.
00000030: 221f 6e80 8e65 e02b 06b8 5f6a cf5c 72b7  ".n..e.+.._j.\r.
00000040: ea51 9354 1197 05ff 892e 843e 53d2 548b  .Q.T.......>S.T.
00000050: 6dc7 b829 940d e6d3 0d60 a913 d604 795f  m..).....`....y_
00000060: f0f9 9afd 183f 0ca7 d4d7 cee7 597b 9e34  .....?......Y{.4
00000070: 7370 bfd1 dfb6 317c 5709 b0bb 20ad c308  sp....1|W... ...
00000080: f7a2 e461 62e8 1250 da3b d54b a423 a5dc  ...ab..P.;.K.#..
00000090: be18 c536 e51a 3724 5eb1 fa20 2755 cab0  ...6..7$^.. 'U..
000000a0: 414a eb0a 6990 5df8 e1e4 dbf4 aacc ef41  AJ..i.]........A
000000b0: c4c1 10de ecc3 3ecd 645a 01c8 2dfe d015  ......>.dZ..-...
000000c0: 48f3 f1b2 b339 63a1 2b8c 269c f530 f6e9  H....9c.+.&..0..
000000d0: cb25 1687 366b 8875 af02 0771 78a6 1bbd  .%..6k.u...qx...
000000e0: 4e9b 3c5b bae1 ae49 3235 2c45 fbd9 fc92  N.<[...I25,E....
000000f0: 15ce 1d2f 3d14 8f1e b567 5219 7e4f 2166  .../=....gR.~O!f


The stage 2 shellcode uses the 0x100 sized byte key to xor the Loki-Bot payload of 0x34801 size then uses the same key to RC4 decrypt the product of the xor operation.



The shellcode then calls CreateProcessW to create a process of itself in suspended mode. It uses NTCreateFile, NTWriteVirtualMemory, NTUnMapViewofSection for process hollowing on the newly created process which contains the Loki-Bot payload. NtGetContextThread and NtSetContextThread and NtSetContextThread resume the process. The parent process terminates allowing the child to run as an orphan process. Because the Loki-Bot payload has already been reviewed by many researchers, we did not post the details about this malware.



There are a few key aspects to this crypter and its behaviour that make it fishy, including its crafty implementation of the VB6 runtime in shellcode, and use of anti-reverse engineering techniques and process hollowing. First, VB6 and the VB6 run time are rather old. While there are numerous binary distributions of software in the wild that were built with VB6 enterprise, it is still suspicious. Other suspicious activities include disabling its own DEP and checking if it’s being virtualized. Lastly, the crypter makes calls to the Windows API with malformed structs (i.e. the lack of an image for Shell_NotifyIcon). The combination of all of these suspicious activities could signal to a sensor like Endgame MalwareScoreTM that the program is up to no good, allowing us to stop it before the final execution occurs.  

As for the future, we are likely to see more samples using legacy run times and features. Judging from this sample, a performant Visual Basic 6 crypter has recently been distributed in the wild. It seems natural that in the future its capabilities will improve and the volume of distribution will increase with continued black market adoption.


Appendix: Stage 1 Code Replication

After careful analysis, we were able to reproduce the method of Stage 1 shellcode execution in VB6. The code below will only display an empty message box.


VB6 Code

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
    (ByRef lpvDest As Any, _
     ByRef lpvSrc As Any, _
     ByVal cbLength As Long)

Private Sub Form_Load()
    Dim lngRc As Long
    CopyMemory lngRc, Me, 4
    CopyMemory lngRc, ByVal lngRc, 4
    CopyMemory ByVal (lngRc + 688), 4202169, 4
End Sub



E9 88 00 00 00 59 E9 8E 00 00 00 5A E8 0C 00 00 00 50 6A 00 6A 00 6A 00 6A 00 
FF D0 C3 64 A1 30 00 00 00 8B 40 0C 8B 40 14 8B 00 8B 58 28 BE 4C 00 53 00 46 
39 33 75 F1 81 7B 04 56 00 42 00 8B 70 10 56 8B 5E 3C 36 8B 34 24 01 DE 8B 5E
78 36 8B 04 24 01 D8 89 C6 83 C6 28 AD 85 C0 74 FB 03 04 24 BB 55 8B EC 83 39 
18 75 EF 81 78 04 EC 0C 56 8D 75 E6 5B 31 DB 53 53 53 54 6A 00 81 04 24 00 00 
04 00 52 51 54 FF D0 83 C4 1C C3 E8 73 FF FF FF 75 73 65 72 33 32 00 E8 6D FF 
FF FF 4D 65 73 73 61 67 65 42 6F 78 41 00



[SECTION .text]
global Start
    jmp     getdll
    pop ecx
    jmp     getstring
    pop     edx ;put string in ebx 
    call    loadapi
    push    eax ;get MessageBoxA
    push    0
    push    0
    push    0
    push    0
    call    eax
    mov     eax, [fs:0x30] ;Get the address of PEB
    mov     eax, [eax+0x0C] ;Get the address of PEB_LDR_DATA
    mov     eax, [eax+0x14] ;Get InMemoryOrderModuleList
    mov     eax, [eax]
    mov     ebx, [eax+0x28]
    mov     esi, 0x53004C ;L S
    inc     esi ;M S for MSVBVM60.DLL
    cmp     [ebx], esi
    jnz     loop1
    cmp     dword [ebx+0x4], 0x420056 ;V B for MSVBVM60.DLL
    mov     esi, [eax+0x10]
    push    esi
    mov     ebx, [esi+0x3C]
    mov     esi, [ss:esp] ;<msvbvm60.Ordinal958>
    add     esi, ebx
    mov     ebx, [esi+0x78]
    mov     eax, [ss:esp] ;<msvbvm60.Ordinal958>
    add     eax, ebx
    mov     esi, eax
    add     esi, 0x28
	test    eax, eax
	jz      short loop2
	add     eax, [esp]
	mov     ebx, 0x83EC8B55
	cmp     dword [eax], ebx
	jnz     short loop2
	cmp     dword [eax+0x4], 0x8D560CEC ;DllFunctionCall
	jnz     short loop2
	pop     ebx
	xor     ebx, ebx
	push    ebx
	push    ebx
	push    ebx
	push    esp
	push    0
	add     dword [esp], 0x40000
	push    edx ;MessageBoxA
	push    ecx ;user32
	push    esp
	call    eax ;DllFunctionCall
	add     esp, 0x1C
    call nextstring
	db 0x75 ;u
	db 0x73 ;s
	db 0x65 ;e
	db 0x72 ;r
	db 0x33 ;3
	db 0x32 ;2
	db 0x00
    call getapi
	db 0x4D ;M
	db 0x65 ;e
	db 0x73 ;s
	db 0x73 ;s
	db 0x61 ;a
	db 0x67 ;g
	db 0x65 ;e
	db 0x42 ;B
	db 0x6F ;o
	db 0x78 ;x
	db 0x41 ;A
	db 0x00