从零开始做题: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")