从零开始做题:pyHAHA
1.题目信息
https://adworld.xctf.org.cn/challenges/list
解题思路
分析pyc文件
分析压缩包中的MP3,分析MP3解隐写得到的字符
01字符串画图
pyc反编译,py代码逆向分析
解题步骤
1.分析pyc
1.1 拿到一个pyc,通过tool.lu/pyc反编译,结果显示反编译不成功,猜测是pyc隐写
补充知识点:
pyc文件:python是一种解释器语言,为了提高运行效率,将py源码文件编译为pyc可执行文件
pyc反编译:通过工具将pyc文件反编译成py源码
1.2 用winhex打开该文件,发现2galf,1galf关键字,看出是倒序了而且压缩了
1.3 将文件进行正序并发现少文件头03F30D0A
补上
f = open('PyHaHa2.pyc','wb')
with open('PyHaHa.pyc','rb') as g:
f.write(g.read()[::-1])
f.close()
1.4 另存为PyHaHa2-modify -zip,用7-zip解压缩得到Dream It Possible
1.5 用工具DeEgger Embedder可以提取Dream It Possible - extracted,得到base32编码的内容
1.6 通过tool.lu/pyc反编译PyHaHa2-modify.pyc得到
#!/usr/bin/env python
# visit http://tool.lu/pyc/ for more information
from os import urandom
def generate(m, k):
result = 0
for i in bin(m ^ k)[2:]:
result = result << 1
if int(i):
result = result ^ m ^ k
if result >> 256:
result = result ^ P
continue
return result
def encrypt(seed):
key = int(urandom(32).encode('hex'), 16)
while True:
yield key
key = generate(key, seed) + 0x3653C01D55L
def convert(string):
return int(string.encode('hex'), 16)
P = 0x10000000000000000000000000000000000000000000000000000000000000425L
flag1 = 'ThIs_Fl4g_Is_Ri9ht'
flag2 = 'Hey_Fl4g_Is_Not_HeRe'
key = int(urandom(32).encode('hex'), 16)
data = open('data.txt', 'r').read()
result = encrypt(key)
encrypt1 = bin(int(data, 2) ^ eval('0x' + hex(result.next())[2:-1] * 22))[2:]
encrypt2 = hex(convert(flag1) ^ result.next())[2:-1]
encrypt3 = hex(convert(flag2) ^ result.next())[2:-1]
print 'flag1:', encrypt2
print 'flag2:', encrypt3
f = open('encrypt.txt', 'w')
f.write(encrypt1)
f.close()
知识点:
一次一密加密法:不可破译
encrypt实现的是一个256bit随机数生成器的功能
generate实现的是在有限域GF(2256)下的平方运算:new_key=(old_key+seed)2
flag1和flag2的密文在前面的zip注释信息已给出
脚本对三段明文使用了同个Seed做了加密,其中后两段明文和密文还有第一段的密文(在那大段的base32里)已知
考虑OTP加密
先由后两段明文和密文算出 key2 和 key3,再在 GF(2256)下进行开方即可得到 seed,key3 = (key2+seed)2
再由第一段密文(即 base32 隐藏的数据)key1 和 seed 解得 key1,Key2= (key1+seed)2
最后对第一段密文(即 base32 隐藏的数据)和 22 次叠加的 key1 做异或得到原始二进制数据
1.7 base32隐写解密得到encrypt
import base64
def get_base32_diff_value(stego_line, normal_line):
base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for i in range(len(normal_line)):
if stego_line[i] != normal_line[i]:
return abs(base32chars.index(chr(stego_line[i]))-base32chars.index(chr(normal_line[i])))
return 0
# base32 隐写解密
def base32stego_decode(lines):
res = ''
for i in lines:
stego_line = i.strip()
normal_line = base64.b32encode(base64.b32decode(i.strip()))
diff = get_base32_diff_value(stego_line, normal_line)
if '=' not in str(stego_line):
continue
if diff:
res += bin(diff)[2:]
else:
res += '0'
return res
with open("Dream It Possible - extracted.txt", 'rb') as f:
file_lines = f.readlines()
en=open("encrypt.txt","w")
en.write(base32stego_decode(file_lines))
en.close()
base32原理实现:五字节为一组,每五个字节会被编译成八个字符,如果不够五字节的倍数则在后面补充相应组数的零,补充了几组就在编码后的文本后面填充几个等号,每个等号表示填充了一组0
base32隐写:进行base32编码时只要明文长度不是五字节的倍数,末尾一定有至少一个被填充的无意义bit位,可以用来隐藏数据。
1.8 解密
from os import urandom
def generate(m, k):
result = 0
for i in bin(m ^ k)[2:]:
result = result << 1
if int(i):
result = result ^ m ^ k
if result >> 256:
result = result ^ P
continue
return result
def convert(string):
return int(string.encode('hex'), 16)
P = 0x10000000000000000000000000000000000000000000000000000000000000425L
flag1 = 'ThIs_Fl4g_Is_Ri9ht'
flag2 = 'Hey_Fl4g_Is_Not_HeRe'
encrypt1 = open('encrypt.txt', 'r').read()
encrypt2 = 0xec8d57d820ad8c586e4be0122b442c871a3d71cd8036c45083d860caf1793ddc
encrypt3 = 0xc40a0be335babcfbd8c47aa771f6a2ceca2c8638caa5924da58286d2a942697e
key3 = encrypt3 ^ convert(flag2)
key2 = encrypt2 ^ convert(flag1)
print('Found key2:',key2)
print('Found key3:',key3)
tmp = key3 - 233333333333L
for i in range(0,255):
tmp = generate(tmp,0)
seed = tmp ^ key2
print 'Found seed:',seed)
print 'use seed generate key3:',generate(key2,seed)+233333333333L
tmp = key2 - 233333333333L
for i in range(0,255):
tmp = generate(tmp,0)
key1 = tmp ^ seed
print 'Found key1:',key1
print 'use key1 generate key2:',generate(key1,seed)+233333333333L
result = eval(hex(int(encrypt1,2))[:-1]) ^ eval('0x'+hex(key1)[2:-1]*22)
data = open('data.txt', 'w')
data.write(bin(result)[2:])
data.close()
1.9 绘图得到flag
from PIL import Image
str=open("data.txt","r").read()
length=240
width=30
pic=Image.new("RGB",(length,width))
i=0
for x in range(length):
for y in range(width):
if str[i] == '0':
pic.putpixel([x,y],(0,0,0))
else:
pic.putpixel([x,y],(255,255,255))
i += 1
pic.show()
pic.save("Fl4g.png")