0x00 前言
纯VM,朝花夕拾
0x01 解
无壳,直接进main函数
这么清晰?直接解了看看
我就知道.jpg
main下面有一大堆vm
可以发现是非常标准的VM
经过重设变量类型(a1
应为_DWORD *
)和一些分析,可以得出各变量的含义:
然后在vm_init里面可以找到opcode
直接写解释器看看各条命令,这里的opcode
是以4bytes
为单位,其中第一个byte
为操作码,后面的是立即数之类的东西
我直接一个拆分,一个字节一个字节读,所以我的eip
的加加要加4
,某些大的立即数也要从后往前读(小端),同时由于第一个值是eip
,我也是自己搞一个来,所以有些地址操作要变,当然里面所有的立即数之类的,特别是放进去的地址后面写的时候也要做变换(后面会有注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import struct def vm(opcode): eip=4 stack = [] while True: if opcode[eip] == 1: stack.append((opcode[eip+2]<<8)+opcode[eip+1]) ins = f'push {hex((opcode[eip+2]<<8)+opcode[eip+1])}' eip+=4 elif opcode[eip] == 2: tmp = stack.pop() ins = f'pop {tmp}' eip+=4 elif opcode[eip] == 3: tmp = stack.pop()+stack.pop() stack.append(tmp) ins = f'add, result = {tmp}' eip+=4 elif opcode[eip] == 4: tmp = stack.pop() | stack.pop() stack.append(tmp) ins = f'and, result = {tmp}' eip+=4 elif opcode[eip] == 5: tmp = stack.pop() stack.append(tmp << opcode[eip+1]) ins = f'lsh, result = {tmp}' eip+=4 elif opcode[eip] == 6: tmp = stack.pop() stack.append(tmp >> opcode[eip+1]) ins = f'rsh, result = {tmp}' eip+=4 elif opcode[eip] == 7: ins = 'read' eip+=4 elif opcode[eip] == 8: tmp = stack.pop() ins = f'print {tmp}' eip+=4 elif opcode[eip] == 9: ins = f'jmp' eip+=4 elif opcode[eip] == 10: ins = f'be' eip +=4 elif opcode[eip] == 11: ins = f'bl' eip+=4 elif opcode[eip] == 12: ins = 'return' print(ins) return print(ins)
with open('./s.txt','rb') as f: o = f.read() opcode = struct.unpack('B'*1568,o)
|
读出来发现不太对,因为中间有be
、bl
这些条件跳转,也有jmp
这种普通跳转
涉及到跳转就比较麻烦了,在跳转之前都要进行一次分析看看跳转实现的条件以及最终的结果
之后就先分析一下跳转条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| _DWORD *__fastcall vm_be(_DWORD *a1) { _DWORD *result;
if ( a1[501] <= 2u ) { puts("be instruction cannot be executed with less than three stack elements!"); fflush(_bss_start); _exit(-1); } result = a1; if ( a1[a1[501] - 2 + 502] == a1[a1[501] - 3 + 502] ) *a1 = a1[a1[501] - 1 + 502]; else ++*a1; return result; }
|
这边含义就是stack[-2]
需要等于stack[-3]
才能使eip
跳转到stack[-1]
,否则eip
直接加加
而加加之后会进到一个jmp
,注意跳转的时候要对应字节数,所以这些跳转我这边是这样写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| elif opcode[eip] == 9: ins = f'jmp' eip = (stack[-1]+1)*4 elif opcode[eip] == 10: ins = f'be' if stack[-2] == stack[-3]: eip = (stack[-1]+1)*4 else: eip+=4 elif opcode[eip] == 11: ins = f'bl' if stack[-2] >= stack[-3]: eip+=4 else: eip = (stack[-1]+1)*4
|
所以最后的解释器长这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import struct def vm(opcode,entered): eip=4 stack = [] while True: if opcode[eip] == 1: stack.append((opcode[eip+2]<<8)+opcode[eip+1]) ins = f'push {hex((opcode[eip+2]<<8)+opcode[eip+1])}' eip+=4 elif opcode[eip] == 2: tmp = stack.pop() ins = f'pop {tmp}' eip+=4 elif opcode[eip] == 3: tmp = stack.pop()+stack.pop() stack.append(tmp) ins = f'add, result = {tmp}' eip+=4 elif opcode[eip] == 4: tmp = stack.pop() | stack.pop() stack.append(tmp) ins = f'and, result = {tmp}' eip+=4 elif opcode[eip] == 5: tmp = stack.pop() stack.append(tmp << opcode[eip+1]) ins = f'lsh, result = {tmp}' eip+=4 elif opcode[eip] == 6: tmp = stack.pop() stack.append(tmp >> opcode[eip+1]) ins = f'rsh, result = {tmp}' eip+=4 elif opcode[eip] == 7: ins = 'read' for i in range(len(entered)): stack.append(ord(entered[i])) eip+=4 elif opcode[eip] == 8: tmp = stack.pop() ins = f'print {chr(tmp)}' eip+=4 elif opcode[eip] == 9: ins = f'jmp' eip = (stack[-1]+1)*4 elif opcode[eip] == 10: ins = f'be' if stack[-2] == stack[-3]: eip = (stack[-1]+1)*4 else: eip+=4 elif opcode[eip] == 11: ins = f'bl' if stack[-2] >= stack[-3]: eip+=4 else: eip = (stack[-1]+1)*4 elif opcode[eip] == 12: ins = 'return' print(ins) return print(ins)
with open('./s.txt','rb') as f: o = f.read() opcode = struct.unpack('B'*1568,o) vm(opcode,r'DASCTF{fake_flag_!!}')
|
运行可以知道整个vm
就是read
某些值然后丢进去做运算,成功即be
和bl
跳转(实际上通过查找opcode
会发现bl
压根没用到,实锤抄代码了),否则进jmp
然后输出错误信息
一个个分析就太麻烦了,这里学个z3结合hook的方法,在read
操作码的地方这么写
1 2 3 4 5 6
| elif opcode[eip] == 7: ins = 'read' s=Solver() x = BitVec('x',16) stack.append(x) eip+=4
|
然后在be判断的地方这样写
1 2 3 4 5 6 7 8 9 10 11
| elif opcode[eip] == 10: ins = f'be' s.add(stack[-2] == stack[-3]) s.add(x<128) s.add(x>32) if(s.check() == sat): m = s.model() print('[+]',end='') eip = (stack[-1]+1)*4 out += chr(m[x].as_long()) print(out)
|
之后运行起来就可以在后面得到flag了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| import struct from z3 import * def vm(opcode): eip=4 stack = [] out = '' while True: if opcode[eip] == 1: stack.append(((opcode[eip+2]<<8)+opcode[eip+1])) ins = f'push {hex(((opcode[eip+2]<<8)|opcode[eip+1]))}' eip+=4 elif opcode[eip] == 2: tmp = stack.pop() ins = f'pop {tmp}' eip+=4 elif opcode[eip] == 3: tmp = stack.pop()+stack.pop() stack.append(tmp) ins = f'add, result = {tmp}' eip+=4 elif opcode[eip] == 4: t1 = stack.pop() tmp = (t1 | stack.pop()) stack.append(tmp) ins = f'and, result = {tmp}' eip+=4 elif opcode[eip] == 5: tmp = stack.pop() stack.append(tmp << opcode[eip+1]) ins = f'lsh, result = {tmp}' eip+=4 elif opcode[eip] == 6: tmp = stack.pop() stack.append(tmp >> opcode[eip+1]) ins = f'rsh, result = {tmp}' eip+=4 elif opcode[eip] == 7: ins = 'read' s=Solver() x = BitVec('x',16) stack.append(x) eip+=4 elif opcode[eip] == 8: tmp = stack.pop() ins = f'print {chr(tmp)}' eip+=4 elif opcode[eip] == 9: ins = f'jmp' eip = (stack[-1]+1)*4 elif opcode[eip] == 10: ins = f'be' s.add(stack[-2] == stack[-3]) s.add(x<128) s.add(x>32) if(s.check() == sat): m = s.model() print('[+]',end='') eip = (stack[-1]+1)*4 out += chr(m[x].as_long()) print(out) else: print(hex(stack[-2])) print(hex(stack[-3])) return if stack[-2] == stack[-3]: eip = (stack[-1]+1)*4 elif opcode[eip] == 11: ins = f'bl' if stack[-2] >= stack[-3]: eip+=4 else: eip = (stack[-1]+1)*4 elif opcode[eip] == 12: ins = 'return' print(ins) return print(ins)
with open('./s.txt','rb') as f: o = f.read() opcode = struct.unpack('B'*1568,o) vm(opcode)
|