首页
社区
课程
招聘
[原创]七神分析之还原篇
发表于: 2026-10-2 20:51 2278

[原创]七神分析之还原篇

2026-10-2 20:51
2278

内容和思路可以看看,有一点点概念就好,也可能还是我对VMP的思考太浅显了,道可道非常道的感觉吧

上文:七神VMP真正的分析与还原

作者: 人生导师
日期: 2026 年 9 月 28 日
注意: 这篇风格属于从头到尾开始写的,我是从创建项目时候就开了这个文档,所以一些踩坑思路就没有删除,只会在后面推翻,

从上篇来看,结构可以看出来了,但是还是没还原到程序,得到的是去虚拟化的 trace 日志,而非程序,现在应该想点办法,把我们这个第二代日志再收缩和结构化
这时候就真应该想一下收缩什么东西,怎么收缩,以什么进行收缩,其实可以回想一下程序都有什么

不是收缩指令,收缩是指“重复的执行行为”,第二代日志 → Structural IR 的过程,本质上是把动态执行序列中的重复、分叉、回边和调用边压缩成结构节点。

其实这五点已经说清楚了,然后来说后续思考和思路

第一种代码形式很简单,根据 worklist 思想把没有跳转(确定执行路径的内容)归类成一块,这块就不变了,也不会动

第二种带有条件的,这种在 VMP 内的范式和体现就是对 IP 的不确定推进,但是但是,VMP 的字节码是非常非常规范的(在当时执行的 handler 情况下,不考虑跨 handler 导致的指令不定长)不会说一个分支+1 一个分支+1.5,那加 1.5 那个分支还走不走了,下一个字节码没办法执行了,这时候其实可以考虑找看一下加的内容(true 和 false 分别加多少,然后我们手动的加减后续执行?有点抽象了,我也写懵逼了)

第三种是循环结构,为什么我敢直接说是循环呢?因为我从 VSCode 的侧边缩略图看它执行了 N 次一模一样,并且还有条件判断,比如下面的案例,可以得知上一条是 0x11b 当前是 0x104 这时候就可以反推走了什么分支了,首先减去 1 得到 0x103 再让 0x11b - 0x103 = 0x18,也就是说这两个是不相等的,再看最后一行,这玩意是相等的所以 IP 等于 0x11c 了,也就是这玩意极有可能是一个 while(reg_a != reg_b)的循环结构

第四种函数调用这个,直接当作默认的基本块处理就好了,调用不是分支,是一个必然会走的指令,只不过跳转到了其他地方然后再回来,并不会影响程序的继续
第五种错误处理就应该看虚拟机有没有实现了,京东的 VMP 是实现了的,错误会有一个小的分支处理然后直接 ret 回去,不过也是防止虚拟机出现意外并不是还原向的错误处理

其实到这里,我在想是不是应该重构一下我的 IR 产出呢?这玩意特太不规律了,现在这个更像是漂亮的日志,不像是能稳定提供下文的 IR ,作用与还原的 IR 其实应该像伪汇编一样,结构化起来,opcode 可以直接获取到,然后就是 operands 的操作顺序了,怎么说呢?搞这东西这么久了,其实也有点感觉了,上面那种产出说明已经可以做到识别操作顺序了,搞一个归一化,opcode dst src(一个或者两个操作数),还有一件事就是复合指令集做了很多步骤,多步怎么像 P-code 一样解析成单步

做出来是真出来了,但是吧,有点操蛋的是它真的太汇编了,并且还给我上了一个伪 SSA,看一下输出吧

新版本的引入了相对中间值,在全局看可能会发现有很多 t 系列变量,但是这些变量的作用域是本次 IR 产出,边界就是 IP 的变化
IP 实际值,而非理论值因为我们只有 trace,0x119+1=0x11a 这个是最后一步发生的事情,但是它的 IP 在被解析的时候就已经成了 0x11a,所以 IR 也就显示的是 0x11a,哈哈哈哈哈,我想起来一个小妙招,直接在写文件的时候直接输出一条线进行拦截,这样就能明确表示每个 handler 的范围了

