[Article] Random malware analysis & unpacking - Stage 2/3

Jan 8, 2020

<- Random Malware Analysis & Unpacking - Stage 1/3

Note : The first thread started by create a queue timer that was creating thread repeatedly. To avoid creating a queuetimer and exiting main thread (and exiting the whole program by the way), the main thread was waiting using WaitForSingleObject that made it wait for 14mn by default. In order to examine further into our binary, I will patch the value passed to this function to be comfortable.

We now are jumping to a function somewhere that has been dynamically allocated, thus we wont be able to set breakpoint that will last on this section. This function is resolving a lot of function dynamically using a method similar to the method used in the main thread to resolve its function names.


The function resolves really specific functions you can check on the screenshot just below.


Those functions are pretty interesting. The presence of :

  • NtUnmapViewOfSection
  • functions relative to thread handling/creation
  • functions relative to read and write memory
  • functions relative to process creation

leads us to understand that the function is going to do a create replacement.

Here is the pseudo code of a classic process replacement (that matches exactly what is happening) in Practical Malware Analysis - a must have - :

CreateProcess(..., "procname", ..., CREATE_SUSPENDED, ...);
VirtualAllocEx(..., ImageBase, SizeOfImage, ...);
WirteProcessMemory(..., headers, ...);
for (i = 0; i < NumberOfSections; i++){
	WirteProcessMemory(..., section, ...);

Here are the lines the program goes trough :

debug029:0047040C call    eax                             ; CreateProcessW
debug029:00470431 call    ecx                             ; GetThreadContext
debug029:00470457 call    eax                             ; ReadProcessMemory
debug029:00470467 push    eax                             ; 	baseAddress
debug029:00470468 mov     ecx, [ebp+var_28]
debug029:0047046B push    ecx                             ; 	handler
debug029:0047046C mov     edx, [ebp+arg_0]
debug029:0047046F mov     eax, [edx+38h]
debug029:00470472 call    eax                             ; NtUnmapViewOfSection
debug029:00430493 call    ecx                             ; VirtualAllocEx
debug029:004304BD call    edx                             ; WriteProcessMemory
debug029:004304D1 loop_sections:                          ; CODE XREF: sub_430360+166↑j
debug029:004304D1 mov     ecx, [ebp+ptr_PE_base]
debug029:004304D4 movzx   edx, word ptr [ecx+6]			  ; [1]
debug029:004304D8 cmp     [ebp+i], edx
debug029:004304DB jge     short end_of_loop
debug029:0043051B call    eax                             ; WriteProcessMemory
debug029:0043051F end_of_loop:
debug029:0043053E call    edx                             ; WriteProcessMemory
debug029:0043056F call    ecx                             ; SetThreadContext
debug029:0043057B call    ecx                             ; ResumeThread

Those series of calls looks very similar to the case described in Practical Malware Analysis.

Note_[1] -> Virgilius project defines the 32 bits header as :

    ULONG Signature;                                                        //0x0
    struct _IMAGE_FILE_HEADER FileHeader;                                   //0x4
    struct _IMAGE_OPTIONAL_HEADER OptionalHeader;                           //0x18
    USHORT Machine;                                                         //0x0
    USHORT NumberOfSections;                                                //0x2
    ULONG TimeDateStamp;                                                    //0x4
    ULONG PointerToSymbolTable;                                             //0x8
    ULONG NumberOfSymbols;                                                  //0xc
    USHORT SizeOfOptionalHeader;                                            //0x10
    USHORT Characteristics;                                                 //0x12

So Signature is 4 bytes, the machine is 2 bytes. We can conclude that ecx+6 (at 0x004304D4) actually points to the NumberOfSections. So we are looping trough sections.

Now I will just use x32dbg to dump every section copied to the process using WriteProcessMemory, reconstruct it and move on to the new PE.

Note : Doing so might be painful and prone to error, plus we aren’t sure that PE header will be correct (entry point, etc). For now I will just dump the buffer from which the bytes are copied in WriteProcessMemory, using x32dbg section dump utility to dump the array containing the PE.

We can now open our newly created PE.

First a quick look at PE-Bear :


From this graphic I can assume that there will be a third step to this binary, and this binary will probably be unpacking the next step and jumping to it.

Here is what the main looks like :


sub 0x401000

This function looks quite similar to what he have seen in the first binary: it probably resolves tons of imports. Plus, we can notice some encoded strings.


As I don’t want to solve all this manualy, I will use a script to create a big structure as in the first stage, and rename fields accordingly with the resolves.

def get_string(addr):
	out = ""
	while True:
		if Byte(addr) != 0:
			out += chr(Byte(addr))
		addr += 1
	return out

id = AddStrucEx(-1, "R", 0);
for i in xrange(0,480,4):
    AddStrucMember(id, "field_%x"%i, i, FF_DWRD, -1, 4);
#Memory Reg [Base Reg + Index Reg + Displacement]
function_name = ""
decoded = False

MakeNameEx(0xF91E0A, 'decodeString', SN_NOWARN);

r = idaapi.get_struc(get_struc_id(STRUCT_NAME))
while True:

	line = GetDisasm(here())
	if "ret" in line:

	if decoded:
		function_name = get_string(GetRegValue("EAX"))
		MakeComm(here(), function_name)
		decoded = False

	if get_operand_type(here(), 0) == MEM_REG:
		offset = line[line.index("+")+1:line.index("]")-1]
		try: # in case the struct was already identified, the cast would fail (e.g : int("R.CreateThread", 16))
			print("offset {:x}".format(int(offset, 16)))
			idaapi.set_member_name(r, int(offset, 16), function_name)
			print("Failed to set struct")
		function_name = ""
	if "decodeString" in line:
		decoded = True
	GetDebuggerEvent(WFNE_SUSP, -1);

After execution and some retype of variables, we get a pretty decent result :


Once everything is resolved, the binary tries to detect any debug, VM or sandbox attempt in multiple functions:


Those functions include anti dbg trick based on loaded dlls (dbghelper.dll), tries to check if it is in a sandbox based on different names and SIDs, but also checking if sandboxie’s dll is present. There are 3 anti-vm techniques used using cpuid, sidt, sgdt. The program also checks if it is being executed from wine.

Once then, it checks is the argument wusaupdate is passed on the command line (and it’s not) :


In the first function we can easily see that it tries to permanently shut down windows defender and stop any windows update :


Giving us easy IOCs - see annex.

On the next function, it will block many ports using window’s firewall :


This one could hardly be taken as an IOC because a legitimate program could use this command.

As I mentioned earlier, those commands wont be executed anyway as no argument is passed to the program.

To analyze the rest of the program, I set a breakpoint to one of the last lines of the main function that looked like a jump to some OEP :


Executing it gives a write error exception in a previous function call. I launch x86dbg to investigate and get stop on the write error exception:


From this screenshot we can notice that the program tries to write the letter M somewhere in memory, right in the .rsrc section (a resource inside the PE file obviously). This looks quite suspicious, could this be the start of MZ magic bytes?

Spoiler : it is.

Modifying the rights on the memory pages used by the resource, and letting the function complete, we can see a nice PE file in memory dump :


I will dump this file, resolve its import and analyze it in Random Malware Analysis & Unpacking - Stage 3/3.

Annex - IOCs

  • /C Reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\" /v DisableAntiSpyware /t REG_DWORD /d 1 /f
  • /C sc config wuauserv start= disabled
  • /C sc stop wuauserv