咕咕咕的我又回来了,学校这几天在放“五一七天小长假”,加上今天的ISCC有点奇奇怪怪,所以来更一篇博客吧!今天我们来讲关于base64隐写那点事儿……

前言

在了解base64隐写之前,我们先来了解一下base64编码。

base64编码是现在网络上最常见的用于传输8Bit字节码的编码方式之一。在最初有的网络并不支持所有的字节,比如某些系统只支持可见字符的传送,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。所以由于不能传送ASCII的控制字符,它的用途就受到了很大的限制。

而Base64旨在通过一种方法,把所有的字符都用64个特定的可打印字符表示来实现传送。选定的64个字符如下:

索引对应字符索引对应字符索引对应字符索引对应字符
0A17R34i51z
1B18S35j520
2C19T36k531
3D20U37l542
4E21V38m553
5F22W39n564
6G23X40o575
7H24Y41p586
8I25Z42q597
9J26a43r608
10K27b44s619
11L28c45t62+
12M29d46u63/
13N30e47v
14O31f48w
15P32g49x
16Q33h50y

加密过程

由于只用到了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位一组划分不是就有剩余了吗?具体可见下图:

这样是没有剩余的:

image-20200501141049483

而这样是有剩余的:

image-20200501143733614

而base64给出的解决方法是在二进制数串后面加0,一直到二进制数串变成8和6的公倍数,然后把只有0的字节编码成"=",如下图:

image-20200501143811351

所以LoV3被编码成了TG9WMw== ,同时base64编码后面最多只可能出现2个==

解密过程

解密的过程可视作加密的逆过程

由于"="是最后为了补齐填充的,所以解密的时候首先把"="删去,然后写出二进制数串,然后从左往右每8位一组,剩余的不足8位丢掉,然后根据转换表获得相应字符:

以上图为例,TG9WMw==首先变成了TG9WMw,对照上图的表写出来二进制数串:

image-20200501165306625

然后每8位一组,剩余不足的丢弃:

image-20200501165327588

所以这里牵涉到了一个地方,由上面的过程我们可以看成,TG9WMw在解密回LoV3的时候,按每8位一组剩余的丢弃来算,最后的110000中的0000是没有用到的。所以换句话说,这剩下的4位无论是0000还是1111,都是要被丢弃的,所以这就提供了一个可以隐藏信息的地方:

image-20200501165655446

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: