IDA来到main函数:
发现调用了FuncTable的+68,+6c,+70的函数,并且初始化了1,2,3,5,6,8等偏移的成员。每个FuncTable中的函数都传递了this指针,那么可以确定这是一个类的调用,然后分析+0x70处:
很明显的虚拟机特征,OD去掉ASLR也很观察出,0x401644就是handler的表,但是这里handler表只是起一个代理作用,真正的vm_handler在类的第一项的函数数组(FuncTable)里面:
handler也很简单,没有花指令等等,直接IDA就能看到伪代码,经过分析一些handler以及初始化部分,IDA创建一个类的结构体:
这样就很轻松的看程序结构啦~~~~,现在handler都已经识别了,主要的就是记录下每个handler的执行顺序以及vm_reg的值还有一些比如立即数的值等等,这里选择注入一个dll来hook掉vm_dispatcher输出Log文件。
Hook点就选择的这里,因为这个时候esi刚好保存的是VM_STRUCT的this指针,下一句刚好是vm_disaptcher,handler的索引都可以知道。Hook代码:
_declspec(naked) void HookMain()
{
__asm
{
pushad;
pushfd;
push esi;
push eax;
call HookLog;
popfd;
popad;
cmp eax, 0x16;
ja _above;
mov ecx, 0x0401545;
jmp ecx;
_above:
mov ecx, 0x0401633;
jmp ecx;
}
}
其中HookLog就是记录信息的call,传递this和handler索引作为参数。
先来一波 ”0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF“ 的Input。
第一次的LOG得到1200多行的handler记录:
第一部分应该是一个while循环,只放出循环的相同部分,这里就是依次在判断输入的字符串是否为'0' - '9' 或者'A' - 'F',如果不是就会直接进行vm_retn:
vPushImm32 Imm32 = 47
vEip_Unknown reg4 = 0
vPopReg32 reg3 , vEsp = 47 //
vMovReg32_Input8 reg0=8 , InputByte = 48 //r0 = Input[0] 48'0'
vMovReg32_XorRegHighLow reg2=0 XOR reg2=0 //r2 = 0
vCheckReg reg0=48 Check reg2=0 //check( Input[0] == 0)
vAddEip_D0 reg5 = 1
vIncInputAndvEip
vPushImm32 Imm32 = 70
vPopReg32 reg1 , vEsp = 70
vCheckReg reg0=48 Check reg1=70 //check( Input[0] > 70'F' )
vAddEip_Z1 reg5 = -1
vPushImm32 Imm32 = 48
vPopReg32 reg1 , vEsp = 48
vCheckReg reg0=48 Check reg1=48 //check( Input[0] < 48'0' )
vAddEip_F1 reg5 = 0
vPushImm32 Imm32 = 57
vPopReg32 reg1 , vEsp = 57
vCheckReg reg0=48 Check reg1=57 //check( Input[0] > 57'9' )
vAddEip_F1 reg5 = -1
vMovReg32_XorRegHighLow reg0=48 XOR reg0=48
vCheckReg reg0=0 Check reg0=0
vAddEip_D0 reg5 = 0
vEip_Unknown reg4 = 47
第二部分就是计算工作,从最后一个Input开始倒着来:
//第二部分check
vDecInput_IncvEip //Input --
vMovReg32_Input8 reg0=0 , InputByte = 70 //r0 = 最后一个Input
vPushImm32 Imm32 = 48
vPopReg32 reg2 , vEsp = 48
vMovReg32_SubRegHighLow reg0=70 SUB reg2=48 //r0 = 最后一个Input - 48
vPushImm32 Imm32 = 10
vPopReg32 reg2 , vEsp = 10
vCheckReg reg0=22 Check reg2=10 //如果是输入是字母'A' - 'F'
vAddEip_F1 reg5 = 1
vPushImm32 Imm32 = 7
vPopReg32 reg2 , vEsp = 7
vMovReg32_SubRegHighLow reg0=22 SUB reg2=7 //r0 = r0 - 7
vPushImm32 Imm32 = 16
vPopReg32 reg2 , vEsp = 16
vMovReg32_MulRegHighLow reg1=0 MUL reg2=16 //r1 = r1 * 16
vMovReg32_AddRegHighLow reg1=0 ADD reg0=15 //r1 = r1 + r0
vEip_Unknown reg4 = 7
vMovReg32_Input8 reg0=10 , InputByte = 57
vPushImm32 Imm32 = 48
vPopReg32 reg2 , vEsp = 48
vMovReg32_SubRegHighLow reg0=57 SUB reg2=48 //r0 = input[i] - 48;
vPushImm32 Imm32 = 10
vPopReg32 reg2 , vEsp = 10
vCheckReg reg0=9 Check reg2=10 //是数字了
vAddEip_F1 reg5 = -1
vPushImm32 Imm32 = 16
vPopReg32 reg2 , vEsp = 16
vMovReg32_MulRegHighLow reg1=16702650 MUL reg2=16 //r1 = r1 * 16
vMovReg32_AddRegHighLow reg1=267242400 ADD reg0=9 //r1 = r1 + r0
vEip_Unknown reg4 = 1
计算的Check部分
vPushImm32 Imm32 = -1573385282 //A2380BBE //Push A2380BBE
vPopReg32 reg2 , vEsp = 2721582014
vCheckReg reg1=4275878552 Check reg2=2721582014 //if(r1 == r2) 这里判断前面计算的是否等于这个,不等于就退出虚拟机
可以很清晰的看到计算的方法和结构,进行6轮运算,每次计算8个输入,最后结果与一个立即数进行一个比较:
伪代码:
r0 = input[i] - 48;
if (r0 > 10) //是字母
r0 = r0 - 7;
//是数字就不减
r1 = r1 * 16;
r1 = r1 + r0;
//8次运算后
if(r1 == 内存中的立即数)
其实算法这里就是将输入的十六进制转换为对应的数字,比如输入0123ABCD,那么输出就是DCBA3210
知道算法后可以进行多次的Log记录,这里因为一次对比失败后直接会退出,所以,这里我每次只取出正确的一组,记录6次就能够得到全部的正确hash。
//1 :A2380BBE
//2 :86CCC775
//3 :14BCEB64
//4 :768F2C49
//5 :7F87C53F
//6 :3E9D1FAE
//EAF1D9E3F35C78F794C2F86746BECB41577CCC68EBB0832A
启动参数带上EAF1D9E3F35C78F794C2F86746BECB41577CCC68EBB0832A,最后: