这是一个win32的VM逆向。
这是第一次做VM逆向,简单的说一下思路:
1.找出主要的虚拟机逻辑片段。
2.找出指令集、堆栈、寄存器。
3.找出要执行的虚拟指令。
4.具体分析。
这次的主函数加了花指令混淆,去除掉以后创建函数然后f5:
同时找到了字符串和虚拟寄存器的地址:
大概的流程摸清楚了,开始分析指令:
指令分析: 0 : 'nop', 1 : 'mov i(寄存器编号), j', 2 : 'push x', 3 : 'push i(寄存器编号)', 4 : 'pop i(寄存器编号)', 5 : 'print', 6 : 'add i(寄存器编号),j(寄存器编号?)', 7 : 'sub', 8 : 'mul', 9 : 'div', 10 : 'xor', 11 : 'jmp', 12 : 'subcmp', 13 : 'je', 14 : 'jne', 15 : 'jg', 16 : 'jl', 17 : 'input', 18 : 'clr?', 19 : 'loadstack', 20 : 'loadstring', 0xff : 'quit',
此处后半部分参考了这篇文章的分析过程(https://blog.csdn.net/weixin_43876357/article/details/108488762)
下面把command给提出来:
command: 01 03 03 05 00 00 11 00 00 01 01 11 0C 00 01 0D 0A 00 01 03 01 05 00 00 FF 00 00 01 02 00 01 00 11 0C 00 02 0D 2B 00 14 00 02 01 01 61 0C 00 01 10 1A 00 01 01 7A 0C 00 01 0F 1A 00 01 01 47 0A 00 01 01 01 01 06 00 01 0B 24 00 01 01 41 0C 00 01 10 24 00 01 01 5A 0C 00 01 0F 24 00 01 01 4B 0A 00 01 01 01 01 07 00 01 01 01 10 09 00 01 03 01 00 03 00 00 01 01 01 06 02 01 0B 0B 00 02 07 00 02 0D 00 02 00 00 02 05 00 02 01 00 02 0C 00 02 01 00 02 00 00 02 00 00 02 0D 00 02 05 00 02 0F 00 02 00 00 02 09 00 02 05 00 02 0F 00 02 03 00 02 00 00 02 02 00 02 05 00 02 03 00 02 03 00 02 01 00 02 07 00 02 07 00 02 0B 00 02 02 00 02 01 00 02 02 00 02 07 00 02 02 00 02 0C 00 02 02 00 02 02 00 01 02 01 13 01 02 04 00 00 0C 00 01 0E 5B 00 01 01 22 0C 02 01 0D 59 00 01 01 01 06 02 01 0B 4E 00 01 03 00 05 00 00 FF 00 00 01 03 01 05 00 00 FF 00 00 00
然后写个脚本来还原伪汇编代码:
1 cmd = [0x01,0x03,0x03,0x05,0x00,0x00,0x11, 2 0x00,0x00,0x01,0x01,0x11,0x0C,0x00,0x01,0x0D, 3 0x0A,0x00,0x01,0x03,0x01,0x05,0x00,0x00,0xFF, 4 0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x11,0x0C, 5 0x00,0x02,0x0D,0x2B,0x00,0x14,0x00,0x02,0x01, 6 0x01,0x61,0x0C,0x00,0x01,0x10,0x1A,0x00,0x01, 7 0x01,0x7A,0x0C,0x00,0x01,0x0F,0x1A,0x00,0x01, 8 0x01,0x47,0x0A,0x00,0x01,0x01,0x01,0x01,0x06, 9 0x00,0x01,0x0B,0x24,0x00,0x01,0x01,0x41,0x0C, 10 0x00,0x01,0x10,0x24,0x00,0x01,0x01,0x5A,0x0C, 11 0x00,0x01,0x0F,0x24,0x00,0x01,0x01,0x4B,0x0A, 12 0x00,0x01,0x01,0x01,0x01,0x07,0x00,0x01,0x01, 13 0x01,0x10,0x09,0x00,0x01,0x03,0x01,0x00,0x03, 14 0x00,0x00,0x01,0x01,0x01,0x06,0x02,0x01,0x0B, 15 0x0B,0x00,0x02,0x07,0x00,0x02,0x0D,0x00,0x02, 16 0x00,0x00,0x02,0x05,0x00,0x02,0x01,0x00,0x02, 17 0x0C,0x00,0x02,0x01,0x00,0x02,0x00,0x00,0x02, 18 0x00,0x00,0x02,0x0D,0x00,0x02,0x05,0x00,0x02, 19 0x0F,0x00,0x02,0x00,0x00,0x02,0x09,0x00,0x02, 20 0x05,0x00,0x02,0x0F,0x00,0x02,0x03,0x00,0x02, 21 0x00,0x00,0x02,0x02,0x00,0x02,0x05,0x00,0x02, 22 0x03,0x00,0x02,0x03,0x00,0x02,0x01,0x00,0x02, 23 0x07,0x00,0x02,0x07,0x00,0x02,0x0B,0x00,0x02, 24 0x02,0x00,0x02,0x01,0x00,0x02,0x02,0x00,0x02, 25 0x07,0x00,0x02,0x02,0x00,0x02,0x0C,0x00,0x02, 26 0x02,0x00,0x02,0x02,0x00,0x01,0x02,0x01,0x13, 27 0x01,0x02,0x04,0x00,0x00,0x0C,0x00,0x01,0x0E, 28 0x5B,0x00,0x01,0x01,0x22,0x0C,0x02,0x01,0x0D, 29 0x59,0x00,0x01,0x01,0x01,0x06,0x02,0x01,0x0B, 30 0x4E,0x00,0x01,0x03,0x00,0x05,0x00,0x00,0xFF, 31 0x00,0x00,0x01,0x03,0x01,0x05,0x00,0x00,0xFF, 32 0x00,0x00,0x00] 33 34 opcode = {0 : 'nop', 35 1 : 'mov i(寄存器编号), j', 36 2 : 'push x', 37 3 : 'push i(寄存器编号)', 38 4 : 'pop i(寄存器编号)', 39 5 : 'print', 40 6 : 'add i(寄存器编号),j(寄存器编号?)', 41 7 : 'sub', 42 8 : 'mul', 43 9 : 'div', 44 10 : 'xor', 45 11 : 'jmp', 46 12 : 'subcmp', 47 13 : 'je', 48 14 : 'jne', 49 15 : 'jg', 50 16 : 'jl', 51 17 : 'input', 52 18 : 'clr?', 53 19 : 'loadstack', 54 20 : 'loadstring', 55 0xff : 'quit',} 56 57 i = 0 #3个byte为一条指令 58 x = 0 59 for ip in cmd: 60 if x % 3 == 0: 61 i += 1 62 print(str(i) + ': ', end='') # 打印行号 63 print(opcode[ip] + ' ', end='') 64 elif x % 3 == 1: 65 print(str(ip)+',', end='') 66 else: 67 print(str(ip)) 68 x += 1
运行结果(因为我的标注所以有点难看、将就一下):
1: mov i(寄存器编号), j 3,3 2: print 0,0 3: input 0,0 4: mov i(寄存器编号), j 1,17 5: subcmp 0,1 6: je 10,0 7: mov i(寄存器编号), j 3,1 8: print 0,0 9: quit 0,0 10: mov i(寄存器编号), j 2,0 11: mov i(寄存器编号), j 0,17 12: subcmp 0,2 13: je 43,0 14: loadstring 0,2 15: mov i(寄存器编号), j 1,97 16: subcmp 0,1 17: jl 26,0 18: mov i(寄存器编号), j 1,122 19: subcmp 0,1 20: jg 26,0 21: mov i(寄存器编号), j 1,71 22: xor 0,1 23: mov i(寄存器编号), j 1,1 24: add i(寄存器编号),j(寄存器编号?) 0,1 25: jmp 36,0 26: mov i(寄存器编号), j 1,65 27: subcmp 0,1 28: jl 36,0 29: mov i(寄存器编号), j 1,90 30: subcmp 0,1 31: jg 36,0 32: mov i(寄存器编号), j 1,75 33: xor 0,1 34: mov i(寄存器编号), j 1,1 35: sub 0,1 36: mov i(寄存器编号), j 1,16 37: div 0,1 38: push i(寄存器编号) 1,0 39: push i(寄存器编号) 0,0 40: mov i(寄存器编号), j 1,1 41: add i(寄存器编号),j(寄存器编号?) 2,1 42: jmp 11,0 43: push x 7,0 44: push x 13,0 45: push x 0,0 46: push x 5,0 47: push x 1,0 48: push x 12,0 49: push x 1,0 50: push x 0,0 51: push x 0,0 52: push x 13,0 53: push x 5,0 54: push x 15,0 55: push x 0,0 56: push x 9,0 57: push x 5,0 58: push x 15,0 59: push x 3,0 60: push x 0,0 61: push x 2,0 62: push x 5,0 63: push x 3,0 64: push x 3,0 65: push x 1,0 66: push x 7,0 67: push x 7,0 68: push x 11,0 69: push x 2,0 70: push x 1,0 71: push x 2,0 72: push x 7,0 73: push x 2,0 74: push x 12,0 75: push x 2,0 76: push x 2,0 77: mov i(寄存器编号), j 2,1 78: loadstack 1,2 79: pop i(寄存器编号) 0,0 80: subcmp 0,1 81: jne 91,0 82: mov i(寄存器编号), j 1,34 83: subcmp 2,1 84: je 89,0 85: mov i(寄存器编号), j 1,1 86: add i(寄存器编号),j(寄存器编号?) 2,1 87: jmp 78,0 88: mov i(寄存器编号), j 3,0 89: print 0,0 90: quit 0,0 91: mov i(寄存器编号), j 3,1 92: print 0,0 93: quit 0,0 94: nop
整体逻辑如下:
1.先判断是不是17位
2.大小写字母分别进行操作并压栈
3.将结果压栈
4.将结果pop出来比较
具体的分析过程还是有参考上面的那篇文章。
然后写一个脚本来还原flag:
1 ans = [7, 2 13, 3 0, 4 5, 5 1, 6 12, 7 1, 8 0, 9 0, 10 13, 11 5, 12 15, 13 0, 14 9, 15 5, 16 15, 17 3, 18 0, 19 2, 20 5, 21 3, 22 3, 23 1, 24 7, 25 7, 26 11, 27 2, 28 1, 29 2, 30 7, 31 2, 32 12, 33 2, 34 2,] 35 ans.reverse() 36 flag = '' 37 for i in range(0, len(ans), 2): 38 x = ans[i] + ans[i+1]*16 39 tmp = (x-1) ^ 71 40 if tmp >= ord('a') and tmp <= ord('z'): 41 flag += chr(tmp) 42 continue 43 tmp = (x+1) ^ 75 44 if tmp >= ord('A') and tmp <= ord('Z'): 45 flag += chr(tmp) 46 continue 47 flag += chr(x) 48 49 print(flag)
跑一下就出来了。
总的来说感觉这种题不是很复杂,但是需要足够细致耐心。