|
| 1 | +/* |
| 2 | + * This demo shows kernel32!OutputDebugStringA can be an architecture-specific stub before reaching |
| 3 | + * the actual implementation in kernelbase.dll. |
| 4 | + * |
| 5 | + * Run "Demo.exe -Run OutputDebugStringHook -Engine=MSDetours" will fail this test, |
| 6 | + * because the original Microsoft Detours hooks only the kernel32 stub. |
| 7 | + * Run "Demo.exe -Run OutputDebugStringHook -Engine=SlimDetours" will pass this test. |
| 8 | + */ |
| 9 | + |
| 10 | +#include "Demo.h" |
| 11 | + |
| 12 | +#if defined(_AMD64_) || defined(_ARM64_) |
| 13 | + |
| 14 | +typedef |
| 15 | +VOID |
| 16 | +WINAPI |
| 17 | +FN_OutputDebugStringA( |
| 18 | + _In_opt_ LPCSTR lpOutputString); |
| 19 | + |
| 20 | +static FN_OutputDebugStringA* g_pfnOutputDebugStringA = NULL; |
| 21 | +static LONG volatile g_lOutputDebugStringACount = 0; |
| 22 | +static CONST UNICODE_STRING g_usKernel32 = RTL_CONSTANT_STRING(L"kernel32.dll"); |
| 23 | +static CONST UNICODE_STRING g_usKernelBase = RTL_CONSTANT_STRING(L"kernelbase.dll"); |
| 24 | +static ANSI_STRING g_asOutputDebugStringA = RTL_CONSTANT_STRING("OutputDebugStringA"); |
| 25 | + |
| 26 | +static |
| 27 | +VOID |
| 28 | +WINAPI |
| 29 | +Hooked_OutputDebugStringA( |
| 30 | + _In_opt_ LPCSTR lpOutputString) |
| 31 | +{ |
| 32 | + _InterlockedIncrement(&g_lOutputDebugStringACount); |
| 33 | + g_pfnOutputDebugStringA(lpOutputString); |
| 34 | +} |
| 35 | + |
| 36 | +#if defined(_AMD64_) |
| 37 | + |
| 38 | +static |
| 39 | +_Success_(return != FALSE) |
| 40 | +BOOL |
| 41 | +DecodeKernel32OutputDebugStringAStub( |
| 42 | + _In_ PBYTE pbCode, |
| 43 | + _Out_ PVOID * ppTarget) |
| 44 | +{ |
| 45 | + if ((pbCode[0] & 0xf0) != 0x40 || pbCode[1] != 0xff || pbCode[2] != 0x25) |
| 46 | + { |
| 47 | + return FALSE; |
| 48 | + } |
| 49 | + |
| 50 | + *ppTarget = *(UNALIGNED PVOID*)(pbCode + 7 + *(UNALIGNED INT32*) & pbCode[3]); |
| 51 | + return TRUE; |
| 52 | +} |
| 53 | + |
| 54 | +#elif defined(_ARM64_) |
| 55 | + |
| 56 | +static |
| 57 | +ULONG |
| 58 | +FetchArm64Opcode( |
| 59 | + _In_ PBYTE pbCode) |
| 60 | +{ |
| 61 | + return *(UNALIGNED ULONG*)pbCode; |
| 62 | +} |
| 63 | + |
| 64 | +static |
| 65 | +INT64 |
| 66 | +SignExtend( |
| 67 | + _In_ UINT64 Value, |
| 68 | + _In_ UINT Bits) |
| 69 | +{ |
| 70 | + UINT64 const Sign = 1ui64 << (Bits - 1); |
| 71 | + return (INT64)((Value & Sign) ? (Value | (~0ui64 << Bits)) : Value); |
| 72 | +} |
| 73 | + |
| 74 | +static |
| 75 | +_Success_(return != FALSE) |
| 76 | +BOOL |
| 77 | +DecodeArm64ImportThunk( |
| 78 | + _In_ PBYTE pbCode, |
| 79 | + _Out_ PVOID * ppTarget) |
| 80 | +{ |
| 81 | + ULONG const Opcode = FetchArm64Opcode(pbCode); |
| 82 | + ULONG const Opcode2 = FetchArm64Opcode(pbCode + 4); |
| 83 | + ULONG const Opcode3 = FetchArm64Opcode(pbCode + 8); |
| 84 | + UINT64 pageLow2; |
| 85 | + UINT64 pageHigh19; |
| 86 | + INT64 page; |
| 87 | + UINT64 offset; |
| 88 | + PBYTE pbTarget; |
| 89 | + |
| 90 | + if ((Opcode & 0x9f00001f) != 0x90000010 || |
| 91 | + (Opcode2 & 0xffe003ff) != 0xf9400210 || |
| 92 | + Opcode3 != 0xd61f0200) |
| 93 | + { |
| 94 | + return FALSE; |
| 95 | + } |
| 96 | + |
| 97 | + pageLow2 = (Opcode >> 29) & 3; |
| 98 | + pageHigh19 = (Opcode >> 5) & ~(~0ui64 << 19); |
| 99 | + page = SignExtend((pageHigh19 << 2) | pageLow2, 21) << 12; |
| 100 | + offset = ((Opcode2 >> 10) & ~(~0ui64 << 12)) << 3; |
| 101 | + pbTarget = (PBYTE)((ULONG_PTR)pbCode & ~(ULONG_PTR)0xfff) + page + offset; |
| 102 | + |
| 103 | + *ppTarget = *(UNALIGNED PVOID*)pbTarget; |
| 104 | + return TRUE; |
| 105 | +} |
| 106 | + |
| 107 | +static |
| 108 | +_Success_(return != FALSE) |
| 109 | +BOOL |
| 110 | +DecodeKernel32OutputDebugStringAStub( |
| 111 | + _In_ PBYTE pbCode, |
| 112 | + _Out_ PVOID * ppTarget) |
| 113 | +{ |
| 114 | + ULONG const Opcode = FetchArm64Opcode(pbCode); |
| 115 | + INT64 branchOffset; |
| 116 | + |
| 117 | + if ((Opcode & 0xfc000000) != 0x14000000) |
| 118 | + { |
| 119 | + return FALSE; |
| 120 | + } |
| 121 | + |
| 122 | + // B <imm26> |
| 123 | + branchOffset = SignExtend((Opcode & 0x03ffffffULL) << 2, 28); |
| 124 | + return DecodeArm64ImportThunk(pbCode + branchOffset, ppTarget); |
| 125 | +} |
| 126 | + |
| 127 | +#endif |
| 128 | + |
| 129 | +static |
| 130 | +HRESULT |
| 131 | +RunOutputDebugStringAHook( |
| 132 | + _In_ DEMO_ENGINE_TYPE EngineType, |
| 133 | + _In_ FN_OutputDebugStringA* pfnKernel32, |
| 134 | + _In_ FN_OutputDebugStringA* pfnKernelBase) |
| 135 | +{ |
| 136 | + HRESULT hr; |
| 137 | + |
| 138 | + _InterlockedExchange(&g_lOutputDebugStringACount, 0); |
| 139 | + g_pfnOutputDebugStringA = pfnKernel32; |
| 140 | + |
| 141 | + hr = HookTransactionBegin(EngineType); |
| 142 | + if (FAILED(hr)) |
| 143 | + { |
| 144 | + return hr; |
| 145 | + } |
| 146 | + hr = HookAttach(EngineType, TRUE, (PVOID*)&g_pfnOutputDebugStringA, Hooked_OutputDebugStringA); |
| 147 | + if (FAILED(hr)) |
| 148 | + { |
| 149 | + HookTransactionAbort(EngineType); |
| 150 | + return hr; |
| 151 | + } |
| 152 | + hr = HookTransactionCommit(EngineType); |
| 153 | + if (FAILED(hr)) |
| 154 | + { |
| 155 | + return hr; |
| 156 | + } |
| 157 | + |
| 158 | + pfnKernelBase("OutputDebugStringHook: kernelbase"); |
| 159 | + pfnKernel32("OutputDebugStringHook: kernel32"); |
| 160 | + |
| 161 | + hr = HookTransactionBegin(EngineType); |
| 162 | + if (SUCCEEDED(hr)) |
| 163 | + { |
| 164 | + hr = HookAttach(EngineType, FALSE, (PVOID*)&g_pfnOutputDebugStringA, Hooked_OutputDebugStringA); |
| 165 | + if (SUCCEEDED(hr)) |
| 166 | + { |
| 167 | + hr = HookTransactionCommit(EngineType); |
| 168 | + } else |
| 169 | + { |
| 170 | + HookTransactionAbort(EngineType); |
| 171 | + } |
| 172 | + } |
| 173 | + return S_OK; |
| 174 | +} |
| 175 | + |
| 176 | +TEST_FUNC(OutputDebugStringHook) |
| 177 | +{ |
| 178 | + NTSTATUS Status; |
| 179 | + HRESULT hr; |
| 180 | + DEMO_ENGINE_TYPE EngineType; |
| 181 | + PVOID hKernel32, hKernelBase; |
| 182 | + FN_OutputDebugStringA *pfnKernel32OutputDebugStringA, *pfnKernelBaseOutputDebugStringA; |
| 183 | + PVOID pStubTarget; |
| 184 | + |
| 185 | + if (FAILED(GetEngineTypeFromArgs(TEST_PARAMETER_ARGC, TEST_PARAMETER_ARGV, &EngineType))) |
| 186 | + { |
| 187 | + TEST_SKIP("Invalid engine type"); |
| 188 | + return; |
| 189 | + } |
| 190 | + |
| 191 | + Status = LdrGetDllHandle(NULL, NULL, (PUNICODE_STRING)&g_usKernel32, &hKernel32); |
| 192 | + if (!NT_SUCCESS(Status)) |
| 193 | + { |
| 194 | + TEST_SKIP("LdrGetDllHandle for kernel32.dll failed with 0x%08lX", Status); |
| 195 | + return; |
| 196 | + } |
| 197 | + Status = LdrGetDllHandle(NULL, NULL, (PUNICODE_STRING)&g_usKernelBase, &hKernelBase); |
| 198 | + if (!NT_SUCCESS(Status)) |
| 199 | + { |
| 200 | + TEST_SKIP("LdrGetDllHandle for kernelbase.dll failed with 0x%08lX", Status); |
| 201 | + return; |
| 202 | + } |
| 203 | + |
| 204 | + Status = LdrGetProcedureAddress(hKernel32, |
| 205 | + &g_asOutputDebugStringA, |
| 206 | + 0, |
| 207 | + (PVOID*)&pfnKernel32OutputDebugStringA); |
| 208 | + if (!NT_SUCCESS(Status)) |
| 209 | + { |
| 210 | + TEST_SKIP("LdrGetProcedureAddress for kernel32!OutputDebugStringA failed with 0x%08lX", Status); |
| 211 | + return; |
| 212 | + } |
| 213 | + Status = LdrGetProcedureAddress(hKernelBase, |
| 214 | + &g_asOutputDebugStringA, |
| 215 | + 0, |
| 216 | + (PVOID*)&pfnKernelBaseOutputDebugStringA); |
| 217 | + if (!NT_SUCCESS(Status)) |
| 218 | + { |
| 219 | + TEST_SKIP("LdrGetProcedureAddress for kernelbase!OutputDebugStringA failed with 0x%08lX", Status); |
| 220 | + return; |
| 221 | + } |
| 222 | + if (!DecodeKernel32OutputDebugStringAStub((PBYTE)pfnKernel32OutputDebugStringA, &pStubTarget)) |
| 223 | + { |
| 224 | + TEST_SKIP("kernel32!OutputDebugStringA is not the expected stub"); |
| 225 | + return; |
| 226 | + } |
| 227 | + |
| 228 | + if (pStubTarget != (PVOID)pfnKernelBaseOutputDebugStringA) |
| 229 | + { |
| 230 | + TEST_SKIP("kernel32!OutputDebugStringA stub does not target kernelbase!OutputDebugStringA"); |
| 231 | + return; |
| 232 | + } |
| 233 | + |
| 234 | + hr = RunOutputDebugStringAHook(EngineType, |
| 235 | + pfnKernel32OutputDebugStringA, |
| 236 | + pfnKernelBaseOutputDebugStringA); |
| 237 | + if (FAILED(hr)) |
| 238 | + { |
| 239 | + TEST_FAIL("Hook OutputDebugStringA failed with 0x%08lX", hr); |
| 240 | + return; |
| 241 | + } |
| 242 | + |
| 243 | + TEST_OK(g_lOutputDebugStringACount == 2); |
| 244 | +} |
| 245 | + |
| 246 | +#endif |
0 commit comments