本题来自于picoCTF2018的一个逆向题
题目描述
Can you crack the key to decrypt map2 for us? The key to map1 is 11443513758266689915.
Hints
Have you heard of z3?
分析
提供了三个文件,和一个key
,这个key
应该是map1
的key
。
查看了一下map1.txt
的内容,并没有看出来是个啥。
再看看decrypt.py
内容,首先看一下主题逻辑,分析过程都在备注里面了。
if __name__ == '__main__':
# 输入应该是python decrypt.py key map.txt的形式,有三个参数
if len(sys.argv) < 3:
print 'Usage: ' + sys.argv[0] + ' <key> <map.txt>'
print 'Example: Try Running ' + sys.argv[0] + ' 11443513758266689915 map1.txt'
exit(1)
# 打开map.txt,cipher用来与key一起进行sha512解密用的
with open(sys.argv[2], 'r') as f:
cipher, chalbox = eval(f.read())
# map1.txt中chalbox[0]=64, key应该小于2**64
key = int(sys.argv[1]) % (1 << chalbox[0])
print 'Attempting to decrypt ' + sys.argv[2] + '...'
# 验证key,主要需要分析的函数
if verify(key, chalbox):
print 'Congrats the flag for ' + sys.argv[2] + ' is:', dec(key, cipher)
else:
print 'Wrong key for ' + sys.argv[2] + '.'
然后分析一下verify
函数,以map1
为例分析一下,因为map1
已经给出了key
def verify(x, chalbox):
# length=64,check=(570, True)
length, gates, check = chalbox
# 将b转换为二进制,从低位到高位组成一个数组,大小为64
b = [(x >> i) & 1 for i in range(length)]
for name, args in gates:
# ('true', []) 为入口
if name == 'true':
b.append(1)
else:
# args由四个元素组成,name有两种,一个是or一个是xor
u1 = b[args[0][0]] ^ args[0][1]
u2 = b[args[1][0]] ^ args[1][1]
if name == 'or':
b.append(u1 | u2)
elif name == 'xor':
b.append(u1 ^ u2)
# 返回b[570] ^ True,一开始b是64位,最后b应该是570+1位
return b[check[0]] ^ check[1]
题目的大致逻辑:
- 将
key
按照64位二进制展开(低位对应数组的前面),得到一个64位大小的由0
和1
组成的数组b
- 根据每一个
gates
中的tuple计算一个b元素,一直到b[570]
- 最后,返回
b[570] ^ True
的结果
b[0]
到b[63]
未知,后面的运算相当于解64元方程的算式,所以这只是个变种的z3
题型。
Z3
是一个微软出品的开源约束求解器,能够解决很多种情况下的给定部分约束条件寻求一组满足条件的解的问题,功能强大且易于使用,常用于解决CTF当中的密码题或逆向题,Z3 在工业应用中实际上常见于软件验证、程序分析等。
我们用z3
来求解,按照decrypt
重新写一个脚本
#!/usr/bin/python2
# -*- coding: utf-8 -*-
import sys
from z3 import *
if __name__ == "__main__":
# 需要将key值去掉,后面会定义求解
if len(sys.argv) != 2:
print('Usage: ' + sys.argv[0] + ' <map.txt>')
exit(1)
with open(sys.argv[1], 'r') as f:
cipher, chalbox = eval(f.read())
s = Solver()
v = []
length, gates, check = chalbox
# 由于key会被转换为由0和1组成的数组,所以将key定义成bool类型的数组
for i in range(length):
e = Bool('v' + str(i))
v.append(e)
for name, args in gates:
if name == 'true':
v.append(True)
else:
# 将异或操作改写成z3里面的异或
u1 = Xor(v[args[0][0]], args[0][1])
u2 = Xor(v[args[1][0]], args[1][1])
if name == 'or':
v.append(Or(u1, u2))
elif name == 'xor':
v.append(Xor(u1, u2))
s.add(Xor(v[check[0]], check[1]) == True)
flag = []
if s.check() == sat:
m = s.model()
for i in range(length):
flag.append('1' if m[v[i]] else '0')
flag = ''.join(flag[::-1])
print(flag)
print(int(flag, 2))
我们用map1.txt
去验证一下
得到的结果正是题目给出的key
,然后去求解map2
的key
。
用他给的decrypt.py
和自己解出来的key
去获得flag
。