LSB隱寫原理解析
背景知識:8位二進制表示顏色
RGB 中的每個通道(紅、綠、藍)都是一個 0~255 的整數(即 8位):
255 = 11111111
128 = 10000000
17 = 00010001
最低位(最右邊一位)叫做 LSB(Least Significant Bit),改動它對人眼幾乎不可見。
一、LSB隱寫的本質
圖像中每個像素的顏色由RGB三個通道組成,每個通道是一個 0~255(8位) 的整數:
| 數字 | 二進制(8位) |
|---|---|
| 218 | 11011010 |
| 150 | 10010110 |
| 149 | 10010101 |
每個顏色的最后一位(最低有效位,即 LSB)可以輕微改變,而不會被人眼察覺。比如:
- 11011010(LSB是0) 改成 11011011(LSB是1)
- 10010110(LSB是0) 改成 10010111(LSB是1)
我們就可以用這些最低位來“編碼”我們要隱藏的秘密數據的二進制位。
Step 1:LSB 隱寫編碼腳本(encode_lsb.py)
from PIL import Image
import numpy as np
# 加載原圖
image = Image.open('pic/test.png')
print(image)
print(image.size)
data = np.array(image)
print(data)
print(data.shape)
flat_data = data.ravel() # 拉平成一維數組
print(flat_data)
original_data = flat_data.copy()
#
# print(original_data)
# # 要嵌入的消息
message = "hello" #替換為你的webshell
binary_str = ''.join([format(ord(c), '08b') for c in message]) # 轉二進制串
#
# print(binary_str)
print("[原始信息]", message)
print("[二進制表示]", binary_str)
#
# # 進行 LSB 修改
for i in range(len(binary_str)):
flat_data[i] = (flat_data[i] & 0xFE) | int(binary_str[i]) # 保留高7位 + 替換 LSB
# # 打印len(binary_str)位像素變化
print("\n[像素變化演示] 答應替換的那些個通道值:前后值對比:")
for i in range(len(binary_str)):
old = format(original_data[i], '08b')
new = format(flat_data[i], '08b')
print(f"第{i+1}個像素通道: 原={old} 新={new}")
# # 保存新圖
# new_data = flat_data.reshape(data.shape)
# new_image = Image.fromarray(new_data.astype(np.uint8))
# new_image.save('pic/encoded.png')
# print("\n隱寫圖像已保存為: pic/encoded.png")
操作拆解
flat_data[i] = (flat_data[i] & 0xFE) | int(binary_str[i])
我們拆成兩部分看:
第一步:flat_data[i] & 0xFE (0xFE 是 11111110的十六進制)
這個操作是:把原來的最低位清零(不變其他7位)
flat_data[i] = 137 # => 二進制: 10001001
執行:
flat_data[i] & 0xFE= 10001001 & 11111110
= 10001000 # 清掉最低位
第二步:| int(binary_str[i])
這一步是:
把新的 LSB(二進制字符串中一個 0 或 1)寫進去
假設 binary_str[i] = "1":
10001000 | 00000001 = 10001001
所以最終寫入了新 LSB。
最終效果:
把原來的 flat_data[i](一個像素通道值)變成:
保留高 7 位 + 替換最低位為 binary_str[i]
Step 2:LSB 解碼腳本(decode_lsb.py)
from PIL import Image
import numpy as np
# 讀取隱寫圖
image = Image.open('pic/encoded.png')
data = np.array(image).ravel()
# 讀取前 5 個字符(5x8=40 位)
bits = ''
for i in range(40):
bits += str(data[i] & 1) # 取最低位
# 每8位還原為字符
message = ''
for i in range(0, len(bits), 8):
byte = bits[i:i+8]
message += chr(int(byte, 2))
print("\n[解碼結果]:", message)
dct隱寫 待續......
PNG 的結構大致是這樣的:
89 50 4E 47 0D 0A 1A 0A ← PNG 頭部(8 字節)
[chunk1] ← IHDR
[chunk2] ← IDAT
[chunk3] ← IEND
每個 chunk(數據塊)結構如下:
| 長度 (4B) | 類型 (4B) | 數據 (N字節) | CRC (4B) |
? CRC 在哪?
PNG 的每個 chunk(如 IHDR, IDAT)后面都帶一個 CRC-32
作用是驗證:chunk_type + chunk_data 的完整性
改動 PNG 的內容(即使是一個比特),CRC 就不合法
所以你如果:
- 對 PNG 做 LSB 隱寫(修改像素的最低位)
- 再保存為 PNG 格式
- 如果用 Pillow、OpenCV 會自動重寫 PNG → 自動計算 CRC
所以一般不會造成 CRC 錯誤(你看不到它錯),但:
如果你只在二進制層修改 .png 文件(不解碼重寫):
不更新 CRC → 顯示失敗 / 打不開
文件校驗失敗
LSB 隱寫 ? 攻擊媒介的可能性
1. 實時攝像頭采集幀圖
- 攝像頭每秒采集若干張圖像,緩存在內存中
- 若在此階段做 幀圖的 LSB 隱寫,可以藏入二進制 payload
2. 嵌入內容(payload)類型:
| 類型 | 可實現目標 |
|---|---|
| shellcode | 被載入后可執行任意代碼(需配合漏洞) |
| EXE 字節流 | 結合誘導用戶“導出/運行” |
| JS/宏代碼 | 針對瀏覽器播放器 / 辦公組件攻擊 |
| 文本指令 | 被分析工具提取并執行(例如 FFmpeg 插件、AI 模型) |
如何實現這種攻擊?
這類攻擊 不會靠播放器“自動執行”,而是依賴后續觸發鏈條。
一種典型路徑
攝像頭 ? 每幀 PNG LSB 隱寫 ? 合成為 MP4 ? 用戶下載查看 ?
AI/圖像模型或插件提取幀 ? 解析 LSB ? 自動讀取指令 ? 觸發惡意行為
實例:真實案例類型(或可行性)
| 攻擊方式 | 說明 |
|---|---|
| 隱寫 ZIP / EXE 到幀中 | LSB 中藏完整 zip/exe,通過提取幀+解碼還原 |
| AI 模型訓練數據污染攻擊 | 攝像頭采集圖像嵌入 adversarial token,影響模型行為 |
| 偽裝為正常視頻誘導手動操作 | 比如用戶提取幀、運行腳本不知情地執行 payload |
| 播放器直接執行 MP4 內 LSB 數據 | 不可行。MP4 播放器不讀取 LSB,無自動執行能力 |
常用隱寫技術中的執行觸發方式:
| 技術 | 自動觸發? | 備注 |
|---|---|---|
| PNG LSB | 需腳本提取、運行 | |
| MP4 元數據 | 可能 | 可寫入 shell/script,但需利用播放器漏洞 |
| FFmpeg 編碼字段 | 可能 | 如某些字幕流 / metadata 被腳本解析 |

浙公網安備 33010602011771號