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.
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.
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);
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.
- 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’.
- 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.
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
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:
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:
Finally, let’s see it in action: