首页
社区
课程
招聘
[原创]ZKM控制流反混淆分析
发表于: 3天前 1024

[原创]ZKM控制流反混淆分析

3天前
1024

Zelix KlassMaster 是 Java 字节码混淆器中最为强悍的混淆器,而令 ZKM 引以为傲的混淆就是它的控制流混淆。

以下演示用的是 ASM 库,也就是 Java 字节码操作与分析框架。

在正常 Java 方法中,控制流一般是树状或者块状的。

比如原始逻辑长这样:

字节码里虽然也是跳转,但结构还算清楚。

控制流平坦化会把这种结构打散,搞进一个调度循环里。逻辑大概会变成这样:

它的执行顺序被打散了,都变成一个个 case,通过 state 状态变量决定下一步跳到哪里。

ZKM 不只用一种平坦化形式。它可能是 switch 调度,也可能是各种 ifgototry-catch 混在一起。

ZKM 会用这些东西混淆:

反编译器会以为某些死代码能执行,会把简单逻辑还原成奇怪的嵌套判断,甚至直接反编译失败。

逻辑可以这样写:

循环是重点。

因为你删掉一个假跳转之后,后面可能会暴露出新的死代码。删掉死代码之后,又可能出现新的常量分支。只跑一遍是不够的。

ZKM 经常会塞一些假的异常处理器。
比如这样:

或者:

这种傻子 handler 是拿来污染控制流图的。

拆法就是遍历 tryCatchBlocks,找到 handler 的 LabelNode 后面的真实指令。如果是经典的 GETSTATIC + ATHROW,就把 try-catch 记录和对应指令删掉。

删的别太激进。正常代码里也可能有 catch 后重新抛出异常 的情况。如果可以确定是假的就删,不能确定就留着。

ZKM 控制流里经常能看到这种结构:

也可能是:

这类分支很多是控制流垃圾。识别 模式 直接就:

可以写成这样:

这个是比较好处理的。

常见假分支:

或者:

但是不能看到 iload + ifeq 就删。正常判断里一堆这种写法。

比较靠谱的判断是看 ILOAD 执行前操作数栈是不是空的。

如果操作数栈不是空的,它突然来个 iload + ifeq,这种就很像 ZKM 的假栈干扰。

ASM 可以用 Analyzer 计算 Frame

ZKM 用 invokedynamic 生成某种状态变量,然后拿这个变量到处跳。

大概长这样:

可以先扫出这种状态变量:

后面遇到 iload x / ifeq 的时候,如果 x 是这种状态变量,就可以考虑清掉。

但要加副作用判断。

因为有些分支包住的是真实代码,比如 PUTSTATICINVOKEVIRTUALATHROW。这种删错了,程序直接寄。

实际处理可以这样:

这就比无脑删多了。

对象分支也会出现类似模式:

或者:

处理思路和 int 分支差不多:

它不只喜欢玩 int,也会拿 对象空值检查 当假边用。

有时候 ZKM 会把一个简单 goto 拆成两个互相抵消的判断。

类似:

如果两个判断用的是同一个变量,而且 opcode 互为反向条件,那它其实就是绕了一圈。

可以改成:

判断函数:

但是有个重要条件:两个跳转之间不能夹真实代码。

如果中间有真实指令,不要改。

当你删掉一堆假分支以后,会留下这种东西:

x 后面已经没人读了。

这时候不能直接删 istore x,因为栈上还有 invokedynamic 的返回值。直接删会导致操作数栈不平衡。

正确做法是:

代码:

清理到后面,经常会出现这种分支:

这是永远跳。

或者:

这是永远不跳。

如果一定跳,就插入 GOTO target
如果一定不跳,就直接删掉常量和条件跳转。

控制流清完以后,方法里会残留大量不可达代码。

比如:

这时候就可以扫一遍。

思路:

这里不要乱删 LabelNodeFrameNodeLineNumberNode。尤其是 label,有些是 跳转目标,有些是 try-catch 边界,删错就损坏 class 文件。


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

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回