[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");
}
学习了,很深奥{:1_921:} https://www.cnblogs.com/john-d/archive/2009/12/05/1617710.html
可以看看这篇帖子,作者写的也是非常详细的。 高深莫测,云里雾里 赞一个,原来可以这么设计虚拟机 学到了 楼主厉害感谢分享 学到了学到了,楼主牛逼 楼主厉害{:1_921:} 学习了 谢谢分享 好好学习!。 担心违规 内容已删除 支持一下 chboy 发表于 2020-7-14 19:33
担心违规 内容已删除
要不得老铁 我这几天刚好也在写这东西
头都裂开了 二娃 发表于 2020-7-14 22:22
我这几天刚好也在写这东西
头都裂开了
思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改🤔 舒默哦 发表于 2020-7-14 23:15
思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改🤔
要考虑的东西挺多的 舒默哦 发表于 2020-7-14 20:50
要不得老铁
我准备说你一发帖就置顶了有点牛逼 但是发出后担心违规就删除了{:301_1004:} 高深莫测,云里雾里 {:301_998:}我在写代码变异,比vm好写点