Some words on how to decrypt trojan Ascesso

Rating: 2 votes, 1.50 average.
This post is a little bit long, for a better reading you can download the pdf version here:

Roaming around Symantec web pages I stumbled on a review of a trojan named Ascesso. The malware does a lot of things that are described inside the “Technical details” section. I decided to give it a try just because it’s interesting to read what a malware does, but it’s much more funny when you play with it! In this blog entry I won’t talk about what the malware does, but I’ll write something about the way I used to obtain a readable dead list inside Ida. One of the next blog entry could be focused on the analysis of the malware, don’t know.

The file I’m going to analyze is named asc3550.sys, md5=BBEB49A32417226CABED3E104A9C94B5.
The malware is crypted, I think it’s a home made protection. If you load the file in Ida you are not able to see too much, almost all the code has been crypted. To view the uncrypted malware you have two options: you can run the driver dumping the memory image using a ring0 dumper (i.e. softice+icedump), or you can use Ida trying to convert the initial output into something really closer to the original driver. Generally, I like to work on a simple dump of the packed/crypted file, but with a driver I prefer to work on a perfect file. With a simple dump of the image you’ll have to deal with instructions like:
.text:0040381B   call  dword ptr ds:0F77B3664h
.text:004028F9   mov   dword ptr [edi], 0F77AF000h
.text:0040294E   movzx eax, byte ptr ds:0F77B36D2h
It’s hard to say what’s going on when you have unknown addresses in front of you. I think you’ll prefer to look at something like:
.text:0040381B   call  ds:_ExAllocatePoolWithTag
.text:004028F9   mov   dword ptr [edi], offset __ImageBase
.text:0040294E   movzx eax, ds:byte_4046D2
This is what I’m going to do.

Initial decryption
When you load the file in Ida the decryption routine is the only visible code:
00400240 000    jmp  short loc_400257   ; Entry point
00400242     sub_400242 proc near
00400242     arg_4= dword ptr  8
00400242 000    lea  edx, [esp+arg_4]
00400246 000    mov  edx, [edx]
00400248 000    mov  edx, [edx+0Ch]
0040024B 000    add  edx, 9540h
00400251 000    mov  eax, 9AEDh
00400256 000    retn
00400256     sub_400242 endp
00400257     loc_400257:
00400257 000    call sub_400242
0040025C 000    pusha
0040025D 020    push 55Ch
00400262 024    pop  ecx
00400263     Decrypt_1:
00400263 020    mov  eax, [edx]        ; Current dword to decrypt
00400265 020    sub  eax, 0FA598390h   ; Decryption: sub operation
0040026A 020    mov  [edx], eax        ; Save the decrypted dword
0040026C 020    lea  edx, [edx+4]      ; Next dword to decrypt
0040026F 020    sub  ecx, 4
00400272 020    test ecx, ecx
00400274 020    jnz  short Decrypt_1
00400276 020    popa
00400277 000    mov  ecx, 9B29h
0040027C 000    add  edx, 2
0040027F 000    add  edx, 6
00400282 000    jmp  edx
These are the starting instructions. Reading through various forums I had the impression that most of the people have some problems trying to anaylize this kind of snippets. It’s pretty obvious what the snippet does, it’s a simple decryption routine consisting in a sub operation. The main problem is: which part of code will be decrypted? Everything depends on value stored inside edx register, which is obtained by the instructions inside the call 400242:
00400242 lea  edx, [esp+8]     ; stack value
00400246 mov  edx, [edx]       ; edx is an address !?!
00400248 mov  edx, [edx+0Ch]   ; edx points to a structure !?!
0040024B add  edx, 9540h       ; add operation
How to know the exact value pointed by [esp+8]? Taking in mind that the file I’m analyzing is a .sys file you can get the value inside the stack with a simple deducting reasoning.

