Malware Unpacking

Since almost all malware is packed, unpacking malware is definitely the first step you'll encounter in the process of reverse engineering a sample, and mastering it saves you a lot of time for the next analysis steps. We will start by defining and stating different types of packers then move on to discussing the Packing/ Unpacking Routines regardless of the Packer Type/Algorithm.

Packers and Friends

Packers/ Protectors/ Cryptors:

Basically a Software that is used to protect other software through means of Obfuscation, Compression and sometimes Virtualization. Back in the day the main use for a packer was to make the binaries smaller in size when the network bandwidth was smaller, but now its main use for legitimate Software is to obfuscate it to protect it's Code. But for Malicious Software Malware it's sole use is to make it hard for Analysts when analyzing Malware Samples.

//Please note that Obfuscation doesn't necessarily end by Unpacking the Sample, Malware Authors tend to Obfuscate most their actual malicious executable by putting tons of junk Code and useless subroutines to stray the analyst from the Important Functionality of the Malware.

=> Types Of Packers

  • Free: Free Packers are very commonly used for unsophisticated software, most of the time there is an unpacker for it. e.g UPX, MPRESS, FSG, npack ..etc

  • Regular: Custom Packers, which is the most common case for malware, that it's solely developed to obfuscate malware to evade AV detection. This can be Custom to a specific Malware Group/Threat Actor, or can be sold on Hack Forums with different Obfuscation/ Compression Features to make it almost Fully Undetectable (FUD). Examples of Custom Packers: YakuzaCrypter / AspireCrypter/ AtillaCrypter which are sold on Hack Forums, Group Specific Packers like Emotet/ ISFB/ Dridex/ Trickbot Packers, such group specific packers can be slightly modified each campaign.

  • Commercial: Occasionally used by Malware, but its basically for Protecting Licensed Software as it's pretty sophisticated and very hard to analyze unless cracked :3, in case of cracked/pirated Commercial packers, Malware might use them to highly obfuscate it's code. e.g: VMProtect, ASPack, Themida and Obsidium.

Different Threat Actors might stick to the same packer in different campaigns/samples, some groups might even use the same packer of a different group. Such cases are good for Threat Intelligence and Attack/Malware Correlations between different malware groups, for example with analysing a sample of Emotet using it's own custom packer, and another sample of Dridex using the same packer, this might mean that these groups are working together.

Common Packing Functionality

=> Packing a Sample

-> Example: A Custom Packer will DES encrypt an executable then Obfuscate it with GZIP Compression.

The main structure of Packed Code, is having a section that contains a piece of code Unpacking Stub with the entry point of the executable pointing to this section. When the Packed Executable is run, this Unpacking Stub is run/executed performing it's Self-Unpacking/ Self-Extracting thing.

In order to obtain a Packed Binary, Packers will parse the file header of the actual executable/binary, and then use whatever algorithm it developed to Pack/ Encrypt/ Compress it's sections. The Packer then will append a new section, that is the Unpacking Stub responsible for reversing the packing operation, with the entry point pointing to this section, that when the packed executable runs, the Unpacking Stub is executed. With this done, a new executable is created_, that is packed/encoded/compressed_ with encoded data and modified headers, and in some cases extra sections. e.g: UPX uses the section names UPX0/ UPX1/ UPX2.

Unpacking Functionality

=> Unpacking a Sample

-> Example: The Unpacking Stub will GZIP decompress then DES decrypt the packed binary to obtain the actual payload.

The Unpacking Stub: will reverse the packing process by

  1. Allocating a region of memory inside the process memory.

  2. Copy Shellcode to the allocated memory, this Shellcode could be encrypted/ or in plaintext.

  3. Jump to/Call the Copied Shellcode to execute it.

  4. The Shellcode will allocate another region of memory, then it will decrypt the actual payload/executable, copy it to the allocated region of memory.

  5. The Unpacking Stub could then Inject the Unpacked Malicious Executable into another process, or Overwrite it's own process memory with the executable, or even execute it in it's own process without overwriting anything.

The Unpacking Process most of the time involves a method of Injection Called Self-Injection where the packed executable will execute and write a region of memory inside it's own process containing the unpacked executable. Yet the Packed Executable can also inject it's the Unpacked Payload in Remote Processes __ Mostly Legitimate Windows Processes to evade detection__ this can be done using several Injection Techniques that we will discuss in later episodes dedicated for Injection Methods.

Imported Functions can either be resolved by the Unpacking Stub assuming it's encoded, or can be decoded/ resolved at runtime dynamically.

Recognizing a Packed Executable

Detecting a packed executable can be done either by recognizing packer Signatures: Constant Values or Byte Patterns or Specific Section names, also an abundance of imports, lack of strings or having lots of nonsensical strings could be an indicator that the program is packed.

=> Statically Recognizing a Packed Sample

  • Signature-Based Tools like PEiD, YaraScan ..etc

  • Strings.

  • Imports.

  • Section Names: UPX0/UPX1 ..etc for UPX Packers**, .aspack** for ASPack Packer.

  • Entropy: The higher the entropy the lower the amount of recurring bytes e.i Randomness, which indicates packed data.

  • VirtualSize/ RawSize: Noticable difference between both could indicate the Sample is packed as the VirtualSize signifies the Code while running/ in memory, RawSize signifies Code on disk. So a Case of a Section's VirtualSize being larger than it's RawSize, is an indicator that this Section will contain data that is Unpacked in Memory.

Unpacking Methods

Unpacking a Sample can be a Real Easy or Real Hard job, from a simple command like [upx -d <Sample>] to Egg-Hunting where you walk through the code trying to re-implement the Packing Algorithm, in all cases Malware or any Software sing Packing really will go about the Same routine into Unpacking it's Actual Code.

=> Main Unpacking Routine

  1. Allocating Memory with the Size of either Shellcode/ Unpacked Executable.

  2. Decrypt Shellcode/ Payload.

  3. Inject Payload into a Remote Process/ Overwrite the process with Unpacked Payload.

  4. Continue Execution form the Unpacked Payload's entry point.

=> Unpacking Methods

-> Statically:

Reverse Engineering the entire Packing Algorithm, i.e: Egg-Hunting.

This method is the hardest, it involves parsing the EXE and Shellcode to replicate the Algorithm, with certain code patterns in mind this method could be fruitful, these code patterns are called Tail Transitions:

-> [ jmp <immediate address> ]: Possible jump to the Unpacked Payoad's OEP.

-> [ push <reg/ address> ; ret ;] : push the entry point of the unpacked code into the stack and use ret instruction to continue execution from that address.

-> [ pusha/pushad ; popa/popad ; ] : instructions used to save registers' state until a certain function executes using these registers, the registers will have their original values restored after the function is done, this is commonly used with packers, and what happens between the pusha and popa is usually the unpacking routine.

-> Hopping between Sections to execute code: Executing code from a Specific Memory page then continue execution from a Different Memory Page usually means that a Section has written data to another Section which will now have the Original Entry Point OEP .

-> Dynamically:

To dynamically unpack a Packed binary we perform whats called Generic Unpacking, which is tracing the execution of the Sample until we find the Original Entry Point OEP, Tracing a Packed Binary can be done by one of these Methods with no Packing Algorithm in Mind:

Setting breakpoints on interesting instruction patterns that's been known to be used in a packed sample:

  • pushad | pusha; popad | popa: With the location of original registers' state is pushed on the Stack with PUSHA | PUSHAD we can set a hardware breakpoint to break on that location once execution returns to it, which will be followed by a jmp or **push <address; reg>**then ret instruction that would call the unpacked code's OEP.

  • for Sections that has larger VirtualSize than RawSize, we can set a breakpoint on accessing this Memory Block that could usually contain the Unpacked Code.

Tracing the execution with breakpoints on most important APIs that's related to Memory Manipulation which Malware Mostly use:

  • VirtualAlloc | VirtualAllocEx

  • VirtualProtect/ | VirtualProtectEx

  • WriteProcessMemory

Practical Examples:

Unpacking Ramnit (Usage of custom packing with an Autoit Script+UPX)

Unpacking Zloader

Unpacking Parallax (Unpacking by following Memory Manipulation API calls in a highly obfuscated sample)

-> Automatically:

The most easiest method is to Fire-and-Forget e.i. use automated tools and security products to do the unpacking process for you. For a complicated custom sample, usually a sandbox that will perform a set of Algorithms to automate the Unpacking of the Sample would be best for saving time and jumping ahead to Deep-Analysis of the Sample in hand.

The Discussion on Packers Cannot end without mentioning the important role of Obfuscation/ Compression and Encryption, which all of them are goals for a Malware Author to Pack his/her Malicious Code, since it adds a good level of hardness for an Analyst to analyze such Samples. Yet The involvement of Packers in Anti-Analysis is of another Episode.

Last updated