yoyoRev 发表于 2025-5-18 21:45

关于vmp3.2的一点研究心得

最近在学代码虚拟化,看了章立春老师的代码虚拟与自动化分析这本书前几章,第三章自己实现了一个简单的虚拟机,明白了虚拟化的基本原理。遂开始研究了一下vmp3.2,谈论一下我的一点理解和心得,如有错误请指正。刚开始接触vmp,所以弄的简单一点,先写了一个简单的程序,编译的时候把随机基址,增量链接啥的全部关掉,然后用vmp3.2对选中的代码进行虚拟化,只模拟这几条指令,代码如下```
#include<windows.h>
#include "VMProtectSDK.h"
#pragma warning(disable : 4996)
int main() {
        DWORD dwval;
        VMProtectBeginVirtualization("yoyo");
        __asm {
                mov eax,0x10000000
                add eax,0x1234
                mov dwval,eax

        }
        VMProtectEnd();
        char buff;
        itoa(dwval, buff, 16);
        MessageBoxA(0,buff,"result",MB_OK| MB_ICONEXCLAMATION);
        return 0;

}
```
然后使用vmp3.2进行虚拟化,只虚拟化三条指令即可。



接下来看虚拟化前后的区别:





可以看到已经被一个push和一个jmp指令所替换,在这里就得说一下虚拟化的基本原理了,如章老师这本书所述,总结一下就是保存现场,模拟执行,然后将模拟之后的结果恢复到真实的寄存器,退出虚拟机。



接下来看vmp是如何做的,在0x401010处压栈,紧接着在0x401015处一个jmp指令跳转到虚拟机入口,



可以看到有两条指令 push 0x92124021和call 0x0041542B,单步f7跟进去```
push ebx
seto bh
not bl
push ecx
movzx ebx, sp
jmp 0x0047CDD3
...
```
入口部分代码,还记得上面所说的吗?要保存现场环境的,所以说真正有用的指令就两条(或者说三条吧)分别是push ebx,push ecx,jmp 0x0047cdd3,其余指令全是垃圾指令,可以当成nop反正不要受到这些指令的影响 就当看不到。
接下来看0x0047cdd3的指令,由于vmp的指令非常多且杂,所以后续不再截图或者写出来



可以看到还是很多的垃圾指令,真正有意义的还是几条push指令罢了。所以我直接将有用的指令重新写了一下

