In this new series of articles “packing”, I will present to you various key technical points used by packers, starting by dropping the malicious code from the resources section.
Attention, here I do not intend to explain what a binary packer is, I will let you search on the internet.
Throughout the series, I’ll be talking about windows packer for PE file formats but I’ll be compiling on Linux. Same, I won’t explain the PE format in detail in this series: the Wikipedia page is very well done!
For this first part, we will talk about compressing binaries in resources and then dropping them in memory from an unpacker.
The knowledge acquired in this article is for strictly educational purposes.
You are not allowed to use tools or techniques for malicious purposes: it is immoral and illegal.
If you’re not sure what you’re doing, don’t.
I cannot be held responsible for any misuse you may make of this knowledge.
1. Prerequisites
If you want to follow the development and test by yourself, there are a few prerequisites.
- Operating system running on a relatively recent Linux kernel.
- Usual development tools (gcc, make, …)
- gcc-mingw and windres tools (gcc-mingw-w64-x86-64 package for Ubuntu 20.04)
- zlib1g-dev package
- A text editor (vim, VSCode, …)
2. Vocabulary
I will use a particular vocabulary in this article. Here are some examples :
- Binary: to designate a compiled object, for example an executable or a library.
- Payload: to designate a piece of code or string of characters necessary and sufficient for the execution of an instruction suite in a process, often malicious.
- Packing, unpacking: respectively the processes for encrypting/compressing/hiding or decrypting/decompressing/revealing a binary or a payload.
- Packer, unpacker: respectively to designate the software (or actions) responsible for packing and unpacking a binary or a payload.
3. Explanation of the resources dropping technique
Before starting to develop, an explanation of what we are going to talk about is needed.
Some packers or malware will hide secret code within the executable itself that seems harmless at first glance. One of the techniques used for this is to store a payload or other executable directly in the resource section (.rsrc
) of the executable. Often this secret binary will be encrypted and/or compressed.
When the parent executable is run, this new executable will be decrypted in memory to be used in the rest of the unpacking process.
We will see here how this storage and recovery of this secret binary works in practice.
For this, we will hide a small binary in a main binary, in the resources section, then retrieve it and decrypt it in memory.
4. Development of the resources dropping technique
The development phase will take place in two stages. First, we will create the small binary to hide, then the main binary which will take care of the unpacking. Attention, I will present here one method to do it, there are many others.
Also, we’ll be sticking to 64-bit (PE32+) binaries for this article. Just know that the principle is the same for 32 bits (PE32).
4.1. Creation of the secret binary
For the sake of this article, we’re going to stick with something extremely simple. We don’t want to create a malicious payload or do anything really big for the hidden binary since the point isn’t what it will do, but it’s good to know how we’re going to hide it and how we’re going to get it back.
So here is the hidden binary. A simple program displaying “Hello!” on standard output.
#include <stdio.h>
int main(void)
{
printf("Hello !\n");
return 0;
}
We compile, and it’s ready! You are free to test it on Windows if you wish.
$ x86_64-w64-mingw32-gcc hidden.c -o hidden.exe
$ file hidden.exe
hidden.exe: PE32+ executable (console) x86-64, for MS Windows
4.2. Generating the resource with Windres
We will compress this hidden.exe binary and store it in resource format so that we can integrate it into a second binary as explained above.
Compression of the binary
In the example of this article, we will use Zlib’s compress2() function to compress our binary.
Without further ado, here is the C code.
#include <zlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
int main(int ac, char **av)
{
if (ac != 3) {
printf("Usage: %s <src_file> <size_of_file>\n", av[0]);
return EXIT_FAILURE;
}
/* input */
char *clear_filename = av[1];
int src_size = atoi(av[2]);
char *clear = (char *)malloc(sizeof(char) * src_size);
/* output */
char *compressed = (char *)malloc(sizeof(char) * src_size);
uLongf dst_size;
/* reading and compression */
int fd_rd = open(clear_filename, O_RDONLY);
read(fd_rd, clear, src_size);
close(fd_rd);
compress2((Bytef *)compressed, &dst_size, (Bytef *)clear, (uLong)src_size, 9);
/* writing */
int fd_wr = open("compressed_binary", O_WRONLY | O_CREAT, 0444);
write(fd_wr, compressed, dst_size);
close(fd_wr);
return EXIT_SUCCESS;
}
As usual, a small explanation is needed.
- Function prototype
- Our program takes two parameters: the name of the file to compress and its size in bytes
- We quickly check that the correct number of arguments has been passed.
- In user input, therefore, we have the file name
clear_filename
and the file sizesrc_size
, and we will also allocate memory in aclear
pointer to store the file data in preparation for compression. - In output, we will have the compressed data stored in the
compressed
allocated pointer as well as the final size of the compressed file, stored indst_size
. - We proceed to read the source file using the
open()
andread()
system calls. - We compress using Zlib’s
compress2()
function, whose prototype is as follows:int compress2(Bytef * dest, uLongf * destLen, const Bytef * source, uLong sourceLen, int level);
- dest: pointer where the compressed data will be stored.
- destLen: pointer where the size of the compressed data in bytes will be stored.
- source: pointer from where the data to be compressed will be read.
- level: level of compression, 9 corresponding to the maximum level of compression.
- Finally, we end up writing this compressed data to a new compressed_binary file.
Let’s test this! (be sure to compile your program with the zlib library: -lz)
gcc compress.c -o compress -lz
ls -l hidden.exe
# -rwxrwxr-x 1 ech0 ech0 316853 avril 13 23:55 hidden.exe
./compress hidden.exe 316853
file compressed_binary
# compressed_binary: zlib compressed data
Obviously, at this point in a real case there would also be additional encryption, for example with a disposable mask also stored in the resources. But for this article we will settle for compression.
Creating resource config files
Now that our compressed binary is ready, we need to create a resource (.rc) file to tell Windres what to consider as a resource.
Without further ado, here is the compressed_binary.rc file.
IDR_RCDATA0 RCDATA compressed_binary
A brief explanation of the syntax:
- IDR_RCDATA0: resource name
- RCDATA: resource type (here, data)
- compressed_binary: name of the file to include as a resource
There you go, all that remains is to call Windres to generate the object file to include as a resource.
Generation of the final resource with Windres
x86_64-w64-mingw32-windres compressed_binary.rc -O coff -o compressed_binary.rc.o
To briefly explain, we give windres our .rc file as input and we request an output coff file (windows object file).
As a reminder and to avoid you getting lost, here is what our files look like.
.
├── compress
├── compress.c
├── compressed_binary
├── compressed_binary.rc
├── compressed_binary.rc.o
├── hidden.c
└── hidden.exe
0 directories, 7 files
The resource is ready, we now move on to the development of the mini unpacker itself. This will be responsible for retrieving the embedded resource itself, then decompressing it in memory. It is therefore to this unpacker that the resource we have just generated will be integrated.
4.3. Writing the mini unpacker
This time, just like for hidden.exe, we are talking about code that is supposed to run on a Windows environment, so we will compile a PE32+ object, the idea will be dropping the code from the resources.
Finding and extracting the resource
We will use the functions of the Windows API: FindResourceA, LoadResource and LockResource. Exceptionally I will not spend time explaining them, since the Microsoft documentation is already complete.
Here is the C code that will retrieve a resource from its own resource section (.rsrc
).
#include <windows.h>
int main(void)
{
HRSRC hRes;
HGLOBAL hResLoad;
PUCHAR Data;
hRes = FindResourceA(NULL, "IDR_RCDATA0", RT_RCDATA);
hResLoad = LoadResource(NULL, hRes);
Data = LockResource(hResLoad);
}
The code compiles correctly.
x86_64-w64-mingw32-gcc depacker.c -o depacker.exe
However, we did not include our resource in this binary, so searching for resource named “IDR_RCDATA0” can only fail. Moreover, we even notice that there is no .rsrc
section in this binary:
$ readpe -S depacker.exe | grep 'Name:'
Name: .text
Name: .data
Name: .rdata
Name: .pdata
Name: .xdata
Name: .bss
Name: .idata
Name: .CRT
Name: .tls
We’ll fix this small detail by including our resource’s object file in the compilation command line:
$ x86_64-w64-mingw32-gcc depacker.c compressed_binary.rc.o -o depacker.exe
$ readpe -S depacker.exe | grep 'Name:'
Name: .text
Name: .data
Name: .rdata
Name: .pdata
Name: .xdata
Name: .bss
Name: .idata
Name: .CRT
Name: .tls
Name: .rsrc
Now the .rsrc
section appears fine, and normally contains our resource (the little compressed hidden.exe binary, if you’ve been following).
We now have code on the unpacking side that is able to extract this resource and store it in memory. However, we still have to decompress it if we want to use it.
Unpacking the hidden binary in memory
To unpack the binary, we need the following elements:
- The compressed data
- The size of the compressed data
- The size of the decompressed data to make the dynamic allocation of the buffer
- A decompression function
We have it all! Well almost… We are missing the size of the compressed and decompressed data. Of course we know it, but it must be indicated to the unpacking program: we’ll just hard-code it.
At this point, we will also need the zlib library for Windows, which we get and compile like follows:
wget http://zlib.net/zlib-1.2.12.tar.gz
tar xf zlib-1.2.12.tar.gz
rm zlib-1.2.12.tar.gz
cd zlib-1.2.12
# Affect PREFIX = x86_64-w64-mingw32-
vim win32/Makefile.gcc
# Cross-Compilation of zlib via mingw
BINARY_PATH=/usr/x86_64-w64-mingw32/bin INCLUDE_PATH=/usr/x86_64-w64-mingw32/include LIBRARY_PATH=/usr/x86_64-w64-mingw32/lib make -f win32/Makefile.gcc
We can therefore now compile our final code depacker.c which will recover the resource in memory and decompress it.
#include <windows.h>
#include "zlib.h"
int main(void)
{
HRSRC hRes;
HGLOBAL hResLoad;
PUCHAR Data;
PUCHAR uncompressed;
ULONG src_size = 89766; // Hard-coded size of compressed binary
ULONG dst_size = 316853; // Hard-coded size of decompressed binary
hRes = FindResourceA(NULL, "IDR_RCDATA0", RT_RCDATA);
hResLoad = LoadResource(NULL, hRes);
Data = LockResource(hResLoad);
uncompressed = (PUCHAR)malloc(sizeof(char) * (dst_size + 1));
uncompress(uncompressed, &dst_size, Data, src_size);
}
The principle of the uncompress()
function is exactly the same at the prototype level as for the compress2()
function initially used.
We compile to check. We obviously do not forget to specify the location of our zlib library (-L
) and to reference it (-lz
), as well as the header files (-I
)
x86_64-w64-mingw32-gcc depacker.c compressed_binary.rc.o -o depacker.exe -L./zlib-1.2.12/ -lz -I zlib-1.2.12/
5. Conclusion of the resources dropping technique
Obviously, as it is, our unpacker is useless. It just grabs the binary and unpacks it into memory, it doesn’t run it.
Ultimately, the goal is obviously to execute this new binary, and this is called dropping (in this case from the resources), performed by a dropper (here, our unpacker).
There are several ways to do this, for example writing it to disk and then executing it quite stupidly, or loading it directly into memory, doing DLL injection, process hollowing, etc.
Also be careful, what I have described in this article is one way of doing things. Indeed, storing the hidden binary in resources is not always the best way to do. Sometimes the binary will be stored in the resources as we did, but sometimes it will be stored in a separate section, or even split into several parts in several different sections… Or simply retrieved from the internet via a network connection.
In short, it should be remembered that the principle of the dropper is globally the same, but the source of the hidden binary or the payload may vary depending on the packers you are dealing with. Here, I chose the example of storage in resources because it is something that is quite close to reality.
I hope you linked this post explaining how packers hide their code by dropping it from the resources section. Thank you for reading!