近几天略有闲暇,欲破解此题。这道题设计非常巧妙,榜上显示仅有5位大牛挑战成功,没做出来也不丢人哈。几经蹂躏,欲罢不能,下面容我这位菜鸟叨叨其坎坷历程,希望能帮助到更多的人。
1、初步分析
IDA Pro打开该程序,会提示入口点地址为0

大量未解析的代码,完全看不出代码的主体逻辑。

用PEiD打开也显示入口点为0

当初想的是既然入口点为400000,就在此处设置断点,单步跟踪即可。但事实是,F8跟踪出错。

换个方法,检查程序是否有字符串可以一步定位到关键点

一无所获,黔驴技穷,突然灵光一现,决定跟踪指令序列,找出程序关键分支。运行Run trace后发现了新生成的段msctfime(后来看了大牛们的Writeup才知道所有采用VB p-code编译的程序运行时都有msctfime段,此为后话)。


没有什么太好的办法,硬着头皮再次查看400000处的汇编代码,其实就是让程序跳到401090处执行。
直接F8跟踪401090,发现作者大量使用push+retn指令,类似于ROP技术来控制程序流程,最终执行来到401088。
之后不断按F8跟踪,就被绕到MSVBVM60的领空出不来,IDA pro也帮不了忙。后来才明白该程序为VB p-code编译的,之前没有遇到过。
在401088位置往上拖动几行可以发现MSVBVM60.MethCallEngine这个API函数,可以判断为VB程序,且采取p-code方式编译。

关于使用OD逆向分析VB p-code程序的方法及技巧可以参照看雪论坛的两篇文章学习
https://bbs.pediy.com/thread-189977.htm
2、逆向分析VB程序
用VBExplorer反编译程序,提示“不是一个VB程序!”

换成VB Decompiler即可,程序由3部分组成:Check_Click_40864C、Form_Load_408040、Proc_0_2_4081F0。

Form_Load_408040:初始化3个变量global_76, global_100, global_104。

Proc_0_2_4081F0:用来检查输入的合法性,即每个输入的字符在0x30-0x39,0x61-0x66之间,即要求输入小写的十六进制数。

Check_Click_40864C为程序主体逻辑,算法非常清晰。首先执行Proc_0_2_4081F0,检查用户的输入;然后分别取出输入的左8位和右8位,赋与变量global_84和global_92,要求两者都大于0,且global_84>global_92,计算出的var_94=global_104, var_8C=global_100。其中global_76, global_100和global_104都是已知的,但global_52, global_60和global_68的数值未知,需要通过OD动态调试确定数值。

点击Disassembler标签页,可以查到global_52,
global_60和global_68的存储位置,譬如loc_4083F1: MemLdFPR8 global_52,表明global_52的数值存储在4083F1。

可以在4083F1下内存访问断点获取。global_60和global_68的数值也依照此法获取,global_52=global_60=global_68=0。



问题1:
别人可能没遇到,当时设置内存访问断点获取global_X等变量值时,每次断点都无法断下来,不知道出了什么问题,为这个问题懊恼了很长时间,换了N种OD。为此花了一段时间专门研读了关于OD调试VB p-code代码的资料,方法技巧没有任何问题。
既然方法和工具无误,只可能输入的数据有问题了。后来发现自己输入的获取断点的测试数据都是诸如此类的
令global_84=a,
global_92=b, global_76=c, global_104=N1, global_100=N2,可以将Check_Click_40864C中的算法过程简化如下
3、重新检视代码,发现反调试手段
(1)利用堆栈段寄存器设置TF标志,控制流程走向
从入口点401090开始动态调试,执行到401095处,F7步入。

之后来到408E20获取ss,执行到408EC0。

此时EFL寄存器数值为0x216

执行完pop ss +
pushfd后,压入堆栈的EFL数值变成了0x316

之后将放入堆栈的EFL数值赋给edi,控制之后的程序流程。

去除跳转指令,简化后的指令如下所示
从上面这段代码可以发现隐藏有TF位判断。根据TF标志决定了不同跳转地址:401088或者408900。
设置TF标志发生408EC0处,在对堆栈段寄存器ss采取pop操作时,下一条指令pushfd指令会隐式执行,此时调试器无法停下来也没办法更改压入栈中的TF标志。在单步执行到408EC2压入堆栈的EFL寄存器数值为0x316,而直接执行到408EC2则压入堆栈的EFL寄存器数值为0x216。0x316与0x216的不同在于第8位不一样,即TF标志位不同。

(2)对OEP RVA=0进行隐形检测
上述代码中含有MOV指令SMC(Self Modifiing Code,自修改代码)。执行完408908后,eax=0x4000D9。408911处的指令将[4000D9]的内容取出存放到eax。此处用来隐形检测OEP RVA是否手工修改过,从而控制流程走向。如果修改过,则程序会跳转到0x4089C0,之后进入VB程序入口点;如果没修改过,OEP RVA=0,则程序跳转到0x408940。

假设我们手工将其改成1090,则程序可以跳转到0x401090,OD可正常调试运行。此时,[4000D9]=0x10


补充:
跳转地址的不同依赖于执行4088DF时的eax值。运算后,如果eax=0x00000057,则4088E4处的jmp指令的机器码就变成E9 5700000000,即jmp 408940;
如果eax=0x000000D7,则4088E4处的jmp指令的机器码就变成E9 D700000000,即jmp 4089C0。巧妙地利用了MOV指令来修改代码。


疑惑:不太明白为什么eax值变化了,为什么jmp的机器码也会发生改变?
(3)时间反调试
00401151 0F31 rdtsc
0040115D 8BD8
mov ebx,eax
….
00408F8A 0F31 rdtsc
00408F96 2BC3 sub eax, ebx
00408FA2 3D 00000003 cmp
eax, 0x3000000
计算时间差,如果大于0x3000000,则证明存在反调试,陷入循环中。该技术很普遍,不赘述。
上述粗体标识的汇编语句为执行MOV指令后的SMC。如下图所示发生变化


(4)检查PEB中的BeingDebugged标志,并修改VB程序的指令
0040115D 8BD8 mov ebx, eax
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课