What Year Is It? VB6 Payload Crypter
JUNE
07, 2018
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
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.
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++)
{
Sleep(0xf);
if (NtYieldExecution() != STATUS_NO_YIELD_PERFORMED)
iDebugged++;
}
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.
Conclusion
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()
Me.Hide
Dim lngRc As Long
CopyMemory lngRc, Me, 4
CopyMemory lngRc, ByVal lngRc, 4
CopyMemory ByVal (lngRc + 688), 4202169, 4
Me.Show
End Sub
SHELLCODE
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
ASSEMBLY
[SECTION .text]
global Start
Start:
jmp
getdll
nextstring:
pop ecx
jmp
getstring
getapi:
pop
edx ;put string in ebx
call
loadapi
push
eax ;get MessageBoxA
push
0
push
0
push
0
push
0
call
eax
retn
loadapi:
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
loop1:
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
loop2:
lodsd
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
retn
getdll:
call nextstring
db 0x75 ;u
db 0x73 ;s
db 0x65 ;e
db 0x72 ;r
db 0x33 ;3
db 0x32 ;2
db 0x00
getstring:
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
Source:
https://www.endgame.com/blog/technical-blog/what-year-it-vb6-payload-crypter