Looking at kernel32.dll as it is loaded into memory, I see the following export ordinal 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
As can be verified, an ordinal of 0 is exported.
But given that the OrdinalBase field of the export directory table is set to 1, how can an ordinal be less than 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 documentation says that the ordinals are biased, i.e.:
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.
Now, this implies that an ordinal of 0 gives rise to an index of -1 into the export address table?
From my point of view, it seems like the ordinals are pre-adjusted (i.e. 1 is subtracted from each), but then the "official" algorithm (also stated in the PE-docs) fails:
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];
The only idea that comes to mind is the following: The loader has adjusted the ordinals within the export ordinal table when it loaded the DLL into memory.
Can anyone give an explanation?