根据我们前面说的内容,可以尝试写一下这方面的代码和内容了,首先这玩意肯定是有一个蓝本的,也就是全部解析的内容,不过有必要再次重申我们得到的:

根据设备 trace 解析得到去虚拟化的 trace,根据去虚拟化的 trace 对虚拟机结构进行恢复

哈哈哈哈哈哈哈,这东西感觉用图算法或者自己硬写缓存+匹配也可以,图算法肯定是方便一些的,甚至我觉得都可以用 shell 脚本进行收缩,我称这个过程为去 trace 化,把 trace 给折叠收缩起来,不知道啥时候能来一个灵感让我对 mem 有使用空间啊

去 trace 化做出来了,prog.txt 落了盘。但先说个丢人的:卡住我很久的那个点,上次其实已经摸到边了,只是我没敢往下想。

上次写到这里的时候我说过这么一句:

IP 实际值,而非理论值因为我们只有 trace,0x119+1=0x11a 这个是最后一步发生的事情,但是它的 IP 在被解析的时候就已经成了 0x11a,所以 IR 也就显示的是 0x11a

我当时把它当成一个「显示上的小瑕疵」,随手加条横线拦截一下就完事了。其实它就是答案。

行尾那个 <vPC> 不是「这条 handler 执行时的 IP」,是 handler 回写 [x23] 之后的值 —— 也就是 post-IP。lifter 里 IR.rIP 的注释其实早就写明白了,原话是「该 handler 回写后的运行时 vPC」,是我自己没当回事。

一旦认下这条,一堆东西自动就位了:

把控制线的形状全捞出来看,就二十来种模板,核心其实是一个:

于是分支、循环、跳转全归一到这一个式子,解出来就是「真枝去哪、假枝去哪」。

我第一版就是这么写的:按 IP 全局去重,同一个 IP 归成一条指令。跑出来一看,1789 个不同的 IP,1177 个是撞车的 —— 同一个 IP 底下挂着好几种完全不同的指令。

一开始以为是 VM 又在搞混淆,后来才反应过来是我蠢:每个 VM 函数各有一套 IP 空间,都从 0 起。 所以「0x79」在这份 trace 里同时是六个不同函数里的六条不相干的指令,跨函数 grep 这个数字必然串味。

那怎么认函数?试过拿操作数表地址聚类 —— 也不行,那是每个调用点各一份的副本:同一个函数从两个地方调进来,ldr t2, [0x7d93c34f70] 里的地址就换了。实测有两个函数(我编的 func 27 / func 31)999 行里只差这一个地址,差点被它骗过去,以为是两个函数。

最后落到「同一 IP 上是什么指令」的指纹聚类。指纹里还得再抹掉一样东西:≥5 位十六进制的常量。因为那些是运行时折叠出来的值,同一条指令每轮跑出来都不一样:

阈值卡在 5 位是权衡出来的:帧大小 -0x4a0、栈槽偏移 0x498、跳转偏移 0x1b 这些结构常量都在 4 位以内,留着刚好够认函数。

VM 的调用约定是:先抬栈,紧接着把返回址存进新帧。

我一开始就靠这个认调用,跑出来 38 个帧、看着挺美。结果发现有 31 个返回配不上 —— 前面明明没进过这么多帧。

原因是有批函数压根不抬栈 —— 叶子函数,序言一个字都不写。只能退回来看 IP 断口:

顺序执行时 post 恒大于 pre(post = pre + 编码长度),post 反而变小,就是换了 IP 空间。

但这个判据我第一版写错了,写成跟「上一条」比,立刻炸出 9000 多个假帧。原因很简单:回边也是 post 变小。

判据必须落在当前这条上 —— 回边自己一定含 add ip,,而调用跳进来那一条是普通指令。改完 69 个帧、零漏配。

工具叫 hy.py,输出 prog.txt,33 个函数、9183 行。长这样:

顺手把 VM 自己的取数舞折了。每条 VM 指令都要先把操作数从表里挖出来,handlers.log 里最开头那条长这样:

这三行是编码不是语义,等价于「这条指令的立即数」,只不过提升器没内存、折不出值,只能原样摊开。顺着临时量的 def 链把它们拼成表达式再代回去,四条并成一条:

中间那串 *(*0x7e23caa3e8 + 0x2) 就是操作数槽的运行时地址,留着当回查 IDA 的锚点,别丢。

还原对不对,得有东西验。能拿来验的只有 trace 自己:

trace 里每一步的实际后继,应该落在解出来的两路落点里。

129648 条转移,对得上 129645 条(99.998%),剩下 3 条散在三个函数里各一处。到这个数我才敢说这东西能用了。

得诚实,这三堵不是我写得烂,是方法本身的天花板:

一、动态 trace 永远只有一条路径。 164 个跳转落点里 27 个(16%)指向从没执行过的槽位:

这次 v5 == v0 一直是假,那一支一次没走,所以 func 0 里根本没有 0x79 这条指令。想要它,只能换输入重跑。没有一个动态 trace 能给你整个程序。

二、常量是每次调用解密出来的。 就是前面说的 func 16 / func 18 —— 同一个函数调两次,999 条指令只差 3 条,全在 mov VREG[0xf], 0x40a1... 这种地方。这批值不在字节码里,在密钥流里,把 VM 拆得再干净也拿不到。


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・91
打赏
分享
最新回复 (55)
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2 楼
666
2026-10-2 21:21
0
雪    币: 499
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3 楼
其实这个事很简单,说白了不就是优化折叠trace日志吗,有正确的语义结合大trace样本输入验证,就算语义可能还是有点问题但是其实只要对于当前trace已经是没有影响了,那实际意义上就已经是解决了vm导致的混淆问题了
2026-10-2 23:34
0
雪    币: 842
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4 楼
学习
2026-10-2 23:43
0
雪    币: 1366
活跃值: (1900)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5 楼
666
2026-10-3 01:42
0
雪    币: 199
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6 楼
111
2026-10-3 08:57
0
雪    币: 4
活跃值: (1545)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7 楼
看不太懂啊
2026-10-3 09:50
0
雪    币: 1339
活跃值: (1445)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8 楼
tingshow 看不太懂啊
啥地方不懂呢?
2026-10-3 10:55
0
雪    币: 1014
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9 楼
666
2026-10-3 10:58
0
雪    币: 1901
活跃值: (6023)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
10 楼
123
2026-10-3 21:03
0
雪    币: 239
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11 楼
123
2026-10-5 08:40
0
雪    币: 110
活跃值: (9616)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12 楼
123
2026-10-5 09:18
0
雪    币: 2790
活跃值: (7191)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13 楼
感谢分享!
2026-10-6 13:57
0
雪    币: 5524
活跃值: (5864)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14 楼
6
2026-10-6 13:58
0
雪    币: 7779
活跃值: (25285)
能力值: ( LV12,RANK:550 )
在线值:
发帖
回帖
粉丝
15 楼
看看
2026-10-6 14:06
0
雪    币: 1
活跃值: (207)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16 楼
6
2026-10-6 23:19
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17 楼
tql
2026-10-6 23:38
0
雪    币: 69
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18 楼
很赞,学习一下了
2026-10-7 21:13
0
雪    币: 8395
活跃值: (7584)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19 楼
很赞,学习一下了
2026-10-7 21:49
0
雪    币: 1688
活跃值: (1638)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
20 楼
学习
2026-10-8 11:25
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21 楼
666
2026-10-8 11:57
0
雪    币: 1
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22 楼
666
2026-10-8 12:20
0
雪    币: 158
活跃值: (5876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23 楼
感谢分享 
2026-10-8 14:27
0
雪    币: 28
活跃值: (1842)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24 楼
看看隐藏
2026-10-8 14:43
0
雪    币: 31
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25 楼
感谢你的积极参与,期待更多精彩内容!
2026-10-8 14:54
0
游客
登录 | 注册 方可回帖
返回