Today I was reading about self-modifying code. I knew some things already – for example it is used sometimes as ‘camouflage’ by malicious programs to cover their intent, by JIT compilers, and for optimising loop evaluation functions. Suppose you have a number of functions that are very similar except for a couple of instructions used for some sort of comparison or evaluation. Instead of having several almost identical functions in memory, one option is to simply over-write the evaluation instructions with a slightly different piece of code every time the function is used.

However, in general, because of it’s usefulness in malicious programs, self modifying code is a bit of a security problem for operating systems. For this reason modern operating systems do not allow memory pages to be both writeable and executable. This is known as W^X – write XOR execute, meaning a page of memory is writable or executable, but never both.

The main reason for this post is an interesting work-around that Edd and I discovered today. It turns out that it is possible to map a file into memory twice. When a file is mapped into memory, the permissions are set at the same time as it is mapped. Given this, you can be a bit sneaky, and map a file as writeable, then map it again as executable. This will give you two pointers to different areas of your programs virtual address space, but both referencing the same file. You can then write some code to the file using one pointer, and execute it using the other.

As an example I created a little test C program with a single function in it as follows:

add(int a, int b)
        return a+b;

main() {
        int a = add(12, 2);

I then built this with debug symbols and no compiler optimisation (since this program doesn’t do anything, with optimisations turned on the add function would disappear completely):

gcc testcode.c -o testcode -g

This function is nothing really to do with the exploit per se. The plan is:

  • Create a file
  • Map the file into memory as writeable
  • Copy the add function from the above code into this writeable memory (ending up in the file)
  • Map the file into memory as executable
  • Assign a function pointer to point to the executable memory
  • Execute the add function that we just wrote into memory using the function pointer

To do this, the code below is used:

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

#define MEM_MAP_FILE "/tmp/a"
#define TESTCODE_FILE "testcode"
#define TESTCODE_SIZE 8427
#define FUNCTION_ADDR 0x394

	void *p1, *p2, *p3;
	int fd, fd2;
	FILE *f, *f2;
	int (*add)(int a, int b);

	/* Create/open the file */
	f = fopen(MEM_MAP_FILE, "w+");
	fd = fileno(f);

	/* Make some space in the file to copy the function in */
	/* Have to write to actually make the space */
	write(fd, "", 1);

	/* Map file as write */
	p1 = mmap(0, FUNCTION_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);

	/* Open test code */
	f2 = fopen(TESTCODE_FILE, "r");
	fd2 = fileno(f2);

	/* mmap the test code */
	p3 = mmap(0, TESTCODE_SIZE, PROT_READ, MAP_SHARED, fd2, 0);

	/* Copy function into writeable memory */

	/* Map the file as executable */
	p2 = mmap(0, FUNCTION_SIZE, PROT_EXEC, MAP_SHARED, fd, 0);

	/* Make function pointer point to the executable memory*/
	add = p2;

	/* Execute function we just wrote! */
	printf("Result = %d\n", (*add)(1, 2));

There are a couple of things to note. First, this code does no checks on return values of mmap, fopen etc. This is purely for readability on this blog! Second, the 3 #define pre-processor macros at the start of the program must be set up for the function that is being copied in from testcode, in our case ‘add’. The first is the size of the testcode executable, which can be found with “ls -l”. The second is the offset of the add function within the executable, and the third the size of the add function. The easiest way to get these last two is probably with objdump. We use “objdump -d testcode” to disassemble the file, and then scroll through until we see the add function, which will look something like:

08048394 <add>:
 8048394:	55                   	push   %ebp
 8048395:	89 e5                	mov    %esp,%ebp
 8048397:	8b 45 0c             	mov    0xc(%ebp),%eax
 804839a:	8b 55 08             	mov    0x8(%ebp),%edx
 804839d:	8d 04 02             	lea    (%edx,%eax,1),%eax
 80483a0:	5d                   	pop    %ebp
 80483a1:	c3                   	ret

Unfortunately objdump relocates the file before disassembly. Therefore, we only use the last 3 digits of the address. So in this case, the add function starts at offset 394 and ends at 3a2 (the last instruction, ret, is only 1 byte long, and starts at 3a1). So the size of the function is 3a2-394 = E.

Running the executable afterwards will now result in:

Result = 3

So we have managed to write to some memory and then execute what we wrote, working around the W^X feature!

However, this isn’t as bad as it might seem. There are actually other ways to get around W^X, for example the mprotect system call. And this doesn’t really allow you to elevate privileges very easily. You might be able to write self-modifying code, but you can’t, for example, overwrite code of a process already running as root, make the text area of an address space writable, the data area executable, or modify a file such as /bin/ls (because you still don’t have write permission to the file).

This entry was posted in Programming, Software, Unix. Bookmark the permalink.

One Response to W^X

  1. Bob Eager says:

    I remember doing this around 1979! We had a JIT compiler for BASIC, and the operating system had only one way of accessing files – mapping them. Used this to put new code into the executable space as each line was compiled, without fiddling with permissions.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>