IDA 类型修复
源码编译成二进制代码的过程中,大量辅助信息被删除,其中最重要的就是类型信息
为什么要修复伪代码中的类型?
- 提高伪代码的准确度
- 指导 IDA 反编译器中的优化器使用正确的优化方案
- 让 IDA 生成的伪代码更接近源码
我们需要手动修复哪些类型?
- 函数返回值类型
- 参数类型 / 局部变量类型 / 全局变量类型
- 数组类型 / 数组大小
- 结构体类型
- 虚表类型
IDA 数组修复主要有两种:
1.局部变量中定义的数组
2.全局变量中定义的数组
数组修复要考虑:数据类型 + 数组大小
IDA 修复枚举值
实验材料:ptrace1
IDA 的类型数据库内置了常见的枚举(宏)的值,可以直接引入并修复。
通过逆向分析,ptrace函数
第一个参数就是ptrace函数的功能号
鼠标锁定摁M键导入枚举值
Ctrl+F5搜索ptrace关键字
找到后双击即可
下图就全部修复完成了
IDA 结构体修复
实验材料:monopoly
这是一个大富翁游戏!
确定结构体大小:
- 内存分配可以直接确定结构体大小
- memcpy / 局部变量偏移差 -> 间接确定 (结构体/类局部变量) 这种大多是在栈上
创建相等大小匿名结构体,并将相关变量、参数的类型修改为该结构体
这里看见这个0x70的new函数,直接在struct视图下创建结构体
快捷键是Shift + F9,打开struct视图
这里在struct视图下,右键点击增加结构体类型
这里可以看见结构体大小
鼠标光标在ends处,增加属性(大小):摁d键
这里直到sizeof显示为0x70为止,每个属性先以dq为单位,进行存储,后续再逆向分析再进行变更
这样结构体就初步设置完成了
这里回到代码处,这里将v0的类型修改为,刚刚我们创建的结构体类型
第一种改法:摁Y键,进行类型修改为struc_1 *类型
第二种改法:摁右键,转化为其他结构体:
这里直接选择我们刚刚创建的类型即可
这里创建好之后,执行下面的函数,我们跟进去看看
这里的a1进行了一系列操作,我们将a1的类修修改为我们定义的类型
因为这个函数在new之后执行,所以可能是构造函数,这里我们改个函数名吧
在构造函数中,有些属性是进行dword操作,这里我们就要将qword,改成dword类型
比如field_48,这里双击field_48进入结构体定义处,将该属性改成dword,这里摁d键
设置好之后下面未定义的也需要改成dword,就像这样
其他变量也是一样修改,把剩下的也修改了,但是当最后一个属性改为dword后,总大小发生了改变,这里我们只需要在下面再增加一个变量即可
最后就是这样
然后在源码中摁F5,代码就好看多了
上面循环处field_4c是以4字节进行访问,并且访问5词,也就是说field_4c开始处是一个以4字节为单位的一个数组,数组大小为5,在结构体定义处,右键点array设置数组,大小为5
最终field_4c后面的变量也将合并进入数组中
源码舒服,重命名为array1
这里进行字符串的初始化和赋值操作
这里我们将field_20改为str1
根据大富翁游戏,初始化的字符串,根据名字都是一些建筑,我们可以把定义的结构体名称改成house,然后将构造函数改成init_house
然后经过构造函数,申请到的对象内存都会存放到,以A1C0为首的地址处
上面一共64个位置,所以类型为house *为类型,数组长度为64个来存放house对象的指针
这里再右键array改为64
这些建筑正好形成地图,将数组名改成map即可
分析另外一个结构体,这个结构体没有使用 new / malloc 分配内存,如何确定大小?
进入函数,看起来明显是个初始化函数
两个变量都是紧挨着的在内存中,属于静态分配了
两个变量位置相减为0x80,所以我们就暂时确定这个结构体的大小为0x80,然后我就去struct视图中去创建一个大小为0x80的结构体,还是老操作
这里其实可以先创建64长度的数组,然后再取消定义,这样就可以快速创建结构体了
类型定义好之后,函数外的全局变量类型不急着更改,先更改函数形参的类型
在函数中判断feild_20可能是个数组,这里先不理会
后面函数形参类型也需要一个个去修改
刷新一下变成这样了,给变量改个名
把剩余需要修改的变量大小继续更改
然后这里根据printf的字符串信息来判断结构体的类型
v6可能是建筑的类型,从map数组中取出来的结构体都是house类型了,然后把变量名都改成自己猜想的名字。
这个函数的实参是map数组元素,形参应该改为house结构体指针
这里有个field_40,不能确定是啥
我们可以用交叉引用,去别的地方找信息,刚好这里有一处
这里将ppp1赋给field_40,所以我们就将field_40改成结构体2类型
以上就是随意逆向的结果,真正正经逆向都是要分析程序逻辑的,我们先随意逆向,分析部分属性信息,有助于我们之后的分析
程序刚开始会让我们输入游戏难度等级
经过分析这个就是获取数字的函数
然后我们先从简单的easy_level开始
从这分析出ppp3是玩家的意思,因为刚开始它就给ppp3给到初始的金额了
然后进入一个循环,首先进入这个函数
这个函数很明显是个循环
有两个函数,一个只有player1另外还有player2,也就是一个人玩玩,另一个人玩
然后这里我们需要输入我们想做的内容
我们先以第一个,这里有个比较,应该是玩家资产数量
要求输入的值不能超过,玩家拥有的资产的数量。所以sub_452F函数估计就是资产的售卖了
这里售卖的函数中
这里资产数组大小为map的总个数为64个
然后是3,购买资产
分析得
最终:
全局字段交叉引用 Ctrl + alt + X (要尽可能将相关函数找出来并修复类型,有利于查找)
IDA 虚表修复
实验材料: vtable
虚表就是C++实现多态的一种机制
为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。我们来看以下的代码。类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
类A的虚表如图1所示:
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了
虚表修复主要是为了重建虚表交叉引用,一般类的第一个地址就是虚表的地址。
这里就是调用虚表的某一个函数
这里类大小为0x28字节
这里我们找到虚表,点击这个就是虚表
然后右边是虚表的符号
这里先进行构建结构体,在结构体中表示虚表,一个函数地址,在64位中为32位
创建好后,创建类结构体,然后将类结构体的第一个属性类型设置为虚表类型
然后将v3的类型设置为类结构体类型
然后就修复完成了
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jaytp@qq.com