标签 自动化分析 下的文章


在很多比赛中,都是以代码量堆积起来的,这样题目需要有强大的逆向的能力,但是在大ai时代,我们可以让ai给我们极大程度的降低逆向分析的压力。

题目附件

通过网盘分享的文件:easypwn_b52bf495bea360db1e259c6827e703ed.zip

链接: https://pan.baidu.com/s/1pA49B0Jpb0FYAEKqSHe1Kg?pwd=aaaa 提取码: aaaa

--来自百度网盘超级会员v4的分享

Easypwn(典型的 bison/flex 生成的 parser:状态栈用 byte,语义栈用 8 字节槽位

这个题目及其复杂,我们通过这个题目学习一下现代ai逆向分析的辅。

技巧一:通过ai写idapython快速一键还原名称

其他的部分可以利用这个方式一键还原,这样就不用看a+的偏移

这里计算机模拟架构非常非常复杂 我们需要用idapython进行导入,要不太麻烦了

下面这个idapython可以直接导入我们需要的

Python

复制代码
import idc
import ida_kernwin

DECLS = r"""
typedef struct StreamCtx
{
void *fp;
unsigned char *buf;
unsigned char *ptr;
int cap;
int len;
int can_realloc;
int is_tty;
int unk40;
int unk44;
int unk48;
int buf_inited;
int slot_state;
int _pad5C;
} StreamCtx;

typedef struct OP_Ctx
{
int *nest_counter;
void *in;
void *out;
unsigned __int64 stream_index;
unsigned __int64 stream_cap;
StreamCtx **streams;

unsigned char ch_saved;
unsigned char _pad49[3];

int buf_valid;
int tok_len;
unsigned char *cur;

idapython使用中遇到的问题及解决

一开始遇到了ModuleNotFoundError: No module named 'ida_struct'这个问题

对于这个问题,我们首先确定我们这个模块有没有这个功能

import pkgutil
mods = sorted([m.name for m in pkgutil.iter_modules()])
print("ida_struct" in mods, "idautils" in mods, "idaapi" in mods)
print([m for m in mods if m.startswith("ida_")][:50])

你返回的结果非常明确:

ida_struct False

idaapi True

idautils True

并且模块列表里有 ida_typeinfida_idc 等,但就是没有 ida_struct

这个 IDA/IDAPython 版本把结构体相关能力主要放在 IDC API(idc.*)和 ida_typeinf/ida_bytes 等模块里了

然后我们把这里的信息喂给ai之后,就能给我写出来正确的idapython的脚本

idapython的使用实例二

下面给你一份 “一键还原已知信息” 的 IDAPython 脚本(兼容你这套 Python 3.11 + 没有 ida_struct 模块 的环境)。它会做三件事:

1 给你已知的全局表(byte_4068xx/word_4063xx)批量重命名(Bison 表 + Flex DFA 表)

2 尽量给这些表打上合理的数组类型(不要求知道确切长度)

3 (可选)把你已导入的 StreamCtx / OP_Ctx 的成员名改成更语义化的 Flex/Bison 命名(如果 idc.set_member_name 在你版本可用就会生效,不可用也不会报错)

你直接把脚本复制到 IDA 的 Python 窗口执行即可。执行完建议:F5 刷新反编译窗口,或者重开一下伪代码视图。

idaoython中的窗口的刷新

这个好像只能还原反汇编,不能还原反编译

这里需要关闭重启一下才行,重复这个步骤可以很快还原这个计算机的函数名称,结构体名称,而不用去手动一个个的去定义。

技巧二:fuzz输入测试(AFL)

这个题目fuzz测试没测出来异常,应该是我的用法的问题

下面这个是特定工具,限制的话是需要提前用这个fuzz进行编译

安装

下载AFL

编译

检测是否成功

运行

建目录 in/,放一些“语法合法”的短表达式:

run.sh

如果你有源码:优先用插桩编译(效果最好)

如果你只有二进制:用 QEMU 模式

-m none 是为了避免一些程序内存稍大导致 AFL 误杀。 -t 1000+ / 2000+ 是给 parser 足够时间(你后面可以再调回去)。

遇到的问题以及解决

解决环境问题

解决工具的依赖的问题(c脚本替代bash脚本)

再次安转依赖

build_qemu_support.sh的脚本修复

拷贝进入linux空间

清理空间

改个配置文件

然后创建一个临时终端,解决python的版本的问题

之后编译的时候可能会出现很多bug,我们用下面这个查看内存占用的情况

删除编译残留

安装AFL+

环境清理

docker拉取

运行,等待

爆破了2小时一无所获,我们可以延长时间的参数

crashes

这个目录通常用于存储 fuzzing 过程中发现的崩溃案例。AFL++ 在测试目标程序时,如果遇到崩溃(比如段错误、总线错误等),它会将触发崩溃的输入保存到这个文件夹中,供进一步分析。

default

这个文件夹可能包含 AFL++ 默认的配置、输入样本或运行时数据。它可能是 fuzzing 过程中使用的初始种子(seeds)文件的目录,也可能是 fuzzing 执行时的默认设置或输入数据的存储位置。

hangs

这个目录用于存储 fuzzing 过程中导致程序挂起(例如死锁或无限循环)的输入数据。当 AFL++ 测试过程中遇到这样的情况时,它会将这些输入保存到这个目录中,以便进一步调试。

plot_data

这个目录通常包含 AFL++ 在 fuzzing 流程中生成的图形数据或统计数据。例如,它可能包含了程序运行过程中的覆盖率信息,用于生成图形化的报告。AFL++ 可以通过这些数据来评估模糊测试的有效性和进展。

queue

这个文件夹保存 fuzzing 测试过程中用来测试程序的所有输入数据(也就是“队列”)。每一个输入样本都会被存储在此文件夹中,并且 AFL++ 会不断地修改、变异这些输入数据,并将变异后的数据放入队列中供程序继续测试。

查看 fuzzer_stats

这个文件记录了 AFL++ 在模糊测试过程中跟踪的关键统计数据

查看 plot_data

这个文件提供了模糊测试的进度数据,并记录了时间、执行情况等与测试进度相关的信息。每一行代表测试过程中的一个时间点

技巧三:结构体的分析技巧

结构体一般是存储在堆上,我们打断点到malloc的位置上,然后对比赋值从物理字段对比ida源码记忆结构体即可,如果抽象能力很强的话可以直接看。

这样的话看的话就比较清晰了,结构体的赋值字段就不用跳着看了

image.png







技巧四:程序流的分析技巧

我们看到漏洞的可能点,我们一个重要的是如何达到我们漏洞的利用点

这里我们可以结合ida的反汇编窗口去一个个对比去看,结合F5,这里和dbg的动调配合起来,道道很多,附一个中间过程对比的截图,主要是有外到内取看判断条件

image.png



这里的分析过程最好是关闭aslr,这样可以看到分支条件哪些是可控的因素,哪些是不可控的因素,哪些可以进入分支,哪些是必然不能进入分支的。

技巧五:弄清楚bug的原因

这个是和程序流一样的,我们要尽可能弄清楚,弄不清楚不是很影响,弄清楚了之后对于做题帮助是实质性的,比如说这个error的报错的原因

out的结构体如下

40 位十进制数大概是:

10^39 ~ 2^129.610^40 ~ 2^132.9

需要的字节数约为 ceil(bitlen/8)ceil(133/8) = 17 字节

也就是说,一个 40 位左右的大整数,转换成 base-256 输出时会产生 ~17 个 rem_byte

但你的 out 只有 16 字节,而且还不是“纯输出 buffer”,里面有指针/错误码。

所以当你写到第 9 个字节开始,out[1](error)就被你自己写脏了,变成非 0,于是:

就走 “error 分支”。

技巧五:漏洞分析技巧

这个是最难的一部分,把程序都还原出来,只不过是降低查找漏洞的难度,而不是我还原出来就一定能找到漏洞。

这个题目关闭了canary,本质上一个栈溢出

我们观察这里来回返回的释放申请,首先推测是结构体的重用的漏洞

这个题目逻辑流一开始往申请的堆块里面输入最大0x400的数据,实际上不到0x400

然后拷贝进入一个堆块,这里的拷贝是存在字符截断的处理的

之后申请一个ctx_manager结构体管理我们拷贝存储的堆块

第一轮检查结束就释放掉我们一开始的这些堆块,之后重复这个流程,然后功能执行的是分析计算

第二轮结束之后,还有一个计算结果标志位的检测,之后就是打印我们第二轮计算的结果,这个流程是这样的

这里的可疑的地方一个是多重释放,另外一个不断取余,我们从感觉是有这两个利用点

下面的这个利用点的话主要是这个

image.png



这里是往栈上的写,写的是我们计算的结果,我们可以利用我们利用残留数值ctx结构体,和这个配合可以写上rop链的

假如说我们不去使用特殊手段的话,我们最多只能写10个数字长度

然后我们发现可能并不是结构体重用的漏洞