Sign in to follow this  
  • entries
    557
  • comments
    1237
  • views
    420959

Untitled

Sign in to follow this  
Evil Steve

77 views

Well, I might as well spew out a large chunk of code. I've been writing an API hooking library, and I'm about halfway through. It works perfectly in x86, but I'd like it to work in x64 too.

Anyway, here we go. I may as well just dump it all here.

Header file (Just structs just now:

//============================================================================
// ProcessLib.h - Functions for doing various "things" to processes
//============================================================================

#ifndef __PROCESSLIB_H__
#define __PROCESSLIB_H__

#include
#include

//============================================================================
// Structures
//============================================================================
struct ImportedFunction
{
std::string strName; // Function name
size_t dwOrdinal; // Function ordinal; only valid if name is empty
size_t dwThunkAddress; // Address of the thunk instruction
size_t dwTargetAddress; // Address of the actual function (In the target DLL)
};

struct DLLImport
{
std::string strName; // DLL name
std::vector vFunctions; // Functions imported
};

struct HookedFunction
{
size_t dwOriginalThunkAddr; // Original import table thunk address in process
size_t dwOriginalThunk; // Original import table thunk value
void* pLoaderThunk; // Address of new loader code (From VirtualAllocEx)
};

#endif // __PROCESSLIB_H__



Source file:

//============================================================================
// ProcessLib.cpp - Main exports
//============================================================================
#define _WIN32_WINNT 0x0500
#include "ProcessLib.h"
#include
#include

#pragma warning (disable:4312) // 'type cast' : conversion from 'DWORD' to 'void *' of greater size
#pragma warning (disable:4311) // 'type cast' : 'type cast' : pointer truncation

//============================================================================
// DLL loader function, copied into target process address space
// Loads szDLLName, gets the address of szFuncName, and jumps to it
// szDLLName and szFuncName are resolved at function call time, and the values
// are inserted into the loader code.
//============================================================================
#define LOADLIB_TOKEN 0x01010101 // LoadLibrary() address
#define GETPROCADDRESS_TOKEN 0x02020202 // GetProcAddress() address
#define FREELIB_TOKEN 0x03030303 // FreeLibrary() address
#define DLLNAME_TOKEN 0x04040404 // Address of DLL name string
#define FUNCNAME_TOKEN 0x05050505 // Address of function name string
#define LOADER_EOF_TOKEN 0x0a0a0a0a // End of loader code
#define DECLARE_VARIABLE(name) name: __asm _emit 0x00 __asm _emit 0x00 __asm _emit 0x00 __asm _emit 0x00

static __declspec(naked) void LoaderStubCode()
{
__asm
{
// Store old EBP (part 1)
push ebp
mov ebp, esp

// Get function base offset to access "local" data
call lblStart
lblStart:
pop ebp
sub ebp, offset lblStart

// Store old EBP (part 2)
pop eax
mov [ebp+varOldEBP], eax

// Store function return address
pop ebx
mov [ebp+varFuncRetAddr], ebx

// Call LoadLibrary() to load target DLL
push DLLNAME_TOKEN
mov eax, LOADLIB_TOKEN
call eax
mov [ebp+varTargetHMod], eax // Store target DLL HMODULE

// Call GetProcAddress()
push FUNCNAME_TOKEN
push eax
mov eax, GETPROCADDRESS_TOKEN
call eax

// Call function
call eax

// Restore stack frame and return address
mov ebx, [ebp+varFuncRetAddr]
push ebx;

push eax // Store function's return value on the stack

// Call FreeLibrary to free the target DLL
push [ebp+varTargetHMod]
mov eax, FREELIB_TOKEN
call eax

mov ebp, [ebp+varOldEBP]

// Restore function return value and stack frame, then return
pop eax
ret

DECLARE_VARIABLE(varTargetHMod)
DECLARE_VARIABLE(varFuncRetAddr)
DECLARE_VARIABLE(varOldEBP)

// EOF for loader code
_emit LOADER_EOF_TOKEN&0xff
_emit (LOADER_EOF_TOKEN>>8)&0xff
_emit (LOADER_EOF_TOKEN>>16)&0xff
_emit (LOADER_EOF_TOKEN>>24)&0xff
}
}

inline void FixupToken(BYTE* pStart, DWORD dwToken, DWORD dwValue)
{
while(*(DWORD*)pStart != LOADER_EOF_TOKEN)
{
while(*(DWORD*)pStart != dwToken && *(DWORD*)pStart != LOADER_EOF_TOKEN)
++pStart;
if(*(DWORD*)pStart != LOADER_EOF_TOKEN)
{
*(DWORD*)pStart = dwValue;
pStart += 4;
}
}
}

//============================================================================
// Read a string from a process memory space
//============================================================================
bool ReadSting(HANDLE hProcess, void* pStartAddress, std::string& str)
{
BYTE* pAddress = (BYTE*)pStartAddress;
str = "";
for(;;)
{
// Read a chunk of data
char szBuff[65];
DWORD dwBytes;
if(!ReadProcessMemory(hProcess, pAddress, szBuff, sizeof(szBuff)-1, &dwBytes))
return false;
szBuff[63] = 0;

// Add it to the buffer
str += szBuff;

// Exit if there's a null terminator in it
bool bBreak = false;
for(size_t i=0; i<sizeof(szBuff)-1; ++i)
{
if(szBuff == 0)
{
bBreak = true;
break;
}
}

if(bBreak)
break;

pAddress += sizeof(szBuff)-1;
}

return true;
}

//============================================================================
// Read all DLL imports from a process
//============================================================================
bool ReadImports(HANDLE hThread, HANDLE hProcess, std::vector& vImports,
std::string* pstrError)
{
// Get thread context
CONTEXT theContext;
theContext.ContextFlags = CONTEXT_FULL;
if(!GetThreadContext(hThread, &theContext))
{
if(pstrError)
*pstrError = "GetThreadContext() failed\n";
return false;
}

// Get address of the FS segment
LDT_ENTRY selEntry;
if(!GetThreadSelectorEntry(hThread, theContext.SegFs, &selEntry))
{
if(pstrError)
*pstrError = "GetThreadSelectorEntry() failed\n";
return false;
}
DWORD dwFSBase = (selEntry.HighWord.Bits.BaseHi << 24) |
(selEntry.HighWord.Bits.BaseMid << 16) |
selEntry.BaseLow;

// Read thread/process environment block to get image base address
DWORD dwBaseAddress=0;
DWORD dwBytes;
{
// Read entire TEB
TEB theTEB;
if(!ReadProcessMemory(hProcess, (void*)dwFSBase, &theTEB, sizeof(theTEB), &dwBytes) ||
dwBytes!=sizeof(theTEB))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// PEB address is in TEB at byte offset 0x30
DWORD dwPEBAddress = *(DWORD*)(((BYTE*)&theTEB)+0x30);

// Base address is in the PEB at offset 0x08
if(!ReadProcessMemory(hProcess, (void*)(dwPEBAddress+8), &dwBaseAddress, sizeof(dwBaseAddress), &dwBytes) ||
dwBytes!=sizeof(dwBaseAddress))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}
}

// Read the DOS header
IMAGE_DOS_HEADER dosHeader;
if(!ReadProcessMemory(hProcess, (void*)dwBaseAddress, &dosHeader, sizeof(dosHeader), &dwBytes) ||
dwBytes!=sizeof(dosHeader))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// Validate it
if(dosHeader.e_magic != IMAGE_DOS_SIGNATURE)
{
if(pstrError)
*pstrError = "Processes DOS header magic number is invalid\n";
return false;
}

// Read NT header
IMAGE_NT_HEADERS ntHeader;
if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+dosHeader.e_lfanew), &ntHeader, sizeof(ntHeader), &dwBytes) ||
dwBytes!=sizeof(ntHeader))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// Validate it
if(ntHeader.Signature != IMAGE_NT_SIGNATURE)
{
if(pstrError)
*pstrError = "Processes NT header magic number is invalid\n";
return false;
}
if(ntHeader.OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_IMPORT)
{
if(pstrError)
*pstrError = "Process does not have an import table\n";
return false;
}

