舒默哦 发表于 2020-7-14 18:23

[vc]代码虚拟机设计

本帖最后由 舒默哦 于 2020-7-14 18:35 编辑

https://static.52pojie.cn/static/image/hrline/1.gif


0x00
具体可以参考《加密与解密》第四版 第二十章。这章内容有点抽象,可以画堆栈图,来辅助分析。

https://static.52pojie.cn/static/image/hrline/1.gif

0x01
代码虚拟化,我的理解就是把一条指令分解,变形膨胀,本质还是代码混淆。只要代码进入虚拟机,就是八仙过海,各显神通了。
举个例子:
void _declspec(naked) code_vm_test()
{
    _asm {

      mov eax,10h
      mov ebx,30h
      add eax,ebx
      mov g_num,eax
      retn
    }
}
上面这个函数加上VM,没加垃圾指令,VM后的代码如下:
mov eax, 0x0
shl eax, 0x2
mov eax, dword ptr ds :
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x10
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, dword ptr ss :
mov ebx, dword ptr ss :
mov eax, ebx
add esp, 0x8
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x0
pop dword ptr ds :
mov eax, filenew.0040A500
jmp eax
mov eax, 0x3
shl eax, 0x2
mov eax, dword ptr ds :
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x30
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, dword ptr ss :
mov ebx, dword ptr ss :
mov eax, ebx
add esp, 0x8
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x3
pop dword ptr ds :
mov eax, filenew.0040A500
jmp eax
mov eax, 0x0
shl eax, 0x2
mov eax, dword ptr ds :
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x3
shl eax, 0x2
mov eax, dword ptr ds :
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, dword ptr ss :
mov ebx, dword ptr ss :
add eax, ebx
add esp, 0x8
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x0
pop dword ptr ds :
mov eax, filenew.0040A500
jmp eax
mov eax, filenew.00406030
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, 0x0
shl eax, 0x2
mov eax, dword ptr ds :
push eax
mov eax, filenew.0040A500
jmp eax
mov eax, dword ptr ss :
mov ebx, dword ptr ss :
mov dword ptr ds : , ebx
add esp, 0x8
mov eax, filenew.0040A500
jmp eax
mov eax, 0x0
mov ebx, dword ptr ss :
add ebp, eax
push ebx
push ebp
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
push dword ptr ds :
pop eax
pop ecx
pop edx
pop ebx
pop ebp
pop esi
pop edi
popfd
pop esp
retn


https://static.52pojie.cn/static/image/hrline/1.gif

0x03
编写流程,可以围绕三个handler来写(普通handler、操作数handler、pop目的操作数的handler),普通handler就是指令handler,操作数handler包括目的操作数handler和源操作数handler。
虚拟机并不完善,因为时间原因,只完成了第一步,没有处理JCC指令、call指令、特殊指令、以及一些改变标志位的指令,以后有时间处理好了我会发布在github上。
测试exe是code_vm_test.exe,源文件:
#include <iostream>
#include <Windows.h>
using namespace std;
int g_num = 0;

void _declspec(naked) code_vm_test()
{
    //MessageBoxA(NULL, 0, 0, 0);
    _asm {

      mov eax,10h
      mov ebx,30h
      add eax,ebx
      mov g_num,eax
      retn
    }
}

int main()
{
    code_vm_test();
    std::cout << g_num << endl;
    system("pause");
}

https://static.52pojie.cn/static/image/hrline/5.gif


项目源码传上来了,整个加VM的流程放到一个CPP中:
#include <iostream>
#include <list>
#include "vmtest.h"
#include "ASM/disasm.h"
#include "Common/header.h"

//存放wmcode的代码的链表
list<VHandler> g_vHandlerList;

int g_total = 0;//记录有多少个handler;
int g_retnaddr = 0;//记录返回地址

//编译生成的Handler代码,并添加到Handler
BOOL CompileHandler(char* asmtext)
{
    char Code = { 0 };//暂存机器码
    int codelen = 0;
    char errtext = { 0 };
    t_asmmodel am;
    char linestr = { 0 };
    int len = strlen(asmtext);
    for (int i = 0; i < len;)
    {
      CString str =asmtext;
      int calc = str.LeftFindChar(asmtext+i, '\n');      
      memset(linestr, 0, TEXTLEN);
      memcpy(linestr, asmtext+i, calc - 1);
      Assemble(linestr, 0, &am, 0, 4, errtext);
      memcpy(Code + codelen, am.code, am.length);
      codelen += am.length;
      i += calc;
    }
    VHandler temp_vhandler;
    memcpy(temp_vhandler.AssembleCode, Code, codelen);
    temp_vhandler.CodeLen = codelen;
   
    g_vHandlerList.push_back(temp_vhandler);
}

