Reverse Engineering Techniques • Windows

Unpacking Modified UPX Malware: Reconstructing Corrupted Headers & Section Names

Repair corrupted section headers and byte signatures in modified UPX binaries to locate the Original Entry Point (OEP) and unpack payloads.

The program is read in by the packer. The packer creates the packed program is just data. then the executable part of the program now is unpacker.

The unpacker reads the packed program decompress it decrypts it whenever it has to do. and then creates a running program and runs it in the memory very much like a windows loader.

The entry point is also hidden. when a program runs it has a entry point.(It has to know which is the first instructions it need to run) For the packer side that too.

banner
Figure: banner Click to zoom ↗

Packers conceal the original code and can hide some PE header values to further complicate the analysis.

▪Sections: Regions of code or data that comprise the executable file, which will be loaded into memory.
▪Entry Point: Address of the first instruction in the program. (The field is officially called Address Of EntryPoint.)
▪Import Address Table (IAT): Pointers to functions in external DLLs (that is, API calls).

Packers differ in techniques, sophistication, and the protection they provide.

UPX

Malware authors have many options for packing executables.

We'll analyse UPX packed sample, which is free and common. It's relatively simple to unpack.

▪It has built-in unpacking capabilities.
▪However, it can be scrambled to complicate unpacking.

Numerous other packers exist. They tend to be more complex than UPX.

▪Armadillo, FSG, Themida, VMProtect, and many others. These are very complex and difficult to debug.
▪Some malware uses a custom, private packer.

Most packers don't include built-in unpacking capabilities.

How do you identify if the sample is packed

If possible, try to identify the packer to research it and look for unpacking tips.

You can tell that the executable might be packed if:

▪The file contains few readable strings.
▪Byte values in the file or some of its sections are too random (high entropy).
▪The file has few imports or recognizable functions.
▪Embedded strings sometimes reveal the name of the packer.

Several tools can check entropy and attempt to identify the packer by its signature.

Consider the packed version of brbbot named as packed.exe How would you be able to tell that it might be packed?

▪Static properties analysis tools that can assist us include PeStudio, Bytehist, Detect It Easy, and Exeinfo PE.

Let’s start our analysis

SAMPLE Metadata

PE studio

This packed sample reveals very few statically characteristics in PE studio.

PE studio - sections
Figure: PE studio - sections Click to zoom ↗

PE studio - sections

You maybe wondering why raw size is circled.

The raw size is the amount of space, that section takes on disk.

Virtual size is how much space it’s going to take in memory. in our case the virtual size is 0x00011000 (69632 bytes) . Then we have unusual names like NPX0, UPX1 .

Also there are very few imports in this sample.

Pe studio - imports
Figure: Pe studio - imports Click to zoom ↗

Pe studio - imports

Sample containing UPX strings.

Pe studio UPX strings
Figure: Pe studio UPX strings Click to zoom ↗

Pe studio UPX strings

There are couple of other tools to try like Detect it easy, EXEinfo and diec.

detect it easy identifying UPX packing for the sample.

DIE
Figure: DIE Click to zoom ↗

DIE

Detect it easy showing that the sample is packed with the entropy of 7

DIE - entropy showing
Figure: DIE - entropy showing Click to zoom ↗

DIE - entropy showing

Add this to the list of thing before doing static analysis.

▪Prior to starting code analysis, assess whether the file is packed.
▪Identifying the packer helps you assess the nature of the challenge ahead of you.
▪If you determine the name of the packer, you might be able to find tools or tutorials that help bypass its protection.

Usually, UPX can automatically unpack the file it generated, but the tool struggles with our sample.

▪UPX is among the limited packers that have integrated unpacking features.(when called with the "-d" option).
▪Our specimen was probably purposefully mangled to prevent UPX from automatically unpacking the file.

here you can see when we tried to unpack the sample using upx it did failed to unpack it.

tried to unpack using upx but it failed.
Figure: tried to unpack using upx but it failed. Click to zoom ↗

tried to unpack using upx but it failed.

So what can we do……

▪Maybe we could modify the headers of brbbot.exe to allow UPX to may manage to unpack it, maybe not.
▪We could try other tools that attempt to automatically handle some packers, such as Ether and UnpacMe.
▪The unpacking scripts for x64dbg can be helpful sometimes.

In a Windows Portable Executable (PE) file (.exe or .dll), the Image Base is the preferred virtual memory address where the Windows loader wants to load the executable.

Think of it as the program's preferred starting address in memory.

Our sample’s image base is 0x0000000140000000 .

Image base in pestudio
Figure: Image base in pestudio Click to zoom ↗

