密碼學 base64stego 還不懂base64的隱寫?詳解12行代碼帶你領略


本文主要講述密碼學 base64隱寫的python腳本解密的方法,以及一些python函數的用法。

隱寫是密碼學的一個概念,現代,我們可以把數據藏到一張圖片中,一段文字中。隱寫對於數據取證,數據隱藏有着十分重要的價值,主要用於軍工,和安全方面。

網上寫了很多多關於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")
        tureb64 =  str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\n")
        offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(tureb64.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():

到底二進制讀出來的line是什么呢!
源數據:IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmV=\n

print(line):b'IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmV=\n' class 'bytes'

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

6 strip("\n")

stegb64 = str(line, "utf-8").strip("\n")  //將讀入的二進制串編成文本串,此時和stego.txt中的base64隱寫串一樣,去除了\n換行符 假!
tureb64 =  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=

tureb64 = IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmU=

str(bytes, encoding, errors)這個很重要

  • str(line)
    b'IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmU=\n'

  • str(line,'utf-8')
    IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmU=

如果既沒有給出encoding也沒有給出errors, str(object)返回object.__str__(),這是object的“非正式”或可打印的字符串表示。對於字符串對象,這是字符串本身。如果object沒有__str__()方法,則str()返回repr(object)。

如果至少給出了encoding或errors中的一個,object應該是bytes-like object(例如bytes或bytearray)。在這種情況下,如果object是一個bytes(或bytearray)對象,則str(bytes, encoding, errors)等價於bytes.decode(encoding, errors)

這里隱寫了數據 '01'

特別!如果沒有變化,也算是一種隱寫 ==->'0000' =->'00' 這個可能根據不同的隱藏方法有關。我們也可以定義只有不同的base64真假編碼可以藏數據。但這里是只要有 = 就有隱寫!

  • eg.strip()
    a=" gho stwwl\n"
    a.strip("\n") = ' gho stwwl'
    去掉一行首部和尾部的換行符,若要去一邊的話還有 rstrip() lstrip()

7 offset 偏離(數字類型)

offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(
(ture64.replace('=','')[-1]))
  • abs() 返回絕對值 V的位置 - U的位置
  • stegb64.replace('=','')[-1] 去掉末尾的'=' 並且返回它的最后一個字符 V
  • rowb64.replace('=','')[-1] 去掉末尾的'=' 並且返回它的最后一個字符 U
  • index() 返回這個字符在 b64chars 中的位置 ,這就是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

在Python中函數是一類公民

def create_adder(x):
    def adder(y):
        return x + y
    return adder

add_10 = create_adder(10)
add_10(3)

13

匿名函數

(lambda x: x > 2)(3)
True

(lambda x, y: x ** 2 + y ** 2)(2, 1)
5
高階函數

list(map(add_10, [1, 2, 3]))
[11, 12, 13]

list(map(max, [1, 2, 3], [4, 2, 1]))
[4, 2, 3]

list(filter(lambda x: x > 5, [3, 4, 5, 6, 7]))
[6, 7]

列表推導是mapfilter的語法糖,請你自己找出對照關系

[.. for in range(10)]

[add_10(i) for i in [1, 2, 3]]
[11, 12, 13]

[x for x in [3, 4, 5, 6, 7] if x > 5]
[6, 7]

列表推導也可以用在集合和字典上

{x for x in 'abcddeef' if x not in 'abc'}
{'d', 'e', 'f'}

{x: x**2 for x in range(5)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

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

最后,這些解碼的字符就連接到一起了。並且,每讀一行數據,加工后輸出一次隱寫數據。

懂(mei)了(dong)嗎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM