BASE64
BASE64
ZG==
YY==
aW==
ZF==
cm==
aM==
b2==
dc==
c2==
Zf==
解码得到 daidrhouse,然而编码得到
ZA==
YQ==
aQ==
ZA==
cg==
aA==
bw==
dQ==
cw==
ZQ==
发现每一串的第二位都发生了改变,但是结果没变。
base64原理
顾名思义,base64编码就是用64个ascii字符作为基础来编码二进制内容的一种编码方式。相信各位一定在网页中看到过base64编码的内嵌图片,甚至QQ音乐传输歌词文件时,也采用了base64编码。将二进制编码为ascii字符,使数据在某些场景下更便于阅读、便于传输。当然,将所有二进制「浓缩」到区区64个字符来表示,一定会在体积上作出妥协。字符在编码完成后,会增大1/3倍。
由于只用到了64个字符,所以使用6个二进制位(2^6 = 64)完全可以把所有的字符表示出来,于是原来的1个字节8位在base64编码中变成了1个字节6位。
换言之:把原本的3个字节变成现在的4个字节,因为(3*8 == 4*6
)
索引表
base64有一张标准编码表,为64个ascii字符排序并赋予索引。
索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
有时为了防止混淆(比如链接),会使用 .
_
来代替索引表中的 +
/
。
隐写原理
base64在解码的时候,会按照字符串末尾的 =
数量来删除相应字节数。或许你已经发现了,当一组字符的数量为1字节或2字节的时候,会有4位或2位二进制在解码时被忽略
即解码时:
解密的时候首先把"="删去,然后写出二进制数串,然后从左往右每8位一组,剩余的不足8位丢掉,然后根据转换表获得相应字符
然后每8位一组,剩余不足的丢弃,
也就是说红色部分会被忽略,所以即使对其进行改变,解码得到的结果依然不变。
解密得到的明文是不变的,那么你重新按照正确的加密流程计算一遍,如果发现结果不一样,那么就说明隐藏进了信息。
CTF题目中出现一大堆base64编码字符串的时候,更需要考虑base64隐写。
base64的解密脚本
import re
import base64
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# ccc.txt为待解密的base64隐写字符串所在的文件
f = open('base.txt','r')
base64str = f.readline()
# pattern2用于匹配两个等号情况时,等号前的一个字符
# pattern2用于匹配一个等号情况时,等号前的一个字符
pattern2 = r'(\S)==$'
pattern1 = r'(\S)=$'
# 提取后的隐写二进制字符加入binstring中
binstring = ''
# 逐行读取待解密的base64隐写字符串,逐行处理
while(base64str):
# 先匹配两个等号的情况,如果匹配不上,再配置一个等号的情况
# 如果无等号,则没有隐藏,无需处理
if re.compile(pattern2).findall(base64str):
# mstr为等号前的一个字符,该字符为隐写二进制信息所在的字符
mstr = re.compile(pattern2).findall(base64str)[0]
# 确认mstr字符对应的base64二进制数,赋值给mbin
mbin = bin(b64chars.find(mstr))
# mbin格式如0b100,mbin[0:2]为0b
# mbin[2:].zfill(6)为将0b后面的二进制数前面补0,使0b后面的长度为6
mbin2 = mbin[0:2] + mbin[2:].zfill(6)
# 两个等号情况隐写了4位二进制数,所以提取mbin2的后4bit
# 赋值给stegobin,这就是隐藏的二进制信息
stegobin = mbin2[-4:]
binstring += stegobin
elif re.compile(pattern1).findall(base64str):
mstr = re.compile(pattern1).findall(base64str)[0]
mbin = bin(b64chars.find(mstr))
mbin2 = mbin[0:2] + mbin[2:].zfill(6)
# 一个等号情况隐写了2位二进制数,所以提取mbin2的后2bit
stegobin = mbin2[-2:]
binstring += stegobin
base64str = f.readline()
# stegobin将各行隐藏的二进制字符拼接在一起
# 从第0位开始,8bit、8bit处理,所以range的步进为8
for i in range(0,len(binstring),8):
# int(xxx,2),将二进制字符串转换为10进制的整数,再用chr()转为字符
print(chr(int(binstring[i:i+8],2)),end='')
加密脚本:
import base64
import re
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('3.txt', 'r') as f:
lines = f.readlines() # 读取所有行
flag = 'c2tzZWN7QmFzZTY0aXNGNG59'
bin_str = ''.join([bin(ord(c)).replace('0b', '').zfill(8) for c in flag])
add = 0
pattern2 = r'(\S)==$'
pattern1 = r'(\S)=$'
with open('0.txt', 'w') as w:
for line in lines:
line = line.strip()
if add >= len(bin_str): # 检查是否已处理完flag
break
if re.search(pattern2, line):
tem = re.search(pattern2, line).group(1)
tembin = bin(b64chars.find(tem))
tembin2 = tembin[0:2] + tembin[2:].zfill(6)
steg = tembin2[-4:]
tembin1 = tembin2[0:4] + bin_str[add:add+4]
stag = b64chars[int(tembin1, 2)]
w.write(line[0:-4] + stag + '==\n') # 使用 write 而不是 writelines,并添加换行符
add += 4
elif re.search(pattern1, line):
tem = re.search(pattern1, line).group(1)
tembin = bin(b64chars.find(tem))
tembin2 = tembin[0:2] + tembin[2:].zfill(6)
steg = tembin2[-2:]
tembin1 = tembin2[0:6] + bin_str[add:add+2]
stag = b64chars[int(tembin1, 2)]
w.write(line[0:-3] + stag + '=\n') # 使用 write 而不是 writelines,并添加换行符
add += 2
NewstarCtf 2023
base!
脚本解码得到
iDMb6ZMnTFMtFuouYZHwPTYAoWjC7Hjca8
即flag{b4se_1s_4_g0od_c0d3}