吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1817|回复: 15
上一主题 下一主题
收起左侧

[原创] vmp3.9.4 手动还原cmp/jne下的handler序列

  [复制链接]
跳转到指定楼层
楼主
LingMo 发表于 2026-4-30 01:00 回帖奖励
本帖最后由 LingMo 于 2026-5-3 21:36 编辑

前言

这几个月学习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:

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()

然后就是关键代码了,我们主要靠这段代码的输出来做后续的工作:

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虚拟机机制有关的代码,包括专属寄存器 (vpcvspvkeyvbase),以及可能的特殊指令(比如 cpuidrdtsc),污点规则(无论是对有意义代码的保留、还是对混淆代码的去除)到时候灵活定制即可,现在由这套代码输出出来的代码经过我最后的还原,我发现效果非常好,只有少数的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] ):

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);

附件

https://github.com/g0th1c54e4/vmp394-handlers/tree/main/cmp%26jne

参考资料

VMP入门: VMP1.81 Demo分析

对vmp3.2虚拟机内爆破的一点研究

VMProtect 3.8.1 混淆策略大揭秘

VMProtect 3.3.1虚拟机&代码混淆机制入门

免费评分

参与人数 8威望 +2 吾爱币 +109 热心值 +7 收起 理由
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Tonyha7 + 2 + 1 用心讨论,共获提升!
lingyi223 + 1 好东西
abaooo + 1 + 1 我很赞同!
358059103 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
buluo533 + 1 + 1 用心讨论,共获提升!
唐小样儿 + 1 + 1 我很赞同!
无闻无问 + 2 + 1 用心讨论,共获提升!

查看全部评分

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

推荐
wojiyin 发表于 2026-4-30 09:07

感谢分享
3#
nb520 发表于 2026-4-30 02:23
4#
无闻无问 发表于 2026-4-30 07:18
5#
bj6688 发表于 2026-4-30 08:03
这个壳真不好弄。
6#
windawin 发表于 2026-4-30 08:27
感谢分享
7#
358059103 发表于 2026-4-30 09:39
感谢老师分享教程,论坛有你而精彩
8#
Y87699R3578 发表于 2026-4-30 10:30
感谢你的经验分享教程,论坛有你而更加精彩
9#
xujili168 发表于 2026-4-30 13:09
感谢分享
10#
fengshangren 发表于 2026-4-30 14:00
如果一个加了VM的代码,能还原VM来进行破解的概率大吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-5-12 02:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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