////////////////////////////////////////////////////////////////////////////// // // Detours Test Program // // Microsoft Research Detours Package // // Copyright (c) Microsoft Corporation. All rights reserved. // // This is a test program to test the DetourAllocateRegionWithinJumpBounds // API, that dynamically allocates an executable region adjacent to a given // address so that we can use the region as a detour function. // // This test program detours the function `target_function`. Instead of // simply specifying a code segment as a detour function, we specify a // dynamically-allocated region into which we copy the code, altering the // return value, from the assembly function `CodeTemplate` as a template. // #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include extern "C" { void *CodeTemplate(); void *CodeTemplate_End(); } void Log(PCSTR format, ...) { char linebuf[1024]; va_list v; va_start(v, format); wvsprintfA(linebuf, format, v); va_end(v); OutputDebugStringA(linebuf); } // This is a target function to be detoured. When detoured, it's expected to // return a non-nullptr value. void *target_function() { std::cout << '+' << __FUNCTION__ << std::endl; return nullptr; } // Helper function to sandwich a given function between `DetourTransactionBegin` // and `DetourTransactionCommit`/`DetourTransactionAbort`. bool DetourTransaction(std::function callback) { LONG status = DetourTransactionBegin(); if (status != NO_ERROR) { Log("DetourTransactionBegin failed with %08x\n", status); return status == NO_ERROR; } if (callback()) { status = DetourTransactionCommit(); if (status != NO_ERROR) { Log("DetourTransactionCommit failed with %08x\n", status); } } else { status = DetourTransactionAbort(); if (status == NO_ERROR) { Log("Aborted transaction.\n"); } else { Log("DetourTransactionAbort failed with %08x\n", status); } } return status == NO_ERROR; } // This class manages one dynamically-allocated region that is allocated by // the Detours API `DetourAllocateRegionWithinJumpBounds`, to which we can // push binary data sequentially to use it as a detour function. class CodeRegionFactory final { template static const T *at(const void *base, uint32_t offset) { return reinterpret_cast( reinterpret_cast(base) + offset); } template static T *at(void *base, uint32_t offset) { return reinterpret_cast( reinterpret_cast(base) + offset); } void *region_ = nullptr; uint8_t *current_ = nullptr, *current_end_ = nullptr; public: CodeRegionFactory(const void *source) { DWORD new_region_size = 0; auto new_region_address = DetourAllocateRegionWithinJumpBounds(source, &new_region_size); if (new_region_address) { region_ = current_ = at(new_region_address, 0); current_end_ = current_ + new_region_size; } else { Log("Cannot find a region near %p\n", source); } } ~CodeRegionFactory() { if (region_ && !VirtualFree(region_, 0, MEM_RELEASE)) { Log("VirtualFree failed - %08x\n", GetLastError()); } } // Pushes binary data to the region if there is enough space, and returns // the start address of a copy in the region if succeeded. void *PushTemplate(const void *start, const void *end) { auto diff = at(end, 0) - at(start, 0); if (diff < 0 || current_ + diff > current_end_) return nullptr; auto start_pos = current_; memcpy(start_pos, start, diff); current_ += diff; return start_pos; } }; int main(int, char**) { std::cout << "1. target_function() without Detour" << std::endl; auto ret = target_function(); std::cout << ret << std::endl; assert(!ret); CodeRegionFactory factory(target_function); void *detour_destination, *detour_target = reinterpret_cast(target_function); // Fill the allocated page with as many instances as possible of the code // template, and pick the last instance while (auto p = factory.PushTemplate(CodeTemplate, CodeTemplate_End)) { detour_destination = p; } bool is_detoured = false; DetourTransaction([&]() { PDETOUR_TRAMPOLINE trampoline = nullptr; void *target = nullptr, *detour = nullptr; auto status = DetourAttachEx(&detour_target, detour_destination, &trampoline, &target, &detour); if (status != NO_ERROR) { Log("DetourAttachEx failed - %08x\n", status); return false; } is_detoured = true; std::cout << "detour: " << target << " --> " << detour << " (trampoline: " << trampoline << " )" << std::endl; return true; }); // Attach failed for some reason. Bail out. if (!is_detoured) return 1; std::cout << "2. target_function() with Detour" << std::endl; ret = target_function(); std::cout << ret << std::endl; assert(ret); // The return value is cracked by the detour function DetourTransaction([&]() { auto status = DetourDetach(&detour_target, detour_destination); if (status != NO_ERROR) { Log("DetourDetach failed - %08x\n", status); return false; } return true; }); std::cout << "3. target_function() without Detour" << std::endl; ret = target_function(); std::cout << ret << std::endl; assert(!ret); return 0; }