基础知识
pe文件里有区段,这里主要用到两种:代码段、数据段
程序在内存中分为两种:栈和堆(一个或多个,堆是可增长的)
常用函数调用约定:cdecl
、stdcall
、thiscall
、fastcall
栈溢出--变量淹没
变量淹没这种情况现实中很少出现,栈溢出一般以控制ebp和返回值为主
简单示例:(VS2022)
#include <iostream>
int main(int argc,int** argv)
{
int a;
char b[4];
scanf("%d",&a);
scanf("%s",b);
std::cout <<"output:" << a;
}
在release编译选项下,越先定义的变量会位于栈中越靠近ebp的位置(定义在下面的变量会淹没上面的变量):此例中a=1,b=6666,因为b数组的溢出导致a变量被淹没成了0:
代码运行结果:
栈溢出--控制返回地址&手工植入代码
示例代码:(WindowsXP+VC6.0)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WINDOWS.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
void main()
{
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
跟变量淹没一样的原理,不过输入更大了,把返回地址也给覆盖了,当程序返回的时候,会跳入我们构造的地址
- 因为WindowsXP还没有ASLR机制,所以可以在输入处硬编码地址
- 因为可以在栈中保存更多的数据,所以可以在栈中构造shellcode
因此,可以在返回地址处填写地址跳转到栈中的shellcode处:
正常情况:
覆盖后:
栈中该地址的内容:
这里之前我有个疑惑:为啥栈中除了造成溢出的函数那里之外,再往下还有一份shellcode
实际上那是进入溢出函数之前的函数将其作为参数存在栈中的,如果溢出函数后续代码修改到了shellcode,就可以跳转到之前栈里的shellcode进行执行
这种构造方式存在问题:硬编码获取user32.MessageBoxA函数,该函数地址在不同系统上是不一样的,而且得加载了user32.dll才会有该函数
快速确认缓冲区溢出位置,可以使用 msf 中的 pattern_create.rb(kali linux :/usr/share/metasploit-framework/tools/exploit/pattern_create.rb
)脚本来生成随机字符串