You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:24:55