栈溢出漏洞利用原理
栈溢出漏洞利用原理
2013-12-26 21:21 3686
下面的代码引自《shellcoder's handbook》英文版,中文版中没有这一节,可能是漏译,也可能是有意为之。下面的过程是经过本人调试并测试通过的,请不要套用,因为不同的平台,其结果可能也是不同的,至少我调试得到的字节数跟书中作者调试得到的字节数是有出入的,但是思路是相同的。
用例代码
overflow.c
编译:gcc -g overflow.c -o overflow
首先使用GDB调试overflow,在主函数处设置断点,如图1
图1
运行程序至断点处,反汇编EIP所指地址处的代码,如图2
图2
从上图,可以看到,do_valid_stuff的函数调用地址为0x08048629,记下这个地址,稍后我们会用到这个地址,改变程序的执行流程。同时,记下call指令的下一条指令的地址,也就是0x08048625,这个值将会在后面遇到。此时,查看一下eax,ebx,ecx,edx,ebp,esp寄存器的内容,如图3
图3
从上图中我们可以看到此时寄存器esp的值是0xbffff3d0,因此在调用validate_serial函数之前,我们看看栈中的内容,如图4
图4
从图中可以看到在调用函数之前,寄存器esp所指的值为0x08048650.
现在来看看validate_serial函数的反汇编结果,如图5
图5
当我们进入函数时,反汇编一下,看看我们的程序运行到何处,如图6
图6
从上图中我们可以看出,目前已经进入到validate_serial函数内,但是该函数的第一条指令还没有执行。此时,我们来看看栈中的情况,如图7
图7
注意看0xbffff3cc地址处的值,其值是0x08048625,这个值是不是有点儿眼熟,看看我们的图1,它是不是call指令的下一个指令的地址,我们要覆盖的值就是它。在图中我们还看到了地址为0xbffff3d0的值是0x08048650,这个值是不是也很眼熟。从这条信息中,我们可以得到一个信息,在进行函数调用时,call指令会将该指令的下一条指令的地址放入到栈中,之后给函数分配栈大小。
当我们多执行几条指令时,如图8
图8
此时,从代码中我们可以看到,系统给该函数分配了0x38个字节,分析一下代码你会发现 -0x20(%ebp)的地址其实就是我们的fscanf将获取到的字符串存储到栈中的地址,我们要覆盖的就是从该处到返回地址之间(包括返回地址)的字节数,也就是0x20+0x4+0x4,第一个4是push %ebp指令占用的4字节,第二个4就是我们要改写的地址字节数,其值我们改写为do_validate_stuff的地址,也就是0x08048629.因此,因此我们构造的字符串如下:
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDD\x29\x86\x04\x08
当我们执行如下命令时,它就会跳到我们指定的调用地址处,这样我们就可以如愿以偿地改变了程序的执行流程.
printf “AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDD\x29\x86\x04\x08”|./overflow
如图所示:
图9
当我们输入13个H时,其结果也是如此,如图所示:
图10
有不对之处,还请不吝赐教!
用例代码
overflow.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#include <stdlib.h> #include <stdio.h> #include <string.h> int valid_serial(char *psz) { size_t len=strlen(psz); unsigned int total=0; size_t i; if (len<10) return 0; for (i=0;i<len;i++) { if ((psz[i]< '0' )||(psz[i]> 'z' )) return 0; total+=psz[i]; } if (total%853==83) return 1; return 0; } int validate_serial() { char serial[24]; fscanf(stdin, "%s" ,serial); if (valid_serial(serial)) return 1; else return 0; } int do_invalid_stuff() { printf ( "Invalid serial number!\nExiting\n" ); exit (1); } int do_valid_stuff() { printf ( "The serial number is valid!\n" ); exit (0); } int main(int argc,char *argv[]) { if (validate_serial()) do_valid_stuff(); else do_invalid_stuff(); return 0; } |
编译:gcc -g overflow.c -o overflow
首先使用GDB调试overflow,在主函数处设置断点,如图1
图1
运行程序至断点处,反汇编EIP所指地址处的代码,如图2
图2
从上图,可以看到,do_valid_stuff的函数调用地址为0x08048629,记下这个地址,稍后我们会用到这个地址,改变程序的执行流程。同时,记下call指令的下一条指令的地址,也就是0x08048625,这个值将会在后面遇到。此时,查看一下eax,ebx,ecx,edx,ebp,esp寄存器的内容,如图3
图3
从上图中我们可以看到此时寄存器esp的值是0xbffff3d0,因此在调用validate_serial函数之前,我们看看栈中的内容,如图4
图4
从图中可以看到在调用函数之前,寄存器esp所指的值为0x08048650.
现在来看看validate_serial函数的反汇编结果,如图5
图5
当我们进入函数时,反汇编一下,看看我们的程序运行到何处,如图6
图6
从上图中我们可以看出,目前已经进入到validate_serial函数内,但是该函数的第一条指令还没有执行。此时,我们来看看栈中的情况,如图7
图7
注意看0xbffff3cc地址处的值,其值是0x08048625,这个值是不是有点儿眼熟,看看我们的图1,它是不是call指令的下一个指令的地址,我们要覆盖的值就是它。在图中我们还看到了地址为0xbffff3d0的值是0x08048650,这个值是不是也很眼熟。从这条信息中,我们可以得到一个信息,在进行函数调用时,call指令会将该指令的下一条指令的地址放入到栈中,之后给函数分配栈大小。
当我们多执行几条指令时,如图8
图8
此时,从代码中我们可以看到,系统给该函数分配了0x38个字节,分析一下代码你会发现 -0x20(%ebp)的地址其实就是我们的fscanf将获取到的字符串存储到栈中的地址,我们要覆盖的就是从该处到返回地址之间(包括返回地址)的字节数,也就是0x20+0x4+0x4,第一个4是push %ebp指令占用的4字节,第二个4就是我们要改写的地址字节数,其值我们改写为do_validate_stuff的地址,也就是0x08048629.因此,因此我们构造的字符串如下:
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDD\x29\x86\x04\x08
当我们执行如下命令时,它就会跳到我们指定的调用地址处,这样我们就可以如愿以偿地改变了程序的执行流程.
printf “AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDD\x29\x86\x04\x08”|./overflow
如图所示:
图9
当我们输入13个H时,其结果也是如此,如图所示:
图10
有不对之处,还请不吝赐教!