Unpacking UPX with PEPyEmu

The UPX packer uses a fairly straightforward method for compressing executables: it re-creates the executable's entry point so that it points to the unpacking routine and adds two custom sections to the binary. These sections are named UPX0 and UPX1. If you load the compressed executable into Immunity Debugger and examine the memory layout (alt-M), you'll see that the executable has a memory map similar to what's shown in Listing 12-3:

Address

Size

Owner

Section

Contains

Access

Initial Access

00100000

00001000

calc_upx

PE Header

R

RWE

01001000

00019000

calc_upx

UPX0

RWE

RWE

0101A000

00007000

calc_upx

UPX1

code

RWE

RWE

01021000

00007000

calc_upx

.rsrc

data,imports RW

RWE

resources

Listing 12-3: Memory layout of a UPX compressed executable.

Listing 12-3: Memory layout of a UPX compressed executable.

We can see that the UPX1 section contains code, and this is where the UPX packer creates the main unpacking routine. The packer runs its unpacking routine in this section, and when it is finished, it JMPs out of the UPX1 section and into the "real" binary's executable code. All we need to do is let the emulator run through this unpacking routine and detect a JMP instruction that takes EIP out of the UPX1 section, and we should be at the original entry point of the executable.

Now that we have an executable that's been packed with UPX, let's utilize PyEmu to unpack and dump the original binary to disk. We are going to be using the standalone PEPyEmu module this time around, so open a new Python file, name it upx_unpacker.py, and punch in the following code.

upx_unpacker.py from ctypes import *

# You must set your path to pyemu sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import PEPyEmu

# Commandline arguments exename = sys.argv[1]

outputfile = sys.argv[2]

# Instantiate our emulator object emu = PEPyEmu()

if exename:

# Load the binary into PyEmu © if not emu.load(exename):

print "[!] Problem loading %s" % exename sys.exit(2)

else:

print "[!] Blank filename specified" sys.exit(3) © # Set our library handlers emu.set_library_handler("LoadLibraryA", loadlibrary) emu.set_library_handler("GetProcAddress", getprocaddress) emu.set_library_handler("VirtualProtect", virtualprotect)

# Set a breakpoint at the real entry point to dump binary © emu.set_mnemonic_handler( "jmp", jmp_handler )

# Execute starting from the header entry point 0 emu.execute( start=emu.entry_point )

We begin by loading the compressed executable into PyEmu ©. We then install library handlers for LoadLibraryA, GetProcAddress, and VirtualProtect. All of these functions will be called in the unpacking routine, so we need to make sure that we trap those calls and then make real function calls with the parameters that UPX is using. The next step is to handle the case when the unpacking routine is finished and jumps to the OEP. We do this by installing a mnemonic handler for the JMP instruction ©. Finally we tell the emulator to begin executing at the executable's entry point 0. Now let's create our library and instruction handlers. Add the following code.

upx_unpacker.py from ctypes import * # You must set your path to pyemu sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import PEPyEmu

