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大礼包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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()

附录

转换表

转换表分为几种形式

表格

转换表:

索引 对应字符 索引 对应字符 索引 对应字符 索引 对应字符
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

文本

以下为python语法

1
table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

引用

本文参考自

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

附件

base64编码过程表: base64.xlsx