int GetScalestr(int bit, OUT char* scalestr)
{
    int sizeidx = 0;
    if (bit == 8)
    {
      sizeidx = 0;
      strcpy_s(scalestr, 6, "byte");
    }
    else if (bit == 16)
    {
      sizeidx = 1;
      strcpy_s(scalestr, 6, "word");
    }
    else if (bit == 32)
    {
      sizeidx = 2;
      strcpy_s(scalestr, 6, "dword");
    }
    return sizeidx;
}

//处理普通handler
void GeneralHandler(t_disasm disasm,int tab_index,int opt_num)
{
   
    CString str_general;
    char optnum = { 0 };
    if (opt_num == 2)//双操作数
    {
      str_general = "mov eax, \n";
      str_general += "mov ebx, \n";
      str_general += vmtable.strInstruction;
      if (vmtable.optype== MEMTYPE)//如果第一个操作数是内存地址
      {         
            int sizeidx = GetScalestr(vmtable.bitnum, optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr ,bl\n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr ,bx\n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr ,ebx\n";
            }
            str_general += "add esp, 8\n";
      }
      else if (vmtable.optype == MEMTYPE)//如果第二个操作数是内存地址
      {
            int sizeidx = GetScalestr(vmtable.bitnum, optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " bl,"+ optnum+" ptr \n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " bx," + optnum + " ptr \n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " ebx," + optnum + " ptr \n";
            }
            str_general += "add esp, 8\n";
            str_general += "push ebx\n";
      }
      else
      {
            str_general += " eax,ebx \n";
            str_general += "add esp, 8\n";
            str_general += "push eax\n";
      }

    }
    if (opt_num == 1)//单操作数
    {
      str_general = "mov ebx, \n";
      str_general += vmtable.strInstruction;
      if ( vmtable.optype == MEMTYPE)
      {
            int sizeidx = GetScalestr(vmtable.bitnum, optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr \n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr \n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr \n";
            }
      }
      else
      {
            str_general += " ebx\n";
      }
      
      str_general += "add esp, 4\n";
      if (vmtable.optype == REGTYPE)
      {
            str_general += "push ebx\n";
      }
    }

    if (opt_num == 0)//没有操作数
    {
      //判断是不是retn
      if (_stricmp("retn", vmtable.strInstruction)==0)
      {
            int retnlength = GetFunSize(vRetn);
            AllocMemory _alloc;
            char* tempbuff = _alloc.auto_malloc<char*>(retnlength);

            memcpy(tempbuff, vRetn, retnlength);

            //修改vRetn的操作数
            *(DWORD*)(tempbuff + 1) = disasm.immconst;

            VHandler temp_vhandler;
            temp_vhandler.CodeLen = retnlength;
            memcpy(temp_vhandler.AssembleCode, tempbuff, retnlength);


            //添加到链表
            g_vHandlerList.push_back(temp_vhandler);

            return;
      }
      else
      {
            str_general = vmtable.strInstruction;
      }

    }
    str_general += "\nmov eax, 0x66668888\njmp eax\n";

    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_general.GetString());
}

//pop目的操作数的handler
void PopHandler(t_disasm disasm, int tab_index, int opt_index)
{
    CString str_pop;
    //1、处理辅助handler
    int t1 = vmtable.optype;
    switch (t1)
    {
    case NONETYPE: //无操作数

      break;
    case IMMTYPE: //立即数
    {

    }

      break;
    case REGTYPE: //寄存器
    {
      unsigned int regnum = disasm.reg;
      str_pop = str_pop+ "mov eax," + disasm.reg + "\n";

      if (vmtable.bitnum == 8)//操作数为8位
      {
            //这儿判断高位(ah)还是低位(al)
            if (true == disasm.highbit)
            {
                str_pop += "xor ebx,ebx\n";
                str_pop += "mov bx,word ptr \n";
                str_pop += "and bx, 0xFF00\n";
                str_pop += "mov word ptr, 0\n";
                str_pop += "pop dword ptr \n";
                str_pop += "or dword ptr ,ebx\n";
            }
            else
            {
                str_pop += "xor ebx,ebx\n";
                str_pop += "mov bx,word ptr \n";
                str_pop += "and bx, 0xFF\n";
                str_pop += "mov word ptr, 0\n";
                str_pop += "pop dword ptr \n";
                str_pop += "or dword ptr ,ebx\n";
            }
      }
      if (vmtable.bitnum == 16)//操作数为16位
      {
            str_pop += "pop word ptr\n";
      }
      if (vmtable.bitnum == 32)//操作数为32位
      {
            str_pop += "pop dword ptr\n";
      }
      str_pop += "mov eax, 0x66668888\njmp eax\n";
    }
    break;
    case MEMTYPE: //内存(包括段寄存器)
      //2、判断是否是段寄存器
      if (vmtable.Segment != 0)
      {
         
      }
      else
      {

      }

      break;

    default:
      break;
    }
    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_pop.GetString());
}

