The Reversers.org Vault : UPXUnpackingTut

HomePage :: Categories :: PageIndex :: Files :: RecentChanges :: RecentlyCommented :: Login/Register

Unpacking UPX


Required Knowledge
Understanding of C/C++
Understanding of ASM
Basic knowledge of executable formats (win32/PE)
Be familiar with the Olly Debugger
Required Tools
The UPX Packer (Source and binary, win32) - Both target of the tutorial, and information about the packer used by the target ;)
The Olly Debugger - The debugger we use to find the real OEP and dump the decompressed EXE.
OllyDump (Olly Plugin) - The plugin for the above tool to dump the EXE.
Import REConstructor - A tool to rebuild the imports from a corrupted PE-header

Unpacking UPX
I recently found this tutorial on manually unpacking UPX, and it briefly told you the basics, and that was that. It had no good info on why the person did what he did, and almost no info about how he did it. Stuff like "find the OEP, (browse down a lot in the code)" could be found, but not explaining how to find what the "correct" OEP is.
Now, I'll try explaining a bit about UPX. I'm fairly inexperienced in packers, but UPX is a great tool for learning, since it's opensource. Now, notice that this is totally unusable knowledge, as the upx packer is free, and can unpack binaries. This is purely for learning. There's two approaches; write an unpacker (which we already have, upx.exe), or dumping the unpacked exe using a debugger and rebuilding it to work like a regular one.

You'll need to have the source for UPX extracted, the upx.exe from v1.25, the Import REConstructor and OllyDbg set up with OllyDump (Just extract OllyDump into the OllyDbg folder and restart Olly).

NOTE: This is aimed at win32, even though UPX supports a multitude of executable formats and platforms.
Our target for unpacking will be upx.exe v1.25 (newest at time of writing), since it's packed by upx when it's distributed.

First things first. Reading README.SRC in the root of the source tells us this:
1) The src/stub directory contains the decompression stubs that
will get added to each compressed executable.
The stubs are mainly written in assembler and get "compiled"
into ordinary C header files.
2) The src directory contains the actual packer sources. The stubs
are #included by the individual executable format handlers.
Okay, so I guess we'll be wanting to check out the stubs for win32/PE, since that's our platform, and the packer source.

These files are src/stub/l_w32pe.asm (unpacker stub (aka loader code)) and src/p_w32pe.cpp (packer). Personally I use vim to edit them, but you could just as well use MSVC++ & RADAsm, or notepad.
 

Loader stub:
    ;__FOOBAR00__

    xor eax, eax
    label1:
    call [esi + 'LOAD']
    jmps label1

    ;__OTHERLABEL__
 

Packer code:
    addLoader("FOOBAR00", NULL);

You should also read doc/loader.txt. The stubs are formatted like on the right side, and they are added to the exe before the compressed data. Doing the addLoader-call will append the code between FOOBAR00 and the next 'label' to the current loader. You might also see some text constants, like 'LOAD' (They're all 4 byte constants). These are things that the packer replaces when it packs the executable, replacing it with valid data. In this case, 'LOAD' will get replaced by an offset to the WinAPI call LoadLibraryA. In the packer (p_w32pe.cpp/.h), these are replaced by patch_le32(loader, codesize, "LOAD", realvalue).
Also, note that 'jmps' in the assembly is the same as 'jmp short', but due to how their 'linker' works, it has to be one word ;)
 


So, when UPX packs a executable, what does it do? Basically, it packs the executable using an compression algorithm, makes a new executable (the "loader"), and appends the compressed data. The loader then knows how to decompress the data, and decompresses it in memory, then jumps to the real code. (This is very simplified, I'm not even 100% certain of the specifics myself. It's a bit harder, since it has to take care of things like file resources (e.g. icons) and dll imports).
What do we want to do? We want the UPX loader to decompress the code, then dump the code after UPX is done with its stuff (and control is left to the real application). To achieve this, we have to find the part where it "jumps to the real code", and then write the executable that's in memory to disk. The UPX loader never touches the files on the disk, so we'll have to write the decompressed data to the disk ourself.

First things first. Let's try to find the "jump to the real code", i.e. the point AFTER UPX has decompressed our code, but not yet started running it.

Crack open src/p_w32pe.cpp and src/stub/l_w32pe.asm in your favorite editor, and find the following places:
p_w32pe.cpp: "void PackW32Pe::pack(OutputFile *fo)" - This is where UPX (the packer) takes a file, makes a loader, compresses it, and writes it to disk, resulting in a compressed EXE.
l_w32pe.asm: The section named "__PEMAIN01__" - This is what UPX (the packer) can theoretically write to the loader.
Now you should open upx.exe in Olly, and you should be greeted by this image:
Compressed Code?
Answer no, as there is no use in analysing the compressed code, as the debugger will misinterpret it completely ;)

Now, take a look at this little picture:
Compressed Code?
As you can see, the entrypoint of this application starts with the same code as in __PEMAIN01__.
The code that creates the start of the loader is (from p_w32pe.cpp):
    addLoader(isdll ? "PEISDLL1" : "",
              "PEMAIN01",
              icondir_count > 1 ? (icondir_count == 2 ? "PEICONS1" : "PEICONS2") : "",
              tlsindex ? "PETLSHAK" : "",
              "PEMAIN02",
              getDecompressor(),
              /*multipass ? "PEMULTIP" :  */  "",
              "PEMAIN10",
              NULL
             );

