Unpacking malware

Adam Tilmar Jakobsen · April 1, 2023

Packers are tools that compress, obfuscate, encrypt or other methods to encode the original code. When the program runs it will decode the code into memory at run time. It is important that prior to starting the analysis assess whether the sample is packed, as it can alter the whole process. Analysis of packed executables is difficult because the code is encoded in such a way that makes it unreadable. Dynamic analysis of the packed program’s code is still possible, but the process is more complex, as you first have to identify the unpacking function location. This does not mean that only malware is packed, there is a legit reason for using packers, such as protecting the algorithms used in trading software. The reason the malware authors choose to pack the code is to avoid AV detection giving more time before it is detected.

If you are able to identify the packer used it can help identify the path needed to unpack the sample, in some cases a tool that will automate the process for you. One of the best resources I found that automates the unpacking process is unpac.me, but as with any other public service when you upload a sample it will become publicly available to everyone.

Before we go more into depth on how to unpack malware. We first need a method for identifying if a sample is packed or not. Indicators of packed code:

  • Few readable strings
  • High entropy in the file (6.5)
  • Very few imports
  • Small code segments

To check the entropy of an executable tool such as PeStudio, Detect It Easy, PEBear. If you see an entropy score of 6.5 and above the chance of it being packed is very high.

-WIP- Thrown in a picture of Detect It easy

Diec is a command line tool that will try and detect it if the sample is packed and the packer used.

diec sample.exe

diec output:

PE32
    Protector: VMProtect(-)[Min protection]
    Compiler: Microsoft Visual C/C++(2003)[-]
    Linker: Microsoft Linker(7.10)[Console32,console]

Common packing techniques:

  • Entry point modification of the sample so that it points to the beginning of the unpacking code instead.
  • The import Address Table (IAT) part of the PE header is encoded making it harder to identify the external dependencies.

Here are the stages of the unpacking processes:

Stage 1 stage 2 stage 3
Execute packed program unpacks the code and stores it in memory Original code gets executed

Packing techniques

There are mainly two types of packing techniques. The first method of unpacking is either using a native RWX memory code segment in PE or allocating a new RWX code segment. Let’s take a look at the different stages of the allocation method:

  1. Allocate memory
  2. Fill the new segment with code
  3. Fix the IAT
  4. Jump to the code.

The second method of unpacking is to unpack into another process. This method uses the process hollowing technic.

  1. create a new “suspended” process
  2. Unmap then replace all the segments
  3. Set origin EIP
  4. Unsuspend the process

Unpacking

There exist multiple paths for unpacking code, and the method used depends on the packer that is used. The easiest is if the sample is packed with UPX, which is a free packer. That comes with a built-in unpacking feature, by using the following command:

upx -d 

Are you faced with samples that are not being packed with UPX. The first step starting the unpacking process, is you need to disable the ASLR on the sample, the method of doing so on Windows be viewed in Window section of this chapter. The easiest and fastest way is to run the malware, and wait for it to unpack itself into memory and then extract it from there.
This can be done using Scylla. It works very much like an debugger, in that it will attach itself to an process.
However This techniques does not work in all cases. And the extracted executable will not be runnable due to entry points and PE header issues.

Process of dumping unpacked process and fixing the import table

  1. Dump - Save the process to disk
  2. IAT autosearch - Look for import address table
  3. Get Imports - acquire the imports
  4. Fix Dump - fix the dump IAT.

When dumping files from memory using the techniques below it important to know how to fix the IAT and entry point of the file. This can be done using Xdbg with a few plugins:

  1. Plugins ⇒ OllyDebugEx ⇒ Get EIP as OEP ⇒ Fix Virtual offset ⇒ Dump Fix imports
  2. Plugins ⇒ scylla ⇒ IAT autosearch ⇒ Get imports ⇒ Fix Dump

The sample is still not runnable as the entry point still points to the unpacking code. We have to change it to point to the begging of the code instead.\

Memory map unpacking technique

This technique involves looking for the memory section with the executable flag E in the protection column with readable strings in the region and extracting the data from memory. You can achieve this with Xdbg.

  1. First load and run the sample in Xdbg
  2. Go over to memory map and locate regions of the process memory that have executable flag (E) in protection column, the unpacked code is probably in one of those segments
  3. Right click follow in disassembler
  4. Search for strings in region, look for readable strings to identify if you have located the unpacked sample.
  5. Extract the memory section and with the IAT and entrypoint with scylla.

In some cases the region is encrypted, do the previous steps and:

  1. Search for intermodular calls in region, to CryptDecrypt, and other decryption API calls.
  2. Set an hardware beakpoint at the encryption call, This is because the code changes at run time, and software breakpoint will not work as it will be overwritten by the code. (Debugger change the code to CC indicating where to stop.)
  3. Rerun the sample.
  4. It will now stop at the hardware breakpoint
  5. Step over the decryption function
  6. You can now extract the executable and fix it up.

Window API unpacking

The method relies on the packer to allocate new memory and write the program to it. The first step is to identify where the sample call VirtualAlloc(Ex), If the VirtualAlloc is set with flProtect of 0x40 it is set with PAGE_EXECUTE_READWRITE. The process can now write data into a new memory location either in itself or external process.
VirtualAlloc just allocates new memory, meaning it will empty at first. A good technique is to obverse memory section in Xdbg is set a hardware breakpoint on the memory region, that gets triggered when the region is executed. You can identify if the new code is in an executable or dll by the file head MZ (4D 5A).

Another approach is to use VirtualProtect. In xdbg you set a breakpoint on VirtualProtect. Run the code until you hit the breakpoint. In the stack view look at the first parameter, this is the memory region whose protection that will be changed.
Right-click on the parameter and follow in dump
You now need to identify if the presence of MZ is present in the region. If it is then you have identified an executable.

You can now extract the memory region, by right click on the memory region and select Follow in memory map In the memory map. the memory region, we are interested in should now be highlighted. Right-click and dump memory to file

You now need to fix the alignment of raw and virtual with pe_unmapper using the base address of the memory region, to ensure it is in disk format.

pe_umapper /in sample.bin /base #baseaddress /out sample_fixed.bin

These techniques will not work on all packers, as Some packers are too complex for the method described above to work, even if you dump the process it might not be able to run due to entry point, IAT or other problems. In these cases, it could be best to allow the sample to run inside the debugger.

Unpacking .Net application

Unpacking .net application can be a little different then other samples. use dnspy instead to open the sample as it works better with .net applications.
One way to look for new modules. Another is looking for assembly or module loading.
You can identify this by using the export project/find option.
The tools below will try and automate the process for you.

de4dot MegaDumper

Twitter, Facebook