base64stego 還不懂base64的隱寫,詳解15行程式碼帶你領略

網上寫了好多關於xctf MISC新手篇的base64Stego隱寫的教程,但大都不太清楚,基本上都是講了一段隱寫原理,直接上程式碼了。但是程式碼是這道題的關鍵,程式碼講了如何解碼這個隱寫的完整流程,這次我以一個python的源碼的解釋,完美解決這道題。
可能會花費你很長時間,大約一天半天,但是我們要有信心,恆心!

base64 隱寫原理 + 破解隱寫的程式碼

仔細看!!!!!!!
Tr0y’s Blog baseStego
存在隱寫的編碼末尾都存在 = ,一個 = 隱寫 2bit
隱寫的編碼,解碼後,再編碼,最後挨著 = 的字元會發生變化。

史上最完全的源碼解析

真小白級此題的隱寫解碼的python解析,

程式碼分析

# -*- coding: utf-8 -*-
import base64
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('stego.txt', 'rb') as f:
    bin_str = ''
    for line in f.readlines():
        stegb64 = str(line, "utf-8").strip("\n")
        rowb64 =  str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\n")
        offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
        equalnum = stegb64.count('=') #no equalnum no offset
        if equalnum:
            bin_str += bin(offset)[2:].zfill(equalnum * 2)
        print(''.join([chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)])) 

1 python 3.8.無法保存

# -*- coding: utf-8 -*-

在 python 3.8 IDE編寫的程式文件無法保存,也就無法運行,加上這一行就可以了保存了。

2 這一行為後面求隱寫數據提供了標尺,後面再解釋

b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

3 python 文件讀寫

with open('1.txt', 'rb') as f:

python提供的打開文件的方法,不需要關閉文件,即不需要寫 f.close() ,但要注意文件操作的程式碼都寫到 f:下面,有格式要求,有縮進。
注意stego.txt要和腳本放到同一目錄下。
“r” – 讀取 – 默認值。打開文件進行讀取,如果文件不存在則報錯。
“b” – 二進位 – 二進位模式(例如影像)。
以二進位讀入文件數據,也可以直接讀入文本數據。
w3school 文件讀寫
部落客 有夢就要去實現它 with open() as f:

4 隱寫數據二進位字元串

bin_str = ''

用來存儲,隱藏的字元flag, 在後面所有求的的隱寫二進位數據都將追加到 bin_str 的尾部

5 readlines()

for line in f.readlines():

可以使用 readline() 方法返回一行:
循環讀入文件,每次讀取一行,下面就是對每一次讀入的二進位數據的一些操作。

6 strip(“\n”)

stegb64 = str(line, "utf-8").strip("\n")  //將讀入的二進位串編成文本串,此時和stego.txt中的base64串一樣,去除了\n換行符 假!
rowb64 =  str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\n")  //解碼後的又編碼的base64串,即原來的base64 真!

可以理解為 utf-8的英文字元 和 ASCII的英文字元 編碼是一致的。 在任何一種編碼格式中 0-127所代表的字元都是一樣的
在base64隱寫中,如果存在隱寫的數據,隱寫數據後的base64 和 沒有隱寫數據的base64 在最後一個字元會發生變化,即=後面
一個 = 隱藏 2bit數據。集齊8bit,就可以拼出一個字元串

  • eg.隱寫
    stegb64 = IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmV=
    rowb64 = IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmU=
    這裡隱寫了數據 ’01’
    特別!如果沒有變化,也算是一種隱寫 ==->’0000′ =->’00’ 這個可能根據不同的隱藏方法有關。我也可以定義只有不同的
  • eg.strip()
    a=” gho stwwl\n”
    a.strip(“\n”) = ‘ gho stwwl’
    去掉一行首部和尾部的換行符,若要去一邊的話還有 rstrip() lstrip()

7 offset 偏離(數字類型)

offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
  • abs() 返回絕對值 V的位置 – U的位置
  • stegb64.replace(‘=’,”)[-1] 去掉末尾的’=’ 並且返回它的最後一個字元 V
  • rowb64.replace(‘=’,”)[-1] 去掉末尾的’=’ 並且返回它的最後一個字元 U
  • index() 返回這個字元在 b64chars 中的位置

8 計算 ‘=’ 的數量

equalnum = stegb64.count('=') #no equalnum no offset
if equalnum:
            bin_str += bin(offset)[2:].zfill(equalnum * 2)

如果存在等號表示隱藏了數據,我們把隱藏的數據轉換成二進位存到 bin_str 中 以追加的方式

  • bin(x) 返回一個整數 int 或者長整數 long int 的二進位表示。
    bin(1)=’0b1′ 上面的例子就是這個(U V)
    bin(2)=’0b10′
    bin(4)=’0b100′
    因為返回的字元串都有 ‘0b’ 但我們只要二進位數據
    [2:] 從 ‘0b’ 之後截取 我們取到’1′
    但是這個隱寫了 2bit 所以用到了 zfill()

  • .zfill(equalnum * 2) 方法返回指定長度的字元串,原字元串右對齊,前面填充0。

  • str = ‘1’
    str.zfill(2) = ’01’
    str.zfill(4) = ‘0001’

經過這次的轉換 我們求解了 ’01’ 的隱藏數據

經過幾個循環

IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmV= ’01’
LCBhcGFydCBmcm9tIHRoZSBzZW5kZXIgYW5kIGludGVuZGVkIHJlY2lwaWVudCwgc3VzcGU= ’00’
Y3RzIHRoZSBleGlzdGVuY2Ugb2YgdGhlIG1lc3M= ’00’
YWdlLCBhIGZvcm0gb2Ygc2VjdXJpdHkgdGhyb3VnaCBvYnNjdXJpdHkuIFS= ’11’

我們得到了 B 0100 0011 這是 碼ascii

輸出

print(''.join([chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)])) 
  • int() 函數用於將一個字元串或數字轉換為整型。
    int(x, base=10)
    x — 字元串或數字。
    base — 進位數,默認十進位。
  • join()

Python join() 方法用於將序列中的元素以指定的字元連接生成一個新的字元串。
str.join(sequence)
sequence — 要連接的元素序列。

str = “-“;
seq = (“a”, “b”, “c”); # 字元串序列
print str.join( seq );
結果 : a-b-c

為了匹配 sequence 生成一個字元列表 以便用於 join();

最後,這些解碼的字元就連接到一起了。

動手寫一遍吧