File Analysis - 2023:10:13
example[.]com) so they can't be clicked by accident.Samples can be downloaded here
Files
- "loki_pro" (da5acdec87c3b5e3dcdbde1d82650d5c1e1f1ce420bdbc99347b662c1a71dda3)
- "loki_pro_unpacked_sections_fixed" (23853735589703c31f22452dc85d9f8fc36fbfd33016cb78f7a5c4606fe4e018)
- "shellcode_zeus.bin" (6f5bcbb40998a8548d8d98ebcf344225b2d89d68f0350655a923d8ceec541959)
ANALAYIS
Loki_Pro.exe (Stage_1)
Stage_1 is a C++ compiled PE32 executable that is a primary loader for the LokiStealer malware. Stage_1 does not attempt to hide symbols or strings. The obfuscation utilized by this packer is that of clutter. Most of the code is dead code, or a series of opaque predicates[1]. Because of the amount of junk code, it is best to use a debugger to identify points of interest. By setting a breakpoint on VirtualAlloc we can watch a new memory region be populated with what appears to be shellcode. Identifying where this happens in Stage_1 is not easy, as VirtualAlloc does not write the memory. We identified the portions of code responsible for unpacking the shellcode and subsequent layers of execution. This process is highlighted below for the sample we analyzed.
A large and cluttered function at 0x0040c830 is the user's main function and includes the following interesting code blocks. It is important to emphasize again that the program intentionally contains copious amounts of junk-code that contributes nothing to the overall objectives of the program. The snippets here are filled with unnecessary code blocks in between.
At 0x004105d3 the encrypted shellcode is loaded into a variable during the program's execution.
POPULATE_LOCAL_WITH_ENCRYPTED_SHELLCODE:
004105d3 83 bd 08 9e ff ff CMP dword ptr [EBP + local_61fc], 0x0
004105da 74 18 JZ LAB_004105f4
004105dc 8b 8d e0 9d ff ff MOV ECX, dword ptr [EBP + local_6224]
004105e2 81 c1 70 81 48 00 ADD ECX, ENCRYPTED_FIRST_STAGE_SHELLCODE
004105e8 51 PUSH ECX
004105e9 8d 8d 20 9e ff ff LEA ECX=>local_61e4, [EBP + 0xffff9e20]
004105ef e8 ac 10 00 00 CALL FUN_004116a0
At 0x00410762 the encrypted shellcode is written to a memory region that was allocated. The address for this memory region is in the EDX register.
WRITE_ENCRYPTED_SHELLCODE:
00410762 8b 95 0c f4 ff ff MOV EDX, dword ptr [EBP + local_bf8]
00410768 03 95 dc 9d ff ff ADD EDX, dword ptr [EBP + local_6228]
0041076e 8a 85 db 9d ff ff MOV AL, byte ptr [EBP + local_6229]
00410774 88 02 MOV byte ptr [EDX], AL
00410776 e9 75 ff ff ff JMP LAB_004106f0
We finally begin the decryption process at 0x00410837, where a CALL is made to a function at 0x0040C190 that decrypts the shellcode. The shellcode decryption function, like its caller, is heavily cluttered with junk code and instructions that contribute nothing to the overall execution of the program. The decryption routine takes three arguments in the manner: void decrypt_shellcode(char *encrypted_shellcode_baseaddr,int size,int key_deriver). In the case of our sample size is 0x167C.
MOVE_ENCRYPTED_SHELLCODE_BYTE_TO_LOCAL
0040c1f8 8b 4d 08 MOV ECX, dword ptr [EBP + param_1]
0040c1fb 03 8d 8c fc ff ff ADD ECX, dword ptr [EBP + local_378] ; shellcode memory region
0040c201 8a 11 MOV DL, byte ptr [ECX]
0040c203 88 95 83 fc ff ff MOV byte ptr [EBP + local_381], DL ; Local var with shellcode
DECRYPT_SHELLCODE
0040c338 b9 03 00 00 00 MOV ECX, 0x3
0040c33d 2b 4d 10 SUB ECX, dword ptr [EBP + param_3] ; param_3 = 0x47
0040c340 0f af 8d 8c fc ff IMUL ECX, dword ptr [EBP + local_378] ; i++
0040c347 0f af 4d 10 IMUL ECX, dword ptr [EBP + param_3]
0040c34b 83 c1 12 ADD ECX, 0x12
0040c34e 0f af 8d dc fd ff IMUL ECX, dword ptr [EBP + local_228]
0040c355 89 8d dc fd ff ff MOV dword ptr [EBP + local_228], ECX
0040c35b 0f b6 95 83 fc ff MOVZX EDX, byte ptr [EBP + local_381] ; EDX = encr_sc_byte[i]
0040c362 33 95 7c fc ff ff XOR EDX, dword ptr [EBP + local_388] ; Decrypt Shellcode: *sc_byte[i] = encr_sc_byte[i] ^ key[i]
0040c368 88 95 7b fc ff ff MOV byte ptr [EBP + local_389], DL
Shellcode Analysis
The first stage shellcode decrypts and executes a second stage shellcode. Upon execution the shellcode jumps to the offset 0x1570. The shellcode then employs stack strings and barrows code from the Zeus banking trojan to walk the PEB and retrieve necessary APIs through several subroutines that are called by 0x370. APIs are located through the calculating the checksum of APIs against the CRC32 ZIP polynomial, who's values are hardcoded at offsets 0x7b and 0x7f. A call to a function located at 0x000014a0 writes a second stage shellcode within the executing memory region of the first shellcode (Note: the shellcode memory dump in the attachments should be analyzed together. There are two blobs in the binary, but Ghidra is able to recognize the jump from the first stage shellcode to the second stage shellcode).
CRC32
0x7b xor ax, 8320h ; CRC32 ZIP Polynomial (0xedb88320)
0x7f xor bx, 0EDB8h