逆向工程核心原理

First Post:

Last Update:

第一章

打补丁

对于修改书中的那个程度有两种办法

1.直接修改字符串缓冲区

2.其他位置生成新字符串并将该地址传递给消息函数

栈帧

使用EBP寄存器访问栈内局部变量、参数、函数返回地址等的手段。ESP寄存器承担着栈顶指针的作用,而EBP寄存器则负责行使栈帧指针的作用。程序运行时ESP的值随时改变,因此在调用函数时,先把基准点(函数起始位置)的ESP值保存到EBP,并维持在函数内部。这样无论ESP值如何变化,以EBP值为基准能够安全访问到相关函数的局部变量、参数、返回值等。其中EBP寄存器作为栈帧指针的作用。
在栈中保存函数返回地址是系统安全隐患之一,攻击者使用缓冲区溢出技术能把保存在栈内存的返回地址更改为其它地址。

举个简单的例子吧:

程序源码:image-20230307205407685

第一步:先让程序运行到main函数暂停,观察栈的初始状态:

image-20230308091711717

当前ESP值为12FF44 ,EBP的值为12FF88

生成main函数的栈帧:

image-20230308091936154

此时栈的状态变为:

image-20230308092038527

这个时候main函数的栈帧就创建好了

接下来执行 long a=1 long b=2:

image-20230308092229470

此时栈的状态变为:

image-20230308092304000

再调用add()函数,对应汇编为:

image-20230308092428791

因为add()函数有两个参数,所以调用前先把参数压住栈,注意入栈的顺序和参数顺序相反(函数参数的逆序存储)

此时栈内状态变为:

image-20230308092617286

接下来进入add()函数内部,分析整个函数的调用过程

返回地址:执行call命令进入函数前,CPU会把函数的返回地址压住栈,用作函数执行完后的返回地址,当执行完call命令后,栈的状态为:

image-20230308092848079

接下来开始执行add()函数,并形成add()函数的栈帧:

image-20230308092938881

栈的状态变为:

image-20230308093003573

add()函数内部有两个局部变量,并用两个形参为其赋初值,汇编如下:

image-20230308093206515

此时栈的状态变为:

image-20230308093228576

再就是执行add的运算了

先将x的值传给EAX,再将y的值与EAX相加并保存在EAX里。

函数执行完毕,开始删除add()的栈帧

image-20230308093524303

image-20230308093619563

再执行retn指令,存储在栈中的返回地址也出栈,此时栈完全恢复到调用add函数前的状态。所以,不管有多少函数嵌套调用,利用栈帧都可以很好的维护

现在,程序执行流重新返回到main()函数里

image-20230308093918137

之后就没什么了,清除main函数的栈帧,程序结束

函数调用约定

cdecl

源代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int main(int argc,char* argv[])
{
return add(1,2);
}

动调发现:add函数1,2以逆序方式压入栈,之后使用add esp,8清理栈,这样的方式就是cdecl

image-20230308094711947

好处:可以像c语言的printf函数一样,向被调用函数中传入多个参数,这种长度可变的参数在其他两种调用方式中很难实现

stdcall

栈的清理工作由add()函数的最后 RENT 8实现,含义为RENT+POP 8字节,及返回后使ESP增加到指定大小

image-20230308095032227

像这样在被调用者add()函数内部清理栈的方式即为stdall方式。

好处:image-20230308095238713

fastcall

使用寄存器而非栈内存来传递参数

image-20230308095348943

第二章