Unpacking a malware with libPeConv (Pykspa case study)

In one of the recent episodes of “Open Analysis Live!” Sergei demonstrated how to statically unpack the Pykspa Malware using a Python script. If you haven’t seen this video yet, I recommend you to watch, it is available here – and the full series is really cool.

The video inspired me to use the same sample and demonstrate an alternative solution, applying my library, libPeConv . The advantage of using libPeConv is that you don’t have to spend time on understanding and rewriting the unpacking algorithm. Instead, you can import the original unpacking function from the original malware. It can speed up the work and be helpful also in the cases when the function of our interest is obfuscated.

Analyzed sample


Static analysis

The static analysis of this malware is already well demonstrated in the mentioned video. I will just recall the important points to which we are going to refer.

Function definition

The function that is responsible for unpacking is available at RVA 0x4520. It has the following prototype:


By analyzing how it is applied, we can find out what are the arguments that should be passed:


The first one is a blob of data (BYTE*), second – size of the blob (DWORD), next comes the name of the file where the output will be written, and last one is some magic char. This is how the function declaration should look:

int __cdecl *unpack_func(BYTE* blob, DWORD blob_size, LPCSTR lpFileName, char magic_val);

Function arguments

The function is applied twice, to decrypt two blobs of data (I call them blob1 and blob2). Important things to note are: the offsets of the blobs, their sizes and the passed magic values.

Decrypting blob1:


  • Blob1 RVA: 0xC030
  • Blob1 size: 0x11000

By following the code before the function call, we can find that the last argument (the magic char) must have the value ‘r’.


Decrypting blob2:


  • Blob2 RVA: 0x1D038
  • Blob2 size: 0x50000

Again, the magic value is ‘r’:


Now we have all the data to implement a static unpacker.

Writing a unpacker

Setting up the project

For this part you need to have Visual Studio, CMake and Git installed.

I already prepared a template that you can use to make a libPeConv-based project, so it is enough to fetch it from my Github: https://github.com/hasherezade/libpeconv_project_template

git clone --recursive https://github.com/hasherezade/libpeconv_project_template.git

Now use the CMake to generate a VisualStudio project:


The malware is 32bit, so it is important to generate a project for 32bit build, otherwise we will not be able to import the sample. Example:


Click “Finish” then  “Generate” and finally you can open the project in Visual Studio.

Unpacker’s code

Code of the full unpacker is very short:


Firstly, we load the original malware (by a function from peconv). We need it to be loaded with all the dependencies and ready to be executed. A function that allows to achieve it is load_pe_executable:

BYTE* peconv::load_pe_executable(LPCSTR path_to_pe, size_t &out_size);

This malware sample has no relocation table, so we not only need it loaded, but it must be loaded at it’s original base. This operation may fail on some runs, so we have to keep it in mind.

size_t v_size = 0;
BYTE *malware = peconv::load_pe_executable(mal_path, v_size);
if (!malware) return -1;

Then, using the known offset and the reconstructed declaration of the unpacking function, we are importing it from the loaded malware.

ULONGLONG func_offset = (ULONGLONG)malware + 0x4520;
unpack_func = (int (__cdecl *) (BYTE*, DWORD, LPCSTR, char)) func_offset;

We also use the known offsets of the blobs, and make pointers to the data. After we called the unpacking function with appropriate arguments, our payloads will be dumped to files with the supplied names.

DWORD res1 = unpack_func((BYTE*)((ULONGLONG) malware + blob1_offset), blob1_size, "blob1_unpack.bin", 'r');
std::cout << "Unpacked blob1, res:" << res1 << std::endl;

DWORD res2 = unpack_func((BYTE*)((ULONGLONG) malware + blob2_offset), blob2_size, "blob2_unpack.bin", 'r');
std::cout << "Unpacked blob2, res:" << res2 << std::endl;

At the end we can free the loaded malware:

peconv::free_pe_buffer(malware, v_size);

That’s all, the unpacker is ready. One last thing we can do is preparing a .bat file that will run the unpacker until the malware get loaded (remember the loading base issue caused by the missing relocation table).

Example of the batch script:

@echo off
peconv_project.exe malware.bin

The full package (except the malware) is available here:

Finally, let’s see it in action:

About hasherezade

Programmer and researcher, interested in InfoSec.
This entry was posted in Malware, Programming, Tutorial and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s