进程使用的内存可以大致分为4类:
动态分配和回收
)内存的栈实际上指的是系统栈。
与栈相关的概念:
示例代码:
#include
int func_B(int arg_B1, int arg_B2)
{
int var_B1, var_B2;
var_B1=arg_B1+arg_B2;
var_B2=arg_B1-arg_B2;
return var_B1*var_B2;
}
int func_A(int arg_A1, int arg_A2)
{
int var_A;
var_A=func_B(arg_A1, arg_A2)+arg_A1;
return var_A;
}
int main(int argc, char **argv, char **envp)
{
int var_main;
var_main=func_A(4,3);
return var_main;
}
每个函数独占自己的栈帧空间,正在运行的函数的栈帧永远在栈顶。
寄存器:
函数栈帧中包含的主要信息
函数调用约定:描述了函数传递参数方式和栈协同工作的细节
针对VC++,有三种函数调用约定(默认为_stdcall):
函数调用的大致步骤:
push 参数3;
push 参数2;
push 参数1;
call 函数地址;
push ebp;
mov ebp, esp;
sub esp, XXX;
函数返回的大致步骤:
add esp, xxx;
pop ebp;
retn;
示例代码:
#include
#include
#define PASSWORD "123456"
int verify_password(char *password)
{
int authenticated;
char buffer[8]; // add local buffto be overflowed
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password); //over flowed here
return authenticated;
}
void main()
{
int valid_flag=0;
char password[1024];
while(1)
{
printf("please input password: ");
scanf("%s",password);
valid_flag=verify_password(password);
if (valid_flag)
{
printf("incorrect password\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
break;
}
}
}
代码执行到verify_password函数时的栈帧状态:
可以看到authenticated位于buffer变量下方,占4个字节,如果buffer越界,则buffer[8]、buffer[9]、buffer[10]、buffer[11]会写入其中。
需要相应的环境进行编译,可下载书籍提供的编译好的EXE文件。
使用OD加载程序,输入“qqqqqqq
”,运行到strcpy:
我们尝试输入"qqqqqqqrst
":
我们尝试输入“qqqqqqqq
”:
需要注意:strcmp函数第一个字符串大于第二个返回1,小于返回-1,即0xFFFFFFFF,溢出后为0xFFFFFF00,无法成功。
改写邻接变量对代码环境的要求相对苛刻,一般是瞄准栈帧最下方的EBP和函数返回地址等栈帧状态值。
再来看下上一个程序的相关信息:
如果buffer[8]的输入还要长就会逐渐覆盖前栈帧EBP和返回地址。以输入“·4321432143214321432
”为例:
我们可以发现溢出成功。
由于键盘输入的ASCII码有限(0x11、0x12等无法输入),对源代码进行一定的修改,让程序从文件中读取字符串。
#include
#include
#include
#define PASSWORD "123456"
int verify_password(char *password)
{
int authenticated;
char buffer[8]; // add local buffto be overflowed
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password); //over flowed here
return authenticated;
}
void main()
{
int valid_flag=0;
char password[1024];
FILE * fp;
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password); //从文件读取密码
valid_flag=verify_password(password);
if (valid_flag)
{
printf("incorrect password\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
我们的目的是尝试在输入错误密码的情况下修改返回地址,直接跳转到密码正确的地方继续执行:
构建password.txt的文件:
运行后:
由于栈内EBP也被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。
我们在buffer里包含自己要执行的代码,也可以通过修改返回地址跳转执行。
先对前面的代码进行修改:
#include
#include
#include
#include //为顺利调用LoadLibrary函数装载user32.dll
#define PASSWORD "123456"
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"); //prepare for message
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password); //从文件读取密码
valid_flag=verify_password(password);
if (valid_flag)
{
printf("incorrect password\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
目标是:调用Windows的API函数MessageBoxA,并显示“failwest”字样。
创建password.txt文件获取相关信息,在文件中写入11
组“4321
”,可以得到以下信息:
汇编语言调用MessageboxA的步骤:
创建password.txt文件:
将上面的代码写入文件,53-56字节写入buffer起始地址0x0019FAB0,其余用0x90(nop指令)填充。
《0day安全:软件漏洞分析技术》