Nowadays malware authors use a lot of techniques to hide malicious payloads in order to bypass security products and to make malware analyst life harder and fun. There are many tools that you can use to extract content from malware and there is not a standard process, you can use different tools, different techniques and different approaches to solve the same problem.
During this post I am going to quickly describe three (well, actually kind of four) of the main flows that takes me in succeed to unpack malware. But let me repeat that there are many ways to perform such a topic, I simply want to share some personal notes on my favorite flows, without pretending to write a full course material on how to Unpack Malware, which it worth of a full university class.
NB: there is a lot do say about packers, how they are, how they behave, there is much to say even on how many packers family are known, but this is not the place for that. What I am doing here is to mostly focusing on quick shot-cuts useful when you are on rush but not such powerful as debugging the entire process.
Method 0: Just Unpack It, I don’t care more
Well, if you are on rush and you just need to try to unpack a sample as quickest as possible, if you don’t care about what is going on, well Sergei Frankoff (@herrcore) and Sean Wilson (@seanmw) did a great job in releasing Unpac.ME. A web application that tries to unpack your sample, there is a limited free plan for using it, it works most of the times especially with known malware families
Method 1: The quick way
One of the quickest way to simply unpack malware is to try to figure out what packer has been used to pack your sample. Once you have the used packer you just need to run the relative un-packer and that’s it, you have done. Detect it Easy or bettern known as DiE would help you in performing such research. It has a wide signature database tracking hundreds different packers. The following image shows DiE spotting a simple (and very didactic, not really real) UPX packer.
Once you know it has been packed through UPX 3.91, just go and grab the used packer (in such case go to https://upx.github.io/) take the relative unpacker and run it against your original sample, you would see a new PE file.
Method 2: The slow but fun way to do !
This is my favorite method since it’s definitely faster than using debug and performing every step by yourself but quite powerful as well getting you the control of many actions happening into memory. Before going into this method you need to know the following main assumptions.
- The packer would performs some operations on bytes (read from external file or from the same file or taken from the network) then it will aggregate such a bytes and later on it will pass execution flow (EIP) to those bytes. We call those bytes the “payload“.
- Injecting control flows is the main strategy used by packers.
- Intercepting the injection flow will abstract us from the used packer
It is now interesting to understand how injection happens on Windows machine. Once we nailed it, we would agree that a quick way to unpack malware is just to grab content from the allocated and injected memory before the main sample (or stub) will make a change of control by passing EIP and Stack to new code.
Main Injection techniques to look for
Fortunately there are not thousands of different possibilities to inject shellcode into memory, so let take a closer look to the main ones. The most used is named process injection.
The process injection schema follows these main steps:
- OpenProcess – The OpenProcess function returns a handle of an existing process object.
- VirtualAllocEX – The VirtualAllocEx function is used to allocate the memory and grant the access permissions to the memory address.
- WriteProcessMemory – The WriteProcessMemory function writes data to an area of memory in a specified process.
- CreateRemoteThread – The CreateRemoteThread function creates a thread that runs in the virtual address space of another process.
Another very used technique is the DLL Injection which follows these steps:
- OpenProcess to Obtain the handle of the target process in which we intend to inject our DLL.
- Find the address of the LoadLibraryA function using GetProcAddress & GetModuleHandleA functions. LoadLibraryA function is used for loading the DLL into the calling process.
- VirtualAllocEX to allocate the memory space for the DLL path from where we will be loading the DLL.
- WriteProcessMemory for writing the DLL path into the allocated memory space.
- CreateRemoteThread for creating a new thread and passed the address of LoadLibraryA as the start address and the address of the DLL file as the parameter for LoadLibraryA function.
Process Hollowing is a nice and very used trick to evade endpoint security and to inject control floes. The main idea is to build a suspended process within un-mapped memory. Then replace the un-mapped memory section with the shellcode and later on map and start the process. The steps follows:
- Create a new target process in suspended state. This can be achieve by passing Create_Suspended value in dwCreationFlags parameter of CreateProcess Windows API.
- Once the process is created in suspended state we will create a new executable section. It wont be bind to any process. This can be done by using ZwCreateSection function.
- We need to locate the base address of the target process. This can be done by querying the target process using ZwQueryInformationProcess function. We can find the address of the process environment block (PEB) and then use ReadProcessMemory function to read the PEB. Once the PEB is read ReadProcessMemory function is used once again to locate the entry point from the buffer.
- We need to bind the section to the target process in order to copy the shellcode in it. To achieve this we need to map the section into current process. This can be done by using ZwMapViewOfSection function and passing handle of the current process by using GetCurrentProcess function.
- Now we will copy each byte of the shellcode into the mapped section which is created in Step 2.
- Once the shellcode is copied we can proceed to map the section into the target process. This can be done by using ZwMapViewOfSection function and passing handle of the target process.
- Once the section is mapped we will locate and construct the patch for the target process so that it can our malicious shellcode instead of the original application code.
- Once the patch is constructed we will use WriteProcessMemory to write the constructed patch into the target process entry point.
- After writing the constructed patch to the target process entry point we need to resume the thread. This can be achieve by using ResumeThread function.
Abusing the Asynchronous Procedure Call (APC) is another way to inject shellcode into processes. The way to exploit this Microsoft functionality follows theses teps:
- Create a new target process in suspended state. This can be achieve by passing Create_Suspended value in dwCreationFlags parameter of CreateProcess Windows API.
- Once the process is created obtain the handle of the target process using OpenProcess Windows API.
- Allocate the memory space for our shellcode in the target process using VirtualAllocEX Windows API.
- Write the shellcode in the allocated memory space using WriteProcessMemory Windows API.
- Obtain the handle of the primary thread from the target process using OpenThread Windows API.
- After obtaining the handle of the thread from the target process we will add a user-mode asynchronous procedure call (APC) object to the APC queue of the specified thread using QueueUserAPC Windows API which will point to the memory address of our shellcode.
- To trigger our shellcode we will resume the suspended thread using ResumeThread Windows API.
The last method that I’am going to describe in my personal notes (but there are many more out there) is called: Process Doppelgänging. Quite a recent technique it uses a very little known API for NTFS transactions.
Briefly speaking, we can create a file inside a transaction, and for no other process this file is visible, as long as our transaction is not committed. It can be used to drop and run malicious payloads in an unnoticed way. If we roll back the transaction in an appropriate moment, the operating system behaves like our file was never created.
hasherezade
The process Doppelgänging is a similar technique used to inject control and to evade common AV. It follows these steps:
- Create a new transaction, using the API CreateTransaction.
- Create a dummy file to store our payload (CreateFileTransacted).
- It is used to create a section (a buffer in a special format), which makes a base for our new process.
- Now it’s time to close it and roll back the transaction (RollbackTransaction).
All these methods are useful to inject payload into memory and to run them keeping a very low rate of detection. Our goal is to intercepts those techniques and to dump the just injected paylaod.
Intercepts these techniques and drop the payload
Now we know the main techniques used by malware to unpack themselves into memory, so we are ready to understand how to hook such functions in order to grab the payload (holding the real behavior). Again there are many techniques to perform that memory extractions, I did change at least 4 workflows until now, but the one I prefer so far is using PE-sieve (download from HERE) to extract injected objects. PE-Sieve is not able to judge the dropped file (are they malicious or not?), so you cannot consider every extracted artifact as a malicious one, you rather need to manually analyze them and express your own assumptions on them.
But let’s start with a practical example. The following image represents a PE file pretending to be a PNG image.
Looking for sections and import table (IAT) we might observe the samples imports only some of the well-known functions we ‘ve just seen in the previous section (VirtualProtect, GetProcAddress, MoveMemory, etc..) and very often used to unpack malware in memory without touching hard-drive.
Even the embedded resources are quite “heavy” which would probably hide some piece of code (??). So … we have a PE file which pretends to be an image, it only imports suspicious functions and it has got a quite heavy resource. Would it be a Malware ?
Well we do have ideas and suspects but let’s see if it injects pieces of code into the memory and let’s see what they do. Here PE-sieve comes to help us. First of all you need to sacrifice a system :D. Yep, really… you need to run on your target the sample and on the other side you need to run pe-sieve by giving the PID of the suspicious sample. PE-sieve will hook and monitor the previous injection patterns and as soon as it find the right pattern it will drop whatsoever (good files, malicious implant, etc etc) the sample injects. The following image shows the found implants running that sample.
The dropped files are placed into a directory named with the monitored PID.
We get some files into that directory. We do have .json report in order to automate results and to wrap them into external projects without using the provided PE-sieve.dll. We have a couple of shellcode (.shc) and three PE. Interesting the 400000.cursor.exe
since has 600KB of code and it is executable, and a new ICO different from the original one. Let’s check it’s own property (following image)
Now, let’s roll back our scarified VM and run this new file on it. Now let’s check its memory to see if something more is happening there.
It looks like we have clear text, no additional encryption/packing stage as shown in memory. We now can follow with classic malware analyses techniques by staging static and dynamic analysis. And, yes, since you are re-scarify your virtual machine, let maximize your effort to grab network traffic and see where it tries to communicate with.
We are facing a nice example of TrickBot version: 1000512 tag: tot793 . The following image shows the same information but coming from the internal systemcall rather then network traces.
So we nailed it. We’ve just extracted the real payload and later on we figured out it was a TrickBot.
Method 3: The old fashion way (debugger)
Everything can be done from the debugger. You can find the above API patterns by yourself and then follow the System calls and stop and copy whenever you want. you can extract or modify the sample behavior on fly and decide to re-run it as many times you need. Yes, you can, but this would take you a lot of time. Time runs against the economy. More time you need to perform your anlaysis more expensive you are, more expensive you are less customers you could have in both ways: money-wise (expensive = for few ~ cheap = for many) and time-wise (sine you have 24h a day, after that hours you cannot accept more customers). So you would need to mediate between quality/fun and time.
If you are following me since time you would probably remember that I was used to this method years ago, before such a great tools were realized (just few examples: IDA Pro Universal Unpacker or All In Memory CryptoWorm or New way to detect Packers etc..) but today I would not suggest you this method unless you are a student or not a professional Malware analyst.