2022GKCTF-EzMachine


[TOC]

程序无壳,我们拖入IDA,发现 main 无法 F5;分析 main 存在花指令,我们去花后重新编译:函数和变量经过了重命名

可以看到程序首先对VM需要的一些变量进行了初始化,然后在循环取指令执行

我们看下 init_vm 函数,可以发现该函数对VM所用到的寄存器,stack,vip等等进行了初始化

我们看下dispatcher:根据byte_4448F0与dispatcher的位置关系可以知道,byte_4448F存储的就是函数的编号

接下来就是去分析各个函数的功能了:

例如 mov_regi_data:这里byte_4449A1和byte_4449A2与opcodes的位置关系很巧妙,它们两个就是对应的第一个操作数和第二个操作数

比如对于:1 3 3 5 1 2;如果opcodes[vip]=5,那么byte_4449A1[vip]=1,byte_4449A2[vip]=2

最后我们得出所有函数的功能:指令长度都是为3,第一个是操作码,第二,三个是操作数

然后就可以把 opcodes 对应的指令跑出来

opcodes=[
  0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, 
  0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03, 
  0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00, 
  0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14, 
  0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A, 
  0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00, 
  0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06, 
  0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00, 
  0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01, 
  0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01, 
  0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00, 
  0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 
  0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02, 
  0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01, 
  0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, 
  0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02, 
  0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05, 
  0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 
  0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02, 
  0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07, 
  0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00, 
  0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02, 
  0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02, 
  0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01, 
  0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D, 
  0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E, 
  0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 
  0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 
]

funcs = {
    0:'nop',
    1:'mov reg_i,data',
    2:'push data',
    3:'push reg_i',
    4:'pop reg_i',
    5:'print by checking reg3',
    6:'add reg_i,reg_j',
    7:'sub reg_i,reg_j',
    8:'mul reg_i,reg_j',
    9:'div 商放在reg0中,余数放在reg1中',
    10:'xor',
    11:'jmp vip = 3 * (第二个操作数 - 1)',
    12:'reg3 = reg_i - reg_j',
    13:'if reg3!=0:vip+=3 else:vip=3*(第一个操作数-1) 12,13其实就是 cmp,je',
    14:'if reg3!=0:vip=3*(第一个操作数-1) else:vip+=3  其实就是 jne',
    15:'jg',
    16:'jl',
    17:'gets(Buffer) 输入的长度保存在 reg0 中',
    18:'buf_memeset0 memset(&Buffer[byte_4449A1[vip]], 0, byte_4449A2[vip]);',
    19:'stack_to_reg_i by reg_j', 
    20:'reg_i = Buffer[reg_j]',
    0xFF:'exit'
}

index = 0
code_count = 0
for opcode in opcodes:
    if index % 3 == 0: #第一个是操作码
        print(f"[{code_count}]:", end='')
        print(str(funcs[opcode]) + ": ", end='')
        code_count += 1
    elif index % 3 == 1: #第一个操作数
        print(str(opcode) + ",", end='')
    else:				#第二个操作数
        print(opcode)
    index += 1

跑出结果如下:这里有些跳转有点问题,需要自己分析一下

[0]:mov reg_i,data: 3,3
[1]:print by checking reg3: 0,0
[2]:gets(Buffer) 输入的长度保存在 reg0 中: 0,0
[3]:mov reg_i,data: 1,17
[4]:reg3 = reg_i - reg_j: 0,1
[5]:if reg3!=0:vip+=3 else:vip=3*(第一个操作数-1) 12,13其实就是 cmp,je: 10,0
[6]:mov reg_i,data: 3,1
[7]:print by checking reg3: 0,0
[8]:exit: 0,0
[9]:mov reg_i,data: 2,0
[10]:mov reg_i,data: 0,17
[11]:reg3 = reg_i - reg_j: 0,2
[12]:if reg3!=0:vip+=3 else:vip=3*(第一个操作数-1) 12,13其实就是 cmp,je: 43,0
[13]:reg_i = Buffer[reg_j]: 0,2
[14]:mov reg_i,data: 1,97
[15]:reg3 = reg_i - reg_j: 0,1
[16]:jl: 26,0
[17]:mov reg_i,data: 1,122
[18]:reg3 = reg_i - reg_j: 0,1
[19]:jg: 26,0
[20]:mov reg_i,data: 1,71
[21]:xor: 0,1
[22]:mov reg_i,data: 1,1
[23]:add reg_i,reg_j: 0,1
[24]:jmp vip = 3 * (第二个操作数 - 1): 36,0
[25]:mov reg_i,data: 1,65
[26]:reg3 = reg_i - reg_j: 0,1
[27]:jl: 36,0
[28]:mov reg_i,data: 1,90
[29]:reg3 = reg_i - reg_j: 0,1
[30]:jg: 36,0
[31]:mov reg_i,data: 1,75
[32]:xor: 0,1
[33]:mov reg_i,data: 1,1
[34]:sub reg_i,reg_j: 0,1
[35]:mov reg_i,data: 1,16
[36]:div 商放在reg0中,余数放在reg1中: 0,1
[37]:push reg_i: 1,0
[38]:push reg_i: 0,0
[39]:mov reg_i,data: 1,1
[40]:add reg_i,reg_j: 2,1
[41]:jmp vip = 3 * (第二个操作数 - 1): 11,0
[42]:push data: 7,0
[43]:push data: 13,0
[44]:push data: 0,0
[45]:push data: 5,0
[46]:push data: 1,0
[47]:push data: 12,0
[48]:push data: 1,0
[49]:push data: 0,0
[50]:push data: 0,0
[51]:push data: 13,0
[52]:push data: 5,0
[53]:push data: 15,0
[54]:push data: 0,0
[55]:push data: 9,0
[56]:push data: 5,0
[57]:push data: 15,0
[58]:push data: 3,0
[59]:push data: 0,0
[60]:push data: 2,0
[61]:push data: 5,0
[62]:push data: 3,0
[63]:push data: 3,0
[64]:push data: 1,0
[65]:push data: 7,0
[66]:push data: 7,0
[67]:push data: 11,0
[68]:push data: 2,0
[69]:push data: 1,0
[70]:push data: 2,0
[71]:push data: 7,0
[72]:push data: 2,0
[73]:push data: 12,0
[74]:push data: 2,0
[75]:push data: 2,0
[76]:mov reg_i,data: 2,1
[77]:stack_to_reg_i by reg_j: 1,2
[78]:pop reg_i: 0,0
[79]:reg3 = reg_i - reg_j: 0,1
[80]:if reg3!=0:vip=3*(第一个操作数-1) else:vip+=3  其实就是 jne: 91,0
[81]:mov reg_i,data: 1,34
[82]:reg3 = reg_i - reg_j: 2,1
[83]:if reg3!=0:vip+=3 else:vip=3*(第一个操作数-1) 12,13其实就是 cmp,je: 89,0
[84]:mov reg_i,data: 1,1
[85]:add reg_i,reg_j: 2,1
[86]:jmp vip = 3 * (第二个操作数 - 1): 78,0
[87]:mov reg_i,data: 3,0
[88]:print by checking reg3: 0,0
[89]:exit: 0,0
[90]:mov reg_i,data: 3,1
[91]:print by checking reg3: 0,0
[92]:exit: 0,0
[93]:nop:

