A simple Linux Crypter

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 file
  • elfi_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 (https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) 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:

  1. Get the offset and size of the section to uncrypt. We have conveniently stored this information in the ELF header
  2. Find out the memory page the encrypted code is located so we can change permissions (we need to decrypt it)
  3. Run the XOR function on the section
  4. 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 every 0x1000 bytes or 4Kb)
  • 0xOOO : Offset within page 0xPP (ranging from 0 to 0xfff)

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

13 Likes

A lot to take in but educational at the same time. Good stuff pico!

2 Likes

Thanks @airth for the positive feedback. It is pretty much appreciated coming from you

1 Like

You’d lost me at the whole pagination thing, but then I scrolled down and saw your tangent and was relieved!

1 Like

Do you recommend any ELF, PE structure documentation?

Wouldn’t you just look at the COFF and maybe some RFCs?

I have looked at a bunch of links. @0x00pf just seems to know more than the links I read.

1 Like

That’s always exciting, when a person you know seems to know more than Google (about a subject).

3 Likes

I know. That part was a bit messy… and I re-wrote it a couple of times. I probably should try again in a week or two :slight_smile:

@airth, the best are the specification

and the includes (/usr/include/elf.h). There are many pages describing the format but, once you now what is it about, the specification is the main document you need.

1 Like

COFF is indeed a reference for PE… but ELF is not based on COFF. Anyway, they share a lot of concepts.
Also thanks for your words ;), that’s appreciated

I have found myself this link very useful.

It is 15 years old, but the ELF format haven’t change much since that :slight_smile: (and phrack… well it’s phrack). For the young ones… that code at the end is uuencoded . The Silvio Cesare papers (in the references of the phrack article) were really good… apparently old those paper about virus have disappear from the Internet… OK I haven’t searched in deep, but the works from Mr Cesare should just pop-up in any regular search.

Maybe is because of this:

2 Likes

AWESOME Job man. This is really good. Everything from the code to the writing is quality!

“Pointer gymnastics” made me crack up :smiley:

Can’t wait to see your next article :wink:

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.