首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
0
10
[原创]七神分析之还原篇
发表于: 9小时前
200
[原创]七神分析之还原篇
一只鸭子
1
9小时前
200
## 前言 **内容和思路可以看看,有一点点概念就好,也可能还是我对VMP的思考太浅显了,道可道非常道的感觉吧** 上文:[七神VMP真正的分析与还原](https://bbs.kanxue.com/thread-292925.htm) > **作者:** 人生导师 > **日期:** 2026 年 9 月 28 日 > **注意:** 这篇风格属于从头到尾开始写的,我是从创建项目时候就开了这个文档,所以一些踩坑思路就没有删除,只会在后面推翻, --- 从上篇来看,结构可以看出来了,但是还是没还原到程序,得到的是去虚拟化的 trace 日志,而非程序,现在应该想点办法,把我们这个第二代日志再收缩和结构化 这时候就真应该想一下收缩什么东西,怎么收缩,以什么进行收缩,其实可以回想一下程序都有什么 1. 普通代码,也就是 a = b + c 之类的 2. 条件语句,也就是 if{}else{}之类的 3. 循环语句,也就是 for(){} while(){}之类的 4. 函数调用,也就是 @CALL 那些东西 5. 错误语句,理论上来说是有这种的,但是我们是从二进制上来的,所以就不考虑这种了,C 本身不是也没 try 吗? 不是收缩指令,收缩是指“重复的执行行为”,第二代日志 → 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)的循环结构 ``` VREG[0x5] = (VREG[0xa] + VREG[0x9]) | 0x105 VREG[0xa] = (VREG[0xa] + 0x4) | 0x106 VREG[0xc] = sext(*(VREG[0x5] + sext(*(0x7df3ad4f9a)))) | 0x10c VREG[0xd] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f80) + 0x2)))) | 0x10c VREG[0xe] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f78) + 0x2)))) | 0x10c VREG[0xc] = (VREG[0xc] ^ VREG[0xd]) | 0x10c VREG[0xd] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f68) + 0x2)))) | 0x10c VREG[0xe] = ((VREG[0xe] >> 0x19) | (VREG[0xe] << 0x7)) | 0x10d VREG[0xd] = ((VREG[0xd] >> (*(*(0x7df3ad50c0) + 0x2) & 0x1f)) | (VREG[0xd] << (0x20 - (*(*(0x7df3ad50c0) + 0x2) & 0x1f)))) | 0x110 VREG[0xc] = (VREG[0xd] ^ VREG[0xc]) | 0x110 VREG[0xd] = sext(*(VREG[0x5] + -0x18)) | 0x111 VREG[0xd] = (VREG[0xd] ^ VREG[0xe]) | 0x115 VREG[0xe] = ((VREG[0xc] >> (*(*(0x7df3ad5180) + 0x2) & 0x1f)) | (VREG[0xc] << (0x20 - (*(*(0x7df3ad5180) + 0x2) & 0x1f)))) | 0x115 VREG[0xd] = (VREG[0xc] ^ VREG[0xd]) | 0x115 VREG[0xc] = ((VREG[0xc] >> (*(*(0x7df3ad5240) + 0x2) & 0x1f)) | (VREG[0xc] << (0x20 - (*(*(0x7df3ad5240) + 0x2) & 0x1f)))) | 0x118 VREG[0xd] = (VREG[0xe] ^ VREG[0xd]) | 0x118 VREG[0xc] = (VREG[0xc] ^ VREG[0xd]) | 0x11b *(VREG[0x5] + 0x0) = VREG[0xc] | 0x11b ip = ((*(ip) + ((VREG[0xa] == VREG[0xb]) ? 0x0 : -0x18)) + 0x1) | 0x104 VREG[0x5] = (VREG[0xa] + VREG[0x9]) | 0x105 VREG[0xa] = (VREG[0xa] + 0x4) | 0x106 VREG[0xc] = sext(*(VREG[0x5] + sext(*(0x7df3ad4f9a)))) | 0x10c VREG[0xd] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f80) + 0x2)))) | 0x10c VREG[0xe] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f78) + 0x2)))) | 0x10c VREG[0xc] = (VREG[0xc] ^ VREG[0xd]) | 0x10c VREG[0xd] = sext(*(VREG[0x5] + sext(*(*(0x7df3ad4f68) + 0x2)))) | 0x10c VREG[0xe] = ((VREG[0xe] >> 0x19) | (VREG[0xe] << 0x7)) | 0x10d VREG[0xd] = ((VREG[0xd] >> (*(*(0x7df3ad50c0) + 0x2) & 0x1f)) | (VREG[0xd] << (0x20 - (*(*(0x7df3ad50c0) + 0x2) & 0x1f)))) | 0x110 VREG[0xc] = (VREG[0xd] ^ VREG[0xc]) | 0x110 VREG[0xd] = sext(*(VREG[0x5] + -0x18)) | 0x111 VREG[0xd] = (VREG[0xd] ^ VREG[0xe]) | 0x115 VREG[0xe] = ((VREG[0xc] >> (*(*(0x7df3ad5180) + 0x2) & 0x1f)) | (VREG[0xc] << (0x20 - (*(*(0x7df3ad5180) + 0x2) & 0x1f)))) | 0x115 VREG[0xd] = (VREG[0xc] ^ VREG[0xd]) | 0x115 VREG[0xc] = ((VREG[0xc] >> (*(*(0x7df3ad5240) + 0x2) & 0x1f)) | (VREG[0xc] << (0x20 - (*(*(0x7df3ad5240) + 0x2) & 0x1f)))) | 0x118 VREG[0xd] = (VREG[0xe] ^ VREG[0xd]) | 0x118 VREG[0xc] = (VREG[0xc] ^ VREG[0xd]) | 0x11b *(VREG[0x5] + 0x0) = VREG[0xc] | 0x11b ip = ((*(ip) + ((VREG[0xa] == VREG[0xb]) ? 0x0 : -0x18)) + 0x1) | 0x11c ``` 第四种函数调用这个,直接当作默认的基本块处理就好了,调用不是分支,是一个必然会走的指令,只不过跳转到了其他地方然后再回来,并不会影响程序的继续 第五种错误处理就应该看虚拟机有没有实现了,京东的 VMP 是实现了的,错误会有一个小的分支处理然后直接 ret 回去,不过也是防止虚拟机出现意外并不是还原向的错误处理 其实到这里,我在想是不是应该重构一下我的 IR 产出呢?这玩意特太不规律了,现在这个更像是漂亮的日志,不像是能稳定提供下文的 IR ,作用与还原的 IR 其实应该像伪汇编一样,结构化起来,opcode 可以直接获取到,然后就是 operands 的操作顺序了,怎么说呢?搞这东西这么久了,其实也有点感觉了,上面那种产出说明已经可以做到识别操作顺序了,搞一个归一化,opcode dst src(一个或者两个操作数),还有一件事就是复合指令集做了很多步骤,多步怎么像 P-code 一样解析成单步 ```python class IR { opcode:str 来源于P-code的操作数字,难度系数评估需要模型进行 operands:list[str] 根据原有lifter.py内容进行排序,具体可看如下参考 rIP:int(runtimeIP) 运行时vPC计数器 }; *(VREG[0x1] + 0x490) = VREG[0x4] | 0x4 str VREG[0x4], [VREG[0x1], 0x490] <0x4> VREG[0x4] = VREG[0x1] | 0xe mov VREG[0x4], VREG[0x1]<0xe> VREG[0x1b] = *(VREG[0xb] + 0x0) | 0x1d ldr VREG[0x1b], [VREG[0xb]] <0x1d> VREG[0x6] = (VREG[0x7] | VREG[0x6]) | 0x21 orr VREG[0x6], VREG[0x7], VREG[0x6] <0x21> ip = ((*(ip) + ((VREG[0x8] == VREG[0x0]) ? 0x0 : 0x8)) + 0x1) | 0x64 这个还没想好怎么拆解,感觉难点在这啊 ``` 做出来是真出来了,但是吧,有点操蛋的是它真的太汇编了,并且还给我上了一个伪 SSA,看一下输出吧 ``` ldr VREG[0x5], [VREG[0x4], 0x258] <0x10b> and VREG[0x8], VREG[0x14], 0x3 <0x10c> add VREG[0x8], VREG[0x8], VREG[0x15] <0x110> add VREG[0x5], VREG[0x7], VREG[0x5] <0x110> ldr VREG[0x8], [VREG[0x8], 0x61] <0x110> add VREG[0x7], VREG[0x7], -0x1 <0x111> ldr VREG[0x5], [VREG[0x5]] <0x112> eor VREG[0x5], VREG[0x5], VREG[0x8] <0x115> ldr VREG[0x8], [VREG[0x4], 0x190] <0x115> add VREG[0x8], VREG[0x14], VREG[0x8] <0x118> add VREG[0x14], VREG[0x14], 0x1 <0x118> str VREG[0x5], [VREG[0x8]] <0x119> ldr t1, [ip] <0x10a> ; IP - 10 + 1 = 0x10a,也就是恢复了进入这块控制流之前 eq t3, VREG[0x6], VREG[0x14] <0x10a> ; IP指针回退意味着字节码的“重复读取” 也就是控制流的显化 csel t2, t3, 0x0, -0x10 <0x10a> add t0, t1, t2 <0x10a> add ip, t0, 0x1 <0x10a> ldr VREG[0x5], [VREG[0x4], 0x258] <0x10b> and VREG[0x8], VREG[0x14], 0x3 <0x10c> add VREG[0x8], VREG[0x8], VREG[0x15] <0x110> add VREG[0x5], VREG[0x7], VREG[0x5] <0x110> ldr VREG[0x8], [VREG[0x8], 0x61] <0x110> add VREG[0x7], VREG[0x7], -0x1 <0x111> ldr VREG[0x5], [VREG[0x5]] <0x112> eor VREG[0x5], VREG[0x5], VREG[0x8] <0x115> ldr VREG[0x8], [VREG[0x4], 0x190] <0x115> add VREG[0x8], VREG[0x14], VREG[0x8] <0x118> add VREG[0x14], VREG[0x14], 0x1 <0x118> str VREG[0x5], [VREG[0x8]] <0x119> ldr t1, [ip] <0x11a> ; IP并没有后退,说明进入了新的程序部分 eq t3, VREG[0x6], VREG[0x14] <0x11a> csel t2, t3, 0x0, -0x10 <0x11a> add t0, t1, t2 <0x11a> add ip, t0, 0x1 <0x11a> add VREG[0x8], VREG[0x4], 0x248 <0x11b> @CALL(0x2dd60c) ``` 新版本的引入了相对中间值,在全局看可能会发现有很多 t 系列变量,但是这些变量的作用域是本次 IR 产出,边界就是 IP 的变化 IP 实际值,而非理论值因为我们只有 trace,0x119+1=0x11a 这个是最后一步发生的事情,但是它的 IP 在被解析的时候就已经成了 0x11a,所以 IR 也就显示的是 0x11a,哈哈哈哈哈,我想起来一个小妙招,直接在写文件的时候直接输出一条线进行拦截,这样就能明确表示每个 handler 的范围了 根据我们前面说的内容,可以尝试写一下这方面的代码和内容了,首先这玩意肯定是有一个蓝本的,也就是全部解析的内容,不过有必要再次重申我们得到的: > 根据设备 trace 解析得到去虚拟化的 trace,根据去虚拟化的 trace 对虚拟机结构进行恢复 哈哈哈哈哈哈哈,这东西感觉用图算法或者自己硬写缓存+匹配也可以,图算法肯定是方便一些的,甚至我觉得都可以用 shell 脚本进行收缩,我称这个过程为**去 trace 化**,把 trace 给折叠收缩起来,不知道啥时候能来一个灵感让我对 mem 有使用空间啊 --- ## 去 trace 化 去 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 = **上一条的 post**,顺着链走就行,不用猜 - 两条指令 IP 相同 ⇔ 是同一条指令(循环里反复跑的那条) - 上次那段 `IP - 10 + 1 = 0x10a,也就是恢复了进入这块控制流之前` 的注释,用 post 语义重读一遍就通了 - 上次贴的 `ip = ((*(ip) + ((VREG[0xa] == VREG[0xb]) ? 0x0 : -0x18)) + 0x1) | 0x104`,那个 `| 0x104` 是 post 不是当前 IP,式子本身读作 `ip = ip + (cond ? 0 : -0x18) + 1` ### 二、控制线的通式 把控制线的形状全捞出来看,就二十来种模板,核心其实是一个: ```asm ldr t1, [ip] ; 取当前 IP eq t3, a, b ; 判据 csel t2, t3, A, B ; cond ? A : B add t0, t1, t2 add ip, t0, K ; ip = ip + (cond ? A : B) + K ``` - **K 是这条指令占几个 0x30 槽**(编码长度),不是 1 也不是 4 —— 这是上次那个「step=3/4/5/6 是常态」的落点 - **A / B 是两路的相对落点**,`A=0` 就是顺序往下走,`B` 为负就是回边 - 绝对落点 = `pre + K + (A 或 B)` 于是分支、循环、跳转全归一到这一个式子,解出来就是「真枝去哪、假枝去哪」。 ### 三、坑一:IP 根本不是全局空间 我第一版就是这么写的:按 IP 全局去重,同一个 IP 归成一条指令。跑出来一看,1789 个不同的 IP,**1177 个是撞车的** —— 同一个 IP 底下挂着好几种完全不同的指令。 一开始以为是 VM 又在搞混淆,后来才反应过来是我蠢:**每个 VM 函数各有一套 IP 空间,都从 0 起。** 所以「0x79」在这份 trace 里同时是六个不同函数里的六条不相干的指令,跨函数 grep 这个数字必然串味。 那怎么认函数?试过拿操作数表地址聚类 —— 也不行,那是**每个调用点各一份**的副本:同一个函数从两个地方调进来,`ldr t2, [0x7d93c34f70]` 里的地址就换了。实测有两个函数(我编的 func 27 / func 31)999 行里只差这一个地址,差点被它骗过去,以为是两个函数。 最后落到「**同一 IP 上是什么指令**」的指纹聚类。指纹里还得再抹掉一样东西:≥5 位十六进制的常量。因为那些是运行时折叠出来的值,同一条指令每轮跑出来都不一样: ``` func 16 里的: mov VREG[0xf], 0x40a123065ce09c66 func 18 里的: mov VREG[0xf], 0xc8205bcf40c8a922 ``` 阈值卡在 5 位是权衡出来的:帧大小 `-0x4a0`、栈槽偏移 `0x498`、跳转偏移 `0x1b` 这些**结构**常量都在 4 位以内,留着刚好够认函数。 ### 四、坑二:没有栈帧的函数,一个字都不留 VM 的调用约定是:先抬栈,紧接着把返回址存进新帧。 ```asm add VREG[0x1], VREG[0x1], -0x380 ; 抬栈 str VREG[0x3], [VREG[0x1], 0x378] ; 返回址进新帧 ``` 我一开始就靠这个认调用,跑出来 38 个帧、看着挺美。结果发现有 **31 个返回配不上** —— 前面明明没进过这么多帧。 原因是有批函数**压根不抬栈** —— 叶子函数,序言一个字都不写。只能退回来看 IP 断口: > 顺序执行时 post 恒大于 pre(post = pre + 编码长度),post 反而变小,就是换了 IP 空间。 但这个判据我第一版写错了,写成跟「上一条」比,立刻炸出 9000 多个假帧。原因很简单:**回边也是 post 变小。** 判据必须落在**当前这条**上 —— 回边自己一定含 `add ip,`,而调用跳进来那一条是普通指令。改完 69 个帧、零漏配。 ### 五、产出 工具叫 `hy.py`,输出 `prog.txt`,33 个函数、9183 行。长这样: ```asm ; ══ func 4 calls=1 insns=13 0000: ldr VREG[0xb], [VREG[0x9], 0x8] 0001: ldr VREG[0x6], [VREG[0x9]] 0002: mov VREG[0x7], 0x0 0003: sxt VREG[0x9], *(*0x7d23c37270 + 0x2) ldr t3, [ip] eq t5, VREG[0x7], VREG[0x9] csel t4, t5, 0xc, 0x0 add t2, t3, t4 add ip, t2, 0x3 -> if (v7 == *(*0x7d23c37270 + 0x2)) goto 0x0012 L0006: ; ← 循环头 0006: orr VREG[0x5], (VREG[0xb] >> (*(*0x7d23c37300 + 0x2) & 0x3f)), (VREG[0xb] << (0x40 - (*(*0x7d23c37300 + 0x2) & 0x3f))) add VREG[0xb], VREG[0x7], VREG[0x8] 0009: add VREG[0x7], VREG[0x7], sxt(*(*0x7d23c37390 + 0x2)) mov VREG[0xb], 0x3534393231656365 000c: add VREG[0x5], VREG[0x6], VREG[0x5] eor VREG[0xb], VREG[0x5], VREG[0xb] 000f: lsr t0, VREG[0x6], 0x3d lsl t1, VREG[0x6], 0x3 orr VREG[0x5], t0, t1 0010: eor VREG[0x6], VREG[0x5], VREG[0xb] 0011: ldr t1, [ip] eq t3, VREG[0x7], VREG[0x9] csel t2, t3, 0x0, -0xc add t0, t1, t2 add ip, t0, 0x1 -> if (v7 == v9) goto 0x0012 else goto 0x0006 L0012: 0012: str VREG[0xb], [VREG[0xa], 0x8] 0013: str VREG[0x6], [VREG[0xa]] 0014: ret ; → 0x0 ``` 顺手把 VM 自己的取数舞折了。每条 VM 指令都要先把操作数从表里挖出来,`handlers.log` 里最开头那条长这样: ```asm ldr t2, [0x7e23caa3e8] <0xe> ldr t1, [t2, 0x2] <0xe> sxt t0, t1 <0xe> str VREG[0x15], [VREG[0x1], t0] <0xe> ``` 这三行是**编码**不是语义,等价于「这条指令的立即数」,只不过提升器没内存、折不出值,只能原样摊开。顺着临时量的 def 链把它们拼成表达式再代回去,四条并成一条: ```asm 000a: str VREG[0x15], [VREG[0x1], sxt(*(*0x7e23caa3e8 + 0x2))] ``` 中间那串 `*(*0x7e23caa3e8 + 0x2)` 就是**操作数槽的运行时地址**,留着当回查 IDA 的锚点,别丢。 ### 六、没有 ground truth 就自己造一个 还原对不对,得有东西验。能拿来验的只有 trace 自己: > trace 里每一步的**实际后继**,应该落在解出来的两路落点里。 129648 条转移,**对得上 129645 条(99.998%)**,剩下 3 条散在三个函数里各一处。到这个数我才敢说这东西能用了。 ### 七、但也撞了三堵墙 得诚实,这三堵不是我写得烂,是方法本身的天花板: **一、动态 trace 永远只有一条路径。** 164 个跳转落点里 27 个(16%)指向从没执行过的槽位: ```asm 004a: csel t2, t3, 0x2e, 0x0 -> if (v5 == v0) goto 0x0079 ; 0x0079 本次未执行 L004b: ``` 这次 `v5 == v0` 一直是假,那一支一次没走,所以 func 0 里**根本没有 0x79 这条指令**。想要它,只能换输入重跑。**没有一个动态 trace 能给你整个程序。** **二、常量是每次调用解密出来的。** 就是前面说的 func 16 / func 18 —— 同一个函数调两次,999 条指令只差 3 条,全在 `mov VREG[0xf], 0x40a1...` 这种地方。**这批值不在字节码里,在密钥流里**,把 VM 拆得再干净也拿不到。 **三、操作数槽还是没解出值。** `*(*0x7e23d2a470 + 0x2)` 这种,提升器没内存。 ### 八、不过,认出了 SM3 给工具加了个 `--consts`,把每个函数的 32 位常量全列出来: ``` func 7 calls=2 0x163138aa 0x172442d7 0x7380166f func 9 calls=23 0x79cc4519 0x7a879d8a func 11 calls=1 0x20220420 func 26 calls=2 0x0e1e2c17 0x3aa452f7 ``` `0x79cc4519` 连着 16 个、`0x7a879d8a` 连着 48 个 —— **这是 SM3 的轮常量 T_j**(j≤15 用前者,j≥16 用后者),没有别的常见算法用这一对。func 7 那边还有 SM3 的 IV。func 9 调了 23 次,23 个 64 字节块 ≈ 1472 字节的消息,跟 IV 一起看,这条线是闭环的。 **但这里有个坑,差点让我漏掉。** 32 位常量在 VM 里是**拆成两半拼**的: ```asm 0001: mov VREG[0x5], 0x79cc0000 ; 高半,先占住 mov VREG[0x7], 0x0 0004: mov VREG[0xa], 0x40 0005: add VREG[0x6], VREG[0x1], 0x20 ... 0010: orr VREG[0xb], VREG[0x5], 0x4519 ; 隔了十几条,低半才补上 ; → 0x79cc0000 | 0x4519 = 0x79cc4519 ``` 所以你在日志里直接 grep `79cc4519` 是 **0 命中**,手翻根本翻不到。`--consts` 就是跟踪寄存器状态把两半合回去的。 ### 九、一点感想 去 trace 化到此算是做完了:566,691 次 handler 执行压成 33 个函数、9183 行,压缩了六十倍。但说句实话,**「直接还原出算法」这个期待得往回调一点**。 还原不是攻击本身,是**仪器**。它的价值不是「把算法读出来」,是把不透明的一坨变成**能认出来的东西**。而认出来这件事,不需要读懂每一行 —— SM3 是靠常量表认出来的,不是靠读懂那 23 轮压缩。 真正能出结果的地方通常是这三个,哪个都不需要你把 VM 读懂: 1. **边界** —— `prog.txt` 里那 316 个 `native 0x2d1d5c` 就是 VM 和 native 的交界,数据从哪进、往哪出全在那儿。SM3 认出来了,下一个问题就变成「它的输入谁给的、摘要谁拿走了」,这个能查;「SM3 每轮怎么转」查了也没用 2. **侧信道** —— 白盒表、DFA、时序,直接抠密钥,绕开实现 3. **反推调用方** —— 调用关系已经出来了,很多时候业务逻辑压根不在 VM 里,VM 只是被当成一个很贵的加密盒子 下一步就是把 33 个函数的常量全过一遍找别的指纹(AES 的 S 盒、SHA/MD5 的 IV、CRC 表),再给那 316 个 `native` 调用做归属。func 11 的 `0x20220420`(这个长得像日期,2022-04-20)和 func 26 的 `0x0e1e2c17` / `0x3aa452f7` 还没认出来。 作者最后:我还原 VMP 是完全没想过还原出来是什么,为什么要还原,也就是说我是盲目追求的 VMP 还原,但是做了这么多其实我真不推荐搞还原,自己耐心下来,把自己 trace 工具修修多想想怎么通过内存、指令、IO 关系之间进行算法还原与推进,我现在还原出来的这个东西我自己都觉得模糊,其实就算是静态还原也做不到 JSVMP 那么好的还原效果(京东的VMP简单一些,还原出来也还是模糊),JSVMP 可以做到完整的代码逻辑以及可替换的代码,但是二进制还原出来的是模糊的逻辑,其他没了,VMP 的还原太吃精力,如果仅仅是研究技术,还是浅藏辄止为好
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
10
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_rjdrqvpa
感谢你的积极参与,期待更多精彩内容!
2小时前
顽劣
感谢你分享这么好的资源!
3小时前
mb_bvvcoitr
+1
感谢你的积极参与,期待更多精彩内容!
6小时前
KooJiSung
这个讨论对我很有帮助,谢谢!
8小时前
mb_asiwnxyv
感谢你的贡献,论坛因你而更加精彩!
8小时前
huangyalei
非常支持你的观点!
8小时前
逆向小玖
你的分享对大家帮助很大,非常感谢!
8小时前
loveqiao
谢谢你的细致分析,受益匪浅!
9小时前
wx_Mark_449
为你点赞!
9小时前
NG最最最
你的分享对大家帮助很大,非常感谢!
9小时前
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
mb_ldbucrik
雪 币:
6
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
700
粉丝
7
关注
私信
mb_ldbucrik
2
楼
666
9小时前
0
安卓逆向test
雪 币:
499
能力值:
( LV1,RANK:0 )
在线值:
发帖
10
回帖
48
粉丝
65
关注
私信
安卓逆向test
3
楼
其实这个事很简单,说白了不就是优化折叠trace日志吗,有正确的语义结合大trace样本输入验证,就算语义可能还是有点问题但是其实只要对于当前trace已经是没有影响了,那实际意义上就已经是解决了vm导致的混淆问题了
6小时前
0
啊你好哇123
雪 币:
765
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
370
粉丝
0
关注
私信
啊你好哇123
4
楼
学习
6小时前
0
mb_ykavguun
雪 币:
1271
活跃值:
(1790)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
165
粉丝
0
关注
私信
mb_ykavguun
5
楼
666
4小时前
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
一只鸭子
1
16
发帖
58
回帖
60
RANK
关注
私信
他的文章
[原创]七神分析之还原篇
166
[原创]基于 Trace 的 VMP 还原实践文档
2760
[讨论]逆向死没死
653
[原创] 七神VMP真正的分析与还原
11069
[原创] 用 P-code 做 VMP 还原:一份实战向的 IR 入门
3421
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部