首页
社区
课程
招聘
[原创]AVMP初见(纯AI)
发表于: 13小时前 605

[原创]AVMP初见(纯AI)

13小时前
605

这篇文章记录的是一次闲鱼 Android 端 x-sign 参数算法的还原过程。样本里的核心逻辑不在普通 Java 层,也不直接以清晰的 native 函数形式出现,而是被放进 AVMP 虚拟机执行。native 层能看到大量解释器、handler、helper 和 descriptor 调用,但真正决定参数结果的数据流藏在 VMCode 里。

本文使用的样本环境大致如下:

分析过程中同时使用了静态和动态证据:静态部分包括 handler table 恢复、VMCode dump、opcode 语义归纳、VMCode 到 IR 翻译;动态部分包括参数捕获、指令级 trace、关键 VM slot 和内存状态快照。文章里的地址、index、opcode 和中间值都来自这些分析产物。

本文不会完整展开所有业务字段,也不会把每个 helper 都逆完。重点放在一个更通用的问题上:面对 AVMP 这类虚拟机保护,怎么从“看不懂的 native dispatch”推进到“能切片、能验证、能还原算法的数据流”。x-sign 只是案例,用来说明 VMCode 转 IR 对参数算法分析到底有什么实际帮助。

在分析带虚拟机保护的参数算法时,最容易陷入的误区是:一直跟 native 汇编,以为把解释器看完就等于把算法看完。实际上,解释器只是执行引擎,真正的参数算法通常在自定义 VMCode 里。本文后面会先介绍 AVMP 的 VMCode 设计,再说明 VMCode 如何转成 IR,最后用 x-sign 的 MD5、SHA1、XOR、Base64 链路说明 IR 对算法还原的贡献。

在普通 native 算法里,我们通常找入口函数、看参数、看内存读写、跟调用链,最后把核心逻辑还原成伪代码。

但 AVMP 保护后,真实逻辑会被拆成几层:

如果只看 native,会看到大量 handler:

这些 handler 不是业务算法本身,只是 VM 指令的实现。直接跟这些 handler,相当于在 CPU 层分析每条机器指令,而不是看编译后的函数逻辑。

所以更有效的路线是:

本次样本里,AVMP launcher 会调用真正的解释器函数。解释器拿到几个关键参数:

解释器的核心流程可以抽象成:

这里有几个重要信息:

这一步的目标不是完全理解所有 handler,而是确认“解释器怎么取指、怎么分发、VMCode 从哪里读”。

样本里 handler table 位于:

但直接看 ELF 文件时,这个区域是零填充。原因是 table 项不是静态写死在文件里的绝对地址,而是通过 .rela.dyn 重定位生成。

也就是说,静态恢复时不能只读文件里的 table bytes,而要解析 relocation:

恢复后可以得到:

这个映射非常关键。后面所有 VMCode decode 都依赖它:

为了避免静态误判,需要做一次运行时交叉验证:观察解释器里真实使用的 table base,比如 X23 = module_base + 0x2ADC60,并确认运行时分发的 handler 和 RELA 恢复出来的 handler 一致。

通过 runtime dump 和 handler 访问模式,可以确认当前 VMCode 指令格式大致如下:

同一个 imm 字段在不同 opcode 里有不同解释:

例如:

看一个真实 VMCode 记录会更直观。x-sign 最终输出点 0x453f 的原始 16 字节如下:

按上面的结构拆开:

结合 opcode 0x242 = st64_base_imm,这条记录就能翻译成:

再看前一条 0x453e

两条连起来就是:

也就是从 VM frame 里取出输出对象指针,再把 %r8 指向的 x-sign 对象写到输出结构体的 +0x20 字段。这个例子说明,16 字节记录不是抽象猜测,而是可以逐字节拆出来,并且能和最终业务语义对应上。

这里最容易踩坑的是跳转。很多 branch opcode 不是跳到字节偏移,而是跳到 VM 指令索引:

如果把 delta 当字节偏移,CFG 会完全错。

opcode 语义可以分三层确认。

第一层是 handler 短路径。很多 handler 非常短,语义可以直接从汇编看出来。例如 st64_base_imm

可以翻成:

第二层是 handler 家族。比如一组 load/store opcode 结构类似,只是访问宽度不同:

这种可以先确认几个代表 handler,再批量归纳。

第三层是 helper 型 opcode。有些 handler 本身只做参数搬运,然后调用一个复杂 helper:

这类不能只看 handler,要继续看 helper。比如某组 raw-FP128 opcode:

这里故意命名成 rawfp128,而不是直接叫 IEEE f128。原因是静态证据能证明它是扩展浮点样式的 raw representation 和 arithmetic helper,但不能随便声称它就是某个标准 ABI 类型。

拿到 opcode 语义后,第一步可以先输出伪汇编:

这一步已经比 native handler 清楚很多,但还不够。伪汇编适合阅读,不适合自动化分析。比如想反向追 %r8 从哪里来、哪些 block 写过 x-sign 对象、哪些循环是 XOR loop,就需要更结构化的表示。

所以第二步是转成 IR。

这里的 IR 不是为了做编译优化,而是为了逆向分析。目标是:

VM slot 统一表示成:

raw-FP / SIMD 类 slot 统一表示成:

这样可以把 ARM64 寄存器噪声去掉,只保留 VM 层变量。

不同宽度的 load/store 统一成:

如果是符号扩展,也要显式保留:

否则后续判断有符号比较、长度检查时会出错。

算术统一成表达式:

这样一眼就能看出:

如果再接一个 xor 常量,就可以识别成:

branch 和 jump 统一成:

AVMP 里经常有 bytecode 子过程,调用和返回不一定是普通 call/ret。对于这种情况,可以在 CFG 构建时做保守近似:对某些直接 jump 同时保留目标边和 fallthrough 边,用来覆盖 VMCode 子过程返回路径。

这不适合生成最终伪代码,但足够做“目标 store 是否可达”“某个窗口附近有哪些 producer”。

不是所有东西都应该强行还原。对未完全解释的复杂调用,IR 保留 side effect:

这样做有两个好处:

本次 x-sign 的完整 AVMP 镜像大小是 0x4e500,历史确认的 x-sign 入口是:

最终输出点位于:

转成 IR 后是:

这条指令的含义很明确:

也就是说,x-sign 最终不是某个 native 函数直接返回的字符串,而是 VM 内部构造出的对象,被写入输出结构体偏移 0x20

从这条 store 反向看,可以得到:

这说明:

再把同一位置和完整 trace 对上,可以看到真实运行时值:

这说明静态 IR 里的:

在该次 trace 中对应真实值:

继续往前看这个 xsign_object 的字段,还能看到:

也就是说,0x453f 不是随便一个对象赋值,而是最终把长度为 0x66 的 x-sign payload 对象挂到输出结果上。

这类信息如果直接从 native handler 跟,会被大量 dispatch 淹没;在 IR 上则是几行数据流。

同一个 0x4e500 镜像,在不同捕获里可能有不同 entry。例如:

这两个 entry 对应同一个镜像 hash,但静态可达结果不同:

所以分析 AVMP 时不能只说“这个 image 是 x-sign”,还要说明:

否则很容易出现“同一个 VMCode 镜像,为什么这次切不到输出点”的误判。

IR 的另一个价值是识别算法形态。

比如某个 seed 变换在 IR 中长这样:

读成高级语义就是:

再比如一个字节 XOR loop:

在动态 trace 里补上具体值后,就可以确认:

本次 x-sign trace 里有一个更具体的 XOR run。0x78c50:bb_464d 这段循环一共命中了 6 组,其中最后一组直接参与最终 Base64 输入:

这个 a9 4e 不是静态常量直接拷贝出来的,而是前面 0x468f..0x4696 的 key setup 算出来的。trace 中两个字节的计算例子如下:

最终 Base64 前的 69 字节二进制状态是:

它的结构可以拆成:

标准 Base64 后得到 92 字节 tail:

前面再拼固定/半固定 header:

所以该次 trace 的最终 payload 是:

这里还可以再往上游补一段:tmp21 里的 md5(input) 来源。

