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