So, first it writes PEISDLL1 to the loader, if we're dealing with a DLL (which we aren't), then it writes PEMAIN01 (which we can see in olly), a part about icons (upx.exe has no icons), a TLS piece (TLS is Thread Local Storage, upx.exe doesn't use it), then it writes PEMAIN02, then it writes the decompressor (which decompresses the original code), PEMAIN10, and it's done.
Let's piece together some of the code;
; __PEMAIN01__
                pushad
                mov     esi, 'ESI0'     ; relocated
                lea     edi, [esi + 'EDI0']
__PEMAIN02__
                push    edi
mpass:
                or      ebp, byte -1

Which is pretty consistent with what Olly gives us:
00446480 > 60               PUSHAD
00446481   BE 00804200      MOV ESI,upx.00428000
00446486   8DBE 0090FDFF    LEA EDI,DWORD PTR DS:[ESI+FFFD9000]
0044648C   57               PUSH EDI
0044648D   83CD FF          OR EBP,FFFFFFFF


Now, if you follow the code in p_w32pe.cpp, you'll see it calls addLoader a bunch of times, based on various conditions. This is stuff like options passed to the exe, how the target binary is built up, etcetera. So, what we want is the LAST piece of the loader code, because when that's all done and golden, we'll have our exe set up (decompressed etcetera) properly. Tracing down tells us that the last call to addLoader is this:
    addLoader("PEMAIN20",
              ih.entry ? "PEDOJUMP" : "PERETURN",
              "IDENTSTR""UPX1HEAD",
              NULL
             );


Using the following ASM (from l_w32pe.asm) as reference:
;       __PEMAIN20__
                popad
reloc_end_jmp:
%ifdef  __PERETURN__
                xor     eax, eax
                inc     eax
                retn    0x0C
%else;  __PEDOJUMP__
                jmpn    .1+'JMPO'
.1:
%endif; __PEDUMMY3__
 

We can quickly figure out that we're looking for a popad followed either by an xor of eax, inc of eax, and a retn, or by a jmp. This jmp will be targeted to the OEP (Original Entrypoint), 'JMPO'. JMPO is an offset relative to the next instruction. Which of these two code-parts that get used are decided by ih.entry, ih is a struct containing the PE-header of the inputfile, and ih.entry is the pointer to the entrypoint function. If this is 0, it just uses the normal entrypoint, and jmps there, if it's not, it sets eax to 1 (i.e. the returnvalue), pops 12 bytes of the stack, and returns.

In order to unpack an EXE that uses the retn code, you'd have to rebuild the PE, and make ih.entry point to the function again (right?), but I won't go into that here.

So, we need to find popad, followed by a near jmp. The target of that jmp will be our OEP. There's multiple ways to do this; you can scroll down the instruction listing until you find it, or you can use a binary search for the opcodes. Problem is, you can't use Ollys command sequence search, since it doesn't support incomplete opcodes (i.e. a jmp without a known target, you can search for jmp 0xF00DF00D, but not for just jmp (even though you know it's a near jump)), so you'll have to know the opcodes for popad (0x61) and near relative jmp (0xE9). The latter is achieved by targeting the main code section in Olly, pressing CTRL + B (or rightclicking, Search For -> Binary String), and entering "61 E9" in the HEX part. You should find this at 004465E2 (assuming you have the same build of upx.exe as I do), followed by a lot of byte zero's. Select the line with the jmp, set a breakpoint (F2), and let the program (or, in this case, the loader) run (F9). It should break at the jmp before the application itself gets to load at all. Step over this instruction (F8) and tada! We're at the OEP! (0041F86F in this upx.exe, for reference) Now we should be left with a decompressed version of the exe in memory, time to start dumping it! :)

Rightclick in the main code view, and choose Dump debugged process:
Click me!
You should be prompted with the following window:
Just click Dump!
Just click Dump. Save it as upx-dumped.exe. DO NOT CLOSE OLLYDBG OR STOP DEBUGGING IT! This will kill the process, and we want the process for reconstructing the imports of the dump. Start up ImportREC.exe, find upx.exe in the "Attach to an Active Process"-window, go to the "IAT Infos needed"-part, and insert the OEP we found into the OEP field (remember to remove the imagebase from it, 00400000, so we'll be left with 1F86F (still assuming you have the same build of upx.exe)), and press IAT AutoSearch. You'll probably get a "You found something!"-dialog, click OK, and Get Imports. Then, you should also do an Auto Trace, as this might find some imports that were missed by get imports. (it won't find any extra in upx.exe)
Finding the imports
Now we'll fix our dump with the proper imports, click "Fix Dump", and target upx-dumped.exe. It'll output the file as upx-dumped_.exe, and if all went well, ImportREC will output an equivilant of the following in its log:
*** New section added successfully. RVA:00049000 SIZE:00001000
Image Import Descriptor size: 14; Total length: 6D2
C:\Documents and Settings\daxxar\Desktop\upx-dumped_.exe saved successfully.


And the exe will be 100% working. In theory, both of the upx-dumped EXEs should work, but that depends entirely on how the target application takes care of imports. In the context of UPX, the step of rebuilding the Imports is not needed, as far as I know, but it's included here for the sake of completeness :)

-- daxxar (AdminDaxxar).

  Attachment Size Date Added
      DumpDebuggedProcess.png   47.21 KB   5/09/2005 3:05 am
      CompressedCode.png   6.23 KB   5/09/2005 1:21 am
      ImportREC.png   13.04 KB   5/09/2005 3:06 am
      OllyAndCode.png   47.97 KB   5/09/2005 3:05 am
      OllyDump.png   7.89 KB   5/09/2005 3:05 am
 


Categories
CategoryTutorialsUnpacking
 Comments [Hide comments/form]
Valid XHTML 1.0 Transitional :: Valid CSS :: Powered by Wikka Wakka Wiki 1.1.6.0
Page was generated in 0.1393 seconds