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
becomesUPX0
, 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 .data
redscan.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
The malware is packed using UPX , we will discuss multiple ways to unpack this sample.
Results From PEstudio
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>
Unpacking Using Binary Patching
To unpack this we can change the NPX0 to UPX0 in a Hexeditor.
Replacing the values.
bless brbbot.exe
save the file , and unpack it using upx
upx -d brbbot_patched.exe
Unpacked Binary(brbbot.exe)
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
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?
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
dll characteristics : that is because originally dlls would have aslr but executables would not and then microsoft changes so both could have it
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.
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.
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.
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:
- Attach Scylla to this running process.
- Dump it to disk.
- Use “IAT Autosearch” to rebuild the Import Address Table.
- 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)
Scylla comes as plugin for x64 debugger, we will use that for our work.
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.