对常见文件文件头和隐写术做个归纳总结
FF D8 FF
文件尾:FF D900 00 02 00
RLE压缩的前5字节 00 00 10 00 0089 50 4E 47 0D 0A 1A 0A
文件尾:AE 42 60 8247 49 46 38 39(37) 61
文件尾:00 3B42 4D
文件头标识(2 bytes) 42(B) 4D(M)49 49 2A 0000 00 01 0038 42 50 53D0 CF 11 E053 74 61 6E 64 61 72 64 20 4AFF 57 50 4325 50 44 46 2D 31 2ED0 CF 11 E0 A1 B1 1A E144 65 6C 69 76 65 72 79 2D 64 61 74 65 3ACF AD 12 FE C5 FD 74 6F21 42 44 4E7B 5C 72 74 66FE FF / Unicode big endian:FF FE / UTF-8:EF BB BF /ANSI编码是没有文件头的50 4B 03 04
文件尾:50 4B52 61 72 2157 41 56 454D 54 68 64FF F1(9)41 56 49 202E 72 61 FD2E 52 4D 4600 00 01 BA(3)6D 6F 6F 7630 26 B2 75 8E 66 CF 114D 54 68 643C 3F 78 6D 6C68 74 6D 6C 3EAC 9E BD 8FE3 82 85 9630 82 03 C941 43 31 304C 00 00 0052 45 47 45 44 49 54 34操作系统识别,从文件头标志,到文件的结束标志位 当系统识别到图片的结束标志位后,默认是不再继续识别的 所以可以在文件尾后面加东西
最简单的是附加字符串
附加方法
copy /b a.jpg+b.txt c.jpg,在a图片里加b的内容,得到c图片识别方法
应用
实例
可以把压缩文件藏在图片文件尾后 看起来还是图片
附加方法
识别方法
实例
主要是针对PNG图片
标准的PNG文件结构应包括:
PNG图片的第一个数据块

蓝色部分就是IHDR
可以修改高度值或宽度值对部分信息进行隐藏
识别方法
实例
识别方法
pngcheck -v hidden.png
可能会出现一个size为0的异常块
提取内容的脚本
#!/usr/bin/python
from struct import unpack
from binascii import hexlify, unhexlify
import sys, zlib
# Returns [Position, Chunk Size, Chunk Type, Chunk Data, Chunk CRC]
def getChunk(buf, pos):
a = []
a.append(pos)
size = unpack('!I', buf[pos:pos+4])[0]
# Chunk Size
a.append(buf[pos:pos+4])
# Chunk Type
a.append(buf[pos+4:pos+8])
# Chunk Data
a.append(buf[pos+8:pos+8+size])
# Chunk CRC
a.append(buf[pos+8+size:pos+12+size])
return a
def printChunk(buf, pos):
print 'Pos : '+str(pos)+''
print 'Type: ' + str(buf[pos+4:pos+8])
size = unpack('!I', buf[pos:pos+4])[0]
print 'Size: ' + str(size)
#print 'Cont: ' + str(hexlify(buf[pos+8:pos+8+size]))
print 'CRC : ' + str(hexlify(buf[pos+size+8:pos+size+12]).upper())
print
if len(sys.argv)!=2:
print 'Usage: ./this Stegano_PNG'
sys.exit(2)
buf = open(sys.argv[1]).read()
pos=0
print "PNG Signature: " + str(unpack('cccccccc', buf[pos:pos+8]))
pos+=8
chunks = []
for i in range(3):
chunks.append(getChunk(buf, pos))
printChunk(buf, pos)
pos+=unpack('!I',chunks[i][1])[0]+12
decompressed = zlib.decompress(chunks[1][3])
# Decompressed data length = height x (width * 3 + 1)
print "Data length in PNG file : ", len(chunks[1][3])
print "Decompressed data length: ", len(decompressed)
height = unpack('!I',(chunks[0][3][4:8]))[0]
width = unpack('!I',(chunks[0][3][:4]))[0]
blocksize = width * 3 + 1
filterbits = ''
for i in range(0,len(decompressed),blocksize):
bit = unpack('2401c', decompressed[i:i+blocksize])[0]
if bit == '\x00': filterbits+='0'
elif bit == '\x01': filterbits+='1'
else:
print 'Bit is not 0 or 1... Default is 0 - MAGIC!'
sys.exit(3)
s = filterbits
endianess_filterbits = [filterbits[i:i+8][::-1] for i in xrange(0, len(filterbits), 8)]
flag = ''
for x in endianess_filterbits:
if x=='00000000': break
flag += unhexlify('%x' % int('0b'+str(x), 2))
print 'Flag: ' + flagLSB,最低有效位,英文是Least Significant Bit
原理
给个直观例子

