Unpacking a malware with libPeConv (Pykspa case study)
2018-01-29 08:04:11 Author: hshrzd.wordpress.com(查看原文) 阅读量:81 收藏

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

bd47776c0d1dae57c0c3e5e2832f13870a38d5fd

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:

header

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

unpack_blob

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

  • 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’.

mov_r

Decrypting blob2:

blob2

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

Again, the magic value is ‘r’:

val_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:

step1

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:

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:

https://gist.github.com/hasherezade/21f0858ee713b60070e2f33ffef44b5f

#include <stdio.h>
#include <windows.h>
#include "peconv.h"
// for the sample: bd47776c0d1dae57c0c3e5e2832f13870a38d5fd
// from: "Unpacking Pykspa Malware With Python and IDA Pro – Subscriber Request Part 1"
// https://www.youtube.com/watch?v=HfSQlC76_s4
int (__cdecl *unpack_func)(BYTE* blob, DWORD blob_size, LPCSTR lpFileName, char r_val) = nullptr;
int main(int argc, char *argv[])
{
if (argc < 2) {
std::cerr << "Args: <path to the malware>" << std::endl;
system("pause");
return 0;
}
DWORD blob1_offset = 0xC030;
DWORD blob1_size = 0x11000;
DWORD blob2_offset = 0x1D038;
DWORD blob2_size = 0x50000;
DWORD unpack_func_offset = 0x4520;
size_t v_size = 0;
LPCSTR mal_path = argv[1];
std::cout << "Reading module from: " << mal_path << std::endl;
BYTE *malware = peconv::load_pe_executable(mal_path, v_size);
if (!malware) {
return1;
}
std::cout << "Loaded" << std::endl;
ULONGLONG func_offset = (ULONGLONG)malware + unpack_func_offset;
unpack_func = (int (__cdecl *) (BYTE*, DWORD, LPCSTR, char)) func_offset;
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;
peconv::free_pe_buffer(malware, v_size);
return 0;
}

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
:try_load
peconv_project.exe malware.bin
IF NOT ERRORLEVEL 0 GOTO try_load
echo.
pause

The full package (except the malware) is available here:
https://drive.google.com/file/d/1ogRJvhEB_rFV5s9wSYVl7MbAEv6xcgyU/view

Finally, let’s see it in action:


文章来源: https://hshrzd.wordpress.com/2018/01/29/unpacking-a-malware-with-libpeconv-pykspa-case-study/
如有侵权请联系:admin#unsafe.sh