函数的参数是如何入栈出栈的
制作逆向靶 建议用debug版而不是release版,因为release版会自作聪明把很多东西省略掉
在调试器中 寄存器EIP 表示当前程序走到哪一步了
ctrl+G 光标定位到某一地址
在单步调试中 右侧哪个寄存器变红了 就是哪个寄存器发生了变化
call 的第一个作用就是 将EIP的值改为函数所在的地址
由硬编码知识 可以计算出返回地址的值,举例
E8 84 FF FF FF (call stack.00401005) 这行硬编码是5个字节 所以 call完的返回地址就是 当前地址+5
call 的第二个作用就是 把返回地址压栈
按F7单步步入(ollydbg环境)
进入函数后发生了什么?(进函数之前push 4 push 3)
push ebp 栈的本质是一段内存,将栈底的地址压入栈, 为划分新的栈区做备忘录 记住旧的栈底位置。 此时栈底和栈顶的位置并没有发生巨大改变(因为入栈了一次,栈顶上移了一格)
mov ebp esp 使栈底=栈顶 抛弃了旧的栈区
sub esp, 44 每一个内存格是4个字节 0~FF FF FF FF ,esp上移0x44 就是上移0x11个格,就是上移17个格子. 至此 新的栈底和栈顶形成了新的栈区
这个新的栈区 就叫做缓冲区
push ebx
push esi
push edi
lea edi,dword ptr SS:[ebp-44] 所谓ebp-44就是新栈顶初次的位置
mov ecx,11 计数0x11次即17个格子 准备填充缓冲区
mov eax,CC CC CC CC 填充物准备,不是硅胶,是一堆C (int3)
rep stos dword ptr es:[edi] (此时的D flag位是0 也就是向下填充)
回顾一下被调用函数
int my_add(int x,int y)
{
int z=2;
return x+y+z;
}
int main()
{
my_add(3,4);
return 0;
}
mov dword ptr ss:[EBP-4],2 填充区底部倒数第二个格子里 装上2 这个2就是局部变量int z=2
此时可得学习经验 一般情况,EBP - 的 都是局部变量, EBP + 的 都是参数
继续分析 EBP-4 是栈底倒数第二格 EBP是旧栈顶 EBP+4 是旧栈底 因此 大量逆向攻击都是攻击EBP+4
EBP+8 参数3 ,EBP+C 参数4
至此 被调用函数的参数和局部变量准备完毕 准备运行
mov eax, dword ptr SS:[EBP+8] =3 eax不清0吗??? 哦 mov 不用清0
add eax, dword ptr SS:[EBP+C] +4
add eax, dword ptr SS:[EBP-4] +2 函数运行完毕 清理现场,恢复旧现场(之前有 push ebx push esi push edi 接下来pop的顺序要反过来)
pop edi
pop esi
pop ebx
mov esp,ebp 把栈顶拉下来 此时新栈已无
pop ebp 把栈底放下去
RETN
那么新栈区中的那些CCCCCCCC呢 那些局部变量呢? 没人管了 所以函数返回会留下一堆垃圾。 下次局部变量如果不赋初始值 就会捡到未知垃圾
RETN时发生的两件事 1 将返回地址弹进EIP
--------------------------------------
返回后发生的事
add esp,8 将参数占用的栈空间返还回来 (这种栈平衡的方法叫 “外平衡”)
================================================================================================
攻击程序举例
#include "stdafx.h"
void attack()
{
while(1){printf("哇哈哈你中招了\n");} 没人调用这个函数啊,怎么会中招?
}
int main(int argc,char* argv[])
{
int arr[5] = {0};
arr[6] = (int)attack; arr[5]是5个局部变量 arr[4]是0,arr[5] 已经是非法访问了 是栈底
arr[6]是栈底下的一格 [EBP+4] 这里存储着函数的返回位置!
}
=====================================================================================
《代码还是数据?》
知识点 函数指针
typedef int (*pFunction)(int x, int y); pFunction是一个函数的指针,此函数有两个整数参数 一个整数返回值
什么时候我们会用函数指针呢? 当我们要利用别人写的程序,只有个dll, 没有头文件,没有lib文件
我们到dll中找到要用的函数的指针,就可以用这个指针执行一段代码
对于逆向工作者 没有什么数据区 代码区,我让你是什么,你就是什么
举例
#include "stdafx.h"
unsigned char loc[] = ( char的范围是-128~127 unsigned char范围是 0~255 )
{
ox55,ox88,oxEC,ox83,oxEC ... ... 这些硬编码是逆向别人写的程序, 用处是加法计算
};
typedef int (*pFun)(int x, int y);
int main(int argc,char* argv[])
{
pFun f =(pFun) &loc; 这是把人家的硬编码扒过来用啊!
printf( "%x",f(4,5) ); 这个程序虽然小简单 但却是蕴含了架壳的思想
getchar();
return 0;
}