Post Info: # Author: Flavio do Carmo Junior aka waKKu
# URL: Author’s Webpage
# Date: January 28, 2012
# Category: Assembly, Exploiting, Programming, Security
As I said in my last post…
I will be writing 3 quick and quite straightforward posts this week.
All (partially) inspired by this video: http://www.youtube.com/watch?v=i2fhNVQPb5I (I am a C Programmer) and regarding to stack overflows.
- 1) Signedness bug
- 2) Widthness overflow
- 3) Pointer Subterfuge
Nothing new I suppose, however, as usual I look forward to demystifying them completely. Therefore, any doubts in any of these bugs, please feel free to comment, elaborate my explation or even curse me by my mistakes.
All codes will be compiled using 32bits, just because I think it is easier to understand and be tested using VMs.
2) Widthness Overflow
Overview
- Widthness overflow bugs are exactly what the name says. Very similar to signedness bugs seen on my last post.
Imagine we have a field that must be small and controlable, skimp green as we are, we could declare a 1 byte variable (char) to save some space. The example is 1 byte long, although it could be any size of variable, as long as it is not the maximum size/width (otherwise would be impossible to overflow).
Code Example:
waKKu@0xcd80: Vulns$ cat width.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int vuln(char *buf) {
unsigned char len;
char buffer[256];
len = strlen(buf);
if ((len < 0 ) || (len > 200)) {
printf("String length too big!!!\n");
return(1);
} else {
printf("Copying content into stack...");
strcpy(buffer, buf);
printf("done.\n");
}
return(0);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s <text>\n", argv[0]);
return(1);
}
vuln(argv[1]);
return(0);
}
waKKu@0xcd80: Vulns$ gcc -m32 -o width width.c -Wall
waKKu@0xcd80: Vulns$ ./width
Usage: ./width <text>
waKKu@0xcd80: Vulns$ ./width hadouken
Copying content into stack...done.
waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x150')
Copying content into stack...done.
waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x200')
Copying content into stack...done.
waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x250')
String length too big!!!
waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x1250')
String length too big!!!
waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x33000')
String length too big!!!
Again, everything seems perfect.
Allow you some time looking at the source and trying to see where is the bug.
OK – let’s dive into the assembly…
waKKu@0xcd80: Vulns$ gdb -q ./width
Reading symbols from /data/research/0xcd80/posts/Vulns/width...(no debugging symbols found)...done.
(gdb) disass vuln
Dump of assembler code for function vuln:
0x08048424 <+0>: push %ebp
0x08048425 <+1>: mov %esp,%ebp
0x08048427 <+3>: push %edi
0x08048428 <+4>: sub $0x134,%esp
0x0804842e <+10>: mov 0x8(%ebp),%eax
0x08048431 <+13>: movl $0xffffffff,-0x11c(%ebp)
0x0804843b <+23>: mov %eax,%edx
0x0804843d <+25>: mov $0x0,%eax
0x08048442 <+30>: mov -0x11c(%ebp),%ecx
0x08048448 <+36>: mov %edx,%edi
0x0804844a <+38>: repnz scas %es:(%edi),%al
0x0804844c <+40>: mov %ecx,%eax
0x0804844e <+42>: not %eax
0x08048450 <+44>: sub $0x1,%eax
0x08048453 <+47>: mov %al,-0x9(%ebp)
0x08048456 <+50>: cmpb $0xc8,-0x9(%ebp)
0x0804845a <+54>: jbe 0x804846f <vuln+75>
0x0804845c <+56>: movl $0x80485c4,(%esp)
0x08048463 <+63>: call 0x8048354 <puts@plt>
0x08048468 <+68>: mov $0x1,%eax
0x0804846d <+73>: jmp 0x80484a2 <vuln+126>
0x0804846f <+75>: mov $0x80485dd,%eax
0x08048474 <+80>: mov %eax,(%esp)
0x08048477 <+83>: call 0x8048344 <printf@plt>
0x0804847c <+88>: mov 0x8(%ebp),%eax
0x0804847f <+91>: mov %eax,0x4(%esp)
0x08048483 <+95>: lea -0x109(%ebp),%eax
0x08048489 <+101>: mov %eax,(%esp)
0x0804848c <+104>: call 0x8048334 <strcpy@plt>
0x08048491 <+109>: movl $0x80485fb,(%esp)
0x08048498 <+116>: call 0x8048354 <puts@plt>
0x0804849d <+121>: mov $0x0,%eax
0x080484a2 <+126>: add $0x134,%esp
0x080484a8 <+132>: pop %edi
0x080484a9 <+133>: pop %ebp
0x080484aa <+134>: ret
End of assembler dump.
(gdb) # Again we have a different "cmpb" instruction. As you probably guessed, "b" means BYTE.
(gdb) # Set the breakpoint
(gdb) break *vuln+50
Breakpoint 1 at 0x8048456
(gdb) r $(perl -e 'print "A"x224')
Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x224')
Breakpoint 1, 0x08048456 in vuln ()
Missing separate debuginfos, use: debuginfo-install glibc-2.14.1-5.i686
(gdb) x/i $eip
=> 0x8048456 <vuln+50>: cmpb $0xc8,-0x9(%ebp)
(gdb) p /d 0xc8
$1 = 200
(gdb) x/x $ebp-0x9
0xffffd14f: 0x000000e0
(gdb) p /d 0xe0
$2 = 224
(gdb) # All similar to signedness bug post here. 224 greater than 200 and the algorithm works correct.
(gdb) # I will finish this execution and start a new one, with a different value. Pay attention on that $ebp-0x9 value.
(gdb) continue
Continuing.
String length too big!!!
[Inferior 1 (process 5513) exited normally]
(gdb) # As expected, string too length. Let's try with a different value
(gdb) # The maximum unsigned value possible to be represented by 1 byte is 255d = 0xffh = 1111 1111b
(gdb) r $(perl -e 'print "A"x256')
Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x256')
Breakpoint 1, 0x08048456 in vuln ()
(gdb) x/i $eip
=> 0x8048456 <vuln+50>: cmpb $0xc8,-0x9(%ebp)
(gdb) p /d 0xc8
$3 = 200
(gdb) x/x $ebp-0x9
0xffffd12f: 0x00000000
(gdb) # o.0 - We sent 256 (255 + 1) and we got zero!
(gdb) # This is due to the fact that:
(gdb) p /x 0xff + 0x1
$3 = 0x100
(gdb) # As you see, if we get only the first right-byte of 0x100, we will get only 0x00.
(gdb) # In the same vein:
(gdb) p /x 0xff + 0x2
$4 = 0x101
(gdb) # So, if we send 257 chars, we must get our char variable (len) equal 1. Let's try.
(gdb) r $(perl -e 'print "A"x257')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x257')
Breakpoint 1, 0x08048456 in vuln ()
(gdb) x/i $eip
=> 0x8048456 <vuln+50>: cmpb $0xc8,-0x9(%ebp)
(gdb) p /d 0xc8
$5 = 200
(gdb) x/x $ebp-0x9
0xffffd12f: 0x00000001
(gdb) # Perfect! ... We have successfully overflowed our 1 byte variable.
(gdb) # If you didn't understand how it will be useful to our exploit, let's see our string in memory.
(gdb) # If you read about calling convention and you saw that vuln() has only one argument, you may know that:
(gdb) # - We use positive offsets from EBP to access function parameters
(gdb) # - We use negative offsets from EBP to access function local variables
(gdb) # As we know better, the first value after EBP is our *precious* Return Address, the following must be our string pointer *buf.
(gdb) x/wx $ebp+4
0xffffd13c: 0x080484e7
(gdb) # Our precious...
(gdb) x/wx $ebp+8
0xffffd140: 0xffffd3ce
(gdb) # *buf
(gdb) x/s 0xffffd3ce
0xffffd3ce: 'A' <repeats 200 times>...
(gdb)
0xffffd496: 'A' <repeats 57 times>
(gdb)
0xffffd4d0: "XDG_SESSION_ID=1"
(gdb) # Environment variables. As we could check, argv[1] has really 257 bytes, and not only 1.
(gdb) # Know we must rely on a non-boundary check function to overflow the reserved buffer and give us control over EIP
(gdb) # That is exactly what strcpy() will do. It will read bytes from *buf (argv[1]) and copy it to our limited buffer
(gdb) # until it reaches a NULL byte.
(gdb) # In order to successfully overwrite the EIP, we need to send some extra bytes to overwrite other local variables
(gdb) # some alignments and EBP, until we reach EIP. Let's try 280.
(gdb) del breakpoints
Delete all breakpoints? (y or n) y
(gdb) r $(perl -e 'print "A"x280')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x280')
Copying content into stack...done.
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) # Success again. EIP properly overwritten.
(gdb) quit
A debugging session is active.
Inferior 1 [process 5816] will be killed.
Quit anyway? (y or n) y
As promised, EIP overwritten.
Local exploitation of non-protected stack overflows are so trivial that I have no intention to cover it anyhow.
Our window of exploitation here was quite big, 200 bytes from 255. However, it is possible that you face some very small window that a fuzzer could not detect.
Imagine if the code was something like:
if ((len < 0 ) || (len > 5))
Here we would need a perfect widthness overflow of 4 bytes (256 to 259) to bypass, as anything bigger or smaller would be blocked.
Conclusion
- Perhaps is better to use mainstream code ideas if you don’t really understand how the architecture/C/asm works. Additionally, always try to compile/keep your programs protected. A crash/DoS is a way better than a Command Execution.
Update:
Also, try to get used to compile your programs using -Wall -Wextra:
waKKu@0xcd80: Vulns$ gcc -m32 -o width width.c -Wall -Wextra width.c: In function ‘vuln’: width.c:10:5: warning: comparison is always false due to limited range of data type [-Wtype-limits]
This message would help you identify the problem.
As usual, any thoughts are welcome.
–waKKu

