CTB-Locker payload obfuscation layers analysis
After studying the CTB-Locker dropper, I wanted to look at the downloaded payload, but by lack of time I can't finished it. By the way, another guys did it very well (see the CIRCL TR-33 Analysis - CTB-Locker / Critroni or the Zairon's CTB-Locker files decryption demonstration feature ). So it would be a waste of time to complete my own analysis.
However, I noticed that all studies focused on CTB-Locker operations or user's files decryption which are obvsiously the main concerns, but no one studied in details the different techniques of obfuscation implemented by CTB-Locker. My early analysis revealed the implementation of numerous techniques of obfuscation, the most complex being different from those used by the dropper and with two more layers. The article below focused on these subjects.
- CTB-Locker Payload obfuscation overview
- Sample studied
- Step 1 - The Stub : loading and deciphering unpacker code
- Step 2 - Second Unpacker : deciphering third unpacker
- Step 3 - Third Unpacker : deciphering fourth unpacker
- Step 4 - Fourth Unpacker : last unpacker deciphering real payload code
- Step 5 - Deciphered Payload code : deciphering .onion addresses
- And after that ?
CTB-Locker Payload obfuscation overview
The five successive skins...
Diagram below provides an overview of the CTB-Locker Payload obfuscation structure :
...and the successives unobfuscation steps mapped in memory
Table below shows how the successives unobfuscation steps are run to decipher and finaly execute the payload (click on the badges 1', 2, 2', 3, 4, 5 to download .bin memory dump) :
|Start address||End address||Step 0 |
|Step 1 |
|Step 2 |
|Step 3 |
|Step 4 |
|0x401000||0x40F000||Section .text||1 Unpacker1 : |
Deciphers copy 1 of unpacker2 and jumps to it
|3 Unpacker3 :|
Deciphers Unpacker4 and Payload in a new memory block (see 0xB70000) then jumps to it...
|0x411000 - 0x4BC000 : Section .rsrc|
|Allocated memory blocks|
|0xAA0000||0xAA0674||1' Unpacker2 copy 1 :|
Deciphers copy 2 of unpacker2
|0xAB0000||0xAB0674||2 Unpacker2 copy 2 :|
Deciphers unpacker3 and Payload in 0xAC0000 then injects them in place of original process and jumps to it
|0xAC0000||0xB63800||2' Deciphered Unpacker3 code|
|0xB70000||0xD8A000||4 Unpacker4 :|
Deciphers Payload, copies .onion ciphered addresses in 5 from 3 then calls deciphered payload
|0xD90000||0xFDAE6C||5 Deciphered Payload|
will deciphers .onion addresses copied by 4 from 3
The following chapters attempt to describe the operations of the five different envelopes and to zoom in on interesting details of steps 1 to 5.
The sample studied is the one downloaded by the dropper analyzed in a precedent write up.
|File name : saf.exe|
|Date : 02/07/2015|
|Size : 760 320|
|MD5 : 39c0e005cd2892a7b315081f9db6dc37|
|SHA-1 : e9c2dda548ca0f53939d8bbf9228a92977964341|
|Name||RVA||V. Size||Offset||R. Size||Flags||Entropy||Packed|
|RCDATA||0x200||0x172E6||0xA3800||75CEB2FADFB7EEEE8A7F6B1AE6265014||Ciphered Payload code and Unpackers 3 & 4|
|RCDATA||0x200||0xBAAE6||0x781||57471C047888E9F46B78517D8406AA03||Empty (zero filled)|
|RCDATA||0x200||0xBB267||0x674||78CB40825EAD64990E80B8A3EEF5DC45||Ciphered Unpacker 2|
You can also see File informations on VirusTotal.
Step 1 - The stub : loading and deciphering second packer code
The first envelope of CTB-Locker payload is similar to the first envelope of his dropper. It contains miles of useless but easily interchangeable code. This is to complicate the task of antiviruses since it is easy to generate different executables for each download of the payload.
The only two interesting code parts are :
The first part waits for a parameterizable number of seconds and performs two checks whose exact purpose escapes me but probably intended to identify the execution within a VM or a debugger.
You can see here this first part with utile code highlighted and commented. And below, the utile code only :
.text:0040465F .text:0040465F loc_40465F: .text:004046A2 push 1 ; Stacking 1. Number of secondes to wait (may be a parameter of packaging tool used ?) .text:004046E3 .text:004046E3 loc_4046E3: .text:004046E3 push 3E8h .text:004046E8 push 0FFFFFFFFh .text:004046EA lea ebx, WaitForSingleObject .text:004046F0 call dword ptr [ebx] ; WaitForSingleObject ( -1, 1000 ); .text:004046F0 ; is equivalent to : .text:004046F0 ; WaitForSingleObject ( GetCurrentProcess(), 1000 ); .text:004046F0 ; witch results in waiting 1s ! .text:0040473B dec dword ptr [esp] ; Decrementing first DWORD on the stack, which contains 1 .text:0040473E jnz short loc_4046E3 ; Waiting more ? Not in our case. .text:0040477D .text:0040477D add esp, 4 ; Deleting the number of seconds to wait pushed in 0x4046A2 .text:004047C4 lea eax, aGdi32_dll ; Getting a reference to "gdi32.dll" string .text:00404807 push eax ; Stacking the "gdi32.dll" string reference .text:00404842 mov ebx, ds:GetModuleHandleA .text:00404848 call ebx ; GetModuleHandle ( "gdi32.dll" ); .text:0040484A push eax .text:0040484B pop ebx ; EBX = GDI32.DLL handle .text:00404890 mov dword_40F212, ebx ; Saving gdi32 handle (ie gdi32 DOS header address) .text:004048E3 add ebx, 3Ch ; EBX refers to GDI32 PE Header offset .text:00404939 mov ebx, [ebx] ; EBX = gdi32 PE Header Offset .text:00404977 add ebx, dword_40F212 ; EBX refers GDI32 PE Header .text:004049C7 add ebx, 0A4h ; EBX refers gdi32 relocation table size .text:00404A0D jb loc_4033C4 ; If BP < 0xFE00, we left... Why ? Anti-VM or anti-sandbox ? .text:00404A13 cmp dword ptr [ebx], 1000h ; If gdi32 relocation table size is above 0x1000, continue .text:00404A19 ja loc_40C0B3 ; ====================> this is the way to the continuation... .text:00404A1F ; .text:00404A1F ; .text:00404A1F ; ...and this is the way to a prematured end ! .text:00404A67 lea edi, ds:6F23AE59h ; EDI=0x6F23AE59 .text:00404ABA sub edi, 6EE37A95h ; EDI=0x6F23AE59-0x6EE37A95=0x4033C4 .text:00404B0C push edi ; Stacking EDI, the next retn instruction will unstack it and put it .text:00404B45 ; in EIP. We will be back at 0x4033C4 .text:00404B45 retn ; =====> Going to 0x4033C4...
One might think that this code was written in assembly. A compiler does not directly utilize ESP to reference a local variable and do not make a
jump 0x4033C4 via
preceded calculations to hide this address (see instructions colored in maroon).
The second part allocates a buffer of 0x678 bytes, copy and decrypts on the fly to this buffer an encrypted piece of code located in the resources and causes the execution of the decrypted code.
By removing unnecessary code, useful code of the second part is the following :
.text:0040C0B3 .text:0040C0B3 loc_40C0B3: ; CODE XREF: .text:00404A19 .text:0040C0B3 ; DATA XREF: .text:0040C4F2 .text:0040C0B3 xor ecx, ecx .text:0040C0B5 xor ecx, 3C572FD1h ; ECX=0x3C572FD1 which is the deciphering key .text:0040C0BB xor edi, edi .text:0040C0BD sub edi, 674h .text:0040C0C3 neg edi ; EDI will be a counter of bytes to copy and decipher .text:0040C0C5 push ecx ; Stacking the deciphering key .text:0040C0C6 mov ebx, 28601AAh .text:0040C0CB sub ebx, 286016Ah .text:0040C0D1 push ebx ; Stacking 0x40 (0x28601AA-0x286016A), ie PAGE_EXECUTE_READWRITE .text:0040C0D2 mov ebx, 286116Ah .text:0040C0D7 sub ebx, 286016Ah .text:0040C0DD push ebx ; Stacking 0x1000 (0x286116A-0x286016A), ie MEM_COMMIT .text:0040C0DE mov esi, 28607DEh .text:0040C0E3 sub esi, 286016Ah .text:0040C0E9 push esi ; Stacking 0x674 .text:0040C0EA mov eax, 0 .text:0040C0EF push eax .text:0040C0F0 call ds:VirtualAlloc ; EAX = VirtualAlloc ( NULL, 0x674, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); .text:0040C0F6 pop ecx .text:0040C0F7 cmp eax, 0 .text:0040C0FA jz loc_4033C4 ; if VirtualAlloc failed, terminate ! .text:0040C100 sub esi, esi .text:0040C102 add esi, 4BB267h ; 0x4BB267 refers ciphered code and payload hidden in resources .text:0040C108 sub edx, edx .text:0040C10A or edx, eax .text:0040C10C push edx ; Stacking new memory block address .text:0040C10D .text:0040C10D loc_40C10D: ; CODE XREF: .text:0040C4A6j .text:0040C157 mov ebx, [esi] ; Loading 4 ciphered bytes from the resources .text:0040C197 adc esi, 4 ; Moving forward of 4 bytes in source .text:0040C1D7 ; ==================== Deciphering 4 bytes loaded in EBX =================== .text:0040C1D7 not ebx .text:0040C21E sub ebx, 19h .text:0040C263 xor ebx, ecx .text:0040C2A9 inc ebx .text:0040C2DE ; ========== And use these bytes as key to decipher the next four ========== .text:0040C2DE mov ecx, ebx .text:0040C329 rol ecx, 1 .text:0040C364 rol ecx, 7 .text:0040C3AB ; ========================================================================== .text:0040C3AB mov [edx], ebx ; Store the 4 deciphered bytes .text:0040C400 lea edx, [edx+4] ; Moving forward of 4 bytes in destination .text:0040C454 lea edi, [edi-4] ; There are 4 bytes less to decipher .text:0040C4A3 cmp edi, 0 ; copy and decipher finished ? .text:0040C4A6 jnz loc_40C10D ; No, next 4 bytes please... .text:0040C4AC .text:0040C4AC mov edx, esp ; Getting address of memory block allocated above and pushed in 0x40C10C .text:0040C4AE mov esi, ds:GetModuleHandleA .text:0040C4B4 push esi ; Stacking address of GetModuleHandleA .text:0040C4F2 push offset loc_40C0B3 ; Stacking decipher routine for future use ? .text:0040C546 jmp dword ptr [edx] ; Going to execute deciphered code...
Note the obfuscated VirtualAlloc() parameters, probably intended to fool antivirus or automated static analysis. And the tricks to hide destination of the last jump.
The decoding routine allocates a
0x674 bytes block, and then decodes the original block while copying it in the allocated buffer.
Coding is based on an
XOR operation with a starting value set at
0x3C572FD loaded in ECX which will be used to decode the first 4 bytes of the block,
each following DWORD will then be decoded using the previous one :
- Decoding each DWORD is performed via a sequence of instructions disseminated through a lot of uninteresting (but replaceable) code. Refined sequence :
The dropper uses a very similar deciphering code.
Ok, now the second packer deciphering code is loaded and deciphered. Step one is achieved. Next step please !
Step 2 - Second unpacker : deciphering third unpacker and payload
The second unpacker is binary identical to the one of the dropper, with the exception of the following three parameters :
|0x664-0x667||Code block address to decipher||0x0000B0A6||0x000172E6|
|0x669-0x66C||Size of the code block to decipher||0x00001A00||0x000A3800|
This confirms the assumption of a settable packer used for the dropper and the payload.
For details of the numerous actions performed by this second unpacker and obfuscation techniques used, read the relevant part of the analysis of the dropper : Envelope 2 : deciphering payload downloader and preparing it's execution.
The adventure does not end here. Indeed, the payload is still encrypted on two levels.
Step 3 - Third unpacker : deciphering fourth unpacker and payload
The third unpacker allocates an area in
0xB70000 of size
0x21A000 bytes, then copies and deciphers on the fly the block located on
0xA339A bytes long.
At this time only the first
0xA2509 bytes are used. The decrypted size is less than the source size because some data are skipped by the deciphering algorithm and others serve as counters.
Here is the code, without jumps, re-ordered and commented :
; ********************************************************************************************************************************* ; Going to decipher and copy from 0x004010B0-0x004A439A to 0x00B70000-0x00C12509, ie 0xA2509 (or 664841) bytes long ; ********************************************************************************************************************************* ; [EBP-20h] = lpflOldProtect ; [EBP-1Ch] = Address of last byte deciphered in source memory block ; [EBP-18h] = Address of last byte deciphered in destination memory block (ie newly allocated) ; [EBP-14h] = Preserves EBX ; [EBP-10h] = Preserves EDI ; [EBP-0Ch] = Preserves EBX ; [EBP-08h] = Address of Imports Table = 0x00401000 ; [EBP-04h] = Address of newly allocated memory block () 00401FA3 push EBP 00440E0C mov EBP, ESP 00456CF9 sub ESP,20h 0040BE41 mov DWORD PTR SS:[EBP-0Ch], EBX ; Storing EBX = 0x8054B6B8 (=> pop EDI / pop ESI / pop EBX / leave / retn 8) 004577F8 mov DWORD PTR SS:[EBP-10h], EDI ; Saving EDI = 0x7C817067 (=> push EAX / Call ExitThread) 00440949 mov DWORD PTR SS:[EBP-14h], ESI ; Saving ESI = 0x12D9E8 (=> stack => " police)") 0048B9D6 call 0048B9DBh ; CALL to next instruction => going to stack return address (ie 0x0048B9DB) 0048B9DB pop EAX ; EAX = 0x0048B9DB 0042E492 sub EAX,8A9DBh ; EAX = 0x00401000 (start of CTB-Locker Imports) 004418F3 mov DWORD PTR SS:[EBP-8], EAX ; We put Imports Table Address in [EBP-8] 00467F6B push 4 004429DE mov ebx, DWORD PTR SS:[EBP-8] ; EBX => Imports table 0043CDB5 push 1000h 004490B2 push 21A000h 0046AD3B push 0 0047404B call DWORD PTR DS:[EBX+4] ; VirtualAlloc ( NULL, 0x21A000, MEM_COMMIT, PAGE_READWRITE ); 004887A7 mov DWORD PTR SS:[EBP-4], EAX ; [EBP-4] = memory block address 0042FD07 mov ESI, DWORD PTR SS:[EBP-8] ; ESI = 0x00401000 00440918 add ESI,0B0h ; ESI = 0x004010B0 00442CA3 mov EDX, C78E5EC3h ; !!! Encryption key !!! 00461B73 mov EDI, DWORD PTR SS:[EBP-4] ; EDI = memory block address (ie 0x00B70000) 0040EBFC ; 0040EBFC ; ========== Deciphering blocks loop ========== 0040EBFC xor EAX, EAX 0046FB58 lods BYTE PTR DS:[ESI] ; AL = 1 byte. Gives the size of block to decipher or the number of bytes to skip. 0048A723 xor AL, DL 0043F5EF je 00476589h ; ====================> Finished ! 004533E5 rol edx, 3 ; rol the key ! 00488A90 imul EDX, EDX, -24F331C1h ; 0042C1A4 cmp AL, 0E0h ; If AL is greather or equal to 0x0E, skip AL-0x0E bytes, 00452BFD jae 00457B58h ; Else copy and decipher AL bytes... 0044B84F mov ECX, EAX ; ECX will be used to count bytes 00466599 ; ========== Deciphering bytes loop ========== 00466599 lods BYTE PTR DS:[ESI] ; AL = 1 byte to copy and decipher 00497C5D xor AL, DL 00464022 rol EDX, 3 004013C2 imul EDX, EDX, -24F331C1h 00482BDB stos BYTE PTR ES:[EDI] ; Store deciphered byte ! 00409BF9 dec ECX ; 0043D334 jne 00466599h ; Next byte please... 00492239 jmp 0040EBFCh ; Next block please... 00457B58 ; 00457B58 ; ====================================================================== 00457B58 lea ESI, [EAX+ESI-0E0h] ; Skiping some source bytes (the ones containing this code ?) 0040DFA4 jmp 0040EBFCh 00476589 ; ====================================================================== 00476589 ; 00476589 mov DWORD PTR SS:[EBP-1Ch], ESI ; [EBP-1Ch] = adresse fin source 0044CD98 mov DWORD PTR SS:[EBP-18h], EDI ; [EBP-18h] = Address of last deciphered byte in newly allocated memory block 00412FC8 lea EAX, [EBP-20h] ; !!! Trick to recover [EBP-4] below with a [EAX+1Ch] 0046314F push EAX ; lpflOldProtect = [EBP-20h] 00410F28 push 20h ; flNewProtect = PAGE_EXECUTE_READ 004977D6 push 21A000h ; dwSize = 0x21A000 0048CD0A push DWORD PTR DS:[EAX+1Ch] ; lpAddress = [EBP-4] (=[EBP-20h+1Ch]) 0048B6F7 call DWORD PTR DS:[EBX+8] ; EAX = VirtualProtect ( [EBP-4], 0x21A000, PAGE_EXECUTE_READ, [EBP-20h] ); 004833F3 push DWORD PTR SS:[EBP-1Ch] ; Stacking 0x4A439A (next byte after last deciphered source byte) 0040AEDA push DWORD PTR SS:[EBP-4] ; Stacking 0xB70000 (address of newly allocated memory block, filled with deciphered code) 00402EDD push DWORD PTR SS:[EBP-8] ; Stacking 0x401000 (address of process's code segment) 00494D18 mov EAX, DWORD PTR SS:[EBP-18h] ; EAX = 0xC12509 00422961 add EAX, -5 ; EAX = 0xC12504 00406EC7 call EAX ; ...following in 0xC12504 which is just 5 bytes before the end of newly deciphered block ! 00456763 mov EBX, DWORD PTR SS:[EBP-0Ch] ; Retoring EBX 00403232 mov ESI, DWORD PTR SS:[EBP-14h] ; Restoring ESI 00481B41 mov EDI, DWORD PTR SS:[EBP-10h] ; Restoring EDI 0044033D leave 004743E1 retn
Note the artifice employed at
0x0048B9D6 to refer discreetly beginning of the code in memory, consisting in a
CALL on next instruction, and then to pop
the return address stacked by the
CALL before removing via a subtraction to meet up with the right value (ie
0x401000 ) in
The deciphering method used here is based on ciphered data organized in blocks of size
0xE0 bytes maximum.
The first byte gives the size of the area to decipher (if the byte contains a value less than
0xE0), or the number of bytes to skip (if the byte contains a value greater than
or equal to
0xE0 skip the
[byte value]-0xE0 following bytes).
Each block is deciphered one byte a time via a simple XOR performed with a 32 bits key which is shifted to the left by 3 bits after each use, and is multiplied with a constant.
The deciphering method can be translated into the following C code :
Step 4 - Fourth Unpacker : last unpacker deciphering real payload code
The last unpacker performs the following actions (here is the code commented except for deciphering functions):
- Fills memory bloc
- Deciphers payload in
0xD91000with a very complicated (in my opinion) deciphering function (See the graph overview)
- Treats all imports with it's own procedure :
- The import table format is a sequence of :
<library-1 name in ASCII>\0 <import name-1 in ASCII>\0<RVA to store function-1 pointer> ... <import name-n in ASCII>\0<RVA to store function-n pointer>\0 ... <library-n name in ASCII>\0 <import name-1 in ASCII>\0<RVA to store function-1 pointer> ... <import name-n in ASCII>\0<RVA to store function-n pointer>\0\0
- The code parses the table and stores the functions adresses with a classical
- Here is the complete list of imported functions
- The import table format is a sequence of :
- Relocates Call, Jmp and conditional jumps with a specific relocation table
The relocations table lists all offsets of constants to relocate. It is a succession of offsets of 1, 2 or 3 bytes long :
- if byte value is 2, then the offset is contained in the following two bytes
- if byte value is 3, then the offset is contained in the following 3 bytes
- else the offset is the byte itself.
The relocation function will parse the code until the offset indicated relocating all
Jxxlong. Once at the offset indicated it will relocate the constant, then fetch the next offset. If the next offset is 0 then it's finished.
!-#cfgbeg_#!. Will be found in 0xE931B0
0x88bytes long ciphered block from
0x4A439A(parts of a ressource) in place of the signature block :
0xDC368Dwhich is the payload entry point !
Step 5 - Deciphered Payload code : deciphering .onion addresses
During the payload initialization, there is an ultimate area to decipher : the block containing the addresses of .onion web sites accessible through the TOR browser and containing the interface where enter the keys to decrypt files after payment.
Deciphering code is another complicated one (still in my opinion !). You can see the deciphering code here (note : it is not commented since it's too much complicated to understand in detail for me and the time I have, homework does have to have an end ;-) ).
Once deciphered, .onion URL are :
And after ?
Analyze the last two complex decryption algorithm or try to understand why the payload's anti-debug tricks does'nt work ?
- CTB-Locker Payload pwd="infected" - WARNING : don't execute this one !!! (ie step1)
- CTB-Locker unpacker 2 deciphered unpacker 2 loaded at 0xAA0000 (ie step1')
- CTB-Locker unpacker 2 copy deciphered unpacker 2 loaded at 0xAB0000 (ie step2)
- CTB-Locker unpacker 2 copy deciphered unpacker 3 at 0xAC0000 (ie step2')
- CTB-Locker unpacker 3 deciphered unpacker 3 containing two times ciphered payload (ie step3)
- CTB-Locker unpacker 4 deciphered unpacker 4 containing ciphered payload (ie step4)
- CTB-Locker deciphered payload loaded at 0xD90000 with ciphered .onion (ie step5)
The tools used are fairly standard :