我们对上面的指令进行分析,然后写出伪汇编

[0]  mov reg3, 3
[1]  print("plz input:")
[2]  gets(Buffer)
        mov reg0, strlen(Buffer)
[3]  mov reg1, 17
[4]  cmp reg0, reg1
[5]  je [9]
[6]  mov reg3, 1
[7]  print("wrong")
[8]  exit
[9]  mov reg2, 0
[10] mov reg0, 17
[11] cmp reg0, reg2
[12] je [42]
[13] reg0 = Buffer[reg2]
[14] mov reg1, 97='a'
[15] cmp reg0, reg1
[16] jl [25]
[17] mov reg1, 122='z'
[18] cmp reg0, reg1
[19] jg [25]
[20] mov reg1, 71
[21] xor reg0, reg1
[22] mov reg1, 1
[23] add reg0, reg1
[24] jmp [35]
[25] mov reg1, 65='A'
[26] cmp reg0, reg1
[27] jl [35]
[28] mov reg1, 90='Z'
[29] cmp reg0, reg1
[30] jg [35]
[31] mov reg1, 75
[32] xor reg0, reg1
[33] mov reg1, 1 
[34] sub reg0, reg1
[35] mov reg1, 16
[36] div reg0, reg1
        reg0 = reg0/reg1, reg1 = reg0%reg1
[37] push reg1
[38] push reg0
[39] mov reg1, 1
[40] add reg2, reg1
[41] jmp [10]
[42] push 7
[43] push 13
......压栈
[75] push 2
[76] mov reg2, 1
[77] reg1 = stack[reg2]
[78] pop reg0
[79] cmp reg0, reg1
[80] jne [90]
[81] mov reg1, 34
[82] cmp reg2, reg1
[83] je [87]
[84] mov reg1, 1
[85] add reg2, reg1
[86] jmp [77]
[87] mov reg3, 0
[88] print("right")
[89] exit
[90] mov reg3, 1
[91] print("wrong")
[92] exit
[93] nop

分析可知这段指令的意思是:
用户输入长度为17的字符串str
然后对用户的输入进行处理,对于str的每一个字符c
如果c是小写字母则c = (c ^ 71) + 1
如果c是大写字母则c = (c ^ 75) - 1
最后 push c%16; push c/16
处理完后,在跟栈中的字符比较
"""

最后写出逆函数:注意栈是先进后出,所有我们得把 data 反一下

print("")
data = [7, 13, 0, 5, 1, 12, 1, 0, 0, 13, 5, 15, 0, 9, 5, 15, 3, 0, 2, 5, 3, 3, 1, 7, 7, 11, 2, 1, 2, 7, 2, 12, 2, 2]
data = data[::-1]
flag = ""
for i in range(0, 34, 2):
    tmp = data[i] + data[i+1] * 16
    x1 = (tmp - 1) ^ 71
    x2 = (tmp + 1) ^ 75
    if x1 >= ord('a') and x1 <= ord('z'):
        flag += chr(x1)
    elif x2 >= ord('A') and x2 <= ord('Z'):
        flag += chr(x2)
    else:
        flag += chr(tmp)
print(flag)
#flag{Such_A_EZVM}

文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录