本文主要講述密碼學 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]
列表推導是map
和filter
的語法糖,請你自己找出對照關系
[.. 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();
最后,這些解碼的字符就連接到一起了。並且,每讀一行數據,加工后輸出一次隱寫數據。