IDA结构体分析

  1. IDA 修复枚举值
  2. IDA 结构体修复
  3. IDA 虚表修复

IDA 类型修复

源码编译成二进制代码的过程中,大量辅助信息被删除,其中最重要的就是类型信息

为什么要修复伪代码中的类型?

  • 提高伪代码的准确度
  • 指导 IDA 反编译器中的优化器使用正确的优化方案
  • 让 IDA 生成的伪代码更接近源码

我们需要手动修复哪些类型?

  • 函数返回值类型
  • 参数类型 / 局部变量类型 / 全局变量类型
  • 数组类型 / 数组大小
  • 结构体类型
  • 虚表类型

IDA 数组修复主要有两种:

1.局部变量中定义的数组

2.全局变量中定义的数组

数组修复要考虑:数据类型 + 数组大小

IDA 修复枚举值

实验材料:ptrace1

IDA 的类型数据库内置了常见的枚举(宏)的值,可以直接引入并修复。

通过逆向分析,ptrace函数

image-20230121101355025

第一个参数就是ptrace函数的功能号

鼠标锁定摁M键导入枚举值

Ctrl+F5搜索ptrace关键字

image-20230121101528976

找到后双击即可

image-20230121101553907

下图就全部修复完成了

image-20230121101813329

IDA 结构体修复

实验材料:monopoly

这是一个大富翁游戏!

确定结构体大小:

  • 内存分配可以直接确定结构体大小
  • memcpy / 局部变量偏移差 -> 间接确定 (结构体/类局部变量) 这种大多是在栈上

image-20230121104326617

创建相等大小匿名结构体,并将相关变量、参数的类型修改为该结构体

这里看见这个0x70的new函数,直接在struct视图下创建结构体

image-20230121105019521

快捷键是Shift + F9,打开struct视图

这里在struct视图下,右键点击增加结构体类型

image-20230121105406208

这里可以看见结构体大小

image-20230121105556782

鼠标光标在ends处,增加属性(大小):摁d键

image-20230121105718374

这里直到sizeof显示为0x70为止,每个属性先以dq为单位,进行存储,后续再逆向分析再进行变更

这样结构体就初步设置完成了

image-20230121110056164

这里回到代码处,这里将v0的类型修改为,刚刚我们创建的结构体类型

image-20230121110310274

第一种改法:摁Y键,进行类型修改为struc_1 *类型

第二种改法:摁右键,转化为其他结构体:

image-20230121110552200

这里直接选择我们刚刚创建的类型即可

image-20230121110626662

这里创建好之后,执行下面的函数,我们跟进去看看

image-20230121120704877

这里的a1进行了一系列操作,我们将a1的类修修改为我们定义的类型image-20230121120850500

因为这个函数在new之后执行,所以可能是构造函数,这里我们改个函数名吧image-20230121121052464

在构造函数中,有些属性是进行dword操作,这里我们就要将qword,改成dword类型image-20230121121546769

比如field_48,这里双击field_48进入结构体定义处,将该属性改成dword,这里摁d键image-20230121121758509

设置好之后下面未定义的也需要改成dword,就像这样image-20230121121852483

其他变量也是一样修改,把剩下的也修改了,但是当最后一个属性改为dword后,总大小发生了改变,这里我们只需要在下面再增加一个变量即可image-20230121122116991

最后就是这样image-20230121122144895

然后在源码中摁F5,代码就好看多了image-20230121122453040

上面循环处field_4c是以4字节进行访问,并且访问5词,也就是说field_4c开始处是一个以4字节为单位的一个数组,数组大小为5,在结构体定义处,右键点array设置数组,大小为5image-20230121122734380

最终field_4c后面的变量也将合并进入数组中image-20230121122825156

源码舒服,重命名为array1image-20230121123416130

这里进行字符串的初始化和赋值操作image-20230121123758528

这里我们将field_20改为str1

