0x1 什么是栈作者:信息安全专家李泉(liquan165)
爱好终端安全的朋友们大家好。最近小编在工作中遇到了许多基于堆栈的缓冲区溢出的例子,而一直没有一个时间记录下来分享给大家,所以我决定写一个简单的BLOG让我们讨论一下这个老生常谈的话题。在开始栈溢出原理分析之前,我们先看一些基础知识。
堆栈是一种具有一定规则的数据结构,我们可以按照一定的规则进行添加和删除数据。它使用的是后进先出的原则。在x86等汇编集合中堆栈与弹栈的操作指令分别为:
PUSH:将目标内存推入栈顶。
POP:从栈顶中移除目标。
下面我们展示的是一段普通C程序的内存布局,包括堆栈,他们常被用于函数的调用与返回。
一个普通的C程序的内存分布
TEXT:包含了要执行的程序代码。
Data:包含程序需要的全局数据、资源等。
Stack:包含函数的输入参数,返回地址以及保存函数的局部变量等。Stack是后进先出的结构。随着函数的调用,它在内存中(从高地址到低地址)向下寻址。
Heap:保存所有动态分配的内存。每当我们用malloc分配获取内存指针时,这个地址就是从堆中分配的。
在基于栈的缓冲区溢出的情况下,我们要重点关注EBP、EIP和ESP这三个寄存器。函数中,EBP指向栈底的高地址,ESP寄存器指向栈顶的低地址。
ESP、EBP在栈中的位置
当执行一个函数的时候,相关的参数以及局部变量等等都会被记录在ESP、EBP中间的区域。一旦函数执行完毕,相关的栈帧就会从堆栈中弹出,然后从预先保存好的上下文中进行恢复,以便保持堆栈平衡。CPU必须要知道函数调用完了之后要去哪里执行(EIP指向),这往往从堆栈弹出的过程中进行EIP赋值的。
为了便于理解,我们假设有一个main函数调用func()函数的程序。程序运行之后,调用func函数,将所需要的参数,全部堆入栈中,然后是返回值入栈此时的栈内分布如下:
函数调用的堆栈分布
在func执行完成之后,相应的栈帧被弹出,此时存储返回值的地址被加载到了EIP寄存器中以继续执行main中剩余的部分。而我们的目的就是控制这个返回值,这样我们就能劫持func返回到指定的恶意代码中去。
0x2 堆栈溢出请看如下C代码:
#include <string.h>
#include <stdio.h>
void function2() {
printf(“Execution flow changed\n”);
}
void function1(char *str){
char buffer[5];
strcpy(buffer, str);
}
void main(int argc, char *argv[])
{
function1(argv[1]);
printf(“Executed normally\n”);
}
1)Main函数调用function1并打印“Executed normally”消息。
2)function1创建了长度为5的一块缓冲区,并且复制Main函数传递过来的字符串
3)function2打印了“Execution flow changed”,程序中的其它位置没有调用过function2函数。
我们的目标是通过以上程序,传入一个特定的参数,来控制整个程序流执行function2函数。下面我们开始对这段代码进行编译。
gcc -g -fno-stack-protector -z execstack -o bufferoverflow overflow.c
-g:告诉GCC编译器将调试信息以及符号等编译到程序中。
-fno-stack-protector:关闭堆栈保护机制。
-z execstack:打开堆栈可执行机制。(关闭堆栈执行保护)
下面我们运行程序。如图
以AAAA作为参数运行程序
下一步使用AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA作为参数来破坏这个程序,这个程序会因为堆栈被破坏而崩溃。
程序崩溃
我们打开GDB来分析一下程序崩溃的原因。使用GDB打开崩溃的二进制文件。使用list选项来显示源代码,然后在函数中调用,在strcpy和函数返回的地方添加断点。
GDB调试
在反复测试以及运行,不断的变换参数内容,并试图找到存储崩溃地址AAAA(0x41414141)的位置。函数调用时,EBP与ESP的内容分别是:
EBP、ESP
strcpy执行过后,EBP与ESP的内容是:
函数退出时EBP与ESP为:
结合之前我们看过的堆栈分布图,返回地址应该是在EBP下方。在计算出EBP以及返回地址后,我们可以尝试劫持程序执行的位置。
下面我们寻找function2的地址来劫持执行。要获取function2的起始地址,请使用以下命令。
disass function2
获取function2的起始地址
我们要知道,本例我们使用的是基于INTEL架构的VM虚拟机,使用的是小字节序,所在我们生成我们需要的载荷(PAYLOAD)之前,将所有字节反转过来。现在使用PYTHON来生成我们的有效载荷,并使用生成的字符串作为程序参数的内容。
run $(python -c 'print "A"*17 "\x1b\x84\x04\x08"')
运行,function2函数被执行,执行流被更改。
运行结果
至此,我们成功的利用了堆栈缓冲区溢出。
作者|李泉(liquan165) 梆梆安全研究院高级安全研究员
主要研究领域|车联网、工控、物联网安全、终端安全等
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved