v爷爷tql!膜v爷爷!何时才能像v爷爷一样优秀阿!
看了v爷爷的出题思路
从解题角度阐述一下思路,欢迎交流=-=
main函数中比较简单
输入通过argv[1]送入
sub_12F9E0中检查格式,并返回除"hctf{}"外的字符个数,要求为73
sub_12FB10中分隔输入,处理为46+27两段内容
sub_12F430中检查第一段46个字符
sub_12F070中解码并写出一个驱动
sub_12E430中在注册表里注册该驱动
下文所有关于硬件虚拟化的了解都是基于做题时搜索查询的,可能会有不少错误,还望指出海涵。
将每个字符分为高5位和低3位,分别保存成两个数组
根据低3位在sub_12F650中变化高5位,变化的方法都是很简单的加和异或
然后将两个数组合成一个,最后比较
反向处理根据结果的奇数位字节反向变换偶数位字节,再合并即可
在写出的函数中将后27个字符放入字节数组中,即作为驱动的data保存
动调可获得完整内容
驱动入口为DriverEntry,几个处理函数用处都不大,关键只有sub_403310这一个函数
开始read了cr寄存器和msr寄存器,申请了几个内存,保存了各种寄存器,不用太关心
但是从sub_401596中可以看到一个不常见的指令:vmxon
这里开始才显露出这个题目的真正獠牙:硬件虚拟化
vmxon表示VMX(Virtual-Machine Extensions)的启动
关键在sub_402B60中的一系列操作
大部分的东西其实都是不用看的,我估计出题人也不是自己一句一句写的(XD,调用的就两个函数VM_WRITE和READ_MSR
关键在最后一个sub_401631,里面是另一个不常见的指令:vmlaunch
vmlaunch表示Guest虚拟机启动,下面运行的指令就都跟外层物理机无关了
相关资料中的流程高度相似,可以以此参考
vmlaunch以后,最大的区别就是一些特殊指令将会被VMX捕获,进入到VMExitProc函数中进行处理
这个函数在0x402f61处绑定到结构体中
默认状态下,IDA似乎没有识别到这个函数,只是一个Dword
需要自己找到对应的地址按P来CreateFunction
这个函数中基本都是环境的保存和恢复
核心逻辑在sub_402880中
开头的五个调用可以参照上文的相关资料恢复出符号
这里的关键是ExitReason,也就是下面处理的switch的变量
可以参考这篇资料
对照发现,VMExitProc中对CPUID、INVD、VMCALL、CR_ACCESS、MSR_READ、MSR_WRITE这几条指令有特殊处理,之后我们需要特殊分析
通过分析几个handler可以大体构建出整个程序的目的
在处理handler之前,首先要声明两个数据结构
几个handler都是在操作他俩,而IDA由于丢失符号导致这两个结构识别的很破碎,需要自己根据handler和上下文语义来恢复
主要是最后检查的时候范围为data的9x9,而对vm_code的几次操作都不超过10
根据eax选择对vm_code异或的数组
根据eax变换vm_code
值得一提的是dword_405374这个数组是vm_code-4(byte)
的地方
首先拆解eax,高1字节作为command,高2字节处理后作为data的index,低2字节选择是否逆序,低1字节作为input的index
主要是根据command和vm_code来处理data
这里有一个很容易误解的点
Dword_405174(point_data)处是一个指针,指针指向data[9][9]的首地址&data[0][0]
从汇编可以很容易看出来
我们知道,在C语言中定义一个数组a[10],那么a就是常量&a[0]。此时如果再令指针p=&a[0]=a,则a[x]
等价于*(a + x*4)
等价于*(p+x*4)
等价于p[x]
也就是说,a[x]和p[x]实际上是相同的
而在IDA中,或者说是汇编中,p和a却是两个量:a是常量地址,在汇编中表示为数组首地址,而p则是一个指针,指向数组首地址。
这意味着对a查看交叉引用找不到对p的调用
这么讲很好理解,但是在汇编里我觉得有点懵orz
好的,说了这么多其实就是说point_data等价于data
vm_code对应的几个函数都是修改data的
最后有两个与众不同的,0xdd和0xff
0xdd里有vmxoff的调用,显然是退出虚拟机用的
0xff里则是一个检查data的函数
简单理一下可以看出来,一共循环9次,每次根据一个数组拿到9个下标,然后要求data的这9个数为不重复的1-9
PS: 这里的check函数虽然有个局部变量作为结果,但是并不会返回,也不影响任何东西,所以正确与否几乎没有显式体现
以及check的9次循环参数给的都是1,正常情况下应该为1-9,需要自己根据理解修正
根据eax变换data
于是现在有了操作方式和最终结果,接下来只要看vm是怎么操作的就可以了
从vmlaunch往后
首先将Dword_40557C赋值为0,这是个虚拟机是否成功运行的标志变量,由Guest虚拟机执行,则当vmoff以后物理机中的该变量将仍然为1。
继续往后走,退到0x4016E0处
这七句分别令VMExitProc调用了cpuid_handler、invd_handler和readmsr_handler,注意readmsr_handler中eax为0x174,而不是0x176,这里F5状态下IDA会将ecx作为宏的参数
继续往后走,进到sub_4030B0中
里面除了几个rdmsr和invd以外就是大量的vmcall在修改data
值得注意的是最后一个参数为vmcall(0xFFEEDEAD);
按照vmcall_handler的意思,这里应该是进行fake_check的
所以处理到这里就结束了
在0x4016CD出的cpuid和invd不纳入考虑是因为此时的代码仍然是物理机执行的,因此这些指令不会被VMX的VMExitProc捕获进行处理
总体来看就是将input和data进行了一些运算,最后按照给定下标进行校验9x9的数独
推好计算,然后逆运算也是可行的
但是让我手算数独是不可能的,这辈子都是不可能的
z3启动!
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!