├── .github └── workflows │ └── windows.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── Tests ├── CMakeLists.txt ├── CdeclTests.cpp ├── FastcallTests.cpp ├── Gadget.h ├── StdcallTests.cpp └── ThiscallTests.cpp └── x86RetSpoof.h /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [windows-2019, windows-2022] 10 | toolset: [MSVC, ClangCL] 11 | configuration: [Debug, Release] 12 | runs-on: ${{ matrix.os }} 13 | name: ${{ matrix.os }}.${{ matrix.toolset }}.${{ matrix.configuration }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: set toolset 17 | if: matrix.toolset == 'ClangCL' 18 | run: echo "toolset=-T ${{ matrix.toolset }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 19 | - name: configure 20 | run: cmake -D BUILD_TESTS=1 -A Win32 ${{ env.toolset }} -S . -B build 21 | - name: build 22 | run: cmake --build build --config ${{ matrix.configuration }} 23 | - name: run unit tests 24 | run: cd build && ctest --output-on-failure --schedule-random -j $env:NUMBER_OF_PROCESSORS 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(x86RetSpoof CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | if (MSVC) 9 | add_compile_options(/W4 /WX /w44388) 10 | endif() 11 | 12 | include_directories(.) 13 | 14 | option(BUILD_TESTS "Build unit tests" OFF) 15 | 16 | if (BUILD_TESTS) 17 | enable_testing() 18 | add_subdirectory(Tests) 19 | endif() 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Daniel Krupiński 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # x86RetSpoof [![Windows](https://github.com/danielkrupinski/x86RetSpoof/actions/workflows/windows.yml/badge.svg?event=push)](https://github.com/danielkrupinski/x86RetSpoof/actions/workflows/windows.yml) 2 | Invoke functions with a spoofed return address. For 32-bit Windows binaries. 3 | 4 | # How to use 5 | 1. Include x86RetSpoof.h in your project. 6 | 2. Find `FF 23` byte sequence (`gadget`, machine code equivalent of `jmp dword ptr [ebx]`) in the executable code section of the module you want the spoofed return address to appear in. The address of it will be the `gadgetAddress` and the invoked function will see it as the return address. 7 | 3. Call the function with `x86RetSpoof::invoke...()` matching the calling convention of the target function. 8 | 9 | ## Example 10 | Calling [MessageBoxW](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw) function: 11 | ```cpp 12 | x86RetSpoof::invokeStdcall(std::uintptr_t(&MessageBoxW), std::uintptr_t(gadgetAddress), nullptr, L"text", L"title", MB_OK); 13 | ``` 14 | -------------------------------------------------------------------------------- /Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(THREADS_PREFER_PTHREAD_FLAG ON) 2 | 3 | find_package(Threads REQUIRED) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare( 7 | googletest 8 | URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip 9 | URL_HASH SHA256=353571c2440176ded91c2de6d6cd88ddd41401d14692ec1f99e35d013feda55a 10 | ) 11 | 12 | FetchContent_MakeAvailable(googletest) 13 | 14 | add_executable(Tests FastcallTests.cpp ThiscallTests.cpp StdcallTests.cpp CdeclTests.cpp) 15 | target_link_libraries(Tests gtest_main gmock Threads::Threads) 16 | 17 | include(GoogleTest) 18 | gtest_discover_tests(Tests) 19 | -------------------------------------------------------------------------------- /Tests/CdeclTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "Gadget.h" 11 | #include "x86RetSpoof.h" 12 | 13 | static void* __cdecl getReturnAddressOfMyself() 14 | { 15 | return _ReturnAddress(); 16 | } 17 | 18 | TEST(InvokeCdeclTest, ReturnAddressOfTheInvokedFunctionIsTheAddressOfTheGadget) { 19 | EXPECT_EQ(x86RetSpoof::invokeCdecl(std::uintptr_t(&getReturnAddressOfMyself), std::uintptr_t(gadget.data())), gadget.data()); 20 | } 21 | 22 | TEST(InvokeCdeclTest, 64bitIntegerIsReturnedCorrectly) { 23 | static constexpr std::uint64_t value = (std::numeric_limits::max)(); 24 | std::uint64_t(__cdecl* const function)() = []{ return value; }; 25 | EXPECT_EQ(x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 26 | } 27 | 28 | TEST(InvokeCdeclTest, FloatIsReturnedCorrectly) { 29 | static constexpr float value = 3.1415f; 30 | float(__cdecl* const function)() = []{ return value; }; 31 | EXPECT_FLOAT_EQ(x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 32 | } 33 | 34 | TEST(InvokeCdeclTest, ExplicitReferenceArgumentIsNotCopied) { 35 | void(__cdecl* const function)(unsigned& value) = [](unsigned& value) { value = 0xDEADBEEF; }; 36 | unsigned number = 0; 37 | x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data()), number); 38 | EXPECT_EQ(number, 0xDEADBEEF); 39 | } 40 | 41 | TEST(InvokeCdeclTest, ReferenceArgumentIsDeducedCorrectly) { 42 | void(__cdecl* const function)(unsigned& value) = [](unsigned& value) { value = 0xDEADBEEF; }; 43 | unsigned number = 0; 44 | x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data()), std::ref(number)); 45 | EXPECT_EQ(number, 0xDEADBEEF); 46 | } 47 | 48 | TEST(InvokeCdeclTest, LvalueArgumentIsNotDeducedToReference) { 49 | std::uintptr_t(__cdecl* const function)(std::uintptr_t) = [](std::uintptr_t value) { return value; }; 50 | std::uintptr_t number = 1234; 51 | EXPECT_EQ(x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data()), number), std::uintptr_t(1234)); 52 | } 53 | 54 | TEST(InvokeCdeclTest, FunctionIsInvokedOncePerCall) { 55 | struct Mock { 56 | MOCK_METHOD(void, called, (), (const)); 57 | }; 58 | static std::unique_ptr mock; 59 | mock = std::make_unique(); 60 | 61 | void(__cdecl* const function)() = []() { mock->called(); }; 62 | EXPECT_CALL(*mock.get(), called()); 63 | x86RetSpoof::invokeCdecl(std::uintptr_t(function), std::uintptr_t(gadget.data())); 64 | mock.reset(); 65 | } 66 | 67 | #define TEST_REGISTER_PRESERVED(registerName) \ 68 | TEST(InvokeCdeclTest, registerName##RegisterIsPreserved) { \ 69 | void(__cdecl* const invokeCdecl)(std::uintptr_t, x86RetSpoof::detail::Context*, std::uintptr_t) = x86RetSpoof::detail::invokeCdecl; \ 70 | void(__cdecl* const function)() = [] {}; \ 71 | x86RetSpoof::detail::Context context; \ 72 | const auto addressOfGadget = std::uintptr_t(gadget.data()); \ 73 | \ 74 | std::uintptr_t registerName##BeforeCall = 0; \ 75 | std::uintptr_t registerName##AfterCall = 0; \ 76 | __asm { \ 77 | __asm mov registerName##BeforeCall, registerName \ 78 | __asm push addressOfGadget \ 79 | __asm lea eax, context \ 80 | __asm push eax \ 81 | __asm push function \ 82 | __asm call invokeCdecl \ 83 | __asm add esp, 12 \ 84 | __asm mov registerName##AfterCall, registerName \ 85 | } \ 86 | \ 87 | EXPECT_EQ(registerName##BeforeCall, registerName##AfterCall); \ 88 | } 89 | 90 | TEST_REGISTER_PRESERVED(ebx); 91 | TEST_REGISTER_PRESERVED(ebp); 92 | TEST_REGISTER_PRESERVED(esi); 93 | TEST_REGISTER_PRESERVED(edi); 94 | -------------------------------------------------------------------------------- /Tests/FastcallTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "Gadget.h" 11 | #include "x86RetSpoof.h" 12 | 13 | static void* __fastcall getReturnAddressOfMyself() 14 | { 15 | return _ReturnAddress(); 16 | } 17 | 18 | TEST(InvokeFastcallTest, ReturnAddressOfTheInvokedFunctionIsTheAddressOfTheGadget) { 19 | EXPECT_EQ(x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(&getReturnAddressOfMyself), std::uintptr_t(gadget.data())), gadget.data()); 20 | } 21 | 22 | TEST(InvokeFastcallTest, 64bitIntegerIsReturnedCorrectly) { 23 | static constexpr std::uint64_t value = (std::numeric_limits::max)(); 24 | std::uint64_t(__fastcall* const function)() = []{ return value; }; 25 | EXPECT_EQ(x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 26 | } 27 | 28 | TEST(InvokeFastcallTest, FloatIsReturnedCorrectly) { 29 | static constexpr float value = 3.1415f; 30 | float(__fastcall* const function)() = []{ return value; }; 31 | EXPECT_FLOAT_EQ(x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 32 | } 33 | 34 | TEST(InvokeFastcallTest, EcxContainsCorrectValueInTheInvokedFunction) { 35 | static constexpr int value = 12345; 36 | int(__fastcall* const function)(int ecx) = [](int ecx) { return ecx; }; 37 | EXPECT_EQ(x86RetSpoof::invokeFastcall(value, 0, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 38 | } 39 | 40 | TEST(InvokeFastcallTest, EdxContainsCorrectValueInTheInvokedFunction) { 41 | static constexpr int value = 12345; 42 | int(__fastcall* const function)(int ecx, int edx) = []([[maybe_unused]] int ecx, int edx) { return edx; }; 43 | EXPECT_EQ(x86RetSpoof::invokeFastcall(0, value, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 44 | } 45 | 46 | TEST(InvokeFastcallTest, EcxAndEdxContainCorrectValueInTheInvokedFunction) { 47 | unsigned(__fastcall* const function)(unsigned, unsigned) = [](unsigned ecx, unsigned edx) { return (ecx << 16) | edx; }; 48 | EXPECT_EQ(x86RetSpoof::invokeFastcall(0xDEAD, 0xBEEF, std::uintptr_t(function), std::uintptr_t(gadget.data())), 0xDEADBEEF); 49 | } 50 | 51 | TEST(InvokeFastcallTest, ExplicitReferenceArgumentIsNotCopied) { 52 | void(__fastcall* const function)(int ecx, int edx, unsigned& value) = [](int, int, unsigned& value) { value = 0xDEADBEEF; }; 53 | unsigned number = 0; 54 | x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data()), number); 55 | EXPECT_EQ(number, 0xDEADBEEF); 56 | } 57 | 58 | TEST(InvokeFastcallTest, ReferenceArgumentIsDeducedCorrectly) { 59 | void(__fastcall* const function)(int ecx, int edx, unsigned& value) = [](int, int, unsigned& value) { value = 0xDEADBEEF; }; 60 | unsigned number = 0; 61 | x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data()), std::ref(number)); 62 | EXPECT_EQ(number, 0xDEADBEEF); 63 | } 64 | 65 | TEST(InvokeFastcallTest, LvalueArgumentIsNotDeducedToReference) { 66 | std::uintptr_t(__fastcall* const function)(int ecx, int edx, std::uintptr_t) = [](int, int, std::uintptr_t value) { return value; }; 67 | std::uintptr_t number = 1234; 68 | EXPECT_EQ(x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data()), number), std::uintptr_t(1234)); 69 | } 70 | 71 | TEST(InvokeFastcallTest, FunctionIsInvokedOncePerCall) { 72 | struct Mock { 73 | MOCK_METHOD(void, called, (), (const)); 74 | }; 75 | static std::unique_ptr mock; 76 | mock = std::make_unique(); 77 | 78 | void(__fastcall* const function)() = []{ mock->called(); }; 79 | EXPECT_CALL(*mock.get(), called()); 80 | x86RetSpoof::invokeFastcall(0, 0, std::uintptr_t(function), std::uintptr_t(gadget.data())); 81 | mock.reset(); 82 | } 83 | 84 | #define TEST_REGISTER_PRESERVED(registerName) \ 85 | TEST(InvokeFastcallTest, registerName##RegisterIsPreserved) { \ 86 | void(__fastcall* const invokeFastcall)(std::uintptr_t, std::uintptr_t, std::uintptr_t, x86RetSpoof::detail::Context*, std::uintptr_t) = x86RetSpoof::detail::invokeFastcall; \ 87 | void(__fastcall* const function)() = [] {}; \ 88 | x86RetSpoof::detail::Context context; \ 89 | const auto addressOfGadget = std::uintptr_t(gadget.data()); \ 90 | \ 91 | std::uintptr_t registerName##BeforeCall = 0; \ 92 | std::uintptr_t registerName##AfterCall = 0; \ 93 | __asm { \ 94 | __asm mov registerName##BeforeCall, registerName \ 95 | __asm push addressOfGadget \ 96 | __asm lea eax, context \ 97 | __asm push eax \ 98 | __asm push function \ 99 | __asm xor ecx, ecx \ 100 | __asm xor edx, edx \ 101 | __asm call invokeFastcall \ 102 | __asm mov registerName##AfterCall, registerName \ 103 | } \ 104 | \ 105 | EXPECT_EQ(registerName##BeforeCall, registerName##AfterCall); \ 106 | } 107 | 108 | TEST_REGISTER_PRESERVED(ebx); 109 | TEST_REGISTER_PRESERVED(ebp); 110 | TEST_REGISTER_PRESERVED(esi); 111 | TEST_REGISTER_PRESERVED(edi); 112 | -------------------------------------------------------------------------------- /Tests/Gadget.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma section(".text") 5 | __declspec(allocate(".text")) constexpr std::array gadget{ 0xFF, 0x23 }; // jmp dword ptr[ebx] 6 | -------------------------------------------------------------------------------- /Tests/StdcallTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "Gadget.h" 12 | #include "x86RetSpoof.h" 13 | 14 | static void* __stdcall getReturnAddressOfMyself() 15 | { 16 | return _ReturnAddress(); 17 | } 18 | 19 | TEST(InvokeStdcallTest, ReturnAddressOfTheInvokedFunctionIsTheAddressOfTheGadget) { 20 | EXPECT_EQ(x86RetSpoof::invokeStdcall(std::uintptr_t(&getReturnAddressOfMyself), std::uintptr_t(gadget.data())), gadget.data()); 21 | } 22 | 23 | TEST(InvokeStdcallTest, 64bitIntegerIsReturnedCorrectly) { 24 | static constexpr std::uint64_t value = (std::numeric_limits::max)(); 25 | std::uint64_t(__stdcall* const function)() = []{ return value; }; 26 | EXPECT_EQ(x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 27 | } 28 | 29 | TEST(InvokeStdcallTest, FloatIsReturnedCorrectly) { 30 | static constexpr float value = 3.1415f; 31 | float(__stdcall* const function)() = []{ return value; }; 32 | EXPECT_FLOAT_EQ(x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 33 | } 34 | 35 | TEST(InvokeStdcallTest, ExplicitReferenceArgumentIsNotCopied) { 36 | void(__stdcall* const function)(unsigned& value) = [](unsigned& value) { value = 0xDEADBEEF; }; 37 | unsigned number = 0; 38 | x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data()), number); 39 | EXPECT_EQ(number, 0xDEADBEEF); 40 | } 41 | 42 | TEST(InvokeStdcallTest, ReferenceArgumentIsDeducedCorrectly) { 43 | void(__stdcall* const function)(unsigned& value) = [](unsigned& value) { value = 0xDEADBEEF; }; 44 | unsigned number = 0; 45 | x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data()), std::ref(number)); 46 | EXPECT_EQ(number, 0xDEADBEEF); 47 | } 48 | 49 | TEST(InvokeStdcallTest, LvalueArgumentIsNotDeducedToReference) { 50 | std::uintptr_t(__stdcall* const function)(std::uintptr_t) = [](std::uintptr_t value) { return value; }; 51 | std::uintptr_t number = 1234; 52 | EXPECT_EQ(x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data()), number), std::uintptr_t(1234)); 53 | } 54 | 55 | TEST(InvokeStdcallTest, FunctionIsInvokedOncePerCall) { 56 | struct Mock { 57 | MOCK_METHOD(void, called, (), (const)); 58 | }; 59 | static std::unique_ptr mock; 60 | mock = std::make_unique(); 61 | 62 | void(__stdcall* const function)() = []{ mock->called(); }; 63 | EXPECT_CALL(*mock.get(), called()); 64 | x86RetSpoof::invokeStdcall(std::uintptr_t(function), std::uintptr_t(gadget.data())); 65 | mock.reset(); 66 | } 67 | 68 | #define TEST_REGISTER_PRESERVED(registerName) \ 69 | TEST(InvokeStdcallTest, registerName##RegisterIsPreserved) { \ 70 | void(__fastcall* const invokeFastcall)(std::uintptr_t, std::uintptr_t, std::uintptr_t, x86RetSpoof::detail::Context*, std::uintptr_t) = x86RetSpoof::detail::invokeFastcall; \ 71 | void(__stdcall* const function)() = [] {}; \ 72 | x86RetSpoof::detail::Context context; \ 73 | const auto addressOfGadget = std::uintptr_t(gadget.data()); \ 74 | \ 75 | std::uintptr_t registerName##BeforeCall = 0; \ 76 | std::uintptr_t registerName##AfterCall = 0; \ 77 | __asm { \ 78 | __asm mov registerName##BeforeCall, registerName \ 79 | __asm push addressOfGadget \ 80 | __asm lea eax, context \ 81 | __asm push eax \ 82 | __asm push function \ 83 | __asm call invokeFastcall \ 84 | __asm mov registerName##AfterCall, registerName \ 85 | } \ 86 | \ 87 | EXPECT_EQ(registerName##BeforeCall, registerName##AfterCall); \ 88 | } 89 | 90 | TEST_REGISTER_PRESERVED(ebx); 91 | TEST_REGISTER_PRESERVED(ebp); 92 | TEST_REGISTER_PRESERVED(esi); 93 | TEST_REGISTER_PRESERVED(edi); 94 | -------------------------------------------------------------------------------- /Tests/ThiscallTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "Gadget.h" 12 | #include "x86RetSpoof.h" 13 | 14 | static void* __fastcall getReturnAddressOfMyself() 15 | { 16 | return _ReturnAddress(); 17 | } 18 | 19 | TEST(InvokeThiscallTest, ReturnAddressOfTheInvokedFunctionIsTheAddressOfTheGadget) { 20 | EXPECT_EQ(x86RetSpoof::invokeThiscall(0, std::uintptr_t(&getReturnAddressOfMyself), std::uintptr_t(gadget.data())), gadget.data()); 21 | } 22 | 23 | TEST(InvokeThiscallTest, 64bitIntegerIsReturnedCorrectly) { 24 | static constexpr std::uint64_t value = (std::numeric_limits::max)(); 25 | std::uint64_t(__fastcall* const function)() = []{ return value; }; 26 | EXPECT_EQ(x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 27 | } 28 | 29 | TEST(InvokeThiscallTest, FloatIsReturnedCorrectly) { 30 | static constexpr float value = 3.1415f; 31 | float(__fastcall* const function)() = []{ return value; }; 32 | EXPECT_FLOAT_EQ(x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 33 | } 34 | 35 | TEST(InvokeThiscallTest, EcxContainsCorrectValueInTheInvokedFunction) { 36 | static constexpr int value = 12345; 37 | int(__fastcall* const function)(int ecx) = [](int ecx) { return ecx; }; 38 | EXPECT_EQ(x86RetSpoof::invokeThiscall(value, std::uintptr_t(function), std::uintptr_t(gadget.data())), value); 39 | } 40 | 41 | TEST(InvokeThiscallTest, ExplicitReferenceArgumentIsNotCopied) { 42 | void(__fastcall* const function)(int ecx, int edx, unsigned& value) = [](int, int, unsigned& value) { value = 0xDEADBEEF; }; 43 | unsigned number = 0; 44 | x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data()), number); 45 | EXPECT_EQ(number, 0xDEADBEEF); 46 | } 47 | 48 | TEST(InvokeThiscallTest, ReferenceArgumentIsDeducedCorrectly) { 49 | void(__fastcall* const function)(int ecx, int edx, unsigned& value) = [](int, int, unsigned& value) { value = 0xDEADBEEF; }; 50 | unsigned number = 0; 51 | x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data()), std::ref(number)); 52 | EXPECT_EQ(number, 0xDEADBEEF); 53 | } 54 | 55 | TEST(InvokeThiscallTest, LvalueArgumentIsNotDeducedToReference) { 56 | std::uintptr_t(__fastcall* const function)(int ecx, int edx, std::uintptr_t) = [](int, int, std::uintptr_t value) { return value; }; 57 | std::uintptr_t number = 1234; 58 | EXPECT_EQ(x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data()), number), std::uintptr_t(1234)); 59 | } 60 | 61 | TEST(InvokeThiscallTest, FunctionIsInvokedOncePerCall) { 62 | struct Mock { 63 | MOCK_METHOD(void, called, (), (const)); 64 | }; 65 | static std::unique_ptr mock; 66 | mock = std::make_unique(); 67 | 68 | void(__fastcall* const function)() = []{ mock->called(); }; 69 | EXPECT_CALL(*mock.get(), called()); 70 | x86RetSpoof::invokeThiscall(0, std::uintptr_t(function), std::uintptr_t(gadget.data())); 71 | mock.reset(); 72 | } 73 | 74 | #define TEST_REGISTER_PRESERVED(registerName) \ 75 | TEST(InvokeThiscallTest, registerName##RegisterIsPreserved) { \ 76 | void(__fastcall* const invokeFastcall)(std::uintptr_t, std::uintptr_t, std::uintptr_t, x86RetSpoof::detail::Context*, std::uintptr_t) = x86RetSpoof::detail::invokeFastcall; \ 77 | void(__fastcall* const function)() = [] {}; \ 78 | x86RetSpoof::detail::Context context; \ 79 | const auto addressOfGadget = std::uintptr_t(gadget.data()); \ 80 | \ 81 | std::uintptr_t registerName##BeforeCall = 0; \ 82 | std::uintptr_t registerName##AfterCall = 0; \ 83 | __asm { \ 84 | __asm mov registerName##BeforeCall, registerName \ 85 | __asm push addressOfGadget \ 86 | __asm lea eax, context \ 87 | __asm push eax \ 88 | __asm push function \ 89 | __asm xor ecx, ecx \ 90 | __asm call invokeFastcall \ 91 | __asm mov registerName##AfterCall, registerName \ 92 | } \ 93 | \ 94 | EXPECT_EQ(registerName##BeforeCall, registerName##AfterCall); \ 95 | } 96 | 97 | TEST_REGISTER_PRESERVED(ebx); 98 | TEST_REGISTER_PRESERVED(ebp); 99 | TEST_REGISTER_PRESERVED(esi); 100 | TEST_REGISTER_PRESERVED(edi); 101 | -------------------------------------------------------------------------------- /x86RetSpoof.h: -------------------------------------------------------------------------------- 1 | /* 2 | x86RetSpoof - https://github.com/danielkrupinski/x86RetSpoof 3 | 4 | Invoke functions with a spoofed return address. 5 | For 32-bit Windows binaries. 6 | Supports __fastcall, __thiscall, __stdcall and __cdecl calling conventions. 7 | Written in C++17. 8 | 9 | Version: 17 February 2023 10 | */ 11 | 12 | /* 13 | MIT License 14 | 15 | Copyright (c) 2022-2023 Daniel Krupiński 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | */ 35 | 36 | #pragma once 37 | 38 | #include 39 | #include 40 | 41 | namespace x86RetSpoof 42 | { 43 | namespace detail 44 | { 45 | struct Context; 46 | } 47 | 48 | template 49 | ReturnType invokeFastcall(std::uintptr_t ecx, std::uintptr_t edx, std::uintptr_t functionAddress, std::uintptr_t gadgetAddress, StackArgs... stackArgs) noexcept 50 | { 51 | detail::Context context; 52 | return invokeFastcall(ecx, edx, functionAddress, &context, gadgetAddress, stackArgs...); 53 | } 54 | 55 | template 56 | ReturnType invokeThiscall(std::uintptr_t ecx, std::uintptr_t functionAddress, std::uintptr_t gadgetAddress, StackArgs... stackArgs) noexcept 57 | { 58 | return invokeFastcall(ecx, 0, functionAddress, gadgetAddress, stackArgs...); 59 | } 60 | 61 | template 62 | ReturnType invokeStdcall(std::uintptr_t functionAddress, std::uintptr_t gadgetAddress, StackArgs... stackArgs) noexcept 63 | { 64 | return invokeThiscall(0, functionAddress, gadgetAddress, stackArgs...); 65 | } 66 | 67 | template 68 | ReturnType invokeCdecl(std::uintptr_t functionAddress, std::uintptr_t gadgetAddress, StackArgs... stackArgs) noexcept 69 | { 70 | detail::Context context; 71 | return invokeCdecl(functionAddress, &context, gadgetAddress, stackArgs...); 72 | } 73 | 74 | namespace detail 75 | { 76 | struct Context { 77 | std::uintptr_t ebxBackup; 78 | std::uintptr_t addressToJumpToInGadget; 79 | std::uintptr_t invokerReturnAddress; 80 | }; 81 | 82 | template 83 | __declspec(naked) ReturnType __fastcall invokeFastcall(std::uintptr_t /*ecx*/, std::uintptr_t /*edx*/, std::uintptr_t /*functionAddress*/, Context* /*context*/, std::uintptr_t /*gadgetAddress*/, StackArgs... /*stackArgs*/) noexcept 84 | { 85 | __asm { 86 | mov eax, [esp + 8] // load a pointer to context into eax 87 | mov [eax], ebx // save ebx in context.ebxBackup 88 | lea ebx, returnHereFromGadget // load the address of the label we want the gadget to jump to 89 | mov [eax + 4], ebx // save the address of 'returnHereFromGadget' in context.addressToJumpToInGadget 90 | pop dword ptr [eax + 8] // pop return address from stack into context.invokerReturnAddress 91 | 92 | lea ebx, [eax + 4] // load the address of context.addressToJumpToInGadget to ebx 93 | ret 4 // pop 'functionAddress' from stack and jump to it, skip 'context' on stack; esp will point to the spoofed return address (gadgetAddress) 94 | 95 | returnHereFromGadget: 96 | push [ebx + 4] // restore context.invokerReturnAddress as a return address 97 | mov ebx, [ebx - 4] // restore ebx from context.ebxBackup 98 | ret 99 | } 100 | } 101 | 102 | template 103 | __declspec(naked) ReturnType __cdecl invokeCdecl(std::uintptr_t /*functionAddress*/, Context* /*context*/, std::uintptr_t /*gadgetAddress*/, StackArgs... /*stackArgs*/) noexcept 104 | { 105 | __asm { 106 | mov eax, [esp + 8] // load a pointer to context into eax 107 | mov [eax], ebx // save ebx in context.ebxBackup 108 | lea ebx, returnHereFromGadget // load the address of the label we want the gadget to jump to 109 | mov [eax + 4], ebx // save the address of 'returnHereFromGadget' in context.addressToJumpToInGadget 110 | pop dword ptr [eax + 8] // pop return address from stack into context.invokerReturnAddress 111 | 112 | lea ebx, [eax + 4] // load the address of context.addressToJumpToInGadget to ebx 113 | ret 4 // pop 'functionAddress' from stack and jump to it, skip 'context' on stack; esp will point to the spoofed return address (gadgetAddress) 114 | 115 | returnHereFromGadget: 116 | sub esp, 12 117 | push [ebx + 4] // restore context.invokerReturnAddress as a return address 118 | mov ebx, [ebx - 4] // restore ebx from context.ebxBackup 119 | ret 120 | } 121 | } 122 | } 123 | } 124 | --------------------------------------------------------------------------------