Manually unpacking a UPX packed binary with radare2 (Part_2)

dlnhxyz
4 min readMay 9, 2021

In this post I will run through how to dump the unpacked program from memory and reconstruct a working ELF, admittedly with quite a bit of winging it involved.

Picking up right where the previous part left off, we have our program unpacked in radare in debug mode and are braked at the OEP.

Run dm to inspect the memory maps of the process:

:> dm
0x0000000000400000 - 0x0000000000401000 - usr 4K s r-- unk0 unk0
0x0000000000401000 - 0x0000000000495000 * usr 592K s r-x unk1 unk1
0x0000000000495000 - 0x00000000004bc000 - usr 156K s r-- unk2 unk2
0x00000000004bc000 - 0x00000000004bd000 - usr 4K s --- unk3 unk3
0x00000000004bd000 - 0x00000000004c4000 - usr 28K s rw- unk4 unk4
0x00007fbc0617b000 - 0x00007fbc0617c000 - usr 4K s r--
0x00007ffdd66ed000 - 0x00007ffdd670e000 - usr 132K s rw- [stack] [stack] ; map._stack_.rw_
0x00007ffdd676a000 - 0x00007ffdd676d000 - usr 12K s r-- [vvar] [vvar] ; map._vvar_.r__
0x00007ffdd676d000 - 0x00007ffdd676e000 - usr 4K s r-x [vdso] [vdso] ; map._vdso_.r_x
0xffffffffff600000 - 0xffffffffff601000 - usr 4K s --x [vsyscall] [vsyscall] ; map._vsyscall_.__x
:>

Now run dmda to dump all memory regions. (dmda stands for debug memory dump all):

:> dmda
Dumped 4096 byte(s) into 0x00400000-0x00401000-r--.dmp
Dumped 606208 byte(s) into 0x00401000-0x00495000-r-x.dmp
Dumped 159744 byte(s) into 0x00495000-0x004bc000-r--.dmp
Dumped 4096 byte(s) into 0x004bc000-0x004bd000----.dmp
Dumped 28672 byte(s) into 0x004bd000-0x004c4000-rw-.dmp
Dumped 4096 byte(s) into 0x7fbc0617b000-0x7fbc0617c000-r--.dmp
Dumped 135168 byte(s) into 0x7ffdd66ed000-0x7ffdd670e000-rw-.dmp
Dumped 12288 byte(s) into 0x7ffdd676a000-0x7ffdd676d000-r--.dmp
Dumped 4096 byte(s) into 0x7ffdd676d000-0x7ffdd676e000-r-x.dmp
Dumped 4096 byte(s) into 0xffffffffff600000-0xffffffffff601000---x.dmp
:>

We saw UPX unpacked the original program to 0x00400000 so lets just try combining these dumps together and see what happens:

$ cat 0x00400000-0x00401000-r--.dmp 0x00401000-0x00495000-r-x.dmp 0x00495000-0x004bc000-r--.dmp 0x004bc000-0x004bd000----.dmp 0x004bd000-0x004c4000-rw-.dmp >> hello_unpacked
$ file hello_unpacked
hello_unpacked: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, missing section headers

Combining the dumps with cat and running file it seems to be detecting it as a 64 bit ELF, let’s just try running it:

$ chmod +x hello_unpacked
$ ./hello_unpacked
Segmentation fault (core dumped)

Well that didn’t quite work, let’s open the thing up again to see what is going wrong. This time we’ll use Cutter because sometimes it is nice to have a GUI:

Opening up the binary we just made from the concatenated memory dumps, it loads and looks OK. Let’s start a debug session and see what’s wrong.
I started a trace session and then let the program run until we hit the segfault.

It looks like this line is the culprit:

0x00402ae4      4d0306                 add  r8, qword [r14]

We hit a segfault when we tried to access the memory at the address stored in r14. Looking at r14 we see it contains the value 0x0, this doesn’t look right as we are trying to access it we would expect it to contain a valid memory address.
Let’s look at how r14 is being populated. Scrolling up we see the line:

0x00402a79      4c8b3500e70b00         mov  r14, qword [0x004c1180]

So lets take a look at whats at address 0x004c1180, seeking to this address in the hexdump we see that yes… it contains zeros:

After *some* trial and error, scrolling through this region of memory we see the address 0x004c2180 does contain a plausible address.
Taking a bit of a leap lets assume that it was the value at address 0x004c2180 that was supposed to be loaded into r14 and not the value at 0x004c1180.

So it looks like the data the program is trying to access has been shifted by 0x1000 bytes or in decimal 4096 bytes.
Again making a bit of another leap, looking back to our memory map we see:

Dumped 4096 byte(s) into 0x004bc000-0x004bd000----.dmp

So there is blob of exactly 4096 bytes with no permissions set that is sitting right before the blob containing the data that we are wanting to access that is shifted by 4096 bytes.
Let’s just see what happens when we recombine the memory dumps as before but omit this one:

$ cat 0x00400000-0x00401000-r--.dmp 0x00401000-0x00495000-r-x.dmp 0x00495000-0x004bc000-r--.dmp 0x004bd000-0x004c4000-rw-.dmp >> hello_unpacked_02
$ chmod +x hello_unpacked_02
$ ./hello_unpacked_02
Hello, World!

And it works!

This is a slightly contrived example with a lot of guesswork but we now have an unpacked ELF file that resembles our original program and runs without errors.

Hopefully this also shows how jumping between Cutter and the raw r2 cli can speed things up when you don’t know exactly what you’re looking for.

--

--