The first driver instruction is the one at 400240, but who brings me there? I mean, there should be an instruction which is executed before the one at 400240. The instruction is a call and it’s somewhere inside IopLoadDriver function (in ntoskrnl.exe):
PAGE:004DCFE2 020 push [ebp+68h+PreviousMode]   ; PUNICODE_STRING RegistryPath
PAGE:004DCFE5 024 push edi                      ; PDRIVER_OBJECT pDriverObject
PAGE:004DCFE6 028 call dword ptr [edi+2Ch]      ; Call DriverEntry
[edi+2C] points to DriverInit, 400240 in this particular case. Look at the last parameter (pDriverObject), it’s really important. You have to concentrate on the stack only, when you are at the first driver instruction you’ll have something like:
esp+00h: IopLoadDriver_return_address
esp+04h: pDriverObject
esp+08h: RegistryPath
esp+0Ch: …
Here’s how the stack looks like when you are inside the “call sub_400242″, at address 400242:
esp+00h: call_400242_return_address
esp+04h: IopLoadDriver_return_address
esp+08h: pDriverObject
esp+0Ch: RegistryPath
esp+10h: …
Now it’s pretty easy to retrieve the starting value of edx:
00400242 lea  edx, [esp+8]        ; edx = pDriverObject
00400246 mov  edx, [edx]          ; edx points to the first byte of DRIVER_OBJECT structure
00400248 mov  edx, [edx+0Ch]      ; edx = DRIVER_OBJECT+0Ch
0040024B add  edx, 9540h          ; edx = edx + 09540
Just a little more step and you’ll have the value we are searching for. Look a the definition of the DRIVER_OBJECT structure, taken from ntddk.h:
typedef struct _DRIVER_OBJECT {
CSHORT Type;                   // +000
CSHORT Size;                   // +002
PDEVICE_OBJECT DeviceObject;   // +004
ULONG Flags;                   // +008
PVOID DriverStart;             // +00C
ULONG DriverSize;              // +010
PVOID DriverSection;           // +014
PDRIVER_EXTENSION DriverExtension;   // +018
UNICODE_STRING DriverName;           // +01C
PUNICODE_STRING HardwareDatabase;    // +024
PFAST_IO_DISPATCH FastIoDispatch;    // +028
PDRIVER_INITIALIZE DriverInit;       // +02C
PDRIVER_STARTIO DriverStartIo;       // +030
PDRIVER_UNLOAD DriverUnload;         // +034
DRIVER_OBJECT+0Ch is DriverStart, the memory address that points to the first byte of the driver. I don’t have the possibility to know the exact value stored inside DriverStart field, but for a static analysis you can suppose that the address is the ImageBase: 0400000. It comes out that the edx value we want is: 0400000+09540 = 0409540

It’s time to decrypt the bytes using this simple idc script:
 static main()
   auto CurrentAddress, i;
   CurrentAddress = 0409540;
      PatchDword(CurrentAddress, (Dword(CurrentAddress) - 0xFA598390));
      CurrentAddress = CurrentAddress + 4;

   Message(”\nDecryption done, last address: %X”, CurrentAddress);
I inserted the Message function just because I wanted to see the first non-decrypted address: 0409A9C; you can remove the function, if you prefer.
Ok, now that the decryption is complete I have to look at the decrypted code. The last instruction of the initial decryption routine will bring me directly at a fresh decrypted instruction, which is the next instruction to be executed?
00400276 popa              ; edx = 0409540
00400277 mov  ecx, 9B29h   ; ecx = 09B29
0040027C add  edx, 2       ; edx = 0409540 + 2 = 0409542
0040027F add  edx, 6       ; edx = 0409542 + 6 = 0409548
00400282 jmp  edx          ; jmp 0409548
The decrypted code is a small routine, all the other bytes of the file are still crypted; maybe I have to deal with some more layers. The decrypted code contains an initializations part, some calls end a final jmp instruction.


It’s pretty easy to understand the initialization code. I won’t attach any snippet,it’s only a sequence of mov/add/lea instructions used to retrieve/store some special values that are used later.
At the end of the initialization part there’s a piece of code which is used to move 044 bytes:
.reloc:00409592 add  edi, [ebp+401BF4h]   ; edi = 400240
.reloc:00409598 lea  esi, [ebp+401D52h]   ; esi = 409A48
.reloc:0040959E mov  ecx, 44h
.reloc:004095A3 rep movsb
Do you know what 400240 is? It’s the entry point of the file. It’s something to remember for the next decryption script.

Sections decryption
The first interesting piece of code is located inside a call at 04095C1, before studying the call I prefer to take a look at the parameters:
.reloc:004095B1 push dword ptr [ebp+401BF8h]   ; ebp+401BF8 and ebp+401BEC were setted
.reloc:004095B7 push dword ptr [ebp+401BECh]   ; up in the initialization part
.reloc:004095BD push 1
.reloc:004095BF push eax
.reloc:004095C0 push ebx
.reloc:004095C1 call Decrypt_First_5_Sections
I can’t get any clue from the parameters, I can’t say nothing else without spying the code. After a little investigation over the previous instructions I discover what the parameters are:
- ebx = DriverStart
- eax = 0409A38
- 1
- dword ptr [ebp+401BECh] = 09540
- dword ptr [ebp+401BF8h] = 08FA0

Just wait some minutes and I’ll tell you everything about these parameters.
As you can see I renamed the call, the name suggests the task performed by the call: all the sections are decrypted. The call contains a loop which is executed until all the sections are not decrypted; it starts from the last section. At each round, it firstly gets the pointer to the IMAGE_SECTION_HEADER of the current section (he needs to know where the section starts/ends) and then decrypt it. The decryption algo is really simple:
004099F8   Decrypt_Section_Bytes:   ; esi points to the beginning of the current section
004099F8 lodsd           ; Get the current dword
004099F9 xor  eax, ebx   ; Decryption consists of a xor operation
004099FB stosd           ; Store the decrypted dword
004099FC sub  ecx, 4
004099FF test ecx, ecx   ; Decrypt every single byte of the section
00409A01 jnz  short Decrypt_Section_Bytes
Another simple decryption. This time a xor instruction between the crypted dword and a fixed value, it’s 0003CF5B5 and it’s located at address 409A38.
Look at the comments I inserted in the code, don’t you have any doubt about it? The decryption routine resides in .reloc section, is it possible to decrypt all the bytes in .reloc section? Well, no because if you decrypt every single byte you’ll decrypt the decryption routine generating a bsod. That’s why the call needs the first two parameters (09540 and 08FA0). The values are taken from .reloc IMAGE_SECTION_HEADER. One of them, 08FA0, is the Raw Offset of of the section. To calculate the number of bytes to decrypt the malware uses 09540 substracting 08FA0 from it, obtaining 05A0. These are the bytes to decrypt. The rest of the bytes belong to the decryption routine I’m checking and they are untouchables.

Exploring the decryption routine I found a strange piece of code I’m not able to totally understand:
0040994C lea  ebx, [eax]               ; ebx -> IMAGE_SECTION_HEADER current section
0040994E inc  ebx                      ; ebx -> 2 byte of the section’s name
0040994F cmp  dword ptr [ebx], ‘crsr’
00409955 jz   short Skip_Decrypt_Section
00409957 mov  ebx, [eax+0Ch]           ; ebx = current_section.SizeOfRawData
0040995A cmp  [ebp+Fixed_Value_1], 1   ; [ebp+Fixed_Value_1] is the pushed value: 1
0040995E jz   short loc_409963         ; Jump everytime
00409960 mov  ebx, [eax+14h]           ; ebx = current_section.PointerToRelocations
00409963     loc_409963:
00409963 mov  ecx, [eax+10h]           ; ecx = current_section.PointerToRawData
00409966 test ecx, ecx
00409978 sub  eax, ebx                 ; eax = 09540 – 08FA0 = 05A0
Three things:
1. it checks for ‘rsrc’. This is a common name generally used for resource type section, if the file contains this particular section the decryption won’t be executed over that section. The driver doesn’t have a .rsrc section for sure, why the author needs to add this check?
2. do you remember the 5 parameters passed to the function? One of them is the value 1. At 40995A it checks that value. The condition is satisfied and the conditional jump occours. This function is called one time only, which is the purpose of this check?
3. it really needs two parameters (09540, 08FA0) and a sub instruction? It only needs 05A0 and a different implementation algorithm.

I don’t have an answer for the first two things, but I think the author ripped this code. Why? It’s a behaviour of some packers, they leave resource section untouched. Moreover, there’s another check for [ebp+Fixed_Value_1]:
0040997F cmp  [ebp+Fixed_Value_1], 1
00409983 jz   short loc_409991     ; Jump…
00409985 push [ebp+Address_409A38]
00409988 push ecx
00409989 push ebx
0040998A call sub_4099A7           ; Pretty similar to Decrypt_current_section
0040998F jmp  short Skip_Decrypt_Section
00409991     loc_409991:
00409991 push [ebp+Address_409A38]
00409994 push ecx
00409995 push ebx
00409996 call Decrypt_current_section
Call_4099A7 is almost identical to Call Decrypt_current_section, they only differ by a pushad, nothing more. Hm, who adds an useless bugged function in his code? I might be wrong but ‘rip’ is the first thing I can think of, what do you think?

Anyway, here is the script I used to decrypt the entire file:
 static main()
   auto CurrentAddress;
   //   I need to copy some bytes from 0409A48 to 0400240, do you
   //   remember the last piece of code in the initialization?
   CurrentAddress = 0409A48;
   while (CurrentAddress < 0409A8C)
       PatchDword(CurrentAddress-09808, Dword(CurrentAddress));
      CurrentAddress = CurrentAddress + 4;
   //   Now I can decrypt the code
   CurrentAddress = 0400240;
   while (CurrentAddress < 0409540)
      PatchDword(CurrentAddress, (Dword(CurrentAddress) ^ 0003CF5B5));
      CurrentAddress = CurrentAddress + 4;
   Message(”\nDecryption complete!”);
The decryption ends here. Now…

Relocation: to fix or not to fix, that is the question
When the file is totally decrypted the loader has to fix some things: relocations, some pe fields and something else. The function able to fix relocation is located at 40962F, essentially the algorithm is:

This diagram should be enough for you to understand the algo. Taking in mind I’m going to reconstruct a fully readable file under IDA, is it necessary to change the relocated addresses? The answer is no; as you can see from the picture above relocation is not applied when DriverStart is equal to ImageBase, I’ll do the same.

Fix PE fields
After relocation there’s a little function, it changes some fields in the PE header.
004095F9     Fix_Some_PE_Fields proc near
004095F9 pusha
004095FA mov  esi, [ebp+401BF4h]   ; esi = DriverStart
00409600 add  esi, [esi+3Ch]       ; DriverStart+3C = file offset of the PE header
00409603 mov  eax, [ebp+401C04h]   ; eax = 0
00409609 mov  [esi+78h], eax       ; PE+78 = IMAGE_DIRECTORY_ENTRY_EXPORT
0040960C mov  eax, [ebp+401C08h]
00409612 mov  [esi+7Ch], eax       ; PE+7C = 0
00409615 mov  eax, [ebp+401BE0h]   ; eax = 08FA0
0040961B mov  [esi+0A0h], eax      ; PE+A0 = IMAGE_DIRECTORY_ENTRY_BASERELOC
00409621 mov  eax, [ebp+401BE4h]
00409627 mov  [esi+0A4h], eax      ; PE+0xA4 = 0308
0040962D popa
0040962E retn
0040962E     Fix_Some_PE_Fields endp
Some minor changes to PE header. It changes rva and size relative to IMAGE_DIRECTORY_ENTRY_EXPORT and IMAGE_DIRECTORY_ENTRY_BASERELOC.
Again, there’s a strange thing in this piece of code. It zero-es PE+78 and PE+7C while they are already zero…

Change FirstThunk field
The last part of the loader, the function starts at 4096B7. The algorithm fixes every single FirstThunk element overwriting the old value with the starting memory address of the relative function.
004096C4 Check_Current__IMAGE_IMPORT_DESCRIPTOR: ; esi -> current _IMAGE_IMPORT_DESCRIPTOR
004096C4   mov  eax, [esi+0Ch]   ; eax = _IMAGE_IMPORT_DESCRIPTOR.Name
004096C7   test eax, eax
004096C9   jz   short loc_4096D5
004096CB   call Fix_Current_FirstThunk_Array
004096D0   add  esi, 14h         ; Jump on the next _IMAGE_IMPORT_DESCRIPTOR
004096D3   jmp  short Check_Current__IMAGE_IMPORT_DESCRIPTOR
The external loop scans every single IMAGE_IMPORT_DESCRIPTOR structure, and the internal function changes every single FirstThunk.
To understand the algo I’ll show you a little example explaining how to fix one element, the first function (KfAcquireSpinLock) of the first dll (Hal.dll). This is the IMAGE_IMPORT_DESCRIPTOR:
OriginalFirstThunk: 24 89 00 00
TimeDateStamp:      00 00 00 00
ForwarderChain:     00 00 00 00
Name:               34 8D 00 00
FirstThunk:         C0 45 00 00
FirstThunk array starts at rva 45C0 and it contains 2 elements:
4045C0: 34 8A 00 00 CC 8C 00 00  00 00 00 00
The first two dwords are relative to the two Hal.dll’s imported functions, and the 3 dword represents the null terminator dword. I’m interested in the first function, _IMAGE_IMPORT_BY_NAME is located at rva 08A34:
INIT:00408A34   dw 6
INIT:00408A36   aKfacquirespinlock_0 db ‘KfAcquireSpinLock’,0
At this point the malware uses MmGetSystemRoutineAddress to retrieve the function’s address, that’s why MmGetSystemRoutineAddress is one of the functions imported by the original encrypted file. Once it has the address of the function it stores the memory address at 4045C0.
There’s a little exception in this system regarding the function from ndis module, the malware doesn’t use MmGet.. function but it takes the addresses directly from the running module. Why? I’m sure you can easily find out why by yourself.

Now, to obtain a readable dead list I have to write a simple idc script able to convert:
.text:0040048D   call ds:dword_4045C0
to the more readable instruction:
.text:0040048D   call ds:_KfAcquireSpinLock
Here is the script:
 static main()
   auto CurrentAddress, _dword;

   CurrentAddress = 04045C0;
   while (CurrentAddress < 04046CC)
      // Do I need to fix this dword?
      if (Byte(CurrentAddress) != 000)
         // Get the pointer to the name of the API, patch and rename the address
         _dword = Dword(CurrentAddress)+0400002;
         PatchDword(CurrentAddress, _dword);
         MakeNameEx(CurrentAddress, “_”+GetString(_dword, -1, ASCSTR_C), SN_AUTO);
      CurrentAddress = CurrentAddress + 4;
   Message(”\nGame over…”);
That’s all, now you can explore the malware.

Submit "Some words on how to decrypt trojan Ascesso" to Digg Submit "Some words on how to decrypt trojan Ascesso" to Submit "Some words on how to decrypt trojan Ascesso" to StumbleUpon Submit "Some words on how to decrypt trojan Ascesso" to Google

Updated October 4th, 2007 at 13:09 by ZaiRoN