吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1312|回复: 13
上一主题 下一主题
收起左侧

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

[复制链接]
跳转到指定楼层
楼主
rsds0duck 发表于 2026-10-2 21:47 回帖奖励

前言

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

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

作者: 人生导师
日期: 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 一样解析成单步

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

二、控制线的通式

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

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 的调用约定是:先抬栈,紧接着把返回址存进新帧。

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 行。长这样:

; ══ 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 里最开头那条长这样:

ldr   t2, [0x7e23caa3e8]  <0xe>
ldr   t1, [t2, 0x2]       <0xe>
sxt   t0, t1              <0xe>
str   VREG[0x15], [VREG[0x1], t0]  <0xe>

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

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%)指向从没执行过的槽位:

    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 里是拆成两半拼的:

    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 的还原太吃精力,如果仅仅是研究技术,还是浅藏辄止为好

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
唐小样儿 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
xixicoco 发表于 2026-10-3 00:59
大佬牛逼,你的每一篇我的读的
3#
Ellerrina 发表于 2026-10-3 02:01
4#
alexcc555 发表于 2026-10-3 07:55
5#
wg521125 发表于 2026-10-3 08:21

虽然搞不懂,发给ai试试哈
6#
 楼主| rsds0duck 发表于 2026-10-3 10:55 |楼主
wg521125 发表于 2026-10-3 08:21
虽然搞不懂,发给ai试试哈

上下两篇都扔进去,要不模型讲不明白
7#
Lyk626799 发表于 2026-10-3 11:21
大佬肯定是牛逼的,虽然我看不懂,但是我会丢给AI解析,看看能不能学习到大佬分享的经验
8#
zhuan 发表于 2026-10-3 15:34
谢谢分享好思路。
9#
小嫩 发表于 2026-10-3 19:47
感谢分析啊.我自己看不懂丢给AII看看
10#
LarryK 发表于 2026-10-3 21:24
谢谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-16 21:01

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表