//处理辅助handler
void AssistHandler(t_disasm disasm,int tab_index,int opt_index)
{
    //1、处理辅助handler
    CString str_assist;
    int t1 = vmtable.optype;
    switch (t1)
    {
    case NONETYPE: //无操作数

      break;
    case IMMTYPE: //立即数
    {
      str_assist = "mov eax,";
      char cstr;
      sprintf_s(cstr, 56, "%X", disasm.immconst);//十进制数字转换为十六进制字符串
      str_assist += cstr;
    }

      break;
    case REGTYPE: //寄存器
    {
      str_assist = "mov eax,";

      str_assist = str_assist +disasm.reg+"\nshl eax,2\n";

      if (vmtable.bitnum==8)//操作数为8位
      {
            //这儿判断高位(ah)还是低位(al)
            if (true == disasm.highbit)
            {
                str_assist += "mov ah,byte ptr";
            }
            else
            {
                str_assist += "mov al,byte ptr";
            }
      }
      if (vmtable.bitnum == 16)//操作数为16位
      {
            str_assist += "mov ax,word ptr";
      }
      if (vmtable.bitnum == 32)//操作数为32位
      {
            str_assist += "mov eax,dword ptr";
      }

    }
    break;
    case MEMTYPE: //内存(包括段寄存器)
      //2、判断是否是段寄存器
      if (vmtable.Segment != -1)
      {
            //段寄存器不用查看操作数的位数,直接翻译
            int index = vmtable.optype;
            const char* reg = vregname;
            
            //构造辅助handler
            str_assist = "mov eax,";
            int decnumb = disasm.adrconst;
            char cstr;      
            sprintf_s(cstr, 56, "%X", decnumb);//十进制数字转换位十六进制字符串
            str_assist = str_assist + reg + ":["+ cstr +"]";   
      }
      else
      {
            str_assist = "mov eax,";
            int decnumb = disasm.adrconst;
            char cstr;
            sprintf_s(cstr, 56, "%X", decnumb);//十进制数字转换位十六进制字符串
            str_assist = str_assist + cstr;
      }

      break;
      
    default:
      break;
    }

    str_assist += "\npush eax\nmov eax,0x66668888\njmp eax\n";
    printf("%s\n", str_assist.GetString());

    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_assist.GetString());
}

void testvmp(char* TargetCode,_Out_ int &CodeLength)
{
    /*char TargetCode[] = {
      0xC2,0x08,0x00,
         0xFE ,0x05,0x00,0x10,0x40,0x00,
      0xBB,0x20,0x00,0x00,0x00,
      0x8A,0x24,0x24,
      0x8B,0xC3,
      0x64 ,0xA1 ,0x30 ,0x00 ,0x00 ,0x00,
      0x66, 0xA1 ,0x00 ,0x10 ,0x40 ,0x00
    };*/
    t_disasm disasm;
    Disasm(TargetCode, 20, (ulong)TargetCode, &disasm, 3);
    CodeLength = disasm.codelen;
    int index = -1;

    for (int i = 0; i < VMTABLEMAXLEN; i++)
    {   
      if (0 == strcmp(vmtable.VMInstrName, disasm.vm_name))
      {
            printf("%s\n", vmtable.strInstruction);
            if (_stricmp(vmtable.strInstruction,"retn")==0)
            {
                GeneralHandler(disasm, i, 0);
                return;
            }
            index = i;
            break;
      }      
    }
    if (-1 != index)
    {
      //1、构造辅助handler
      
      if (-1 != vmtable.optype)//目的操作数
      {
            AssistHandler(disasm, index, 0);
      }
      
      if (-1 != vmtable.optype)//源操作数
      {
            AssistHandler(disasm, index, 1);
      }

      //2、普通handler处理
      int opt_num = (vmtable.optype ? 1 : 0) + (vmtable.optype ? 1 : 0);
      GeneralHandler(disasm,index, opt_num);

      //3、pop到目的操作数
      if (opt_num)//如果有目的操作数才处理
      {
            PopHandler(disasm, index, 0);
      }
      
    }
}

