先来看一下base64的概念
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(38 = 46 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
规则
关于这个编码的规则:
1.把3个字符变成4个字符
2.每76个字符加一个换行符
3.最后的结束符也要处理
4.10个数字,26个大写字母,26个小写字母,1个+,一个/刚好64个字符
例子
为方便理解,举下例子
转换前 11111011, 11101111, 10111110 (二进制)
首先按每6位分开 111110, 111110, 111110, 111110
再到最高位添加两个0
转换后 00111110, 00111110, 00111110, 00111110 (二进制)
上面的三个字节是原文,下面的四个字节是转换后的Base64编码,其前两位均为0。
转换后,我们用一个码表来得到我们想要的字符串(也就是最终的Base64编码),这个表是这样的:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 索引 对应字符0 A1 B2 C3 D4 E5 F6 G7 H8 I9 J10 K11 L12 M13 N14 O15 P16 Q17 R18 S19 T20 U21 V22 W23 X24 Y25 Z26 a27 b28 c29 d30 e31 f32 g33 h34 i35 j36 k37 l38 m39 n40 o41 p42 q43 r44 s45 t46 u47 v48 w49 x50 y51 z52 053 154 255 356 457 558 659 760 861 962 +63 /
虽然python解base64很方便
最好还是先写个脚本加深对base64的理解
文末我会贴出我的调试脚本
那么base64隐写到底是什么东西呢?
关键是base64解码的时候
1.检查base64编码后面有几个等于号
2.把字符串按照base64表转换成46的倍数位数二进制
3.__删除等于号的个数8的bit__(等于号不在base64规定的范围内,只是为了补足长度,所以解码时要删除)
4.按照6个bit一组转成字符
此处的关键就是,解码的时候,会删除等于号的个数8的bit
且我们只用6个bit表示一个等于号(xxxxxx)
那么,意思就是我们可以控制__等于号个数2bit__的字符
NJCTF2017有一道misc是base64隐写
从里面抽出一条来
1
2
3
4
5
|
import base64
s = "QnkgcmVhc29uIG9mIGhpcyBmYWxsZW4gZGl2aW5pdHm="
print base64.b64decode(s)
print s
print base64.b64encode(base64.b64decode(s))
|
会发现返回
1
2
3
|
By reason of his fallen divinity
QnkgcmVhc29uIG9mIGhpcyBmYWxsZW4gZGl2aW5pdHm=
QnkgcmVhc29uIG9mIGhpcyBmYWxsZW4gZGl2aW5pdHk=
|
会发现有区别,但是并不影响正常显示
来分析一下为啥,就拿我的昵称举例
首先是把每位字符转化成ascii的二进制形式
1
2
|
c | 0 | 1 | 4
01100011 | 00110000 | 00110001 | 00110100
|
进行base64编码时,按每6位分开,如果不能被6整除要用0补齐
1
2
|
011000 11|0011 0000|00 110001 | 001101 00
011000 | 110011 | 000000 | 110001 | 001101 | 000000
|
并且所有高位补两个0至8位
1
2
|
011000 | 110011 | 000000 | 110001 | 001101 | 000000
00011000 | 00110011 | 00000000 | 00110001 | 00001101 | 00000000
|
这样才能刚好用64个不同形式表示
1
2
|
00011000 | 00110011 | 00000000 | 00110001 | 00001101 | 00000000
Y | z | A | x | N | A
|
但是会发现字符只有6位数不够被4整除
所以到字符后面添加2个等号
1
2
|
YzAxNA
YzAxNA==
|
解密时
先找出YzAxNA==每位字符在base64码表中的位置
由于等号没有我就不表示了
1
2
|
Y | z | A | x | N | A
011000 | 110011 | 000000 | 110001 | 001101 | 000000
|
将二进制连接起来并按每八位划分
1
2
|
011000110011000000110001001101000000
01100011 | 00110000 | 00110001 | 00110100 | 0000
|
会发现末尾多了等号数*2bit的0
这些0在解密时是要被删除的
所以base64隐写的精髓就是这几bit可控
如
1
2
3
4
5
|
YzAxNA==
YzAxNB==
YzAxNC==
......
YzAxNO==
|
解密的到的都是一样的结果
调试脚本
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
72
73
74
75
76
77
78
|
# -*- coding:utf-8 -*-
base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def encode(str):
s = str
s_bin = ""
for i in range(0,len(s)):
s_bin = s_bin + bin(ord(s[i])).replace("0b","").rjust(8,"0") #把每个字符二进制化
print s_bin
if len(s_bin) % 6 != 0: #判断二进制长度能否被6整除
for j in range(1,6):
if (len(s_bin) + j) % 6 == 0:
bin_length = len(s_bin) + j #获取二进制长度
break
s_bin = s_bin.ljust(bin_length,"0") #把二进制长度补到能被6整除
print s_bin
encode = ""
for k in range(0,len(s_bin)/6):
index = k * 6
each_bin = s_bin[index:index+6].zfill(8) #把6Bit再添两位高位0
print each_bin
each_enc = int(each_bin,2) #转化为10进制
print each_enc
encode = encode + base[each_enc]
# print encode
if len(encode) % 4 != 0: #判断解密后字符串是否能被4整除,如果不能,要在末尾加=
for l in range(1,4):
if (len(encode) + l) % 4 == 0:
encode_length = len(encode) + l
print encode.ljust(encode_length,"=")
def raw(str): #获取每位字符的二进制形式
s = str
for i in range(0,len(s)):
print s[i],bin(ord(s[i])).replace("0b","").rjust(8,"0")
|