2020.03.08-2020.03.15
IDA
枚举
‘View’->‘Open subviews’->‘Enumerations’ 打开枚举窗口,按’Insert’插入新的枚举类型,按’N’添加成员
选中需要重新定义的数据,按’M’后将其转换
FLIRT
库文件快速识别与鉴定技术
在一系列编译器的标准库文件里自动找出调用的函数,如,把’call 406E40’识别为’call strlen’
如果没有自动识别出来,可以强制使用编译器特征文件(xxxx.sig)
‘View’->‘Open subviews’->'Signatures’或’Shift+F5’打开签名窗口,右键’Apply new signature’选择签名文件
(不知道把这部分叫作啥)
2.
输入输出等函数可在name窗口中查看
32位软件逆向技术
启动函数
Windows程序执行并不是由WinMain函数开始的,首先执行的是启动函数的相关代码(由编译器生成),完成后才调用WinMain函数
c/c++程序的启动函数作用基本相同,包括 检索指向新进程的命令行指针、检索指向新进程的环境变量指针、全局变量初始化和内存栈初始化等
分析程序的过程中可以略过启动代码,直接将重点放到WinMain函数上
函数
通过call…ret把函数调用和其他跳转指令区别开
直接调用:call 函数首地址
间接调用:call [ eax ] (通过寄存器传递函数地址或动态计算函数地址)
函数的参数
函数传递参数有3种方式:栈方式、寄存器方式、通过全局变量进行隐含参数传递方式
每一种机制与使用的编译语言有关
利用栈传递参数
函数计算结束后,由调用者或函数本身修改栈,使栈恢复原样(平衡栈数据)
调用约定:为了实现函数调用而建立的协议(按照什么顺序入栈;由谁来平衡栈…)
- c规范(__cdecl)函数按照从右到左的顺序入栈,由调用者负责清除栈(c/c++/mfc(微软基础类库)默认调用约定)
- stdcall调用约定按照从右到左传递参数,并由调用的函数在返回前清理传送参数的内存栈
- stdcall调用约定是Win32 API采用的约定方式,在Win32 API种也有一些函数采用(__cdecl)调用,如wsprintf
c、c++、pascal 等高级语言的子程序执行过程基本相似:
- 调用者将函数执行完毕时应返回的地址和参数压入栈
- 子程序通过’ebp 指针+偏移量’对栈中的操作进行寻址
- 子程序使用ret或retf返回,此时cpu将eip置为栈中保存的地址
栈的操作对象只能是双操作数(占4个字节)
用ebp存取栈
用ret平衡栈时,在ret指令后加一个操作数,表示在ret指令后给esp加上操作数 如’ret 8’相当于在返回后将esp+8,ret后面的值等于参数个数*4h
enter 和 leave指令可以帮助进行栈的维护
1 | enter xxxx,0 ;0表示创建xxxx大小的空间来放置局部变量 |
ENTER 有两个操作数:第一个是常数,定义为局部变量保存的堆栈空间字节数;第二个定义了过程的词法嵌套级。
ENTER numbytes, nestinglevel
Numbytes 总是向上舍入为 4 的倍数,以便 ESP 对齐双字边界。Nestinglevel 确定了从主调过程堆栈帧复制到当前帧的堆栈帧指针的个数。
利用寄存器传递参数
绝大多数编译器都遵循fastcall规范
不同的编译器实现的fastcall稍有不同
名称修饰约定
c++编译器会按照某种规则改写每一个入口点的符号名,从而允许同一个名字有多个用法且不破坏链接器。这种技术称为名称改编或名称修饰
在vc++种,函数修饰名由编译类型(c/c++)、函数名、类(class)名、调用约定、返回类型等决定
函数的返回值
最常见的是return操作符,还有通过参数 按 传引用方式 返回值、通过全局变量返回值
用return操作符返回值
一般情况下返回值放在eax中,如果超过大小,高32位就会放在edx
对于一个返回两个参数和的子函数:
对应c语言代码:
1 | add(int x,int y){ |
通过参数按传引用方式返回值
传递参数的方式有:传值和传引用
传值调用时会建立参数的一份复本,并把它传给调用参数
传引用允许调用函数修改原始变量的值(指针)
1 | void max(int *a,int *b){ |