Image base in pestudio

Before trying to unpack the specimen, disable ASLR.

What is ASLR

ASLR (Address Space Layout Randomization) is a security feature in Windows (and other operating systems) that randomizes where a program and its components are loaded into memory each time they run.

Its goal is to make it much harder for attackers or malware exploits to predict memory addresses.

▪The PE header includes the ImageBase field. This is the virtual address where the executable wants to be loaded into memory.
▪With ASLR, Windows ignores ImageBase, randomizing the base address to make it harder to exploit the process.
▪This complicates unpacking and other code analysis techniques.

You can disable ASLR on a per-file basis:

▪Find the DIlCharacteristics field in the PE header.
▪Disable the DynamicBase flag in it.

You can use a PE editor such as CFF Explorer to do this.

▪Load the packed sample (packed.exe) file in CFF Explorer on Windows Flare VM.
sample loaded in CFF explorer
Figure: sample loaded in CFF explorer Click to zoom ↗

sample loaded in CFF explorer

▪Locate the Optional Header section on the left and click it.
Optional Header
Figure: Optional Header Click to zoom ↗

Optional Header

▪Scroll on the right to the DICharacteristics row and select "Click here”.
DLLCharactersitcs - Click here
Figure: DLLCharactersitcs - Click here Click to zoom ↗

DLLCharactersitcs - Click here

It’s DLLCharactertics that’s because Originally dll’s would have ASLR, Executable would have not. And then microsoft changes so both could have it.

▪Uncheck the "DLL can move" field and click OK.
Uncheck DLL can move
Figure: Uncheck DLL can move Click to zoom ↗

Uncheck DLL can move

That’s only change you need to make changing one bit

▪Save the modification (Ctrl+S), overwriting the original file

An alternative is to use the command

C
setdllcharacteristics -d

Now we are going to run the sample this going to seem weird but open up process hacker first. and run the sample.

Process hacker - packed.exe running
Figure: Process hacker - packed.exe running Click to zoom ↗

Process hacker - packed.exe running

The theory is because it’s running it had to be unpacked already. Cannot be running in a packed form.

Double click on the process of the sample in process hacker.

How can we confirm if something is unpacked What do we look for?

we look for strings.

Click on the memory tab then strings.

Strings
Figure: Strings Click to zoom ↗

Strings

There’s lot more strings than we use to see because these are loaded in dynamically. They are from the environment.

Scroll down to the bottom you will find a domain brb.3dtuts.by .

Strings containing a domain.
Figure: Strings containing a domain. Click to zoom ↗

Strings containing a domain.

There are lot of strings which are disperse but it is proof that it was unpacked.

So if it’s memory and not packed how can we dump it out of memory.

Couple of things you need to know

When a program is loaded into memory, It puts into different parts of the memory because on disk disk have 512 byte sector memory is 4k pages(4096-byte) so it had to do some maneuvering so the executable run.

Though there are program that helps us to take out of memory and put it on disk in disk format. the program we are going to use is Scylla.

this is sample is 64 bit so we will use Scylla x64.

Let’s do it ………

Open Scylla and attach the sample process in scylla. even though the program is running.

Attaching the sample process in scylla.
Figure: Attaching the sample process in scylla. Click to zoom ↗

Attaching the sample process in scylla.

Once attached the sample process In the bottom we’ll see a log window.

Now we need to click on IAT Autosearch . The Import address stored differently on disk than memory. then we have click imports to get those imports and then we have to fix the dump Fix Dump . But we don’t have any dump yet. we need to dump it in the disk first.

So Dump will be the first step then well do the rest of the steps.

Click on dump. it automatically names the file packed_dump.exe now click save.

click in dump and saving it to the disk.
Figure: click in dump and saving it to the disk. Click to zoom ↗

click in dump and saving it to the disk.

We don’t have any dedicated button to do this in one go we need to follow the steps.

now let’s do IAT Auto search . it does found it click ok.

IAT auto search
Figure: IAT auto search Click to zoom ↗

IAT auto search

Now click get the imports. we’ll see some green check marks

get the imports
Figure: get the imports Click to zoom ↗

get the imports

Now we have to fix the dump. In another words we are going to fix the program that is on the disk because it not runnable.

SO we fix the dump and pick the dumped program packed_dump.exe not the original the dumped one we have to fixed the dumped program.

click on Fix dump then select the dumped program.

selecting the **`packed_dump.exe` program to fix the dump.**
Figure: selecting the **`packed_dump.exe` program to fix the dump.** Click to zoom ↗

selecting the packed_dump.exe program to fix the dump.

