CTF逆向专题连载之虚拟机vm混淆(2)

  • A+
所属分类:逆向工程

前言

在上篇文章CTF逆向专题连载之虚拟机vm混淆(1)中,我们了解到了虚拟机的结构,目的是为了辨别题目是否是虚拟机的考点。这篇文章将讲述虚拟机题目的两种做法。
虚拟机题目的解题方法一般分为3种:
  1. 动态调试汇编代码。

  2. 动态调试源码级别代码。

  3. angr解决虚拟机。

这次我们来讲解一下“动态调试汇编代码”和“动态调试源码级别代码”两种方法。

解题方法1:动态调试汇编代码

在虚拟机中,通过取得不同的字节码,调用不同处的一段汇编代码来进行各种操作。因此,单一的汇编代码并不能传达足够的信息,因此我们需要进行动态调试:理解当字节码不同时,不同的汇编代码段落,代表着什么意思。

我们从题目来讲解动态调试方法。

GACTF easyRe

题目考点:SMC+虚拟机

在过了SMC自解码后,我们进入到main函数。

CTF逆向专题连载之虚拟机vm混淆(2)

进入到函数sub_8058838,观察grap图

CTF逆向专题连载之虚拟机vm混淆(2)

根据上述信息可知:这是个虚拟机题目(PS:看不出可参照上文)

我们使用ida进行动态调试,从而确定不同操作码的作用。根据取到的操作码不同su动态调试过程没办法演示,需要自己调试观察。
可知各个不同操作码(字节码)的作用(PS:操作码的作用如此,但运算操作时的数值不是固定的)
0x9:a1[1] = input;0x16:a1[9] = a1[1];0x80:略过,没有影响。0x22:a1[1] >> = a1[2]  ; a1[2] = 13(这些值不是固定的)0x77:a1[1] ^= a1[9]    ; a1[9] = 输出时的a1[1]0x10:a1[9] = a1[1]       ; a1[1]计算之后出来的值又放至a1[9]0x23:a1[1] << = a1[2]  ; a1[2] = 90x31:a1[1] &=a1[2]       ; a1[2] = 0x78F396000x77:a1[1] ^a1[9]      ; a1[9] = 0x10后的a1[9]0x10:a1[9] = a1[1]       ; 0x23:a1[1] << a1[2]       ; a1[2] = 170x31:a1[1] &=a1[2]       ; a1[2] = 0x85D400000x77:a1[1] ^a1[9]       ;0x10:a1[9] = a1[1]       ;0x22:a1[1] >>=a1[2]       ;a1[2] = 190x77:a1[1] ^a1[9]       ;0xA0:a1 ==? 653840640  ;最终值是否等于653840640,不等于则退出
0xA4:存a1[1]
0xA1:print("flag:") ;长度为33的0xC1:取输入的flag0xB1:取前边4个计算结果0x77:flag的每一位分别与四个中的一个^计算0xC2:比较。与一个地方的值比较
0xC1:取flag0xB2:取前边结果的第二个 ;B1 B2 B3 B4分别是四个前边的值0x77:^0xC2:
0xC1:取flag
读者可以自己多调试几次,确定好操作码的意思。
以下是相对完整的记录过程:
操作码说明0x9:a1[1] = input;0x16:a1[9] = a1[1];0x80:略过,没有影响。0x22:a1[1] >> = a1[2]  ; a1[2] = 130x77:a1[1] ^= a1[9]    ; a1[9] = 输出时的a1[1]0x10:a1[9] = a1[1]       ; a1[1]计算之后出来的值又放至a1[9]0x23:a1[1] << = a1[2]  ; a1[2] = 90x31:a1[1] &=a1[2]       ; a1[2] = 0x78F396000x77:a1[1] ^a1[9]      ; a1[9] = 0x10后的a1[9]0x10:a1[9] = a1[1]       ; 0x23:a1[1] << a1[2]       ; a1[2] = 170x31:a1[1] &=a1[2]       ; a1[2] = 0x85D400000x77:a1[1] ^a1[9]       ;0x10:a1[9] = a1[1]       ;0x22:a1[1] >>=a1[2]       ;a1[2] = 190x77:a1[1] ^a1[9]       ;0xA0:a1 ==? 653840640  ;最终值是否等于653840640,不等于则退出-----------------------------------------------------------未知内容-a1[1]  a1[9]->该值是否是上边的计算结果?0x9:input                ; 同样是上边的输入  0xFFE8BC9A0x31:a1[1] & 0xff       ;&a1[2]0x43:a1[1] * 0x08629008 ;*a1[3] 乘法  == 0x1340x41:a1[1] + 0x180xA4:存a1[1]

下一个input开始0x9:input0x22:input >> 0x8 ; 0x31:input & 0xFF ;这两步结合起来应该是 取3,4处,前边那里是取1,2处0x44:input / 0xBC ;除法,没错的话,此时的input是7,结果为1A0x41:input + 0x21 ;加法 0xA4:存
下一个input开始0x9:input0x22:input >> 0x10 ;0x31:input & 0xFF ;这两步结合起来应该是 取 5 6处0x77:input ^ 0xBB ;0x41:input + 0xff ; 0xA4:存
下一个input开始0x9:input0x22:input >> 0x180x31:input & 0xff ;取 7 8 处0x42:input - 0xA0 ;0x41:input + 0x77 ;0xA4:存

