【VC】【笔记】自写vmp壳子编写报告(一)
本帖最后由 舒默哦 于 2020-9-24 10:35 编辑https://static.52pojie.cn/static/image/hrline/1.gif
前言:
[*]第一次设计vmp框架和编写,因设计得有点匆忙,导致后面茶饭不思,一直在改bug,现在基本上处理好了。在这里吐槽一下,如果时间充足,至少框架会设计的更好一点。
[*]因为要兼容64位,汇编引擎我用的是XEDParse,反汇编引擎用的BeaEngine。另外,这个vmp不能对驱动加vm,驱动vm的话,一些处理方法会不一样。
[*]在vmp设计中,我定义了几个模块:指令分析器,垃圾块指令构造器,IAT加密模块,反调试模块。
[*]这几个模块的说明篇幅有点长,这篇文章把指令分析器和垃圾块指令构造器做个说明。
https://static.52pojie.cn/static/image/hrline/3.gif
0x00有些问题的说明。
1、有些奇葩的指令现在仍然无法处理,如下:mov eax,nextcall //nextcall是函数地址
mov ecx,nextaddr //nextaddr是jmp ecx下一行的地址
push ecx
jmp eax
add esp,4
上面这些指令等价于:
mov eax,nextcall
call eax
add esp,4
都是可以处理的,归类于不可模拟指令。
但是下面这种情况,处理起来就有问题
mov ecx,nextaddr //nextaddr是jmp ecx下一行的地址
jmp ecx
add esp,4
....
这个jmp ecx是在函数内跳转,不能归于不可模拟指令。
在函数内跳转,一般情况是 jmp 后面跟的地址,比如
mov eax,nextcall
mov ecx,nextaddr
jmp L11
add esp,4
L11:
ret
标号L11其实就是地址标号,在vm指令解析器解析指令的时候,L11是随着活动的进行可以传递的属性,方便加了垃圾指令之后,让新地址和旧地址进行匹配,
然后进行回填。而jmp ecx,“ecx”只是一个寄存器,不是地址,即使进行传递,那么在新地址和旧地址进行匹配的时候(即新地址和“ecx”匹配),无法匹配,会出BUG。
2、把一个函数vm的时候,要写开始地址和结尾地址,不写结尾地址的话,那么就要写一个函数来自动识别要vm的函数的结尾地址。以下函数就是自动识别要vm的函数的结尾地址,
主要功能是遇到跳转指令比如JCC或者JMP指令,就记录下来,与后面遇到的ret指令所在的地址进行比较,如果小于ret指令所在的地址,那么,这条ret就是结束地址,否则继续往下判断。
struct FUNCANDINSLENGTH
{
int inslen = 0;//记录指令的条数
int hcodelength = 0;//记录硬编码长度
bool istrue = true;//读取指令是否成功
};
//计算函数的长度
FUNCANDINSLENGTH Disassembler_(DWORD virtualaddr,DWORD instruction)
{
FUNCANDINSLENGTH fuclen;
int inslength = 0;//记录指令的条数
vector<DWORD>JccAddr;//记录JCC后面的地址
DISASM disAsm = { 0 };
// 3. 配置结构体,初始化反汇编的opcode
disAsm.EIP = (UIntPtr)instruction;
disAsm.VirtualAddr = virtualaddr; // opcode 指令的地址
disAsm.Archi = 0; // 0 => 32 , 1 => 64
disAsm.Options = 0x000; // masm 汇编指令格式
int nCount = 0;// 用于记录在循环当中,反汇编了多少个字节
int nLen = 0; // 用于记录当前的汇编指令的字节数
while (true)
{
nLen = Disasm(&disAsm); // 每次只反汇编一条汇编指令, 并且返回当前得到的汇编指令的长度
unsigned int uAddr = disAsm.VirtualAddr;
printf("%08X | ", uAddr); // 打印地址
printOpcode((const unsigned char*)disAsm.EIP, nLen); // 打印opcode
printf(" | %s\n", disAsm.CompleteInstr); // 打印反汇编指令
if (inslength == 0x1000)
{//防止死循环
MessageBoxA(NULL, "未知错误,读取指令失败!!", "提示", MB_OK);
fuclen.istrue = false;
return fuclen;
}
++inslength;//记录指令的条数,为后面申请内存提供依据
nCount += nLen; // 累加已经反汇编的字节数
disAsm.EIP += nLen; // 定位到下一条汇编指令
disAsm.VirtualAddr += nLen; // 设置到下一条汇编指令的地址
//如果遇到jcc指令或者jmp指令,记录后面的地址
for (int i = 0; i < JCCNUMSS; i++)
{
if (0 == stricmp(disAsm.Instruction.Mnemonic, JCCSTR))
{
DWORD tempaddr = 0;
sscanf_s(disAsm.Argument1.ArgMnemonic, "%X", &tempaddr);
JccAddr.push_back(tempaddr);
break;
}
}
if (
(0 == stricmp(disAsm.Instruction.Mnemonic, "ret ")) ||
(0 == stricmp(disAsm.Instruction.Mnemonic, "retn ")) ||
(0 == stricmp(disAsm.Instruction.Mnemonic, "int3 "))
)
{
int isretn = 1;
for (int i = 0; i < JccAddr.size(); i++)
{
if ((*(JccAddr.begin()+i)) > disAsm.VirtualAddr- nLen)
{
isretn = 0;
break;
}
}
if (isretn || (0 == stricmp(disAsm.Instruction.Mnemonic, "int3 ")))
{
fuclen.hcodelength = nCount;
fuclen.inslen = inslength;
return fuclen;
}
}
}
}
3、没有处理异常。另外,如果要保护的函数里有检查堆栈的函数,一定要注意标志寄存器的处理问题,比如退出虚拟机的时候,出栈完成了,
后面还有改变标志寄存器的指令,则可以使用xor ecx,ecx(返回值一般是eax,所有可以用ecx寄存器)这指令再平衡回来,因为检查堆栈的函数只检查ZF标志位。
https://static.52pojie.cn/static/image/hrline/4.gif
0x01 指令分析器
1、指令分析器的功能就是把要保护的指令,翻译为中间表示,可以用一个结构体来保存这些中间表示和一些需要传递的属性,当然,也可以把指令分析器理解为一个有穷自动机(接收指令 -> 解析指令 -> 中间表示)。我用的是BeaEngine引擎,构造自动机来解析指令的时候就要遵循BeaEngine反汇编引擎的规则,指令分析器的主框架如下://解析要保护的指令,翻译为中间表示
void MiddleRepresent(DISASM disAsm)
{
/*----------------------------------------------------------------------------------*/
/* 1、是否有操作3 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument3.ArgType)
{
switch (disAsm.Argument3.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
break;
case MEMORY_TYPE: //内存
break;
case CONSTANT_TYPE://常数
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 2、是否有操作2 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument2.ArgType)
{
switch (disAsm.Argument2.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
break;
case MEMORY_TYPE: //内存
break;
case CONSTANT_TYPE://常数
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 3、是否有操作1 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument1.ArgType)
{
switch (disAsm.Argument1.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
break;
case MEMORY_TYPE: //内存
break;
case CONSTANT_TYPE://常数
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 4、处理普通handler */
/*----------------------------------------------------------------------------------*/
//省略...
/*----------------------------------------------------------------------------------*/
/* 5、判断是否有辅助handler */
/*----------------------------------------------------------------------------------*/
if (
0x10000000 != disAsm.Argument1.ArgType ||
0x10000000 != disAsm.Argument2.ArgType ||
0x10000000 != disAsm.Argument3.ArgType
)
{
if (NO_ARGUMENT != disAsm.Argument1.ArgType)
{
switch (disAsm.Argument1.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
break;
case MEMORY_TYPE: //内存
break;
case CONSTANT_TYPE://常数
break;
default:
break;
}
}
}
}
2、指令是从右往左解析的,比如:
mov eax,ecx
翻译为中间表示就是
vPushRegVR_ecx//操作2
vPushRegVR_eax //操作1
vMOV //普通handler
vPopReg VR_eax //辅助handler
3、整个指令解析器的构造,如下:
//处理内存操作,把内存翻译为中间表示
void VMLoader2::MemoryMiddle(DISASM disAsm,MEMORYTYPE memtype)
{
/*
* vPushImm4
* vPushReg
* vMUL_MEM //内存操作专用乘法handler
* vPushReg
* vAdd
* vPushImm4
* vAdd
* vWriteMemDs4/2/1
*/
MIDDLESTRUCT midstr;
DATATABLE datatbl;
midstr.originaddr = disAsm.VirtualAddr;
if (memtype.Scale!=0)
{
char vpushreg4[] = "vPushImm4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
datatbl.data = memtype.Scale;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
for (int i = 0; i < REGNUMS; i++)
{
if (memtype.IndexRegister == tempreg.index)
{
char vpushreg4[] = "vPushReg4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
DATATABLE datatbl;
datatbl.data = i;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
break;
}
}
char vmul_mem[] = "vMUL_MEM ";
printf("%s\n", vmul_mem);
memcpy(midstr.vmfunc, vmul_mem, sizeof(vmul_mem));
m_middle.push_back(midstr);//操作码压入中间表示
}
int i = 0;
for (; i < REGNUMS; i++)
{
if (memtype.BaseRegister == tempreg.index)
{
char vpushreg4[] = "vPushReg4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
DATATABLE datatbl;
datatbl.data = i;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
break;
}
}
if ((memtype.Scale != 0) && (i != REGNUMS))
{
char vadd[] = "vAdd4 ";
printf("%s\n", vadd);
memcpy(midstr.vmfunc, vadd, sizeof(vadd));
m_middle.push_back(midstr);//操作码压入中间表示
}
if (0 != memtype.Displacement)
{
char vpushreg4[] = "vPushImm4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
datatbl.data = memtype.Displacement;
m_datatable.push_back(datatbl);//数据压入数据表
}
if ((0 != memtype.Displacement) && (i != REGNUMS))
{
char vadd[] = "vAdd4 ";
printf("%s\n", vadd);
memcpy(midstr.vmfunc, vadd, sizeof(vadd));
m_middle.push_back(midstr);//操作码压入中间表示
}
}
//解析要保护的指令,翻译为中间表示
void VMLoader2::MiddleRepresent(DISASM disAsm)
{
MIDDLESTRUCT midstr;
midstr.originaddr = disAsm.VirtualAddr;
bool IsSimulation = true;
//vm的环境准备
if (start_bool)
{
char vresumestart[] = "VMStartVM_2 ";
printf("%s\n", vresumestart);
memcpy(midstr.vmfunc, vresumestart, sizeof(vresumestart));
m_middle.push_back(midstr);//压入VMStartVM_2
start_bool = false;
}
/*----------------------------------------------------------------------------------*/
/* 0、判断是否是不可模拟 */
/*----------------------------------------------------------------------------------*/
for (int i = 0; i < FUNNUMS; i++)
{
if (stricmp(disAsm.Instruction.Mnemonic, g_FunName.s_opecode) == 0)
{
IsSimulation = false;
break;
}
}
if (0 == stricmp(disAsm.Instruction.Mnemonic, "jmp ") && ((disAsm.Argument1.ArgType & 0xF0000000)== MEMORY_TYPE)||
0 == stricmp(disAsm.Instruction.Mnemonic, "push ") && ((disAsm.Argument2.ArgType & 0xF0000000) == MEMORY_TYPE)||
0 == stricmp(disAsm.Instruction.Mnemonic, "pop ") && ((disAsm.Argument1.ArgType & 0xF0000000) == MEMORY_TYPE)
)
{
IsSimulation = true;
}
//0、1 如果是不可模拟指令,处理好后直接返回
if (IsSimulation)
{
char retnotaddr[] = "vRetnNOT_ ";
printf("%s\n", retnotaddr);
memcpy(midstr.vmfunc, retnotaddr, sizeof(retnotaddr));
m_middle.push_back(midstr);//先压入vRetnNOT
char notsimulate[] = "vNotSimulate ";
char vresumestartaddr;
sprintf(vresumestartaddr, "%X", m_vmps.vresumestartaddr);
printf("%s\n", notsimulate);
memcpy(midstr.vmfunc, notsimulate, sizeof(notsimulate));
memcpy(midstr.param1, disAsm.CompleteInstr, STRUCTIONLENGTH);
memcpy(midstr.param2, vresumestartaddr, OPECODELENGTH);
m_middle.push_back(midstr);//再压入不可模拟指令的handler
char vresumestart[] = "vResumeStart_ ";
printf("%s\n", vresumestart);
memcpy(midstr.vmfunc, vresumestart, sizeof(vresumestart));
m_middle.push_back(midstr);//压入vResumeStart_
return;
}
/*----------------------------------------------------------------------------------*/
/* 1、是否有操作3 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument3.ArgType)
{
switch (disAsm.Argument3.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
break;
case MEMORY_TYPE: //内存
break;
case CONSTANT_TYPE://常数
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 2、是否有操作2 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument2.ArgType)
{
switch (disAsm.Argument2.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
{
for (int i = 0; i < REGNUMS; i++)
{
if (tempreg.index == (disAsm.Argument2.ArgType & 0xFFFF))
{
if (0x20 == disAsm.Argument2.ArgSize)
{//32位
char vpushreg4[] = "vPushReg4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else if (0x10 == disAsm.Argument2.ArgSize)
{//16位
char vpushreg4[] = "vPushReg2 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{//8位(要判断高位还是低位)
for (int i = 0; i < 14; i++)
{
if (stricmp(disAsm.Argument2.ArgMnemonic, regname_) == 0)
{
if (i<=3)//小于等于3是低位
{
char vpushreg4[] = "vPushReg1_low ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{
char vpushreg4[] = "vPushReg1_above ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
break;
}
}
}
DATATABLE datatbl;
datatbl.data = i;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
break;
}
}
}
break;
case MEMORY_TYPE: //内存
{
if (0 == stricmp("pop ", disAsm.Instruction.Mnemonic)) break;
if (0 == stricmp("ret ", disAsm.Instruction.Mnemonic)) break;
MemoryMiddle(disAsm, disAsm.Argument2.Memory);
if (0x20 == disAsm.Argument2.ArgSize)
{//32位
char vReadMem[] = "vReadMemDs4 ";
printf("%s\n", vReadMem);
memcpy(midstr.vmfunc, vReadMem, sizeof(vReadMem));
}
else if (0x10 == disAsm.Argument2.ArgSize)
{//16位
char vReadMem[] = "vReadMemDs2 ";
printf("%s\n", vReadMem);
memcpy(midstr.vmfunc, vReadMem, sizeof(vReadMem));
}
else
{//8位
char vReadMem[] = "vReadMemDs1 ";
printf("%s\n", vReadMem);
memcpy(midstr.vmfunc, vReadMem, sizeof(vReadMem));
}
memcpy(midstr.param1, disAsm.Instruction.Mnemonic, 16);//参数一
memcpy(midstr.param2, disAsm.Argument2.ArgMnemonic, 32);//参数二
m_middle.push_back(midstr);//操作码压入中间表示
}
break;
case CONSTANT_TYPE://常数
{
DWORD constnums = 0;
sscanf(disAsm.Argument2.ArgMnemonic, "%X", &constnums);
char vpushimm4[] = "vPushImm4 ";
printf("%s\n", vpushimm4);
memcpy(midstr.vmfunc, vpushimm4, sizeof(vpushimm4));
m_middle.push_back(midstr);//操作码压入中间表示
DATATABLE datatbl;
datatbl.data = constnums;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
}
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 3、是否有操作1 */
/*----------------------------------------------------------------------------------*/
if (NO_ARGUMENT != disAsm.Argument1.ArgType)
{
switch (disAsm.Argument1.ArgType & 0xF0000000)
{
case REGISTER_TYPE: //寄存器
{
for (int i = 0; i < REGNUMS; i++)
{
if (tempreg.index == (disAsm.Argument1.ArgType & 0xFFFF))
{
if (0x20 == disAsm.Argument1.ArgSize)
{//32位
char vpushreg4[] = "vPushReg4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else if (0x10 == disAsm.Argument1.ArgSize)
{//16位
char vpushreg4[] = "vPushReg2 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{//8位(要判断高位还是低位)
for (int i = 0; i < 14; i++)
{
if (stricmp(disAsm.Argument1.ArgMnemonic, regname_) == 0)
{
if (i <= 3)//小于等于3是低位
{
char vpushreg4[] = "vPushReg1_low ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{
char vpushreg4[] = "vPushReg1_above ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
break;
}
}
}
DATATABLE datatbl;
datatbl.data = i;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
break;
}
}
}
break;
case MEMORY_TYPE: //内存(操作数1如果是内存,且操作数2不为空,则处理好后直接退出)
{
if (0 == stricmp("push ",disAsm.Instruction.Mnemonic)) break;
MemoryMiddle(disAsm, disAsm.Argument1.Memory);
if (NO_ARGUMENT == disAsm.Argument2.ArgType) break;//这步是对待单个操作数的
if (0x20 == disAsm.Argument1.ArgSize)
{//32位
char vWriteMem[] = "vWriteMemDs4 ";
printf("%s\n", vWriteMem);
memcpy(midstr.vmfunc, vWriteMem, sizeof(vWriteMem));
}
else if (0x10 == disAsm.Argument1.ArgSize)
{//16位
char vWriteMem[] = "vWriteMemDs2 ";
printf("%s\n", vWriteMem);
memcpy(midstr.vmfunc, vWriteMem, sizeof(vWriteMem));
}
else
{//8位
char vWriteMem[] = "vWriteMemDs1 ";
printf("%s\n", vWriteMem);
memcpy(midstr.vmfunc, vWriteMem, sizeof(vWriteMem));
}
memcpy(midstr.param1, disAsm.Instruction.Mnemonic, 16);//参数一
memcpy(midstr.param2, disAsm.Argument1.ArgMnemonic, 32);//参数二
m_middle.push_back(midstr);//操作码压入中间表示
return;
}
break;
case CONSTANT_TYPE://常数
{
DWORD constnums = 0;
sscanf(disAsm.Argument1.ArgMnemonic, "%X", &constnums);
char vpushimm4[] = "vPushImm4 ";
printf("%s\n", vpushimm4);
memcpy(midstr.vmfunc, vpushimm4, sizeof(vpushimm4));
m_middle.push_back(midstr);//操作码压入中间表示
DATATABLE datatbl;
datatbl.data = constnums;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
}
break;
default:
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 4、处理普通handler */
/*----------------------------------------------------------------------------------*/
//4、1 如果是ret,直接返回
if (0 == stricmp("ret ", disAsm.Instruction.Mnemonic))
{
if (disAsm.Argument1.ArgType == 0x10000000)
{
char vpushreg[] = "vPushImm4 ";
printf("%s\n", vpushreg);
memcpy(midstr.vmfunc, vpushreg, sizeof(vpushreg));
m_middle.push_back(midstr);//压入vPushReg4
DATATABLE datatbl;
datatbl.data = 0;
datatbl.recodeOaddr = disAsm.VirtualAddr;
m_datatable.push_back(datatbl);//数据压入数据表
}
char callmem[] = "vRETN ";
printf("%s\n", callmem);
memcpy(midstr.vmfunc, callmem, sizeof(callmem));
m_middle.push_back(midstr);//压入vRETN
return;
}
//4、2 如果操作数2是内存(针对的是二地址指令),m_middle链表最后两个元素互换
if ((disAsm.Argument2.ArgType & 0xF0000000) == MEMORY_TYPE)
{
if (0 != stricmp("pop ", disAsm.Instruction.Mnemonic))
{
swap(m_middle, m_middle);
}
goto ttttt__;
}
//4、3 如果是调用函数,则处理好后,直接返回
if (0 == stricmp("call ", disAsm.Instruction.Mnemonic))
{
//如果操作数1是内存
if ((disAsm.Argument1.ArgType & 0xF0000000) == MEMORY_TYPE)
{
char callmem[] = "vCallMem ";
printf("%s\n", callmem);
memcpy(midstr.vmfunc, callmem, sizeof(callmem));
m_middle.push_back(midstr);//先压入vCallMem
}
else
{
//删除vPushImm4
m_middle.pop_back();
}
char retnotaddr[] = "vRetnNOT_ ";
printf("%s\n", retnotaddr);
memcpy(midstr.vmfunc, retnotaddr, sizeof(retnotaddr));
m_middle.push_back(midstr);//压入vRetnNOT
char vcall[] = "vCALL ";
printf("%s\n", vcall);
memcpy(midstr.vmfunc, vcall, sizeof(vcall));
m_middle.push_back(midstr);
char vresumestart[] = "vResumeStart_ ";
printf("%s\n", vresumestart);
memcpy(midstr.vmfunc, vresumestart, sizeof(vresumestart));
m_middle.push_back(midstr);//压入vResumeStart_
return;
}
//4、4 处理普通handler
for (int i = 0; i < FUNNUMS; i++)
{
if (0 == stricmp(g_FunName.s_opecode,disAsm.Instruction.Mnemonic))
{
printf("%s\n", g_FunName.vm_opcoed);
memcpy(midstr.vmfunc, g_FunName.vm_opcoed,OPECODELENGTH);
m_middle.push_back(midstr);//操作码压入中间表示
break;
}
}
/*----------------------------------------------------------------------------------*/
/* 5、判断是否有辅助handler */
/*----------------------------------------------------------------------------------*/
if (
0x10000000 != disAsm.Argument1.ArgType ||
0x10000000 != disAsm.Argument2.ArgType ||
0x10000000 != disAsm.Argument3.ArgType
)
{
ttttt__:
//5.1 判断这条指令是否改变操作数1的值,比如cmp和test操作数就不会改变操作数1的值,直接返回
for (int i = 0; i < NOTREGNUMS; i++)
{
if (stricmp(cmp_opecode, disAsm.Instruction.Mnemonic) == 0)
{
return;
}
}
//5.2 判断是否是JCC或者JMP指令,是则处理好后直接返回
for (int i = 0; i < JCCNUMS; i++)
{//判断是否是JCC指令
if (0 == stricmp(disAsm.Instruction.Mnemonic, JCCstr))
{
//改变数据表的数据
m_datatable.at(m_datatable.size() - 1).originaddr = disAsm.VirtualAddr;
//多压入一个空数据到数据表
DATATABLE datatbl;
datatbl.data = 0;
m_datatable.push_back(datatbl);//数据压入数据表
//再压入一个空数据到数据表
datatbl.data = 0;
m_datatable.push_back(datatbl);//数据压入数据表
//JCC指令前插入两个vPushImm4
char vpushimm4[] = "vPushImm4 ";
printf("%s\n", vpushimm4);
memcpy(midstr.vmfunc, vpushimm4, sizeof(vpushimm4));
m_middle.insert(m_middle.end()-1, midstr);//操作码压入中间表示
m_middle.insert(m_middle.end() - 1, midstr);//操作码压入中间表示
return;
}
}
//5.2 处理普通辅助handler
switch (disAsm.Argument1.ArgType & 0xF0000000)
{
case REGISTER_TYPE://寄存器
{
for (int i = 0; i < REGNUMS; i++)
{
if (tempreg.index == (disAsm.Argument1.ArgType & 0xFFFF))
{
if (0x20 == disAsm.Argument1.ArgSize)
{//32位
char vpopreg4[] = "vPopReg4 ";
printf("%s\n", vpopreg4);
memcpy(midstr.vmfunc, vpopreg4, sizeof(vpopreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else if (0x10 == disAsm.Argument1.ArgSize)
{//16位
char vpopreg4[] = "vPopReg2 ";
printf("%s\n", vpopreg4);
memcpy(midstr.vmfunc, vpopreg4, sizeof(vpopreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{//8位(要判断高位还是低位)
for (int i = 0; i < 14; i++)
{
if (stricmp(disAsm.Argument1.ArgMnemonic, regname_) == 0)
{
if (i <= 3)//小于等于3是低位
{
char vpushreg4[] = "vPopReg1_low ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{
char vpushreg4[] = "vPopReg1_above ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
break;
}
}
}
DATATABLE datatbl;
datatbl.data = i;
m_datatable.push_back(datatbl);//数据压入数据表
}
}
//如果操作1是esp,即改变栈大小,比如指令sub esp,0x100,
//则要调用vCheckESP()函数,检查VMContext是否被覆盖
if (16 == (disAsm.Argument1.ArgType & 0xFFFF))
{//16在BegEngine反汇编引擎约定的是esp寄存器
char vcheckesp[] = "VCheckESP ";
printf("%s\n", vcheckesp);
memcpy(midstr.vmfunc, vcheckesp, sizeof(vcheckesp));
m_middle.push_back(midstr);//操作码压入中间表示
}
//如果操作码是pop,把vPOP添加到m_middle链表,然后返回
if (0 == stricmp("pop ",disAsm.Instruction.Mnemonic))
{
if (0x20 == disAsm.Argument1.ArgSize)
{
char vpushreg4[] = "vPOP4 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
else
{
char vpushreg4[] = "vPOP2 ";
printf("%s\n", vpushreg4);
memcpy(midstr.vmfunc, vpushreg4, sizeof(vpushreg4));
m_middle.push_back(midstr);//操作码压入中间表示
}
return;
}
}
break;
case MEMORY_TYPE://内存
{
//
}
break;
case CONSTANT_TYPE:
{
//
}
break;
}
}
}
4、绝大多数情况下,都是零地址指令、一地址指令、二地址指令,很少有三地址指令,所以把操作3省略没有处理。
5、把handler操作和数据分开保存,如:
mov eax,ecx
翻译为中间表示就是
vPushRegVR_ecx//操作2
vPushRegVR_eax //操作1
vMOV //普通handler
vPopReg VR_eax //辅助handler
把VR_ecx、VR_eax、VR_eax分离出来保存在一个数据表的结构体中。
翻译就可以这样表示了:
vPushReg
vPushReg
vMOV
vPopReg
6、内存操作处理起来比较麻烦,MemoryMiddle函数用来专门处理内存操作。例如这条指令 mov dword ptr,eax,可以译成如下的中间表示:vPushReg //eax
vPushImm4//4
vPushReg4//ecx
vMUL_MEM//*
vPushReg4 //eax
vAdd4 //+
vPushImm4 //0x401000
vAdd //+
vWriteMemDs4
4 ecx * 可以理解为后缀表示法,其实这是和handler设计和堆栈操作有关的。
7、局部变量的操作,如 mov dword ptr,eax,仍然用MemoryMiddle函数来翻译:
vPushImm4//0xFFFFFFF8
vPushReg4 //ebp
vAdd4
负8会被BeaEngine引擎解析为0xFFFFFFF8,ebp-0x8与0xFFFFFFF8+ebp是等价的
8、下面举个完整的例子:
void _declspec(naked) _stdcallcode_vm_test(int x)
{
//MessageBoxA(NULL, 0, 0, 0);
_asm {
sub esp,0x150
push eax
push ecx
push edx
lea ecx, code_vm_test
add ecx,10h
push ecx
pop dword ptr
jmp L14
sub esp,0x150
L14:
mov ecx,1
xor eax,eax
mov ah,10h
mov bl,30h
L13:
add ecx,1
add ah,bl
cmp ecx,0x10
jle L13
//je L11
add eax,0x432
mov ebx,4
mov ecx,1
mov byte ptr,ah
//mov word ptr,ax
//mov dword ptr,eax
jmp L12
//L11:
mov g_num,eax
call test2
L12:
mov eax, 01h //eax=1:取CPU序列号
xor edx, edx
cpuid
mov acpuid, eax
mov dl,byte ptr
mov lcpuid, edx
pop edx
pop ecx
pop eax
add esp,0x150
retn 4
}
}
上面这个函数,翻译为中间表示如下:
VMStartVM_2
vPushImm4
vPushReg4
vSUB4
vPopReg4
VCheckESP
vPushReg4
vPUSH
vPushReg4
vPUSH
vPushReg4
vPUSH
vPushImm4
vReadMemDs4
vPushReg4
vPopReg4
vPushImm4
vPushReg4
vAdd4
vPopReg4
vPushReg4
vPUSH
vRetnNOT_
vNotSimulate
vResumeStart_
vPushImm4
vJMP
vPushImm4
vPushImm4
vPushReg4
vSUB4
vPopReg4
VCheckESP
vPushImm4
vPushReg4
vMOV4
vPopReg4
vPushReg4
vPushReg4
vXOR4
vPopReg4
vPushImm4
vPushReg1_above
vMOV4
vPopReg1_above
vPushImm4
vPushReg1_low
vMOV4
vPopReg1_low
vPushImm4
vPushReg4
vAdd4
vPopReg4
vPushReg1_low
vPushReg1_above
vAdd4
vPopReg1_above
vPushImm4
vPushReg4
vCMP
vPushImm4
vJLE
vPushImm4
vPushImm4
vPushReg4
vAdd4
vPopReg4
vPushImm4
vPushReg4
vMOV4
vPopReg4
vPushImm4
vPushReg4
vMOV4
vPopReg4
vPushReg1_above
vPushImm4
vPushReg4
vMUL_MEM
vPushReg4
vAdd4
vPushImm4
vAdd4
vWriteMemDs1
vPushImm4
vJMP
vPushImm4
vPushReg4
vPushImm4
vWriteMemDs4
vPushImm4
vRetnNOT_
vCALL
vResumeStart_
vPushImm4
vPushReg4
vMOV4
vPopReg4
vPushReg4
vPushReg4
vXOR4
vPopReg4
vRetnNOT_
vNotSimulate
vResumeStart_
vPushReg4
vPushImm4
vWriteMemDs4
vPushImm4
vReadMemDs1
vPushReg1_low
vPopReg1_low
vPushReg4
vPushImm4
vWriteMemDs4
vPushReg4
vPopReg4
vPOP4
vPushReg4
vPopReg4
vPOP4
vPushReg4
vPopReg4
vPOP4
vPushImm4
vPushReg4
vAdd4
vPopReg4
VCheckESP
vPushImm4
vRETN
9、中间表示的设计非常的重要,它牵涉到后面的一系列的操作,需要好好考虑,这个指令解析器其实可以推翻重新设计的,我总感觉有点混乱,但时间原因,没弄了。
或者先不分离数据到数据表,把数据放到中间表示的数据结构里面,到后面再处理?
见仁见智,有初学者看到这篇文章的话可以少走一些弯路。
https://static.52pojie.cn/static/image/hrline/4.gif
0x02 垃圾指令构造器
垃圾指令构造器的设计非常简单,难点在于垃圾指令的选择,有些指令是不能作为垃圾指令的,改变普通寄存器的指令不能用,比如AAA指令,会改变eax寄存器的值。具体参考Intel手册。
下面是垃圾指令的构造器
//VMTABEL表的元素个数
#define VMTABLEMAXLEN 0x1000
//没有用到寄存器
#define NONE -1
//操作数类型
#define SEG_UNDEF -1 //没有段寄存器
#define SEG_ES 0 // Indexes of segment/selector registers
#define SEG_CS 1
#define SEG_SS 2
#define SEG_DS 3
#define SEG_FS 4
#define SEG_GS 5
enum optype
{
NONETYPE,
IMMTYPE,
REGTYPE,
MEMTYPE,
CSTYPE,
DSTYPE,
ESTYPE,
SSTYPE,
FSTYPE,
GSTYPE,
};
struct VMTable
{
char VMInstrName; //VM命令名称
char strInstruction; //相对的汇编指令
int OperandNum; //操作数个数
int Segment; //段前缀
int optype; //操作类型(寄存器,立即数,内存数)
int bitnum; //位数
int NeedReg; //执行命令前要使用的寄存器
int SaveReg; //执行命令后要保存的指令
BOOL Reg2Esp; //第2个寄存器是否恢复,一般为0不恢复
};
VMTable vmtable32 =
{
//MOV
{"VMOV_REG08_REG08","MOV",2, SEG_UNDEF, REGTYPE, REGTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG16_REG16","MOV",2, SEG_UNDEF, REGTYPE, REGTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG32_REG32","MOV",2, SEG_UNDEF, REGTYPE, REGTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG08_IMM32","MOV",2, SEG_UNDEF, REGTYPE, IMMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG16_IMM32","MOV",2, SEG_UNDEF, REGTYPE, IMMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG32_IMM32","MOV",2, SEG_UNDEF, REGTYPE, IMMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG08_MEM08","MOV",2, SEG_UNDEF, REGTYPE, MEMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG16_MEM16","MOV",2, SEG_UNDEF, REGTYPE, MEMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG32_MEM32","MOV",2, SEG_UNDEF, REGTYPE, MEMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM08_REG08","MOV",2, SEG_UNDEF, MEMTYPE, REGTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM16_REG16","MOV",2, SEG_UNDEF, MEMTYPE, REGTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM32_REG32","MOV",2, SEG_UNDEF, MEMTYPE, REGTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM08_IMM32","MOV",2, SEG_UNDEF, MEMTYPE, IMMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM16_IMM32","MOV",2, SEG_UNDEF, MEMTYPE, IMMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_MEM32_IMM32","MOV",2, SEG_UNDEF, MEMTYPE, IMMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM08_IMM32","MOV",2, SEG_FS, MEMTYPE, IMMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM16_IMM32","MOV",2, SEG_FS, MEMTYPE, IMMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM32_IMM32","MOV",2, SEG_FS, MEMTYPE, IMMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM08_IMM32","MOV",2, SEG_GS, MEMTYPE, IMMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM16_IMM32","MOV",2, SEG_GS, MEMTYPE, IMMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM32_IMM32","MOV",2, SEG_GS, MEMTYPE, IMMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG08_FSMEM08","MOV",2, SEG_FS, REGTYPE, MEMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG16_FSMEM16","MOV",2, SEG_FS, REGTYPE, MEMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG32_FSMEM32","MOV",2, SEG_FS, REGTYPE, MEMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG08_GSMEM08","MOV",2, SEG_GS, REGTYPE, MEMTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG16_GSMEM16","MOV",2, SEG_GS, REGTYPE, MEMTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_REG32_GSMEM32","MOV",2, SEG_GS, REGTYPE, MEMTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM08_REG08","MOV",2, SEG_FS, MEMTYPE, REGTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM16_REG16","MOV",2, SEG_FS, MEMTYPE, REGTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_FSMEM32_REG32","MOV",2, SEG_FS, MEMTYPE, REGTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM08_REG08","MOV",2, SEG_GS, MEMTYPE, REGTYPE,8,8, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM16_REG16","MOV",2, SEG_GS, MEMTYPE, REGTYPE,16,16, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{"VMOV_GSMEM32_REG32","MOV",2, SEG_GS, MEMTYPE, REGTYPE,32,32, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VCMC","CMC",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VNOP","NOP",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VSTC","STC",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VSTD","STD",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VCLC","CLC",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, RT_Eax,NONE,NONE,NONE, RT_Eax,NONE,NONE,NONE },
{ "VNOT","NOT",1, SEG_UNDEF, REGTYPE, NONETYPE,8,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VNOT","NOT",1, SEG_UNDEF, REGTYPE, NONETYPE,16,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VNOT","NOT",1, SEG_UNDEF, REGTYPE, NONETYPE,32,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
{ "VCLD","CLD",0, SEG_UNDEF, NONETYPE, NONETYPE,0,0, NONE,NONE,NONE,NONE, NONE,NONE,NONE,NONE },
......等等
//结束标志
{"end","end",0, 0, 0, 0,0x520000,0,0,0,0,0,0,0,0,0 }
}
//获取随机数(范围在Min_到MAX_)
DWORD SrandNum(int Min_, int Max_)
{
return rand() % (Max_ - Min_) + Min_;
}
//求vmtable32结构体数组的长度
int VMLoader2::VMLength()
{
for (int i = 0; i < VMTABLEMAXLEN; i++)
{
if (vmtable32.bitnum == 0x520000)
{
return i;
}
}
return 0;
}
//vmtable32结构体数组的长度
int m_vmlength = 0;
//生成垃圾指令
CString VMLoader2::ProduceRubbishOpecode(char* reg04, char* reg05)
{
VMTable vmtbl = vmtable32;
CString str = vmtbl.strInstruction;
//1、目的操作
switch (vmtbl.optype)
{
case NONETYPE://没有操作数
break;
case IMMTYPE://立即数
{
if (8 == vmtbl.bitnum)
{
str = str + " " + 4;
}
else if (16 == vmtbl.bitnum)
{
str = str + " " + 4;
}
else
{
str = str + " " + 8;
}
}
break;
case REGTYPE://寄存器
{
if (8 == vmtbl.bitnum)
{
for (int i = 0; i < 14; i++)
{
if (stricmp(reg04, regname_) == 0)
{
str = str + " " + regname_;
break;
}
}
}
else if (16 == vmtbl.bitnum)
{
for (int i = 0; i < 14; i++)
{
if (stricmp(reg05, regname_) == 0)
{
str = str + " " + regname_;
break;
}
}
}
else
{
str = str + " " + reg05;
}
}
break;
case MEMTYPE://内存
{//随机选择vmp1节中没有用到的内存
DWORD dnum = SrandNum(m_vmps.vmp1_startaddr+0x4000, m_vmps.vmp1_startaddr+0x5000);
CString memstr = dnum;
if (8 == vmtbl.bitnum)
{
str = str + " byte ptr[" + memstr.GetString() + "]";
}
else if (16 == vmtbl.bitnum)
{
str = str + " word ptr[" + memstr.GetString() + "]";
}
else
{
str = str + " dword ptr[" + memstr.GetString() + "]";
}
}
break;
default:
break;
}
//2、源操作数
switch (vmtbl.optype)
{
case NONETYPE://没有操作数
break;
case IMMTYPE://立即数
{
if (8 == vmtbl.bitnum)
{
str = str + "," + 4;
}
else if (16 == vmtbl.bitnum)
{
str = str + "," + 8;
}
else
{
str = str + "," + 4;
}
}
break;
case REGTYPE://寄存器(操作数2的寄存器可以在8个寄存器中任意选择)
{
if (0 == stricmp(vmtbl.strInstruction,"xchg"))
{//如果是xchg,寄存器则选择reg04,或者reg05
if (8 == vmtbl.bitnum)
{
for (int i = 0; i < 14; i++)
{
if (stricmp(reg05, regname_) == 0)
{
str = str + "," + regname_;
break;
}
}
}
else if (16 == vmtbl.bitnum)
{
for (int i = 0; i < 14; i++)
{
if (stricmp(reg04, regname_) == 0)
{
str = str + "," + regname_;
break;
}
}
}
else
{
str = str + "," + reg04;
}
break;
}
if (8 == vmtbl.bitnum)
{
str = str + "," + regname_;
}
else if (16 == vmtbl.bitnum)
{
str = str + "," + regname_;
}
else
{
str = str + "," + regname_;
}
}
break;
case MEMTYPE://内存
{//随机选择vmp1节内的地址,或者选esp寄存器
DWORD dnum = SrandNum(m_vmps.vmp1_startaddr, m_vmps.vmstartaddr);
CString memstr = dnum;
const char* memchr = { memstr.GetString(),"esp+20","esp+28","esp+0x30","esp+0x14" };
const char* srandstr = memchr;
if (8 == vmtbl.bitnum)
{
str = str + ",byte ptr[" + srandstr + "]";
}
else if (16 == vmtbl.bitnum)
{
str = str + ",word ptr[" + srandstr + "]";
}
else
{
str = str + ",dword ptr[" + srandstr + "]";
}
}
break;
default:
break;
}
return str;
}
每调用一次ProduceRubbishOpecode就可以构造一条垃圾指令,其实这个垃圾指令构造器还可以细化。想要怎么设计,看需求。
https://static.52pojie.cn/static/image/hrline/4.gif
0x03
handler的设计可以把要用到的handler放到一个表格中归类。
举个vJAE的例子,如下:
CString vJAE(char* VR0, char* VR1)//jae jnc jnb(无符号 大于等于跳转 CF=0)
{
CString str = "push dword ptr\n";
str = str + "pop "+ VR0 +"\n";
str = str + "mov " + VR1 + ",0\n";
str = str + "and "+ VR0 +",1\n";
str = str + "mov "+ VR0 +",dword ptr\n";
str = str + "cmove " + VR1 + ",dword ptr\n";
str = str + "cmove "+ VR0 +",dword ptr\n";
str = str + "add ebp," + VR1 + "\n";
str = str + "add esi,"+ VR0 +"\n";
str = str + "add esp,0xC\n";
return str;
}
感谢大佬 感谢分享 ,学习了 强大,学习中,非常好的资料。 厉害大佬 学习了 厉害,即使我确实看明白了些理论。。。然而这辈子估计是没有这个动力和耐心了。。。
加油加油,52pj大神! 大神真厉害,感谢分享 感谢分享 起码我就做不到 膜拜…学习一波 站里大神超多,很棒,有用,收藏起来!!! 又是一个拨头发的利器:'(weeqw 牛人。感谢分享。 论坛里的大佬是真的有时间,我看一下就头晕了 度娘灬魂手 发表于 2020-9-23 21:07
论坛里的大佬是真的有时间,我看一下就头晕了
{:301_997:}我也没学多久,离大佬差太远了,努力就有收获,慢慢赶吧 厉害?? 厉害?? ,我到现在还一头雾水! 站里大神超多,很棒,有用,收藏起来!!! 好文,楼主好有耐心和时间。 舒默哦 发表于 2020-9-23 21:23
我也没学多久,离大佬差太远了,努力就有收获,慢慢赶吧
我光PE结构就学到半途而废,这些是真的不敢碰了,头发都快没了 度娘灬魂手 发表于 2020-9-23 23:59
我光PE结构就学到半途而废,这些是真的不敢碰了,头发都快没了
哈哈。。。。 站里大神超多,很棒,有用,收藏起来!!!