根据大富翁游戏,初始化的字符串,根据名字都是一些建筑,我们可以把定义的结构体名称改成house,然后将构造函数改成init_houseimage-20230121124542969

然后经过构造函数,申请到的对象内存都会存放到,以A1C0为首的地址处image-20230121125509200

image-20230121125527179

上面一共64个位置,所以类型为house *为类型,数组长度为64个来存放house对象的指针image-20230121125756372

这里再右键array改为64image-20230121125834301

这些建筑正好形成地图,将数组名改成map即可image-20230121130116722

分析另外一个结构体,这个结构体没有使用 new / malloc 分配内存,如何确定大小?image-20230121132813993

进入函数,看起来明显是个初始化函数image-20230121132852076

两个变量都是紧挨着的在内存中,属于静态分配了image-20230121132938102

两个变量位置相减为0x80,所以我们就暂时确定这个结构体的大小为0x80,然后我就去struct视图中去创建一个大小为0x80的结构体,还是老操作image-20230121133259989

这里其实可以先创建64长度的数组,然后再取消定义,这样就可以快速创建结构体了

类型定义好之后,函数外的全局变量类型不急着更改,先更改函数形参的类型image-20230122155016716

在函数中判断feild_20可能是个数组,这里先不理会

后面函数形参类型也需要一个个去修改image-20230122155424970

刷新一下变成这样了,给变量改个名image-20230122182413118

把剩余需要修改的变量大小继续更改image-20230122183001258

然后这里根据printf的字符串信息来判断结构体的类型image-20230122183705595

v6可能是建筑的类型,从map数组中取出来的结构体都是house类型了,然后把变量名都改成自己猜想的名字。

这个函数的实参是map数组元素,形参应该改为house结构体指针image-20230122185813240

这里有个field_40,不能确定是啥image-20230122190051439

我们可以用交叉引用,去别的地方找信息,刚好这里有一处image-20230122190406178

这里将ppp1赋给field_40,所以我们就将field_40改成结构体2类型

以上就是随意逆向的结果,真正正经逆向都是要分析程序逻辑的,我们先随意逆向,分析部分属性信息,有助于我们之后的分析

程序刚开始会让我们输入游戏难度等级image-20230122191713348

经过分析这个就是获取数字的函数image-20230122191835438

然后我们先从简单的easy_level开始image-20230122192539523

从这分析出ppp3是玩家的意思,因为刚开始它就给ppp3给到初始的金额了image-20230122192843765

然后进入一个循环,首先进入这个函数image-20230122193721525

这个函数很明显是个循环image-20230122193909583

有两个函数,一个只有player1另外还有player2,也就是一个人玩玩,另一个人玩image-20230122195530524image-20230122195544442

然后这里我们需要输入我们想做的内容image-20230122202153532

我们先以第一个,这里有个比较,应该是玩家资产数量image-20230122202349924

要求输入的值不能超过,玩家拥有的资产的数量。所以sub_452F函数估计就是资产的售卖了

这里售卖的函数中image-20230122203152235

这里资产数组大小为map的总个数为64个image-20230122203328139

然后是3,购买资产image-20230122210502911

分析得image-20230122210702534

最终:image-20230122210742758

image-20230122210913452

全局字段交叉引用 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所示:image-20230122212103043

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了

虚表修复主要是为了重建虚表交叉引用,一般类的第一个地址就是虚表的地址。

这里就是调用虚表的某一个函数image-20230122213810302

这里类大小为0x28字节image-20230122213834372

这里我们找到虚表,点击这个就是虚表image-20230122213906848

然后右边是虚表的符号image-20230122213950337

这里先进行构建结构体,在结构体中表示虚表,一个函数地址,在64位中为32位image-20230122214250424

创建好后,创建类结构体,然后将类结构体的第一个属性类型设置为虚表类型image-20230122214426259

然后将v3的类型设置为类结构体类型image-20230122214509446

然后就修复完成了image-20230122214621630

image-20230122214835711


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jaytp@qq.com

×

喜欢就点赞,疼爱就打赏