```
        push 0x402110
        push 0x92124021
        call 0x41542b

0x41542b:
        push ebx
        push ecx
        push ebp
        push eax
        push esi
        push edx
        push edi
        push eflags

        mov ecx,0
        push ecx
        mov esi,dword ptr ds://此时esi的值是0x92124021
        dec esi
        xor esi,0x7733592f
        inc esi
        xor esi,0x66bf7d8f
        rol esi,3
        sub esi,0x1cb30155   //上面操作对esi进行解密 此时esi=0x4023a7
        lea esi,dword ptr ds:
       
        mov edi,esp                        //       
        lea esp,dword ptr ss:
        mov ebx,
        sub ebx,ecx
        lea ebp,dword ptr ds:
        sub esi,0x4
        mov eax,
        xor eax,ebx
        not eax
        dec eax
        ror eax,0x3
        not eax
        bswap eax
        lea eax, dword ptr ds:
        xor ebx,eax
        add ebp,eax
        jmp ebp   //此时ebp = 0x42ca8c等价于jmp 0x42ca8c
       
0x42ca8c:
        mov eax,dword ptr ds:
        add edi,0x4
        lea esi,dword ptr ds:
        movzx ecx, byte ptr ds:
        xor cl,bl
        inc cl
        neg cl
        add cl,0xB
        ror cl,0x1
        sub cl,0x8e
        neg cl
        xor cl,0x54
        dec cl
        xor bl,cl
       
        mov dword ptr ss:, eax         //上述对cl寄存器的操作最终得到的是一个偏移量 ,后续都是这种操作,不再赘述.此时ecx=8
        sub esi,0x4
        mov ecx, dword ptr ds:
        xor ecx, ebx
        sub ecx, 0x3E850F9
        rol ecx, 0x1
        sub ecx, 0x6BA107A2
        xor ecx, 0x53345A
        lea ecx, ds:
        neg ecx
        add ecx, 0x177B784E
        rol ecx, 0x1
        xor ebx, ecx
        add ebp,ecx
        push ebp   
        ret       //跳转到0x40d855处
       
0x40d855:
        mov eax, dword ptr ds: //的内容是初始时压栈的eflags寄存器的值 后续会保存到虚拟机栈中
        add edi,4                                        //修正edi,指向下一个成员
        lea esi, ds:
        movzx ecx, byte ptr ds:
        xor cl, bl
        inc cl
        neg cl
        add cl, 0xB
        ror cl, 0x1
        sub cl, 0x8E
        neg cl
        xor cl,0x54
        dec cl
        xor bl,cl
        mov dword ptr ss:, eax //ecx=0x2c 保存真实的elags寄存器到虚拟机的elags寄存器,初始化虚拟机环境 vmcontext.eflags
        sub esi,4
        mov ecx, dword ptr ds:
        xor ecx, ebx
        sub ecx, 0x3E850F9
        rol ecx, 0x1
        lea ecx, ds:
        xor ecx, 0x53345A
        lea ecx, ds:
        neg ecx
        add ecx, 0x177B784E
        rol ecx, 0x1
        xor ebx, ecx
        add ebp,ecx
        jmp ebp        //        此时ebp=0x0044FA3F
       
0x0044FA3F:

        mov eax, dword ptr ds://的内容是初始时压栈的edi寄存器的值 后续会保存到虚拟机栈中
        add edi,4
        sub esi, 0x1
        movzx ecx, byte ptr ds:
        xor cl, bl
        inc cl
        neg cl
        add cl,0xB
        ror cl,1
        sub cl,0x8e
        neg cl
        xor cl,0x54
        dec cl
        xor bl, cl
        mov dword ptr ss:, eax //ecx=0x24 保存原始edi vmcontext.edi
        sub esi,4
        mov ecx, dword ptr ds:
        xor ecx, ebx
        sub ecx, 0x3E850F9
        rol ecx, 0x1
        sub ecx, 0x6BA107A2
        xor ecx, 0x53345A
        lea ecx, ds:
        neg ecx
        add ecx, 0x177B784E
        rol ecx, 0x1
        xor ebx, ecx
        add ebp, ecx
        jmp ebp //ebp=0x004710C6
```




依旧是部分指令,在这里我们可以先划出堆栈图,此时有朋友应该能发现刚开始压入的0x92124021不是随便的,而是会通过这个值进行解密得到opcode尾地址,并且复制给esi寄存器,为啥是尾地址呢,因为后续操作都是sub esi,4
sub esi,1 lea esi,,lea esi, 其中这两条lea指令是和sub指令等价的。还有两条指令也是我们需要注意的,lea esp,dword ptr ss:        //提升堆栈,为虚拟机分配堆栈和环境,lea ebp,dword ptr ds: // 可以将ebp理解为基址,因为在后面都是加ebp实现跳转,在得到opcode尾地址之后,就开始对opcode进行访问,然后解密,解密完成加上ebp得到最终要跳转的地址。



再仔细观察一下,应该会想到这些指令看着很复杂,但是终究就是算跳转地址,所做的真正有用的指令是有限的,比如mov dword ptr ss:, eax //ecx=0x2c 保存真实的elags寄存器到虚拟机的elags寄存器,初始化虚拟机环境 vmcontext.eflags这些指令,边做完有用的指令边计算跳转,那么在这条指令中ecx又是怎么来的呢,在上面所述的代码中ecx是一个偏移量,也是通过opcoede进行动态解密得到的,看下面的堆栈图



这些偏移都是通过opcode计算得出,理解成虚拟机的虚拟环境,一个特别的数据结构,这么一说是不是感觉有种拨开云雾的感觉,然后我跟着虚拟机的解密写了一部分的解密,太多了实现不想写了,因为原理就是这样了,解密opcode,得到跳转地址,偏移啥的

