这次记录一些较为基础的部分,有利于在逆向分析时可以相对较为轻松一点的看懂反编译出来的代码(一般为汇编)
一:
我们知道在我们写一个Win32程序时首先要有一个入口点(main,WinMain之类的)但是其实每一个Win32程序都是有一个启动函数为开头的,具体过程为:
1:检查命令行指针
2:检查环境变量指针
3:全局变量初始化
4:对战初始化
5:调用应用程序进入点函数,并将命令行参数传入(有的话)
当函数返回时主函数(main)启用exit然后将返回值返回给启动函数,启动函数退出线程。
2:
在C语言中我们可以看见库函数在返回值和标识符之间会有一个类似于_cdecl之类的标识符,这个就是调用约定,我找到了我C++的笔记对调用约定的补充说明和函数命名法则的部分说明:
(查了下Wiki有了以下结果和总结)
调用惯例 出栈方 参数传递 名字修饰
cdecl;函数调用方 ;从右至左的顺序压参数入;下划线+函数名
pascal;函数本身;从左至右的顺序入栈;较为复杂,参见pascal文档
stdcall;函数本身;从右至左的顺序压参数入栈;下划线+函数名+@+参数的字节数, 如函数 int func(int a, double b)的修饰名是 _func@12
fastcall;函数本身 头两个;DWORD(4字节)类型或者更少字节的参数 被放入寄存器,其他剩下的参数按从右至左的顺序入栈;@+函数名+@+参数的字节数
thiscall;不一定 ;从右至左的顺序压参数入栈(有时会通过寄存器传递this指针);不详
再对调用管理做一个解释:
以下是调用者来完成被调用函数出栈的:(解堆栈的代码每次调用时都需要生成一遍以确定需要出栈字节数)
cdecl:
这个是在X86平台上C的一个调用标准了(用得非常普遍)
首先是实参由右往左入栈
函数名称在编译时只加一个_
函数退出时由被调用者清除栈
返回值过大时(大于64字节并且不是POD类型的值,满足条件的值一般都是由寄存器传递的)会在调用时为函数多申请一部分空间并将该控件地址当作第一个参数隐式传递给调用函数(GCC是不论任何大小都是如此)
syscall和cdecl差不多 syscall是32位OS/2 API的标准。
optlink和cdecl差不多optlink在IBM VisualAge编译器中被使用。
以下是被调用函数自身完成出栈的(此时在编译阶段就要求知道栈上有多少字节需要处理,所以不支持可变参)并且是在返回原函数前解栈的
pascal参数是从左到右入栈的
register(这个也是一个关键词,用于申请变量储存在寄存器上(并不是每次都成功,具体有计算机决定)这个关键词的变量不可寻址(寄存器无地址可寻))Borland fastcall的别名
Borland fastcall 从左到右传三个参数到:EAX, EDX和ECX中剩下的入栈。
stdcall 是Windows API的标准调用约定除了由被调用者清除栈之外和cdecl差不多,区别在于命名函数名后缀以符号”@”,后跟传递的函数参数所占的栈空间的字节长度
两者皆可(被调用者清栈或是调用者,主要基于是否是可变参)
thiscall是调用C++非静态成员函数时使用此约定
GCC中和cdecl差不多(固定由调用者清除,区别在于this指针在最后入栈(解释了第一个隐藏参数为this同时也是第一个参数)
在微软的Visual C++中区别在于this指针是由寄存器传递的其余同cdecl
这里对上面最一些补充吧:
首先出栈方指的实际上就是恢复栈区的那一方因为我们知道,在函数调用的时候(VS下)的汇编实现代码大概是:
push ebp
mov ebp,esp
sub esp,0C0H ;这个值具体要看函数内的零食变量的数量和大小而定,栈是由高地址向低地址走的
push ebx
push esi
push edi
….
xor eax,eax
mov eax,… ;这个一般都是返回值
pop edi
pop esi
pop ebx
add esp,0C0H;这个值是一开始减去的
cmp ebp,esp
call __RTC_CheckEsp ;这两句事确保栈区没有发生异常的错误(类似于栈溢出修改了返回地址之类的)
mov esp,ebp
pop epb
ret
——————————————————————————————————————————————
调用方:
add esp,…;参数的总字节大小
mov …,eax;接收返回值
此时的add esp的操作实际上就是恢复(平衡)栈区的操作,这里事_cdecl的形式,其他的类似于_stdcall也就是把那个平衡的操作放到了被调用方里面( ret 后面会跟一个数字的,这个数字就是平衡栈区的)
然后来说下参数的问题,从左往右入栈的话比如:
int PASCAL a(int b,int c,int d);
此时对应的参数b,c,d分为在内存中为[ebp+10H],[ebp+0C],[ebp+8]这三个(ebp一般为栈底地址,[]实际上就是解除引用,ebp一开始[ebp+0]实际上事一开始push ebp它自己的值,VS下可以跑一下这个:
#include<stdio.h>
int _stdcall a(int b, int c,int d)
{
_asm
{
xor eax,eax
add eax,[ebp+8]
add eax,[ebp+0xc]
add eax,[ebp+0x10]
}
}
int main()
{
_asm
{
push eax
}
int c = a(1, 2, 3);
_asm
{
pop eax
}
printf("%d", c);
return 0;
}
所以此时在内存中,我们看到的参数和函数的声明的是相反的,从又到左反之。
然后就是函数的名称修饰,我之前的笔记上面的那个可能有一些不全面,这里从《加密与解密》里面扒下来一些细化的东西:
里面有一个_fastcall我没有说到,这个就是全部参数(部分,根据编译器而定)由寄存器传输