// Read the import descriptor table
vImports.clear();
for(size_t i=0; ; ++i)
{
// For each DLL...
IMAGE_IMPORT_DESCRIPTOR imports;
IMAGE_DATA_DIRECTORY& iat = ntHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+iat.VirtualAddress+i*sizeof(imports)),
&imports, sizeof(imports), &dwBytes) || dwBytes!=sizeof(imports))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// Is this the end of the list?
if(imports.OriginalFirstThunk == 0 && imports.FirstThunk == 0)
break;

// Grab DLL name
DLLImport theImport;
if(!ReadSting(hProcess, (void*)(dwBaseAddress+imports.Name), theImport.strName))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// For each function in the DLL...
for(size_t j=0; ; ++j)
{
// Read the thunk
IMAGE_THUNK_DATA theThunk;
if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+imports.OriginalFirstThunk+j*sizeof(theThunk)),
&theThunk, sizeof(theThunk), &dwBytes) || dwBytes!=sizeof(theThunk))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// Is this the end of the list?
if(theThunk.u1.AddressOfData == 0)
break;

// Is this a thunk by ordinal?
ImportedFunction func;
if(theThunk.u1.Ordinal & 0x80000000)
{
func.dwOrdinal = theThunk.u1.Ordinal&0x7fffffff;
}
else
{
// Grab function name
if(!ReadSting(hProcess, (void*)(dwBaseAddress+theThunk.u1.ForwarderString+2), func.strName))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}
func.dwOrdinal = 0;
}

