Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

368 lines
9.7KB

  1. /*
  2. \todo:
  3. What if there's a loop in the function which loops back to an overwritten instruction?
  4. start:
  5. jmp hook_function
  6. bla
  7. jXX start+3
  8. */
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <assert.h>
  12. #include <stdbool.h>
  13. #include <Windows.h>
  14. #include "udis86.h"
  15. #include "list.h"
  16. #include "hook.h"
  17. #define MINIMUM_REQUIRED_FUNCTION_LENGTH_SHORT_HOOK 5
  18. #define MINIMUM_REQUIRED_FUNCTION_LENGTH_LONG_HOOK 16
  19. #define MAXIMUM_DISTANCE_FOR_SHORT_HOOK 0xFFFFFFFF
  20. #define PAGE_BOUNDARY 0x1000
  21. /**
  22. \brief Estimates the function size by checking opcodes until
  23. * an instruction that signifies the end of the function or
  24. * MINIMUM_REQUIRED_FUNCTION_LENGTH_LONG_HOOK bytes were found
  25. \return 0 on error
  26. */
  27. static size_t estimate_function_length(void* function);
  28. /**
  29. \brief Either builds the trampoline
  30. \param functionSize How much of the function shall be copied
  31. \param trampolineSize out parameter
  32. */
  33. static void* build_trampoline(void* function, size_t functionSize, size_t* trampolineSize);
  34. /**
  35. \brief jmp +X == +X + sizeof(jump type) + eip
  36. */
  37. static size_t get_jump_offset(ud_t* ud, size_t offsetOfInstr);
  38. /**
  39. \todo add loop & jecx (and possibly more) - though both are probably only used to jump back
  40. and currently is_jump is only called to from functions that don't care about that
  41. */
  42. static bool is_jump(enum ud_mneomic_code op);
  43. /**
  44. \brief Writes
  45. jmp [$+0] // $+0 == addr
  46. addr: xxx
  47. */
  48. static size_t write_x64_jump(unsigned char* where, void* toWhere);
  49. static bool is_end_of_function(enum ud_mneomic_code opc, ud_t* ud, size_t furthestJump, size_t instrOffset);
  50. static bool loops_into_overwritten_code(void* function);
  51. static size_t write_jcc_jump(const uint8_t* instruction, void* whereToWrite, void* originalAddr);
  52. static void fix_jcc_jumps(struct ListHead* instructions, struct ListHead* jccs);
  53. static int32_t get_rip_delta(ud_t* ud);
  54. static size_t write_instr_with_rip_delta(ud_t* ud, uint64_t absoluteAddr, const uint8_t* opcBuf, unsigned char* where);
  55. int hook(void* function, size_t functionLength, void* replacement, void** trampoline)
  56. {
  57. ptrdiff_t d = 0; // function minus replacement
  58. size_t needed = 0,
  59. trampolineSizeNeeded = 0;
  60. assert(function);
  61. assert(replacement);
  62. assert(trampoline);
  63. if(!functionLength)
  64. functionLength = estimate_function_length(function);
  65. /* is it actually possible to hook, space wise? */
  66. d = (ptrdiff_t)function - (ptrdiff_t)replacement;
  67. needed = d > MAXIMUM_DISTANCE_FOR_SHORT_HOOK ?
  68. MINIMUM_REQUIRED_FUNCTION_LENGTH_LONG_HOOK : MINIMUM_REQUIRED_FUNCTION_LENGTH_SHORT_HOOK;
  69. if(functionLength < needed)
  70. return NOT_ENOUGH_SPACE;
  71. if(loops_into_overwritten_code(function))
  72. return LOOPS_INTO_OVERWRITTEN_CODE;
  73. //! fixme: Remove in real code
  74. needed = MINIMUM_REQUIRED_FUNCTION_LENGTH_LONG_HOOK;
  75. printf("---\nNow build the trampoline\n");
  76. if((*trampoline = build_trampoline(function, needed, &trampolineSizeNeeded)) < 0)
  77. return (int)*trampoline;
  78. printf("Needed for trampoline %p: %d (Found %d)\n", *trampoline, trampolineSizeNeeded, functionLength);
  79. return SUCCESS;
  80. }
  81. static void* build_trampoline(void* function, size_t functionSize, size_t* trampolineSize)
  82. {
  83. ud_t ud;
  84. void* trampoline;
  85. assert(trampolineSize);
  86. ud_init(&ud);
  87. ud_set_input_buffer(&ud, function, (size_t)function | (PAGE_BOUNDARY - 1)); // Can't read further than page boundary - there be dragons
  88. ud_set_pc(&ud, (uint64_t)function);
  89. ud_set_mode(&ud, 64);
  90. ud_set_syntax(&ud, UD_SYN_INTEL);
  91. *trampolineSize = 0;
  92. if(!(trampoline = VirtualAlloc(NULL, PAGE_BOUNDARY, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)))
  93. return (void*)CANT_ALLOC;
  94. printf("trampoline @ %p\n", trampoline);
  95. size_t sz = 0;
  96. for(; sz <= functionSize;)
  97. {
  98. size_t instrLen = 0,
  99. offsetOfInstr = sz;
  100. bool neededFixup = false;
  101. uint8_t* addrForInstr = (uint8_t*)trampoline + *trampolineSize;
  102. const uint8_t* buf = NULL;
  103. assert(*trampolineSize < PAGE_BOUNDARY);
  104. if(!(instrLen = ud_disassemble(&ud)) || ud.error)
  105. return 0;
  106. buf = ud_insn_ptr(&ud);
  107. sz += instrLen;
  108. printf("%p %s", ud_insn_off(&ud), ud_insn_asm(&ud));
  109. // Check for XXX [rip + ?]
  110. const struct ud_operand* op = NULL;
  111. for(int i = 0; op = ud_insn_opr(&ud, i); i++)
  112. {
  113. if((op->type & UD_OP_REG) == UD_OP_REG)
  114. {
  115. if(op->base != UD_R_RIP)
  116. continue;
  117. neededFixup = true;
  118. int32_t ripDelta = get_rip_delta(&ud);
  119. void* absoluteAddr = ripDelta + ud_insn_off(&ud) + instrLen;
  120. printf("RIP + %x == %p\n", ripDelta, absoluteAddr);
  121. *trampolineSize += write_instr_with_rip_delta(&ud, absoluteAddr, buf, addrForInstr);
  122. break;
  123. }
  124. }
  125. // Check for jmp/jcc that would jump into the void after the trampoline
  126. size_t originalOff;
  127. if((originalOff = get_jump_offset(&ud, offsetOfInstr)) >= functionSize)
  128. {
  129. neededFixup = true;
  130. void* absoluteAddr = originalOff + (uint8_t*)function;
  131. #if 0
  132. printf("\nWould jump behind trampoline (to %p -- offs: %d) into the void\n", absoluteAddr, originalOff);
  133. printf("Instr in Trampoline @ %d jumps %d forward\n", *trampolineSize, originalOff);
  134. printf("%p - %p = %p\n", trampoline, ((ptrdiff_t)function + originalOff), (ptrdiff_t)trampoline - ((ptrdiff_t)function + originalOff));
  135. #endif
  136. // Normal jump
  137. if(buf[0] == 0xEB || buf[0] == 0xE9)
  138. {
  139. *trampolineSize += write_x64_jump(addrForInstr, absoluteAddr);
  140. }
  141. // jcc todo: jecxz, loop(?)
  142. else
  143. {
  144. *trampolineSize += write_jcc_jump(buf, addrForInstr, absoluteAddr);
  145. }
  146. }
  147. // todo: Fix relative calls
  148. printf("\n");
  149. // If it was a normal instruction, just copy it
  150. if(!neededFixup)
  151. {
  152. memcpy(addrForInstr, buf, instrLen);
  153. *trampolineSize += instrLen;
  154. }
  155. }
  156. // Build jmp back to original function
  157. *trampolineSize += write_x64_jump((uint8_t*)trampoline + *trampolineSize, (uint8_t*)function + sz);
  158. return trampoline;
  159. }
  160. static size_t write_instr_with_rip_delta(ud_t* ud, uint64_t value, const uint8_t* opcBuf, unsigned char* where)
  161. {
  162. // 5, d, 15, 1d, 25
  163. printf(value);
  164. /*XXX yyy, [addr]
  165. jmp behind
  166. addr:
  167. value
  168. behind:
  169. */
  170. return 0;
  171. }
  172. static int32_t get_rip_delta(ud_t* ud)
  173. {
  174. const struct ud_operand* op = NULL;
  175. int32_t ret = 0;
  176. for(int i = 0; op = ud_insn_opr(ud, i); i++)
  177. {
  178. // Only the RIP offset is interesting
  179. if((op->type & UD_OP_REG) != UD_OP_REG || op->base != UD_R_RIP)
  180. continue;
  181. return op->lval.sdword;
  182. }
  183. return ret;
  184. }
  185. static size_t write_jcc_jump(const uint8_t* instruction, void* whereToWrite, void* originalAddr)
  186. {
  187. /*
  188. jX $+?
  189. is transformed into:
  190. jnX behind
  191. jmp [originalAddr]
  192. originalAddr:
  193. ?
  194. behind:
  195. */
  196. // Stolen from https://github.com/TsudaKageyu/minhook/blob/master/src/trampoline.c
  197. uint8_t cond = ((instruction[0] != 0x0F ? instruction[0] : instruction[1]) & 0x0F);
  198. uint8_t newConditionOpc = 0x71 ^ cond; // Invert the condition in x64 mode to simplify the conditional jump logic.
  199. // jnx behind
  200. char jccTemplate[] = {newConditionOpc, 0x0F};
  201. memcpy((unsigned char*)whereToWrite, jccTemplate, sizeof(jccTemplate));
  202. size_t written = sizeof(jccTemplate);
  203. // jmp [originalAddr]
  204. written += write_x64_jump((unsigned char*)whereToWrite + written, originalAddr);
  205. return written;
  206. }
  207. static size_t get_jump_offset(ud_t* ud, size_t offsetOfInstr)
  208. {
  209. const struct ud_operand* op = NULL;
  210. for(int i = 0; op = ud_insn_opr(ud, i); i++)
  211. {
  212. if(op->type == UD_OP_JIMM)
  213. {
  214. return op->lval.sdword + offsetOfInstr + ud_insn_len(ud);
  215. }
  216. // all other types are weird jumps, like jumps to a register or to []
  217. }
  218. return 0;
  219. }
  220. static bool is_jump(enum ud_mneomic_code op)
  221. {
  222. return op >= UD_Ijo && op <= UD_Ijmp;
  223. }
  224. static bool is_end_of_function(enum ud_mneomic_code opc, ud_t* ud, size_t furthestJump, size_t instrOffset)
  225. {
  226. if(opc == UD_Iret && furthestJump <= instrOffset)
  227. return true;
  228. else if(opc == UD_Ijmp)
  229. {
  230. const struct ud_operand* op = ud_insn_opr(ud, 0);
  231. if(op->type == UD_OP_MEM || op->type == UD_OP_REG)
  232. return true;
  233. }
  234. // todo: 0xCC ?
  235. return false;
  236. }
  237. static size_t estimate_function_length(void* function)
  238. {
  239. size_t furthestJump = 0; //! Functions can have multiple return points. The library assumes that if a RET is hit and a jmp/jcc went over it, this RET isn't the last
  240. unsigned char* p = function;
  241. size_t funcLen = 0;
  242. ud_t ud;
  243. ud_init(&ud);
  244. ud_set_input_buffer(&ud, function, (size_t)function | (PAGE_BOUNDARY - 1)); // Can't read further than page boundary - there be dragons
  245. ud_set_pc(&ud, (uint64_t)function);
  246. ud_set_mode(&ud, 64);
  247. ud_set_syntax(&ud, UD_SYN_INTEL);
  248. while(funcLen < MINIMUM_REQUIRED_FUNCTION_LENGTH_LONG_HOOK && !ud_input_end(&ud))
  249. {
  250. size_t instrLen = 0,
  251. offsetOfInstr = funcLen;
  252. if(!(instrLen = ud_disassemble(&ud)) || ud.error)
  253. return 0;
  254. p += instrLen;
  255. funcLen += instrLen;
  256. printf("%p %s", ud_insn_off(&ud), ud_insn_asm(&ud));
  257. enum ud_mneomic_code op = ud_insn_mnemonic(&ud);
  258. if(is_jump(op))
  259. {
  260. size_t off = get_jump_offset(&ud, offsetOfInstr);
  261. if(off > furthestJump)
  262. furthestJump = off;
  263. }
  264. if(is_end_of_function(op, &ud, furthestJump, offsetOfInstr))
  265. {
  266. printf("end of function\n");
  267. break;
  268. }
  269. printf("\n");
  270. }
  271. return funcLen;
  272. }
  273. static size_t write_x64_jump(unsigned char* where, void* toWhere)
  274. {
  275. const char jmpMemTemplate[] = {0x48, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00};
  276. memcpy(where, jmpMemTemplate, sizeof(jmpMemTemplate));
  277. where += sizeof(jmpMemTemplate);
  278. memcpy(where, &toWhere, sizeof(toWhere));
  279. return sizeof(jmpMemTemplate)+sizeof(toWhere);
  280. }
  281. //! fixme:
  282. static bool loops_into_overwritten_code(void* function)
  283. {
  284. return false;
  285. }