Python 构造 - 解析可变数量的可变长度记录
Python construct - parsing a variable number of variable length records
我正在使用 construct 2.8 对某些 long-lost Pascal 程序创建的某些文件的 header 进行逆向工程。
header是由很多不同的记录组成的,有些记录是可选的,不知道顺序是不是固定的。
例如,其中两条记录如下所示:
header_record_filetype = cs.Struct(
'record_type' / cs.Int8ub,
'file_type' / cs.PascalString(cs.Int16ub),
'unknown' / cs.Int8ub
)
header_record_user = cs.Struct(
'record_type' / cs.Int8ub,
'user' / cs.PascalString(cs.Int16ub)
)
我还确定了六个。
我将如何让解析器根据 record_type
成员为未知数量的记录选择正确的记录类型,直到它遇到类型为 0 的记录(或到达类型的末尾)文件)?
您选择了一个有趣的挑战。看来构造确实支持各种条件定义:http://construct.readthedocs.io/en/latest/misc.html#conditional
此外,我还找到了示例,例如这个信息性示例:https://github.com/construct/construct/blob/master/construct/examples/formats/executable/elf32.py
我可能还会定义一个 header 和 body 类型,例如:
header_body_record_filetype = cs.Struct(
'file_type' / cs.PascalString(cs.Int16ub),
'unknown' / cs.Int8ub
)
header_body_record_user = cs.Struct(
'user' / cs.PascalString(cs.Int16ub)
)
header_record = cs.Struct(
'record_type' / cs.Int8ub,
'body' / Embedded(IfThenElse(this.record_type == "user",
header_body_record_user,
header_body_record_filetype,
))
)
我是这样解决的:
header = cs.Struct(
'record_type' / cs.Int8ub,
'record' / cs.Switch(cs.this.record_type, {header_record_type_0x01: header_record_0x01,
header_record_type_filename: header_record_filename,
header_record_type_filetype: header_record_filetype,
header_record_type_user: header_record_user,
header_record_type_end: header_record_end,
header_record_type_image_metadata: header_record_image_metadata},
default=header_record_end
),
'offset' / cs.Tell
)
with open(sys.argv[1], 'rb') as f:
h = f.read(2048)
index = 0
record_type = h[index]
while record_type != 0:
record = header.parse(h[index:])
print(record)
index += record.offset
record_type = record.record_type
但我不知道这是否是最好的*方式。
*对于 "best".
的某些值
编辑
我发现 RepeatUntil() 构造隐藏在帮助页面的底部。所以现在我有了这个:
header = cs.Struct(
'type' / cs.Enum(cs.Int8ub,
file_metadata=0x01,
filename=0x02,
file_type=0x03,
user=0x0A,
image_metadata=0x10,
end=0xFF),
'record' / cs.Switch(cs.this.type, {'file_metadata': header_record_file_metadata,
'filename': header_record_filename,
'file_type': header_record_filetype,
'user': header_record_user,
'end': header_record_end,
'image_metadata': header_record_image_metadata}),
'size' / cs.Tell
)
with open(sys.argv[1], 'rb') as f:
h = f.read(2048)
records = cs.RepeatUntil(lambda obj, lst, ctx: obj.type == 'end', header).parse(h)
print(records)
感觉更简洁,更符合 construct 的声明性。
郑重声明,我是 Construct 开发人员。如果您希望此代码与当前版本保持同步,则:
- 字符串类需要有
encoding
,它是强制性的
- 嵌入式不支持
IfThenElse
和Switch
类
我正在使用 construct 2.8 对某些 long-lost Pascal 程序创建的某些文件的 header 进行逆向工程。
header是由很多不同的记录组成的,有些记录是可选的,不知道顺序是不是固定的。
例如,其中两条记录如下所示:
header_record_filetype = cs.Struct(
'record_type' / cs.Int8ub,
'file_type' / cs.PascalString(cs.Int16ub),
'unknown' / cs.Int8ub
)
header_record_user = cs.Struct(
'record_type' / cs.Int8ub,
'user' / cs.PascalString(cs.Int16ub)
)
我还确定了六个。
我将如何让解析器根据 record_type
成员为未知数量的记录选择正确的记录类型,直到它遇到类型为 0 的记录(或到达类型的末尾)文件)?
您选择了一个有趣的挑战。看来构造确实支持各种条件定义:http://construct.readthedocs.io/en/latest/misc.html#conditional
此外,我还找到了示例,例如这个信息性示例:https://github.com/construct/construct/blob/master/construct/examples/formats/executable/elf32.py
我可能还会定义一个 header 和 body 类型,例如:
header_body_record_filetype = cs.Struct(
'file_type' / cs.PascalString(cs.Int16ub),
'unknown' / cs.Int8ub
)
header_body_record_user = cs.Struct(
'user' / cs.PascalString(cs.Int16ub)
)
header_record = cs.Struct(
'record_type' / cs.Int8ub,
'body' / Embedded(IfThenElse(this.record_type == "user",
header_body_record_user,
header_body_record_filetype,
))
)
我是这样解决的:
header = cs.Struct(
'record_type' / cs.Int8ub,
'record' / cs.Switch(cs.this.record_type, {header_record_type_0x01: header_record_0x01,
header_record_type_filename: header_record_filename,
header_record_type_filetype: header_record_filetype,
header_record_type_user: header_record_user,
header_record_type_end: header_record_end,
header_record_type_image_metadata: header_record_image_metadata},
default=header_record_end
),
'offset' / cs.Tell
)
with open(sys.argv[1], 'rb') as f:
h = f.read(2048)
index = 0
record_type = h[index]
while record_type != 0:
record = header.parse(h[index:])
print(record)
index += record.offset
record_type = record.record_type
但我不知道这是否是最好的*方式。
*对于 "best".
的某些值编辑
我发现 RepeatUntil() 构造隐藏在帮助页面的底部。所以现在我有了这个:
header = cs.Struct(
'type' / cs.Enum(cs.Int8ub,
file_metadata=0x01,
filename=0x02,
file_type=0x03,
user=0x0A,
image_metadata=0x10,
end=0xFF),
'record' / cs.Switch(cs.this.type, {'file_metadata': header_record_file_metadata,
'filename': header_record_filename,
'file_type': header_record_filetype,
'user': header_record_user,
'end': header_record_end,
'image_metadata': header_record_image_metadata}),
'size' / cs.Tell
)
with open(sys.argv[1], 'rb') as f:
h = f.read(2048)
records = cs.RepeatUntil(lambda obj, lst, ctx: obj.type == 'end', header).parse(h)
print(records)
感觉更简洁,更符合 construct 的声明性。
郑重声明,我是 Construct 开发人员。如果您希望此代码与当前版本保持同步,则:
- 字符串类需要有
encoding
,它是强制性的 - 嵌入式不支持
IfThenElse
和Switch
类