// Read the actual import table entry
func.dwThunkAddress = dwBaseAddress+imports.FirstThunk+j*sizeof(theThunk);
if(!ReadProcessMemory(hProcess, (void*)func.dwThunkAddress, &theThunk, sizeof(theThunk), &dwBytes) ||
dwBytes!=sizeof(theThunk))
{
if(pstrError)
*pstrError = "ReadProcessMemory() failed\n";
return false;
}

// Should never happen, but check to be sure...
if(theThunk.u1.AddressOfData == 0)
break;

// Store address in function list
func.dwTargetAddress = theThunk.u1.AddressOfData;
theImport.vFunctions.push_back(func);
}
vImports.push_back(theImport);
}

/*
// Dump all imports (DEBUG)
for(size_t i=0; i {
OutputDebugString(vImports.strName.c_str());
OutputDebugString(":\n");
for(size_t j=0; j.vFunctions.size(); ++j)
{
OutputDebugString("+ ");
OutputDebugString(vImports.vFunctions[j].strName.c_str());
OutputDebugString("\n");
}
OutputDebugString("\n");
}
*/

return true;
}

//============================================================================
// Hook a function in the target process
//============================================================================
bool HookFunction(HANDLE hProcess, HookedFunction& funcInfo, const std::vector& vImports,
const std::string& strDllName, const std::string& strFuncName,
const std::string& strNewDllName, const std::string& strNewFuncName,
std::string* pstrError)
{
// Check WE can load the new DLL and function, because if not, the target process won't...
HMODULE hModTarget = LoadLibraryA(strNewDllName.c_str());
if(!hModTarget)
{
if(pstrError)
*pstrError = "Failed to load " + strNewDllName + "\n";
return false;
}
if(!GetProcAddress(hModTarget, strNewFuncName.c_str()))
{
if(pstrError)
*pstrError = "Failed to find " + strNewFuncName + "\n";
return false;
}
FreeLibrary(hModTarget);

// Find the function to hook
const ImportedFunction* pFunc = NULL;
for(size_t i=0; i {
if(_stricmp(vImports.strName.c_str(), strDllName.c_str()) == 0)
{
for(size_t j=0; j.vFunctions.size(); ++j)
{
if(vImports.vFunctions[j].strName == strFuncName.c_str())
{
pFunc = &vImports.vFunctions[j];
break;
}
}
break;
}
}
if(!pFunc)
{
if(pstrError)
*pstrError = "Failed to find " + strDllName + ":" + strFuncName + " to hook\n";
return false;
}

// Write the loader into the process address space
BYTE* pProcessMem;
DWORD dwBytes;
{
// Get length of loader code
BYTE* pLoaderCode = (BYTE*)&LoaderStubCode;
DWORD dwLoaderCodeLen=0;
while(*(DWORD*)(pLoaderCode+dwLoaderCodeLen) != LOADER_EOF_TOKEN)
++dwLoaderCodeLen;
dwLoaderCodeLen += 4;

// Allocate memory for loader + data block
DWORD dwDataBlockLen = (DWORD)(strNewDllName.length()+1 + strNewFuncName.length()+1);
DWORD dwSize = dwLoaderCodeLen + dwDataBlockLen;
BYTE* pMemory = new BYTE[dwSize];

// Copy in loader code
memcpy(pMemory, pLoaderCode, dwLoaderCodeLen);

// Copy in DLL name
DWORD dwDLLNameOffset = dwLoaderCodeLen;
memcpy(pMemory + dwDLLNameOffset, strNewDllName.c_str(), strNewDllName.length()+1);

// Copy in function name
DWORD dwFuncNameOffset = dwLoaderCodeLen + strNewDllName.length()+1;
memcpy(pMemory + dwFuncNameOffset, strNewFuncName.c_str(), strNewFuncName.length()+1);

// Fixup function call tokens
FixupToken(pMemory, LOADLIB_TOKEN, (DWORD)&LoadLibrary);
FixupToken(pMemory, GETPROCADDRESS_TOKEN, (DWORD)&GetProcAddress);
FixupToken(pMemory, FREELIB_TOKEN, (DWORD)&FreeLibrary);

// Allocate buffer for loader
pProcessMem = (BYTE*)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(!pProcessMem)
{
if(pstrError)
*pstrError = "VirtualAllocEx() failed\n";
delete[] pMemory;
return false;
}

// Fixup string tokens
FixupToken(pMemory, DLLNAME_TOKEN, (DWORD)(pProcessMem+dwDLLNameOffset));
FixupToken(pMemory, FUNCNAME_TOKEN, (DWORD)(pProcessMem+dwFuncNameOffset));

// Copy buffer to target process
if(!WriteProcessMemory(hProcess, pProcessMem, pMemory, dwSize, &dwBytes) || dwBytes != dwSize)
{
if(pstrError)
*pstrError = "WriteProcessMemory () failed\n";
delete[] pMemory;
return false;
}
delete[] pMemory;
}

// Fill in function information
funcInfo.dwOriginalThunkAddr = pFunc->dwThunkAddress;
funcInfo.dwOriginalThunk = pFunc->dwTargetAddress;
funcInfo.pLoaderThunk = pProcessMem;

// Write the new thunk location
if(!WriteProcessMemory(hProcess, (void*)pFunc->dwThunkAddress, &pProcessMem, sizeof(pProcessMem), &dwBytes)
|| dwBytes != sizeof(pProcessMem))
{
if(pstrError)
*pstrError = "WriteProcessMemory () failed\n";
return false;
}

return true;
}

//============================================================================
// Unhook a function in the target process
//============================================================================
bool UnhookFunction(HANDLE hProcess, const HookedFunction& funcInfo)
{
// Revert the thunk
DWORD dwBytes;
WriteProcessMemory(hProcess, (void*)funcInfo.dwOriginalThunkAddr, &funcInfo.dwOriginalThunk,
sizeof(DWORD), &dwBytes);

// Free the space used by the loader
VirtualFreeEx(hProcess, funcInfo.pLoaderThunk, 0, MEM_RELEASE);
return true;
}


It's a bit of a pain to read through, but it's pretty well commented. There's also probably quite a lot of bugs in it, but I'll worry about them later.

As for what it does: You use the API to get all exports in a process (And I'll add support for all exports in a .exe file too at some point), then hook the import table to point at some loader code. The loader code is allocated in the target process using VirtualAllocEx(), and it loads a specified DLL, and calls a function in it - passing the parameters passed to the loader (Which would be the parameters passed to the hooked function) - and then unloads the DLL. That lets me quickly change the DLL without having to run code to unload the DLL first. If the loaded DLL wants to stick around, it can always call LoadLibrary() on itself (I think anyway).

Anyway, I'll dump something in my journal once I have the x64 version working (Or I give up), and I get all the code tidied up.

BED.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now