GNU/Linux Crypters. Crafting Binaries
SECURITY
GNU/Linux Crypters. Crafting Binaries
2017-12-12
By
picoFlamingo

Updated: 13 January 2023
Almost 2 years ago I wrote about a PoC Crypter for GNU/Linux. In the meantime I had learned a couple of things and it's time to revisit that old post and update it with new and fresh information.
If you are reading this, you will likely know what a crypter is ;). Just in case you don't, our objective is to crypt the code of a given program and only decrypt it when the program gets executed. The file in the disk will be encrypted and therefore its real content won't be directly accessible.

Crypters are normally used by people that wants to protect its SW (keeping it hidden) or malware developers. It is hard to say who is worst, but we are not here to judge anybody. Let's get into the technical details.

PoCrypt2

PoCrypt worked encrypting all the code in a predefined section and modifying the main function to explicitly execute a decryption function at the very beginning of the program execution. PoCrypt2 is going to do exactly the same but in a more elegant way. Specifically:

  • PoCrypt2 will avoid the use of a function to indicate the end of the code to crypt. If you remember the original post, despite of being a pretty lame solution, this approach also caused issues when the binary was optimised.

  • PoCrypt2 removes the need to modify the main function

There is no change in the functionality of the system whatsoever, but the overall solution is pretty neat :).

Getting Section Details with a Linker Script

So, instead of adding a dummy function in the secured segment in order to calculate its size, we are going to use a linker script to get the needed information, i.e. the memory address and size of the section we want to crypt.

A linker script is a sequence of instruction for the linker to execute. Those scripts allows us to do special arrangements on the latest stage of the binary generation. Linker scripts can be pretty complex but, fortunately for us, we can just add commands on top of the default script in our system instead of completely re-write the one of those beasts.

What we are going to do is to create two symbols using a couple of the available builtin functions. This is the script:

SECTIONS
{
        ss_size = SIZEOF (.secure);
        ss_addr = ADDR   (.secure);
}

Pretty obvious isn't it?. This script will add two symbols to our binary, namely ss_size and ss_addr containing, respectively, the size and the address of the section named .secure.

Compiling and trying

It's time to try our linker script. Let's write a simple program for our test:

#include <stdio.h>
#include <stdint.h>

extern void* ss_size;
extern void* ss_addr;

#define SECURE_FUNC __attribute__((section(".secure")))

SECURE_FUNC int
secure_func (char *s)
{
      printf ("This function is secured\n");
      printf ("%s", s);
      return 0;
}

int
main ()
{
      printf ("PoCrypt2\n");
      printf ("Secured Segment at   : %p\n", (void*)((uint64_t)&ss_addr));
      printf ("Secured Segment size : 0x%x bytes\n", (int)((uint64_t)&ss_size));
      
}

The program declares the two symbols the linker will create as external variables so we can access them in our code. We have also declared a function on the .secure section so the section gets actually created and has some content. Also note how to access the content of the linker symbols.

Compile time!.

$ gcc -o test test.c -Wl,test.lds

Obviously we have named our test program test.c and our linker script test.lds.

Let's check if everything worked as expected:

$ ./test
PoCrypt2
Secured segment at   : 0x400622
Secured Segment size : 0x33 bytes
$ readelf -S test | grep -A 1 ".secure"
   [15] .secure           PROGBITS         0000000000400622  00000622
    0000000000000033  0000000000000000  AX       0     0     1

So far so good. Now it is time to add the decryption code to our program without changing main.

Understanding Linker Symbols

Update: 13 January 2023
When I wrote this post back in 2017, I just copied some code from the internet to be able to read the symbols in my main program. I didn't pay much attention to it as it worked. Recently, I had to revisit this topic and the complicated cast to access the symbol catch my attention so I dive deeper in the topic.

So what happens is that we are asking the linker script to produce a symbol whose value will be, for one symbol the address where our .secure section starts and for the other, its size.

When we write a program, the compiler generates symbol for different program entities as for instance, variables or functions. In those cases, the symbol value is set to the memory pointer where the variable is stored or where the function is located.

This way, for example, when we set a variable:

int foo = 123;

what we are asking the compiler to do is something like: * Get the value of the symbol foo... that is the symbol value that is usually a pointer, so, the value of the symbol will be & foo * Store the value in that pointer (*(&foo) = 123);

