1625-5 王子昂 总结《2017年11月9日》 【连续第405天总结】
A. 08067CTF-re400
B.
这次的程序是VMP保护的……正好从来没接触过,练习一发0v0
首先查壳,无反应
看来VMP只保护了核心部分的代码啊
拖入IDA反编译:
耿直的超乎寻常的流程……
要求输入为16个字符
将输入转成int数组后通过sub_401120进行check
然而这个call里面就全是乱码了,看来这就是关键的VMP啦
首先去下来VMP分析插件(看雪和吾爱都有)
拷到OD目录下以后重启即可
右键关键call,选择分析虚拟机,然后选择分析虚拟程序
选择插件-VMP分析插件-打开调试窗口
就可以看到虚拟机指令窗口啦
这里面每一行都是原汇编分析出的对应虚拟机指令
虽然指令还是虚拟机插件作者所自创的指令,但至少比起原来不明所以的汇编清晰多啦
POP、Add等指令都是熟悉的指令,如果还有不理解的指令可以去看雪/吾爱/压缩包内的说明文档找~(我就没想通SubFlag啥意思qaq
现在还比较迷茫,我们再进行两步就能够得到最精简的代码了:
右击-查看-表达式
右击-查看-最终操作
既去除了无关和复杂操作,又给出了清晰易懂的表达式,让我们仰慕一下插件作者OTZ
到这里基本上就可以等价于汇编了,ReadMemDs就是从输入中读出值,右边表达式中的vx是指左边Pop指令对应的vRx寄存器,mx则是临时变量(我也不知道在哪儿~只能从值对应猜啦)
可以看到,代码依次读入每个字符,进行了一番左移操作后累加了起来
其实稍微计算一下就能看出来就是sum = sum*10 + input[i]
用这么神奇的表示方法大概是因为虚拟机中没有乘法指令,于是就优化成各种左移和加法了吧。
不过汇编中的乘法指令落到CPU底层上的实现应该也是各种左移和加法吧-0-毕竟都是二进制数嘛
16个字符共进行了4次如上循环,切分成4个四位数
然后对它们进行运算校验
这里貌似也没什么好方法,只能慢慢计算了
注意一共有4次校验,最后一次没有通过vJmp来跳转,而是直接使用了Flag标志位:
这个SubFlag就是校验v37-0xBB80是否为0的,通过最后的标志位来表达在外部的返回值中,因此也要求通过
最后总结出4个方程:
这个计算起来有点麻烦呀……
想起来之前刚好搞了Z3约束求解器下来,这东西解方程正好用嘛
安装的时候又费了好大劲,pip安装下来的貌似不太对劲,还是得乖乖去github上拖
弄下来以后又报初始化错误,然后把libz3.dll放进去,再核对32位和64位版本后才终于能用=A=
python3:
import z3
z3.init(r"E:\Users\hasee\AppData\Local\Programs\Python\Python35-32\Lib\site-packages\libz3.dll")
solver = z3.Solver()
v11 = z3.Int('a')
v16 = z3.Int('b')
v19 = z3.Int('c')
v23 = z3.Int('d')
# 限制解的范围
solver.add(v11 < 9999, v16 < 9999, v19 < 9999, v23 < 9999)
solver.add(v11 > 0, v16 > 0, v19 > 0, v23 > 0)
# 添加方程
solver.add(v11 * 5 + v19 * 3 + v23 * 2 + v16 * 4 == 0x130D9,
v11 * 4 + v16 * 2 + v19 * 6 + v23 * 3 == 0x10aef,
v11 * 3 + v16 + v19 * 7 + v23 * 5 == 0x11a6b,
v11 * 2 + v16 * 3 + v19 * 5 + v23 == 0xbb80
)
# 求解
if (solver.check() == z3.sat):
print(solver.model())
else:
print("unsat")
注意Z3默认情况下是只输出一个解的,因此如果方程约束条件不对的话将只能得到错误解╮(╯_╰)╭
输出解后按照顺序将其拼合成字符串即可
总的来说这个VMP还是挺简单的,作为学习工具来讲非常好~
萌新首次接触VMP就很快搞定啦~
再次感谢SWPU和08067的师傅们~
C. 明日计划
刷pwn去=A=