Win32/Upatre.BI - Part FourPayload Format

This is the fourth part of the four part series on "Win32/Upatre.BI". Check out the other parts here:

This last article is all about the second stage payload of Upatre.

  1. 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.
  2. 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:

  1. 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.
  2. 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.


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:

  1. In USHORT CompressionFormat: 0x102 which represents COMPRESSION_ENGINE_MAXIMUM OR COMPRESSION_FORMAT_LZNT1.
  2. Out PUCHAR UncompressedBuffer: space allocated by Upatre; can hold 7.9 MB
  3. In ULONG UncompressedBufferSize: hard-coded in Upatre to 6 MB, see the first graph view in this article.
  4. 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.
  5. In ULONG CompressedBufferSize: set to the dword from offset 0xE into the payload.
  6. 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 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 
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:
-> 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
comments powered by Disqus