#define VMSTART (0x2000)    //.vmp1节的前0x2000个字节留给VStartVM函数和其他需要处理的东西
#define VMTABLE (0x1000)    //.vmp1节的0x1000地址处存放跳转表
#define VMDISPATCH (0x500) //.vmp1节的0x500地址处存放调度器
int g_handleraddr_calc = 0;//记录handler存放到哪里了
int g_table_calc = 0;//记录跳转表存放到哪里了
//处理g_vHandlerList链表
void SolutionWmcode(char* filebuff,PEInfo peinfo)
{
    PE pe;
    ULONG_PTR ulSizeOfImage =peinfo.ImageBase + pe.AlignSize(peinfo.SizeofImage, peinfo.SectionAlignment);//.vmp1节的开始地址
    ULONG_PTR vHandlerAddr = (ULONG_PTR)(filebuff + VMSTART+ g_handleraddr_calc);//handler存放的地址
    ULONG_PTR vJmpTable = (ULONG_PTR)(filebuff + VMTABLE+ g_table_calc);//跳转表
    ULONG_PTR vMmDispatcher = ulSizeOfImage + VMDISPATCH;//调度器地址
    list<VHandler>::iterator iter = g_vHandlerList.begin();
    for (int i = 0; i < g_vHandlerList.size(); i++)
    {
      int codelength = (*iter).CodeLen;
      char* assemblecode = (*iter).AssembleCode;

      //修复handler跳转到调度器的地址
      *(DWORD*)(assemblecode + codelength - 6) = vMmDispatcher;

      //把handler的地址存入跳转表
      *(WORD*)vJmpTable = g_handleraddr_calc;

      //存入handler代码
      memcpy((char*)vHandlerAddr, assemblecode, codelength);

      vHandlerAddr += codelength;
      vJmpTable += 2;
      g_table_calc += 2;
      g_handleraddr_calc += codelength;
      ++iter;
      ++g_total;//记录handler的个数
    }
    g_vHandlerList.clear();
}


