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

Dba_sys發表於2021-05-18

網上寫了好多關於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();

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

動手寫一遍吧

相關文章