```
#include <stdio.h>
#include<windows.h>
UINT32 init_esi;
UINT32 init_ebp = 0x466596;
UINT32 CalcEsiBase(UINT32 key, UINT32 ecx) {
    key--;                  // dec esi
    key ^= 0x7733592f;      // xor esi, 0x7733592f
    key++;                  // inc esi
    key ^= 0x66bf7d8f;      // xor esi, 0x66bf7d8f
    key = (key << 3) | (key >> (32 - 3));// rol esi, 3
    key -= 0x1cb30155;      // sub esi, 0x1cb30115
    return key + ecx;       // lea esi,
}

UINT32 CalcJmpAddress(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp, UINT32 ecx) {
    ebx -= ecx;
    opcode_ptr -= 4;                         // sub esi, 4
    UINT32 eax = *reinterpret_cast<UINT32*>(opcode_ptr); // mov eax, (读取 DWORD)
    eax ^= ebx;                     // xor eax, ebx
    eax = ~eax;                     // not eax
    eax--;                            // dec eax
    eax = (eax >> 3) | (eax << (32 - 3)); // ror eax, 3
    eax = ~eax;                     // not eax
    eax = _byteswap_ulong(eax);   // bswap eax (GCC/Clang)
    eax -= 0x62320A2C;                // lea eax,
    ebx ^= eax;                     // xor ebx, eax
    initial_ebp += eax;                     // add ebp, eax

    return initial_ebp;                     // jmp ebp
}

UINT32 CalcRetAddress(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp) {
    opcode_ptr -= 4;               // sub esi, 0x4
    UINT32 ecx = *reinterpret_cast<UINT32*>(opcode_ptr); // mov ecx,

    ecx ^= ebx;                  // xor ecx, ebx
    ecx -= 0x3E850F9;            // sub ecx, 0x3E850F9
    ecx = (ecx << 1) | (ecx >> 31); // rol ecx, 1
    ecx -= 0x6BA107A2;             // sub ecx, 0x6BA107A2
    ecx ^= 0x53345A;               // xor ecx, 0x53345A
    ecx += 0x53205C24;             // lea ecx,
    ecx = (~ecx) + 1; ;                  // neg ecx
    ecx += 0x177B784E;             // add ecx, 0x177B784E
    ecx = (ecx << 1) | (ecx >> 31); // rol ecx, 1

    ebx ^= ecx;                  // xor ebx, ecx
    initial_ebp += ecx;            // add ebp, ecx

    return initial_ebp;            // 返回更新后的 ebp
}
UINT32 CalcEbpAddress(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp) {
    opcode_ptr -= 4;               // sub esi, 4
    UINT32 eax = *reinterpret_cast<UINT32*>(opcode_ptr); // mov eax,

    eax ^= ebx;                  // xor eax, ebx
    eax = (eax >> 3) | (eax << (32 - 3)); // ror eax, 3
    eax ^= 0x643A73B8;             // xor eax, 0x643A73B8
    eax--;                         // dec eax
    eax = ~eax;                  // not eax

    ebx ^= eax;                  // xor ebx, eax
    initial_ebp += eax;            // add ebp, eax

    return initial_ebp;            // 返回更新后的 ebp
}
UINT32 CalcEbpAddress2(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp) {
    opcode_ptr -= 4;               // sub esi, 0x4
    UINT32 eax = *reinterpret_cast<UINT32*>(opcode_ptr); // mov eax,

    eax ^= ebx;                  // xor eax, ebx
    eax = (eax << 3) | (eax >> (32 - 3)); // rol eax, 3
    eax++;                         // inc eax
    eax = _byteswap_ulong(eax);    // bswap eax (字节序反转)
    eax = (eax << 2) | (eax >> (32 - 2)); // rol eax, 0x2

    ebx ^= eax;                  // xor ebx, eax
    initial_ebp += eax;            // add ebp, eax

    return initial_ebp;            // 返回更新后的 ebp
}
UINT32 CalcEbpAddress3(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp) {
    opcode_ptr -= 4;               // sub esi, 4
    UINT32 ecx = *reinterpret_cast<UINT32*>(opcode_ptr); // mov ecx,

    ecx ^= ebx;                  // xor ecx, ebx
    ecx -= 0x7F4B67A2;             // sub ecx, 0x7F4B67A2
    ecx = ~ecx;                  // not ecx
    ecx--;                         // dec ecx
    ecx = ~ecx;                  // not ecx

    ebx ^= ecx;                  // xor ebx, ecx
    initial_ebp += ecx;            // add ebp, ecx

    return initial_ebp;            // 返回更新后的 ebp
}
UINT32 CalcEbpAddress4(UINT32& ebx, UINT32& opcode_ptr, UINT32& initial_ebp) {

    opcode_ptr -= 4;
    UINT32 ecx = *reinterpret_cast<UINT32*>(opcode_ptr);
    ecx ^= ebx;
    ecx = (ecx << 2) | (ecx >> 30);
    ecx += 0x3C7C310B;
    ecx = ~ecx + 1;
    ecx =_byteswap_ulong(ecx);
    ecx--;
    ebx ^= ecx;
    initial_ebp += ecx;
    return initial_ebp;
}
UINT32 CalcImm32(UINT32& ebx, UINT32& opcode_ptr) {
    opcode_ptr -= 4;               // sub esi, 0x4
    UINT32 ecx = *reinterpret_cast<UINT32*>(opcode_ptr); // mov ecx,

    ecx ^= ebx;                  // xor ecx, ebx
    ecx--;                         // dec ecx
    ecx = _byteswap_ulong(ecx);    // bswap ecx (字节序反转)
    ecx ^= 0x408E4C0B;             // xor ecx, 0x408E4C0B
    ecx -= 0x7FF90503;             // sub ecx, 0x7FF90503

    ebx ^= ecx;                  // xor ebx, ecx

    return ecx;                  // 返回计算后的ecx值
}
UINT32 DecodeImm32(UINT32& ebx, UINT32& opcode_ptr) {
    opcode_ptr -= 4;
    UINT32 ecx = *reinterpret_cast<UINT32*>(opcode_ptr);
    ecx ^= ebx;
    ecx--;
    ecx = _byteswap_ulong(ecx);
    ecx ^= 0x408E4C0B;
    ecx -= 0x7FF90503;
    ebx ^= ecx;
    return ecx;
}
UINT8 opcode[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xE4, 0x8F, 0xA9, 0x3A, 0x59, 0x37, 0xD8, 0x75, 0x8D, 0x2A,
0x98, 0xAB, 0xCB, 0x10, 0x40, 0xE8, 0xBE, 0x81, 0x5A, 0x99, 0xEB, 0x74, 0x4B, 0xA0, 0x7F, 0xFA,
0x37, 0xA8, 0x4B, 0x2C, 0x1F, 0xB6, 0x0D, 0x61, 0x4C, 0x01, 0xF1, 0xA5, 0xF1, 0x04, 0x57, 0x67,
0x0C, 0xD1, 0xD7, 0x72, 0x8D, 0xA8, 0x82, 0x1F, 0xBA, 0xC8, 0x6B, 0xC8, 0xD2, 0x85, 0x67, 0x7F,
0xF0, 0x9B, 0x00, 0x00, 0x00, 0x3B, 0xB3, 0x67, 0x08, 0xF7, 0xCF, 0xC4, 0x36, 0x08, 0x3B, 0x25,
0xB9, 0x89, 0xF7, 0x35, 0x40, 0xC7, 0x40, 0x08, 0x40, 0xA8, 0x4D, 0x3F, 0x08, 0x54, 0x0D, 0x8B,
0x92, 0xF7, 0xD5, 0x65, 0x5E, 0xCB, 0xF7, 0xC1, 0x5E, 0x5D, 0xE2, 0xF7, 0x66, 0x23, 0x5A, 0xDC,
0x24, 0x03, 0xE2, 0xEF, 0xD5, 0x2A, 0xF0, 0x4C, 0x10, 0x05, 0x3E, 0xF9, 0x8B, 0x33, 0x42, 0xB5,
0x12, 0x6F, 0xBB, 0x94, 0xEB, 0x35, 0xFC, 0x23, 0x0B, 0xF0, 0x7E, 0x14, 0x3E, 0x08, 0x86, 0xAA,
0x66, 0x47, 0x08, 0x6E, 0xA8, 0xE2, 0xFC, 0x4B, 0x79, 0x16, 0x80, 0xFE, 0xCB, 0xA2, 0x7D, 0x7F,
0x0E, 0x6F, 0x46, 0xD7, 0x38, 0x08, 0x8A, 0xA1, 0x46, 0xF1, 0x05, 0xE2, 0x23, 0xE1, 0xD3, 0xD1,
0xDA, 0xF8, 0x4B, 0x9A, 0x3C, 0x73, 0xEA, 0x30, 0x03, 0x9B, 0xF2, 0xF7, 0x0E, 0x1A, 0x30, 0x98,
0xD1, 0xDF, 0xF9, 0x1B, 0xCF, 0xEE, 0xD1, 0xF1, 0x80, 0x2D, 0x31, 0xD3, 0xDB, 0x86, 0xD9, 0x0C,
0x0C, 0x5D, 0x6E, 0x7A, 0xDE, 0x82, 0x29, 0x7C, 0x64, 0x3A, 0xB3, 0x7C, 0x00, 0x24, 0x4D, 0x07,
0xFC, 0xFE, 0x5B, 0xA5, 0x8B, 0xD3, 0xFF, 0x9B, 0xEF, 0xB3, 0xFC, 0xFD, 0x9B, 0x49, 0x75, 0xBE,
0x03, 0x24, 0xE7, 0x39, 0xD5, 0x85, 0xDB, 0x21, 0x9A, 0xE9, 0xF9, 0x1B, 0x7D, 0x5A, 0x0C, 0xFB,
0xDB, 0x86, 0x3D, 0xBE, 0xFF, 0x9B, 0x70, 0x01, 0x22, 0x7C, 0xE4, 0xF9, 0xB7, 0x50, 0x39, 0x13
};

UINT32 CalcOffset(UINT32& opcode_ptr, UINT32& ebx) {
   
    opcode_ptr--;
    UINT8 cl = *reinterpret_cast<UINT8*>(opcode_ptr);      // movzx ecx, byte ptr
    UINT8 bl = ebx & 0xFF;       // 取 key 的低 8 位 (bl)

    cl ^= bl;                      // xor cl, bl
    cl++;                         // inc cl
    cl = -cl;                      // neg cl (cl = ~cl + 1)
    cl += 0xB;                     // add cl, 0xB
    cl = (cl >> 1) | (cl << 7);    // ror cl, 1 (循环右移 1 位)
    cl -= 0x8E;                  // sub cl, 0x8E
    cl = -cl;                      // neg cl
    cl ^= 0x54;                  // xor cl, 0x54
    cl--;                        // dec cl
    bl ^= cl;                      // xor bl, cl (更新 key 的低 8 位)
    ebx = (ebx & 0xFFFFFF00) | bl; // 更新 key 的低 8 位
    return cl;                     // 返回计算后的 cl
}
int main() {
    init_esi = CalcEsiBase(0x92124021, 0);
    UINT32 init_ebx = init_esi;
    UINT32 opcode_ptr = reinterpret_cast<UINT32>(opcode) + sizeof(opcode);   
    UINT32 jmpebp = CalcJmpAddress(init_ebx, opcode_ptr, init_ebp, 0);
    printf("jmpebp:%x\n", jmpebp);
    UINT32 offset = CalcOffset(opcode_ptr, init_ebx);
    printf("ecxoffset:%x\n", offset);
    for (int i = 0; i < 11;i++) {
      jmpebp = CalcRetAddress(init_ebx, opcode_ptr, init_ebp);
      printf("retebp:%x\n", jmpebp);
      if (i == 10) {
            break;
      }
      offset = CalcOffset(opcode_ptr, init_ebx);
      printf("ecxoffset:%x\n", offset);
    }
    jmpebp = CalcEbpAddress(init_ebx, opcode_ptr, init_ebp);
    printf("ebpaddress:%x\n", jmpebp);
    UINT32 imm32 = CalcImm32(init_ebx, opcode_ptr);
    printf("imm32:%x\n", imm32);
    jmpebp = CalcEbpAddress2(init_ebx, opcode_ptr, init_ebp);
    printf("ebpaddress:%x\n", jmpebp);
    jmpebp = CalcEbpAddress3(init_ebx, opcode_ptr, init_ebp);
    printf("ebpaddress:%x\n", jmpebp);
    offset = CalcOffset(opcode_ptr, init_ebx);
    printf("ecxoffset:%x\n", offset);
    jmpebp = CalcRetAddress(init_ebx, opcode_ptr, init_ebp);
    printf("retebp:%x\n", jmpebp);
    jmpebp = CalcEbpAddress4(init_ebx, opcode_ptr, init_ebp);
    printf("ebpaddress:%x\n", jmpebp);
    imm32 = DecodeImm32(init_ebx, opcode_ptr);
    printf("imm32:%x\n", imm32);
    jmpebp = CalcEbpAddress2(init_ebx, opcode_ptr, init_ebp);
    printf("ebpaddress:%x\n", jmpebp);
    offset = CalcOffset(opcode_ptr, init_ebx);
    printf("ecxoffset:%x\n", offset);
    jmpebp = CalcRetAddress(init_ebx, opcode_ptr, init_ebp);
    printf("retebp:%x\n", jmpebp);
    imm32 = DecodeImm32(init_ebx, opcode_ptr);
    printf("imm32:%x\n", imm32);
        return 0;
}
```

