Python解析ZIP文件偏移异常:解压后文件开头多4字节问题
手动解析ZIP文件时解压内容开头多出4字节的问题
我正在用Python手动解析ZIP文件,已定位到End Of Central Directory(EOCD)签名b'\x50\x4b\x05\x06',参考ZIP文件格式规范实现了解析逻辑。但从file_header_offset位置读取compressed_size大小的二进制内容后,解压出的文件开头有时会多出4字节。
解析逻辑
# End Of Central Directory file.seek(centralDirOffset) res['disc_number'] = int.from_bytes(file.read(2), 'little') res['cd_start_disc'] = int.from_bytes(file.read(2), 'little') res['n_cd_on_disc'] = int.from_bytes(file.read(2), 'little') res['n_c_total'] = int.from_bytes(file.read(2), 'little') res['cd_size'] = int.from_bytes(file.read(4), 'little') res['cd_offset'] = int.from_bytes(file.read(4), 'little') res['comment_len'] = int.from_bytes(file.read(2), 'little') # Files Specific file.seek(res['cd_offset']) res['signature'] = binascii.hexlify(file.read(4)).decode("utf-8") res['version'] = struct.unpack('<H', file.read(2))[0] res['min_ver'] = struct.unpack('<H', file.read(2))[0] res['bit_flag'] = struct.unpack('<H', file.read(2))[0] res['compression_method'] = struct.unpack('<H', file.read(2))[0] res['f_last_modif_time'] = struct.unpack('<H', file.read(2))[0] res['f_last_modif_date'] = struct.unpack('<H', file.read(2))[0] res['crc_32'] = struct.unpack('<I', file.read(4))[0] res['compressed_size'] = struct.unpack('<I', file.read(4))[0] res['uncompressed_size'] = struct.unpack('<I', file.read(4))[0] res['f_name_length'] = struct.unpack('<H', file.read(2))[0] res['extra_field_length'] = struct.unpack('<H', file.read(2))[0] res['file_comment_length'] = struct.unpack('<H', file.read(2))[0] res['disc_n_file_start'] = struct.unpack('<H', file.read(2))[0] res['intern_file_attr'] = struct.unpack('<H', file.read(2))[0] res['extern_file_attr'] = struct.unpack('<I', file.read(4))[0] res['file_header_offset'] = struct.unpack('<I', file.read(4))[0] res['file_name'] = struct.unpack(f'{res["f_name_length"]}s', file.read(res['f_name_length']))[0].decode() res['extra_field'] = struct.unpack(f'{res["extra_field_length"]}s', file.read(res['extra_field_length']))[0] res['file_comment'] = struct.unpack(f'{res["file_comment_length"]}s', file.read(res['file_comment_length']))[0].decode() # Skipping to file header res['file_offset'] = res['file_header_offset'] + 30 + res['f_name_length'] + res['extra_field_length'] res['file_size'] = res['compressed_size']
复现方法
使用以下函数获取采用Deflate压缩算法(ID为8)的单文件ZIP的中央目录偏移:
def findCentralDirectory(file): eocd_signature = b'\x50\x4b\x05\x06' file.seek(-22, 2) while True: h = file.read(4) if not h: logging.error("[!] Error -02: EOCD signature cannot be found. Make sure file is not corrupt.") sys.exit(-2) if h == eocd_signature: return file.tell()
期望结果
希望脚本能计算出正确的偏移量,使读取的二进制块可通过zlib.decompress正常解压。
问题分析与解决方案
1. 修正本地文件头长度计算
ZIP本地文件头的固定部分长度为32字节(4字节签名+后续12个字段共28字节),代码中使用的30少算了2字节,若叠加其他解析偏差会导致读取的压缩数据包含本地文件头的4字节签名,最终解压后开头多出4字节。
修正偏移计算代码:
# 修正本地文件头固定长度为32字节 res['file_offset'] = res['file_header_offset'] + 32 + res['f_name_length'] + res['extra_field_length']
2. 验证中央目录项签名
确保res['signature']的值为504b0102(中央目录项标准签名),若不符说明cd_offset指向位置错误,后续字段解析全部失效。可添加校验:
if res['signature'] != '504b0102': raise ValueError("Invalid central directory entry signature")
3. 适配Deflate压缩流
Deflate压缩算法(ID=8)生成的ZIP数据可能是原始Deflate流(无zlib头),直接使用zlib.decompress()会导致解压异常。需指定wbits=-15参数:
import zlib # 读取压缩数据 file.seek(res['file_offset']) compressed_data = file.read(res['compressed_size']) # 根据压缩方法解压 if res['compression_method'] == 8: # 处理原始Deflate流 uncompressed_data = zlib.decompress(compressed_data, wbits=-15) elif res['compression_method'] == 0: # 无压缩 uncompressed_data = compressed_data else: raise NotImplementedError(f"Compression method {res['compression_method']} not supported")
4. 处理数据描述符场景
若bit_flag & 0x08 != 0,说明ZIP使用了数据描述符,此时本地文件头中的CRC、压缩大小等为占位值,实际值在压缩数据之后。该场景不影响压缩数据起始位置,仅需在验证文件完整性时使用数据描述符中的值。
内容的提问来源于stack exchange,提问作者Belani Hassen
相关产品推荐
相关产品推荐

