Base64編碼是一種常見的編碼方式,本文首先介紹了Base64編碼的原理,在此基礎上介紹了Base64隱寫的原理,然後給出了CTF比賽中Base64隱寫的一個實例,最後在附錄中給出了Base64編碼解碼的Python代碼。
Base64編碼的原理
Base64是一種基於64個可打印字符表示二進制數據的表示方法,其一大特點是能夠將不可打印字符編碼爲可打印字符。
Base64使用的64個可打印字符及其索引如下表:
簡單來說,就是A-Za-z0-9+/這64個可打印字符。
編碼時,將要編碼的內容轉換爲二進制數據,每6位作爲一組,從表中找到對應的字符。因爲ASCII編碼8位表示一個字符,3個ASCII剛好可以編碼成4個字符(3*8=4*6),因此一般以3個ASCII字符爲一個編碼的基本單位:
但需要編碼的文本字節數並不總是3的倍數,不可避免會遇見最後只剩下2個或1個字符的情況,需要特殊處理:
%3=2的情況:
%3=1的情況:
Base64隱寫的原理
從Base64編碼的原理我們很自然推出Base64解碼的過程:
- 丟掉末尾的所有‘=’;
- 每個字符查錶轉換爲對應的6位索引,得到一串二進制字符串;
- 從頭開始,每次取8位轉換爲對應的ASCII字符,如果不足8位則丟棄。
在解碼的第3步中,會有部分數據被丟棄(即不會影響解碼結果),這些數據正是我們在編碼過程中補的0(本文第三第四張圖中加粗的部分)。也就是說,如果我們在編碼過程中不全用0填充,而是用其他的數據填充,仍然可以正常編碼解碼,因此這些位置可以用於隱寫。
解開隱寫的方法就是將這些不影響解碼結果的位提取出來組成二進制串,然後轉換成ASCII字符串。
Base64隱寫實例
在GXYCTF2019中出現了一道Base64隱寫的題目,題目網址爲:https://buuoj.cn/challenges#[GXYCTF2019]SXMgdGhpcyBiYXNlPw==
題目文件中每一行爲一串Base64編碼後的字符串,解題思路大致如下:
- 依次讀取每行,從中提取出隱寫位。
- 如果最後沒有‘=’,說明沒有隱寫位,跳過。
- 如果最後是一個‘=’,說明有兩位隱寫位,將倒數第二個字符轉化爲對應的二進制索引,然後取後兩位。
- 如果最後是兩個‘=’,說明有四位隱寫位,將倒數第三個字符轉化爲對應的二進制索引,然後取後四位。
- 將每行提取出的隱寫位依次連接起來,每8位爲一組轉換爲ASCII字符,最後不足8位的丟棄。
解題腳本如下:
def inttobin(a, n):
ret = bin(a)[2:]
while len(ret) < n:
ret = '0' + ret
return ret
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
f = open("flag.txt", "r")
tmpbin = ''
res = ''
line = f.readline()
while line:
if line[-3] == '=':
if line[-4] == '=':
tmpbin += inttobin(table.index(line[-5]), 6)[2:]
else:
tmpbin += inttobin(table.index(line[-4]), 6)[4:]
line = f.readline()
quotient = len(tmpbin)/8
for i in range(quotient):
res += chr(int(tmpbin[8*i:8*i+8], 2))
print res
附錄
Base64編碼和解碼腳本:
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def inttobin(a, n):
res = bin(a)[2:]
while len(res) < n:
res = '0' + res
return res
def b64encode(strin):
res = ''
quotient = len(strin)/3
remainder = len(strin)%3
for i in range(quotient):
tmp = strin[i*3:i*3+3]
tmpbin = inttobin(ord(tmp[0]), 8) + inttobin(ord(tmp[1]), 8) + inttobin(ord(tmp[2]), 8)
print tmpbin
for j in range(4):
index = int(tmpbin[6*j:6*j+6], 2)
res += table[index]
if remainder == 1:
tmpbin = inttobin(ord(strin[-1]), 8) + '0000'
res += table[int(tmpbin[:6], 2)]
res += table[int(tmpbin[6:], 2)]
res += '=='
elif remainder == 2:
tmpbin = inttobin(ord(strin[-2]), 8) + inttobin(ord(strin[-1]), 8) + '00'
res += table[int(tmpbin[:6], 2)]
res += table[int(tmpbin[6:12], 2)]
res += table[int(tmpbin[12:], 2)]
res += '='
return res
def b64decode(strin):
res = ''
while strin[-1] == '=':
strin = strin[:-1]
tmpbin = ''
for i in range(len(strin)):
tmpbin += inttobin(table.index(strin[i]), 6)
remainder = len(tmpbin) / 8
for i in range(remainder):
res += chr(int(tmpbin[i*8:i*8+8], 2))
return res