Win32::API::Callback::IATPatch - Hooking and Patching a DLL's Imported C Functions |
Win32::API::Callback::IATPatch - Hooking and Patching a DLL's Imported C Functions
use Win32::API; use Win32::API::Callback; warn "usually fatally errors on Cygwin" if $^O eq 'cygwin'; # do not do a "use" or "require" on Win32::API::Callback::IATPatch # IATPatch comes with Win32::API::Callback my $LoadLibraryExA; my $callback = Win32::API::Callback->new( sub { my $libname = unpack('p', pack('J', $_[0])); print "got $libname\n"; return $LoadLibraryExA->Call($libname, $_[1], $_[2]); }, 'NNI', 'N' ); my $patch = Win32::API::Callback::IATPatch->new( $callback, "perl518.dll", 'kernel32.dll', 'LoadLibraryExA'); die "failed to create IATPatch Obj $^E" if ! defined $patch; $LoadLibraryExA = Win32::API::More->new( undef, $patch->GetOriginalFunctionPtr(), ' HMODULE WINAPI LoadLibraryExA( LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags ); '); die "failed to make old function object" if ! defined $LoadLibraryExA; require Encode; #console will get a print of the dll filename now
Win32::API::Callback::IATPatch allows you to hook a compile time dynamic linked
function call from any DLL (or EXE, from now on all examples are from a DLL to
another DLL, but from a EXE to a DLL is implied) in the Perl process, to a
different DLL in the same Perl process, by placing a Win32::API::Callback object
in between. This module does not hook GetProcAddress function calls. It
also will not hook a function call from a DLL to another function in the same
DLL. The function you want to hook must appear in the import table of the
DLL you want to use the hook. Functions from delay loaded DLL have their own
import table, it is different import table from the normal one IATPatch supports.
IATPatch will not find a delay loaded function and will not patch it. The hook
occurs at the caller DLL, not the callee DLL. This means your callback will be
called from all the calls to a one function in different DLL from the one
particular DLL the IATPatch object patched. The caller DLL is modified at
runtime, in the Perl process where the IATPatch was created, not on disk,
not globally among all processes. The callee or exporting DLL is NOT modified,
so your hook callback will be called from the 1 DLL that you choose to hook with
1 IATPatch object. You can create multiple IATPatch objects, one for each DLL in
the Perl process that you want to call your callback and not the original
destination function. If a new DLL is loaded into the process during runtime,
you must create a new IATPatch object specifically targeting it. There may be a
period from when the new DLL is loaded into the process, and when your Perl
script creates IATPatch object, where calls from that new DLL goto the real
destination function without hooking. If a DLL is unloaded, then reloaded into
the process, you must call Unpatch(0)
method on the old IATPatch object, then
create a new IATPatch object. IATPatch has no notification feature that a DLL
is being loaded or unloaded from the process. Unless you completely control, and
have the source code of the caller DLL, and understand all of the source code of
that DLL, there is a high chance that you will NOT hook all calls from that
DLL to the destination function. If a call to the destination function is
dangerous or unacceptable, do not use IATPatch. IATPatch is not Microsoft
Detours or the like in any sense. Detours and its brethern will rewrite the
machine code in the beginning of the destination function call, hooking all
calls to that function call process wide, without exception.
Why this module was created? So I could mock kernel32 functions to unit test Perl's C function calls to Kernel32.
my $patch = Win32::API::Callback::IATPatch->new( $A_Win32_API_Callback_Obj, $EXE_or_DLL_hmodule_or_name_to_hook, $import_DLL_name, $import_function_name_or_ordinal);
Creates a new IATPatch object. The Win32::API::Callback will be called as long
as the IATPatch object exists. When an IATPatch object is DESTROYed, unless
->Unpatch(0)
is called first, the patch is undone and the original
function is directly called from then on by that DLL. The DLL is not reference
count saved by an IATPatch object, so it may be unloaded at any time. If it is
unloaded you must call ->Unpatch(0)
before a DESTROY. Otherwise the DESTROY
will croak when it tries to unpatch the DLL. The DLL to hook must be a valid
PE file, while in memory. DLL and EXE ``packers'' can create invalid PE
files that do load successfully into memory, but they are not full PE files in
memory. On error, undef is returned and an error code is available through
Win32::GetLastError/$^E in the perlvar manpage. The error code may be from either
IATPatch directly, or from a Win32 call that IATPatch made. IATPatch objects
do not go through a fork in the perlfunc manpage onto the child interp. IATPatch is fork safe.
The hook dll name can be one of 3 things, if the dllname is multiple things
(a number and a string), the first format found in the following order is used.
A string "123"
(a very strange DLL name BTW), this DLL is converted to DLL
HMODULE with GetModuleHandle. If there are 2 DLLs with the same filename,
refer to GetModuleHandle's
MSDN documentation
on what happens. Then if the
DLL name is an integer 123456
, it is interpreted as a HMODULE directly.
If DLL name undefined, the file used to create the calling process will be
patched (a .exe). Finally if the DLL name is defined, a fatal error croak occurs.
It is best to use an HMODULE, since things like SxS can create multiple DLLs with
the same name in the same process. How to get an HMODULE, you are on your own.
$import_function_name_or_ordinal
can be one of 2 things. First it is checked if
it is a string, if so, it is used as the function name to hook. Else it is used
as an integer ordinal to hook. Importing by ordinal is obsolete in Windows, and
you shouldn't ever have to use it. The author of IATPatch was unable to test if
ordinal hooking works correctly in IATPatch.
die "failed to undo the patch error: $^E" if ! $IATPatch->Unpatch(); #undo the patch #or die "failed to undo the patch error: $^E" if ! $IATPatch->Unpatch(1); #undo the patch #or die "failed to undo the patch error: $^E" if ! $IATPatch->Unpatch(0); #never undo the patch #or die "failed to undo the patch error: $^E" if ! $IATPatch->Unpatch(undef); #never undo the patch
Unpatches the DLL with the original destination function from the new in the Win32::API::Callback::IATPatch manpage call. Returns undef on failure with error number available through Win32::GetLastError/$^E in the perlvar manpage. If Unpatch was called once already, calling it again will fail, and error will be ERROR_NO_MORE_ITEMS.
Returns the original function pointer found in the import table in 123456
format. If the returned pointer is 0, Unpatch in the Win32::API::Callback::IATPatch manpage
was called earlier. There are no error numbers associated with this method.
This value can be directly used to create a function pointer based Win32::API
object to call the original destination function from inside your Perl callback.
See SYNOPSIS in the Win32::API::Callback::IATPatch manpage for a usage example.
the Win32::API::Callback manpage
http://msdn.microsoft.com/en-us/magazine/cc301808.aspx
Daniel Dragan ( bulkdd@cpan.org ).
Copyright (C) 2012 by Daniel Dragan
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available.
Win32::API::Callback::IATPatch - Hooking and Patching a DLL's Imported C Functions |