#define path_in "C:\\Users\\86188\\Desktop\\code_vm_test.exe"
#define path_out "C:\\Users\\86188\\Desktop\\加了VM的code_vm_test.exe"
//加载文件,加vm
void LoadFile(char* path)
{
    AllocMemory _alloc;
    PEInfo peinfo;
    PE pe;
    pe.GetPEInformation_(path, &peinfo);

    //去掉随机基址
    peinfo.OptionalHeader->DllCharacteristics = 0;

    //拉伸文件
    ULONG_PTR StretchAddr = pe.StretchFile(peinfo.FileBuffer, peinfo.SizeofImage);
    //更新peinfo信息
    pe.GetPEInformation_1((char*)StretchAddr, &peinfo, peinfo.FileSize);

    //申请内存
    int newsectionsize = 0x1000 * 20;//.vmp1所在节的大小,设置为80k
    char* Vmp1_Buff = _alloc.auto_malloc<char*>(newsectionsize);

    //选择要vm的起始和结束地址
    ULONG_PTR StartAddr = 0x401110 - peinfo.ImageBase + StretchAddr;
    ULONG_PTR EndAddr = 0x401122 - peinfo.ImageBase + StretchAddr;
    int CodeLength = 0;
    int TotelLength = EndAddr - StartAddr;
    for (int i = 0; i < TotelLength;)
    {
      testvmp((char*)StartAddr+i, CodeLength);//把code转换为WMcode
      SolutionWmcode(Vmp1_Buff, peinfo);//处理g_vHandlerList链表
      i += CodeLength;
    }

//#define VMSTART (0x2000)    //.vmp1节的前0x2000个字节留给VStartVM函数和其他需要处理的东西
//#define VMTABLE (0x1000)    //.vmp1节的0x1000地址处存放跳转表
//#define VMDISPATCH (0x500) //.vmp1节的0x500地址处存放调度器

    ULONG_PTR ulSizeOfImage = peinfo.ImageBase + pe.AlignSize(peinfo.SizeofImage, peinfo.SectionAlignment);//.vmp1节的开始地址

    ULONG_PTR ulDispatcherAddr = (ULONG_PTR)Vmp1_Buff + VMDISPATCH;

    //填充VStartVM
    int start_len = GetFunSize(VStartVM);
    memcpy(Vmp1_Buff, VStartVM, start_len);
    //修复VMstartVM里的数据
    *(ULONG_PTR*)(Vmp1_Buff + start_len - 6) = ulSizeOfImage + VMDISPATCH;
    *(ULONG_PTR*)(Vmp1_Buff + start_len - 11) = ulSizeOfImage + VMTABLE;

    //填充调度器
    int dispatcher_len = GetFunSize(VMDispatcher);
    memcpy(Vmp1_Buff+ VMDISPATCH, VMDispatcher, dispatcher_len);
    //修复调度器里数据
    *(ULONG_PTR*)(Vmp1_Buff + VMDISPATCH + 1) = ulSizeOfImage + VMSTART;

    //抹掉目标文件里加了vm的代码
    for (int i = 0; i < TotelLength; i++)
    {
      *(char*)(StartAddr + i) = 0x90;
    }

    //在目标文件添加跳转地址
    int jump_len = GetFunSize(JumpToVmp);
    memcpy((char*)StartAddr, JumpToVmp, jump_len);
    //修复数据
    *(ULONG_PTR*)(StartAddr + 1) = ulSizeOfImage;

    //还原成源文件大小
    char* filebuff = pe.ImageBuff_To_FileBuff((char*)StretchAddr, peinfo.FileSize);

    //添加新节
    pe.addSeciton((ULONG_PTR)filebuff, newsectionsize, (char*)".vmp1");

    //申请内存,合并节
    char* NewFileBuff = _alloc.auto_malloc<char*>(newsectionsize + peinfo.FileSize);
    memcpy(NewFileBuff, filebuff, peinfo.FileSize);
    memcpy(NewFileBuff + peinfo.FileSize, Vmp1_Buff, newsectionsize);

    //保存文件
    FileOperation fileopt;
    fileopt.SaveFile_(NewFileBuff, peinfo.FileSize + newsectionsize, (char*)path_out);
}

//主函数
int main()
{
    //加载文件
    LoadFile((char*)path_in);

    std::cout << "Hello World!\n";
    system("pause");
}

renny 发表于 2020-7-15 19:50

学习了,很深奥{:1_921:}

andersgong 发表于 2020-8-5 08:25

https://www.cnblogs.com/john-d/archive/2009/12/05/1617710.html
可以看看这篇帖子,作者写的也是非常详细的。

fancotse 发表于 2020-8-5 08:56

高深莫测,云里雾里

prontosil 发表于 2020-8-3 18:31

赞一个,原来可以这么设计虚拟机

亲爱的靳萌萌 发表于 2020-8-3 13:38

学到了 楼主厉害感谢分享

不凡_不懂 发表于 2020-8-1 09:38

学到了学到了,楼主牛逼

coder014 发表于 2020-8-1 09:47

楼主厉害{:1_921:} 学习了

xueli 发表于 2020-7-31 10:22

谢谢分享

李斯隆 发表于 2020-7-30 22:07

好好学习!。

chboy 发表于 2020-7-14 19:33

担心违规 内容已删除

Mirana 发表于 2020-7-14 20:19

支持一下

舒默哦 发表于 2020-7-14 20:50

chboy 发表于 2020-7-14 19:33
担心违规 内容已删除

要不得老铁

二娃 发表于 2020-7-14 22:22

我这几天刚好也在写这东西
头都裂开了

舒默哦 发表于 2020-7-14 23:15

二娃 发表于 2020-7-14 22:22
我这几天刚好也在写这东西
头都裂开了

思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改&#129300;

二娃 发表于 2020-7-15 00:40

舒默哦 发表于 2020-7-14 23:15
思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改&#129300;

要考虑的东西挺多的

chboy 发表于 2020-7-15 09:11

舒默哦 发表于 2020-7-14 20:50
要不得老铁

我准备说你一发帖就置顶了有点牛逼 但是发出后担心违规就删除了{:301_1004:}

caicaiwuguo 发表于 2020-7-15 12:42

高深莫测,云里雾里

Lixinist 发表于 2020-7-15 15:11

{:301_998:}我在写代码变异,比vm好写点
页: [1] 2 3 4 5 6 7 8
查看完整版本: [vc]代码虚拟机设计