Now click open. It will look like nothing happend. but now in you log window it will show Import rebuild success. with the new file created named as packed_dump_SCY.exe

Import Rebuild success log
Figure: Import Rebuild success log Click to zoom ↗

Import Rebuild success log

There’s still one thing that doesn’t work it’s because if we try running it. it try to run the unpacked stuff. Because the entry point is still pointing to the unpacking code which is now trying to unpacking something which is already unpacked.

We could perform static analysis on the dumped file; however, it is not runnable, probably because of the Entry Point field.

▪The unpacked code is possibly in NPXO, which is no longer empty.
▪The other sections that probably contain the unpacking code and the packed program are still present.
▪We didn't adjust the Entry Point.

Now we can see the raw-size is no longer zero it’s now 0x0000FE00 (65024 bytes) .

packed_dump_SCY.exe in pestudio showing the raw size is 0x0000FE00 (65024 bytes)
Figure: packed_dump_SCY.exe in pestudio showing the raw size is 0x0000FE00 (65024 bytes) Click to zoom ↗

packed_dump_SCY.exe in pestudio showing the raw size is 0x0000FE00 (65024 bytes)

Then why did we do this approach in the first place well how long did it take.

dump → IAT Auto search → Get Import → Fix dump.

That’s it.

Dumping the process offers a fast way of unpacking the specimen.

▪Let the harmful software extract itself afterward, extract it.
▪This method is quick and does not need much accuracy.
▪The output file includes decoded strings and code and is properly appropriate for static analysis.
▪Nonetheless, in numerous instances, the file may not be executable because of the wrong Entry Point value and additional PE header problems.

Now we will patch it on the fly while it’s running to change it’s behaviour so it won’t stop immediately id it see something it didn’t like.

Using a Debugger to dump it

Use a debugger for a more controlled approach to unpacking.

What we need to follow

