2

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?

Erlend Graff
  • 1,098
  • 16
  • 27
Shuzheng
  • 11,288
  • 20
  • 88
  • 186
  • But what about the algorithm in the PE documentation for looking up the symbol RVA? This algorithm is incorrect then? It uses the ordinals as part of the RVA lookup. Also, what are the numbers in the ordinal table if not proper ordinals (corresponding to those seen using dumpbin)? – Shuzheng Oct 12 '16 at 13:56
  • Also, you say entry 0 is ordinal 4, but why does it display 3 then? – Shuzheng Oct 12 '16 at 14:02
  • @HansPassant : Also, why is 1 (OrdinalBase) subtracted from each ordinal? If you could answer these questions, you'd make Christmas come early this year :) – Shuzheng Oct 12 '16 at 14:08

1 Answers1

3

This is a known error in the PE/COFF specification. The algorithm specified is plain wrong, and it should be

ordinal = ExportOrdinalTable [i] + OrdinalBase;

not

ordinal = ExportOrdinalTable [i];

as the ordinal table actually contains unbiased ordinals.

Erlend Graff
  • 1,098
  • 16
  • 27
  • Thank you, do you have a source or just personal experience? – Shuzheng Oct 12 '16 at 15:30
  • 1
    Experience from writing a PE parser and loader for a previous project :) – Erlend Graff Oct 12 '16 at 16:13
  • I have come to think about the following: The algorithm works for inspecting the DLL statically? It seems that loading the DLL into memory changes ordinals, e.g. changes from 348 (verified using DUMPBIN) to 347 (verified at run-time). Your identity seems absurd: ordinal = ExportOrdinalTable [i] + OrdinalBase; - Why add the OrdinalBase just to subtract it immediately afterwards? – Shuzheng Oct 12 '16 at 16:18
  • Actually, DUMPBIN outputs biased ordinals, not the unbiased ones stored in the ordinal table (I had to revisit some of my PE parsing code to verify this). In other words, the loader does *not* change the ordinals. In the case of ExitProcess, the value stored in the table is 347, and the "actual" (biased) ordinal is 248. – Erlend Graff Oct 12 '16 at 20:22
  • W.r.t. the ordinal identity, it is a matter of semantics. Of course, you could just do `SymbolRVA = ExportAddressTable[ExportOrdinalTable[i]]` (I do agree that adding OrdinalBase, and then subtracting it looks a bit stupid) - I just wanted to point out where in the documentation the actual error was, as the *real* ordinal value is the *biased* one (after adding OrdinalBase). I.e., it does not make sense to assign the variable `ordinal = ExportOrdinalTable[i]`, as this "unbiased ordinal" is actually just an index. – Erlend Graff Oct 12 '16 at 20:28
  • Thank you, I just think it is wierd that Microsoft doesn't fix such bug. Developers rely on it, and the documentation has had several revisions. – Shuzheng Oct 13 '16 at 06:33
  • Also, the documentation actually defines an ordinal to be an index into export address table. – Shuzheng Oct 13 '16 at 06:36
  • To quote the documentation: "Other image files can import a symbol by using an index to this table (an ordinal) or, optionally, by using the public name that corresponds to the ordinal if a public name is defined." - so an ordinal is an index... – Shuzheng Oct 13 '16 at 06:47
  • I agree that it is confusing, as the documentation is not consistent in its definition or use of the term "ordinal" (for example, it also says "Every exported symbol has an ordinal value, which is just the index into the export address table (plus the Ordinal Base value)"). Hence, I prefer to clarify by using the terms "unbiased ordinal" and "biased ordinal" instead. However, when just using the term "ordinal", it is more correct to let this be synonymous with "biased ordinal", as this is what's used when importing by ordinal (and displayed by utilities such as DUMPBIN). – Erlend Graff Oct 13 '16 at 18:52
  • @Shuzheng: please accept my answer if you found it useful. – Erlend Graff Oct 29 '16 at 01:13