Skip to content

Category: Uncategorized

Pwnable.kr CTF writeup: level 6 – random

This one turned out to be surprisingly easy. What’s required is an understanding of random number generators, and how they’re not really random. Implementations of random number generators are known as PRNGs, acknowledging that they are pseudo random number generators. They use various tricks in order to produce streams of not obviously connected numbers, but without an understanding of how they’re meant to work it can be easy to produce a problem similar to the one described in this CTF.

Code

#include

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

Pretty simple to understand, we have two variables, random and key. A decimal value is read into key through scanf, and rand() assigns a “random” number to the variable ‘random’. We then XOR the values and if the result is 0xdeadbeef we give up the flag. In theory, this should be hard to beat. Wouldn’t we have to keep trying values until we happen to get the right one? And with such a large problem space, that could take a very long time.

Key Insight

The most important thing to recognise is that this is incorrect use of the rand() function. Using PRNG correctly involves “seeding” the generator with a value that changes frequently, a common tactic is to use the current timestamp supplied to srand (there are some issues with this too, see if you can figure them out). This leads to predictable behaviour, which becomes a problem when knowing the random value allows us to exploit the behaviour. A list of historical examples of where this has been used in real-world attacks can be found on wikipedia’s page on random number generator attacks

Since the rand() call isn’t seeded, it’s fairly trivial to check the value of a few runs of the program in GDB. Setting a breakpoint for the next line after the call to rand() makes it easy to observe what’s returned from the function into the RAX register (the one used to store return values traditionally). A few runs through and it’s easy to see that this value doesn’t change. Due to failure to seed the PRNG it’s returning the exact same value each time, which happens to be 0x6b8b4567. Now we have the following simple equation to solve: something XOR 0x6b8b4567 = 0xdeadbeef. The value we’re looking for will be 0xdeadbeef XOR 0x6b8b4567, which ends up being = 0xb526fb88 which is 3039230856. Let’s try it:

random@ubuntu:~$ ./random 
3039230856
Good!
Mommy, I thought libc random is unpredictable...

Pwnable.kr CTF walkthrough: level 1 fd

Pwnable.kr contains a set of CTF challenges ranging from beginner to advanced. I’ve been working through them to increase my understanding of binary exploitation and Reverse Engineering, and I’ll be posting walkthroughs for ones I’ve solved

Logging in to the machine specified, we see a banner and have a bash shell, and listing the contents of the homedir we can see we have three files, a binary, a C source file (of the binary) and the flag file.

$ ssh fd@pwnable.kr -p2222
fd@pwnable.kr's password: 
 ____  __    __  ____    ____  ____   _        ___      __  _  ____  
|    \|  |__|  ||    \  /    ||    \ | |      /  _]    |  |/ ]|    \ 
|  o  )  |  |  ||  _  ||  o  ||  o  )| |     /  [_     |  ' / |  D  )
|   _/|  |  |  ||  |  ||     ||     || |___ |    _]    |    \ |    / 
|  |  |  `  '  ||  |  ||  _  ||  O  ||     ||   [_  __ |     \|    \ 
|  |   \      / |  |  ||  |  ||     ||     ||     ||  ||  .  ||  .  \
|__|    \_/\_/  |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
                                                                     
- Site admin : daehee87.kr@gmail.com
Last login: Sat Feb 17 06:54:22 2018 from 121.125.207.90
fd@ubuntu:~$ ls
fd  fd.c  flag
fd@ubuntu:~$ 

Checking the ownership of the files yields the following:

fd@ubuntu:~$ ls -l
total 16
-r-sr-x--- 1 fd_pwn fd   7322 Jun 11  2014 fd
-rw-r--r-- 1 root   root  418 Jun 11  2014 fd.c
-r--r----- 1 fd_pwn root   50 Jun 11  2014 flag

So we know that the ‘fd’ executable has the SUID bit set, and will run as fd_pwn. We can’t access the flag directly, as it’s owned by fd_pwn, but presumably we can subvert the execution of the fd binary and access the flag file as the user that owns it. A quick look at the source of fd yields a contrived exploitable example, but one that still requires a couple of insights to solve:

#include 
#include 
#include 
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

The important call here is to read(), where data is read into a buffer from a file descriptor obtained by converting the user supplied string to integer and subtracting 0x1234 (4660 in decimal). If you're unfamiliar with linux's file descriptors read this first. The key insight is that processes in linux have numerical file descriptors assigned to them, by default each has 3 (0, 1 and 2) corresponding to STDIN, STDOUT and STDERR. In theory, if we can make the read() call obtain its data from the file descriptor 0, it can be tricked into obtaining data from standard input and placing it in the character buffer 'buf'. We can then simply supply the string LETMEWIN on the command line and obtain the flag. Supplying 4660 as the user input results in the fd being 0, and prompts for a string. And we're done!

fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!