学习目标:
- 1.函数的本质
- 2.学会栈溢出的使用
学习内容:
一.认识两个寄存器esp,ebp
1.esp指向栈顶地址
2.ebp指向栈底地址
3.eip存储下一条执行指令的地址
二.认识相关指令
1.push 寄存器或数字 先esp-=4再让寄存器或数字赋值给esp指向地址的值,就是压入的意思
2.pop 寄存器 将esp指向地址的值赋给寄存器,再esp+=4 就是弹出的意思
3.call 可以看作是先esp-=4 再给栈顶地址赋值为函数的下一条指令最后在跳转到另一个指令去执行函数
4.ret 函数的结束 看作是pop eip即将之前call存的指令值给eip
三。认识函数的调用过程
//要把优化关掉,不然就找不到add函数
#include <iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int main()
{
int c=add(1, 2);
cout << c;
return 0;
}
这个函数比较简单,也就给我老师拿来入门
先看看调用前是咋样的吧
int c=add(1, 2);
00281014 6A 02 push 2
00281016 6A 01 push 1
00281018 E8 E3 FF FF FF call 00281000
可以看到他将我们的参数从右向左依次压入了栈中
此时栈中
0028101D下一条命令地址 |
1 |
2 |
int add(int a, int b)
{
00281000 55 push ebp
00281001 8B EC mov ebp,esp
return a + b;
00281003 8B 45 08 mov eax,dword ptr [ebp+8]
00281006 03 45 0C add eax,dword ptr [ebp+0Ch]
}
压入ebp(方便后面复原栈),让ebp=esp,再给eax赋值成第一个参数,再加上第二个参数
因为栈是越往下地址越大所以很容易想到
原先ebp的值(现ebp,esp指向) |
0028101D下一条命令地址 |
1 |
2 |
00281009 5D pop ebp
0028100A C3 ret
此时ebp复原了没变过
0028101D下一条命令地址 |
1 |
2 |
0028101D 83 C4 08 add esp,8
00281020 89 45 FC mov dword ptr [ebp-4],eax
函数出来后可以看到esp下降了2格,因为ret原因又降了一格,最后esp也复原了
总结:
调用函数前后esp,ebp都会保持平衡,这样子省了空间,达到要用就拿过了,不用就给别人用的效果(个人感觉)。
学习项目:
目标:用栈溢出去攻击驴百万的项目,要把那个安全检查关了
直接上带代码
#include <iostream>
#include<windows.h>
using namespace std;
void Hack()
{
while (1)
{
cout << "hello"<<"你的电脑已被我入侵了"<<endl;
Sleep(1000);
}
return;
}
int GetAge()
{
int rt;
std::cout << "请输入学员信息" << endl;
std::cin >> rt;
return rt;
}
int count()
{
int total= 0;
int i = 0;
int age[10];
//age[10]不明 age[11]是sum,age[12]是i,age[13]是ebp,age[14]是指令
//0到10正常输入 然后分别输入sum,i,sum随便,i不要动
//然后是ebp随便,最后把api输入,再输入0
do {
age[i] = GetAge();
total+= age[i];
} while (age[i++] != 0);
return total;
}
int main()
{
cout << "=======驴百万学院 学员年龄统计系统 ======\n";
cout << "\n API:" << (int)Hack << endl;
cout << "\n{说明:最多输入10个学员信息,当输入0时代表输入结束" << endl;
cout << "\n驴百万学院的总年龄为:" << count();
return 0;
}
可以看到只是输入函数地址就能执行,老师有个视频更高级,没输入都能调
原理:每个函数会在栈底存储下一条执行指令的值,我们只需串改这个值成我们想要执行的函数地址,例如下载病毒之类的。里面的数组在栈里是从上到下分布,理论上索引足够大就能访问到这个值
主要分析count函数
00711090 55 push ebp
先看下这个esp,ebp位置,ebp是栈底,ebp下面是call的指令
00711091 8B EC mov ebp,esp
让esp指向ebp就是栈顶与栈底一致
00711093 83 EC 34 sub esp,34h
栈顶变高了 3*16+4=52个字节就是13个int
int total= 0;
00711096 C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0
int i = 0;
0071109D C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
然后声明两个局部变量 地址分别是 total:ebp-8 i:ebp-4
栈空间的最下面八个字节给了这两个
do {
age[i] = GetAge();
007110A4 E8 A7 FF FF FF call 00711050 //GetAge函数
007110A9 8B 4D FC mov ecx,dword ptr [ebp-4]
007110AC 89 44 8D CC mov dword ptr [ebp+ecx*4-34h],eax
total+= age[i];
007110B0 8B 55 FC mov edx,dword ptr [ebp-4]
007110B3 8B 45 F8 mov eax,dword ptr [ebp-8]
007110B6 03 44 95 CC add eax,dword ptr [ebp+edx*4-34h]
007110BA 89 45 F8 mov dword ptr [ebp-8],eax
} while (age[i++] != 0);
GetAge函数
std::cin >> rt;
00711074 8D 4D FC lea ecx,[ebp-4]
00711077 51 push ecx
00711078 8B 0D 70 30 71 00 mov ecx,dword ptr ds:[00713070h]
0071107E FF 15 40 30 71 00 call dword ptr ds:[00713040h]
return rt;
00711084 8B 45 FC mov eax,dword ptr [ebp-4]
}
看到返回值是eax返回的ebp-4就是rt
再看这里是个循环赋值 给ecx i的值 i最初=0
007110A9 8B 4D FC mov ecx,dword ptr [ebp-4]
007110AC 89 44 8D CC mov dword ptr [ebp+ecx*4-34h],eax
所以判断数组 age[0]是ecx=0时即ebp-34h
age[9]是ebp+36-34h=ebp-10h
所以age分布在ebp-34h到ebp-10h中
age[10]=ebp-0ch//从头到尾都没这个 所以数组跟total就隔了一个int
所以age[11]是total age[12]是i age[13]是ebp age[14]是指令地址,改成我们要的函数地址就行