我正在反向工程自定义二进制文件格式。我正在使用010编辑器来检查此文件。我发现了表示UTF-8字符串的数据结构,它的标头部分的长度为一或两个字节,然后是实际的UTF-8字符串数据。标头部分似乎存储了字符串数据所需的字节总数。

╔════════════╦═══════════════╦═══════════════════╗
║ first byte ║ optional byte ║ UTF-8 string data ║
╚════════════╩═══════════════╩═══════════════════╝


标头中的第二个字节是可选的,仅在需要UTF-8字符串时才存在超过128个字节要存储。当字符串长度小于或等于128个字节时,解码它的长度很容易。但是,当字符串长度> 128时,我无法从标题部分计算字符串长度。因此,我进行了实验,并生成了许多具有不同字符串长度的二进制文件,结果如下。字符串长度是存储字符串所需的字节总数。第一列和第二列是标头部分中的第一和第二个可选字节。

我的问题是,如何使用标头部分中的值计算长度大于128个字节的字符串长度? >

#1 楼

这不是pascal / delphi字符串格式,因为它们是恒定的1字节或4字节长的字段。

它确实与ASN1格式有些相似,除了ASN1带有一个附加字段表示

总的来说,这看起来像第一个字节的最高有效位不是长度的一部分,并且具有将长度再增加一个字节的特殊含义。

如果使用该表:

In [24]: t
Out[24]:
[['7D', 'N/A', '126'],
 ['7E', 'N/A', '127'],
 ['7F', 'N/A', '128'],
 ['80', '01', '129'],
 ['81', '01', '130'],
 ['C7', '01', '200'],
 ['C8', '01', '201'],
 ['F9', '01', '250'],
 ['FE', '01', '255'],
 ['FF', '01', '256'],
 ['80', '02', '257'],
 ['81', '02', '258'],
 ['82', '02', '259'],
 ['F3', '03', '500'],
 ['F4', '03', '501'],
 ['F5', '03', '502'],
 ['F6', '03', '503'],
 ['80', '04', '513']]


,将MSB拆分为第一个字节:
首先,请注意0x7D是十进制的125,而不是126。这对于MSB清除的所有值都是正确的,因此所有这些值总是加一。

然后如果设置了MSB,第二个字节中的值乘以128,然后加到第一个字节的结果中。

下面的示例使用前两个字节来计算结果(并将其附加为最后一个字节)值:

In [36]: t2=[[int(l[0], 16)&0b10000000>0, hex(int(l[0], 16)&0b01111111)[2:]]+l[1:] for l in t]

In [37]: t2
Out[37]:
[[False, '7d', 'N/A', '126'],
 [False, '7e', 'N/A', '127'],
 [False, '7f', 'N/A', '128'],
 [True, '0', '01', '129'],
 [True, '1', '01', '130'],
 [True, '47', '01', '200'],
 [True, '48', '01', '201'],
 [True, '79', '01', '250'],
 [True, '7e', '01', '255'],
 [True, '7f', '01', '256'],
 [True, '0', '02', '257'],
 [True, '1', '02', '258'],
 [True, '2', '02', '259'],
 [True, '73', '03', '500'],
 [True, '74', '03', '501'],
 [True, '75', '03', '502'],
 [True, '76', '03', '503'],
 [True, '0', '04', '513']]


上面计算的代码不是很漂亮,所以这是一个简短的python函数,其功能相同:

In [53]: t3 = [l+[int(l[1], 16) + 1 + (int(l[2], 16)*128 if l[0] else 0)] for l in t2]

In [54]: t3
Out[54]:
[[False, '7d', 'N/A', '126', 126],
 [False, '7e', 'N/A', '127', 127],
 [False, '7f', 'N/A', '128', 128],
 [True, '0', '01', '129', 129],
 [True, '1', '01', '130', 130],
 [True, '47', '01', '200', 200],
 [True, '48', '01', '201', 201],
 [True, '79', '01', '250', 250],
 [True, '7e', '01', '255', 255],
 [True, '7f', '01', '256', 256],
 [True, '0', '02', '257', 257],
 [True, '1', '02', '258', 258],
 [True, '2', '02', '259', 259],
 [True, '73', '03', '500', 500],
 [True, '74', '03', '501', 501],
 [True, '75', '03', '502', 502],
 [True, '76', '03', '503', 503],
 [True, '0', '04', '513', 513]]
看一下它,可能更直观,它是第二个字节是第一个字节的7位(MSB除外)的延续。 />
def calc(first_byte, second_byte):
    value = 1
    value += first_byte & 0x7F
    msb = bool(first_byte >> 7)
    if msb:
        value += second_byte * 128


我们缺少在第二个字节上设置了MSB的任何值,但是这种方法可能是顺序的,通过设置所有先前MSB的MSB可以使用更大的范围个字节。

#2 楼

看起来像LEB128编码。



本质上,这是一种可变长度编码,其中每个字节的最低7位存储值的一部分,如果不是最后一个字节,则设置每个字节的最高位字节。连续的7位值以小端顺序存储。有关更多详细信息,尤其是有关如何处理带符号数字的详细信息,请参阅DWARF调试标准或Android DEX文件格式文档。



这类似于MIDI文件和ASN.1编码中使用的可变长度数量格式,该格式使用大端顺序。 br />