咕咕咕的我又回来了,学校这几天在放“五一七天小长假”,加上今天的ISCC有点奇奇怪怪,所以来更一篇博客吧!今天我们来讲关于base64隐写那点事儿……
在了解base64隐写之前,我们先来了解一下base64编码。
base64编码是现在网络上最常见的用于传输8Bit字节码的编码方式之一。在最初有的网络并不支持所有的字节,比如某些系统只支持可见字符的传送,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。所以由于不能传送ASCII的控制字符,它的用途就受到了很大的限制。
而Base64旨在通过一种方法,把所有的字符都用64个特定的可打印字符表示来实现传送。选定的64个字符如下:
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
由于只用到了64个字符,所以使用6个二进制位(2^6 = 64)完全可以把所有的字符表示出来,于是原来的1个字节8位在base64编码中变成了1个字节6位。
换言之:把原本的3个字节变成现在的4个字节,因为(3*8 == 4*6
)
所以在加密的时候首先写出原字符ASCII码对应的二进制数字,每个字符都可以得到一个8位的01串,再把该01串重新按照每6位一组划分即可得到一个新的数字,对照上图给出的表格可以得到一个新的字符。
但是这里有一个问题了:如果明文的字节数刚好是3的倍数那没有问题,按照6位一组划分肯定是刚刚好的;但是如果明文的字节数不是3的倍数,那按照6位一组划分不是就有剩余了吗?具体可见下图:
这样是没有剩余的:
而这样是有剩余的:
而base64给出的解决方法是在二进制数串后面加0,一直到二进制数串变成8和6的公倍数,然后把只有0的字节编码成"=",如下图:
所以LoV3
被编码成了TG9WMw==
,同时base64编码后面最多只可能出现2个==
解密的过程可视作加密的逆过程
由于"="是最后为了补齐填充的,所以解密的时候首先把"="删去,然后写出二进制数串,然后从左往右每8位一组,剩余的不足8位丢掉,然后根据转换表获得相应字符:
以上图为例,TG9WMw==
首先变成了TG9WMw
,对照上图的表写出来二进制数串:
然后每8位一组,剩余不足的丢弃:
所以这里牵涉到了一个地方,由上面的过程我们可以看成,TG9WMw
在解密回LoV3
的时候,按每8位一组剩余的丢弃来算,最后的110000
中的0000
是没有用到的。所以换句话说,这剩下的4位无论是0000
还是1111
,都是要被丢弃的,所以这就提供了一个可以隐藏信息的地方:
TG9WMx==
解密后依然是LoV3
,但已经隐藏进了一个1进去,那么这就是base64隐写
那么,在平时我们如何判断有没有信息被隐藏进去了呢
最简单的就是你是否隐藏信息,解密得到的明文是不变的,那么你重新按照正确的加密流程计算一遍,如果发现结果不一样,那么就说明隐藏进了信息。
一般CTF题目中出现一大堆base64编码字符串的时候,更需要考虑base64隐写
这里附上一个base64提取隐藏信息的脚本:
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('flag_encode.txt', 'rb') as f:
bin_str = ''
for line in f.readlines():
stegb64 = ''.join(line.split())
rowb64 = ''.join(stegb64.decode('base64').encode('base64').split())
offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
equalnum = stegb64.count('=')
if equalnum:
bin_str += bin(offset)[2:].zfill(equalnum * 2)
print ''.join([chr(int(bin_str[i:i + 8], 2)) for i in xrange(0, len(bin_str), 8)])
感谢中南大学极光网安实验室,部分图片源自其公众号:bowtie: