在进入WinMain之前,编译器会添加启动函数,获取系统版本,初始化全局变量,加载运行库等等的工作,最后在进入WinMain,分析程序的时候可以略过启动代码,重点放在WinMain函数中
函数组成:函数名、入口参数、返回值、函数功能
函数的识别:通过call和ret识别,将call的下一行代码压入栈中来记录返回到哪里,换句话说将参数和返回地址(下一行)一起传给被调用函数
调用约定(Calling Convention):
子程序在最后用一句 ret n(n是个整数=4*参数个数),功能是ret之后再add esp+n
堆栈传参:
堆栈法访问参数:用ebp记录esp,间接访问参数;或者直接用esp访问参数
局部变量:esp移动让出空间,ebp访问;enter和leave来管理;sub esp,8分配8个字节用于局部变量
DWORD:4个字节
寄存器传参:()fastcall)实际上可能通过任何寄存器传参,VC将最左2个小于4字节用ecx,edx;BC/Delphi将最左3个小于4字节的参数用eax,edx,ecd;Watcom C分别用eax,edx,ebx,ecx。(thiscall)用于类的非静态方法,参数用堆栈传,this用ecx寄存器传。
名称修饰(Name Mangling/Decoration):为了操作符和函数重载,C++编译器改写入口点的符号名。
return返回值一般放在eax中,如果超出容量,高32位放在edx。
参数引用返回:指针的操作。其实就是高级语言中传值和传指针的区别
局部变量和参数区别:用ebp访问:ebp-XXXX调用局部变量;ebp+XXXX调用参数。
分配局部变量:如果不用ebp(编译器优化模式)esp-n == push reg;撤回相反
Push-reg:将reg用作临时变量
全局变量:一般硬编码在.data区块,可读写;如果在只读区,一般是常量。可以用于注册与否的标记
数组:同类型的元素的集合,在内存中按顺序连续存放,可以通过类似于“基址+偏移”访问各元素,基址可以是常量或寄存器,为定值;偏移可以访问相应单元(结构赋值也类似)
虚函数:每个实例其实有一个指向虚函数表(VTBL)的指针,这里说的this指针也就是指向虚函数表的指针,虚函数表的表项是指向虚成员函数的。虚函数表地址跟普通函数一样,是固定的(绝对地址)
比较操作:cmp整数,浮点数fcom/fcomp
If-then-else:汇编之后cmp a,b;jz(jnz)xxxx
Test:text eax,eax:若eax为0,则与运算结果为0,ZF为1(减法相等),jne是ZF=1不跳转(不等才跳)
Switch分支:没有优化跟多个if差不多;优化之后有减法还是跟多个if差不多;case是有规律的可能会有跳转表。
跳转指令计算:不用去刻意记,可以推导。可以注意向前向后跳,多数都要查书才记得。
条件设置指令:用于消除转移指令,当标志位为某些数值时,设置寄存器为1,setge,cmov,fcmov
循环:向前条容易是循环。一般用ecx计数,
加减法:没优化add&sub。Lea c, [a+b78h],c=a+b+78
乘法:没优化mul&imul。 Lea ecx, dword ptr [eax+8*eax] (eax*9)。Lea指令还可以做2的整数次幂的乘法。
除法:代价高,比乘法多消耗10倍cpu时间。Div和idiv,优化过会转为乘法等,难从反汇编代码中理解
字符串:C风格最末\0,pascal和delphi在字符串的头说明字符串的长度
大小写:大写41h~5Ah,小写是61h~7Ah,相差20h。二进制的第五位0代表大写,1代表小写
计算字符串长度:strlen(),mov ecx, FFFFFFFF,很可能是在获取字符串长度
指令修改技巧:寄存器清零mov 0,xor,sub eax eax,多用eax。