网鼎杯 signal 基础vm逆向及vm逆向分析思路
什么是VM(虚拟机保护)?
分析流程:
vm_start :虚拟机入口函数 ,初始化虚拟机
vm_dispatcher: 调度器,解释op_code,并选择相应的函数执行,当函数执行完后会返回这里,形成一个循环,直到执行完
vm_code:程序可执行代码形成的操作码
一般VM分析解题流程:
这里借用一位大佬的总结图片
来看看2020网鼎杯的signal
首先IDA分析
发现是一个vm逆向 其中vm opread_code 位于变量v4当中,首先写一个IDA python 脚本提取出opread_code
from idaapi import * start_addr=0x403040 list=[] for i in range(114): t=Dword(start_addr) list.append(t) start_addr+=4 print list print len(list)
整理后
op_code=[10,4,16,8, 3,5,1,4,32,8, 5,3,1,3,2,8,11,1,12,8,4, 4,1,5,3,8,3,33,1,11,8,11,1,4, 9,8,3,32,1,2,81,8,4,36,1,12,8,11, 1,5,2,8,2,37,1,2,54,8,4,65,1,2,32, 8,5,1,1,5,3,8,2,37,1,4,9,8,3,32,1,2, 65,8,12,1,7,34,7,63,7,52,7,50,7,114,7,51, 7,24,7,4294967207, 7,49,7,4294967281,7, 40,7,4294967172,7,4294967233, 7,30,7,122]
接下来分析 vm opread部分
典型的VM switch结构
分析发现:每一个分支里面都有v10 而且都有v10++的操作,我们可以将其理解为汇编中的eip(rip)指针或者索引
接着分析其他的case 可以依次发现每个case的作用(也就是做一个简单运算)
其中a1[]已经被我修改为input
进一步分析我们发现:v6 v7 v8 v9是数组的索引,v5为操作的返回值,进一步重命名变量,以有利于分析
其中 case7为必须满足的条件,只要我们的输入满足case7不跳出则能得到flag的值,
我们可以根据case7逆向算出v4[]的值,进而反推出input
这是我们再看看op_code
op_code=[10,4,16,8, 3,5,1,4,32,8, 5,3,1,3,2,8,11,1,12,8,4, 4,1,5,3,8,3,33,1,11,8,11,1,4, 9,8,3,32,1,2,81,8,4,36,1,12,8,11, 1,5,2,8,2,37,1,2,54,8,4,65,1,2,32, 8,5,1,1,5,3,8,2,37,1,4,9,8,3,32,1,2, 65,8,12,1,7,34,7,63,7,52,7,50,7,114,7,51, 7,24,7,4294967207, 7,49,7,4294967281,7, 40,7,4294967172,7,4294967233, 7,30,7,122]
发现在比较操作数7第一次出现的位置之后,都是一些无效指令(大于12),
所以,加密运算应该是在操作数7第一次出现之前完成。
有效的op_code应该是
a=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1]
下面开始写逆算法(加变减,乘法变除法)
op_code=[10,4,16,8, 3,5,1,4,32,8, 5,3,1,3,2,8,11,1,12,8,4, 4,1,5,3,8,3,33,1,11,8,11,1,4, 9,8,3,32,1,2,81,8,4,36,1,12,8,11, 1,5,2,8,2,37,1,2,54,8,4,65,1,2,32, 8,5,1,1,5,3,8,2,37,1,4,9,8,3,32,1,2, 65,8,12,1,7,34,7,63,7,52,7,50,7,114,7,51, 7,24,7,4294967207, 7,49,7,4294967281,7, 40,7,4294967172,7,4294967233, 7,30,7,122] for i in range(len(op_code)): if(op_code[i]==7): v4.append(op_code[i+1]) v4.reverse() op_code=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1] op_code.reverse() v9 = 0 tmp=0 v5=0 flag=[] for i in range(0,len(op_code)): if i ==len(op_code)-1: flag.append(tmp) if op_code[i]==1 and op_code[i-1]!=1: v5 = v4[v9] v9+=1 flag.append(tmp) if op_code[i]==2: if(op_code[i+1]!=3 and op_code[i+1]!=4 and op_code[i+1]!=5): tmp = v5 - op_code[i-1] #print(tmp,v5,a[i-1]) if op_code[i]==3: if(op_code[i+1]!=2 and op_code[i+1]!=4 and op_code[i+1]!=5): tmp = v5 + op_code[i-1] #LOBYTE是al有8位,参与运算的5、33、32是全值,所以LOBYTE可省略 if op_code[i]==4: if(op_code[i+1]!=3 and op_code[i+1]!=2 and op_code[i+1]!=5): tmp = v5^op_code[i-1] if op_code[i]==5: if(op_code[i+1]!=3 and op_code[i+1]!=4 and op_code[i+1]!=2): tmp = int(v5/op_code[i-1]) if op_code[i]==8: v5 = tmp if op_code[i]==11: tmp = v5 +1 if op_code[i]==12: tmp = v5 -1 flag.reverse() out='' for j in flag: out +=chr(j) print("flag{"+out+"}")
52上也有大佬用符号执行做的 我还不会 去学习一下
修改的idb exp 及源文件下载链接 https://files.cnblogs.com/files/nigacat/signal.rar