蓝帽杯2020-MISC-熟悉的解密
前言
怎么说呢,我觉得对我一个不懂加解密算法的人来说,应该算是Crypto,不过对于专业人员来说可能TEA、AES、RSA、LCG这种应该都比较ez(like 编码?)
一、题目
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uDT==
Iy0qLSBjb2Rpbmc6IHV0Zi04IC0qLQ0=
aW1wb3J0IHN5cw3=
ZnJvbSBjdHlwZXMgaW1wb3J0ICoN
ZGVmIGVuY2lwaGVyKHYsIGspOg1=
ICAgIHkgPSBjX3VpbnQzMih2WzBdKQ2=
ICAgIHogPSBjX3VpbnQzMih2WzFdKQ0=
ICAgIHN1bSA9IGNfdWludDMyKDApDc==
ICAgIGRlbHRhID0gMHg5ZTM3NzliOQ3=
ICAgIG4gPSAzMg0=
ICAgIHcgPSBbMCwwXQ0=
ICAgIHdoaWxlKG4+MCk6DT==
ICAgICAgICBzdW0udmFsdWUgKz0gZGVsdGEN
ICAgICAgICB5LnZhbHVlICs9ICggei52YWx1ZSA8PCA0ICkgKyBrWzBdIF4gei52YWx1ZSArIHN1bS52YWx1ZSBeICggei52YWx1ZSA+PiA1ICkgKyBrWzFdDT==
ICAgICAgICB6LnZhbHVlICs9ICggeS52YWx1ZSA8PCA0ICkgKyBrWzJdIF4geS52YWx1ZSArIHN1bS52YWx1ZSBeICggeS52YWx1ZSA+PiA1ICkgKyBrWzNdDT==
ICAgICAgICBuIC09IDEN
ICAgIHdbMF0gPSB5LnZhbHVlDS==
ICAgIHdbMV0gPSB6LnZhbHVlDW==
ICAgIHJldHVybiB3DW==
ZGVmIGVuY29kZXN0cih0ZXh0LCBrZXkpOg1=
ICAgIGNpcGhlckxpc3QgPSBbXQ2=
ICAgIHRleHQgKz0gKDggLSBsZW4odGV4dCkgJSA4KSAqIGNocigwKQ0=
ICAgIGZvciBpIGluIHJhbmdlKGxlbih0ZXh0KS84KToN
ICAgICAgICB2MSA9IDAN
ICAgICAgICB2MiA9IDAN
ICAgICAgICBmb3IgaiBpbiByYW5nZSg0KToN
ICAgICAgICAgICAgdjErPSBvcmQodGV4dFtpKjgral0pIDw8ICg0LWotMSkqOA1=
ICAgICAgICAgICAgdjIrPSBvcmQodGV4dFtpKjgrais0XSkgPDwgKDQtai0xKSo4DT==
ICAgICAgICBjaXBoZXJMaXN0LmFwcGVuZChlbmNpcGhlcihbdjEsdjJdLGtleSkpDX==
ICAgIHJldHVybiBjaXBoZXJMaXN0DX==
Dd==
aWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoN
ICAgIGtleSA9IFsxMSwyMiwzMyw0NF0N
CWZsYWcgPSA/DQ==
ICAgIGNpcGhlciA9IGVuY29kZXN0cihmbGFnMSxrZXkpDQ==
CSNjaXBoZXIgPSBbWzQwMTgyODkyMzNMLCAyOTUwMzIwMTUxTF0sIFsxNzcxODI3NDc4TCwgNDkzOTgwODc2TF0sIFsxODYzMjg0ODc5TCwgMTEzNzc5NzU5OUxdLCBbMjc1OTcwMTUyNUwsIDM5NTc4ODUwNTVMXSwgWzI2MDA4NjY4MDVMLCA3ODg1MDcyNExdXQ0=
二、解题
1、解码
看着base,上cyberchef
base64成功,python源码
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys
from ctypes import *
def encipher(v, k):y = c_uint32(v[0])z = c_uint32(v[1])sum = c_uint32(0)delta = 0x9e3779b9n = 32w = [0,0]while(n>0):sum.value += deltay.value += ( z.value << 4 ) + k[0] ^ z.value + sum.value ^ ( z.value >> 5 ) + k[1]z.value += ( y.value << 4 ) + k[2] ^ y.value + sum.value ^ ( y.value >> 5 ) + k[3]n -= 1w[0] = y.valuew[1] = z.valuereturn w
def encodestr(text, key):cipherList = []text += (8 - len(text) % 8) * chr(0)for i in range(len(text)/8):v1 = 0v2 = 0for j in range(4):v1+= ord(text[i*8+j]) << (4-j-1)*8v2+= ord(text[i*8+j+4]) << (4-j-1)*8cipherList.append(encipher([v1,v2],key))return cipherListif __name__ == "__main__":key = [11,22,33,44]flag = ? #"flag{57735e0c-6d02-11ea-8072-040e3c032fa7}" 放这里了,后面调试才看懂cipher = encodestr(flag1,key)#cipher = [[4018289233L, 2950320151L], [1771827478L, 493980876L], [1863284879L, 1137797599L], [2759701525L, 3957885055L], [2600866805L, 78850724L]]
2、加密解析
可以看到加密算法内外两层,将 flag 和 key 进行 encipher() 和 encodestr() 双重加密得到五组由两个整数组成的列表
(1)内层encipher()
TEA算法,主要依托黄金分割delta = 0x9e3779b9进行32层加密,每次加密key是(33-n)*delta这种(直到不满足n>0)
(2)外层encodestr()
将每次计算的一组数分别通过移位运算按照每8位进行分组排列拆分转换为字符
3、解密思路
(1)内层encipher()
TEA算法解密较为简单,整体算法结构基本不变
①将初始sum改为32*delta
即0xc6ef3720
,循环处理方式改为递减
② [y,z]两组数改为迭减,计算顺序反向先计算z,再通过z计算y
③n改为递加
解密后得到5组数据
from ctypes import c_uint32def decipher(v, k):y = c_uint32(v[0])z = c_uint32(v[1])delta = 0x9e3779b9sum = c_uint32(delta * 32) # This is the sum after 32 rounds of encryptionn = 32w = [0, 0]while n > 0:z.value -= ((y.value << 4) + k[2]) ^ (y.value + sum.value) ^ ((y.value >> 5) + k[3])y.value -= ((z.value << 4) + k[0]) ^ (z.value + sum.value) ^ ((z.value >> 5) + k[1])sum.value -= deltan -= 1w[0] = y.valuew[1] = z.valuereturn w# 本题用法
vs = [[4018289233, 2950320151], [1771827478, 493980876], [1863284879, 1137797599], [2759701525, 3957885055], [2600866805,78850724]] # 加密后的数据
for v in vs:k = [11, 22, 33, 44] # 密钥decrypted = decipher(v, k)print(decrypted) # 解密后的数据
#[1718378855, 2067085111]
#[859137328, 1663907428]
#[808594737, 828727597]
#[942683954, 758133808]
#[1694498816, 0]
(2)外层encodestr()
外层相对简单,进行位移操作每次取8位转换成字符
#方法一
List = [[1718378855, 2067085111],[859137328, 1663907428],[808594737, 828727597],[942683954, 758133808],[1694498816, 0]
]def int_to_string(val):result = ""for j in range(4):char = chr(val >> (8 * (3 - j)) & 0xFF)result += charval -= (val >> (8 * (3 - j))) << (8 * (3 - j))return resultdef main():for i in range(5):now = List[i][0]print(int_to_string(now), end='')now = List[i][1]print(int_to_string(now), end='')if __name__ == "__main__":main()
当然也可以直接调n2s实现,除了格式需要调整,没啥异常
from libnum import n2sList = [[1718378855, 2067085111],[859137328, 1663907428],[808594737, 828727597],[942683954, 758133808],[1694498816, 0]
]for i in range(4):x,y = List[i]print(n2s(x), n2s(y))
4、base64隐写
🔥这个是第一次了解,做个记录,师傅写的很详细
https://www.tr0y.wang/2017/06/14/Base64steg/#%E8%A7%A3%E5%AF%86
(1)原理
简单来说就是base64补位的==位置存在不大于2*2的隐写空间
引用原文:
例如:直接解密
VHIweQ==
与VHIweR==
',得到的结果都是'Tr0y'注意红色的 0,我们在解码的时候将其丢弃了,所以这里的值不会影响解码. 所以我们可以在这进行隐写。为什么等号的那部分 0 不能用于隐写?因为修改那里的二进制值会导致等号数量变化,解码的第 1 步会受影响。自然也就破坏了源字符串。而红色部分的 0 是作为最后一个字符二进制的组成部分,还原时只用到了最后一个字符二进制的前部分,后面的部分就不会影响还原。
唯一的影响就是最后一个字符会变化。
如果你直接解密
VHIweQ==
与VHIweR==
',得到的结果都是'Tr0y'。当然,一行 base64 顶多能有 2 个等号,也就是有
2*2
位的可隐写位。所以我们得弄很多行,才能隐藏一个字符串,这也是为什么题目给了一大段 base64 的原因。接下来,把要隐藏的 flag 转为 8 位二进制,塞进去就行了。
也有简单的判断方法,比如出现大段base64密文,同时cyberchef自动解密时,整段复制进去无法自动解密,但手动拖入base64仍可成功解密,此时就可以测试base64隐写
(2)解密
#加密
# -*- coding: cp936 -*-
import base64flag = 'Tr0y{Base64isF4n}' #flag
bin_str = ''.join([bin(ord(c)).replace('0b', '').zfill(8) for c in flag])base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'with open('0.txt', 'rb') as f0, open('1.txt', 'wb') as f1: #'0.txt'是明文, '1.txt'用于存放隐写后的 base64for line in f0.readlines():rowstr = base64.b64encode(line.replace('\n', ''))equalnum = rowstr.count('=')if equalnum and len(bin_str):offset = int('0b'+bin_str[:equalnum * 2], 2)char = rowstr[len(rowstr) - equalnum - 1]rowstr = rowstr.replace(char, base64chars[base64chars.index(char) + offset])bin_str = bin_str[equalnum*2:]f1.write(rowstr + '\n')
#解密
import base64b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('1.txt', 'rb') as f:flag = ''bin_str = ''for line in f.readlines():stegb64 = str(line, "utf-8").strip("\n")rowb64 = str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\n")offset = abs(b64chars.index(stegb64.replace('=','')[-1]) - b64chars.index(rowb64.replace('=','')[-1]))equalnum = stegb64.count('=') #no equalnum no offsetif equalnum:bin_str += bin(offset)[2:].zfill(equalnum * 2)print([chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)])
补充到解密出来的第一段flag后面就行(见加密代码内flag注释),不再说明了