Description
WarZone is a Remote Access Trojan (RAT) that is sold on a publicly available website, as a Malware-as-a-Service.
MD5
: 48FF98ED6AE74DA9C1FEF59B40699BAESHA256
: 4537FAB9DE768A668AB4E72AE2CCE3169B7AF2DD36A1723DDAB09C04D31D61A5
Reversing and Config Extraction
Starting our analysis from the function start
, we quickly spot a function sub_405D70()
which sets the value of a global variable dword_54DB64
to 32 and then calls HeapAlloc
. After calling sub_405D70
, the program calls a function sub_405D9D(&dword_419000, (unsigned int)&unk_41902C);
where dword_419000 is a pointer to functions.
1 | .data:00419000 dword_419000 dd 0 ; DATA XREF: start+66↑o |
sub_405D9D
loops through all of these functions and eventually calls them, and these functions are doing some basic initialization stuff. After that, we notice a function call sub_413435(v6, v6)
.
We see that this function calls a bunch of other functions. Decompiling some of these functions, we observe that the argument passed to them might be a struct.
This function is probably an initializer that initializes the member of some struct. To fix this thing, we reset the pointer type of this
and then create a new struct type. I’m setting the default type of each struct member as _DWORD
for making reversing easy. I’ll fix the types later if necessary. This would make our life as a reverse engineer easier than before. Similarly, we can define new struct types for other functions as well, wherever necessary.
After that, the program calls GetModuleFileNameA(0, Filename, 0x104u)
.Here, the first argument is NULL, so the API call would retrieve the path of the executable for the current process. After that, data is read from the executable file (the current executable) and a pointer to the data is passed to the function sub_411BF8(v10, (int)current_exe_data_ptr, 8538, &v11)
.
This seems to be a hash function. Googling some of these constants, we find out that this is Murmur
hash. This hash is just used to ensure that only instance of the malware runs at a time. If the hash already exists, the call to CreateEventA
returns a handle to the existing object and GetLastError()
returns ERROR_ALREADY_EXISTS
. In this case, the malware performs cleanup by freeing the allocated memory, handles, etc. and then terminates. But, if the call to CreateEventA
returns a new handle, the malware starts its actual work.
1 | if ( hObject ) |
The program creates a new registry key HKCU\Software\Microsoft\Windows\CurrentVersion\Internet\Settings
, sets the values MaxConnectionsPer1_0Server
and MaxConnectionsPerServer
to 10 and then closes the handle to registry key. Now, let’s reverse the function sub_405A10
. It eventually calls sub_411CA2
which is really interesting.
It stores the address of sub_411CA2
into v0 and loops back until it reaches at the beginning of the PE file (‘\x90ZM’) is the famous MZ header present at the beginning of PE files. A pointer to the beginning is then passed as the second argument to the function sub_410346
.
Consider this condition: if ( *(_WORD *)a2 != 0x5A4D )
. 0x5A4D is the magic number for PE executables which is present as e_magic
in the struct IMAGE_DOS_HEADER
. Let’s change the type of a2
to PIMAGE_DOS_HEADER
.
Now, it looks better!e_lfanew
is the last member of the DOS header structure, it holds an offset to the start of NT headers. This member is important to the PE loader on windows systems because it tells the loader where to look for the file header. From line 29, v4 = (char *)pe_base + pe_base->e_lfanew;
gives the address of the start of NT headers. Now, consider this condition if ( *(_DWORD *)v4 != 0x4550 )
. 0x4550 is the signature present in NT headers (defined by the struct IMAGE_NT_HEADERS). All we need to do to make it look better is to set the type of v4 to PIMAGE_NT_HEADERS
. After that, the malware checks whether the exe is suitable for i386 or AMD64 i.e 32 bit or 64 bit (using the value of Machine field in IMAGE_NT_HEADERS). Changing the types of some other local variables, by checking the values dynamically, we get the following code:
1 | if ( Machine == 0x14C ) |
This initializes various structs which are probably going to be used later. Let’s move back to the caller of this function and reverse further.
Using dynamic analysis, we can find that the call to the function sub_8233BF((char **)&lpAddress, a2, ".bss");
returns a pointer to the string .bss
.
The call sub_9F02B9(&var_mtd_struct, &var_section_data, (LPCSTR *)var_bss_str);
stores the section header for .bss at var_section_data
.
These values match with the values calculated by PE Bear.
Moving forward, we find a function sub_9360AA
with a similar pattern to RC4, so I decided to find out the decrypted data using dynamic analysis. The decrypted data is present at 0xab270
, we can use IDAPython to retrive the data present at this address.
1 | Python>data = get_bytes(0xab270,70) |
Taking some elements from this list,
1 | d1 = [0x31,0x0,0x36,0x0,0x35,0x0,0x2e,0x0,0x32,0x0,0x32,0x0,0x2e,0x0,0x35,0x0,0x2e,0x0,0x36,0x0,0x36] |
This gives us the C2 165.22.5.66
. Also, [0x57,0x4] is a common method of storing the port, i.e port 0x457 which is 1111
.