这人眼看不出颜色区别,但最低位不一样
嵌入脚本
from PIL import Image
import math
class LSB:
def __init__(self):
self.im=None
def load_bmp(self,bmp_file):
self.im=Image.open(bmp_file)
self.w,self.h=self.im.size
self.available_info_len=self.w*self.h # 不是绝对可靠的
print ("Load>> 可嵌入",self.available_info_len,"bits的信息")
def write(self,info):
"""先嵌入信息的长度,然后嵌入信息"""
info=self._set_info_len(info)
info_len=len(info)
info_index=0
im_index=0
while True:
if info_index>=info_len:
break
data=info[info_index]
x,y=self._get_xy(im_index)
self._write(x,y,data)
info_index+=1
im_index+=1
def save(self,filename):
self.im.save(filename)
def read(self):
"""先读出信息的长度,然后读出信息"""
_len,im_index=self._get_info_len()
info=[]
for i in range(im_index,im_index+_len):
x,y=self._get_xy(i)
data=self._read(x,y)
info.append(data)
return info
#===============================================================#
def _get_xy(self,l):
return l%self.w,int(l/self.w)
def _set_info_len(self,info):
l=int(math.log(self.available_info_len,2))+1
info_len=[0]*l
_len=len(info)
info_len[-len(bin(_len))+2:]=[int(i) for i in bin(_len)[2:]]
return info_len+info
def _get_info_len(self):
l=int(math.log(self.w*self.h,2))+1
len_list=[]
for i in range(l):
x,y=self._get_xy(i)
_d=self._read(x,y)
len_list.append(str(_d))
_len=''.join(len_list)
_len=int(_len,2)
return _len,l
def _write(self,x,y,data):
origin=self.im.getpixel((x,y))
lower_bit=origin%2
if lower_bit==data:
pass
elif (lower_bit,data) == (0,1):
self.im.putpixel((x,y),origin+1)
elif (lower_bit,data) == (1,0):
self.im.putpixel((x,y),origin-1)
def _read(self,x,y):
data=self.im.getpixel((x,y))
return data%2
if __name__=="__main__":
lsb=LSB()
# 写
lsb.load_bmp('test.bmp')
info1=[0,1,0,1,1,0,1,0]
lsb.write(info1)
lsb.save('lsb.bmp')
# 读
lsb.load_bmp('lsb.bmp')
info2=lsb.read()
print (info2)识别方法
提取脚本
from PIL import Image
im = Image.open("extracted.bmp")
pix = im.load()
width, height = im.size
extracted_bits = []
for y in range(height):
for x in range(width):
r, g, b = pix[(x,y)]
extracted_bits.append(r & 1)
extracted_bits.append(g & 1)
extracted_bits.append(b & 1)
extracted_byte_bits = [extracted_bits[i:i+8] for i in range(0, len(extracted_bits), 8)]
with open("extracted2.bmp", "wb") as out:
for byte_bits in extracted_byte_bits:
byte_str = ''.join(str(x) for x in byte_bits)
byte = chr(int(byte_str, 2))
out.write(byte)实例
JPEG图像格式使用离散余弦变换(Discrete Cosine Transform,DCT)函数来压缩图像
Jsteg隐写
实现
import math
import cv2
import numpy as np
def dct(m):
m = np.float32(m)/255.0
return cv2.dct(m)*255
class Jsteg:
def __init__(self):
self.sequence_after_dct=None
def set_sequence_after_dct(self,sequence_after_dct):
self.sequence_after_dct=sequence_after_dct
self.available_info_len=len([i for i in self.sequence_after_dct if i not in (-1,1,0)]) # 不是绝对可靠的
print ("Load>> 可嵌入",self.available_info_len,'bits')
def get_sequence_after_dct(self):
return self.sequence_after_dct
def write(self,info):
"""先嵌入信息的长度,然后嵌入信息"""
info=self._set_info_len(info)
info_len=len(info)
info_index=0
im_index=0
while True:
if info_index>=info_len:
break
data=info[info_index]
if self._write(im_index,data):
info_index+=1
im_index+=1
def read(self):
"""先读出信息的长度,然后读出信息"""
_len,sequence_index=self._get_info_len()
info=[]
info_index=0
while True:
if info_index>=_len:
break
data=self._read(sequence_index)
if data!=None:
info.append(data)
info_index+=1
sequence_index+=1
return info
#===============================================================#
def _set_info_len(self,info):
l=int(math.log(self.available_info_len,2))+1
info_len=[0]*l
_len=len(info)
info_len[-len(bin(_len))+2:]=[int(i) for i in bin(_len)[2:]]
return info_len+info
def _get_info_len(self):
l=int(math.log(self.available_info_len,2))+1
len_list=[]
_l_index=0
_seq_index=0
while True:
if _l_index>=l:
break
_d=self._read(_seq_index)
if _d!=None:
len_list.append(str(_d))
_l_index+=1
_seq_index+=1
_len=''.join(len_list)
_len=int(_len,2)
return _len,_seq_index
def _write(self,index,data):
origin=self.sequence_after_dct[index]
if origin in (-1,1,0):
return False
lower_bit=origin%2
if lower_bit==data:
pass
elif origin>0:
if (lower_bit,data) == (0,1):
self.sequence_after_dct[index]=origin+1
elif (lower_bit,data) == (1,0):
self.sequence_after_dct[index]=origin-1
elif origin<0:
if (lower_bit,data) == (0,1):
self.sequence_after_dct[index]=origin-1
elif (lower_bit,data) == (1,0):
self.sequence_after_dct[index]=origin+1
return True
def _read(self,index):
if self.sequence_after_dct[index] not in (-1,1,0):
return self.sequence_after_dct[index]%2
else:
return None
if __name__=="__main__":
jsteg=Jsteg()
# 写
sequence_after_dct=[-1,0,1]*100+[i for i in range(-7,500)]
jsteg.set_sequence_after_dct(sequence_after_dct)
info1=[0,1,0,1,1,0,1,0]
jsteg.write(info1)
sequence_after_dct2=jsteg.get_sequence_after_dct()
# 读
jsteg.set_sequence_after_dct(sequence_after_dct2)
info2=jsteg.read()
print (info2)Outgusee算法
识别方法
数字水印(digital watermark)
盲水印
识别方法
实例
容差
容差比较的隐写
识别方法
比如把整个二进制都逆序 得到一堆乱码
识别方法
实例
gif每帧是某个图的一部分 提取每帧再拼接
工具
实例
简单提一下
本来想自己整理下 看到国光大佬的很全面 就直接放个链接
CTF中音频隐写的一些整理总结
实例
看图说话

类似图片隐藏文件 直接看例子吧 攻防世界 Misc高手进阶区 3分题 小小的PDF
对常见文件文件头和图片音频文档隐写术做了个总结
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。