Monday, October 22, 2012

Smash the Stack IO Level 2 Writeup

Introduction

We can use the password found in the previous writeup to log in to the server as the 'level2' user. As always, the levels are in /levels. We can see that there are two possible levels for level2: level2, and level2_alt. For the sake of this post, I will focus on level2, but may update it with the solution for level2_alt later.


Analyzing Level2

For this challenge, we are given two things: a SUID binary (level02), and its corresponding source code (level02.c). Let's take a look at the source code:

 level2@io:~$ cat /levels/level02.c  
 //a little fun brought to you by bla  
 #include <stdio.h>  
 #include <stdlib.h>  
 #include <signal.h>  
 #include <setjmp.h>  
 void catcher(int a)  
 {  
     setresuid(geteuid(),geteuid(),geteuid());  
     printf("WIN!\n");  
     system("/bin/sh");  
     exit(0);  
 }  
 int main(int argc, char **argv)  
 {  
     puts("source code is available in level02.c\n");  
     if (argc != 3 || !atoi(argv[2]))  
         return 1;  
     signal(SIGFPE, catcher);  
     return abs(atoi(argv[1])) / atoi(argv[2]);  
 }  

As we can see, we want to find a way to cause the 'catcher' function to execute, since it spawns a shell with level3 permissions. With this in mind, let's step through the main function:

  • We start by checking to see if we provide 3 arguments (one of which is reserved for the name of the program). If so, we attempt to convert the second user provided argument to an integer. Then, if the result is not 0, we continue. If we either provide an invalid amount of arguments, or if atoi(our_second_argument) returns a 0, the program returns 1.
  • If, however, we provide 2 valid arguments, and the first is successfully converted a non-zero integer, we continue. We then set a signal handler for the SIGFPE signal which, when the signal occurs, calls the catcher function.
  • After this, we convert both of our arguments to integers, and divide our first argument by our second argument. Then, we attempt to return the absolute value of the result.

This program is very short, which is good for us, because we can assume that every statement has a particular purpose. Since we want the 'catcher' function to execute, we can see that we want to cause a SIGFPE signal to occur. Let's investigate when this particular signal occurs. Wikipedia tells us that this signal occurs due an arithmetic operation, such as a divide by zero. This seems reasonable, since we assume we could simply put '0' as our second argument. However, remember the first 'if' statement checks to ensure that when converted to an integer, our second argument is not 0. Therefore, this will not be possible, and we must look further.

By looking in the 'signal' man page, we can see the following note: "Also dividing the most negative integer by -1 may generate SIGFPE". Bingo. Why would this occur? We remember that integers in ANSI C are represented by 4 bytes, or 32 bits. Therefore, to represent negative numbers, the leading bit must be reserved for the sign of the number. With this being the case, the maximum positive number (INT_MAX) is one less than the maximum negative number (INT_MIN). We can see that the INT_MIN will therefore be the number -1.0 * 2^31 (-2147483648). Therefore, if we divide this number by -1, it will result in an integer overflow, since the resulting positive number cannot be represented as an integer. This will cause the SIGFPE signal to occur, giving us a shell.

Let's verify this to see if it works:

 level2@io:~$ /levels/level02 -2147483648 -1  
 source code is available in level02.c  
 WIN!  
 sh-4.1$ cat /home/level3/.pass  
 G2K2EP1luDpdNQ  

Awesome, it worked. With this password, we can now SSH in as level3 and attempt the next challenge. I'll post more writeups as soon as I can. As always, if you have a question (or comment), leave me a comment below!

-Jordan

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi. I don't understand why it's generating the SIGFPE signal, since there's an Absolute Value function over the first param atoi, so in the end the operation would be abs(-2147483648)/-1 or 2147483648/-1 = -2147483648 which would be valid, assuming the 2147483648 value can go through the abs function. Help me understan pleaseeee.

    ReplyDelete
    Replies
    1. Ok never mind, I noticed the abs() function has an "undefined behavior" for values it can't handle (such as INT_MIN). Well, "undefined behavior" doesn't really say much but it turns out it will simply return the same value it received as input in this case. So that clears things up, and I just wanted to mention this since you didn't address the abs() part in your writeup. Thanks for this writeup anyway.

      Delete