▪Load the packed specimen in a debugger (we'll use X64dbg)
▪Locate the end of the unpacker and set a breakpoint there.
▪This method can be demanding and lengthy with certain packers.
▪We will begin by exploring how to accomplish that through a fairly simple example.
▪Run the specimen to let it unpack the original program into memory and pause at the end of the unpacker.
▪Single-step to let the process jump to the unpacked code (OEP).
▪Dump the unpacked process at that time (we'll use OllyDumpEx).
▪Fix up the PE header, paying attention to the IAT and EP.

let’s open the packed sample make sure you load the packed sample in x64dbg.

after opening the sample in x64dbg the next thing is to scroll and find where the code changes some places where the code is changing. where cide liiks little bit odd or different.

scrolling down we’ll see a lot of random stuff. try to find where the code changes.

I did found the area where the code changes like this.

All of a sudden we got this instead of the repeative instructions and all are zero’s.

place where the code is changing in x64 dbg.
Figure: place where the code is changing in x64 dbg. Click to zoom ↗

place where the code is changing in x64 dbg.

There’s a bunch of zeros’s that means we probably find the end of the unpacker.

we also have a jump:

C
000000014001A6EC | E9 A398FEFF              | jmp packed.140003F94                    |

This is common for unpacker to do a jump at the end of the unpacking process to where the code starts.

highlight this jump instruction the click Debug then Run until selection.

Highlighting the jump instruction then debug the select run until selection.
Figure: Highlighting the jump instruction then debug the select run until selection. Click to zoom ↗

Highlighting the jump instruction then debug the select run until selection.

Now the RIP is at the jump instruction

RIP at the jump instruction jmp packed.140003F94
Figure: RIP at the jump instruction jmp packed.140003F94 Click to zoom ↗

RIP at the jump instruction jmp packed.140003F94

Now do a step over.

we are at the address:

C
0000000140003F94   | 48:83EC 28               | sub rsp,28                              |
after step over we are the address: 0000000140003F94
Figure: after step over we are the address: 0000000140003F94 Click to zoom ↗

after step over we are the address: 0000000140003F94

Ok now we have to prove that the sample is unpacked.

right click on the blank area on the left then search for → Current region → String references

like this

clicking String references
Figure: clicking String references Click to zoom ↗

clicking String references

you will move to a new tab called References Now these strings will look different from the process hacker strings we saw earlier.

String References window in x64dbg.
Figure: String References window in x64dbg. Click to zoom ↗

String References window in x64dbg.

You will find more meaningfull strings and now we know this thing is working……

We are pretty sure that it’s unpacked now.

Click the CPU tab to go back.

Dumping

Now let’s dump it…

Under plugins click OllyDumpEx → Dump Process

Plugins → OllyDumpEx → Dump Process
Figure: Plugins → OllyDumpEx → Dump Process Click to zoom ↗

Plugins → OllyDumpEx → Dump Process

A new window OllyDumpEx will pop up like this

OllyDumpEx window
Figure: OllyDumpEx window Click to zoom ↗

OllyDumpEx window

There’s a couple of things we need to fix.

first one is Get RIP as OEP because RIP is pointing to the original entry point

next we need to double click section below the UPX1 .this one

click Get RIP as OEP it will change to 00003F94

clicked Get RIP as OEP changed to 00003F94
Figure: clicked Get RIP as OEP changed to 00003F94 Click to zoom ↗

clicked Get RIP as OEP changed to 00003F94

Now double click on the second entry of the below section. this one.

second entry of the below section UPX1
Figure: second entry of the below section UPX1 Click to zoom ↗

second entry of the below section UPX1

after double clicking the second section UPX1 we’ll get this window Edit section .

Edit Section window
Figure: Edit Section window Click to zoom ↗

Edit Section window

we need to check MEM_WRITE the last entry on the right hand side like this and click Apply.

Checked MEM_WRITE
Figure: Checked MEM_WRITE Click to zoom ↗

Checked MEM_WRITE

Those are the 2 changes you need to make before clicking Dump .

1.Get RIP as OEP
2.check MEM_WRITE of the UPX1

Now click Dump and it already supplied a name packed_dump_64.exe. go ahead and click save.

Dumping and saving the file named: packed_dump_64.exe
Figure: Dumping and saving the file named: packed_dump_64.exe Click to zoom ↗

Dumping and saving the file named: packed_dump_64.exe

you will see dump ok window click finish.

Now we have a dumped program well it will still not run. Because OllyDumpEx even though it fixed the original entry point for us and it saved it in a disk format. It didn’t fixed the IAT Import Address Table. So we still need to fix the IAT.

Fixing the Import Address Table - IAT

For that we will use another plugin and that is Scylla . do not use the scylla that’s on your system. we are going to use the plugin because the plugin interacts with the debugger and understand what’s in the memory a little bit better.

Let’s run the Scylla plugin in X64dbg.

It automatically attached the program we are debugging so we don’t have to do it manually.

Scylla Plugin automatically attached the program we are debugging.
Figure: Scylla Plugin automatically attached the program we are debugging. Click to zoom ↗

Scylla Plugin automatically attached the program we are debugging.

We also do not click dump.

we click IAT Auto search → Get Imports → Fix Dump in this order.

Click IAT Auto search click Yes to Advanced result. Now it did find IAT

IAT Auto search found:
Figure: IAT Auto search found: Click to zoom ↗

IAT Auto search found:

Now click on Get Imports . all the imports are green.

Get Imports: all the imports are green
Figure: Get Imports: all the imports are green Click to zoom ↗

Get Imports: all the imports are green

Now click Fix dump and make sure to select the program that was dumped by OllyDumpEx which is packed_dump_64.exe and click open.

At the bottom it will say Import Rebuild success with a file named packed_dump_64_SCY.exe in the log window.

Screenshot 2026-08-07 at 7.19.33 PM.png
Figure: Screenshot 2026-08-07 at 7.19.33 PM.png Click to zoom ↗

If you get to this point you can close the debugger, and make sure the sample is not running.

Now we have the file packed_dump_64_SCY.exe run it and see if it’s run……

and here in the process hacker we can see it sure enough it’s running.

Process hacker: showing the sample **`packed_dump_64_SCY.exe` is running**
Figure: Process hacker: showing the sample **`packed_dump_64_SCY.exe` is running** Click to zoom ↗

Process hacker: showing the sample packed_dump_64_SCY.exe is running

We now finally have a unpacked program.

Let’s drop the unpacked sample in PE studio to verify

In the sections the entry point is 0x00003F94 .

Pe studio - entry point of the **`packed_dump_64_SCY.exe`**  is 0x00003F94
Figure: Pe studio - entry point of the **`packed_dump_64_SCY.exe`** is 0x00003F94 Click to zoom ↗

Pe studio - entry point of the packed_dump_64_SCY.exe is 0x00003F94

In the imports we have 115 counts. All those IAT’s were rebuild That’s the another sign of unpacked.

PE studio - Imports showing 115 counts.
Figure: PE studio - Imports showing 115 counts. Click to zoom ↗

PE studio - Imports showing 115 counts.

In the strings we have now 1270 strings.

Pe studio Strings section - containing 1270 strings
Figure: Pe studio Strings section - containing 1270 strings Click to zoom ↗

Pe studio Strings section - containing 1270 strings

Once again we verify it’s unpacked.

Copied