Saturday, January 14, 2012

Numeric: ns1

gera's Insecure Programming ns1.c

I added some printf debug statements.

/* n1.c *
* specially crafted to feed your brain by gera@core-sdi.com */

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

#define MAX_SIZE 80

unsigned int atoul(char *str) {
unsigned int answer=0;
for (;*str && isdigit(*str);
answer *= 10, answer += *str++-'0');

printf("unsigned int answer = 0x%x (%u)\n", answer, answer);
return answer;
}

int main(int argv, char **argc) {
char buf[MAX_SIZE],*pbuf=buf;
int count = atoul(argc[1]);
printf("signed int count = 0x%x (%d)\n", count, count);

if (count >= MAX_SIZE) count = MAX_SIZE-1;
printf("after bounds check, count = 0x%x (%d)\n", count, count);

while (count--) {
printf("count = 0x%x (%d)\n", count, count);
*pbuf++=getchar();
}
*pbuf=0;
}

Here are some test values.

dennis@ipa:~$ ./n1 30
unsigned int answer = 0x1e (30)
signed int count = 0x1e (30)
after bounds check, count = 0x1e (30)

dennis@ipa:~$ ./n1 100
unsigned int answer = 0x64 (100)
signed int count = 0x64 (100)
after bounds check, count = 0x4f (79)

dennis@ipa:~$ ./n1 1000000000000
unsigned int answer = 0xd4a51000 (3567587328)
signed int count = 0xd4a51000 (-727379968)
after bounds check, count = 0xd4a51000 (-727379968)

As can be seen from the second test and the boundary condition check in the code, if count is greater than 80 it is reset to MAX_SIZE-1 (79).

In the third test, if count becomes very large, it overflows the signed type and turns into a negative number.

The first important boundary number is 2147483648 which is where a 32-bit signed int overflows.

dennis@ipa:~$ echo "AAAA" | ./n1_debug 2147483647 | head -5
unsigned int answer = 0x7fffffff (2147483647)
signed int count = 0x7fffffff (2147483647)
after bounds check, count = 0x4f (79)
count = 0x4e (78)
count = 0x4d (77)

dennis@ipa:~$ echo "AAAA" | ./n1_debug 2147483648 | head -5
unsigned int answer = 0x80000000 (2147483648)
signed int count = 0x80000000 (-2147483648)
after bounds check, count = 0x80000000 (-2147483648)
count = 0x7fffffff (2147483647)
count = 0x7ffffffe (2147483646)

The second is 4294967296, where a 32-bit signed int overflows again and turns back into a positive value.

dennis@ipa:~$ echo "AAAA" | ./n1_debug 4294967295 | head -5
unsigned int answer = 0xffffffff (4294967295)
signed int count = 0xffffffff (-1)
after bounds check, count = 0xffffffff (-1)
count = 0xfffffffe (-2)
count = 0xfffffffd (-3)

dennis@ipa:~$ echo "AAAA" | ./n1_debug 4294967296 | head -5
unsigned int answer = 0x0 (0)
signed int count = 0x0 (0)
after bounds check, count = 0x0 (0)

By supplying a number between these two boundaries, the boundary condition check will be bypassed.

The while loop will getchar() a character and write it to buf until count is zero. Since count has been overflowed it is a very large negative number and the loop will continue to write characters past buf and over important stack things such as main's saved eip.

(gdb) run 2147483648 < in
Starting program: /home/dennis/n1/n1_debug 2147483648 < in
unsigned int answer = 0x80000000 (2147483648)
signed int count = 0x80000000 (-2147483648)
after bounds check, count = 0x80000000 (-2147483648)
count = 0x7fffffff (2147483647)
count = 0x7ffffffe (2147483646)
count = 0x7ffffffd (2147483645)
...
(gdb) info frame
Stack level 0, frame at 0xbffffaf8:
eip = 0x80485c8 in main (n1_debug.c:29); saved eip 0x41414141
called by frame at 0x41414141
source language c.
Arglist at 0xbffffaf8, args: argv=1094795585, argc=0x41414141
Locals at 0xbffffaf8, Previous frame's sp is 0x0
Saved registers:
ebp at 0xbffffaf8, eip at 0xbffffafc
(gdb) x/32x buf
0xbffffaa0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffab0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffac0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffad0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffae0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffaf0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffb00: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffb10: 0x41414141 0x41414141 0x41414141 0x41414141

The problem I couldn't solve while trying to exploit this bug (on this system atleast) was that the numbers required are so large that the while loop will write past the stack and into inaccessible memory causing a segfault.

0xbfffffd0: 0xffffffff 0xffffffff 0xffffffff 0xffffffff
0xbfffffe0: 0xffffffff 0xffffffff 0xffffffff 0xffffffff
0xbffffff0: 0xffffffff 0xffffffff 0xffffffff 0xffffffff
0xc0000000: Cannot access memory at address 0xc0000000
(gdb) x/i $eip
0x80485c8
: mov %dl,(%eax)
(gdb) x/x $eax
0xc0000000: Cannot access memory at address 0xc0000000
...
count = 0x7ffffaa1 (2147482273)
count = 0x7ffffaa0 (2147482272)
count = 0x7ffffa9f (2147482271)

Program received signal SIGSEGV, Segmentation fault.
0x080485c8 in main (argv=1094795585, argc=0x41414141) at n1_debug.c:29
29 *pbuf++=getchar();