png图片增加通道检测
parent
6ebd0c35ef
commit
10fbbc8d6f
10
README.md
10
README.md
|
@ -1,3 +1,11 @@
|
|||
# ctf_analyze_image
|
||||
|
||||
应对CTF中常见图片分析题
|
||||
应对CTF中常见图片分析题,暂时只支持PNG图片
|
||||
|
||||
## 处理能力
|
||||
文件信息、文件头修复、文件尾修复、文件尺寸修复、填充数据识别、通道信息检测
|
||||
|
||||
## 使用方法
|
||||
```
|
||||
python3 ctf_analyze_image.py test.png
|
||||
```
|
|
@ -6,345 +6,46 @@ import re
|
|||
import sys
|
||||
import time
|
||||
from PIL import Image
|
||||
from type.PNG import PNG
|
||||
# from type.JPG import JPG
|
||||
# from type.GIF import GIF
|
||||
|
||||
|
||||
class PNG:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
return 100
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
text = b'tEXt'
|
||||
index = data.find(text)
|
||||
if index > 32:
|
||||
length = int.from_bytes(data[index-4: index], byteorder='big')
|
||||
chunk_data = data[index+4: index+4+length]
|
||||
print('[*] 图像信息:')
|
||||
print(bytes(chunk_data).split(b'\x00'))
|
||||
|
||||
info = chunk_data
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
iend = b'\x49\x45\x4E\x44\xAE\x42\x60\x82'
|
||||
length = len(data)
|
||||
index = data.find(iend)
|
||||
if index == -1:
|
||||
index = data.find(iend[0:4])
|
||||
if index != -1:
|
||||
index = index + 4
|
||||
else:
|
||||
index = index + 8
|
||||
|
||||
if index == -1:
|
||||
print('[x] 缺少png文件格式结束标识符IEND')
|
||||
elif index < 32:
|
||||
print('[x] 缺少png文件格式结束标识符IEND位置貌似不太对')
|
||||
else:
|
||||
tail = data[index:]
|
||||
if len(tail) > 0:
|
||||
changed = True
|
||||
print('[x] 检测到文件尾部有垃圾数据,已尝试修复')
|
||||
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
index = data.find(sig)
|
||||
if index > 0:
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = bytes(data[:index])
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != sig:
|
||||
print('[x] 检测到文件头异常,已尝试修复')
|
||||
data[0:8] = sig
|
||||
changed = True
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
data_begin = chunk_begin + 8
|
||||
width_begin = data_begin
|
||||
height_begin = data_begin + 4
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
ctype = data[chunk_begin+4: chunk_begin+8] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
chunk_data = data[data_begin: data_begin+length]
|
||||
width = int.from_bytes(chunk_data[0:4], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
crc = int.from_bytes(data[data_begin+length:data_begin+length+4], byteorder='big')
|
||||
|
||||
if PNG.test_ihdr_chunk(ctype + chunk_data, crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
b = False
|
||||
if not b:
|
||||
print('[x] 尝试修复图片宽度...')
|
||||
chunk = ctype + chunk_data
|
||||
for i in range(1, 2048):
|
||||
chunk[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if PNG.test_ihdr_chunk(bytes(chunk), crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
b = True
|
||||
break
|
||||
if not b:
|
||||
print('[x] 尝试修复图片高度...')
|
||||
chunk = ctype + chunk_data
|
||||
for i in range(1, 2048):
|
||||
chunk[8:12] = i.to_bytes(4, byteorder='big')
|
||||
if PNG.test_ihdr_chunk(chunk, crc):
|
||||
height = i
|
||||
print(f'[*] 图片高度修复成功,宽度值为{i}')
|
||||
data[height_begin: height_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
b = True
|
||||
break
|
||||
if not b:
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data, width, height
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
|
||||
class JPG:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
return 100
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
info = {}
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
index = data.find(sig)
|
||||
if index != -1:
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = data[:index]
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != sig:
|
||||
print('[x] 检测到文件头异常,已尝试修复')
|
||||
data[0:8] = sig
|
||||
changed = True
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
width_begin = chunk_begin + 8
|
||||
height_begin = chunk_begin + 12
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
chunk_data = data[chunk_begin+4: chunk_begin+4+length]
|
||||
ctype = chunk_data[0:4] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
width = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[8:12], byteorder='big')
|
||||
crc = chunk_data[-4:]
|
||||
|
||||
if JPG.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
if width == 0:
|
||||
print('[x] 检测到图片宽度设置异常,尝试修复...')
|
||||
for i in range(1, 2048):
|
||||
chunk_data[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if JPG.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
break
|
||||
if width == 0:
|
||||
print('[x] 图片宽度修复失败')
|
||||
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
|
||||
class GIF:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
return 100
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
info = {}
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
index = data.find(sig)
|
||||
if index != -1:
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = data[:index]
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != sig:
|
||||
print('[x] 检测到文件头异常,已尝试修复')
|
||||
data[0:8] = sig
|
||||
changed = True
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
width_begin = chunk_begin + 8
|
||||
height_begin = chunk_begin + 12
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
chunk_data = data[chunk_begin+4: chunk_begin+4+length]
|
||||
ctype = chunk_data[0:4] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
width = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[8:12], byteorder='big')
|
||||
crc = chunk_data[-4:]
|
||||
|
||||
if GIF.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
if width == 0:
|
||||
print('[x] 检测到图片宽度设置异常,尝试修复...')
|
||||
for i in range(1, 2048):
|
||||
chunk_data[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if GIF.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
break
|
||||
if width == 0:
|
||||
print('[x] 图片宽度修复失败')
|
||||
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
types = [PNG] # 暂时先只处理PNG,其他的都没写好呢[PNG, JPG, GIF]
|
||||
|
||||
|
||||
def check_image_type(filename: str, data: bytearray):
|
||||
file_type1 = None
|
||||
if re.match('.*\.png$', filename.lower()):
|
||||
file_type1 = 'png'
|
||||
elif re.match('.*\.jpg$', filename.lower()):
|
||||
file_type1 = 'jpg'
|
||||
elif re.match('.*\.gif$', filename.lower()):
|
||||
file_type1 = 'gif'
|
||||
for model in types:
|
||||
if re.match(f'.*\.{model.extension}$', filename.lower()):
|
||||
file_type1 = model
|
||||
break
|
||||
|
||||
file_type2 = None
|
||||
weight = 0
|
||||
weight2 = PNG.test(data)
|
||||
if weight2 > weight:
|
||||
file_type2 = 'png'
|
||||
weight = weight2
|
||||
weight2 = JPG.test(data)
|
||||
if weight2 > weight:
|
||||
file_type2 = 'jpg'
|
||||
weight = weight2
|
||||
weight2 = GIF.test(data)
|
||||
if weight2 > weight:
|
||||
file_type2 = 'gif'
|
||||
weight = weight2
|
||||
for model in types:
|
||||
weight2 = model.test(data)
|
||||
if weight2 > weight:
|
||||
file_type2 = model
|
||||
weight = weight2
|
||||
|
||||
if file_type1 != None:
|
||||
if file_type1 == file_type2:
|
||||
print(f'看起来文件格式是{file_type1}')
|
||||
print(f'看起来文件格式是{file_type1.extension}')
|
||||
else:
|
||||
print(f'文件名中格式是{file_type1},但是解析文件头格式发现{weight}%是{file_type2}')
|
||||
print(f'文件名中格式是{file_type1.extension},但是解析文件头格式发现{weight}%是{file_type2.extension}')
|
||||
else:
|
||||
if file_type2 != None:
|
||||
file_type1 = file_type2
|
||||
print(f'检测文件头认为文件格式{weight}%是{file_type2}')
|
||||
print(f'检测文件头认为文件格式{weight}%是{file_type2.extension}')
|
||||
else:
|
||||
print('未检测到任何图片格式特征,该文件可能不是图片文件')
|
||||
|
||||
index = input('要指定文件格式吗?或者直接回车。(1、png 2、jpg 3、gif):')
|
||||
for i in range(len(types)):
|
||||
print(f' {i}、{types[i].extension}')
|
||||
index = input('要指定文件格式吗?或者直接回车。请输入文件格式序号:')
|
||||
if index.isdigit():
|
||||
index = int(index)
|
||||
if index == 1:
|
||||
file_type1 = 'png'
|
||||
elif index == 2:
|
||||
file_type1 = 'jpg'
|
||||
elif index == 3:
|
||||
file_type1 = 'gif'
|
||||
file_type1 = types[index]
|
||||
|
||||
return file_type1
|
||||
|
||||
|
@ -358,14 +59,7 @@ def main(filename: str):
|
|||
data = bytearray(f.read())
|
||||
|
||||
# 确定文件格式
|
||||
file_type = check_image_type(filename, data)
|
||||
model = None
|
||||
if file_type == 'png':
|
||||
model = PNG
|
||||
elif file_type == 'jpg':
|
||||
model = JPG
|
||||
elif file_type == 'gif':
|
||||
model = GIF
|
||||
model = check_image_type(filename, data)
|
||||
|
||||
# 检测并修复
|
||||
changed = False
|
||||
|
@ -375,6 +69,16 @@ def main(filename: str):
|
|||
changed1, data, tail = model.check_tail(data)
|
||||
changed2, data, head = model.repair_sig(data)
|
||||
changed3, data, width, height = model.repair_size(data)
|
||||
changed4, data, data_append = model.check_data(data)
|
||||
checked_channel = False # 是否检测过RGB通道,如果没检测过,则下面还会尝试检测一遍
|
||||
try:
|
||||
# 尝试分析通道信息
|
||||
changed5, data_r, data_g, data_b = model.check_channels(filename, width, height)
|
||||
checked_channel = True
|
||||
except Exception as e:
|
||||
pass
|
||||
#print('[x] 尝试打开图片失败,失败原因:' + str(e))
|
||||
|
||||
changed = changed1 or changed2 or changed3
|
||||
else:
|
||||
print('[*] 未进行任何处理')
|
||||
|
@ -399,9 +103,16 @@ def main(filename: str):
|
|||
f = open(os.path.join(dirpath, 'head'), 'wb')
|
||||
f.write(head)
|
||||
f.close()
|
||||
for i in range(len(data_append)):
|
||||
f = open(os.path.join(dirpath, 'data_append_' + str(i)), 'wb')
|
||||
f.write(data_append[i])
|
||||
f.close()
|
||||
print(f'[*] 分析结果已保存到 {dirpath}')
|
||||
# 尝试打开图片看一看
|
||||
if not checked_channel:
|
||||
# 尝试分析通道信息
|
||||
changed5, data_r, data_g, data_b = model.check_channels(imagepath, width, height)
|
||||
try:
|
||||
# 尝试打开图片看一看
|
||||
im = Image.open(imagepath)
|
||||
im.show()
|
||||
except Exception as e:
|
||||
|
@ -412,6 +123,7 @@ def main(filename: str):
|
|||
print('[*] 分析完成!')
|
||||
|
||||
# 开始执行
|
||||
filename = sys.argv[1]
|
||||
# filename = 'G:\\1.png'
|
||||
if len(sys.argv) > 1:
|
||||
filename = sys.argv[1]
|
||||
|
||||
main(filename)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import binascii
|
||||
|
||||
|
||||
class GIF:
|
||||
extension = 'gif'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
return 100
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
info = {}
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
index = data.find(sig)
|
||||
if index != -1:
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = data[:index]
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != sig:
|
||||
print('[x] 检测到文件头异常,已尝试修复')
|
||||
data[0:8] = sig
|
||||
changed = True
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
width_begin = chunk_begin + 8
|
||||
height_begin = chunk_begin + 12
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
chunk_data = data[chunk_begin+4: chunk_begin+4+length]
|
||||
ctype = chunk_data[0:4] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
width = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[8:12], byteorder='big')
|
||||
crc = chunk_data[-4:]
|
||||
|
||||
if GIF.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
if width == 0:
|
||||
print('[x] 检测到图片宽度设置异常,尝试修复...')
|
||||
for i in range(1, 2048):
|
||||
chunk_data[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if GIF.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
break
|
||||
if width == 0:
|
||||
print('[x] 图片宽度修复失败')
|
||||
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
|
||||
@staticmethod
|
||||
def check_data(data: bytearray):
|
||||
return False, data, []
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import binascii
|
||||
|
||||
|
||||
class JPG:
|
||||
extension = 'jpg'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
rate = 0
|
||||
if data.find(b'\xff\x') != -1:
|
||||
rate = 100
|
||||
else:
|
||||
if data.find(b'IHDR') != -1:
|
||||
rate = rate + 30
|
||||
if data.find(b'IEND') != -1:
|
||||
rate = rate + 30
|
||||
return rate
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
info = {}
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
index = data.find(sig)
|
||||
if index != -1:
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = data[:index]
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != sig:
|
||||
print('[x] 检测到文件头异常,已尝试修复')
|
||||
data[0:8] = sig
|
||||
changed = True
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
width_begin = chunk_begin + 8
|
||||
height_begin = chunk_begin + 12
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
chunk_data = data[chunk_begin+4: chunk_begin+4+length]
|
||||
ctype = chunk_data[0:4] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
width = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[8:12], byteorder='big')
|
||||
crc = chunk_data[-4:]
|
||||
|
||||
if JPG.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
if width == 0:
|
||||
print('[x] 检测到图片宽度设置异常,尝试修复...')
|
||||
for i in range(1, 2048):
|
||||
chunk_data[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if JPG.test_ihdr_chunk(chunk_data[:-4], crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
break
|
||||
if width == 0:
|
||||
print('[x] 图片宽度修复失败')
|
||||
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
|
||||
@staticmethod
|
||||
def check_data(data: bytearray):
|
||||
return False, data, []
|
|
@ -0,0 +1,238 @@
|
|||
import binascii
|
||||
import zlib
|
||||
from PIL import Image
|
||||
|
||||
class PNG:
|
||||
extension = 'png'
|
||||
sig = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 检测文件格式,返回百分比
|
||||
@staticmethod
|
||||
def test(data: bytearray):
|
||||
rate = 0
|
||||
if data.find(PNG.sig) != -1:
|
||||
rate = 100
|
||||
else:
|
||||
if data.find(b'IHDR') != -1:
|
||||
rate = rate + 30
|
||||
if data.find(b'IEND') != -1:
|
||||
rate = rate + 30
|
||||
return rate
|
||||
|
||||
# 获取图片信息
|
||||
@staticmethod
|
||||
def get_info(data: bytearray):
|
||||
text = b'tEXt'
|
||||
index = data.find(text)
|
||||
chunk_data = ''
|
||||
if index > 32:
|
||||
length = int.from_bytes(data[index-4: index], byteorder='big')
|
||||
chunk_data = data[index+4: index+4+length]
|
||||
print('[*] 图像信息:')
|
||||
print(bytes(chunk_data).split(b'\x00'))
|
||||
|
||||
info = chunk_data
|
||||
return info
|
||||
|
||||
def check_tail(data: bytearray):
|
||||
changed = False
|
||||
tail = None
|
||||
iend = b'\x49\x45\x4E\x44\xAE\x42\x60\x82'
|
||||
length = len(data)
|
||||
index = data.find(iend)
|
||||
if index == -1:
|
||||
index = data.find(iend[0:4])
|
||||
if index != -1:
|
||||
index = index + 4
|
||||
else:
|
||||
index = index + 8
|
||||
|
||||
if index == -1:
|
||||
print('[x] 缺少png文件格式结束标识符IEND')
|
||||
elif index < 32:
|
||||
print('[x] 缺少png文件格式结束标识符IEND位置貌似不太对')
|
||||
else:
|
||||
tail = data[index:]
|
||||
if len(tail) > 0:
|
||||
changed = True
|
||||
print('[x] 检测到文件尾部有垃圾数据,已尝试修复')
|
||||
|
||||
return changed, data, tail
|
||||
|
||||
# 修复文件头
|
||||
@staticmethod
|
||||
def repair_sig(data: bytearray):
|
||||
changed = False
|
||||
head = None
|
||||
print('[*] 检测文件头')
|
||||
index = data.find(PNG.sig)
|
||||
if index > 0:
|
||||
# 如果能检测到文件头
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
head = bytes(data[:index])
|
||||
data = data[index:]
|
||||
changed = True
|
||||
elif data[0:8] != PNG.sig:
|
||||
# 如果检测不到文件头,那么需要进一步确认有没有可能是被篡改或者缺失文件头,可以根据下一个chunk标记来辅助确认
|
||||
index2 = data.find(b'IHDR')
|
||||
if index2 != -1:
|
||||
# 能检测到有图片尺寸信息的IHDR标记
|
||||
if index2 == 12:
|
||||
# 说明文件头只是写的不对,直接替换
|
||||
data[0:8] = PNG.sig
|
||||
elif index2 > 12:
|
||||
# 说明前面还是有垃圾数据
|
||||
head = bytes(data[:index2-4])
|
||||
data = bytearray(PNG.sig) + data[index2-4:]
|
||||
print('[x] 检测到文件头前有垃圾数据,已尝试修复')
|
||||
elif index2 > 3 and index2 < 12:
|
||||
# 说明文件头长度不够
|
||||
data = bytearray(PNG.sig) + data[index2-4:]
|
||||
print('[*] 检测到文件头异常,已尝试修复')
|
||||
changed = True
|
||||
else:
|
||||
# 说明可能不是png图片了
|
||||
print('[x] 未发现png图片特征,无法修复')
|
||||
return changed, data, head
|
||||
|
||||
# 修复尺寸
|
||||
@staticmethod
|
||||
def repair_size(data: bytearray):
|
||||
changed = False
|
||||
print('[*] 检测文件尺寸')
|
||||
chunk_begin = 8
|
||||
data_begin = chunk_begin + 8
|
||||
width_begin = data_begin
|
||||
height_begin = data_begin + 4
|
||||
length = int.from_bytes(data[chunk_begin:chunk_begin+4], byteorder='big')
|
||||
ctype = data[chunk_begin+4: chunk_begin+8] # 应该为 b'\x49\x48\x44\x52',即 b'IHDR'
|
||||
chunk_data = data[data_begin: data_begin+length]
|
||||
width = int.from_bytes(chunk_data[0:4], byteorder='big')
|
||||
height = int.from_bytes(chunk_data[4:8], byteorder='big')
|
||||
crc = int.from_bytes(data[data_begin+length:data_begin+length+4], byteorder='big')
|
||||
|
||||
if PNG.test_ihdr_chunk(ctype + chunk_data, crc):
|
||||
print(' - 图片尺寸设置正确')
|
||||
else:
|
||||
b = False
|
||||
if not b:
|
||||
print('[x] 尝试修复图片宽度...')
|
||||
chunk = ctype + chunk_data
|
||||
for i in range(1, 2048):
|
||||
chunk[4:8] = i.to_bytes(4, byteorder='big')
|
||||
if PNG.test_ihdr_chunk(bytes(chunk), crc):
|
||||
width = i
|
||||
print(f'[*] 图片宽度修复成功,宽度值为{i}')
|
||||
data[width_begin: width_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
b = True
|
||||
break
|
||||
if not b:
|
||||
print('[x] 尝试修复图片高度...')
|
||||
chunk = ctype + chunk_data
|
||||
for i in range(1, 2048):
|
||||
chunk[8:12] = i.to_bytes(4, byteorder='big')
|
||||
if PNG.test_ihdr_chunk(chunk, crc):
|
||||
height = i
|
||||
print(f'[*] 图片高度修复成功,宽度值为{i}')
|
||||
data[height_begin: height_begin+4] = width.to_bytes(4, byteorder='big')
|
||||
changed = True
|
||||
b = True
|
||||
break
|
||||
if not b:
|
||||
temp = input(f'当前宽度值为{width},高度值为{height},是否尝试将高度值设置为更高?可输入高度值或直接回车:')
|
||||
if temp.isdigit():
|
||||
height = int(temp)
|
||||
data[height_begin: height_begin+4] = height.to_bytes(4, byteorder='big')
|
||||
print(f'[*] 已将图片高度设置为{height}')
|
||||
changed = True
|
||||
return changed, data, width, height
|
||||
|
||||
@staticmethod
|
||||
def test_ihdr_chunk(chunk_data, crc):
|
||||
crc32 = binascii.crc32(chunk_data) & 0xffffffff
|
||||
return crc32 == crc
|
||||
|
||||
@staticmethod
|
||||
def check_data(data: bytearray):
|
||||
idats = []
|
||||
index = 0
|
||||
print('[*] 检测IDAT数据块')
|
||||
while True:
|
||||
index = data.find(b'IDAT', index)
|
||||
if index != -1:
|
||||
length = int.from_bytes(data[index-4: index], byteorder='big')
|
||||
data_begin = index + 4
|
||||
idats.append((data_begin, length))
|
||||
index = index + length + 8
|
||||
else:
|
||||
break
|
||||
|
||||
changed = False
|
||||
b = False
|
||||
idat_append = []
|
||||
for idat in idats:
|
||||
if not b:
|
||||
# 默认每个IDAT数据块最大为65445,如果出现一个数据块小于65445,说明数据块结束。如果后续又出现了IDAT数据块,则说明可能有异常数据
|
||||
if idat[1] < 65445:
|
||||
b = True
|
||||
else:
|
||||
print('[x] 检测到异常IDAT数据块')
|
||||
idat_data = data[idat[0]: idat[0]+idat[1]]
|
||||
idat_result = binascii.hexlify(zlib.decompress(idat_data)) # 究竟是不是要这样处理有待研究
|
||||
idat_append.append[idat_result]
|
||||
|
||||
return changed, data, idat_append
|
||||
|
||||
# 处理RGB三个通道
|
||||
@staticmethod
|
||||
def check_channels(filename: str, width: int, height: int):
|
||||
image = Image.open(filename)
|
||||
pixels_r = []
|
||||
pixels_g = []
|
||||
pixels_b = []
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
r, g, b = image.getpixel((x, y))
|
||||
pixels_r.append(r)
|
||||
pixels_g.append(g)
|
||||
pixels_b.append(b)
|
||||
|
||||
data_r = PNG.check_channel(pixels_r, width, height, '红')
|
||||
data_g = PNG.check_channel(pixels_g, width, height, '绿')
|
||||
data_b = PNG.check_channel(pixels_b, width, height, '蓝')
|
||||
|
||||
return False, data_r, data_g, data_b
|
||||
|
||||
# 对单通道的处理方式
|
||||
@staticmethod
|
||||
def check_channel(channel: bytearray, width: int, height: int, channel_name):
|
||||
data_set = set(channel)
|
||||
value_len = len(data_set)
|
||||
|
||||
print('[*] 正在检测%s色通道'%channel_name)
|
||||
|
||||
# value_len为1说明所有像素都一样,不包含任何信息
|
||||
if value_len == 1:
|
||||
return False
|
||||
|
||||
# value_len小于17说明有可能包含01的二进制信息,或者8、10、16进制信息
|
||||
if value_len < 17:
|
||||
print('[?] 通道颜色值仅有' + str(value_len) + '个值, 建议继续分析此通道信息')
|
||||
|
||||
# 说明应该不只是黑白信息,可能都为可打印字符
|
||||
if value_len > 2 and value_len < 95:
|
||||
# 提取出可打印字符
|
||||
print_list = [chr(i) for i in channel if i > 31 and i < 127]
|
||||
print_str = ''.join(print_list)
|
||||
print('[?] 看看通道中这些可打印信息有没有用吧:' + print_str)
|
||||
return print_str
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue