将十六进制值转换为十进制值的问题

逆向工程 二元分析 十六进制 字符串 结构 二进制格式
2021-06-23 08:50:00

我正在对自定义二进制文件格式进行逆向工程。我正在使用 010 Editor 检查这个文件。我找到了表示 UTF-8 字符串的数据结构,它的标头部分长度为一或两个字节,然后是实际的 UTF-8 字符串数据。标题部分似乎存储字符串数据所需的总字节数。

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

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

╔════╦════╦═══════════════╗
║ 01 ║ 02 ║ String length ║
╠════╬════╬═══════════════╣
║ 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 ║
╚════╩════╩═══════════════╝

我在某处读到 Pascal\Delphi 正在使用字符串格式,它有标题来保存字符串长度,而不是像 C 中的空终止字符串,这看起来与我的情况相似。

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

2个回答

这不是 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 从第一个字节中分离出来:

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']]

首先,让我们注意0x7D十进制是 125,而不是 126。对于 MSB 清晰的所有值都是如此,因此所有这些值总是加 1。

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

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

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]]

上面做计算的代码不是很漂亮,所以这里有一个简短的python函数,它做同样的事情:

def calc(first_byte, second_byte):
    value = 1
    value += first_byte & 0x7F
    msb = bool(first_byte >> 7)
    if msb:
        value += second_byte * 128

另一种可能更直观的方式是,第二个字节是第一个字节的 7 位(不包括 MSB)的延续。这是一个基于该方法进行相同计算的函数:

def calc(first_byte, second_byte):
    value = 1
    value += first_byte & 0x7F
    msb = bool(first_byte >> 7)
    if msb:
        value += second_byte << 7

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

这看起来像LEB128编码。 

 

本质上,这是一种可变长度编码,其中每个字节的最低 7 位存储部分值,如果不是最后一个字节,则设置每个字节的最高位。连续的 7 位值以小端顺序存储。

 

有关更多详细信息,尤其是有关如何处理签名数字的详细信息,请参阅DWARF 调试标准Android DEX 文件格式文档。

 

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