栈缓冲区溢出导致异常执行路径

3

我读了一些关于堆栈缓冲区溢出的文章,比如这篇,并学习了攻击者如何通过覆盖函数指针来利用堆栈缓冲区溢出漏洞。然后我编写了一个小程序来演示一个攻击:

#include <stdio.h>
#include <string.h>

void fun1 ( char * input ) {
    char buffer[10];
    strcpy( buffer, input );
    printf( "In fun1, buffer= %s\n", buffer );
}

void fun2 ( void ) {
    printf ( "HELLO fun2!\n" );
}

int main ( int argc, char * argv[] )
{
    printf ( "Address of fun2: %p\n", fun2 );
    fun1( "abcdefghijklmnopqrstuv\x52\x84\x04\x08" );
    return 0;
}

该程序是在Fedora 14 x86下使用GCC 4.5.1编译的。以下是输出结果: $ ./exp01 fun2的地址:0x8048452 在fun1中,缓冲区为abcdefghijklmnopqrstuvR� HELLO fun2! HELLO fun2!
我们可以看到fun2()被成功调用了,但我不知道为什么它会运行两次。然后我使用GDB(见下文)进行了调试。(我只知道一些关于GDB的基本指令 ╮( ̄▽ ̄)╭)
我搜索了一些关键词,如“__libc_csu_fini()”,但没有找到一个清晰的方法来帮助我理解程序的执行路径。我对编译器和进程内部结构了解太少了,所以我认为我可能需要找一些详细描述这些东西的书籍或文章。有什么建议吗?谢谢!
GDB记录如下:
(gdb) list 7 printf("在fun1中,buffer= %s\n", buffer); 8 } 9 10 void fun2(void) { 11 printf("你好,fun2!\n"); 12 } 13 14 int main(int argc, char* argv[]) 15 { 16 printf("fun2的地址:%p\n", fun2); (gdb) 17 fun1("abcdefghijklmnopqrstuv\x52\x84\x04\x08"); 18 return 0; 19 } (gdb) break 16 断点1已在文件hello.c的第16行设置。 (gdb) run 正在启动程序:/home/yuliang/test/hello Breakpoint 1, main (argc=1, argv=0xbffff394) at hello.c:16 16 printf("fun2的地址:%p\n", fun2); 缺少单独的调试信息,使用:debuginfo-install glibc-2.13-2.i686 (gdb) step fun2的地址:0x8048452 17 fun1("abcdefghijklmnopqrstuv\x52\x84\x04\x08"); (gdb) fun1(input=0x804859a "abcdefghijklmnopqrstuvR\204\004\b") at hello.c:6 6 strcpy(buffer, input); (gdb) 7 printf("在fun1中,buffer= %s\n", buffer); (gdb) 在fun1中,buffer= abcdefghijklmnopqrstuvR� 8 } (gdb) fun2() at hello.c:10 10 void fun2(void) { (gdb) 11 printf("你好,fun2!\n"); (gdb) 你好,fun2! 12 } (gdb) 0x08048500 in __libc_csu_fini() (gdb) 一直单步执行,直到从没有行号信息的函数__libc_csu_fini返回。 fun2() at hello.c:10 10 void fun2(void) { (gdb) 11 printf("你好,fun2!\n"); (gdb) 你好,fun2! 12 } (gdb) 无法访问地址0x76757477的内存。 (gdb) 一直单步执行,直到从没有行号信息的函数__libc_csu_init返回。 0x009aae36 in __libc_start_main() from /lib/libc.so.6 (gdb) 一直单步执行,直到从没有行号信息的函数__libc_start_main返回。 程序以0241代码退出。 (gdb)

当您在fun1中没有printf的情况下运行程序,程序是否会执行两次? - Azrael3000
2
你需要使用机器码调试器来逐步执行这些内容——寻找gdb nexti、stepi和disas命令。C模式下的调试器会变得非常混乱,因为它依赖于堆栈帧的有效性来知道正在执行哪些操作,而当然它们不再有效,因为你刚刚改变了它们。 - David Given
感谢您的回复@Azrael3000。我在fun2中注释掉了printf。它回到了main()中的printf,并进入了一个死循环。但是,fun2()的地址现在是0x804843e。如果我调用fun1(“abcdefghijklmnopqrstuv\x3e\x84\x04\x08”)而不是fun1(“abcdefghijklmnopqrstuv\x52\x84\x04\x08”),fun2仍然会运行两次。 - Yuliang
非常有用的信息。我真的需要更多地了解gdb。谢谢@DavidGiven。 - Yuliang
我同意David的观点。调试器将依赖于具有有效堆栈指针并从那里展开堆栈的操作。 - Gargi Srinivas
1个回答

2

在 gdb 中运行程序时,在 strcpy() 前暂停,查看栈帧(这是保存 eip 的位置)。然后运行直到 printf() 后暂停,再次查看已存储的 eip(命令为 info frame)。

由于您将函数 fun2() 的地址传递给了 fun1(),它将覆盖保存的 eip,并且一旦调用 return(在您的情况下隐含),下一个指令将被执行(由 eip 给出,而在您的情况下它是 fun2() 的地址)

不要忘记阅读 aleph1 的 Smashing the stack for fun and profit


感谢 @dwalter!我知道 fun2() 会在 fun1() 返回后运行,但我不知道为什么它在我的机器上运行了两次。似乎这不是每台机器和每个操作系统都能复现的问题,至少在我的一个朋友的 Gentoo 上不行。我稍后会尝试你的方法来获取堆栈的更多信息。再次感谢你的推荐! - Yuliang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接