Picking Apart Poweliks - Fileless(ish) Malware

Poweliks is an evasive click-fraud trojan that uses several interesting evasion techniques. It contains both multiple stages and programming languages, and heavily influenced other evasive malware families, such as kovter. No executables persist on the file system, and it stores its payload in the registry. Here is a brief overview of the flow of execution:
  1. Original executable drops JavaScript payload and persistence in the registry, then deletes itself and exits
  2. JavaScript executes and invokes a PowerShell script
  3. PowerShell script runs shellcode
  4. Shellcode loads and transfers execution to a DLL
  5. DLL injects shellcode into dllhost.exe, calls out to C2, and ensures that the machine stays infected by monitoring the run keys
  6. No new executables persist on the file system, the original executable exits once it spawns rundll32.exe, and will delete itself
Infection flow of execution - original .exe is just a dropper, does not run again (or persist) after the first cycle of execution
Persistent flow of execution

The process tree shows some interesting parent/child process relationships. Usually when malware spawns another process, there is some code injection taking place.
While still in procmon, and before digging into the process activity, I like checking for dropped files (WriteFile) and new registry values (RegSetValue). The malware achieves persistence via the Run key in the HKCU hive, and drops an obfuscated payload within the same subkey.
The persistence mechanism is a small piece of JavaScript that simply invokes the neighboring payload. 'document.write' can operate similar to an 'eval', meaning execute. The parameter (payload) being passed to 'document.write' is read-in using 'Reg.Read'. The JavaScript is being interpreted via rundll, which is loading the mshtml.dll library to call the function 'RunHTMLApplication'.
This key attempts to hide/protect itself through using a value name that begins with a null terminator, which tools like regedit are not able to properly handle. This is due to Windows being written in C. In C, a string is just an array of characters, in which the end of the array/string is marked with a null-terminator.
The payload (in the same subkey) is encoded (jscript.encode) and will decode at runtime.
I found a tool online (https://gist.github.com/bcse/1834878) that will decode this type of encoding very easily. Here is the command and output:
scrdec18-VC8.exe encoded.txt decoded.txt
The decoded script makes a call to a weird domain, and then runs a powershell script. The powershell script is base64 encoded and is invoked via the 'Run()' method.
 Here is the end of the script, as well as the JavaScript that evaluates it.
The powershell payload is run via 'iex' or 'Invoke-Expression', which is equivalent to 'evaluate' or 'execute'.
This can be decoded quickly through swapping out 'iex' for 'Write-Host'.
Within the decoded powershell is another long base64 string that will decode to a byte array. This is usually indicative of shellcode. The variable '$p' will hold the byte array, which is declared in powershell as type '[byte[]]'.
We can obtain the contents of this variable easily with PowerShell ISE. Set a breakpoint after the the variable '$p' is defined, then use the .NET method 'WriteAllBytes()' to dump the shellcode to a binary file. This command is: '[io.file]::writeallbytes('output.file',$byteArray)'. We can then run cat on the file just to confirm that the dump succeeded.
Next, we can then hone in on this binary in a hexeditor. The first few bytes appear to be valid opcodes, setting up a new stack frame (prologue). These hex characters are the instructions that will be directly interpreted by the CPU. This confirms the shellcode theory.
55 - push ebp
8B EC - mov ebp, esp
Scrolling through the hex, there are several interesting pieces of code. Below are a few references to WinAPI Calls associated with dynamically loading code (VirtualAlloc, LoadLibrary, GetProcAddress), broken up into 4 byte chunks (this is 32-bit architecture).
Later in the shellcode is a DLL (4D 5A - MZ), which we can extract for more in-depth analysis. Notice the anomalous PE sections 'MPRESS1/2', this binary appears to be packed with the free MPRESS packer.
Within the DLL are references to a couple hardcoded IPs, as well as a few interesting network-related strings and API Calls.
Now we can begin reversing the shellcode, it begins with building a stack string that helps to resolve the 'kernel32.dll' library.
The APIs we saw in the hex editor are moved into local variables that will be used later in the code (they are backwards due to endianness). The highlighted selection shows a small hiccup in IDA's interpretation of a pretty important instruction.
0x64 - FS segment register
0xA1 - mov eax
0x30 - offset to PEB in FS segment register
After I saw this, I loaded the binary into remnux, disassembled the shellcode with NDISASM, and received the correct results. Here is a comparison of the output from the two programs (NDISASM on bottom) and the NDISASM command I used:
command: 'ndisasm -b32 shellcode.bin'
To load its necessary modules, the shellcode finds the PEB, jumps to the 'PEB_LDR_DATA' struct, then from there finds the 'InLoadOrderModuleList', which is a doubly linked list of the process' loaded modules.
After enumerating the necessary modules, the malware then calls 'GetProcAddress' to resolve the address of 'VirtualAlloc', which is then called to allocate new memory to be written to.
One argument is of particular interest to us for the call to 'VirtualAlloc', the fourth argument (flProtect) holds the memory protection constant for the newly allocated region of memory. 0x40 represents 'PAGE_EXECUTE_READWRITE', which are the necessary permissions for code injection. The hex dump below shows the newly allocated memory.
A few instructions below the call to 'VirtualAlloc' is the instruction 'movsb', which will move the byte at [esi] to [edi]. The 'repe' mnemonic will cause 'movsb' to repeat until the 'ECX' register is 0. This instruction is used to move the DLL we saw earlier into the newly allocated region of memory, byte by byte. See that the hex dump window is now populated with the DLL.
After this injection is complete, execution is transferred to the DLL, which first checks to see if it is running under powershell, with a simple substring function.

If it is running under powershell, then 'dllhost.exe' is spawned via 'CreateProcessA'. Perhaps the most important argument passed to this API Call is the sixth (dwCreationFlags). The number 0x404 represents two Creation Flags:
0x400 - CREATE_UNICODE_ENVIRONMENT
0x4 - CREATE_SUSPENDED
The 'CREATE_SUSPENDED' flag will halt execution of the main thread of the newly created process until 'ResumeThread' is called. 
Next, new memory is allocated in the target process (dllhost.exe) via 'VirtualAllocEx'. Notice the same memory protection constant (0x40 - PAGE_EXECUTE_READWRITE) being passed to the call. This function returns the base address of the newly allocated region of memory, which is then passed to the call to 'WriteProcessMemory'.
Here is the newly allocated memory in the target process (dllhost.exe).
The third argument passed to 'WriteProcessMemory' is a pointer to the buffer of data to be written to the target process. Looking at the hexdump below, the data appears to be the shellcode from the powershell script.
Here is the shellcode following successful injection into dllhost.exe
At this point, execution is then transferred to the shellcode via 'ResumeThread'. Now what if this payload is not running under powershell? If the malware is not running under powershell, it assumes that is has already successfully injected into dllhost. Next, the malware finds its hardcoded C2 IPs using a relative offset from the PE header of the DLL. The address of the PE header is returned via 'RtlImageNtHeader', which is then used to access the C2 IPs through a relative offset.
The C function 'strncpy' is then used to copy the string into a buffer. Before and after seen below.

Next, the malware queries the registry for the encoded payload, if it is not found, then it will drop the persistence mechanism and the payload. This is to ensure that the machine remains infected.
The IPs are then plucked from the buffer and a simple TCP socket is created for comms over port 80.
The malware then calls out via the socket 'send' function, it has the capability for both 'POST' and 'GET' methods, and has no User Agent string.
Here is a view of the traffic at the packet level - nothing particularly interesting.
This next portion shows the malware's backdoor capability, it takes in commands sent from the C2, reads them in, XORs them, and then stores them in a buffer.
Here is the XOR loop/routine
To test this subroutine, I wrote a sample command string and dropped it on the INetSim server.
The dump window shows the command following the XOR routine.
The next stage of Poweliks traditionally includes a click-fraud campaign, although the malware also has displayed the ability to receive and execute C2 commands.
Behavioral Detections:
  • Monitor run keys for new additions
  • Monitor anomalous parent/child process relationships - rundll spawning powershell, powershell spawning dllhost
  • Monitor risky powershell behavior - 'iex'/'invoke-expression'
  • Monitor registry for anomalously large entries
  • Monitor proxy logs for direct IP beaconing or direct IP POST traffic
Thanks for reading!

Comments