This process is done by the compiler and we do not have to care about this.

However, when we create a linker symbol like the one we used above, the value stored in the symbol won't be a pointer to a memory position where that value is stored... it is the value itself.

Knowing all this we can rewrite the code above this way.

extern char ss_size;
extern int ss_addr;

int
main ()
{
      printf ("PoCrypt2\n");
      printf ("Secured Segment at   : %p\n", &ss_addr);
      printf ("Secured Segment size : 0x%x bytes\n", &ss_size);
      
}

Alternatively, we can let the compiler treat the symbols as array names and in that case we do not even need to use the & operator.

extern char ss_size[];
extern int ss_addr[];


int
main ()
{
      printf ("PoCrypt2\n");
      printf ("Secured Segment at   : %p\n", ss_addr);
      printf ("Secured Segment size : 0x%x bytes\n", ss_size);
      
}

Check https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html for more details.

Constructors

Ideally we would like to get our code decrypted before the main function gets executed. We could patch the ELF entry point, insert our stub (the decryption code) somewhere in the file and call back to the original entry point. That is fine if we were writing a virus, but, as we have full control on the building process we can do it a lot simpler.

We are going to use a constructor function. Roughly, the standard entry point of a C program is _start function (yes, it is not main). This function normally (for the standard C library and gcc), calls a function named__libc_start_main. This function is provided by libc.so and dynamically linked. The __libc_start_main function sets up a few things for main before calling it (which is the last thing it does)..

One thing __libc_start_main does is to call functions with the attribute constructor. So we just need to add our stub as a constructor and we will be good. Let's start creating a minimal stub code for testing this:

#include <stdio.h>

static void poc_stub (void) __attribute__ ((constructor));
static void poc_stub (void)
{
      printf ("I'm the stub!!!\n");
}

Now we can recompile our test program together with our new stub and run the result:

$ gcc -o test test.c stub.c -Wl,test.lds
$ ./test
I'm the stub!!!
PoCrypt2
Secured segment at   : 0x400632
Secured Segment size : 0x33 bytes

Voila!... our stub gets executed just before main as expected.

Update: 13 January 2023

NOTE: Constructors are provided by the standard C library, as part of the start up code. This startup code is provided by the so-called crtX.o files, where X can take different values. The ones we are interested on are 1 and 0. crt1.o provides a _start function implementation that supports contructors... it basically runs through the init_array section pointers and runs them. The crt0.o does the same, but does not process constructors (roughly).

Putting it all together

Now that we have everything ready for getting into real business. The off-line encryption tool we created for PoCrypter will work fine for crypting our .secure segment. Therefore, we just need to add the decrypt function in our stub. We will also take this opportunity to clean-up the test.c code and remove everything that is not needed. So test.c will look like this:

#include <stdio.h>
#include <stdint.h>

#define SECURE_FUNC __attribute__((section(".secure")))

SECURE_FUNC int
secure_func (char *s)
{
      printf ("This function is secured\n");
      printf ("%s", s);
      return 0;
}

SECURE_FUNC int
main ()
{
      printf ("PoCrypt2\n");
      secure_func ("Hello World!\n");
}

Yes!... now we can also crypt main!

After that, we add to stub.c the functions needed for the actual decryption of the code. For now, and to keep it simple, we will be using a fixed key. If you really need to provide the key externally, try using an environmental variable or a file, for instance. The new stub.c code looks like this:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

static char *key = "PoCrypt2";
static int key_len = 8;
extern void* ss_size;
extern void* ss_addr;

void
xor_mem (unsigned char *text, int size)
{
  int i = 0;
  for (; i < size; text[i] ^= key[(i % key_len)], i++);
}

void
decrypt_mem (unsigned char *ptr, int psize)
{
      /* Calculate page boundary for the provided src pointer*/
      size_t pagesize = sysconf(_SC_PAGESIZE);
      uintptr_t pagestart = (uintptr_t)ptr & -pagesize;

      if (!key) exit (-1);

      /* Make the pages writable...*/
      if (mprotect ((void*)pagestart, psize, 
                    PROT_READ | PROT_WRITE | PROT_EXEC) < 0) exit (-2);
      xor_mem (ptr, psize);
}


static void poc_stub (void) __attribute__ ((constructor));
static void poc_stub (void)
{
      decrypt_mem ((void*)((uint64_t)&ss_addr), (int)((uint64_t)&ss_size));
}

