当 `OrdinalBase` 字段设置为 1 时,`kernel32.dll` 如何导出序数 0?

How can `kernel32.dll` export an ordinal of 0, when its `OrdinalBase` field is set to 1?

看着 kernel32.dll 加载到内存中,我看到以下导出序号 table:

(gdb) x /400hd $eax

0x776334b0 <Wow64Transition+71576>:     3       4       5       6       7       8       9       10
0x776334c0 <Wow64Transition+71592>:     11      12      13      14      15      16      17      18
0x776334d0 <Wow64Transition+71608>:     19      20      21      22      23      24      25      26
0x776334e0 <Wow64Transition+71624>:     27      28      29      30      31      32      33      34
0x776334f0 <Wow64Transition+71640>:     35      36      37      38      39      40      41      42
0x77633500 <Wow64Transition+71656>:     43      44      45      46      47      48      49      50
0x77633510 <Wow64Transition+71672>:     51      52      53      54      55      56      57      58
0x77633520 <Wow64Transition+71688>:     59      60      61      62      63      64      65      66
0x77633530 <Wow64Transition+71704>:     67      68      69      70      0       71      72      73
0x77633540 <Wow64Transition+71720>:     74      75      76      77      78      79      80      81
0x77633550 <Wow64Transition+71736>:     82      83      84      85      86      87      88      89
0x77633560 <Wow64Transition+71752>:     90      91      92      93      94      95      96      97

经验证,导出序号0。

但是导出目录table的OrdinalBase字段设置为1,序号怎么会小于1呢?:

Ordinal Base: The starting ordinal number for exports in this image. This field specifies the starting ordinal number for the export address table. It is usually set to 1.

文档说序数是有偏差的,即:

The export ordinal table is an array of 16-bit indexes into the export address table. The ordinals are biased by the Ordinal Base field of the export directory table. In other words, the ordinal base must be subtracted from the ordinals to obtain true indexes into the export address table.

现在,这意味着序数 0 会导致导出地址的索引为 -1 table?

从我的角度来看,似乎序数是预先调整的(即从每个中减去 1),但是 "official" 算法(也在 PE-docs 中说明)失败了:

Thus, when the export name pointer table is searched and a matching string is found at position i, the algorithm for finding the symbol’s address is:

i = Search_ExportNamePointerTable (ExportName); 
ordinal =
ExportOrdinalTable [i]; 
SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];

想到的唯一想法是:加载程序在将 DLL 加载到内存时调整了导出序号 table 中的序号。

谁能解释一下?

这是 PE/COFF 规范中的一个已知错误。指定的算法完全错误,应该是

ordinal = ExportOrdinalTable [i] + OrdinalBase;

没有

ordinal = ExportOrdinalTable [i];

因为序数 table 实际上包含 无偏 序数。