Log in

View Full Version : Weird export forwarding thanks to Vista x64 SP1


TiGa
March 30th, 2008, 21:12
After installing the SP1 for Vista x64, I noticed that ImpREC stopped working properly on some files using DefWindowProcA and DefWindowProcW from user32.dll.
These 2 APIs are forwarded as usual respectively to NtdllDefWindowProc_A and NtdllDefWindowProc_W from ntdll.dll but cannot be "unforwarded" back to user32.dll using the traditional method.

I'll explain how the loader usually resolves forwarded exports, the unforwarding method used by ImpREC and why it fails on those 2 particular cases.

Using the information provided by the Import Directory of the executable, the loader looks for a matching name or ordinal in the specified dll.
After a match is found, the corresponding entry from the AddressOfFunctions array is retrieved from the Export Directory and augmented by the ImageBase of the dll.
That value is then written to the IAT.

Sometimes, for compatibility reasons, an import can be forwarded to another one from a different module.
In that case, the AddressOfFunctions entry does not lead to code but to an ASCII string composed of the module name and the import name or ordinal.
Code:
.text:7DCA751E ; LRESULT __stdcall DefWindowProcA(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
.text:7DCA751E DefWindowProcA db 'NTDLL.NtdllDefWindowProc_A',0
.text:7DCA7539 ; Exported entry 151. DefWindowProcW
.text:7DCA7539 public DefWindowProcW
.text:7DCA7539 ; LRESULT __stdcall DefWindowProcW(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
.text:7DCA7539 DefWindowProcW db 'NTDLL.NtdllDefWindowProc_W',0

The loader then restarts the whole process from the beginning, using the info from the forwarding string rather than the original info from the Import Directory of the executable.
The Entry Point of the newly-found function is written to the IAT instead.

ImpREC finds the IAT and goes through it to identify all the imports that it contains.
Some entries belong to ntdll.dll and need to be "unforwarded" to their original location.
DefWindowProcA should be unforwarded from NtdllDefWindowProc_A.

http://img510.imageshack.us/img510/4355/presp1zr0.th.png
http://img510.imageshack.us/my.php?image=presp1zr0.png

If everything goes well, the original import is found by "bruteforcing" the Entry Point of every function until a forwarding string is found: NTDLL.DefWindowProc_A at the Entry Point of DefWindowProcA in user32.dll and then comes in some guesswork.

Because an import could have been forwarded from another module, doesn't mean that it really was, there are some false-positives.
The guesswork is based on a very crude probability analysis based on the module name of the previous import and the module name of the next import.
EndDialog from user32.dll is almost never forwarded from shlwapi32.dll.

Something changed with Vista x64 SP1 through some modifications of wow64.dll since the content of the \SysWOW64 directory should be the same as the standard Vista 32-bit SP1.
Some hotfix is applied only during run-time by WoW64.

http://img177.imageshack.us/img177/3155/postsp11vz2.th.png
http://img177.imageshack.us/my.php?image=postsp11vz2.png

http://img177.imageshack.us/img177/3144/postsp12vf6.th.png
http://img177.imageshack.us/my.php?image=postsp12vf6.png

During execution, the AddressOfFunctions entries of DefWindowProcA and DefWindowProcW from user32.dll are modified.
The RVA based at the ImageBase becomes greater than the SizeOfImage and leads into the memory area of ntdll.dll rather than the usual forwarding string.
Instead of 0x0001751E, the AddressOfFunctions becomes 0x015C3D42 or 0x01793D42 or 0x01B83D42 for DefWindowProcA.

The same result is achieved but this method prevents the unforwarding of some imports through the traditional method since there is no forwarding string anymore.
It leads into code now:
Code:
ntdll.dll:77C43D42 ntdll_NtdllDefWindowProc_A:
ntdll.dll:77C43D42 jmp dsff_77CB6020
...
ntdll.dll:77CB6020 off_77CB6020 dd offset loc_7669C0E7 ; DATA XREF: ntdll.dll:ntdll_NtdllDefWindowProc_Ar

And jumps back to user32.dll:
Code:
user32.dll:7669C0E7 loc_7669C0E7: ; DATA XREF: ntdll.dllff_77CB6020o
user32.dll:7669C0E7 push 10h
user32.dll:7669C0E9 push offset unk_7669C158
user32.dll:7669C0EE call near ptr unk_766BC240
user32.dll:7669C0F3 call near ptr unk_766980D7
...

Unforwarding is still possible anyway since a side-effect could be identified: the Entry Point of both imports become identical.
GetProcAddress(DefWindowProcA) == GetProcAddress(NtdllDefWindowProc_A)
GetProcAddress(DefWindowProcW) == GetProcAddress(NtdllDefWindowProc_W)

To sum everything up:
It doesn't really matter, the average user of Vista x64 SP1 would never notice the difference.
Unpacking in a 32-bit VM instead is still more reliable than under WoW64.

This will be a part of my proposed upcoming ReCon talk.

dELTA
March 31st, 2008, 03:42
Nice to see your finding becoming an informative blog post. Maybe an ImpREC plugin that handles this situation would add an extra spice to your RECon talk?

Nacho_dj
March 31st, 2008, 07:16
Quote:
[Originally Posted by TiGa;73713]
The guesswork is based on a very crude probability analysis based on the module name of the previous import and the module name of the next import.

But remember that this trick won't work in a shuffled IAT, like some Armadillo ones...

Anyway, nice work, mate

Good luck at your congress.

Cheers

Nacho_dj

TiGa
April 1st, 2008, 04:01
Maybe "guessing" was not the best possible choice of word.
I'd say "predict the most probable outcome" but it sounds like guessing again.
"Analysing the context" sounds less random.
But Nacho_dj is right anyway.

Sometimes there is no context:
Code:
<dword 0x0 separator>
EndDialog
ptr:blah
<dword 0x0 separator>


It could either be forwarded from shlwapi or really be EndDialog.
Flip a coin or do nothing.
That's what ImpREC does in that case, it does nothing.

@Delta: Check your PM box
On Vista x64, ImpREC already holds together with duct tape and ty-wraps.
Plugins and AutoTrace crash miserably 9 times out of 10.

TiGa