HMODULE WINAPI LoadLibrary( _in LPCTSTR lpFileName

© def loadlibrary(name, address):

# Retrieve the DLL name dllname = emu.get_memory_string(emu.get_memory(emu.get_register("ESP") + 4))

# Make a real call to LoadLibrary and return the handle dllhandle = windll.kernel32.LoadLibraryA(dllname) emu.set_register("EAX", dllhandle)

# Reset the stack and return from the handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 8) emu.set_register("EIP", return_address)

return True

FARPROC WINAPI GetProcAddress(

_in HMODULE hModule,

_in LPCSTR lpProcName

© def getprocaddress(name, address):

# Get both arguments, which are a handle and the procedure name handle = emu.get_memory(emu.get_register("ESP") + 4) proc_name = emu.get_memory(emu.get_register("ESP") + 8)

# lpProcName can be a name or ordinal, if top word is null it's an ordinal if (proc_name >> 16):

procname = emu.get_memory_string(emu.get_memory(emu.get_register("ESP") + 8))

else:

procname = arg2

# Add the procedure to the emulator emu.os.add_library(handle, procname) import_address = emu.os.get_library_address(procname)

# Return the import address emu.set_register("EAX", import_address)

# Reset the stack and return from our handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 8) emu.set_register("EIP", return_address)

return True

BOOL WINAPI VirtualProtect(

_in LPVOID lpAddress,

_in SIZE_T dwSize,

_in DWORD flNewProtect,

_out PDWORD lpflOldProtect

© def virtualprotect(name, address):

# Just return TRUE emu.set_register("EAX", 1)

# Reset the stack and return from our handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 16) emu.set_register("EIP", return_address)

return True

# When the unpacking routine is finished, handle the JMP to the OEP 0 def jmp_handler(emu, mnemonic, eip, op1, op2, op3):

# The UPX1 section if eip < emu.sections["UPX1"]["base"]:

print "[*] We are jumping out of the unpacking routine." print "[*] OEP = 0x%08x" % eip # Dump the unpacked binary to disk dump_unpacked(emu) # We can stop emulating now emu.emulating = False return True

Our LoadLibrary handler traps the DLL name from the stack before using ctypes to make an actual call to LoadLibraryA, which is exported from kernel32.dll. When the real call returns, we set the EAX register to the returned handle value, reset the emulator's stack, and return from the handler. In much the same way, the GetProcAddress handler retrieves the two function parameters from the stack and makes the real call to GetProcAddress, which is also exported from kernel32.dll. We then return the address of the procedure that was requested before resetting the emulator's stack and returning from the handler. The VirtualProtect handler © returns a value of True, resets the emulator's stack, and returns from the handler. The reason we don't make a real VirtualProtect call here is because we don't need to actually protect any pages in memory; we just want to make sure that the function call emulates a successful VirtualProtect call. Our JMP instruction handler 0 does a simple check to test whether we are jumping out of the unpacking routine, and if so it calls the dump_unpacked function to dump the unpacked executable to disk. It then tells the emulator to stop execution, as our unpacking chore is finally finished.

The last step will be to add the dump_unpacked routine to our script; we'll add it after our handlers.

upx_unpacker.py def dump_unpacked(emu): global outputfile fh = open(outputfile, 'wb')

print "[*] Dumping UPX0 Section"

base = emu.sections["UPX0"]["base"] length = emu.sections["UPX0"]["vsize"]

print "[*] Base: 0x%08x Vsize: %08x"% (base, length)

for x in range(length):

fh.write("%c" % emu.get_memory(base + x, l))

print "[*] Dumping UPXl Section"

base = emu.sections["UPXl"]["base"] length = emu.sections["UPXl"]["vsize"]

print "[*] Base: 0x%08x Vsize: %08x" % (base, length)

for x in range(length):

fh.write("%c" % emu.get_memory(base + x, 1))

We are simply dumping the UPX0 and UPX1 sections to a file, and this is the last step in unpacking our executable. Once this file has been dumped to disk, we can load it into IDA, and the original executable code will be available for analysis. Now let's run our unpacking script from the command line; you should see output similar to what's shown in Listing 12-4.

C:\>C:\Python25\python.exe upx_unpacker.py C:\calc_upx.exe calc_clean.exe

[*] We are jumping out of the unpacking routine.

[*] Dumping UPX0 Section

[*] Base: 0x01001000 Vsize: 00019000

[*] Dumping UPX1 Section

[*] Base: 0x0101a000 Vsize: 00007000

Listing 12-4: Command line usage of upx_unpacker.py

You now have the file C:\calc_clean.exe, which is the raw code for the original calc.exe executable before it was packed. You're now on your way to being able to use PyEmu for a variety of reversing tasks!

Was this article helpful?

0 0

Responses

  • ANNA
    How to check a file is packed with upx in python?
    10 months ago

Post a comment