Recently, I’ve come across a Golang malware sample which have been packed by UPX. The sample was made to be cannot be unpack using UPX tool. So, I thinks it’s good to write a blog/note to explain how the UPX manually unpacking works.
UPX tool doesn’t work
In the figure below, there are certain situation when the malware author makes their packed malware cannot be unpack by the UPX tool to slow down the analyst. Therefore, we need to do the manually unpacking to unpack the malware.
As described in the figure above, when we
strings the binary, we can see a lot of “UPX” string indicate the file packed with UPX. But, when we try to unpack it with
-d using UPX tool, the tool saying the file is not packed with UPX.
In another case, in figure below, the windows executable has been packed and cannot be unpack by the UPX tool automatically due to some alteration. In this case, if we know how to alter back to become the original will be good. But, somehow not everyone knows which bytes to be change or alter.
So, to solve this problems, manual unpacking come to the rescue!
The required tools to unpack the ELF will be:
- IDA Pro
- Linux VM.
- OllydumpEx plugin for IDA Pro
While, unpacking EXE we going to use:
Setup OllyDumpEx for IDA Pro
- Download the plugin here
- Copy the files into IDA Pro directory plugins.
Set-up IDA Pro debugger
In order to dynamic analysis using IDA for ELF, we need to copy IDA linux debug server file into our Linux VM and run it. The file location is at
%IDA installation directory%/dbgsrv/linux_server64. After you did run the
./linux_server64 and it will start listening for the connection from the IDA Pro in your host.
While in IDA, open the packed sample and we can see there are 6 functions include
start function which are very few functions indicate the binary is packed. Select the last function from the 6 function.
This function is actually being the first callee in the
This function will invoke few functions like sysmap, sysmmap and sysmprotect to initialize the environment to unpack the code. Scrolling down through the function and find
jmp r13 instruction then put the breakpoint at that line.
Basically, the code
jmp r13 will transfer the RIP to the address contain in r13.
We now can start debug the sample by choose the debugger. Select
Remote Linux debugger.
In the options, make sure you put the path based on your Linux VM, not your Windows. And for the IP address, fill in with your Linux VM IP address which I suggest to use Host-only network adapter.
After done setup the configuration, click OK and then run the debugger. It then will break at the breakpoint we setup previously. If a warning pop-up, just click OK.
Soon we run the debugger. It will stop at the breakpoint
Step into and it will transfer the RIP into a new code region which the first instruction is call function.
Step into the call function.
Now, we arrived at another region of code.
Scroll down until you see the following assembly instruction:
pop rax jmp qword ptr [r14-8]
Breakpoint at the line
jmp qword ptr [r14-8] and continue run. Step into the
JMP instruction then
step over until
Step over the retn will transfer our RIP to another region of code. At this part, breakpoint at the end of the line of the assembly code which is
In the below figure, you will see IDA prompts that the RIP has been changed, which means that we have to jump back to the real start function which are OEP of the unpacked sample.
Then you will see real start function like figure below. This indicate we have successfully arrived at OEP of the unpacked sample.
At this point, open OllyDumpEx plugin at
Edit > Plugins > OllyDumpEx. Just click
Dump button and save the unpack file in your Windows. And click
Now, try to run the binary. If it can be run without any error like the packed version, it’s indicate that our unpacked binary successfully unpacked. Another indicator is, to compared functions detected in IDA between the packed and unpacked version of the sample. The unpacked version will have a lot of functions like below:
Manually unpacking Windows executable packed with UPX is easy. Just find the “tail jump” instruction and we can start dump the executable.
Finding the tail jump
First, load the binary in the x32dbg. And the x32dbg will pause in the module ntdll.dll. At here, just click run and the debugger will automatically stop at entry point of the sample. The first instruction for UPX packed sample should be
pushad instruction. This instruction will pushes the contents of the general-purpose registers onto the stack.
pushad instruction will be pairing with
popad at the end of the function. So, for UPX packed sample, the tail jump can be identify by looking forward the
popad instruction which usually the tail jump is made after the
Or another alternative to find tail jump, just looks for a lot of
add byte ptr ds:[eax], al and the one
jmp before the first
add byte ptr ds:[eax], al is the tail jump.
Breakpoint on the tail jump, run and step into the address. Now, we arrived into the Original Entry Point.
The packed sample now has been unpacked into the memory and we can start dump from here using
OllyDumpEx Plugins > OllyDumpEx > Dump Process.
Following the number in the figure above, first we will get the current EIP as our OEP because the current EIP is the OEP. Click the
Get EIP as OEP button. In our case, the OEP is
0x401190. And then click
Dump button. Save into the disk and click Finish.
Before the program (dump) can be work and running smoothly, we need to build the Import Address Table (IAT).
Now open up Scylla by cliciking the Scylla icon at the above of the x32dbg interface.
Now click on 1IAT Autosearch1 button so the Scylla will automatically find the Import Address Table (IAT) of our sample. After that, click on
Get Imports button to get a list of imports that the executable already has. Then, click
Fix dump and choose the dumped binary file to fix the dumped binary we dumped using the OllyDumpEx. We do this step because the previous dump binary doesn’t have IAT.
Now the fixed dump will be save with the
SCY append at the back of the dumped file.
To verify whether we successfully unpacked the file or not. Load the binary into dissassembler or debugger, and now the program have a lot of functions compared to the packed version.