跳转算法有很多,直接扣汇编给ai,让ai实现就好了,从初始esi之前复制,扣出opcode进行解密,已经可以看到了立即数0x10000000和0x1234



又可以对上述汇编指令进行进一步优化 将计算跳转的指令,计算偏移的指令,计算立即数的指令全部优化掉可以得到更精简的指令

```
        push 0x402110
        push 0x92124021
        push 0x440841
       
        push ebx
        push ecx
        push ebp
        push eax
        push esi
        push edx
        push edi
        pushfd       
        mov ecx,0
        push ecx
        //mov esi, 0x4023a7
        mov edi,esp
        lea esp, ss:        //        提升堆栈 为虚拟机分配所需内存
       
        lea ebp, ds:        //        跳转基址
       
        mov eax, dword ptr ds:                //0 压入的ecx
        add edi,4
        mov dword ptr ss:, eax
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //初始context.eflags
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.edi
       
        mov eax, dword ptr ds:        
        add edi,4
        mov dword ptr ss:, eax //context.edx
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.esi
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.eax
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.ebp
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.ecx
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //context.ebx
       
        mov eax, dword ptr ds:               
        add edi,4
        mov dword ptr ss:, eax //0x440841 最开始的call的返回,其实没啥用
       
        mov eax, dword ptr ds:       
        add edi,4
        mov dword ptr ss:, eax////0x92124021 用于解密opcode的数据,现在也没啥用
       
       
        //上面是保存现场环境
        mov edx,edi
        sub edi,4
        mov dword ptr ds:, edx
       
        sub edi,4
        mov dword ptr ds:,4
        mov ecx, dword ptr ds: //mov ecx,4
        mov edx, dword ptr ds:
        add ecx,edx
        mov dword ptr ds:, ecx //ecx = old_esp 被vm之前的esp (第一条push指令(push 0x402110)前的esp) 感觉这几行代码完全没有意义
       
        pushfd
        pop dword ptr ss:
        mov eax, dword ptr ds:                //一个eflags的值 依旧没意义
       
        add edi,4
        mov dword ptr ss:, eax //一个毫无意义的eflags,可能是为了占位esp+0x3c
        mov edi, dword ptr ds:                //edi = old_esp
       
        //解密得到imm32,解密步骤直接省略
        mov ecx,0x10000000
        sub edi,4
        mov dword ptr ds:, ecx        //ecx=0x10000000
       
        mov eax, dword ptr ds:
        mov dword ptr ss:, eax         //0x10000000
       
        //解密得到imm32
        mov ecx,0x1234
        sub edi,4
        mov dword ptr ds:, ecx
       
        //将解密得到的数据写入栈,覆盖之前的数据
       
        mov eax, dword ptr ss:         //        取出0x10000000
        sub edi,4
        mov dword ptr ds:, eax
       
        mov ecx, dword ptr ds:                //0x10000000
        mov edx, dword ptr ds:                //0x1234
        add ecx, edx        //        求和
        mov dword ptr ds:, ecx        //保存求和后的结果到栈
       
        //由于add指令会影响eflgas寄存器 得到求和之后的eflags
        pushfd
        pop dword ptr ss:
       
       
        mov eax, dword ptr ds:                //求和之后的eflags
        add edi,4
        mov dword ptr ss:, eax //求和之后的eflags覆盖之前的垃圾数据(0x92124021) vmcontext.eflags
       
        mov eax, dword ptr ds: //求和后的结果
        add edi,4
        mov dword ptr ss:, eax        //将得到的0x10001234覆盖之前的无意义eflags 后续会发现esp+0x3c是vmcontext.eax
       
        mov eax, dword ptr ss:
        sub edi,4
        mov dword ptr ds:, eax        //无意义的指令 因为此时的值就是0x10001234 可能单纯为了sub edi,4?猜测
       
       
        mov eax, dword ptr ss:   //vmcontext.ebp
        sub edi,4
        mov dword ptr ds:, eax      //vmcontext.ebp
        sub edi,4
       
        //就是是解密imm32 可能突然觉得这个立即数毫无头绪,但是模拟的指令有一条是mov dword ptr ss:, eax
        //这样看是不是就有什么发现了
        mov ecx,0xFFFFFFE8
       
        mov dword ptr ds:, ecx         //0xFFFFFFE8或者-0x18
        mov ecx, dword ptr ds:   
        mov edx, dword ptr ds:        //        ebp
        add ecx,edx                                                //得到ebp-0x18的地址
        mov dword ptr ds:, ecx //保存ebp-0x18的地址
       
       
        pushfd
        pop dword ptr ss:        //上面有add指令,依旧是保存eflags,但是没啥用其实
        mov eax, dword ptr ds:         //eflags
        add edi,4
        mov dword ptr ss:, eax        //eflags
       
        mov edx, dword ptr ds:   //此时edx=ebp-0x18
        mov eax, dword ptr ds:        //取出0x10001234
        mov dword ptr ss:, eax        //完成mov dword ptr ss:, eax的虚拟化
       
        //解密得到返回地址
        mov ecx,0040102E
       
        sub edi,4
        mov dword ptr ds:, ecx    //此时ecx的值恰好是虚拟机返回地址
       
        //后面将虚拟寄存器转化成特殊的数据结构,从低地址到高地址分别是
        /*
        eflags
        edi
        edx
        esi
        eax=0x10001234
        ebp
        ecx
        ebx
        0x40102e //解密得到的返回地址
        */
        //与后面的pop reg刚好是对应的
       
        mov eax, dword ptr ss:
        sub edi,4
        mov dword ptr ds:, eax //vmcontext.ebx
       
        mov eax, dword ptr ss:
        sub edi,4
        mov dword ptr ds:, eax        //vmcontext.ecx
       
        mov eax, dword ptr ss:
        sub edi,4
        mov dword ptr ds:, eax //vmcontext.ebp
       
        mov eax, dword ptr ss:       
        sub edi,4
        mov dword ptr ds:, eax        //应该是vmcontext.eax
       
        mov eax, dword ptr ss:               
        sub edi,4
        mov dword ptr ds:, eax        //vmcontext.esi
       
        mov eax, dword ptr ss:               
        sub edi, 0x4
        mov dword ptr ds:, eax        //vmcontext.edx
       
        mov eax, dword ptr ss:               
        sub edi,4
        mov dword ptr ds:, eax        //vmcontext.edi
       
        mov eax, dword ptr ss:
        sub edi,4
        mov dword ptr ds:, eax                        //vmcontext.eflags
       
        mov esp,edi                        //设置esp寄存器 准备恢复寄存器的值到真实寄存器
       
        //恢复现场
        popfd
        pop edi
        pop edx
        pop esi
        pop eax
        pop ebp
        pop ecx
        pop ebx
        ret
       
//至此,整个虚拟化的分析大概完成
```

至此,整个虚拟化的流程大概分析完毕了,vmp就是边解密边执行再加上各种的垃圾指令很容易懵逼,刚开始接触vm,依旧有很多不足之处,仍需继续努力

284406022 发表于 2025-5-22 14:31

非常好, 既有原理,又有实战, 对于我这样只懂逆向无壳的人来说:是看懂了的,以后学习逆向vmp,希望交流和分享

likemian 发表于 2025-5-18 21:55

牛B啊猫猫

wsck63304521 发表于 2025-5-19 00:03

现在很多大佬都玩vm中直接爆破

schm168 发表于 2025-5-19 05:30

有个虚拟问题很想请教大师指点

GoogleHacking 发表于 2025-5-19 07:59

楼主用的这啥软件,olldbg吗?

mengxz2023 发表于 2025-5-19 08:24

太牛掰了。

onetwo888 发表于 2025-5-19 08:56

感谢大神分享!!!!!!!

mhdythaha 发表于 2025-5-19 10:10

牛掰了。

linlr 发表于 2025-5-19 17:31

对入门新手很友好。学习了,感谢

qlqd 发表于 2025-5-20 07:27

技术流,NB!
页: [1] 2 3 4
查看完整版本: 关于vmp3.2的一点研究心得