[CTF/Reverse] [GWCTF 2019]babyvm
侧边栏壁纸
  • 累计撰写 65 篇文章
  • 累计收到 3 条评论

[CTF/Reverse] [GWCTF 2019]babyvm

x1n
x1n
2021-10-20 / 0 评论 / 88 阅读 / 正在检测是否收录...

[GWCTF 2019]babyvm

最近好久没做两道像样的逆向了,今天回BUU决定把以前的坑填补一下。随手开了一道VM题。

函数0x600CD1(省略Base)是一个VM的初始化函数,结合其内容确定结构体格式

image-20211020130148234.png

image-20211020130231262.png

之后可以很方便的通过结构体格式整理出VM OPcode,其中0xF1是可扩展的Mov,F2是XOR,F4是NOP,F5 输入,F6一个自定义的MUL,F7一个MUL,F8一个SWAP

根据控制流,分析当遇到NOP时停止,于是写出这样的脚本

Opt = [
    ...略
]
Target = [
    0x46, 0x7A, 0x7B, 0x61, 0x4D, 0x7B, 0x61, 0x4D, 0x7C, 0x7D, 0x66, 0x4D, 0x74, 0x7E, 0x73, 0x75, 0x4D, 0x20, 0x21, 0x21
]
Pointer = 0
while Opt[Pointer] != 0xF4 :
    # print(Pointer)
    if Opt[Pointer] == 0xF1 :
        if Opt[Pointer + 1] == 0xE1 :
            print("MOV AX, [{}]".format(hex(Opt[Pointer + 2])))
        elif Opt[Pointer + 1] == 0xE2 :
            print("MOV BX, [{}]".format(hex(Opt[Pointer + 2])))
        elif Opt[Pointer + 1] == 0xE3 :
            print("MOV CX, [{}]".format(hex(Opt[Pointer + 2])))
        elif Opt[Pointer + 1] == 0xE4 :
            print("MOV [{}], AX".format(hex(Opt[Pointer + 2])))
        elif Opt[Pointer + 1] == 0xE5 :
            print("MOV DX, [{}]".format(hex(Opt[Pointer + 2])))
        elif Opt[Pointer + 1] == 0xE7 :
            print("MOV [{}], BX".format(hex(Opt[Pointer + 2])))
        Pointer += 5
    elif Opt[Pointer] == 0xF2 :
        print("XOR AX, BX")
    elif Opt[Pointer] == 0xF5 :
        print("INPUT")
    elif Opt[Pointer] == 0xF6 :
        print("MOV AX, CX + 2*BX + 3*AX")
    elif Opt[Pointer] == 0xF7 :
        print("MUL AX, DX")
    elif Opt[Pointer] == 0xF8 :
        print("SWAP AX, BX")
    elif Opt[Pointer] == 0xF4 :
        print("NOP")
    else :
        print("Wrong at {}".format(str(Pointer)))
    Pointer += 1

打出来之后发现不太对劲,好多操作都没有,检查发现遇到了一个NOP之后停了,但是后面仍有操作码。更改循环条件为内存末跳出,得到这样的类汇编

INPUT
MOV AX, [0x0]
XOR AX, BX
MOV [0x20], AX
MOV AX, [0x1]
XOR AX, BX
MOV [0x21], AX
MOV AX, [0x2]
XOR AX, BX
MOV [0x22], AX
MOV AX, [0x3]
XOR AX, BX
MOV [0x23], AX
MOV AX, [0x4]
XOR AX, BX
MOV [0x24], AX
MOV AX, [0x5]
XOR AX, BX
MOV [0x25], AX
MOV AX, [0x6]
XOR AX, BX
MOV [0x26], AX
MOV AX, [0x7]
XOR AX, BX
MOV [0x27], AX
MOV AX, [0x8]
XOR AX, BX
MOV [0x28], AX
MOV AX, [0x9]
XOR AX, BX
MOV [0x29], AX
MOV AX, [0xa]
XOR AX, BX
MOV [0x2a], AX
MOV AX, [0xb]
XOR AX, BX
MOV [0x2b], AX
MOV AX, [0xc]
XOR AX, BX
MOV [0x2c], AX
MOV AX, [0xd]
XOR AX, BX
MOV [0x2d], AX
MOV AX, [0xe]
XOR AX, BX
MOV [0x2e], AX
MOV AX, [0xf]
XOR AX, BX
MOV [0x2f], AX
MOV AX, [0x10]
XOR AX, BX
MOV [0x30], AX
MOV AX, [0x11]
XOR AX, BX
MOV [0x31], AX
MOV AX, [0x12]
XOR AX, BX
MOV [0x32], AX
MOV AX, [0x13]
XOR AX, BX
MOV [0x33], AX
NOP
Wrong at 262
...
Wrong at 287
INPUT
MOV AX, [0x0]
MOV BX, [0x1]
XOR AX, BX
MOV [0x0], AX
MOV AX, [0x1]
MOV BX, [0x2]
XOR AX, BX
MOV [0x1], AX
MOV AX, [0x2]
MOV BX, [0x3]
XOR AX, BX
MOV [0x2], AX
MOV AX, [0x3]
MOV BX, [0x4]
XOR AX, BX
MOV [0x3], AX
MOV AX, [0x4]
MOV BX, [0x5]
XOR AX, BX
MOV [0x4], AX
MOV AX, [0x5]
MOV BX, [0x6]
XOR AX, BX
MOV [0x5], AX

MOV AX, [0x6]
MOV BX, [0x7]
MOV CX, [0x8]
MOV DX, [0xc]
MOV AX, CX + 2*BX + 3*AX
MUL AX, DX
MOV [0x6], AX
MOV AX, [0x7]
MOV BX, [0x8]
MOV CX, [0x9]
MOV DX, [0xc]
MOV AX, CX + 2*BX + 3*AX
MUL AX, DX
MOV [0x7], AX
MOV AX, [0x8]
MOV BX, [0x9]
MOV CX, [0xa]
MOV DX, [0xc]
MOV AX, CX + 2*BX + 3*AX
MUL AX, DX
MOV [0x8], AX

MOV AX, [0xd]
MOV BX, [0x13]
SWAP AX, BX
MOV [0xd], AX
MOV [0x13], BX
MOV AX, [0xe]
MOV BX, [0x12]
SWAP AX, BX
MOV [0xe], AX
MOV [0x12], BX
MOV AX, [0xf]
MOV BX, [0x11]
SWAP AX, BX
MOV [0xf], AX
MOV [0x11], BX
NOP

两段都需要Input,不知道出题人是怎么处理的,但是感觉应该是第一段没有被执行。不过两段的逆向都比较简单,我猜直接从第二段入手。检视内存的时候发现Target旁边有这种东西:结合Swap和交叉引用,猜测这才是真正的Target和真正的Check,出题人把这个函数藏在程序里面但是没有真正的调用。

image-20211020132410890.png

前六位 a[i] ^= a[i+1]

中间a[i] = (a[i+2] + 2*a[i+1] + 3*a[i+2])*a[0xc]

后面swap(a[d+i], a[0x13-i])

所以有了这样的脚本

Fake_Target = [
    0x46, 0x7A, 0x7B, 0x61, 0x4D, 0x7B, 0x61, 0x4D, 0x7C, 0x7D, 0x66, 0x4D, 0x74, 0x7E, 0x73, 0x75, 0x4D, 0x20, 0x21, 0x21
]
Target = [
    0x69, 0x45, 0x2A, 0x37, 0x09, 0x17, 0xC5, 0x0B, 0x5C, 0x72, 0x33, 0x76, 0x33, 0x21, 0x74, 0x31, 0x5F, 0x33, 0x73, 0x72
]
Flag = ""
for i in range(3) :
    Target[0xd+i], Target[0x13-i] = Target[0x13-i], Target[0xd+i]
for i in range(8, 5, -1) :
    Target[i] //= Target[0xc]
    Target[i] -= Target[i+2] + 2*Target[i+1]
    Target[i] //= 3
for i in range(6, 0, -1) :
    Target[i-1] ^= Target[i]
for i in Target :
    Flag += chr(i&0xFF)
print(Flag)

显然是不太对的,写的时候我就知道..但是懒得解方程所以还是先打上试试(

因为在MUL的时候可能出现溢出,对于程序来说这部分溢出可以直接忽略了,但是我们除回来的时候就难以忽略了,至少不能直接用除的。我先尝试一下爆破所有可以被0x33整除的

for i in range(8, 5, -1) :
    for j in range(0x300) :
        if (0x33 * j) & 0xFF == Target[i] :
            print("T[{}] Maybe Eql to {}".format(i, hex(0x33 * j)))
'''
T[8] Maybe Eql to 0xa5c
T[8] Maybe Eql to 0x3d5c
T[8] Maybe Eql to 0x705c
T[7] Maybe Eql to 0x280b
T[7] Maybe Eql to 0x5b0b
T[7] Maybe Eql to 0x8e0b
T[6] Maybe Eql to 0x7c5
T[6] Maybe Eql to 0x3ac5
T[6] Maybe Eql to 0x6dc5
'''

其实我们需要的只是被枚举了的 j 而已,在这个前提下,T[i] 的中间结果还必须要被3整除,很容易就能得到实际上的Target(处理的时候记得要从T8开始往前推,因为7、6的减数与后面的有关)

T[8] Maybe Eql to 0x705c
T[7] Maybe Eql to 0x5b0b
T[6] Maybe Eql to 0x6dc5

最后得到Flag{Y0u_hav3_r3v3rs3_1t!}

0

评论 (0)

取消