再之后:0xA1:print("flag:") ;长度为33的0xC1:取输入的flag0xB1:取前边4个计算结果0x77:flag的每一位分别与四个中的一个^计算0xC2:比较。与一个地方的值比较
0xC1:取flag0xB2:取前边结果的第二个 ;B1 B2 B3 B4分别是四个前边的值0x77:^0xC2:
0xC1:取flag0xB4: ;貌似不是按顺序的 cmp :0x95 0x106 .data:0804B164 db 0ADh 相差八位取值应该使用脚本提取B1/2/3/4的顺序。也用脚本提取最终的比较值 题目就能完成了


因此,根据调试结果,写解密脚本。

"""1.smc2.虚拟机操作:    通过一个个字节码,分为了三部分的操作:        2.1 输入一个值,经过一系列计算后,等于某个值。使用z3求解。        2.2 该值分为4部分,各自进行计算。得出四个中间值,用于后边的计算。        2.3 进行flag的输入。对输入的flag进行计算。最终与其中的一些字节码比较。得出结果    很多数据 比如 最终比较的值,中间值的选择,我都直接用idapython提取出来了。3.模拟执行的办法 要实现。"""from z3 import *s = Solver()a = BitVec("a", 34)print(type(a)) a ^= (a >> 13)a ^= (a << 9) & 0x78F39600a ^= (a << 17) & 0x85D40000a ^= a >> 19s.add(a == 0x26F8D100) print(s.check())m = s.model()print(m)[a = 4293442714]  # print("".join(list(map(chr, range(ord('a'), ord('z') + 1))))+"abcdefg")# print(len("abcdefghijklmnopqrstuvwxyzabcdef")) tmp = [0x14C, 0x3B, 0x152, 0x0D6] # 取 B = [0x1,0x2,0x4,0x3,0x2,0x4,0x1,0x3,0x1,0x1,0x3,0x3,0x4,0x2,0x3,0x1,0x3,0x3,0x2,0x1,0x3,0x1,0x3,0x4,0x1,0x4,0x1,0x3,0x2,0x3,0x1,0x2]# [0xb1,0xb2,0xb4,0xb3,0xb2,0xb4,0xb1,0xb3,0xb1,0xb1,0xb3,0xb3,0xb4,0xb2,0xb3,0xb1,0xb3,0xb3,0xb2,0xb1,0xb3,0xb1,0xb3,0xb4,0xb1,0xb4,0xb1,0xb3,0xb2,0xb3,0xb1,0xb2]print(len(B)) fin = [0x10b,0x7a,0x95,0x106,0x7d,0xad,0x12f,0x165,0x12d,0x12f,0x139,0x10d,0xbb,0x8,0x10d,0x13f,0x13a,0x161,0x57,0x120,0x10d,0x13f,0x13f,0xb5,0x113,0xa0,0x121,0x10d,0xb,0x139,0x173,0x46]print(len(fin))# for i in range(len(B)):#    print(tmp[B[i] - 1])for i in range(len(B)):    fin[i] ^= tmp[B[i] - 1] # tmp的范围是[0,3] 但B的范围是从1开始的,是[1,4]for i in fin:    print(chr(i),end="")# print(fin)



解题方法2:动态调试汇编代码

使用源码级调试,我们可以根据虚拟机题目作一定的修改,比如去混淆,patch等操作。使ida可以F5汇编代码。
依旧是该题目
进入函数sub_8048838后F5,发现报错"too big function"
CTF逆向专题连载之虚拟机vm混淆(2)
修改配置文件IDA 7.0cfghexrays.cfg
MAX_FUNCSIZE            = 64        // Functions over 64K are not decompiled // 修改为:MAX_FUNCSIZE            = 1024        // Functions over 64K are not decompiled
再报错“stack frame is too big”,在函数处按U->Alt+P。
之后便可以F5并动态调试。

CTF逆向专题连载之虚拟机vm混淆(2)      与汇编动态调试相同,观察寄存器等内容,作记录。调试结果与前文相同,不多赘述。


总结

  1. 汇编代码动态调试是最直观最直接的办法。

  2. 虚拟机题目调试需要较长时间,需要耐心。

  3. 源码级代码调试的优点在于:源码级代码调试比汇编代码会更加容易分析,加快代码阅读速度;而缺点在于需要对代码作修整,若是题目复杂,可能效率上反而低。

系列文章链接:

CTF逆向专题连载之虚拟机vm混淆(1)


(题目出自2020 GACTF)题目下载链接:

https://pan.baidu.com/s/1hjmg47QLyCcwvZhWSHg0pA

提取码:fybv



本文始发于微信公众号(山石网科安全技术研究院):CTF逆向专题连载之虚拟机vm混淆(2)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: