// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/iat_patch.h"
#include "base/logging.h"
namespace iat_patch {
struct InterceptFunctionInformation {
bool finished_operation;
const char* imported_from_module;
const char* function_name;
void* new_function;
void** old_function;
IMAGE_THUNK_DATA** iat_thunk;
DWORD return_code;
};
static void* GetIATFunction(IMAGE_THUNK_DATA* iat_thunk) {
if (NULL == iat_thunk) {
NOTREACHED();
return NULL;
}
// Works around the 64 bit portability warning:
// The Function member inside IMAGE_THUNK_DATA is really a pointer
// to the IAT function. IMAGE_THUNK_DATA correctly maps to IMAGE_THUNK_DATA32
// or IMAGE_THUNK_DATA64 for correct pointer size.
union FunctionThunk {
IMAGE_THUNK_DATA thunk;
void* pointer;
} iat_function;
iat_function.thunk = *iat_thunk;
return iat_function.pointer;
}
static bool InterceptEnumCallback(const PEImage &image, const char* module,
DWORD ordinal, const char* name, DWORD hint,
IMAGE_THUNK_DATA* iat, void* cookie) {
InterceptFunctionInformation* intercept_information =
reinterpret_cast<InterceptFunctionInformation*>(cookie);
if (NULL == intercept_information) {
NOTREACHED();
return false;
}
DCHECK(module);
if ((0 == lstrcmpiA(module, intercept_information->imported_from_module)) &&
(NULL != name) &&
(0 == lstrcmpiA(name, intercept_information->function_name))) {
// Save the old pointer.
if (NULL != intercept_information->old_function) {
*(intercept_information->old_function) = GetIATFunction(iat);
}
if (NULL != intercept_information->iat_thunk) {
*(intercept_information->iat_thunk) = iat;
}
// portability check
COMPILE_ASSERT(sizeof(iat->u1.Function) ==
sizeof(intercept_information->new_function), unknown_IAT_thunk_format);
// Patch the function.
intercept_information->return_code =
ModifyCode(&(iat->u1.Function),
&(intercept_information->new_function),
sizeof(intercept_information->new_function));
// Terminate further enumeration.
intercept_information->finished_operation = true;
return false;
}
return true;
}
DWORD InterceptImportedFunction(HMODULE module_handle,
const char* imported_from_module,
const char* function_name, void* new_function,
void** old_function,
IMAGE_THUNK_DATA** iat_thunk) {
if ((NULL == module_handle) || (NULL == imported_from_module) ||
(NULL == function_name) || (NULL == new_function)) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
PEImage target_image(module_handle);
if (!target_image.VerifyMagic()) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
InterceptFunctionInformation intercept_information = {
false,
imported_from_module,
function_name,
new_function,
old_function,
iat_thunk,
ERROR_GEN_FAILURE};
// First go through the IAT. If we don't find the import we are looking
// for in IAT, search delay import table.
target_image.EnumAllImports(InterceptEnumCallback, &intercept_information);
if (!intercept_information.finished_operation) {
target_image.EnumAllDelayImports(InterceptEnumCallback,
&intercept_information);
}
return intercept_information.return_code;
}
DWORD RestoreImportedFunction(void* intercept_function,
void* original_function,
IMAGE_THUNK_DATA* iat_thunk) {
if ((NULL == intercept_function) || (NULL == original_function) ||
(NULL == iat_thunk)) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
if (GetIATFunction(iat_thunk) != intercept_function) {
// Check if someone else has intercepted on top of us.
// We cannot unpatch in this case, just raise a red flag.
NOTREACHED();
return ERROR_INVALID_FUNCTION;
}
return ModifyCode(&(iat_thunk->u1.Function),
&original_function,
sizeof(original_function));
}
DWORD ModifyCode(void* old_code, void* new_code, int length) {
if ((NULL == old_code) || (NULL == new_code) || (0 == length)) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
// Change the page protection so that we can write.
DWORD error = NO_ERROR;
DWORD old_page_protection = 0;
if (VirtualProtect(old_code,
length,
PAGE_READWRITE,
&old_page_protection)) {
// Write the data.
CopyMemory(old_code, new_code, length);
// Restore the old page protection.
error = ERROR_SUCCESS;
VirtualProtect(old_code,
length,
old_page_protection,
&old_page_protection);
} else {
error = GetLastError();
NOTREACHED();
}
return error;
}
IATPatchFunction::IATPatchFunction()
: module_handle_(NULL),
original_function_(NULL),
iat_thunk_(NULL),
intercept_function_(NULL) {
}
IATPatchFunction::~IATPatchFunction() {
if (NULL != intercept_function_) {
DWORD error = Unpatch();
DCHECK_EQ(NO_ERROR, error);
}
}
DWORD IATPatchFunction::Patch(const wchar_t* module,
const char* imported_from_module,
const char* function_name,
void* new_function) {
DCHECK_EQ(static_cast<void*>(NULL), original_function_);
DCHECK_EQ(static_cast<IMAGE_THUNK_DATA*>(NULL), iat_thunk_);
DCHECK_EQ(static_cast<void*>(NULL), intercept_function_);
HMODULE module_handle = LoadLibraryW(module);
if (module_handle == NULL) {
NOTREACHED();
return GetLastError();
}
DWORD error = InterceptImportedFunction(module_handle,
imported_from_module,
function_name,
new_function,
&original_function_,
&iat_thunk_);
if (NO_ERROR == error) {
DCHECK_NE(original_function_, intercept_function_);
module_handle_ = module_handle;
intercept_function_ = new_function;
} else {
FreeLibrary(module_handle);
}
return error;
}
DWORD IATPatchFunction::Unpatch() {
DWORD error = RestoreImportedFunction(intercept_function_,
original_function_,
iat_thunk_);
DCHECK(NO_ERROR == error);
// Hands off the intercept if we fail to unpatch.
// If IATPatchFunction::Unpatch fails during RestoreImportedFunction
// it means that we cannot safely unpatch the import address table
// patch. In this case its better to be hands off the intercept as
// trying to unpatch again in the destructor of IATPatchFunction is
// not going to be any safer
if (module_handle_)
FreeLibrary(module_handle_);
module_handle_ = NULL;
intercept_function_ = NULL;
original_function_ = NULL;
iat_thunk_ = NULL;
return error;
}
} // namespace iat_patch