Nothing really special or unexpected here. We just add write permissions to the .secure section so we can modify it, and then we just decrypt its content using the information provided by the linker script. We can now recompile and check that any code in section .secure is encrypted... You can use objdump -d test for that.... But what happens when you run strings?

Hiding strings

Did you run strings?, Sure you did?. And yes, it doesn't look promising. All those strings are visible, including the ones used by the secured code. That may be fine in some cases, but in this case, we are going to go further and try to also crypt the strings contained by the binary. String literals are stored in the .rodata section, which is intended to store read only data. So we can just extend our linker script, and add a second decryption call in our stub and we should be done.... Well, not really, but that is a good start.

I will skip the changes to PoCrypter. Check my github if you don't want to do them yourself... I you want just convert the section name in pocrypt.c into a variable instead of a constant and change the function declarations to propagate the section name as needed.

Let's go on taking a quick look to the modifications needed in the other files. First our linker script that now needs to emit symbols also for .rodata section.

SECTIONS {
    ss_size  = SIZEOF (.secure);
    ss_addr  = ADDR   (.secure);
    sro_size = SIZEOF (.rodata);
    sro_addr = ADDR   (.rodata);
}

That was easy. Our test.c file is already clean, so we just need to update our stub. And here is where we will face a little problem.

Crypting too much

The problem is that the key to decrypt our code and data, as it is declared right now (as a literal) it will be stored in the .rodata section... and therefore.... it will be crypted as all other strigs!!!. Don't panic. There are many different ways to solve this little hiccup. In this case, I will use a pretty simple one that basically consist on initialise the key from a constant, character by character. This way, the key itself (actually each one of the characters) will be stored in the text segment and therefore not crypted.

There are other ways to achieve the same. Let us know in the comments how do you do it.

This is how our new stub will look like. You can check function poc_stub for the key initialisation code.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

#define MAX_KEY 8
static char key[MAX_KEY];
static int key_len = (KEY_LEN > MAX_KEY ? MAX_KEY : KEY_LEN);

extern void* ss_size;
extern void* ss_addr;
extern void* sro_size;
extern void* sro_addr;

void 
xor_mem (unsigned char *text, int size)
{
  int i = 0;
  for (; i < size; text[i] ^= key[(i % key_len)], i++);
}

void
decrypt_mem (unsigned char *ptr, int psize)
{
  /* Calculate page boundary for the provided src pointer*/
  size_t pagesize = sysconf(_SC_PAGESIZE);
  uintptr_t pagestart = (uintptr_t)ptr & -pagesize;

  /* Make the pages writable...*/
  if (mprotect ((void*)pagestart, psize, 
        PROT_READ | PROT_WRITE | PROT_EXEC) < 0) exit (-2);

  xor_mem (ptr, psize);

}

static void poc_stub (void) __attribute__ ((constructor));
static void poc_stub (void)
{
  memset (key, 0, MAX_KEY);

  key[0] = _K[0];
  key[1] = _K[1];
  key[2] = _K[2];
  key[3] = _K[3];
  key[4] = _K[4];
  key[5] = _K[5];
  key[6] = _K[6];
  key[7] = _K[7];
  
  decrypt_mem ((void*)((uint64_t)&ss_addr), (int)((uint64_t)&ss_size));
  decrypt_mem ((void*)((uint64_t)&sro_addr), (int)((uint64_t)&sro_size));
}

You may have noticed that the constants _K and KEY_LEN are not defined in the previous code snippet. I'm passing them from the command-line. The reason is that I have to pass the same password also to the off-line crypter, so it is convenient to create a variable in the Makefile and use it multiple times.

So, the final piece of the puzzle is the Makefile. There you go:

all:test 

KEY=PoCrypt2
KLEN=`expr length ${KEY}`

test: test.c stub.c
    ${CC} ${CFLAGS} -o $@ $+ -Wl,$@.lds -D_K=\"${KEY}\" -DKEY_LEN=${KLEN}
    pocrypter "${KEY}" .secure $@
    pocrypter "${KEY}" .rodata $@

.PHONY:
clean:
    rm test

Which assumes that the binary pocrypter (from the original PoCypter) is in the PATH. OK, this was it. Any comment, suggestion or general feedback is welcomed!.

Header Image Credits: John Salvino

 
Tu publicidad aquí :)