首页
论坛
课程
招聘
[原创][HCTF-2018] Rev-Spiral -解题思路
2018-11-12 20:44 2677

[原创][HCTF-2018] Rev-Spiral -解题思路

2018-11-12 20:44
2677

v爷爷tql!膜v爷爷!何时才能像v爷爷一样优秀阿!
看了v爷爷的出题思路
从解题角度阐述一下思路,欢迎交流=-=

spiral

main函数中比较简单
输入通过argv[1]送入
sub_12F9E0中检查格式,并返回除"hctf{}"外的字符个数,要求为73
sub_12FB10中分隔输入,处理为46+27两段内容
sub_12F430中检查第一段46个字符
sub_12F070中解码并写出一个驱动
sub_12E430中在注册表里注册该驱动

 

下文所有关于硬件虚拟化的了解都是基于做题时搜索查询的,可能会有不少错误,还望指出海涵。

check1(sub_12F430)

将每个字符分为高5位和低3位,分别保存成两个数组
根据低3位在sub_12F650中变化高5位,变化的方法都是很简单的加和异或
然后将两个数组合成一个,最后比较
反向处理根据结果的奇数位字节反向变换偶数位字节,再合并即可

check2(spiral_core.sys)

在写出的函数中将后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处绑定到结构体中

vmwrite(0x6C16, (int)VMExitProc);

默认状态下,IDA似乎没有识别到这个函数,只是一个Dword
需要自己找到对应的地址按P来CreateFunction

VMEXITPROC分析

这个函数中基本都是环境的保存和恢复
核心逻辑在sub_402880中
开头的五个调用可以参照上文的相关资料恢复出符号

//获取本次Exit的详细信息
ULONG ExitReason = VMRead_ULONG(VMX_VMCS_RO_EXIT_REASON);//0x4402
ULONG ExitInterruptionInformation = VMRead_ULONG(VMX_VMCS_RO_EXIT_INTERRUPTION_INFO);//0x4404
ULONG ExitInstructionLength = VMRead_ULONG(VMX_VMCS_RO_EXIT_INSTR_LENGTH);//0x440C
ULONG ExitQualification = VMRead_ULONG(VMX_VMCS_RO_EXIT_QUALIFICATION);//0x6400

这里的关键是ExitReason,也就是下面处理的switch的变量
可以参考这篇资料

 

对照发现,VMExitProc中对CPUID、INVD、VMCALL、CR_ACCESS、MSR_READ、MSR_WRITE这几条指令有特殊处理,之后我们需要特殊分析

 

通过分析几个handler可以大体构建出整个程序的目的

 

在处理handler之前,首先要声明两个数据结构
几个handler都是在操作他俩,而IDA由于丢失符号导致这两个结构识别的很破碎,需要自己根据handler和上下文语义来恢复
主要是最后检查的时候范围为data的9x9,而对vm_code的几次操作都不超过10

int vm_code[10]
int data[9][9]
cpuid_handler

根据eax选择对vm_code异或的数组

invd_handler

根据eax变换vm_code
值得一提的是dword_405374这个数组是vm_code-4(byte)的地方

vmcall_handler

首先拆解eax,高1字节作为command,高2字节处理后作为data的index,低2字节选择是否逆序,低1字节作为input的index

 

主要是根据command和vm_code来处理data
这里有一个很容易误解的点
Dword_405174(point_data)处是一个指针,指针指向data[9][9]的首地址&data[0][0]
从汇编可以很容易看出来

.text:00401CE9                 mov     eax, point_data
.text:00401CEE                 mov     ecx, [eax+edx*4]

我们知道,在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,需要自己根据理解修正

readmsr_handler

根据eax变换data

 

于是现在有了操作方式和最终结果,接下来只要看vm是怎么操作的就可以了

Guest虚拟机的流程

从vmlaunch往后
首先将Dword_40557C赋值为0,这是个虚拟机是否成功运行的标志变量,由Guest虚拟机执行,则当vmoff以后物理机中的该变量将仍然为1。

 

继续往后走,退到0x4016E0处

.text:0040171D                 mov     eax, 0DEADBEEFh
.text:00401722                 cpuid
.text:00401724                 mov     eax, 4437h
.text:00401729                 invd
.text:0040172B                 mov     eax, 174h
.text:00401730                 mov     ecx, 176h
.text:00401735                 rdmsr

这七句分别令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启动!

 

全盘照抄,将input填入27个BitVec(32)即可
最后sovle一下就行

 

注意这个虽然每次运算过后都会&0xff但是本质上data还是Int型的,如果使用8位的BitVec会产生很要命的错误,折腾了我3个小时orz
output中有大于255的初值,如果直接舍弃高位,对于除法将会产生错误

 

顺便看在v爷爷答应我面基赔一血的份上,就不告诉大家他不慎出了个多解了。

 

不过这里其实也是给自己提了个醒,一共就6个多解,事实上也是可以接受的
z3的约束求解只会掏出一个可行解,要跑出所有解需要自己另行去重
以前也做过类似的操作,只不过不像这次使用的去重代码更通用
以后使用z3时最好都考虑上多解的可能

 

由于大量函数直接从IDA中复制,因此一些代码会比较丑陋,XD见谅

data = [0x07, 0xE7, 0x07, 0xE4, 0x01, 0x19, 0x03, 0x50, 0x07, 0xE4, 0x01, 0x20, 0x06, 0xB7, 0x07, 0xE4, 0x01, 0x22, 0x00, 0x28, 0x00, 0x2A, 0x02, 0x54, 0x07, 0xE4, 0x01, 0x1F, 0x02, 0x50, 0x05, 0xF2, 0x04, 0xCC, 0x07, 0xE4, 0x00, 0x28, 0x06, 0xB3,    0x05, 0xF8, 0x07, 0xE4, 0x00, 0x28, 0x06, 0xB2, 0x07, 0xE4,    0x04, 0xC0, 0x00, 0x2F, 0x05, 0xF8, 0x07, 0xE4, 0x04, 0xC0,    0x00, 0x28, 0x05, 0xF0, 0x07, 0xE3, 0x00, 0x2B, 0x04, 0xC4,    0x05, 0xF6, 0x03, 0x4C, 0x04, 0xC0, 0x07, 0xE4, 0x05, 0xF6,    0x06, 0xB3, 0x01, 0x19, 0x07, 0xE3, 0x05, 0xF7, 0x01, 0x1F,    0x07, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    0x00, 0x00]
flag = ""
for i in range(46):
    v = data[i*2]
    v1 = data[i*2+1]
    if(v==0):
        v1 -= 34
    elif(v==1):
        v1 -= 19
    elif(v==2):
        v1 -= 70
    elif(v==3):
        v1 -= 66
    elif(v==4):
        v1 ^= 0xca
    elif (v == 5):
        v1 ^= 0xfe
    elif (v == 6):
        v1 ^= 0xbe
    elif (v == 7):
        v1 ^= 0xef
    data[i*2+1] = v1
    flag += chr((v1<<3)|v)
print(flag)


def final_check(x):
    for i in x:
        s.add(i>0, i<10)
    for i in range(9):
        for j in range(i+1, 9):
            # print(x[i]!=x[j])
            s.add(x[i]!=x[j])

def invd(x):
    if(x==0x4433):
        for i in range(5):
            vm_code[2*i], vm_code[2*i+1] = vm_code[2*i+1], vm_code[2*i]
    elif(x==0x4434):
        buff = vm_code[0]
        for i in range(9):
            vm_code[i] = vm_code[i+1]
        vm_code[9] = buff
    elif(x==0x4437):
        v4 = vm_code[7]
        for k in range(3):
            vm_code[k+7] = vm_code[7-k-1]
            if(k==2):
                vm_code[7-k-1] = vm_code[3]
            else:
                vm_code[7-k-1] = vm_code[k+7+1]
        vm_code[3] = vm_code[3-0-2]
        vm_code[3-0-2] = vm_code[3-0-1]
        vm_code[3-1-1] = v4

def read_msr(x):
    if(x==374):
        v3 = data[76]
        v4 = data[36]
        for i in range(8, 0, -1):
            data[9*i+4] = data[9*(i-1)+4]
        data[4] = v3
        for i in range(8):
            data[36+i] = data[37+i]
        data[44] = v4
    elif(x==372):
        v6 = data[80]
        v7 = data[8]
        for i in range(8, 0, -1):
            data[10*i] = data[10*(i-1)]
        data[0] = v6
        for j in range(1, 9):
            data[8*j] = data[8*j+8]
        data[8*9] = v7

def vmcall(x):
    command = x>>24
    index = (x>>16)
    index = (index&0xf)+9*((index&0xf0)>>4)
    cho = (x>>8)&0xff
    x = x&0xff
    # c[x] = 1
    if(cho==0xcc):
        l = a
    else:
        l = a[::-1]
    # print(index, command, vm_code, data)
    if (command == vm_code[0]):
        data[index] = l[x]
    elif (command == vm_code[1]):
        data[index] += l[x]
    elif (command == vm_code[2]):
        data[index] -= l[x]
    elif (command == vm_code[3]):
        data[index] = data[index]/l[x]
    elif (command == vm_code[4]):
        data[index] *= l[x]
    elif (command == vm_code[5]):
        data[index] ^= l[x]
    elif (command == vm_code[6]):
        data[index] ^= l[x]+l[x-1]-l[x+1]
    elif (command == vm_code[7]):
        data[index] ^= l[x]*16
    elif (command == vm_code[8]):
        data[index] |= l[x]
    elif (command == vm_code[9]):
        data[index] ^= l[x+1]^l[x-1]^(l[x-2] + l[x] - l[x+2])
    else:
        print("Error: %d"%x)
    data[index] &= 0xff

from z3 import *
s = Solver()
a = [BitVec("a%d"%i, 32) for i in range(27)]
for i in a:
    s.add(i>32, i<127)
