Well, today I ran into an oddity. I wrote my own version of GetProcAddress a while ago to get function addresses from remote processes. I obviously spent quite a lot of time reading up on the PE architecture to figure out the best way to approach this.
From the PECOFF v8 specification (which, as I take it is the most up-to-date official specification), there is the following notation about the Export Name Pointer Table
:
The export name pointer table is an array of addresses (RVAs) into the export name table. The pointers are 32 bits each and are relative to the image base. The pointers are ordered lexically to allow binary searches.
So I took this into account when writing my version of GetProcAddress. Obviously there would be a nice efficiency improvement in walking the export table in say...KERNEL32.dll (1300+ exported functions) using a binary search over a linear search.
This worked for a while until today when I ran into a weird issue. It appears that some of the exported functions in Kernel32 aren't actually ordered lexically and this was throwing off my binary search. The following is an excerpt from a Exported Dll dump using the function I'll post below:
Ordinal: 810 Name: K32QueryWorkingSetEx
Ordinal: 811 Name: LCIDToLocaleName
Ordinal: 812 Name: LCMapStringA
Ordinal: 813 Name: LCMapStringEx
Ordinal: 814 Name: LCMapStringW
Ordinal: 815 Name: LZClose
Ordinal: 816 Name: LZCloseFile
Ordinal: 817 Name: LZCopy
Ordinal: 818 Name: LZCreateFileW
Ordinal: 819 Name: LZDone
Ordinal: 820 Name: LZInit
Ordinal: 821 Name: LZOpenFileA
Ordinal: 822 Name: LZOpenFileW
Ordinal: 823 Name: LZRead
Ordinal: 824 Name: LZSeek
Ordinal: 825 Name: LZStart
Ordinal: 826 Name: LeaveCriticalSection
Ordinal: 827 Name: LeaveCriticalSectionWhenCallbackReturns
Ordinal: 828 Name: LoadAppInitDlls
Ordinal: 829 Name: LoadLibraryA
Ordinal: 830 Name: LoadLibraryExA
Anyone spot the issue here? Despite the documentation claiming that the export table is ordered lexically, LZRead is listed before LeaveCriticalSection.
I've always taken lexical ordering to be synonymous with alphabetical ordering when dealing with strings, am I wrong here or is there some weird issue with Kernel32's export table?
Function used to dump exports:
void DumpExports(PBYTE pBase)
{
freopen("B:\\PeDump.txt", "wb", stdout);
IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)pBase;
IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)(pBase + pDosHd->e_lfanew);
IMAGE_DATA_DIRECTORY expDir = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (expDir.Size)
{
IMAGE_EXPORT_DIRECTORY *pExpDir = (IMAGE_EXPORT_DIRECTORY*)(pBase + expDir.VirtualAddress);
WORD *pOrds = (WORD*)(pBase + pExpDir->AddressOfNameOrdinals);
DWORD *pNames = (DWORD*)(pBase + pExpDir->AddressOfNames);
for(unsigned long i = 0; i < pExpDir->NumberOfNames; i++, pOrds++, pNames++)
printf("Ordinal: %d\tName: %s\n", *pOrds, (char*)(pBase + *pNames));
}
else
{
printf("No functions are exported from this image.\n");
}
fflush(stdout);
freopen("CON", "w", stdout);
}
EDIT: I'm an idiot. Of course 'Z' is before 'o', It's 3am and my brain isn't functioning. Very sorry.
EDIT EDIT: Okay, I'm not totally insane. Half of the problem was that apparently C#'s string.CompareTo extension doesn't compare lexically.
For example
"LoadLibraryW".CompareTo("LZRead");
Returns "-1". This was the source of my confusion.
LZRead
is lexicographically before LeaveCriticalSection
using ascii. Don't use case insensitivity and it looks like it will work.
An interesting observation about the documentation.
The pointers are 32 bits each ... are ordered lexically to allow binary searches.
It is hard to understand why one would do a binary search for a pointer (rather than a symbol name) but that is what is says.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With