ctf_analyze_image/ctf_analyze_image.py

418 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import binascii
import json
import os
import random
import re
import sys
import time
from PIL import Image
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
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'
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
if file_type1 != None:
if file_type1 == file_type2:
print(f'看起来文件格式是{file_type1}')
else:
print(f'文件名中格式是{file_type1},但是解析文件头格式发现{weight}%是{file_type2}')
else:
if file_type2 != None:
file_type1 = file_type2
print(f'检测文件头认为文件格式{weight}%是{file_type2}')
else:
print('未检测到任何图片格式特征,该文件可能不是图片文件')
index = input('要指定文件格式吗?或者直接回车。(1、png 2、jpg 3、gif)')
if index.isdigit():
index = int(index)
if index == 1:
file_type1 = 'png'
elif index == 2:
file_type1 = 'jpg'
elif index == 3:
file_type1 = 'gif'
return file_type1
# 总函数
def main(filename: str):
# 读取图片数据
data = None
with open(filename, 'rb') as f:
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
# 检测并修复
changed = False
if model != None:
info = model.get_info(data)
# print(json.dumps(info))
changed1, data, tail = model.check_tail(data)
changed2, data, head = model.repair_sig(data)
changed3, data, width, height = model.repair_size(data)
changed = changed1 or changed2 or changed3
else:
print('[*] 未进行任何处理')
if changed:
# 创建结果目录
basename = os.path.basename(filename)
dirname = filename.replace('.', '_') + str(int(time.time()))
dirpath = os.path.join(os.path.dirname(os.path.abspath(filename)), dirname)
os.mkdir(dirpath)
# 保存解析结果
imagepath = os.path.join(dirpath, basename)
f = open(imagepath, 'wb')
f.write(data)
f.close()
if changed1 and tail:
f = open(os.path.join(dirpath, 'tail'), 'wb')
f.write(tail)
f.close()
if changed2 and head:
f = open(os.path.join(dirpath, 'head'), 'wb')
f.write(head)
f.close()
print(f'[*] 分析结果已保存到 {dirpath}')
# 尝试打开图片看一看
try:
im = Image.open(imagepath)
im.show()
except Exception as e:
print('[x] 尝试打开图片失败,失败原因:' + str(e))
else:
print('[*] 未进行任何处理')
print('[*] 分析完成!')
# 开始执行
filename = sys.argv[1]
# filename = 'G:\\1.png'
main(filename)