c = [0 for i in range(81)]
vm_code = [163, 249, 119, 166, 193, 199, 78, 209, 81, 255]
t1 = [147, 200, 69, 149, 245, 242, 120, 230, 105, 198]
t2 = [144, 205, 64, 150, 240, 254, 120, 227, 100, 199]
data = [7, 206, 89, 35, 9, 5, 3, 1, 6, 2, 6, 5, 125, 86, 240, 40, 4, 89, 77, 77, 75, 83, 9, 1, 15, 87, 8, 211, 56, 111, 665, 225, 54, 2, 118, 855, 106, 170, 884, 420, 93, 86, 87, 7, 127, 8, 168, 176, 9, 50, 2, 6, 1123, 1129, 5, 198, 2, 37, 104, 51, 50, 103, 1, 113, 1, 1287, 99, 8, 6, 163, 1525, 6, 49, 952, 101, 512, 40, 87, 1, 165, 9]
# init
v6 = data[40];
for i in range(4):
    data[8 * i + 40] = data[8 * i + 40-1];
    for j in range(2*i+1):
      data[3 - i + 9 * (i + 4 - j)] = data[3 - i + 9 * (i + 4 - (j + 1))];
    for k in range(2*i+2):
      data[k + 9 * (3 - i) + 3 - i] = data[10 * (3 - i) + k + 1];
    for l in range(2*i+2):
      data[9 * (l + 3 - i) + i + 5] = data[9 * (3 - i + l + 1) + i + 5];
    for m in range(2*i+2):
      data[9 * (i + 5) + i + 5 - m] = data[9 * (i + 5) + i + 5 - (m + 1)];
data[72] = v6;

# cpuid==0xCAFEBABE
# for i in range(10):
#     vm_code[i] ^= t1[i]
# invd(0x4437)

# cpuid==0xDEADBEEF
for i in range(10):
    vm_code[i] ^= t2[i]
invd(0x4437)
read_msr(372)

read_msr(374)
invd(0x4433)
vmcall(0x30133403);
vmcall(0x3401CC01);
vmcall(0x36327A09);
vmcall(0x3300CC00);
vmcall(0x3015CC04);
vmcall(0x35289D07);
vmcall(0x3027CC06);
vmcall(873647107);
vmcall(807849222);
vmcall(872947457);
vmcall(856802050);
vmcall(908446725);
vmcall(959499271);
vmcall(925144069);
vmcall(872575488);
vmcall(958622468);
vmcall(875655944);
vmcall(923061250);
invd(0x4434);
vmcall(944971537);
vmcall(875940873);
vmcall(943901706);
vmcall(892914443);
vmcall(928041997);
vmcall(910258445);
vmcall(945146895);
vmcall(928500752);
vmcall(926941196);
vmcall(826736911);
vmcall(826723339);
vmcall(927138318);
vmcall(909512458);
vmcall(827509774);
vmcall(877960210);
vmcall(877728016);
vmcall(877186060);
vmcall(909364232);
invd(0x4437);
vmcall(813747223);
vmcall(930360342);
vmcall(846318612);
vmcall(964938777);
vmcall(880982807);
vmcall(895990805);
vmcall(830997530);
vmcall(0x3965CC12);
vmcall(0x32869C19);
vmcall(0x3785CC1A);
vmcall(0x3281CC18);
vmcall(0x3262DC14);
vmcall(0x3573CC15);
vmcall(0x37566613);
vmcall(0x3161CC11);
vmcall(0x3266CC13);
vmcall(0x39844818);
vmcall(0x3777CC16);
# print(data)
# check = [4, 5, 6, 7, 8, 21, 23, 39, 55]
check = [0, 1, 2, 3, 18, 19, 20, 35, 36, 4, 5, 6, 7, 8, 21, 23, 39, 55, 16, 32, 48, 49, 64, 80, 81, 82, 96, 17, 33, 34, 50, 51, 52, 53, 65, 66, 22, 37, 38, 54, 67, 68, 69, 70, 84, 24, 40, 56, 72, 88, 103, 104, 120, 136, 71, 85, 86, 87, 101, 102, 118, 119, 135, 83, 98, 99, 100, 114, 116, 117, 133, 134, 97, 112, 113, 115, 128, 129, 130, 131, 132]
for j in range(9):
    v5 = [0 for i in range(9)]
    for i in range(9):
        v5[i] = data[((check[i+9*j]&0xf0)>>4)*9 + (check[i+9*j]&0xf)]
    print(v5)
    final_check(v5)
print(s.check())
# 去重并求出所有解
while(s.check()==sat):
    m = s.model()
    flag2 = ""
    for i in a:
        flag2 += chr(m[i].as_long())
    print(flag2)
    exp = []
    for val in a:
        exp.append(val!=m[val])
    s.add(Or(exp))

[公告]春风十里不如你,看雪团队诚邀你的加入!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (1)
雪    币: 2473
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
PYGame 活跃值 2018-11-12 21:42
2
0
我还以为是v校
游客
登录 | 注册 方可回帖
返回