From 10fbbc8d6f405a9bc12706beaf645f27d174f799 Mon Sep 17 00:00:00 2001 From: amuliang <982632988@qq.com> Date: Tue, 12 Dec 2023 01:05:48 +0800 Subject: [PATCH] =?UTF-8?q?png=E5=9B=BE=E7=89=87=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=80=9A=E9=81=93=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- ctf_analyze_image.py | 372 +++++-------------------------------------- type/GIF.py | 92 +++++++++++ type/JPG.py | 99 ++++++++++++ type/PNG.py | 238 +++++++++++++++++++++++++++ 5 files changed, 480 insertions(+), 331 deletions(-) create mode 100644 type/GIF.py create mode 100644 type/JPG.py create mode 100644 type/PNG.py diff --git a/README.md b/README.md index 8b92e79..76fd547 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ # ctf_analyze_image -应对CTF中常见图片分析题 \ No newline at end of file +应对CTF中常见图片分析题,暂时只支持PNG图片 + +## 处理能力 +文件信息、文件头修复、文件尾修复、文件尺寸修复、填充数据识别、通道信息检测 + +## 使用方法 +``` +python3 ctf_analyze_image.py test.png +``` \ No newline at end of file diff --git a/ctf_analyze_image.py b/ctf_analyze_image.py index 1749762..4e0c8c4 100644 --- a/ctf_analyze_image.py +++ b/ctf_analyze_image.py @@ -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) diff --git a/type/GIF.py b/type/GIF.py new file mode 100644 index 0000000..a404e50 --- /dev/null +++ b/type/GIF.py @@ -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, [] + diff --git a/type/JPG.py b/type/JPG.py new file mode 100644 index 0000000..a9247a9 --- /dev/null +++ b/type/JPG.py @@ -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, [] \ No newline at end of file diff --git a/type/PNG.py b/type/PNG.py new file mode 100644 index 0000000..dc1cddf --- /dev/null +++ b/type/PNG.py @@ -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 + + + +