Malware Packing

Packing is like wrapping a program in layers to hide its contents. Malware authors use packers to compress or encrypt the original program and add a small unpacking stub. The stub is a tiny piece of code that runs first. When the packed file is executed, the stub decompresses (or decrypts) the real malicious code into memory and then hands control to it (courses.cs.umbc.edu ,redscan.com). This means on disk you only see a wrapper, not the actual malware. Packing makes static analysis very hard, because the real code (and its strings or import table) stays hidden until runtime.

Impact of Packing on Static Malware Analysis

  • Analysts only see the unpacking stub and seemingly random (high entropy) data.
  • Readable strings, function names, and useful metadata are often missing or obfuscated.
  • The Import Table is minimal or manipulated, hiding actual API calls.
  • Section names and structures are altered (.text​ becomes UPX0​, etc.), making the file suspicious and harder to analyze statically.
  • Disassemblers like Ghidra or IDA show misleading or incomplete code paths until the file is unpacked

Impact of packing on dynamic analysis

  • When executed, the stub unpacks the real malware code into memory.
  • Analysts can observe the real code after unpacking.
  • Memory dumps after unpacking reveal the original malware.

Packing is like putting your program into a locked box.The box hides the real code, making it harder for defenders to read or change it.When you run the packed program, it first unpacks itself in memory, then does its real job.

Original Program (on disk) 
  +------------+-----------+
  | .text (code) | .idata  |
  | .rdata (.data)         |
  | ... (other sections)   |
  +------------------------+
  Entry point here ⬇︎

Packed Program (on disk after packing)
  +----------------------+
  | Stub (decompressor)  |  <-- contains code to unpack
  | Compressed data      |  (the original code encrypted)
  | (filler/empty space) |
  +----------------------+
  Entry point is at Stub ⬇︎

The packer takes the original executable (with its code, data, imports, etc.) and compresses or encrypts its contents. It then creates a new executable with a new header and stub. For example, UPX (a common packer) names its sections UPX0​, UPX1​, etc., instead of .text​ or .dataredscan.commedium.com. The original entry point (OEP) of the program is replaced by the packer stub. This stub is a tiny loader program whose job is to restore the original program in memory7orvs.github.io courses.cs.umbc.edu.

When you run the packed executable, the operating system loads this stub into memory and begins executing it. The stub allocates memory, decompresses or decrypts the original program into that space, and rebuilds the import table and other metadata.

Finally, the stub jumps to the now-unpacked original entry point (OEP), transferring control to the real malware code.

Runtime unpacking (in memory):
  [OS loads packed EXE] ---> [Stub runs]
      |
      v
  Stub does:
    - Allocate memory space 
    - Decompress original code into it 
    - Fix up Import Table (using LoadLibrary/GetProcAddress)
    - Jump to Original Entry Point

  After this:
  [Memory: Original code runs]

Runtime Flow Diagram

Packed EXE (disk) --> Stub runs (allocates memory, unpacks code, fixes imports) --> Original code executes in memory

                                Runtime
  [Disk]               [Memory after unpack]               
  +------------+        +------------------+                  
  | Stub code  | -----> | Unpacked .text   |  (Original code) 
  | Compressed |        | Unpacked .data   |  (Data restored)  
  | data blob  |        | IAT rebuilt      |  (Imports resolved) 
  +------------+        +------------------+                  

UPX a common “packer” with built‑in “unpacker”

  • UPX is a free tool that both packs and unpacks executables.
  • To pack, you run
upx <sample>  

To unpack, you run:

upx -d <sample>

  • But attackers can tweak packers so those tools break.
  • We need a manual plan‑B to peel away the packing ourselves

We will be analyzing a sample Brbbot.exe and perform static and dynamic analysis to understand it’s behaviour

> sha256sum brbbot.exe 

> f9227a44ea25a7ee8148e2d0532b14bb640f6dc52cb5b22a9f4fa7fa037417fa  brbbot.exe

image

The malware is packed using UPX , we will discuss multiple ways to unpack this sample.

image

Results From PEstudio

image

Seeing NPX0/UPX0 and UPX1 is also a big hint you’ve got a UPX‐packed binary. This could be different incase of other packers used by the malware But a software packed using UPX has section names as UPX, UPX1. but here we have NPX0.

This has been done intentionally by the attacker, this way we wont be able to unpack it using the default method.

┌─────────────────────────────┐
         Packed EXE          
├─────────────────────────────┤
 Sections:                   
  ├── NPX0  (compressed code)
  └── UPX1  (unpacker stub)  
                             
 Imports:                    
  ├── Kernel32.VirtualProtect
  ├── Kernel32.LoadLibraryA  
  └── Kernel32.GetProcAddress
                             
 Strings:                    
  └── (Mostly none          
       data appears random)  
└─────────────────────────────┘

upx -d <sample>

image

Unpacking Using Binary Patching

image

To unpack this we can change the NPX0 to UPX0 in a Hexeditor.

image

image

image

Replacing the values.

bless brbbot.exe

image

save the file , and unpack it using upx

upx -d brbbot_patched.exe 

Unpacked Binary(brbbot.exe)

image

Legitimate executables use names like .text​, .rdata​, .data​ or .rsrc​.

┌─────────────────────────────┐
        Original EXE         
├─────────────────────────────┤
 Sections:                   
  ├── .text     (code)       
  ├── .data     (data)       
  └── .rdata    (IAT, etc.)  
                             
 Imports:                    
  ├── Win32 APIs:            
      - CreateFileA         
      - send, recv          
      - RegOpenKeyEx        
      - ...                 
                             
 Strings:                    
  ├── "C:\Windows\..."       
  ├── "http://malicious"     
  └── "Hello, world"         
└─────────────────────────────┘

Entropy

image

What is Entropy?

  • A measure of randomness or disorder in data.

  • Range: 0.0 (perfectly uniform—every byte the same) to 8.0 (perfectly random all 256 byte-values equally likely).

  • Normal code/data isn’t purely random: machine-code opcodes, ASCII strings, import tables, resource blobs → entropy ≈ 5–6.

  • Compressed/encrypted (packed) data looks random → entropy > 7.5

Entropy  
0.0 ─┬───────┬───────┬───────┬───────┬─ 8.0
     1       3       5       7       8
        low    normal    high(random)

Packed with UPX

+--------+---------------+----------------+-------------------------------------------------------------+
| Section|   Raw-Size    |  Virtual-Size  | What this means                                             |
+--------+---------------+----------------+-------------------------------------------------------------+
| NPX0   |     0 bytes   | 110,592 bytes  | On disk it’s empty; Windows reserves ~108 KB in RAM         |
|        |               |                | as a placeholder for unpacked code.                         |
+--------+---------------+----------------+-------------------------------------------------------------+
| UPX1   |  34,816 bytes |  36,864 bytes  | On disk holds the UPX-compressed blob; Windows maps ~36 KB  |
|        |               |                | for it in memory, and its stub unpacks the real code into   |
|        |               |                | the NPX0 section at runtime.                                |
+--------+---------------+----------------+-------------------------------------------------------------+

On Disk:                     In Memory:
+----------------------+     +----------------------+
| UPX1 │█████ (34K)    |  ─► | UPX1 │████ (36K)     |
| NPX0 │      (0K)     |     | NPX0 │████ (108K)    |
+----------------------+     +----------------------+

  • NPX0 raw-size = 0
    → There is no actual data on disk for that section.

  • NPX0 virtual-size > 0
    → Windows will carve out that much space in RAM when you run the program.

  • UPX1 raw-size > 0
    → This is the compressed blob the packer stub will execute.

  • UPX1 virtual-size slightly larger due to section alignment rounding.+

unpacking within memory

When malware authors pack a program, they compress or encrypt the real payload, so it’s hidden in the file on disk. When the program runs, it unpacks itself in memory, and the real malicious code becomes visible only while it’s running.

Most packers don’t encrypt memory after unpacking — they only hide content on disk. So by watching memory while the malware runs, you bypass the packing layer. This is a core dynamic analysis trick.

What is ImageBase?

image

  • When you compile a Windows program, the compiler writes a preferred load address into the EXE header called the ImageBase (e.g. ​0x140000000).

  • This is merely a suggestion to Windows: “Please load me at this address in memory.”

  • If ASLR is enabled, Windows can ignore the ImageBase suggestion and load the program at a random address each time it runs.

  • If ASLR is disabled, Windows will honor the ImageBase and load the program at that exact address.

But Why disable ASLR for unpacking?

so we are going to open the specimen in CFF explorer —> optional headers —> click here

image

dll characteristics : that is because originally dlls would have aslr but executables would not and then microsoft changes so both could have it

image

CLick on file ---> save ---> overwirite the original file --> yes

So now your prorgam will run on image base, anytime it runs

https://github.com/DidierStevens/DidierStevensSuite

we can disable the DynamicBase flag using the command-line tool
setdllcharacteristics by Didier Stevens. This tool is available as free. To disable the flag, invoke the tool with the -d
parameter.

image

STEPS TO VERIFY MALWARE IS UNPACKED IN MEMORY

Step 1: Run the packed malware sample (malware.exe)
        [As Administrator, in a safe  environment]

Step 2: Open Process Hacker/any other tool
        [A tool like Task Manager but more powerful]

Step 3: Find the brbbot.exe process
        Right-click > Properties

Step 4: Go to:
        Memory > Strings... > [Set "Minimum length" to 10] > OK

Step 5: Analyze the output
        Look at readable strings found in memory.
        These may not be visible in the packed EXE file itself.

We ’re not reading the file on disk, We ’re reading the live memory of the process to get unpacked strings.

We want to dump (extract) the unpacked version of the malware from memory to a file, so we can analyze the real code statically.

Why Do We Dump from Memory?

[On Disk: brbbot.exe] → Packed, obfuscated, unreadable
[In Memory: Running Process] → Fully unpacked, real code visible!
[Dump] → Save that clean, unpacked version to analyze

But there is a problem: Dumped Executables Are Often Broken

we will be using Scylla a Windows tool used in reverse engineering to rebuild the Import Address Table (IAT) of a (Portable Executable) file.

image

Open Scylla64 and choose brbbot.exe, we are going to attach it, its very much like a debugger, with a debugger you can attach to a running program and start debugging it even though the program was already running we do the same thing with Scylla, we click on brbbot as our choice and down in the bottom theres a little log window.

image

image

image

image

Dumped EXEs Often Still Don’t Work

Even after using Scylla to dump a process and fix the Import Address Table , the dumped file often crashes when you try to run it. Because the Entry Point still points to the unpacking stub the small bit of code that unpacks the real code into memory.

Before: Packed EXE on Disk

+---------------------+
| .text (stub code)   |   Entry Point here (at the unpacking stub)
+---------------------+
| Compressed Code     |
| Compressed Data     |
+---------------------+
| Import Table (fake) |
+---------------------+

When loaded into memory, the stub runs, unpacks the real code, fixes imports dynamically, then jumps to the real OEP.

After: Process is Unpacked in Memory

+---------------------+
| Real Code in Memory |
| Real OEP is here    |   We need to point EP to here!
+---------------------+
| Real IAT (rebuilt)  |
+---------------------+
| .data, .rdata, etc. |
+---------------------+

At this stage, everything is ready in memory, and Scylla can dump it. So we:

  1. Attach Scylla to this running process.
  2. Dump it to disk.
  3. Use “IAT Autosearch” to rebuild the Import Address Table.
  4. Click Dump.

But… we forgot one thing!

Dumped EXE Still Points to Old Stub

Dumped EXE from Scylla:

+-------------------------+
| Entry Point  Stub Code|    This stub tries to unpack again
+-------------------------+
| Real Code is here       |
| Real OEP is here        |
+-------------------------+

As a result: The dumped file crashes

Lets load the malware inside x64debug and find its entry point (unpacking stub)

image

image

image

image

Scylla comes as plugin for x64 debugger, we will use that for our work.

image

After carefully dumping the process from memory and correcting its structure, we now have a clean, working executable that is ready for both static and dynamic analysis.