我阅读了一些关于Stack Buffer Overflow的文章,比如this一篇,并了解了攻击者如何通过覆盖函数指针来利用Stack Buffer-Overflow错误。然后我写了一个小程序来演示攻击:
#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中,buffer =abcdefghijklmnopqrstuvR
HELLO fun2!
HELLO fun2!
我们可以看到fun2()被成功调用,但我不知道为什么它会运行两次。然后我GDB了它(见下文)。 (我只知道一些关于GDB的基本指令(¯▽¯)╭)
我用Google搜索了一些关键词,例如“__libc_csu_fini()”,但没有找到一种明确的方法可以帮助我理解程序的执行路径。我对编译器和进程的内部结构知之甚少,所以我认为我可能必须找到一些详细描述这些内容的书籍或文章。有什么建议吗?谢谢!
GDB记录:
(gdb)列表
7 printf(“在fun1中,缓冲区=%s \ n”,缓冲区);
8}
9
10 void fun2(void){
11 printf(“HELLO 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返回0;
19}
(gdb)中断16
断点1位于0x804846f:文件hello.c,第16行。
(gdb)运行
启动程序:/ home / yuliang / test / hello
hello.c处的断点1,main(argc = 1,argv = 0xbffff394):16
16 printf(“fun2的地址:%p \ n”,fun2);
缺少单独的debuginfos,请使用:debuginfo-install glibc-2.13-2.i686
(gdb)步骤
fun2的地址:0x8048452
17 fun1(“abcdefghijklmnopqrstuv \ x52 \ x84 \ x04 \ x08”);
(gdb)
fun1(输入= 0x804859a“abcdefghijklmnopqrstuvR \ 204 \ 004 \ b”)在hello.c:6
6 strcpy(缓冲区,输入);
(gdb)
7 printf(“在fun1中,缓冲区=%s \ n”,缓冲区);
(gdb)
在fun1中,buffer =abcdefghijklmnopqrstuvR
8}
(gdb)
hello.c上的fun2():10
10 void fun2(void){
(gdb)
11 printf(“HELLO fun2!\ n”);
(gdb)
HELLO fun2!
12}
(gdb)
__libc_csu_fini()中的0x08048500
(gdb)
单步执行直到退出函数__libc_csu_fini,
没有行号信息。
hello.c上的fun2():10
10 void fun2(void){
(gdb)
11 printf(“HELLO fun2!\ n”);
(gdb)
HELLO fun2!
12}
(gdb)
无法访问地址0x76757477
的内存(gdb)
单步执行直到退出函数__libc_csu_init,
没有行号信息。
来自/lib/libc.so.6的__libc_start_main()中的0x009aae36
(gdb)
单步执行直到退出函数__libc_start_main,
没有行号信息。
程序退出,代码为0241。
(gdb)
答案 0 :(得分:0)
在strcpy()
之前不久在gdb中运行程序并查看堆栈框架(存储保存文件的位置)。然后运行到printf()
后不久,再次查看存储的eip(命令为info frame
)。
因为你将函数fun2()
的地址传递给fun1()
,它将覆盖保存的eip,一旦调用return(在你的情况下隐含),就会执行下一条指令(给出通过eip,在您的情况下,它是fun2()
)
不要忘记阅读aleph1的Smashing the stack for fun and profit