1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "HardHook.h"
7 #include "ods.h"
8
9 void *HardHook::pCode = NULL;
10 unsigned int HardHook::uiCode = 0;
11
12 const int HardHook::CODEREPLACESIZE = 6;
13 const int HardHook::CODEPROTECTSIZE = 16;
14
15 /**
16 * @brief Constructs a new hook without actually injecting.
17 */
HardHook()18 HardHook::HardHook() : bTrampoline(false), call(0), baseptr(NULL) {
19 for (int i = 0; i < CODEREPLACESIZE; ++i) {
20 orig[i] = replace[i] = 0;
21 }
22
23 // assert(CODEREPLACESIZE == sizeof(orig) / sizeof(orig[0]));
24 // assert(CODEREPLACESIZE == sizeof(replace) / sizeof(replace[0]));
25 }
26
27 /**
28 * @brief Constructs a new hook by injecting given replacement function into func.
29 * @see HardHook::setup
30 * @param func Funktion to inject replacement into.
31 * @param replacement Function to inject into func.
32 */
HardHook(voidFunc func,voidFunc replacement)33 HardHook::HardHook(voidFunc func, voidFunc replacement)
34 : bTrampoline(false), call(0), baseptr(NULL) {
35 for (int i = 0; i < CODEREPLACESIZE; ++i)
36 orig[i] = replace[i] = 0;
37 setup(func, replacement);
38 }
39
40 /**
41 * @return Number of extra bytes.
42 */
modrmbytes(unsigned char a,unsigned char b)43 static unsigned int modrmbytes(unsigned char a, unsigned char b) {
44 unsigned char lower = (a & 0x0f);
45 if (a >= 0xc0) {
46 return 0;
47 } else if (a >= 0x80) {
48 if ((lower == 4) || (lower == 12))
49 return 5;
50 else
51 return 4;
52 } else if (a >= 0x40) {
53 if ((lower == 4) || (lower == 12))
54 return 2;
55 else
56 return 1;
57
58 } else {
59 if ((lower == 4) || (lower == 12)) {
60 if ((b & 0x07) == 0x05)
61 return 5;
62 else
63 return 1;
64 } else if ((lower == 5) || (lower == 13))
65 return 4;
66 return 0;
67 }
68 }
69
70 /**
71 * @brief Tries to construct a trampoline from original code.
72 *
73 * A trampoline is the replacement code that features the original code plus
74 * a jump back to the original instructions that follow.
75 * It is called to execute the original behavior. As it is a replacement for
76 * the original, the original can then be overwritten.
77 * The size of the trampoline is at least CODEREPLACESIZE. Thus, CODEREPLACESIZE
78 * bytes of the original code can afterwards be overwritten (and the trampoline
79 * called after those instructions for the original logic).
80 * CODEREPLACESIZE has to be smaller than CODEPROTECTSIZE.
81 *
82 * As commands must not be destroyed they have to be disassembled to get their length.
83 * All encountered commands will be part of the trampoline and stored in pCode (shared
84 * for all trampolines).
85 *
86 * If code is encountered that can not be moved into the trampoline (conditionals etc.)
87 * construction fails and NULL is returned. If enough commands can be saved the
88 * trampoline is finalized by appending a jump back to the original code. The return value
89 * in this case will be the address of the newly constructed trampoline.
90 *
91 * pCode + offset to trampoline:
92 * [SAVED CODE FROM ORIGINAL which is >= CODEREPLACESIZE bytes][JUMP BACK TO ORIGINAL CODE]
93 *
94 * @param porig Original code
95 * @return Pointer to trampoline on success. NULL if trampoline construction failed.
96 */
cloneCode(void ** porig)97 void *HardHook::cloneCode(void **porig) {
98
99 if (! pCode || uiCode > 4000) {
100 pCode = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
101 uiCode = 0;
102 }
103 // If we have no memory to clone to, return.
104 if (! pCode) {
105 return NULL;
106 }
107
108 unsigned char *o = (unsigned char *) *porig;
109
110 DWORD origProtect;
111 if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
112 fods("HardHook: CloneCode failed; failed to make original code read and executable");
113 return NULL;
114 }
115
116 // Follow relative jumps to next instruction. On execution it doesn't make
117 // a difference if we actually perform all the jumps or directly jump to the
118 // end of the chain. Hence these jumps need not be part of the trampoline.
119 while (*o == 0xe9) { // JMP
120 unsigned char *tmp = o;
121 int *iptr = reinterpret_cast<int *>(o+1);
122 o += *iptr + 5;
123
124 fods("HardHook: CloneCode: Skipping jump from %p to %p", *porig, o);
125 *porig = o;
126
127 // Assume jump took us out of our read enabled zone, get rights for the new one
128 DWORD tempProtect;
129 VirtualProtect(tmp, CODEPROTECTSIZE, origProtect, &tempProtect);
130 if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
131 fods("HardHook: CloneCode failed; failed to make jump target code read and executable");
132 return NULL;
133 }
134 }
135
136 unsigned char *n = (unsigned char *) pCode;
137 n += uiCode;
138 unsigned int idx = 0;
139
140 do {
141 unsigned char opcode = o[idx];
142 unsigned char a = o[idx+1];
143 unsigned char b = o[idx+2];
144 unsigned int extra = 0;
145
146 switch (opcode) {
147 case 0x50: // PUSH
148 case 0x51:
149 case 0x52:
150 case 0x53:
151 case 0x54:
152 case 0x55:
153 case 0x56:
154 case 0x57:
155 case 0x58: // POP
156 case 0x59:
157 case 0x5a:
158 case 0x5b:
159 case 0x5c:
160 case 0x5d:
161 case 0x5e:
162 case 0x5f:
163 break;
164 case 0x6a: // PUSH immediate
165 extra = 1;
166 break;
167 case 0x68: // PUSH immediate
168 extra = 4;
169 break;
170 case 0x81: // CMP immediate
171 extra = modrmbytes(a,b) + 5;
172 break;
173 case 0x83: // CMP
174 extra = modrmbytes(a,b) + 2;
175 break;
176 case 0x8b: // MOV
177 extra = modrmbytes(a,b) + 1;
178 break;
179 default: {
180 int rmop = ((a>>3) & 7);
181 if (opcode == 0xff && rmop == 6) { // PUSH memory
182 extra = modrmbytes(a,b) + 1;
183 break;
184 }
185
186 fods("HardHook: CloneCode failed; Unknown opcode %02x at %d: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
187 opcode, idx, o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7], o[8], o[9], o[10], o[11]);
188 DWORD tempProtect;
189 VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect);
190 return NULL;
191 break;
192 }
193 }
194
195 n[idx] = opcode;
196 ++idx;
197
198 for (unsigned int i = 0; i < extra; ++i)
199 n[idx+i] = o[idx+i];
200 idx += extra;
201
202 } while (idx < CODEREPLACESIZE);
203
204 DWORD tempProtect;
205 VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect);
206
207 // Add a relative jmp back to the original code, to after the copied code
208 n[idx++] = 0xe9;
209 int *iptr = reinterpret_cast<int *>(&n[idx]);
210 const int JMP_OP_SIZE = 5;
211 int offs = o - n - JMP_OP_SIZE;
212 *iptr = offs;
213 idx += 4;
214
215 uiCode += idx;
216
217 FlushInstructionCache(GetCurrentProcess(), n, idx);
218
219 fods("HardHook: trampoline creation successful at %p", n);
220
221 return n;
222 }
223
224 /**
225 * @brief Makes sure the given replacement function is run once func is called.
226 *
227 * Tries to construct a trampoline for the given function (@see HardHook::cloneCode)
228 * and then injects replacement function calling code into the first 6 bytes of the
229 * original function (@see HardHook::inject).
230 *
231 * @param func Pointer to function to redirect.
232 * @param replacement Pointer to code to redirect to.
233 */
setup(voidFunc func,voidFunc replacement)234 void HardHook::setup(voidFunc func, voidFunc replacement) {
235 if (baseptr)
236 return;
237
238 fods("HardHook: Setup: Asked to replace %p with %p", func, replacement);
239
240 unsigned char *fptr = reinterpret_cast<unsigned char *>(func);
241 unsigned char *nptr = reinterpret_cast<unsigned char *>(replacement);
242
243 call = (voidFunc) cloneCode((void **) &fptr);
244
245 if (call) {
246 bTrampoline = true;
247 } else {
248 // Could not create a trampoline. Use alternative method instead.
249 // This alternative method is dependant on the replacement code
250 // restoring before calling the original. Otherwise we get a jump recursion
251 bTrampoline = false;
252 call = func;
253 }
254
255 DWORD origProtect;
256 if (VirtualProtect(fptr, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
257 replace[0] = 0x68; // PUSH immediate 1 Byte
258 unsigned char **iptr = reinterpret_cast<unsigned char **>(&replace[1]);
259 *iptr = nptr; // (imm. value = nptr) 4 Byte
260 replace[5] = 0xc3; // RETN 1 Byte
261
262 // Save original 6 bytes at start of original function
263 for (int i = 0; i < CODEREPLACESIZE; ++i)
264 orig[i] = fptr[i];
265
266 baseptr = fptr;
267
268 inject(true);
269
270 DWORD tempProtect;
271 VirtualProtect(fptr, CODEPROTECTSIZE, origProtect, &tempProtect);
272 } else {
273 fods("HardHook: setup failed; failed to make original code read and executable");
274 }
275 }
276
setupInterface(IUnknown * unkn,LONG funcoffset,voidFunc replacement)277 void HardHook::setupInterface(IUnknown *unkn, LONG funcoffset, voidFunc replacement) {
278 fods("HardHook: setupInterface: Replacing %p function #%ld", unkn, funcoffset);
279 void **ptr = reinterpret_cast<void **>(unkn);
280 ptr = reinterpret_cast<void **>(ptr[0]);
281 setup(reinterpret_cast<voidFunc>(ptr[funcoffset]), replacement);
282 }
283
reset()284 void HardHook::reset() {
285 baseptr = 0;
286 bTrampoline = false;
287 call = NULL;
288 for (int i = 0; i < CODEREPLACESIZE; ++i) {
289 orig[i] = replace[i] = 0;
290 }
291 }
292
293 /**
294 * @brief Injects redirection code into the target function.
295 *
296 * Replaces the first 6 Bytes of the function indicated by baseptr
297 * with the replacement code previously generated (usually a jump
298 * to mumble code). If a trampoline is available this injection is not needed
299 * as control flow was already permanently redirected by HardHook::setup .
300 *
301 * @param force Perform injection even when trampoline is available.
302 */
inject(bool force)303 void HardHook::inject(bool force) {
304 if (! baseptr)
305 return;
306 if (! force && bTrampoline)
307 return;
308
309 DWORD origProtect;
310 if (VirtualProtect(baseptr, CODEREPLACESIZE, PAGE_EXECUTE_READWRITE, &origProtect)) {
311 for (int i = 0; i < CODEREPLACESIZE; ++i) {
312 baseptr[i] = replace[i]; // Replace with jump to new code
313 }
314
315 DWORD tempProtect;
316 VirtualProtect(baseptr, CODEREPLACESIZE, origProtect, &tempProtect);
317
318 FlushInstructionCache(GetCurrentProcess(), baseptr, CODEREPLACESIZE);
319 }
320
321 // Verify that the injection was successful
322 for (int i = 0; i < CODEREPLACESIZE; ++i) {
323 if (baseptr[i] != replace[i]) {
324 fods("HardHook: Injection failure noticed at byte %d", i);
325 }
326 }
327 }
328
329 /**
330 * @brief Restores the original code in a target function.
331 *
332 * Restores the first 6 Bytes of the function indicated by baseptr
333 * from previously stored original code in orig. If a trampoline is available this
334 * restoration is not needed as trampoline will correctly restore control
335 * flow.
336 *
337 * @param force If true injection will be reverted even when trampoline is available.
338 */
restore(bool force)339 void HardHook::restore(bool force) {
340
341 if (! baseptr)
342 return;
343 if (! force && bTrampoline)
344 return;
345
346 DWORD origProtect;
347 if (VirtualProtect(baseptr, CODEREPLACESIZE, PAGE_EXECUTE_READWRITE, &origProtect)) {
348 for (int i = 0; i < CODEREPLACESIZE; ++i)
349 baseptr[i] = orig[i];
350 DWORD tempProtect;
351 VirtualProtect(baseptr, CODEREPLACESIZE, origProtect, &tempProtect);
352
353 FlushInstructionCache(GetCurrentProcess(), baseptr, CODEREPLACESIZE);
354 }
355 }
356
print()357 void HardHook::print() {
358 fods("HardHook: code replacement: %02x %02x %02x %02x %02x => %02x %02x %02x %02x %02x (currently effective: %02x %02x %02x %02x %02x)",
359 orig[0], orig[1], orig[2], orig[3], orig[4],
360 replace[0], replace[1], replace[2], replace[3], replace[4],
361 baseptr[0], baseptr[1], baseptr[2], baseptr[3], baseptr[4]);
362 }
363
364 /**
365 * @brief Checks whether injected code is in good shape and injects if not yet injected.
366 *
367 * If injected code is not found injection is attempted unless 3rd party overwrote
368 * original code at injection location.
369 */
check()370 void HardHook::check() {
371 if (memcmp(baseptr, replace, CODEREPLACESIZE) != 0) {
372 // The instructions do not match our replacement instructions
373 // If they match the original code, inject our hook.
374 if (memcmp(baseptr, orig, CODEREPLACESIZE) == 0) {
375 fods("HardHook: Reinjecting hook into function %p", baseptr);
376 inject(true);
377 } else {
378 fods("HardHook: Function %p replaced by third party. Lost injected hook.");
379 }
380 }
381 }
382