早期我们在 0x4e500 的 x-sign 路径里定位到一段 MD5-like routine。静态上它有非常标准的 MD5 特征:

动态验证时,在 AVMP index 0x4502 捕获 MD5 输入,在 0x4503 捕获 digest 输出,再在 0x4514/0x4520 附近捕获 tmp21。其中一个样本是:

这个 input 也不是随机二进制。捕获到的 MD5 输入是 100% 可打印字符串,开头和 doCommandNative(70102) 的 Java 参数 arg[1] 是同一类 canonical signing string:

后面还会继续拼接一段可打印的 security material,所以关系更准确地说是:

不同请求、不同时间戳会得到不同的 digest。上面 e731... 是 MD5 封装验证样本;后面完整 x-sign 链路里的 122eff... 是另一条 trace 的 digest16。值不同不影响数据流关系。

把 MD5 来源、tmp21 封装、下游 XOR/Base64 串起来,当前 trace 中从参数材料到 x-sign 的算法流程可以写成:

其中几个关键具体值是:

这类 loop 在 native handler 层看起来是大量 ldr/str/br 的解释器噪声;在 IR 层就是普通的字节循环。

静态 IR 里还可以看到一些 hash-like 或 base64-like 的块:

这些信息很有用,但不能直接等价为“它就是当前 x-sign 主路径”。原因是一个 AVMP 镜像可能包含多段通用算法实现、工具函数、备用路径或不同命令共享的逻辑。

正确做法是:

在本次分析中,IR 帮助把“看起来像标准算法的块”和“当前 x-sign 实际执行路径”分开。这样可以避免因为看到 SHA/MD5 常量就过早下结论。

早期 decoder 里大量 opcode 只能叫:

这种情况下,IR 仍然能跑,但切片和算法识别会有很多断点。随着 opcode 语义补全,IR 质量会明显提升。

本次处理后的一个结果是:

并且剩余 opaque 全是单例。更重要的是:

这意味着 x-sign 相关路径的 opcode 层面已经基本打通。后面真正的难点转为:

换句话说,VMCode 转 IR 不是终点,但它把问题从“指令级看不懂”推进到了“数据流和对象语义还原”。

结合这次 x-sign 分析,可以整理出一套相对通用的流程。

目标是找到:

如果看到类似:

基本可以判断进入了 VM 解释器。

dump 时至少记录:

同一个 image 可能有多个 entry,所以不要只按 size 去重,最好按 image hash 去重,同时保留 entry 列表。

如果 table 在文件里不是直接地址,要看 relocation。

输出最好长这样:

并用运行时分发做一次校验。

优先级可以这样排:

不要为了清零 opaque 去分析所有单例。只有落在目标路径上的单例才值得优先处理。

IR 不必一步到 SSA。先做到:

这些已经足够支持很多静态分析。


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 46
支持
分享
最新回复 (20)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
1
11小时前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
1
11小时前
0
雪    币: 158
活跃值: (4746)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习!!!
11小时前
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
 学习!!! 
11小时前
0
雪    币: 1606
活跃值: (7428)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
6
1
11小时前
0
雪    币: 176
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
6+666
10小时前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
666
9小时前
0
雪    币: 76
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9

看看

最后于 9小时前 被温泉划水鱼编辑 ,原因:
9小时前
0
雪    币: 2778
活跃值: (3052)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
6666
9小时前
0
雪    币: 109
活跃值: (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
666
8小时前
0
雪    币: 104
活跃值: (8582)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
tql
8小时前
0
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
666
7小时前
0
雪    币: 292
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
666666666
7小时前
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
感谢分享
5小时前
0
雪    币: 9874
活跃值: (6284)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
很好的分析文章
5小时前
0
雪    币: 2957
活跃值: (2390)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
17
666
4小时前
0
雪    币: 0
活跃值: (450)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
11111111
4小时前
0
雪    币: 4183
活跃值: (6877)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
19
感谢分享
4小时前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
感谢分享
4小时前
0
雪    币: 3659
活跃值: (4799)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
学习
3小时前
0
游客
登录 | 注册 方可回帖
返回