base64详解

Posted    on 2017, Oct5, Thursday 20:47:02
Modified on 2017, Oct6, Friday 02:02:25

Tags: base64

原理

众所周知,任何数据在内存中都是以Byte为单位进行存储的,每个Byte包含了8个二进制位,因此本文不区分二进制数据与文本,而是将其作为一个整体进行讨论。

编码

base64编码包括以下几个步骤

  1. 将每个BYTE转为8位二进制
  2. 若二进制位不是6的倍数则在其后面补0直到其变为6的倍数
  3. 将每6个二进制位作为整体转换为10进制
  4. 通过查表将十进制转为字符
  5. 若转出的长度不为4的倍数,则在其后补’=’

注: 转换表见附录

若是以编码 CNSS 为例,则整个过程为

base64encode

解码

base64解码包括以下几个步骤

  1. 将除了 = 以外的别的字符通过查表转为十进制数字
  2. 将十进制数字转为6位二进制
  3. 删去等号个数*2位二进制
  4. 将二进制转为BYTE

黑魔法:base64隐写

由上面的过程可以看出,在编码的过程中补上了一些0,若是补充的不是0,而是一些有意义的数据,那么就可以在不改变解码结果的情况下,添加额外的数据

脚本

下面为python3的脚本,包括生成隐写,解隐写

目前已加入ctf大礼包 : ctf大礼包

import base64

def get_base64_diff_value(s1, s2):
    """get base64 diff value"""
    base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    for i in range(len(s2)):
        if s1[i] != s2[i]:
            return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))
    return 0

def solve_stego(stego_str_list):
    '''stego_str_list str列表'''
    bin_str = ''
    for stego_str in stego_str_list:
        stego_str = stego_str.replace('\n', '')
        norm_str = base64.b64encode(base64.b64decode(stego_str)).decode()
        diff = get_base64_diff_value(stego_str, norm_str)
        pads_num = stego_str.count('=')
        bin_str += bin(diff)[2:].zfill(pads_num * 2)


    ret = b''
    for i in range(0, len(bin_str), 8):
        ret += int(bin_str[i:i + 8], 2).to_bytes(1, 'little')
    return ret

def to_stego(normal_data, stego_data, stego_bit_len=4):
    base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    bin_str = ''
    for each in stego_data:
        bin_str += bin(each)[2:].zfill(8)
    line_len = len(normal_data) // (len(bin_str) // stego_bit_len)
    if stego_bit_len == 4:
        line_len = line_len - ((line_len % 3 - 1) + 3) % 3 # 令 line_len % 3 = 1 右边建立了一个 0 1 2 -> 2 0 1 的映射
    else:
        line_len = line_len - ((line_len % 3 - 2) + 3) % 3 # 令 line_len % 3 = 2 右边建立了一个 0 1 2 -> 1 2 0 的映射
    if line_len <= 0:
        return []
    ret = []
    index = 0
    while True:
        if index >= len(normal_data):
            break
        encode_str = base64.b64encode(normal_data[index:index + line_len]).decode()
        # print('index=%d bin_str=%s' % (index, bin_str))
        index += line_len
        # print(encode_str)
        if bin_str != '':
            if stego_bit_len == 4:
                now_encode = bin_str[:4]
                bin_str = bin_str[4:]
            tmp_list = encode_str.rpartition(encode_str[-1 * (stego_bit_len // 2 + 1)])
            encode_str = tmp_list[0] + base64chars[base64chars.index(tmp_list[1]) + int(now_encode, 2)] + tmp_list[2]
        ret.append(encode_str)
    return ret


def main():
    with open('2.txt', 'rb') as fp:
        file_lines = fp.readlines()
    stego_str_list = []
    for each in file_lines:
        stego_str_list.append(each.decode().replace('\n', ''))
    print(solve_stego(stego_str_list))

    l = to_stego(b'123456789012345678901234567890123456', b'cnss', 4)
    print(l)
    print(solve_stego(l))

if __name__ == '__main__':
    main()

附录

1. 转换表

转换表分为几种形式

1. 表格

转换表:

索引 对应字符 索引 对应字符 索引 对应字符 索引 对应字符
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
2. 文本

以下为python语法

table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

2. 引用

本文参考自

  1. 百度百科 : base64
  2. 推酷 : ZJPCCTF:我未见过的base64隐写

3. 附件

base64编码过程表: base64.xlsx