前言
这几个月学习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虚拟机机制有关的代码,包括专属寄存器 (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] ):
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虚拟机&代码混淆机制入门