前言:
最近在忙毕设与复试的内容,304测试暂时没顾上,今天分析了一下sub_E90函数,此函数内容还是挺多的。
知识点:
1.(_DWORD )?
(_DWORD )是强制类型转化,然后在提领指针。
dword是指注册表的键值,每个word为2个字节,dword双字即为4个字节。
结合以上信息可以推出其中一个代码*(_DWORD *)a1 + 11)的理解应为:
从 a1[11] 开始,按DWORD格式取出 四个字节。
2.byte类型
byte,即字节,由8位的二进制组成,byte类型的数据是8位带符号的二进制数。
byte类型与int类型的区别?
1、类型不一样,byte是字节数据的类型,int是整型数据的类型。
2、占用的字节不一样,byte占用1 个字节,int占用4个字节。
3、大小范围不一样,byte 的大小范围是道-128—127 ,int的大小范围是-2147483648到2147483647。
3.exit(0),_exit(0),exit(1),exit(-1)作用与区别
exit(0):正常运行程序并退出程序。
_exit(0):不能输出结果,未清除I/O缓存,不打印。
exit(1):非正常运行导致退出程序;
exit(-1):非正常运行导致退出程序,与1类似。
在main中return v;的效果与exit(v);相同。
伪代码分析
__int64 __fastcall sub_E90(__int64 *a1)
{
__int64 *v1; // rbx
unsigned int v2; // er14
__int64 v4; // rsi
unsigned __int64 v5; // rcx
__int64 v6; // rdx
_BYTE *v7; // r15
__int16 v8; // di
unsigned __int16 v9; // ax
__int16 v10; // ax
__int16 v11; // cx
__int16 v12; // ax
__int64 v13; // rax
__int16 v14; // ax
__int64 v15; // rax
__int16 v16; // ax
unsigned __int16 v17; // ax
__int16 v18; // dx
unsigned __int16 v19; // ax
int v20; // eax
int v21; // ecx
int v22; // eax
__int64 v23; // rax
v1 = a1; //v1指向数组a1
v2 = 1;
if ( !*((_DWORD *)a1 + 11) ) //a1[11]为0
{
v4 = *a1; //v4等于a1[0]
v5 = a1[1]; //v5等于a1[1]
v6 = *((unsigned __int16 *)a1 + 15); //v6等于a1[15]
v7 = (_BYTE *)(*a1 + v6); //v7等于a1[0]+v6,byte型
if ( (unsigned __int64)v7 >= v5 ) //v7大于v5
LABEL_72:
exit(-1); //非正常运行退出程序
v8 = v6 + 4;
*((_WORD *)v1 + 15) = v6 + 4; //v1[15]=v6+4
v2 = 0;
switch ( *v7 + 112 ) //v7+112
{
case 0:
return v2; //返回v2
case 128:
v11 = *((_WORD *)v1 + (unsigned __int8)v7[2] + 8) + *((_WORD *)v1 + (unsigned __int8)v7[3] + 8); //v11=a1[v7[2]+8]+a1[v7[3]+8]
goto LABEL_25; //执行LABEL_25之后的代码
。。。
。。。
。。。
}
return v2;
}
总结
sub_E90完成的是将读取的字节码翻译执行。
把这些字节码送入指令执行引擎函数sub_E90中,将字节码指令从虚拟地址0处开始逐条执行,当遇到HALT指令或发生错误时停止。
myvm中sub_E90函数执行的是中间switch那段,用于识别各种机器指令,并执行。
机器指令按照操作数的不同来分,包括双操作数指令(OP DEST,SRC)、单操作数指令(OPDEST)、无操作数指令(op)等3种。由于操作数的不同直接关系到指令指针IP的变化不同,因此R[IP]值的变化将因具体指令的操作数不同而异。
所以switch中的各个case,是各条指令,比如jmp,jz,sub等
具体功能后续再分析。