As @dtm has explained us how to write a crypter for Windows, and @TheDoctor has done the same for C#, I’m going to talk about how to build similar stuff for GNU/Linux. This post is kind of based on something else I wrote some months ago for a different community. I had tried to make a twist of the original paper, but… to be honest, it is difficult to come up with something simpler.
I will skip the scan-time crypter. There are no big differences compared to what has already been said in this community. So, by now, you should know enough to build such a crypter for GNU/Linux, following what my colleagues have already explained to you.
The technique I’m going to present is pretty simple and can only secure relatively small parts of the binary. I will first briefly describe how the system works and then present you a possible implementation.
The Technique
This technique allows you to crypt parts of your application using whatever algorithm you want. As usual I will use the classical XOR encoder so we do not spend time talking about cryptography and we can focus on the crypter itself.
We will have to write a small program that will encrypt parts of the application we want to secure (we will name this the off-line crypter). The secured application will also need a couple of functions (the so-called stub) to decrypt at run-time the secured parts.
In order to easily identify which parts of the application we want secured, we will be pushing them into a special section within the ELF file. Your program’s code usually ends up in the .text
section but, you are free to create additional sections for code or data in your program. And that is what we are going to do.
This way, the off-line crypter can examine the binary we want to process and easily find the parts that have to be encrypted, and the parts that have to remain unencrypted.
Enough theory, let’s start looking at the off-line crypter
A Simple Crypter
The off-line crypter is surprisingly simple. We will make use of a couple of function we have already used in the post ( ELFun File Injector). I will not discuss them again in this post.
The functions I’m talking about are:
get_file_size
. This function returns the size in bytes of an opened fileelfi_find_section
. This function returns a pointer to an ELF section structure for a memory mapped ELF file.
Be free to check the post mentioned above for details.
In addition to these two function we will need a XOR encoder function. Something like this:
#define KEY_MASK 0x7
static char key[8] ="ABCDEFGH";
void
xor_block (unsigned char *data, int len)
{
int i;
for (i = 0; i < len; data[i] ^= key[i & KEY_MASK], i++);
}
This time I’m forcing a power of two length on the key so we can use the AND operator instead of the MODULUS operator. Just for fun.
And that is it. Now, we have all the elements to write our encoding functions. Here it is:
int
encode (char *f)
{
int fd, len;
unsigned char *p;
Elf64_Shdr *s;
printf ("+ Encoding\n");
if ((fd = open (f, O_RDWR, 0)) < 0) DIE ("open");
len = (get_file_size (fd));
if ((p = mmap (0, len,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) == MAP_FAILED) DIE ("mmap");
if ((s = elfi_find_section (p, ".secure")) == NULL)
{
fprintf (stderr, "- No secure section found... Nothing to do!\n");
close (fd);
exit (1);
}
xor_block (p+s->sh_offset, s->sh_size);
/* Store Offset and size */
*((int*) (p + 0x09)) = s->sh_offset;
*((short*)(p + 0x0d)) = s->sh_size;
printf ("+ Offsets: 0x%x, 0x%x\n", s->sh_offset, s->sh_size);
close (fd);
}
This function may look scary at first glance but it is actually a pretty simple function.
The first thing we do is to open the file we want to crypt and map it in memory. This code is actually also used in the ELFun post. Mapping the file in memory is just a convenient way to modify it, without having to use lseek
, read
and write
functions.
Once our file is in memory we can search for our special section. We had named it .secure
, but you can actually use any name you want. Using the elfi_find_section
function we get a pointer to an ELF section structure containing all the section details. Specifically, we are interested in the section’s file offset and the section’s size.
Next step is to use this information, the offset and size of the section, to crypt that memory block using our XOR crypt function. Finally, we store the offset and the size in the ELF header, so the run-time decrypt function can quickly find out which memory block needs to be processed.
The ELF header contains, at the very beginning a 16 bytes field including some generic information (the ELF magic number, word-length, endianess,…). The last 7 bytes of this field are reserved for future uses. Well, this is a future use, isn’t it?. That means that we can store up to 7 bytes in the header starting at offset 0x09.
This is what the last two lines in the function do. We use 4 bytes (an int
) to store the offset to the section, and 2 bytes (a short
) to store the size of the section… and we still have 1 extra free byte!!
Now we can close our file and dump all our changes into the file in the disk, effectively applying all these changes to the program file.
A Secured Program.
In order to test our off-line crypter we need a test program with some code in a section named ‘.secured’ and containing a bit of code able to decrypt anything on that section (our stub).
First, let’s produce a main
function.
int
main (int argc, char *argv[])
{
uncrypt ();
secure_main (argc, argv);
}
Sure. We do not need much more. First we uncrypt the secured parts (this is our stub… probably I should had call it stub :), and then we run the rest of the program. In this case we just call the function secure_main
that will contain the code we want to secure.
Next point is how to push secure_main
into the .secure
section so our off-line crypter can find it and do its stuff.
The easiest way to force some functions in a specific section is to use the so-called GCC’s function attributes… the __attribute__
keyword (Function Attributes (Using the GNU Compiler Collection (GCC))) will do the trick:
#define CRYPT_ME __attribute__((section(".secure")))
CRYPT_ME int
secure_main (int argc, char *argv[])
{
printf ("This was crypted!\n");
getchar ();
return 0;
}
For convenience, we have defined a pre-processor macro so we just prefix any function definition with that macro to push it into the .secure
section. Yes, that is it.
The UNCRYPT Function
So, the last part of our system is the uncrypt
function. Once again, all that pointer gymnastics at the beginning may look intimidating, but the concept behind this is pretty simple:
- Get the offset and size of the section to uncrypt. We have conveniently stored this information in the ELF header
- Find out the memory page the encrypted code is located so we can change permissions (we need to decrypt it)
- Run the XOR function on the section
- Restore permissions on the memory block so the binary does not look suspicious at run-time.
Let’s take a quick look to the function before diving into the details.
#define DEFAULT_EP ((unsigned char*)0x400000)
int
uncrypt ()
{
int p = *((int *)(DEFAULT_EP + 0x09));
int len = *((short *)(DEFAULT_EP + 0x0d));
/* Change Permissions */
unsigned char *ptr = DEFAULT_EP + p;
unsigned char *ptr1 = DEFAULT_EP + p + len;
size_t pagesize = sysconf(_SC_PAGESIZE);
uintptr_t pagestart = (uintptr_t)ptr & -pagesize;
int psize = (ptr1 - (unsigned char*)pagestart);
/* Make the pages writable...*/
if (mprotect ((void*)pagestart, psize, PROT_READ | PROT_WRITE | PROT_EXEC) < 0)
perror ("mprotect:");
xor_block (DEFAULT_EP + p, len);
if (mprotect ((void*)pagestart, psize, PROT_READ | PROT_EXEC) < 0)
perror ("mprotect:");
printf ("+ Ready to run!\n");
}
Let’s dissect the function.
Getting the Offset and Size
The first thing to do is to retrieve the section offset and size. These values were stored in the ELF header by the off-line crypter, and now we have to retrieve them.
So, when a program is executed, the beginning of the file is directly loaded at a default memory address. For 64bits ELF Linux files, this address is 0x400000
. To keep it simple we just assume that this is always the case.
You can force the loading of the program in a different address using special linker flags. Also note that PIE (Position Independent Executables) binaries are mapped at different addresses.
What all this means, is that the ELF header (the same bytes at the beginning of the program file) is available at address 0x4000000
, and therefore we can easily retrieve our data from the header at offsets 0x09
and 0x0d
. This is what the two first lines do.
Finding the Code’s Pages
Well, this probably needs some explanation. We need to use the system call mprotect
to change the memory permissions of our .secure
section. Executable code goes into segments with the read and the execution permissions, but not the write permission. We want to decrypt a memory block located in that area, and for that, either we allocate another memory block to write the decoded bytes and then do some manipulations of the op-codes (something that may be a bit complex), or we just change permissions and decode in-place.
So, mprotect
expects as first parameter a pointer that is page aligned… Check the man page if you do not believe me.
There is no reason for our .secure
section to be page aligned. Actually, it will probably be somewhere in the middle of a page, or even laying between two different pages. So, we have to find out the page boundary for the memory block we are interested on. Then, we can change the page permissions to decrypt the code store there, and write it back in the same place.
Understanding Paging
If you are already familiar with concept of Memory Page you can skip this section. Otherwise keep reading.
There are two main memory management strategies: Pagination and Segmentation. In practice, you will always find a combination of both: Segmented Pagination or Paged Segmentation (roughly). At the end, it all depends on the support provided by the processor. Actually the support provided by the MMU (Memory Management Unit). Modern processors have an integrated MMU, but old ones use to have an external one (another chip in the board).
Anyway, the processor will keep a table of pages or segments, depending on how it was designed, and will provide mechanisms to assign, among other things permissions to them. This is the basis for virtual memory management… we need the processor to produce page faults to swap in and out pieces of memory… Anyway, we want to change those permissions, otherwise, the MMU will generate a exception… a segmentation fault.
From our application point of view, segments are defined by the ELF format as a base address and a size. An those segment will span through one or more pages. Pages are just defined by a page size. Think about them as an array. The whole memory gets organized in chunks of the page size. Let’s see how a 1Mb memory looks when it is organized in 4Kb pages (4096 bytes or in hexadecimal 0x1000
):
0x00000 |-----------|
| |
|...........| 0x00XXX
| |
0x01000 |-----------|
| |
|...........| 0x01XXX
| |
0x02000 |-----------|
... 0xffXXX
0xff000 |-----------| 0xfffff
So, the two high bits in the address, represents the page number/index, and the rest of the nibbles in the address, represents the offset within the page. Something like this:
0xPPOOO
0xPP
: Page index (increases every0x1000
bytes or 4Kb)0xOOO
: Offset within page 0xPP (ranging from 0 to0xfff
)
If we just delete the last 3 nibbles of the address we are actually getting the page base address (0XPP000
).
If you pay attention, you will see that, if our page size is, for instance 0x1000
, we can always get the page base address ANDing any address with the mask 0xff000
. Such an operation will delete the lower 3 nibbles (12 bits) in the address and automatically provide the base page address we need for mprotect
.
So, given a page size, for instance 0x1000
, the mask we need is actually the arithmetic negative of that value, for our example -0x1000
= 0xff000
.
Back to the Crypter
Now, you should easily understand what the code in the uncrypt
function does. It calculates the base page address of the page where the starting address of the .secure
section (stored in the ELF header) is located. Then, we recalculate the size of our block with respect to the page base address, so we pass the right size to the mprotect
system call. I think you understand why (code spanning multiple pages).
Now we can finally change permissions.
mprotect ((void*)pagestart, psize, PROT_READ | PROT_WRITE | PROT_EXEC)
The mprotect
will add write permissions (PROT_WRITE
) to the pages were our crypted code is, so we can just run xor_block
directly on the code and decrypt it.
After that, we call again mprotect
to restore the permissions so our process does not look suspicious.
Well, all this thing is actually pretty simple. I think that trying to explain it for everybody I have manage to make this look very complicated… It is not, really.
Testing
OK, let’s do some testing to see in practice how all this works.
First, let’s compile our test program. We have called it prog
and it will run a function called secure_main
that just prints a message and waits for the user to press a key:
$ make prog
$ readelf -h prog | grep Magic
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
$ objdump -d prog | grep -A 15 "\.secure"
Disassembly of section .secure:
00000000004008d8 <secure_main>:
4008d8: 55 push %rbp
4008d9: 48 89 e5 mov %rsp,%rbp
4008dc: 48 83 ec 10 sub $0x10,%rsp
4008e0: 89 7d fc mov %edi,-0x4(%rbp)
4008e3: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4008e7: bf 14 09 40 00 mov $0x400914,%edi
4008ec: e8 1f fc ff ff callq 400510 <puts@plt>
4008f1: e8 4a fc ff ff callq 400540 <getchar@plt>
4008f6: b8 00 00 00 00 mov $0x0,%eax
4008fb: c9 leaveq
4008fc: c3 retq
We can see that our Magic field in the ELF header looks good (all those 0s at the end), and that we have a normal function in the .secure
section that prints something and then waits for the user input (puts
+ getchar
).
Let’s run our off-line crypter against this innocent program:
$ ./crypter_rt prog
+ Encoding
+ 31 section in file. Looking for section '.secure'
+ Offsets: 0x8d8, 0x25
$ readelf -h prog | grep Magic
Magic: 7f 45 4c 46 02 01 01 00 00 d8 08 00 00 25 00 00
$ objdump -d prog | grep -A15 "\.secure"
Disassembly of section .secure:
00000000004008d8 <secure_main>:
4008d8: 14 0a adc $0xa,%al
4008da: ca a1 0d lret $0xda1
4008dd: c5 ab 58 c8 vaddsd %xmm0,%xmm10,%xmm1
4008e1: 3f (bad)
4008e2: bf 0c cc 33 b7 mov $0xb733cc0c,%edi
4008e7: f7 55 4b notl 0x4b(%rbp)
4008ea: 03 44 ad 59 add 0x59(%rbp,%rbp,4),%eax
4008ee: bb b7 be aa 09 mov $0x9aabeb7,%ebx
4008f3: b8 ba b9 ff 48 mov $0x48ffb9ba,%eax
4008f8: 41 rex.B
4008f9: 42 rex.X
4008fa: 43 rex.XB
4008fb: 8d .byte 0x8d
Now, the header’s Magic
field contains some data. Actually the offset and size (the off-line crypter prints the values for you to check). The .secure
section is now crypted… objdump cannot make sense out of it. However, if we run the program:
$ ./prog
+ Ready to run!
This was crypted!
It works OK!
I have added the getchar
function to stop the program execution so we can check the run-time memory map of the process. Run the program and while the process is waiting for the key, find out the process PID:
ps ax | grep prog
Then check the memory map with the command:
$ cat /proc/PID/maps
00400000-00401000 r-xp 00000000 08:06 6700454 /home/pico/prog
00600000-00601000 r--p 00000000 08:06 6700454 /home/pico/prog
00601000-00602000 rw-p 00001000 08:06 6700454 /home/pico/prog
7f8614f19000-7f86150cd000 r-xp 00000000 08:06 14224791 /lib/x86_64-linux-gnu/libc-2.15.so
...
You can see how the code is mapped at 0x400000
with read and execution permissions, but without write permissions (first line). You can try to remove the second mprotect
in the uncrypt
function and check how the run-time memory map will look like. A process with a executable and writable memory block is just suspicious…
As usual, you can get the source code from github to play with it:
Next
This crypter is very basic and there is a lot of room for improvement, but I hope you have got some tools to walk the next steps on your own. Anyway, I will drop a couple of ideas for you:
- Store the pointer and size of the memory portion to crypt somewhere else in the file instead of using the ELF header…
- Find alternative ways to specify the block (or blocks) of memory to encrypt/decrypt, instead of using the suspicious
.secure
section. - Use the ideas on the ELFun tutorial to inject your stub on any program so your can crypt programs even if they do not include the stub code.
- What about crypt/decrypt on demand?..
Looking forward to your progress!
Happy Hacking!
pico