Win32/Upatre.BI - Part Four
Payload Format- June 20, 2015
- reverse engineering
- malware analysis upatre
- no comments
- Download Handler
- Decryption
- Payload Size and Check Key Validation
- Unpacking Stub
- The Payload Format
- Cracking Payloads
- The Script
- Payloads in the Wild
- Sample 0xbda3abd2
- Sample 0x95e79d9a
- Sample 0x457f0283
- Sample 0x105beb32
- Sample 0x8d00dfdc
- Sample 0x8d00dfdc
- Sample 0x7422731b
- Sample 0x07ea0d7c
For more information about the malware in this blog post see the Malpedia entry on Upatre.
This last article is all about the second stage payload of Upatre.
- The first part shows how the download handler decrypts and parses payload. The analysis leads to a description of the payload’s format. It also shows how the unpacking stub of the payload decompresses, prepares and launches the embedded executable. Although technically not part of Upatre’s binary, I found these steps to be so prevalent that they still should be part of the analysis on Upatre.
- The second part presents a small script to decrypt and decompress many of Upatre’s payloads. The script is applied to a couple of payloads, which all turned out to be of the Dyre banking trojan family. This comes at now suprise, see for instance this article (in German).
Download Handler
The previous blog post showed two cases that lead to the download handler:
- During initialization, Upatre finds that the preconfigured temp file exists and exceeds about 1 KB in size. If so, the file’s content is passed to the handler.
- One of the download targets returned more than 150 KB, which are then tackled by the download handler. This is the usual way the download handler is invoked.
Decryption
The downloaded payload is encrypted with with a preconfigured four byte XOR key. The keys are stored in an array of decryption keys, and referenced by the fourth field of the target which was used to download the payload, see part 2 of this blog series. I have yet to see an Upatre sample that uses more than one decryption key though. In configurations of the ten analysed samples the targets all referenced the same key, listed as decryption key.
The first four bytes of the payload are not encrypted. This would allow Upatre to use the correct magic numbers for some pretend file types; for example if the payload is disguised as a PDF, then the payload could start with "%PDF". This doesn’t seem to be the case though, the first four bytes are neither valid magic numbers nor are they used in any way by Upatre.
The following graph view shows how the decryption key is fetched from the array of keys based on the target number. The four byte key then decrypts the payload from offset 4 to the end:
The decryption can be summarized as follows, with key being the four byte decryption key and the routine f denoting a key scheduling algorithm:
I found five different ways Upatre modifies the key for the next XOR decryption:
- decremental: k ← k - 1
- double decremental: k ← k - 2
- incremental: k ← k + 1
- left rotating: k ← rol(k)
- check key based: k ← k + ck, with ck being the check key
The next picture shows implementations for all five variants taken from real Upatre samples:
Payload Size and Check Key Validation
At 0x12 bytes into the decrypted payload, i.e., 0xE bytes into the ciphertext, the payload’s size in bytes is stored as a little endian 4 byte value. Upatre compares the actual file size of the payload to that size field:
00401844 pop ebx ; ebx points to payload
00401845 push 12h
00401847 pop eax ; -> 0x12
00401848 pop ecx
00401849 mov eax, [eax+ebx]
0040184C mov esi, ebx
0040184E pop ebx
0040184F cmp eax, ecx ; compare_to_filesize
00401851 jnz error_2902
The size of the payload can be calculated of course and enables an effective known-plaintext attack. More on that later.
A 0x2902 error message is sent to the C2 server if the values don’t coincide, and the payload is discarded. If the sizes match, then a second field is checked:
00401857 mov eax, [ebp+64h+tar_nr]
0040185A inc ah
0040185C inc ah
0040185E call [ebp+64h+get_field_of_target]
00401861 mov cl, ah
00401863 shl ecx, 2
00401866 mov eax, [ebp+64h+check_keys]
00401869 add ecx, eax
0040186B mov eax, [ecx]
0040186D mov ecx, [esi+4] ; esi points to payload
00401870 cmp eax, ecx
00401872 jz short execute_payload
The instruction at offset 0x40186D retrieves the little endian dword at 4 bytes into the decrypted payload. This value is compared to the check key, which is retrieved like the decryption key before. Upatre uses the same index to reference the decryption key and check key. As for the decryption keys, all samples only have one check key configured. If the check key does not match the 4 bytes at 4 bytes into the payload, then the error message 0x2904 is sent to the C2 server.
Unpacking Stub
If both the payload size and check key values are shipshape, then Upatre considers the payload valid and will start to execute part of the payload. It first marks the first 4KB of the payload as executable, which covers all of the unpacking stub:
0040188A execute_payload: ; CODE XREF: start+5F8j
0040188A lea eax, [ebp+64h+old_protection]
0040188D push eax
0040188E push PAGE_EXECUTE_READWRITE
00401890 push 1000h
00401895 push esi ; esi points to payload
00401896 call [ebx+imports.VirtualProtect]
Then it calls the address given by the relative virtual address at 8 bytes into the payload:
0040189C xor eax, eax
0040189E mov ax, [esi+8] ; word at offset 8
004018A2 add eax, esi
004018A4 mov ecx, [ebp+64h+file_size]
004018A7 call eax
The last call hands the control over to the payload, which could do what it please. However, all payloads that I examined performed the same initial steps. There are even variants of Upatre that moved these parts away from the payload into Upatre’s code base (more on that later).
The following analysis is based on the payload downloaded by Upatre sample 457f0283c17b00b29e335829f6a716fd. Both the encrypted and decrypted payload can be downloaded here (Warning, contains malware. ZIP file encrypted with password “infected”).
The word at offset 8 into the payload, i.e., the relative address of the entry point, is 0x00E0 and points to the next snippet:
01DE00E0 lea eax, [ebp+64h+const_six_million]
01DE00E3 push eax
01DE00E4 mov eax, [esi+0Eh]
01DE00E7 push eax
01DE00E8 movzx eax, word ptr [esi+0Ch]
01DE00EC add eax, esi
01DE00EE push eax
01DE00EF mov eax, [ebp+64h+const_six_million]
01DE00F2 push eax
01DE00F3 mov edi, [ebp+64h+allocated_space]
01DE00F6 push edi
01DE00F7 push 102h
01DE00FC call [ebx+imports.ntdll_RtlDecompressBuffer]
The code reuses the stack frame of Upatre and depends on some local variables set by Upatre. It also requires that register esi
points to the start of the decrypted payload. These ties to Upatre show the close relationship the subroutine has to its downloader.
The parameters of the RtlDecompressBuffer call with the corresponding pushed values are:
- In USHORT CompressionFormat: 0x102 which represents
COMPRESSION_ENGINE_MAXIMUM
ORCOMPRESSION_FORMAT_LZNT1
. - Out PUCHAR UncompressedBuffer: space allocated by Upatre; can hold 7.9 MB
- In ULONG UncompressedBufferSize: hard-coded in Upatre to 6 MB, see the first graph view in this article.
- In PUCHAR CompressedBuffer: determined by adding the two byte relative address at offset 0xC into the payload to the payload’s start address. My payload has set the relative address 0x03F0, which results in the absolute address 0x01DE03F0.
- In ULONG CompressedBufferSize: set to the dword from offset 0xE into the payload.
- Out PULONG FinalUncompressedSize: pointer to the
const_six_million
value, will be overwritten with the decompressed buffer size.
After decompression, the unpacking stub reads the first four bytes of the uncompressed space and compares them to the famous “MZ” start of Windows executables:
01DE0102 mov eax, [edi]
01DE0104 dec al
01DE0106 inc ah
01DE0108 cmp ax, 5B4Ch ; -> MZ
01DE010C jz short is_exe
If the decompression leads to something other than an executable, then the payload uses Upatre’s C2 callback routine to send a 0x2904 error message. The payload then returns control back over to Upatre, which will continue with its main loop. The payload also sleeps for 2 seconds before returning when more than five fails for the same target are recorded:
01DE010E mov eax, [ebp+64h+tar_nr]
01DE0111 mov ecx, eax
01DE0113 mov edx, [ebp+64h+fails]
01DE0116 shl eax, 2
01DE0119 add edx, eax
01DE011B mov eax, [edx]
01DE011D cmp eax, 5
01DE0120 ja short return
01DE0122 inc eax
01DE0123 mov [edx], eax
01DE0125 mov ax, 2904h
01DE0129 call [ebp+64h+send_user_infos]
01DE012C push 1
01DE012E push 7D0h
01DE0133 call [ebx+imports.kernel32_SleepEx]
01DE0139
01DE0139 return:
01DE0139 retn
If the decompressed buffer does start with MZ, then the stub starts to load the executable — much like the second stage unpacking stub from part 1 of this blog series. First, space the size of the executable is allocated. Second, all headers are copied over:
Third, the sections are copied:
Fourth, imports are resolved:
And fifth, the relocations are taken care of:
Finally, the PEB structure is updated with the new image base. This concludes the unpacking stub and the tail jump to the entry point is made:
The whole unpacking stub seems to be common for all payloads. It would therefore make sense to include it in Upatre. This is what some version of Upatre do: rather than delivering the unpacking routine by payload, they have it already built in. They also lack the idea of check key, which is missing from the configuration of those samples. The payload therefore only consists of a four byte header and compressed data, see the next section for an illustration.
The Payload Format
The following illustration shows the payload format with all known fields:
The simplified format without unpacking stub and check key looks like this:
Cracking Payloads
The regular, non-simplified payload format can easily be decrypted if the keys are modified by any method other than check key-base. The known plain text from the payload size field (at offset 0x12 into the payload) is all one needs to find the decryption key. The size value uniquely determines the XOR key for all key scheduling algorithms except rotating; because the payload size field is not aligned to four bytes, one gets four potential keys for the rotating XOR encryption. Not knowing the key-scheduling algorithm leaves us 7 potential keys, which can easily be brute-forced.
Cracking the payload instead of retrieving the decryption key from Upatre is not only easier, but also often the only option. Upatre — at least in my samples — does not have any persistence measures and might be long gone when doing forensics. The downloaded payload, on the other hand, is always written to Window’s Temp folder with the preconfigured temp file name.
The Script
I wrote a small script that tries the 7 combinations. For each combination, the payload is decrypted. The script then checks whether the offsets to the compressed data and unpacking script lie within the payload, and if the size of the compressed data does not exceed the total payload size. If these sanity checks pass, the compressed data is unpacked. If uncompressing is successful, then the first two bytes are checked for the “MZ” header, and if found, then the decompressed is dumped to a file and the decryption key and check key are printed. Here’s and example run:
$ python upatre_payload_decrypter.py a655_24.19.25.40__j11.zip.enc
testing potential keys
key (with ksa = dec): f28ef287
-> invalid offsets
key (with ksa = rol): 2e51cf28
key (with ksa = rol): 2e51df28
-> invalid offsets
key (with ksa = rol): 3e51cf28
-> begins with MZ header, this is it!
-> written decrypted exe to: decrypted_a655_24.19.25.40__j11.zip.enc
-> decrypt_key: 3e51cf28
-> ksa : rol
-> check_key : 74090789
-> stub entry: 00000108
-> compressed start: 00000420
Decrypting the simplified format is harder, because there is no apparent known plaintext. You can, however, run the script with option -k to provide the decryption key. You can also decompress the check key-based encryption if you provide the keys with –key and –check_key.
Payloads in the Wild
Finally some payloads that I was able to download. All payloads were of the Dyre / Dyzap family, and all payloads have been decrypted with the decryption script.
Sample 0xbda3abd2
- Upatre sample
- bda3abd2f2a632873baee0fb78fd2813
- payload format
- regular
- decryption key
- 523ea087
- ksa
- left rotating
- check_key
- 2b2f8604
- payload hash
- 0c4031194bc1e1f7f434bcc9e8032a00
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x95e79d9a
- Upatre sample
- 95e79d9abcc0735d3ce79d75c4cea1ae
- payload format
- regular
- decryption key
- 71a588f1
- ksa
- double decrementing
- check_key
- 4e1a87a9
- payload hash
- 6cb4fb8ba84c451eb12a6da09bf175a5
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x457f0283
- Upatre sample
- 457f0283c17b00b29e335829f6a716fd
- payload format
- regular
- decryption key
- 2B415B20
- ksa
- left rotating
- check_key
- 64FDFF57
- payload hash
- 512d6d894ccb1454530c1ee9751826d8
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x105beb32
- Upatre sample
- 105beb3223cbff3641cbe881bc4fbf45
- payload format
- regular
- decryption key
- 3e51cf28
- ksa
- left rotating
- check_key
- 74090789
- payload hash
- 8c70899bdd7af6730730b167c59ca96a
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x8d00dfdc
- Upatre sample
- 8d00dfdc4d7932b8db5322ce439cd44b
- payload format
- regular
- decryption key
- 3e51cf28
- ksa
- left rotating
- check_key
- 74090789
- payload hash
- a7d91f69110128ba88628c4f3886cd89
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x8d00dfdc
- Upatre sample
- 846cb6984114499978d4fe29b5f5afa7
- payload format
- regular
- decryption key
- 5b244d31
- ksa
- left rotating
- check_key
- 0250f3d8
- payload hash
- b4b42f2fc627639d9c8e61df68a8085a
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x7422731b
- Upatre sample
- 7422731bbe817e85dbac70f3f98243b6
- payload format
- simplified
- decryption key
- 27185cd3
- ksa
- decrementing
- check_key
- (no check key)
- payload hash
- 2e4576dcc0731dc07717f8e1913e3bf7
- payload malware
- Dyre / Dyzap, Virustotal, Malwr
Sample 0x07ea0d7c
- Upatre sample
- 07ea0d7c04af3520da43f38ef8211aa8
- payload format
- simplified
- decryption key
- 55c28387
- ksa
- decrementing
- check_key
- (no check key)
- payload hash
- 50910945fd33606e8b91d077585b7c03
- payload malware
- Dyre / Dyzap, Virustotal, Malwr