855 lines
26 KiB
C++
855 lines
26 KiB
C++
/*
|
|
* [Overview]
|
|
* The contents of this module allow the new TWAINDSM.DLL (tied to the 2.x version
|
|
* of the TWAIN Specifiction) to hook functions that might try to access the 1.x
|
|
* TWAIN_32.DLL. We need this because the DG_CONTROL/DAT_NULL/MSG_* triplets are
|
|
* sent from the data source (driver) to the application through the data source
|
|
* manager (DSM). The technique used was for a driver to issue a LoadLibrary or
|
|
* a GetModuleHandle, which would result in it attaching to the same TWAIN_32.DLL
|
|
* first loaded by the application. We have to force the driver to attach to the
|
|
* new TWAINDSM.DLL instead.
|
|
*
|
|
*
|
|
* [Diagram]
|
|
* This is the problem we have...
|
|
* +-----+ +--------------+ +--------------+
|
|
* | app | <-------> | TWAINDSM.DLL | --------> | |
|
|
* +-----+ +--------------+ | |
|
|
* | driver (1.x) |
|
|
* +--------------+ | |
|
|
* error <----- | TWAIN_32.DLL | <-------- | |
|
|
* +--------------+ +--------------+
|
|
*
|
|
* By hooking the appropriate function, we change the driver's request to get
|
|
* the DSM_Entry function from TWAIN_32.DLL into the DSM_Entry function from
|
|
* DSM_ TWAINDSM.DLL
|
|
* +-----+ +--------------+ +--------------+
|
|
* | app | <-------> | TWAINDSM.DLL | <-------> | |
|
|
* +-----+ +--------------+ | |
|
|
* | driver (1.x) |
|
|
* +--------------+ | |
|
|
* | TWAIN_32.DLL | | |
|
|
* +--------------+ +--------------+
|
|
*
|
|
* Note that TWAIN_32.DLL is still in the picture. This is intentional,
|
|
* because we're (indirectly) hooking GetProcAddress. The driver still
|
|
* does a LoadLibrary or GetModuleHandle on TWAIN_32.DLL. This design
|
|
* was selected because it results in the smallest possible hook code,
|
|
* and makes the smallest possible change to the 1.x driver. Everything
|
|
* runs the same as a TWAIN_32.DLL session, we just return a pointer to
|
|
* a different DSM_Entry function.
|
|
*
|
|
*
|
|
* [Hooked Functions]
|
|
* This is the ntdll.dll function we're hooking from kernel32.dll...
|
|
* LdrGetProcedureAddress (or LdrGetProcedureAddressForCaller)
|
|
* This gives us full coverage for the following function...
|
|
* GetProcAddress
|
|
*
|
|
*
|
|
* [Solution]
|
|
* The hook code is derived from the BugSlayer HookImportedFunctionByName function
|
|
* described in the August 1998 MSJ. The code has been considerably simplified to
|
|
* meet the limited needs of the DSM.
|
|
*
|
|
*
|
|
* [Risks]
|
|
* 32-bit Windows applications using TWAINDSM.DLL will never be able to access
|
|
* the TWAIN_32.DLL DSM_Entry function. Since this is by design, it's not so
|
|
* much a risk as "the plan", but it still deserves to be mentioned.
|
|
*
|
|
* We're not fully loading the TWAIN_32.DLL, because we don't have control over
|
|
* it, and we can't be sure what it's doing in DllMain. This could be a problem
|
|
* with a very badly behaved application. The scenerio is so convoluted, though,
|
|
* that it seems a good risk to keep TWAIN_32.DLL as uninvolved as possible.
|
|
*
|
|
* The application will crash if it does a FreeLibrary() on the DSM without first
|
|
* doing all the necessary MSG_CLOSEDS and MSG_CLOSEDSM calls. This can be
|
|
* mitigated by hooking LdrUnloadDll() and watching for an attempt to unload the
|
|
* DSM. But this adds complexity that rewards extremely bad coding behavior, so
|
|
* it's not going to be added unless we have a lot of bad actors we have to deal
|
|
* with.
|
|
*
|
|
* Hooks should be avoided whenever possible. Therefore the DSM code only installs
|
|
* the hook if the application attempts to DG_CONTROL/DAT_IDENTITY/MSG_OPENDS a 1.x
|
|
* driver on a 32-bit Windows system.
|
|
*
|
|
* This system is only designed to work with Window 2000 and higher, there is no
|
|
* intention of supporting either Windows NT or any of the Windows 9x platforms.
|
|
* Nor is there a reason to, since they don't have a file protection scheme (save for
|
|
* WinME, but hopefully nobody is still using that)...
|
|
*/
|
|
|
|
#include "dsm.h"
|
|
|
|
|
|
/**
|
|
* This entire file is only used for 32-bit Windows systems, so there is no need to
|
|
* compile it for anything else...
|
|
*/
|
|
#if TWNDSM_OS_64BIT
|
|
#pragma message( "hook code disabled for 64-bit builds..." )
|
|
#elif TWNDSM_OS_32BIT
|
|
#pragma message( "hook code enabled for 32-bit builds..." )
|
|
|
|
|
|
|
|
/**
|
|
* We use this to build pointers from the various data structures we
|
|
* have to navigate to set up the hooks...
|
|
*/
|
|
#define MakePtr(cast,ptr,AddValue) (cast)((DWORD_PTR)(ptr)+(DWORD_PTR)(AddValue))
|
|
|
|
|
|
/**
|
|
* Things we do with our Hook function...
|
|
*/
|
|
enum EHOOK
|
|
{
|
|
HOOK_ATTACH = 0,
|
|
HOOK_DETACH = 1
|
|
};
|
|
|
|
|
|
/**
|
|
* Need this for our Ldr functions...
|
|
*/
|
|
typedef struct _ANSI_STRING {
|
|
USHORT Length;
|
|
USHORT MaximumLength;
|
|
PSTR Buffer;
|
|
} ANSI_STRING, *PANSI_STRING;
|
|
|
|
|
|
/**
|
|
* typedefs of our hooked functions, so we can cast them nice when we make
|
|
* our calls...
|
|
*/
|
|
typedef NTSYSAPI DWORD (NTAPI *LdrGetProcedureAddress_t)
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Oridinal,
|
|
__out PVOID *FunctionAddress
|
|
);
|
|
typedef NTSYSAPI DWORD (NTAPI *LdrGetProcedureAddressForCaller_t)
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Oridinal,
|
|
__out PVOID *FunctionAddress,
|
|
__in BOOL bValue,
|
|
__in PVOID *CallbackAddress
|
|
);
|
|
|
|
/**
|
|
* Forward declarations for our functions, so we can build our m_proc table...
|
|
*/
|
|
DWORD NTAPI LocalLdrGetProcedureAddress
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Oridinal,
|
|
__out PVOID *FunctionAddress
|
|
);
|
|
DWORD NTAPI LocalLdrGetProcedureAddressForCaller
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Oridinal,
|
|
__out PVOID *FunctionAddress,
|
|
__in BOOL bValue,
|
|
__in PVOID *CallbackAddress
|
|
);
|
|
|
|
/**
|
|
* The entry point we want to return to the caller, instead of the one they
|
|
* thought they were getting from TWAIN_32.DLL...
|
|
*/
|
|
extern DSMENTRY DSM_Entry
|
|
(
|
|
TW_IDENTITY *_pOrigin,
|
|
TW_IDENTITY *_pDest,
|
|
TW_UINT32 _DG,
|
|
TW_UINT16 _DAT,
|
|
TW_UINT16 _MSG,
|
|
TW_MEMREF _pData
|
|
);
|
|
|
|
|
|
/**
|
|
* The hook class...
|
|
*/
|
|
class CTwHook
|
|
{
|
|
public:
|
|
|
|
// Initialize our piece-of-data...
|
|
CTwHook()
|
|
{
|
|
memset(&pod,0,sizeof(pod));
|
|
}
|
|
|
|
// The destructor cleans us all up (but Hook does the real work)...
|
|
~CTwHook()
|
|
{
|
|
Hook(HOOK_DETACH);
|
|
memset(&pod,0,sizeof(pod));
|
|
}
|
|
|
|
// The workhorse, this is where the hooking fun really takes place...
|
|
bool Hook
|
|
(
|
|
EHOOK _ehook
|
|
);
|
|
|
|
// Determine if this DS ID has been hooked
|
|
bool DSID_Is_Hooked
|
|
(
|
|
TW_UINT32 DSID
|
|
);
|
|
|
|
// Add this DS ID as being hooked
|
|
void Hook_Add_DSID
|
|
(
|
|
TW_UINT32 DSID
|
|
);
|
|
|
|
// Remove this DS ID as being hooked
|
|
bool Hook_Remove_DSID
|
|
(
|
|
TW_UINT32 DSID
|
|
);
|
|
|
|
private:
|
|
|
|
// We use a pod so that we don't have to worry about initialization
|
|
// issues...
|
|
struct Pod
|
|
{
|
|
PROC pOriginal; // The original procedure we found
|
|
TW_UINT32 HookedDSs[MAX_NUM_DS];
|
|
} pod;
|
|
};
|
|
|
|
|
|
/**
|
|
* This is where we're keeping our stuff, it has global scope in this module so we can
|
|
* use it in our hook functions. Even though this is static, every attempt has been
|
|
* made to make it thread safe. We also have a reference counter, since the application
|
|
* may choose to load more than one 1.x driver...
|
|
*/
|
|
static int s_iHookCount = 0;
|
|
static CTwHook *s_ptwhook = (CTwHook*)NULL;
|
|
|
|
|
|
/**
|
|
* We start by loading these functions using GetProcAddress, since they are
|
|
* wacky NTDLL things that are undocumented. Later on we change them to the
|
|
* actual pointers to the Ldr functions, and this is what allows us to be
|
|
* used safely, no matter what the state of the CTwHook object is in...
|
|
*/
|
|
static LdrGetProcedureAddress_t OriginalLdrGetProcedureAddress = 0;
|
|
static LdrGetProcedureAddressForCaller_t OriginalLdrGetProcedureAddressForCaller = 0;
|
|
|
|
|
|
/**
|
|
* A static value for the TWAIN_32.DLL we load, which provides us a very
|
|
* simple way to see if someone is working with this DLL...
|
|
*/
|
|
static HMODULE s_hmoduleTWAIN32 = 0;
|
|
|
|
/**
|
|
* A static value for the DSMEntry of the TWAIN_32.DLL we load,
|
|
* which provides us a way to call the original TWAIN_32 if we
|
|
* did not want to hook this data souce.
|
|
*/
|
|
static DSMENTRYPROC TWAIN32_DSMEntry = 0;
|
|
|
|
|
|
|
|
/**
|
|
* The entry point we want to return to the caller, instead of the one they
|
|
* thought they were getting from TWAIN_32.DLL...
|
|
*/
|
|
DSMENTRY DSM_HookedEntry
|
|
(
|
|
TW_IDENTITY *_pOrigin,
|
|
TW_IDENTITY *_pDest,
|
|
TW_UINT32 _DG,
|
|
TW_UINT16 _DAT,
|
|
TW_UINT16 _MSG,
|
|
TW_MEMREF _pData
|
|
)
|
|
{
|
|
if ( ((_DAT == DAT_NULL)
|
|
|| (_DAT == DAT_CALLBACK && _MSG == MSG_INVOKE_CALLBACK))
|
|
&& _pOrigin
|
|
&& s_ptwhook
|
|
&& s_ptwhook->DSID_Is_Hooked(_pOrigin->Id))
|
|
{
|
|
return (DSM_Entry(_pOrigin,_pDest,_DG,_DAT,_MSG,_pData));
|
|
}
|
|
|
|
return (TWAIN32_DSMEntry(_pOrigin,_pDest,_DG,_DAT,_MSG,_pData));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Derived from John Robbins' BugSlayer code in MSJ (so the 'I' in the comments in this
|
|
* function refers to John), this function hooks or unhooks the stuff we want to examine.
|
|
* Note that this code is optimized to hook the Ldr functions in ntdll.dll. If you want
|
|
* to hook something else, especially if it's in a different DLL, you're going to have
|
|
* some work to do...
|
|
* @param[in] EHOOK _ehook we're hooking or unhooking
|
|
* @return true or false
|
|
*/
|
|
bool CTwHook::Hook
|
|
(
|
|
EHOOK _ehook
|
|
)
|
|
{
|
|
UINT uResult;
|
|
BOOL boolResult;
|
|
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
|
|
PIMAGE_THUNK_DATA pOrigThunk;
|
|
PIMAGE_THUNK_DATA pRealThunk;
|
|
PIMAGE_IMPORT_BY_NAME pByName;
|
|
bool bDoHook;
|
|
MEMORY_BASIC_INFORMATION mbi_thunk;
|
|
DWORD_PTR *pTemp;
|
|
DWORD dwOldProtect;
|
|
PIMAGE_DOS_HEADER pDOSHeader;
|
|
PIMAGE_NT_HEADERS pNTHeader;
|
|
PSTR szCurrMod;
|
|
HMODULE hmodule;
|
|
char szTwain32[MAX_PATH];
|
|
const char *szFunctionName;
|
|
|
|
// Initialize stuff when we're doing an attach...
|
|
if (_ehook == HOOK_ATTACH)
|
|
{
|
|
// If we don't find TWAIN_32.DLL, then we're not going to
|
|
// make life any better by doing the hooks, so bail. Also,
|
|
// We're not going to allow any flavor of GetProcAddress
|
|
// to access this library, which is why we're using the
|
|
// extra flag, to keep TWAIN_32.DLL from loading anything
|
|
// other than itself.
|
|
//
|
|
// BUG ALERT
|
|
// ~~~~~~~~~
|
|
// There is a potential bug here, but one that depends
|
|
// on really bad behavior. If the application loads
|
|
// TWAINDSM.DLL, then loads TWAIN_32.DLL, then unloads
|
|
// TWAINDSM.DLL, then does a GetProcAddress for DSM_Entry
|
|
// with TWAIN_32.DLL, it's going to go ka-boom. If this
|
|
// is a problem, then go back to using ::LoadLibrary()...
|
|
memset(szTwain32,0,sizeof(szTwain32));
|
|
uResult = ::GetWindowsDirectory(szTwain32,sizeof(szTwain32)-1);
|
|
if (!uResult)
|
|
{
|
|
return(false);
|
|
}
|
|
SSTRCAT(szTwain32,sizeof(szTwain32)-1,"\\TWAIN_32.DLL");
|
|
s_hmoduleTWAIN32 = ::LoadLibraryEx(szTwain32,NULL,DONT_RESOLVE_DLL_REFERENCES);
|
|
if (0 == s_hmoduleTWAIN32)
|
|
{
|
|
return(false);
|
|
}
|
|
|
|
// Load the undocumented routine, we're going for both, if we find
|
|
// LdrGetProcedureAddressForCaller, then we'll prefer it over LdrGetProcedureAddress...
|
|
szFunctionName = "";
|
|
OriginalLdrGetProcedureAddress = 0;
|
|
OriginalLdrGetProcedureAddressForCaller = 0;
|
|
hmodule = GetModuleHandle("ntdll.dll");
|
|
if (hmodule)
|
|
{
|
|
szFunctionName = "LdrGetProcedureAddressForCaller";
|
|
OriginalLdrGetProcedureAddressForCaller = (LdrGetProcedureAddressForCaller_t)GetProcAddress(hmodule,szFunctionName);
|
|
if (!OriginalLdrGetProcedureAddressForCaller)
|
|
{
|
|
szFunctionName = "LdrGetProcedureAddress";
|
|
OriginalLdrGetProcedureAddress = (LdrGetProcedureAddress_t)GetProcAddress(hmodule,szFunctionName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're detaching, then make sure our reference to TWAIN_32.DLL is gone...
|
|
else
|
|
{
|
|
if (s_hmoduleTWAIN32)
|
|
{
|
|
::FreeLibrary(s_hmoduleTWAIN32);
|
|
s_hmoduleTWAIN32 = 0;
|
|
}
|
|
|
|
// If we don't have an old pointer, then we're done...
|
|
if (NULL == pod.pOriginal)
|
|
{
|
|
return (true);
|
|
}
|
|
|
|
// Pick the name...
|
|
if (OriginalLdrGetProcedureAddressForCaller)
|
|
{
|
|
szFunctionName = "LdrGetProcedureAddressForCaller";
|
|
}
|
|
else
|
|
{
|
|
szFunctionName = "LdrGetProcedureAddress";
|
|
}
|
|
}
|
|
|
|
// This is where we'll be hooking into Ldr functions, the calls
|
|
// themselves are in ntdll.dll. Kernel32 calls them, and that's
|
|
// where we can take advantage of the DLL indirection to do this
|
|
// spiffy DLL injection thingy...
|
|
// Starting with Windows7 the hooking has moved from kernel32.dll
|
|
// to a new library kernelbase.dll. Atempt kernelbase first
|
|
// if it fails then we are not Windows7 and try kernel32
|
|
hmodule = GetModuleHandle("kernelbase.dll");
|
|
if ( NULL == hmodule )
|
|
{
|
|
hmodule = GetModuleHandle("kernel32.dll");
|
|
}
|
|
|
|
// Get the DOS header...
|
|
pDOSHeader = (PIMAGE_DOS_HEADER)hmodule;
|
|
|
|
// Is this the MZ header?
|
|
if ( !pDOSHeader
|
|
|| (TRUE == IsBadReadPtr(pDOSHeader,sizeof(IMAGE_DOS_HEADER)))
|
|
|| (IMAGE_DOS_SIGNATURE != pDOSHeader->e_magic))
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
// Get the PE header.
|
|
pNTHeader = MakePtr(PIMAGE_NT_HEADERS,pDOSHeader,pDOSHeader->e_lfanew);
|
|
|
|
// Is this a real PE image?
|
|
if ( (TRUE == IsBadReadPtr(pNTHeader,sizeof(IMAGE_NT_HEADERS)))
|
|
|| (IMAGE_NT_SIGNATURE != pNTHeader->Signature))
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
// If there is no imports section, leave now.
|
|
if (0 == pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
// Get the pointer to the imports section.
|
|
pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR,pDOSHeader,pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
|
|
|
// Loop through the import module descriptors looking for the
|
|
// ntdll.dll module, which is where the Ldr functions live...
|
|
while (NULL != pImportDesc->Name)
|
|
{
|
|
szCurrMod = MakePtr(PSTR,pDOSHeader,pImportDesc->Name);
|
|
if (0 == _stricmp(szCurrMod,"ntdll.dll"))
|
|
{
|
|
// Found it.
|
|
break;
|
|
}
|
|
// Look at the next one.
|
|
pImportDesc++ ;
|
|
}
|
|
|
|
// If the name is NULL, then the module is not imported.
|
|
if (NULL == pImportDesc->Name)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
// Get the original thunk information for this DLL. I can't use
|
|
// the thunk information stored in pImportDesc->FirstThunk
|
|
// because the loader has already changed that array to fix up
|
|
// all the imports. The original thunk gives me access to the
|
|
// function names.
|
|
pOrigThunk = MakePtr(PIMAGE_THUNK_DATA,hmodule,pImportDesc->OriginalFirstThunk);
|
|
|
|
// Get the array the pImportDesc->FirstThunk points to because
|
|
// I'll do the actual bashing and hooking there.
|
|
pRealThunk = MakePtr(PIMAGE_THUNK_DATA,hmodule,pImportDesc->FirstThunk);
|
|
|
|
// Determines whether I hook the function
|
|
bDoHook = false;
|
|
|
|
// Loop through and find the function to hook.
|
|
while (NULL != pOrigThunk->u1.Function)
|
|
{
|
|
// Look only at functions that are imported by name, not those
|
|
// that are imported by ordinal value.
|
|
if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))
|
|
{
|
|
// Look at the name of this imported function.
|
|
pByName = MakePtr(PIMAGE_IMPORT_BY_NAME,hmodule,pOrigThunk->u1.AddressOfData);
|
|
|
|
// We found it, so scoot...
|
|
if ( pByName->Name
|
|
&& ('\0' != pByName->Name[0])
|
|
&& (0 == _stricmp(szFunctionName,(char*)pByName->Name)))
|
|
{
|
|
bDoHook = true;
|
|
break;
|
|
}
|
|
|
|
// Increment both tables, and continue...
|
|
pOrigThunk++;
|
|
pRealThunk++;
|
|
}
|
|
}
|
|
|
|
// If we found something, then hook or unhook it, as appropriate...
|
|
if (true == bDoHook)
|
|
{
|
|
// I found a function to hook. Now I need to change
|
|
// the memory protection to writable before I overwrite
|
|
// the function pointer. Note that I'm now writing into
|
|
// the real thunk area!
|
|
VirtualQuery(pRealThunk,&mbi_thunk,sizeof(MEMORY_BASIC_INFORMATION));
|
|
if (FALSE == VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,PAGE_EXECUTE_READWRITE,&mbi_thunk.Protect))
|
|
{
|
|
return(false);
|
|
}
|
|
|
|
// Save the original address, if we're hooking...
|
|
if (_ehook == HOOK_ATTACH)
|
|
{
|
|
// This cast should make the compiler happy about the change in the word size...
|
|
pod.pOriginal = (PROC)(INT_PTR)pRealThunk->u1.Function;
|
|
if (OriginalLdrGetProcedureAddressForCaller)
|
|
{
|
|
OriginalLdrGetProcedureAddressForCaller = (LdrGetProcedureAddressForCaller_t)pod.pOriginal;
|
|
}
|
|
else
|
|
{
|
|
OriginalLdrGetProcedureAddress = (LdrGetProcedureAddress_t)pod.pOriginal;
|
|
}
|
|
}
|
|
|
|
// Microsoft has two different definitions of the
|
|
// PIMAGE_THUNK_DATA fields as they are moving to
|
|
// support Win64. The W2K RC2 Platform SDK is the
|
|
// latest header, so I'll use that one and force the
|
|
// Visual C++ 6 Service Pack 3 headers to deal with it.
|
|
|
|
// Hook the new function if we're hooking, or the original function
|
|
// if we're closing...
|
|
if (_ehook == HOOK_ATTACH)
|
|
{
|
|
pTemp = (DWORD_PTR*)&pRealThunk->u1.Function;
|
|
if (OriginalLdrGetProcedureAddressForCaller)
|
|
{
|
|
*pTemp = (DWORD_PTR)LocalLdrGetProcedureAddressForCaller;
|
|
}
|
|
else
|
|
{
|
|
*pTemp = (DWORD_PTR)LocalLdrGetProcedureAddress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTemp = (DWORD_PTR*)&pRealThunk->u1.Function;
|
|
*pTemp = (DWORD_PTR)(pod.pOriginal);
|
|
}
|
|
|
|
// Change the protection back to what it was before I
|
|
// overwrote the function pointer.
|
|
boolResult = VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,mbi_thunk.Protect,&dwOldProtect);
|
|
if (boolResult == FALSE)
|
|
{
|
|
// Okay, this isn't good, but we're not going to do
|
|
// anything about it, because presumably this isn't
|
|
// the end of the world...
|
|
}
|
|
}
|
|
|
|
// All done...
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
* Determine if the DS has been hooked by checking the ID
|
|
* @param[in] DSID The DS ID to check to see if it has been hooked
|
|
* @return true or false
|
|
*/
|
|
bool CTwHook::DSID_Is_Hooked(TW_UINT32 DSID)
|
|
{
|
|
int count = min(MAX_NUM_DS,s_iHookCount);
|
|
for (int i=0; i<count; i++)
|
|
{
|
|
if (pod.HookedDSs[i] == DSID)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
/**
|
|
* Add this DS to the list of being hooked
|
|
* @param[in] DSID The DS ID to add to the list
|
|
* @note We allow an application to open the DSM several times and each
|
|
* application to open several DSs. (The application must use a different
|
|
* name each time it loads the DSM). It is possible for a DS to be opened
|
|
* several times by different applications. (Although most DSs only support
|
|
* one connection.) Therefore we need to allow multiple instances of the
|
|
* same DSID. No duplicate check needed.
|
|
*/
|
|
void CTwHook::Hook_Add_DSID(TW_UINT32 DSID)
|
|
{
|
|
pod.HookedDSs[s_iHookCount] = DSID;
|
|
s_iHookCount++;
|
|
}
|
|
|
|
/**
|
|
* Remove this DS from the list of being hooked
|
|
* @param[in] DSID The DS ID to remove from the list
|
|
* @return true or false
|
|
*/
|
|
bool CTwHook::Hook_Remove_DSID(TW_UINT32 DSID)
|
|
{
|
|
int count = min(MAX_NUM_DS, s_iHookCount);
|
|
|
|
for(int i=0; i<count; i++)
|
|
{
|
|
if(pod.HookedDSs[i] == DSID)
|
|
{
|
|
while(i+1<count)
|
|
{
|
|
pod.HookedDSs[i] = pod.HookedDSs[i+1];
|
|
}
|
|
pod.HookedDSs[i] = 0;
|
|
s_iHookCount--;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Should never get here. It means we are trying to
|
|
// remove a DS that was never hooked.
|
|
kLOG((kLOGERR,"Trying to removing a hook for a DSID (%d) that was never added.", DSID ));
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* The undocumented LdrGetProcedureAddress...
|
|
* @param[in] PHMODULE ModuleHandle we're using to get a function pointer
|
|
* @param[in_opt] PANSI_STRING FunctionName of thing we're trying to find
|
|
* @param[in_opt] WORD Ordinal, or the number of the things we're trying to find
|
|
* @param[out] PVOID *FunctionAddress being sent back to the caller
|
|
* @return DWORD
|
|
*/
|
|
DWORD NTAPI LocalLdrGetProcedureAddress
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Ordinal,
|
|
__out PVOID *FunctionAddress
|
|
)
|
|
{
|
|
// See if the caller is asking for TWAIN_32.DLL, and if so, then dive in
|
|
// and return our DSM_Entry. This works because attempts to load the
|
|
// same DLL more than once just bump up the reference count. Or put
|
|
// another way, if function A does a LoadLibrary("xyz") and then function
|
|
// B does a LoadLibrary("xzy") the HMODULE values returned will be found
|
|
// to be the same.
|
|
//
|
|
// We don't have to check the FunctionName or the Ordinal, there's only
|
|
// one possible return from TWAIN_32.DLL (thank you initial designers),
|
|
// and I haven't throught of a good security reason why I need to bother
|
|
// checking...
|
|
if (ModuleHandle == s_hmoduleTWAIN32)
|
|
{
|
|
// Get and store the original address in case we need it
|
|
(OriginalLdrGetProcedureAddress(ModuleHandle,FunctionName,Ordinal,(PVOID*)&TWAIN32_DSMEntry));
|
|
// Return the address to our own function
|
|
*FunctionAddress = ::DSM_HookedEntry;
|
|
return (ERROR_SUCCESS);
|
|
}
|
|
|
|
// Otherwise let the call continue unmolested...
|
|
return (OriginalLdrGetProcedureAddress(ModuleHandle,FunctionName,Ordinal,FunctionAddress));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* The undocumented LdrGetProcedureAddressForCaller...
|
|
* @param[in] PHMODULE ModuleHandle we're using to get a function pointer
|
|
* @param[in_opt] PANSI_STRING FunctionName of thing we're trying to find
|
|
* @param[in_opt] WORD Ordinal, or the number of the things we're trying to find
|
|
* @param[out] PVOID *FunctionAddress being sent back to the caller
|
|
* @param[in] BOOL bValue
|
|
* @param[in] PVOID *CallbackAddress
|
|
* @return DWORD
|
|
*/
|
|
DWORD NTAPI LocalLdrGetProcedureAddressForCaller
|
|
(
|
|
__in HMODULE ModuleHandle,
|
|
__in_opt PANSI_STRING FunctionName,
|
|
__in_opt WORD Ordinal,
|
|
__out PVOID *FunctionAddress,
|
|
__in BOOL bValue,
|
|
__in PVOID *CallbackAddress
|
|
)
|
|
{
|
|
// See if the caller is asking for TWAIN_32.DLL, and if so, then dive in
|
|
// and return our DSM_Entry. This works because attempts to load the
|
|
// same DLL more than once just bump up the reference count. Or put
|
|
// another way, if function A does a LoadLibrary("xyz") and then function
|
|
// B does a LoadLibrary("xzy") the HMODULE values returned will be found
|
|
// to be the same.
|
|
//
|
|
// We don't have to check the FunctionName or the Ordinal, there's only
|
|
// one possible return from TWAIN_32.DLL (thank you initial designers),
|
|
// and I haven't throught of a good security reason why I need to bother
|
|
// checking...
|
|
if (ModuleHandle == s_hmoduleTWAIN32)
|
|
{
|
|
// Get and store the original address in case we need it
|
|
(OriginalLdrGetProcedureAddressForCaller(ModuleHandle,FunctionName,Ordinal,(PVOID*)&TWAIN32_DSMEntry,bValue,CallbackAddress));
|
|
// Return the address to our own function
|
|
*FunctionAddress = ::DSM_HookedEntry;
|
|
return (ERROR_SUCCESS);
|
|
}
|
|
|
|
// Otherwise let the call continue unmolested...
|
|
return (OriginalLdrGetProcedureAddressForCaller(ModuleHandle,FunctionName,Ordinal,FunctionAddress,bValue,CallbackAddress));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Install the hooks and load the library, if hooks are already installed,
|
|
* then just load the library...
|
|
*/
|
|
HMODULE InstallTwain32DllHooks
|
|
(
|
|
const char* const _lib,
|
|
const bool _hook,
|
|
const TW_UINT32 _DSID
|
|
)
|
|
{
|
|
HMODULE hmodule;
|
|
CTwHook *ptwhook;
|
|
DWORD dwResult;
|
|
|
|
// Init stuff...
|
|
ptwhook = (CTwHook*)NULL;
|
|
|
|
// We hook before we load the library so that we can intercept calls
|
|
// to the Ldr functions during DllMain. But we only do the hook if
|
|
// it's the first time we've been here, and we've been asked to do
|
|
// hooking (which we won't be if the DSM is doing GetFirst/GetNext to
|
|
// enumerate the drivers)...
|
|
if (_hook)
|
|
{
|
|
// If we already have a hook in place, then bump up our
|
|
// reference counter...
|
|
if ( (s_iHookCount > 0)
|
|
&& ((CTwHook*)NULL != s_ptwhook))
|
|
{
|
|
s_ptwhook->Hook_Add_DSID(_DSID);
|
|
}
|
|
|
|
// Otherwise load the beastie...
|
|
else
|
|
{
|
|
// Allocate our object...
|
|
s_iHookCount = 0;
|
|
ptwhook = new CTwHook();
|
|
if (ptwhook)
|
|
{
|
|
// Do the hook...
|
|
if (ptwhook->Hook(HOOK_ATTACH))
|
|
{
|
|
// This activates our hooking functions to look for
|
|
// attempts to get DSM_Entry...
|
|
s_ptwhook = ptwhook;
|
|
s_ptwhook->Hook_Add_DSID(_DSID);
|
|
}
|
|
// No joy, cleanup...
|
|
else
|
|
{
|
|
delete ptwhook;
|
|
ptwhook = (CTwHook*)NULL;
|
|
s_ptwhook = (CTwHook*)NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load the library the caller asked for...
|
|
hmodule = ::LoadLibrary(_lib);
|
|
|
|
// If we hooked for this module, and the LoadLibrary failed, then
|
|
// undo the hook...
|
|
if ( (NULL == hmodule)
|
|
&& ((CTwHook*)NULL != ptwhook)
|
|
&& _hook)
|
|
{
|
|
dwResult = ::GetLastError();
|
|
s_ptwhook->Hook_Remove_DSID(_DSID);
|
|
if (s_iHookCount <= 0)
|
|
{
|
|
s_ptwhook = (CTwHook*)NULL;
|
|
delete ptwhook;
|
|
s_iHookCount = 0;
|
|
}
|
|
::SetLastError(dwResult);
|
|
}
|
|
|
|
// All done...
|
|
return(hmodule);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Uninstall the hooks (if needed), and free the library, if hooks are not currently
|
|
* installed, then just free the library...
|
|
*/
|
|
BOOL UninstallTwain32DllHooks
|
|
(
|
|
const HMODULE _hmodule,
|
|
const bool _unhook,
|
|
const TW_UINT32 _DSID
|
|
)
|
|
{
|
|
if(_unhook)
|
|
{
|
|
if (s_ptwhook)
|
|
{
|
|
// Remove the DS from the list and decrement the count...
|
|
s_ptwhook->Hook_Remove_DSID(_DSID);
|
|
|
|
// If we're at zero, then cleanup. I'm probably being a bit
|
|
// paranoid about the cleanup scheme, but I like being paranoid
|
|
// when it comes to hooks...
|
|
if (s_iHookCount <= 0)
|
|
{
|
|
CTwHook *ptwhook = s_ptwhook;
|
|
s_ptwhook = (CTwHook*)NULL;
|
|
delete ptwhook;
|
|
s_iHookCount = 0;
|
|
}
|
|
}
|
|
}
|
|
// Free the library...
|
|
return(::FreeLibrary(_hmodule));
|
|
}
|
|
|
|
|
|
|
|
// TWDSM_OS_64BIT/TWNDSM_OS_32BIT
|
|
#else
|
|
#error error, we need to be either 32-bit or 64-bit...
|
|
#endif
|