代码虚拟化:
因为数值可能被占用,所以我选的都是些不常用的。
除了这些以外,我还自定义了一个寄存器。。。
然后自己根据自定义的字节码写出一个小程序。
因为这字节码都是用数组存放的,所以call jmp jnz的偏移都用数组下标就可以了(为了方便起见)(call到API的话就不太适用了,你可以做记号来判断等等)
因为这只是一个模型,还有很多功能没有实现,谁让我技术太烂了。。以后我会补上的。
编译环境:XP下的VC++6.0
EasyVm.h
EV.c
我认为代码虚拟化是将native转换为字节码,但是字节码是不能被机器识别的,所以就需要有对应的解释器来解释他。因为字节码是我们定义的,所以一般的工具不能正确的识别它。就因为这样,虚拟机保护的代码比较难以识别破解,但是解释器一般情况下都是native 因为这样才能使解释器运行起来并解释字节码。
#define EXor 0x77//xor
#define ERetn 0xCC//retn
#define ECmp 0x78//cmp
#define EJnz 0x79//jnz
#define EJmp 0x80//jmp
#define EMov 0xC4//mov
#define ECall 0xEE//call
#define ERetn 0xFE//retn
#define EAdd 0xFF//add
#define ENot 0xEC//not
然后是虚拟寄存器的值(用来判断寄存器的)
因为数值可能被占用,所以我选的都是些不常用的。
#define VMyEax 0xF1//eax
#define VMyEcx 0xF2//ecx
#define VMyEdx 0xF3//edx
#define VMyEbx 0xF4//ebx
#define VMyEsp 0xF5//esp
#define VMyEbp 0xF6//ebp
#define VMyEsi 0xF7//esi
#define VMyEdi 0xF8//edi
#define VMyEip 0xF9//eip
然后定义虚拟寄存器(16位)
struct MyVM
{
DWORD MyEax;
DWORD MyEcx;
DWORD MyEdx;
DWORD MyEbx;
DWORD MyEsp;
DWORD MyEbp;
DWORD MyEsi;
DWORD MyEdi;
DWORD MyEip;
DWORD MyC;
DWORD MyP;
DWORD MyA;
DWORD MyZ;
DWORD MyS;
DWORD MyT;
DWORD MyD;
DWORD MyO;
}MyVM={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
typedef struct MyVM *pMyEasyVM,MyEasyVM;
MyEasyVM VM;//初始化虚拟寄存器
除了这些以外,我还自定义了一个寄存器。。。
然后自己根据自定义的字节码写出一个小程序。
因为这字节码都是用数组存放的,所以call jmp jnz的偏移都用数组下标就可以了(为了方便起见)(call到API的话就不太适用了,你可以做记号来判断等等)
因为这只是一个模型,还有很多功能没有实现,谁让我技术太烂了。。以后我会补上的。
编译环境:XP下的VC++6.0
EasyVm.h
//
/* 字节码可以自己定义,只要用到的函数中的代号在指令集里! */
/* 也可以自己添加哦 */
//
#include <stdio.h>
#include <windows.h>
/*/// 指令集 */
/
#define EXor 0x77//xor
#define ERetn 0xCC//retn
#define ECmp 0x78//cmp
#define EJnz 0x79//jnz
#define EJmp 0x80//jmp
#define EMov 0xC4//mov
#define ECall 0xEE//call
#define ERetn 0xFE//retn
#define EAdd 0xFF//add
#define ENot 0xEC//not
//输入信息
#define vm_read 0xEA//自定义函数
//输出信息
#define vm_write_err 0xCE//自定义函数
#define vm_write_success 0xCF//自定义函数
///
/*/// 虚拟寄存器值 /*/
/
#define VMyEax 0xF1//eax
#define VMyEcx 0xF2//ecx
#define VMyEdx 0xF3//edx
#define VMyEbx 0xF4//ebx
#define VMyEsp 0xF5//esp
#define VMyEbp 0xF6//ebp
#define VMyEsi 0xF7//esi
#define VMyEdi 0xF8//edi
#define VMyEip 0xF9//eip
#define VMyAddr 0xFA
/
struct MyVM
{
DWORD MyEax;
DWORD MyEcx;
DWORD MyEdx;
DWORD MyEbx;
DWORD MyEsp;
DWORD MyEbp;
DWORD MyEsi;
DWORD MyEdi;
DWORD MyEip;
DWORD MyAddr;//自定义的地址寄存器
DWORD MyC;
DWORD MyP;
DWORD MyA;
DWORD MyZ;
DWORD MyS;
DWORD MyT;
DWORD MyD;
DWORD MyO;
}MyVM={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
typedef struct MyVM *pMyEasyVM,MyEasyVM;
MyEasyVM VM;//初始化虚拟寄存器
/* 字节码 */
unsigned char MyCode[]={EMov,VMyEbx,89,vm_read,ECmp,VMyAddr,VMyEbx,EJnz,/*8*/11,vm_write_success,ERetn,vm_write_err,ERetn};//代码
int MyRead;//写进去的信息
/* 有时为了方便调试,会用到这些函数 */
void pEax()
{
printf("eax=%xH\n",VM.MyEax);
}
void pEcx()
{
printf("ecx=%xH\n",VM.MyEcx);
}
void pEdx()
{
printf("edx=%xH\n",VM.MyEdx);
}
void pEbx()
{
printf("ebx=%xH\n",VM.MyEbx);
}
void pEsp()
{
printf("esp=%xH\n",VM.MyEsp);
}
void pEbp()
{
printf("ebp=%xH\n",VM.MyEbp);
}
void pEsi()
{
printf("esi=%xH\n",VM.MyEsi);
}
void pEdi()
{
printf("edi=%xH\n",VM.MyEdi);
}
void pEip()
{
printf("eip(指令执行处)=%xH(%u)\n",VM.MyEip,VM.MyEip);
}
void RunCode()
{
int i,k=1;
BOOL Jnz=FALSE,Jmp=FALSE,Call=FALSE,Retn=FALSE;
Run:
for(i=VM.MyEip;i<sizeof(MyCode);i++)
{
if(MyCode[i]==vm_write_success)
{
VM.MyEax=printf("ok\n");//返回值
VM.MyEip+=2;//这里在数组中占用一个字节
}
if(MyCode[i]==vm_write_err)
{
VM.MyEax=printf("error!\n");//返回值
VM.MyEip+=2;//这里在数组中占用一个字节
}
if(MyCode[i]==vm_read)
{
//输入信息
VM.MyEax=scanf("%d",&VM.MyAddr);//返回值
VM.MyEip+=2;//这里在数组中占用一个字节
}
if(MyCode[i]==EXor)
{
int j;
DWORD MyArgc,argc1;
MyArgc=MyCode[i+1];
argc1=MyCode[i+2];
VM.MyEdx=MyArgc;
VM.MyEbx=argc1;
VM.MyEdx=VM.MyEdx^VM.MyEbx;
printf("xor %u,%u = %u\n",MyArgc,argc1,VM.MyEdx);
VM.MyEip+=4;
}
if(MyCode[i]==EAdd)
{
int j;
DWORD MyArgc,argc1;
MyArgc=MyCode[i+1];
argc1=MyCode[i+2];
VM.MyEdx=MyArgc;
VM.MyEbx=argc1;
VM.MyEdx=VM.MyEdx+VM.MyEbx;
printf("add %u,%u = %u\n",MyArgc,argc1,VM.MyEdx);
VM.MyEip+=4;
}
if(MyCode[i]==ENot)
{
int j;
DWORD MyArgc;
MyArgc=MyCode[i+1];
VM.MyEdx=MyArgc;
VM.MyEdx=~VM.MyEdx;
printf("not %u = %u\n",MyArgc,VM.MyEdx);
VM.MyEip+=3;
}
if(MyCode[i]==EMov)
{
DWORD r,r1;
r=MyCode[i+1];
r1=MyCode[i+2];
/* 将值拷贝到虚拟寄存器中,原数据被覆盖 */
if(r==VMyEax)
VM.MyEax=r1;
if(r==VMyEcx)
VM.MyEcx=r1;
if(r==VMyEdx)
VM.MyEdx=r1;
if(r==VMyEbx)
VM.MyEbx=r1;
if(r==VMyEsp)
VM.MyEsp=r1;
if(r==VMyEbp)
VM.MyEbp=r1;
if(r==VMyEsi)
VM.MyEsi=r1;
if(r==VMyEdi)
VM.MyEdi=r1;
VM.MyEip+=4;
}
if(MyCode[i]==ECmp)
{
DWORD r3,r4,r5,r6;
r3=MyCode[i+1];
r4=MyCode[i+2];
if(r3==VMyEax)
r5=VM.MyEax;
else if(r3==VMyEcx)
r5=VM.MyEcx;
else if(r3==VMyEdx)
r5=VM.MyEdx;
else if(r3==VMyEbx)
r5=VM.MyEbx;
else if(r3==VMyEsp)
r5=VM.MyEsp;
else if(r3==VMyEbp)
r5=VM.MyEbp;
else if(r3==VMyEsi)
r5=VM.MyEsi;
else if(r3==VMyEdi)
r5=VM.MyEdi;
else if(r3==VMyAddr)
r5=VM.MyAddr;
else
r5=r3;
if(r4==VMyEax)
r6=VM.MyEax;
else if(r4==VMyEcx)
r6=VM.MyEcx;
else if(r4==VMyEdx)
r6=VM.MyEdx;
else if(r4==VMyEbx)
r6=VM.MyEbx;
else if(r4==VMyEsp)
r6=VM.MyEsp;
else if(r4==VMyEbp)
r6=VM.MyEbp;
else if(r4==VMyEsi)
r6=VM.MyEsi;
else if(r4==VMyEdi)
r6=VM.MyEdi;
else if(r4==VMyAddr)
r6=VM.MyAddr;
else
r6=r4;
if(r6==r5)
VM.MyZ=0;
else
VM.MyZ=1;
VM.MyEip+=4;//Eip的值加4
}
if(MyCode[i]==EJnz)
{
if(VM.MyZ==1)//读取标志位
{
int JnzAddress;
JnzAddress=MyCode[i+1];
VM.MyEip=JnzAddress;//从虚拟寄存器中的EIP指向处开始执行
Jnz=TRUE;
break;
}
else
VM.MyEip+=3;
}
if(MyCode[i]==EJmp)
{
//直接跳转
int JmpAddress;
JmpAddress=MyCode[i+1];
VM.MyEip=JmpAddress;//从虚拟寄存器中的EIP指向处开始执行
Jmp=TRUE;
break;
}
if(MyCode[i]==ECall)
{
int CallAddress;
CallAddress=MyCode[i+1];
VM.MyEsp=i+2;//call返回地址
VM.MyEip=CallAddress;
Call=TRUE;
break;
}
if(MyCode[i]==ERetn)
{
VM.MyEip=VM.MyEsp;
Retn=TRUE;
break;
}
}
if(Jnz)
{
Jnz=FALSE;
goto Run;
}
if(Jmp)
{
Jmp=FALSE;
goto Run;
}
if(Call)
{
Call=FALSE;
goto Run;
}
if(Retn)
{
if(VM.MyEsp<=0)
return;
Retn=FALSE;
k+=1;
goto Run;
}
}
EV.c
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "EasyVm.h"
#define pause system("pause")
int main(void)
{
printf("Hello World\n");
printf("Address:%xH\n",&MyCode);
RunCode();
pause;
return 0;
}
这里的字节码是一个简单的CrackMe。注册成功的图片:
注册失败的图片:
虚拟机保护效果:
OD中查看:
如果在OD中查看字节码,并且不知道每个字节码的意思,不分析解释器的话,谁能看出这是什么意思呢?
因为这是字节码,机器不能识别,只有对应的解释器才能识别,一般的工具都是没有效果的!因为字节码是我们自己定义的。
动态调试:
也是一样,并不会被识别,因为处理器并不会理会这些字节码,理会这些字节码的只有我们的解释器!而且调试也是在调试解释器哦。其实我们还可以分析解释器,来分析字节码。不过解释器中的控制流会复杂很多,也能增加分析难度。
如果要爆破的话,把解释器中的jnz解释函数nop掉就行了,如果字节码有很多jnz的话。嘿嘿嘿嘿 卵用~~~
刚刚我就nop掉了,结果破解成功了,不过我是依照OD搜索出来的EasyVm.h中的内容才破解出来的。
注意:如果写出这样的CM给别人破解的话,千万别原封不动的发出去哦~如果是我的话我就会给代码绕一大圈然后乱序。