首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
18
15
[原创] vmp3.9.4 手动还原cmp/jne下的handler序列
发表于: 2026-4-30 00:53
14597
[原创] vmp3.9.4 手动还原cmp/jne下的handler序列
LingMo0412
2
2026-4-30 00:53
14597
# 前言 这几个月学习vmp以来一直没尝试过自己亲手还原vmp,所以就想着试试反正这几天也没课。花了一整天时间,从上午到晚上总算完成了,工作量虽然大,但也是一次很好的学习经历。 # 命名规范 我还不知道要遵循什么命名规范,比如VR寄存器要不要带q\d\w\b后缀来表示使用到的虚拟寄存器的宽度,运算宽度是用字节数(1/2/4/8)来表示还是用位数(8/16/32/64)来表示,专属寄存器如指示字节码位置的是叫 vpc 还是 vip 等等,后面我觉得各自按自己的喜好来规定就可以了。 在这里,我把指示字节码位置的专属寄存器叫 `vpc`、指示虚拟堆栈的叫 `vsp`、指示当前handler位置的叫 `vbase`、指示滚动密钥的叫 `vkey`。用来寻址虚拟寄存器的就是 `rsp`,它就不用起什么名了,但真要取的话我就叫它 `vreg`。 # 要保护的代码  **r10d** 是我们的输入,如果输入值为 `3735928559`(0xdeadbeef),**rax** 的值就会变成 `0xdeadbeef`。 # 保护选项   随便提一下,根据我的经验,**10%** 的复杂性可以让一个复合 handler 有最多两个简单 handler,而 **20%** 就是最多三个。因为我样本量不够,也不能真的保证 **10%** 的复杂性就是最多两个简单 handler,但至少在最后的分析结果来看,**10%** 的复杂性里,一个复合 handler 就是最多只有两个简单 handler。 **100%** 复杂性的话,就是最多十个,下面是我分析的其他100%复杂性样本的 handler 序列,可以简单看一下: ``` [0] vPopReg64 VR22; vPopReg64 VR20; vPopReg64 VR5; vPopReg64 VR18; vPopReg64 VR24; vPopReg64 VR1; vPopReg64 VR14 [1] vPopReg64 VR12; vPopReg64 VR0; vPopReg64 VR21; vPopReg64 VR4; vPopReg64 VR6; vPopReg64 VR19; vPopReg64 VR17; vPopReg64 VR15; vPopReg64 VR2; vPopReg64 VR13 [2] vPushImm64 0x140001188; vPushReg64 VR4; vAdd64; vPopReg64 VR25; vPushVsp64 [3] vPopReg64 VR26; vPushReg64 VR15; vPopReg64 VR27; vPushReg64 VR6; vPopReg64 VR28; vPushReg64 VR24; vPopReg64 VR29; vPushReg64 VR21; vPopReg64 VR30; vPushImm64 0x140000000 [4] vPushReg64 VR4; vAdd64; vReadMemDs64; vPopReg64 VR16 [5] vPushImm64 0x1400011c0 ... ``` # 采集跟踪 加壳完成后,我们就用 x64dbg 对被vm的代码进行跟踪采集,输入正确的跟踪文件是 `success.trace64`,输入错误的跟踪文件是 `failed.trace64`   # 保留有效指令 如果直接通过看跟踪来一个个分析 handler 的话,如此多的混淆代码会让人看的头晕眼花的,因此我们需要留下真正有意义的代码再进行分析 首先是从追踪里分割 handler: ```py handlers: list[list[Supertrace.InstructionRecord]] = [] handler: list[Supertrace.InstructionRecord] = [] for i, ins in enumerate(record): if (i + 1 >= len(record)): nextIns = None else: nextIns = record[i + 1] ttins = triton.Instruction() ttins.setAddress(ins.ins_address) ttins.setOpcode(ins.bytes) ctx.disassembly(ttins) ins.ttins = ttins for memAcc in ins.mem_accs: if (memAcc.type == Supertrace.AccessType.READ and (vmpsec_begin <= memAcc.acc_address <= vmpsec_end)): ctx.taintMemory(triton.MemoryAccess(memAcc.acc_address, memAcc.acc_size)) # 污染来自VM区块的字节码(确保不会把混淆间接跳转也给识别进去) handler.append(ins) compatibleProcessing(ctx, ttins, ins, nextIns, True, False) # 执行指令 if (checkIndirectIns(ttins) and (nextIns is not None) and ttins.isTainted()): handlers.append(handler.copy()) handler.clear() ``` 然后就是关键代码了,我们主要靠这段代码的输出来做后续的工作: ```py def getMask(n): if (n == 1): return 0xff elif (n == 2): return 0xffff elif (n == 4): return 0xffffffff elif (n == 8): return 0xffffffffffffffff else: raise ValueError for i, handler in enumerate(handlers): firstIns = handler[0] tmpctx = triton.TritonContext() tmpctx.setArchitecture(triton.ARCH.X86_64) tmpctx.setMode(triton.MODE.ALIGNED_MEMORY, True) tmpctx.setMode(triton.MODE.AST_OPTIMIZATIONS, True) tmpctx.setMode(triton.MODE.CONSTANT_FOLDING, True) tmpctx.setMode(triton.MODE.TAINT_THROUGH_POINTERS, True) initTritonCtxEnv(tmpctx, firstIns, main_thread.teb) # 需要自己提前识别专属寄存器是哪些,如果有专属寄存器的变更还得自己判断并修正 if (firstIns.dbg_id >= 0x35e9): continue vspRegName = "r8" vpcRegName = "rdi" vkeyRegName = "r9" vbaseRegName = "rbx" # vspRegName = "rsi" # vpcRegName = "rbx" # vkeyRegName = "rdi" # vbaseRegName = "rbp" vspReg = getattr(tmpctx.registers, vspRegName) vpcReg = getattr(tmpctx.registers, vpcRegName) vkeyReg = getattr(tmpctx.registers, vkeyRegName) vbaseReg = getattr(tmpctx.registers, vbaseRegName) tmpctx.taintRegister(vspReg) tmpctx.taintRegister(vpcReg) tmpctx.taintRegister(vkeyReg) tmpctx.taintRegister(vbaseReg) initVsp = tmpctx.getConcreteRegisterValue(vspReg) initRsp = firstIns.reg_dump64.regcontext.csp print(f"--[{i}]---------- {hex(firstIns.dbg_id)} addr: {hex(firstIns.ins_address)} --------------") print(f"|\tvsp[{vspRegName}]: {hex(tmpctx.getConcreteRegisterValue(vspReg))[2:]} vpc[{vpcRegName}]: {hex(tmpctx.getConcreteRegisterValue(vpcReg))[2:]} rsp: {hex(firstIns.reg_dump64.regcontext.csp)[2:]} vkey[{vkeyRegName}]: {hex(tmpctx.getConcreteRegisterValue(vkeyReg))[2:]} vbase[{vbaseRegName}]: {hex(tmpctx.getConcreteRegisterValue(vbaseReg))[2:]}") memInfo = [] for ii, ins in enumerate(handler): if (ii + 1 >= len(handler)): nextIns = None else: nextIns = handler[ii + 1] ttins = triton.Instruction() ttins.setAddress(ins.ins_address) ttins.setOpcode(ins.bytes) for memAcc in ins.mem_accs: if (memAcc.type != Supertrace.AccessType.READ): continue for i in range(memAcc.acc_size): memi = triton.MemoryAccess(memAcc.acc_address + i, triton.CPUSIZE.BYTE) if (not tmpctx.isConcreteMemoryValueDefined(memi)): oldby = (memAcc.old_data >> (i * 8)) & 0xFF tmpctx.setConcreteMemoryValue(memi, oldby) tmpctx.processing(ttins) insType = ttins.getType() if (insType == triton.OPCODE.X86.CPUID): tmpctx.taintRegister(tmpctx.registers.rax) tmpctx.taintRegister(tmpctx.registers.rbx) tmpctx.taintRegister(tmpctx.registers.rcx) tmpctx.taintRegister(tmpctx.registers.rdx) elif (insType == triton.OPCODE.X86.RDTSC): tmpctx.taintRegister(tmpctx.registers.rax) tmpctx.taintRegister(tmpctx.registers.rdx) elif (insType == triton.OPCODE.X86.RDTSCP): tmpctx.taintRegister(tmpctx.registers.rax) tmpctx.taintRegister(tmpctx.registers.rcx) tmpctx.taintRegister(tmpctx.registers.rdx) elif (insType == triton.OPCODE.X86.RDSEED or insType == triton.OPCODE.X86.RDRAND): opreg: triton.Register = ttins.getOperands()[0] tmpctx.taintRegister(opreg) show = False if (insType == triton.OPCODE.X86.LEA and not ttins.isTainted()): # 与rsp有关的地址lea指令寻址 readRegs = ttins.getReadRegisters() for ttreg, ast in readRegs: if (tmpctx.getParentRegister(ttreg).getId() == triton.REG.X86_64.RSP): regop: triton.Register = ttins.getOperands()[0] show = True if (tmpctx.getParentRegister(regop).getId() != triton.REG.X86_64.RSP): # 比如 lea rdx, qword ptr ss:[rsp+0x20] tmpctx.taintRegister(regop) break if (ttins.isTainted() or show or insType == triton.OPCODE.X86.CPUID or insType == triton.OPCODE.X86.RDTSC or insType == triton.OPCODE.X86.RDTSCP or insType == triton.OPCODE.X86.RDSEED or insType == triton.OPCODE.X86.RDRAND): asm = str(ttins) asm = asm.replace(" + riz", "") # 针对内存操作数,删除'riz'的字眼 for memacc in ins.mem_accs: # 读取了vm字节码就在前面空一行 if (vmpsec_begin <= memacc.acc_address <= vmpsec_end): print("|") changed_vspOrvpc = " " for ttreg, astNode in ttins.getWrittenRegisters(): # 修改了vsp、vpc、vbase或vkey就在前面加个*号 if (tmpctx.getParentRegister(ttreg).getName() in [vspRegName, vpcRegName, vbaseRegName, vkeyRegName]): changed_vspOrvpc = "*" # 跳转下一个handler的代码部分也空行 if (ii == len(handler) - 1 and ttins.getType() == triton.OPCODE.X86.JMP): # "jmp reg"形式 print(f"|") elif (ii == len(handler) - 2 and ttins.getType() == triton.OPCODE.X86.MOV and handler[ii + 1].ttins.getType() == triton.OPCODE.X86.RET): # "mov [rsp], reg; ret"形式 print(f"|") print(f"|{changed_vspOrvpc}{hex(ins.dbg_id)} {asm}") for ttreg, astNode in ttins.getWrittenRegisters(): # 修改了vkey就空一行 if (tmpctx.getParentRegister(ttreg).getName() in [vkeyRegName]): print("|") for memacc in ins.mem_accs: mask = getMask(memacc.acc_size) vrInfo = "" VRLIMIT = 0x100 # 该值需从checkVsp的修正的ecx值里获取 if ((initRsp + VRLIMIT > memacc.acc_address >= initRsp) and (memacc.acc_address & 7) == 0): # 可能的虚拟寄存器寻址 off = (memacc.acc_address - initRsp) vrN = off // 8 vrInfo = f" [+{hex(off)} = VR{vrN}]" vspInfo = "" # 判断内存读写的地址是否位于虚拟寄存器以下 和 栈内存的底部(防止把类似0x10000这些地址也解析成了虚拟栈读写) if ((stackMemArea.addr + stackMemArea.size) >= memacc.acc_address >= (initRsp + VRLIMIT)): # 可能的虚拟栈寻址 off = memacc.acc_address - initVsp # 偏移都基于handler首部时刻的vsp开始计算 if (off == 0): vspInfo = f" vsp[+0x0]" elif (off > 0): vspInfo = f" vsp[+{hex(off)}]" else: # off < 0 vspInfo = f" vsp[{hex(off)}]" if (memacc.type == Supertrace.AccessType.READ): memInfo.append(f"| {hex(ins.dbg_id)} [R]{vrInfo}{vspInfo} {hex(memacc.acc_address)[2:]}:\t{hex(memacc.new_data & mask)[2:]}") elif (memacc.type == Supertrace.AccessType.WRITE): memInfo.append(f"| {hex(ins.dbg_id)} [W]{vrInfo}{vspInfo} {hex(memacc.acc_address)[2:]}:\t{hex(memacc.old_data & mask)[2:]} -> {hex(memacc.new_data & mask)[2:]}") if (len(memInfo) > 0): print("--------------------------") for memI in memInfo: print(memI) print() print() ``` 代码的思想就是污染所有与vm虚拟机机制有关的代码,包括专属寄存器 (`vpc`、`vsp`、`vkey`、`vbase`),以及可能的特殊指令(比如 `cpuid`、`rdtsc`),污点规则(无论是对有意义代码的保留、还是对混淆代码的去除)到时候灵活定制即可,现在由这套代码输出出来的代码经过我最后的还原,我发现效果非常好,只有少数的handler还是把混淆代码给带进来的,但也无碍。 > 专属寄存器里不用单独污染 rsp,污染它的话会带出很多混淆指令,我们只对与 rsp 寻址有关的指令污染即可。比如 `lea rdx, qword ptr ss:[rsp+0x20]`,我们只污染 rdx。 > 这套代码还不能灵活处理专属寄存器变更的情况,所以使用前还是得先自己去跟踪里手动追一下,比如在索引 0x35e9 之前的 (vsp, vpc, vkey, vbase) 是 (rsi, rbx, rdi, rbp),在 0x35e9 索引之后就变成了 (r8, rdi, r9, rbx) 那个 `VRLIMIT` 的值是 checkVsp 里进行 `rep movsb` 复制的总大小(也就是 `ecx` 寄存器的值,`rep movsb` 指令的复制大小是看 `ecx` ):  我们也得先手动在跟踪里找出这个值,通过这个值,我们就能确定真正的虚拟寄存器的界限是多少,因为 checkVsp 的作用是重新提升虚拟栈,它自然就需要通过复制数据来提前保护虚拟寄存器的安全,因此观察它复制了多少字节,就能知道虚拟寄存器的准确界限。在这次的vm样本里,我们的 `VRLIMIT` 是 0x100,(0x100 // 8) = 0x20 = 32,我们的虚拟寄存器区间就是 `VR0` ~ `VR31`。 输出效果(前三个handler): ``` --[0]---------- 0x80 addr: 0x1402257c3 -------------- | vsp[r8]: 64f492f688 vpc[rdi]: 14000651c rsp: 64f492f480 vkey[r9]: 1400f4f42 vbase[rbx]: 1402257c3 | | 0x81 0x1402257c8: mov ecx, dword ptr [rdi - 4] | 0x83 0x1402257d1: xor ecx, r9d | 0x87 0x1402257df: neg ecx | 0x8b 0x1402257ef: lea ecx, [rdx + rcx - 0x5fc1bee2] | 0x8c 0x1402257f6: not ecx | 0x90 0x14011659c: lea ecx, [rcx + rbp*2 + 0x54309f19] | 0x93 0x1401165a8: ror ecx, 3 | 0x96 0x1401165b5: bswap ecx | 0x99 0x1401165cb: mov qword ptr [rsp + rbp - 0xc02], r9 | 0x9a 0x1401165d3: xor dword ptr [rsp + rax - 0x9fffff8], ecx |*0x9c 0x1401165de: mov r9, qword ptr [rsp + rax*2 - 0x1407fff8] | | 0x9e 0x1401165ee: movsxd rcx, ecx |*0xa0 0x1401165f5: adc rbx, rcx | 0xa3 0x140116604: mov rbp, qword ptr [r8 + rax*2 - 0x140860fa] | | 0xa7 0x140116620: movzx eax, byte ptr [rdi + rsi*2 - 0x764ef967] | 0xab 0x14011663a: xor al, r9b | 0xad 0x140116640: dec al | 0xb1 0x1400c32bb: not al | 0xb2 0x1400c32bd: sbb al, 0x37 | 0xb6 0x14018384c: rol al, 1 | 0xb8 0x140183851: dec al |*0xba 0x140183857: xor r9b, al | | 0xbc 0x14018385f: lea rax, [rsp + rax + 0x18] | 0xbe 0x140183866: mov qword ptr [rax + r11*2], rbp | 0xc1 0x140084b20: mov rcx, qword ptr [r8 + rsi*2 - 0x764ef95a] |*0xc3 0x140084b2c: lea r8, [r8 + r11*4 + 0x10] | | 0xc7 0x140084b40: xor bpl, byte ptr [rsi + rdi - 0x3b277cb7] |*0xca 0x140084b4d: lea rdi, [rdi + rsi - 0x3b277cb7] | 0xcb 0x140084b55: xor bpl, r9b | 0xcc 0x140084b58: sub bpl, r10b | 0xcf 0x140084b63: rol bpl, 1 | 0xd0 0x140084b66: xor bpl, 0x1d | 0xd1 0x140084b6a: add bpl, r10b | 0xd2 0x140084b6d: btr edx, r10d | 0xd3 0x140084b71: ror bpl, 1 |*0xd5 0x140084b75: xor r9b, bpl | | 0xd8 0x140084b88: lea rbp, [rsp + rbp + 0x18] | 0xdb 0x140084b95: mov qword ptr [rbp + rsi*2 - 0x764ef962], rcx | | 0xde 0x140084ba6: mov qword ptr [rsp + rdx - 0x20d51f31], rbx | 0xdf 0x140084bae: ret 0x10 -------------------------- | 0x81 [R] 140006518: 629fecfd | 0x99 [W] 64f492f478: c53800c09 -> 1400f4f42 | 0x9a [W] 64f492f478: 400f4f42 -> 400f4f42 | 0x9c [R] 64f492f478: 1400f4f42 | 0xa3 [R] vsp[+0x0] 64f492f688: 7ffffffffffffffc | 0xa7 [R] 140006517: 7b | 0xbe [W] [+0x20 = VR4] 64f492f4a0: 0 -> 7ffffffffffffffc | 0xc1 [R] vsp[+0x8] 64f492f690: 21c3885b3e0 | 0xc7 [R] 140006516: 0 | 0xdb [W] [+0x70 = VR14] 64f492f4f0: 64f492f570 -> 21c3885b3e0 | 0xde [W] 64f492f468: b1 -> 1402257c3 | 0xdf [R] 64f492f468: 1402257c3 --[1]---------- 0xe0 addr: 0x1402257c3 -------------- | vsp[r8]: 64f492f698 vpc[rdi]: 140006516 rsp: 64f492f480 vkey[r9]: 1400f4f12 vbase[rbx]: 1402257c3 | | 0xe1 0x1402257c8: mov ecx, dword ptr [rdi - 4] | 0xe3 0x1402257d1: xor ecx, r9d | 0xe7 0x1402257df: neg ecx | 0xeb 0x1402257ef: lea ecx, [rdx + rcx - 0x5fc1bee2] | 0xec 0x1402257f6: not ecx | 0xf0 0x14011659c: lea ecx, [rcx + rbp*2 + 0x54309f19] | 0xf3 0x1401165a8: ror ecx, 3 | 0xf6 0x1401165b5: bswap ecx | 0xf9 0x1401165cb: mov qword ptr [rsp + rbp - 0xc02], r9 | 0xfa 0x1401165d3: xor dword ptr [rsp + rax - 0x9fffff8], ecx |*0xfc 0x1401165de: mov r9, qword ptr [rsp + rax*2 - 0x1407fff8] | | 0xfe 0x1401165ee: movsxd rcx, ecx |*0x100 0x1401165f5: adc rbx, rcx | 0x103 0x140116604: mov rbp, qword ptr [r8 + rax*2 - 0x140860fa] | | 0x107 0x140116620: movzx eax, byte ptr [rdi + rsi*2 - 0x764ef967] | 0x10b 0x14011663a: xor al, r9b | 0x10d 0x140116640: dec al | 0x111 0x1400c32bb: not al | 0x112 0x1400c32bd: sbb al, 0x37 | 0x116 0x14018384c: rol al, 1 | 0x118 0x140183851: dec al |*0x11a 0x140183857: xor r9b, al | | 0x11c 0x14018385f: lea rax, [rsp + rax + 0x18] | 0x11e 0x140183866: mov qword ptr [rax + r11*2], rbp | 0x121 0x140084b20: mov rcx, qword ptr [r8 + rsi*2 - 0x764ef95a] |*0x123 0x140084b2c: lea r8, [r8 + r11*4 + 0x10] | | 0x127 0x140084b40: xor bpl, byte ptr [rsi + rdi - 0x3b277cb7] |*0x12a 0x140084b4d: lea rdi, [rdi + rsi - 0x3b277cb7] | 0x12b 0x140084b55: xor bpl, r9b | 0x12c 0x140084b58: sub bpl, r10b | 0x12f 0x140084b63: rol bpl, 1 | 0x130 0x140084b66: xor bpl, 0x1d | 0x131 0x140084b6a: add bpl, r10b | 0x132 0x140084b6d: btr edx, r10d | 0x133 0x140084b71: ror bpl, 1 |*0x135 0x140084b75: xor r9b, bpl | | 0x138 0x140084b88: lea rbp, [rsp + rbp + 0x18] | 0x13b 0x140084b95: mov qword ptr [rbp + rsi*2 - 0x764ef962], rcx | | 0x13e 0x140084ba6: mov qword ptr [rsp + rdx - 0x20d51f31], rbx | 0x13f 0x140084bae: ret 0x10 -------------------------- | 0xe1 [R] 140006512: 629fecad | 0xf9 [W] 64f492f478: c53800c09 -> 1400f4f12 | 0xfa [W] 64f492f478: 400f4f12 -> 400f4f12 | 0xfc [R] 64f492f478: 1400f4f12 | 0x103 [R] vsp[+0x0] 64f492f698: 0 | 0x107 [R] 140006511: 37 | 0x11e [W] [+0x48 = VR9] 64f492f4c8: 64f492f570 -> 0 | 0x121 [R] vsp[+0x8] 64f492f6a0: 202 | 0x127 [R] 140006510: 28 | 0x13b [W] [+0x0 = VR0] 64f492f480: 0 -> 202 | 0x13e [W] 64f492f468: b1 -> 1402257c3 | 0x13f [R] 64f492f468: 1402257c3 --[2]---------- 0x140 addr: 0x1402257c3 -------------- | vsp[r8]: 64f492f6a8 vpc[rdi]: 140006510 rsp: 64f492f480 vkey[r9]: 1400f4f5a vbase[rbx]: 1402257c3 | | 0x141 0x1402257c8: mov ecx, dword ptr [rdi - 4] | 0x143 0x1402257d1: xor ecx, r9d | 0x147 0x1402257df: neg ecx | 0x14b 0x1402257ef: lea ecx, [rdx + rcx - 0x5fc1bee2] | 0x14c 0x1402257f6: not ecx | 0x150 0x14011659c: lea ecx, [rcx + rbp*2 + 0x54309f19] | 0x153 0x1401165a8: ror ecx, 3 | 0x156 0x1401165b5: bswap ecx | 0x159 0x1401165cb: mov qword ptr [rsp + rbp - 0xc02], r9 | 0x15a 0x1401165d3: xor dword ptr [rsp + rax - 0x9fffff8], ecx |*0x15c 0x1401165de: mov r9, qword ptr [rsp + rax*2 - 0x1407fff8] | | 0x15e 0x1401165ee: movsxd rcx, ecx |*0x160 0x1401165f5: adc rbx, rcx | 0x163 0x140116604: mov rbp, qword ptr [r8 + rax*2 - 0x140860fa] | | 0x167 0x140116620: movzx eax, byte ptr [rdi + rsi*2 - 0x764ef967] | 0x16b 0x14011663a: xor al, r9b | 0x16d 0x140116640: dec al | 0x171 0x1400c32bb: not al | 0x172 0x1400c32bd: sbb al, 0x37 | 0x176 0x14018384c: rol al, 1 | 0x178 0x140183851: dec al |*0x17a 0x140183857: xor r9b, al | | 0x17c 0x14018385f: lea rax, [rsp + rax + 0x18] | 0x17e 0x140183866: mov qword ptr [rax + r11*2], rbp | 0x181 0x140084b20: mov rcx, qword ptr [r8 + rsi*2 - 0x764ef95a] |*0x183 0x140084b2c: lea r8, [r8 + r11*4 + 0x10] | | 0x187 0x140084b40: xor bpl, byte ptr [rsi + rdi - 0x3b277cb7] |*0x18a 0x140084b4d: lea rdi, [rdi + rsi - 0x3b277cb7] | 0x18b 0x140084b55: xor bpl, r9b | 0x18c 0x140084b58: sub bpl, r10b | 0x18f 0x140084b63: rol bpl, 1 | 0x190 0x140084b66: xor bpl, 0x1d | 0x191 0x140084b6a: add bpl, r10b | 0x192 0x140084b6d: btr edx, r10d | 0x193 0x140084b71: ror bpl, 1 |*0x195 0x140084b75: xor r9b, bpl | | 0x198 0x140084b88: lea rbp, [rsp + rbp + 0x18] | 0x19b 0x140084b95: mov qword ptr [rbp + rsi*2 - 0x764ef962], rcx | | 0x19e 0x140084ba6: mov qword ptr [rsp + rdx - 0x20d51f31], rbx | 0x19f 0x140084bae: ret 0x10 -------------------------- | 0x141 [R] 14000650c: 629fece5 | 0x159 [W] 64f492f478: c53800c09 -> 1400f4f5a | 0x15a [W] 64f492f478: 400f4f5a -> 400f4f5a | 0x15c [R] 64f492f478: 1400f4f5a | 0x163 [R] vsp[+0x0] 64f492f6a8: 1 | 0x167 [R] 14000650b: a7 | 0x17e [W] [+0x98 = VR19] 64f492f518: 7ffeb1da453d -> 1 | 0x181 [R] vsp[+0x8] 64f492f6b0: 140002280 | 0x187 [R] 14000650a: 6d | 0x19b [W] [+0xa0 = VR20] 64f492f520: 64f492f680 -> 140002280 | 0x19e [W] 64f492f468: b1 -> 1402257c3 | 0x19f [R] 64f492f468: 1402257c3 ...... ``` 接下来就是漫长的人工分析了,交给 Claude、GPT 他们来分析更好,只不过我还没准备好我的提示词。 # 最终还原结果 大部分的 handler 是复合 handler,我用 `;` 来分割里面的简单 handler。 ``` VR4 = r8 VR14 = rbx VR9 = r13 VR0 = rflags VR19 = rax VR20 = rcx VR7 = Relocation Address VR21 = r11 VR13 = rsi VR18 = r15 VR15 = r14 VR17 = r10 VR1 = rdi VR5 = rbp VR8 = r12 VR11 = rdx VR22 = r9 =================字节码区域[0x1400060f9 - 0x14000651c]================= loc_0: // 首次进入虚拟机 [0] vPopReg64 VR4; vPopReg64 VR14 [1] vPopReg64 VR9; vPopReg64 VR0 [2] vPopReg64 VR19; vPopReg64 VR20 [3] vPopReg64 VR7; vPopReg64 VR21 [4] vPopReg64 VR13; vPopReg64 VR18 [5] vPopReg64 VR15; vPopReg64 VR17 [6] vPopReg64 VR1; vPopReg64 VR5 [7] vPopReg64 VR8; vPopReg64 VR11 [8] vPopReg64 VR22; vPushImm64 0x140001188 [9] vPushReg64 VR7; vAdd64 [10] vPopReg64 VR24 [11] vPushVsp64; vPopReg64 VR25 [12] vPushReg64 VR5 [13] vPopReg64 VR26 [14] vPushReg64 VR13 [15] vPopReg64 VR27; vPushReg64 VR1 [16] vPopReg64 VR28; vPushReg64 VR14 [17] vPopReg64 VR29 [18] vPushImm32 0xdeadbeef [19] vPopReg32 VR16 [20] vPushReg32 VR16; vPushReg32 VR17 [21] vPopReg32 VR2 [22] vPushReg32 VR2; vPushReg32 VR2 [23] vNand32; vAdd32 // nand(P, P) = not(P) [24] vPushVsp64 [25] vReadMem32; vNand32 [26] vPopReg32 VR6 [27] vPushImm64 0x140006176 // 候选VPC1 [28] vPushImm64 0x1400061f3 // 候选VPC2 [29] vPushVsp64 [30] vPushImm16 0x19; vPushImm32 0x0 [31] vPushImm32 0x7fffffff; vPushReg32 VR6 [32] vAdd32; vPushReg32 VR6 [33] vNor32 [34] vPushImm32 0x80000000; vNand32 [35] vPushVsp64; vReadMem32 [36] vNand32 [37] vShr64; vPopReg64 VR10 [38] vPushImm16 0x3; vPushReg64 VR10 [39] vShr64; vPushImm64 0xfffffffffffffff7 [40] vNor64 [41] vAdd64; vReadMem64 // 得到了下一个新的vpc值 [42] vPopReg64 VR12; vPopReg64 VR23 // VR12 = 下一个新的vpc值 [43] vPopReg64 VR23; vPushReg64 VR12 [44] vPushReg64 VR7; vAdd64 [45] vPopReg64 VR12; vPushReg64 VR5 [46] vPushReg64 VR14 [47] vPushReg64 VR21; vPushReg64 VR4 [48] vPushReg64 VR7 [49] vPushImm16 0x14; vPushImm32 0x0 [50] vPushReg32 VR2; vPushReg32 VR2 [51] vNand32; vPushReg32 VR16 [52] vPushReg32 VR16 [53] vNor32; vNor32 [54] vPushReg32 VR2; vPushReg32 VR16 [55] vNor32; vNor32 [56] vPushReg32 VR2; vPushReg32 VR2 [57] vNor32 [58] vPushReg32 VR6; vPushReg32 VR6 [59] vNor32; vNor32 [60] vPushReg32 VR2; vPushReg32 VR6 [61] vNor32; vNor32 [62] vNand32; vPushVsp64 [63] vReadMem32 [64] vNor32 [65] vPushImm32 0x80000000; vNand32 [66] vPushVsp64; vReadMem32 [67] vNor32 [68] vShr64; vPushImm16 0x18 // of [69] vPushImm32 0x0 [70] vPushReg32 VR6; vPushImm32 0x80000000 [71] vNand32; vPushVsp64 [72] vReadMem32; vNand32 [73] vShr64; vAdd64 // sf [74] vPushImm16 0x19; vPushImm32 0x0 [75] vPushImm32 0x7fffffff; vPushReg32 VR6 [76] vAdd32; vPushReg32 VR6 [77] vNor32 [78] vPushImm32 0x80000000; vNand32 [79] vPushVsp64; vReadMem32 [80] vNor32; vShr64 [81] vAdd64; vPushImm32 0x0 // zf [82] vPushReg32 VR2; vPushReg32 VR2 [83] vNand32; vPushReg32 VR16 [84] vPushReg32 VR16; vNor32 [85] vNor32; vPushReg32 VR2 [86] vPushReg32 VR16; vNor32 [87] vNor32 [88] vPopReg32 VR10 [89] vPushReg32 VR10; vPushReg32 VR10 [90] vNor32; vPushReg32 VR6 [91] vPushReg32 VR6 [92] vNand32; vNor32 [93] vPushReg32 VR10; vPushReg32 VR6 [94] vNor32; vNor32 [95] vPushImm32 0x10 [96] vNand32; vPushVsp64 [97] vReadMem32; vNand32 [98] vAdd64 // af [99] vPushImm16 0x4; vPushReg8 VR6 [100] vShr8 [101] vPopReg8 VR10; vPushImm16 0x2 [102] vPushImm16 0x0; vPushImm32 0x0 [103] vPushReg8 VR6 [104] vPushReg8 VR6; vNand8 [105] vPushReg8 VR10 [106] vNand8 [107] vPushReg8 VR6 [108] vPushReg8 VR10; vPushReg8 VR10 [109] vNand8; vNand8 [110] vNand8 [111] vPushImm16 0xf; vNand16 [112] vPushVsp64; vReadMem16 [113] vNor16 [114] vPushImm16 0x9669; vShr16 [115] vPushImm16 0x1; vNand16 [116] vPushVsp64; vReadMem16 [117] vNor16 [118] vSal64 [119] vAdd64 // pf [120] vPushImm16 0x1f; vPushImm32 0x0 [121] vPushReg32 VR2; vPushReg32 VR2 [122] vNor32; vPushReg32 VR16 [123] vNand32; vPushVsp64 [124] vReadMem32; vNor32 [125] vPushReg32 VR2; vPushReg32 VR16 [126] vPushReg32 VR16; vNor32 [127] vNor32; vPushReg32 VR2 [128] vPushReg32 VR2; vNand32 [129] vPushReg32 VR16; vNor32 [130] vNor32; vPushReg32 VR6 [131] vNand32; vPushVsp64 [132] vReadMem32; vNand32 [133] vNor32 [134] vPushVsp64; vReadMem32 [135] vNor32 [136] vPushImm32 0x80000000; vNand32 [137] vPushVsp64; vReadMem32 [138] vNand32 [139] vShr64; vAdd64 // cf [140] vPushReg64 VR0; vPushImm64 0xfffffffffffff72a [141] vNand64 [142] vPushVsp64 [143] vReadMem64; vNand64 [144] vAdd64; vPopReg64 VR23 // 给VR23写入新的rflags [145] vPushReg64 VR23; vPushReg64 VR19 [146] vPushReg64 VR1; vPushReg64 VR11 [147] vPushReg64 VR13; vPushReg64 VR18 [148] vPushReg64 VR9; vPushReg64 VR17 [149] vPushReg64 VR22; vPushReg64 VR15 [150] vPushReg64 VR20; vPushReg64 VR8 [151] vPushReg64 VR12 [152] vJmp // 将跳转到 loc_1 或 loc_2 loc_1: // jne跳转未生效,是0xdeadbeef [153] vPopReg64 VR0; vPopReg64 VR20 [154] vPopReg64 VR8; vPopReg64 VR15 [155] vPopReg64 VR3; vPopReg64 VR17 [156] vPopReg64 VR18; vPopReg64 VR22 [157] vPopReg64 VR9; vPopReg64 VR13 [158] vPopReg64 VR19; vPopReg64 VR11 [159] vPopReg64 VR1; vPopReg64 VR4 [160] vPopReg64 VR23; vPopReg64 VR14 [161] vPopReg64 VR7; vPushImm64 0x140002534 [162] vPushReg64 VR1; vAdd64 [163] vPopReg64 VR12; vPushReg64 VR7 [164] vPushReg64 VR14; vPushReg64 VR23 [165] vPushReg64 VR4; vPushReg64 VR1 [166] vPushReg64 VR11; vPushReg64 VR19 [167] vPushReg64 VR13; vPushReg64 VR9 [168] vPushReg64 VR22; vPushReg64 VR18 [169] vPushReg64 VR17; vPushReg64 VR3 [170] vPushReg64 VR15; vPushReg64 VR8 [171] vPushReg64 VR20; vPushReg64 VR0 [172] vPushReg64 VR12; vJmp // 将跳转到 loc_3 loc_2: // jne跳转已生效,不是0xdeadbeef [173] vPopReg64 VR5; vPopReg64 VR20 [174] vPopReg64 VR8; vPopReg64 VR21 [175] vPopReg64 VR15; vPopReg64 VR17 [176] vPopReg64 VR3; vPopReg64 VR0 [177] vPopReg64 VR22; vPopReg64 VR18 [178] vPopReg64 VR13; vPopReg64 VR11 [179] vPopReg64 VR9; vPopReg64 VR19 [180] vPopReg64 VR23; vPopReg64 VR4 [181] vPopReg64 VR1; vPushImm64 0x1400025d1 [182] vPushReg64 VR9; vAdd64 [183] vPopReg64 VR14; vPushReg64 VR1 [184] vPushReg64 VR4; vPushReg64 VR23 [185] vPushReg64 VR19; vPushReg64 VR9 [186] vPushReg64 VR11; vPushReg64 VR13 [187] vPushReg64 VR18; vPushReg64 VR22 [188] vPushReg64 VR0; vPushReg64 VR3 [189] vPushReg64 VR17; vPushReg64 VR15 [190] vPushReg64 VR21; vPushReg64 VR8 [191] vPushReg64 VR20; vPushReg64 VR5 [192] vPushReg64 VR14; vJmp // 将跳转到 loc_4 =================字节码区域[0x140002534 - 0x14000265f]================= loc_3: [193] vPopReg64 VR20 [194] vPopReg64 VR9; vPopReg64 VR12 [195] vPopReg64 VR18 [196] vPopReg64 VR19; vPopReg64 VR4 [197] vPopReg64 VR6; vPopReg64 VR10 [198] vPopReg64 VR15; vPopReg64 VR3 [199] vPopReg64 VR7; vPopReg64 VR17 [200] vPopReg64 VR22; vPopReg64 VR1 [201] vPopReg64 VR21; vPopReg64 VR8 [202] vPopReg64 VR11; vPushImm64 0x140001188 [203] vPushReg64 VR22 [204] vAdd64; vPopReg64 VR24 [205] vPushImm64 0xdeadbeef [206] vPopReg64 VR0; vPushImm64 0x1615d9430 [207] vPopReg64 VR23 [208] vPushReg64 VR11 [209] vPushReg64 VR8; vPushReg64 VR21 [210] vPushReg64 VR1; vPushReg64 VR22 [211] vPushReg64 VR17; vPushReg64 VR0 [212] vPushReg64 VR3; vPushReg64 VR15 [213] vPushReg64 VR10; vPushReg64 VR6 [214] vPushReg64 VR4; vPushReg64 VR19 [215] vPushReg64 VR18; vPushReg64 VR12 [216] vPushReg64 VR9; vPushReg64 VR20 [217] vNop loc_4: [218] vPopReg64 VR14; vPopReg64 VR7 [219] vPopReg64 VR16; vPopReg64 VR23 [220] vPopReg64 VR20; vPopReg64 VR13 [221] vPopReg64 VR18; vPopReg64 VR19 [222] vPopReg64 VR10; vPopReg64 VR12 [223] vPopReg64 VR15; vPopReg64 VR3 [224] vPopReg64 VR4; vPopReg64 VR17 [225] vPopReg64 VR2; vPopReg64 VR8 [226] vPopReg64 VR5; vPushImm64 0x140001188 [227] vPushReg64 VR4; vAdd64 [228] vPopReg64 VR24; vPushImm64 0x1400011c0 [229] vPushReg64 VR4; vAdd64 [230] vPushReg64 VR23; vPushReg64 VR7 [231] vPushReg64 VR2; vPushReg64 VR19 [232] vPushReg64 VR14; vPushReg64 VR8 [233] vPushReg64 VR20; vPushReg64 VR13 [234] vPushReg64 VR17; vPushReg64 VR10 [235] vPushReg64 VR12; vPushReg64 VR16 [236] vPushReg64 VR5; vPushReg64 VR15 [237] vPushReg64 VR3; vPushReg64 VR18 [238] vExit r15 = VR18 rflags = VR3 rax = VR15 rbp = VR5 r14 = VR16 rdi = VR12 rdx = VR10 r8 = VR17 r13 = VR13 r10 = VR20 rbx = VR8 r12 = VR14 rsi = VR19 r11 = VR2 rcx = VR7 r9 = VR23 ``` 绘制出的控制流图:  借助 **GPT-5.5 Thinking** 还原出来的语义(从 [18] 到 [152] ): ```cpp uint32_t A = VR2; uint32_t B = VR16; // 0xDEADBEEF uint32_t R = VR6; // A - B uint64_t target; if (R == 0) target = 0x1400061F3; else target = 0x140006176; // VPC 修正 / 滚动 key 修正 VR12 = target + VR7; // 模拟 cmp/sub 的 EFLAGS uint64_t flags = 0; flags |= (((A ^ B) & (A ^ R)) & 0x80000000) >> 20; // OF flags |= (R & 0x80000000) >> 24; // SF flags |= (R == 0 ? 1ull : 0ull) << 6; // ZF flags |= (A ^ B ^ R) & 0x10; // AF flags |= parity_even(R & 0xff) << 2; // PF flags |= (((~A & B) | (~(A ^ B) & R)) & 0x80000000) >> 31; // CF VR23 = (VR0 & 0xfffffffffffff72a) | flags; // 最后更新虚拟机的 VPC/VKEY StoreVPC(VR12); ``` # 附件 <a href="elink@236K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4x3s2c8Z5x3h3x3#2y4r3f1@1i4K6u0r3N6X3#2H3x3K6V1@1i4K6u0V1K9r3q4F1k6r3I4W2M7Y4y4Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4E0M7q4)9J5y4e0t1$3K9X3&6W2"><mark class="encrypted">d47K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4x3s2c8Z5x3h3x3#2y4r3f1@1i4K6u0r3N6X3#2H3x3K6V1@1i4K6u0V1K9r3q4F1k6r3I4W2M7Y4y4Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4E0M7q4)9J5y4e0t1$3K9X3&6W2</mark></a> # 参考资料 [VMP入门: VMP1.81 Demo分析](https://bbs.kanxue.com/thread-286278.htm) 对vmp3.2虚拟机内爆破的一点研究 > <mark class="encrypted">546K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6X3L8%4u0#2L8g2)9J5k6i4m8Z5M7q4)9K6c8X3#2G2k6q4)9K6c8s2k6A6k6i4N6@1K9s2u0W2j5h3c8Q4x3U0k6@1K9h3c8Q4x3@1b7J5x3o6R3K6y4e0R3J5</mark> [VMProtect 3.8.1 混淆策略大揭秘](https://bbs.kanxue.com/thread-290726.htm) [VMProtect 3.3.1虚拟机&代码混淆机制入门](https://bbs.kanxue.com/thread-251680-1.htm)
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-5-3 21:34 被LingMo0412编辑 ,原因:
#VM保护
收藏
・
18
点赞
・
15
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
周游列国
感谢你分享这么好的资源!
19小时前
wx_晨梦
为你点赞!
2026-7-16 05:32
马来
感谢你的积极参与,期待更多精彩内容!
2026-7-2 19:55
iCookiie
感谢你分享这么好的资源!
2026-5-21 10:08
我不是深渊
你的帖子非常有用,感谢分享!
2026-5-19 08:51
n00bzx
+3
感谢你分享这么好的资源!
2026-5-13 02:46
一半人生
+1
感谢你的积极参与,期待更多精彩内容!
2026-5-10 07:06
定清先生
你的帖子非常有用,感谢分享!
2026-5-6 22:23
Dy1an
非常支持你的观点!
2026-5-6 11:21
我的小拇指啊
期待更多优质内容的分享,论坛有你更精彩!
2026-5-4 23:23
mb_iesvmzqc
为你点赞!
2026-5-4 23:19
lingyi223
这个讨论对我很有帮助,谢谢!
2026-5-1 21:56
ReGenesis
你的分享对大家帮助很大,非常感谢!
2026-4-30 16:17
R0g
这个讨论对我很有帮助,谢谢!
2026-4-30 11:10
东方玻璃
感谢你的贡献,论坛因你而更加精彩!
2026-4-30 00:57
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
6
)
mb_hxcaoonh
雪 币:
80
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
57
粉丝
0
关注
私信
mb_hxcaoonh
2
楼
学习学习
2026-5-4 22:39
0
AsuraCoder
雪 币:
994
活跃值:
(1631)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
55
粉丝
0
关注
私信
AsuraCoder
3
楼
就这么点就这么难啊啊
2026-5-4 23:11
0
啊你好哇123
雪 币:
753
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
367
粉丝
0
关注
私信
啊你好哇123
4
楼
狠狠学习
2026-5-5 15:38
0
inwelove
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
7
粉丝
0
关注
私信
inwelove
5
楼
学习一下
2026-5-20 22:22
0
limda
雪 币:
220
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
21
粉丝
0
关注
私信
limda
6
楼
太厉害了,学习一下
2026-7-28 18:08
0
mb_kppjdiod
雪 币:
63
活跃值:
(933)
能力值:
( LV2,RANK:10 )
在线值:
发帖
8
回帖
37
粉丝
3
关注
私信
mb_kppjdiod
7
楼
VMP头都大得,这么几行代码混淆成这样,有没有什么模型插件能大概还原下吗,有QQ群交流吗
2026-8-7 02:59
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
LingMo0412
2
5
发帖
14
回帖
100
RANK
关注
私信
他的文章
[原创] vmp3.9.4 手动还原cmp/jne下的handler序列
14597
[原创]刺杀vmp3.9.4混淆
25698
[原创] 记录下基于符号执行求解发现vmp分支的思路
16401
[原创]开源个x64dbg trace文件解析
2743
[原创] 笔记 - 学习使用活跃变量分析来清除基本块内的垃圾指令
2720
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部