Packing and dropping from resources

Cover image representing a packing process by using a resources dropper

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.

Warning

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
  • text editor (vim, VSCode, …)
Note
Unlike the manipulations of the kernel on the previous articles, here there is no risk for your system to compile and test the example which we are going to talk about.

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.
  • Packingunpacking: respectively the processes for encrypting/compressing/hiding or decrypting/decompressing/revealing a binary or a payload.
  • Packerunpacker: 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;
}
Explanation

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 size src_size, and we will also allocate memory in a clear 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 in dst_size.
  • We proceed to read the source file using the open() and read() 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
Success
And it’s a success, we managed to compress our hidden.exe binary with zlib, and we also note that its size has greatly decreased, going from 316853 to 89766 bytes. The original goal was not really to reduce the size, but rather to hide the binary in the resources.

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
Explanation

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 APIFindResourceALoadResource 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
Success

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.

Tip
For the example in this article, we’ll just hard-code it, but be aware that it’s entirely possible, for example, to include this size in a second resource (e.g. IDR_RCDATA1) in exactly the same way we included the compressed binary. Thus, it would be enough for the unpacker to recover this second resource to have the size of the first compressed and decompressed resource.

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
Tip
We just cross-compiled zlib for Windows via mingw, on a Linux machine. This kind of stuff can be done in many other cases, you don’t actually need Windows to compile for Windows.

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/
Success
There you go, our unpacker meant for dropping the code from the resources is finally ready. You can test it in a Windows environment.

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 injectionprocess hollowing, etc.

Info
We will cover this next part in a future article, to see how we can intelligently run this decrypted binary on the Windows environment.

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!

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Scroll to Top