Welcome back for another round of the IO wargame from SmashTheStack.org. Today we will be looking at level2. I’ll assume everyone can connect and has access to level2. Go ahead and open ssh and log in to level2. First, let’s jump over to /levels and get a listing of all files with the name beginning with “level02”. level02 and, since it’s supplied, level02.c. Quick view of level02.c gives us the following:
level2@io:/levels$ more 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]);
}
Program starts with main and prints out text about the source code. Next it checks how many command line arguments were supplied. and what the first one is. The program requires 2 arguments after the filename (which always counts as the 1st argument anytime a program is ran). The program also requires that the 1st user supplied argument not be 0. We will see why shortly. After checking the arguments, the program registers a handler function to a signal value. In this case the function catcher is registered to fire when the SIGFPE signal is encountered. Well, I wonder what we can find out about SIGFPE. According to the linux man page and info from linux.die.net,
According to POSIX, the behavior of a process is undefined after it ignores a SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(2) or raise(3). Integer division by zero has undefined result. On some architectures it will generate a SIGFPE signal. (Also dividing the most negative integer by -1 may generate SIGFPE.) Ignoring this signal might lead to an endless loop.
Well isn’t that interesting? Certain division situations can cause a SIGFPE. Looking at the last line of main, we can see the program divides the integer values of the two supplied arguments, and the 1st one is the divisor. That must be why the program doesn’t allow the value of 0, to prevent SIGFPE and division by zero instability. However, reading the above, it also says dividing the most negative integer by -1 could generate a SIGFPE, and the program doesn’t protect against the user entering those. So let’s try it! Doing the math or looking it up, we see the most negative integer in 32 bit two’s complement is -2147483648. So we try…
level2@io:/levels$ ./level02 -2147483648 -1
source code is available in level02.cWIN!
sh-4.1$ whoami
level3
Oh wow, and we’re already done since catcher, the function we registered to SIGFPE, was already programmed to spawn a shell as level3 (since the level02 program is SUID). Don’t forget to grab the password for level3 from /home/level3/.pass
That does it for today. Remember to watch your special cases, and your exception throwing and always verify user input for all cases, especially boundary cases. Also, test your programs for those cases. It’s too easy for a tired programmer to miss a boring test case on input and subsequently leave a program open to exploitation. Humans aren’t machines and thus mistakes happen. So test, test, test, and find those mistakes before someone else does.