Advertisement
Guest User

Untitled

a guest
Apr 9th, 2014
713
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.18 KB | None | 0 0
  1. antiflag.sys - Writing a kernel driver to remove the LLKHF_INJECTED flag
  2. ------------------------------------------------------------------------
  3.  
  4. #####################################################################
  5. written by UsualSuspect for reddit.com/r/reverseengineering, 11/29/10
  6. May be copied or redistributed as long as the full document as it is
  7. stays intact.
  8. #####################################################################
  9.  
  10. Introduction
  11. ------------
  12.  
  13. A while ago I came across a game that checked for injected keyboard input. It did so by the probably well-known method of using SetWindowsHookEx() with a hook type of WH_KEYBOARD_LL. When a key is pressed, Windows notifies all hooks and supplies the following data structure:
  14.  
  15.  
  16. typedef struct tagKBDLLHOOKSTRUCT {
  17. DWORD vkCode;
  18. DWORD scanCode;
  19. DWORD flags;
  20. DWORD time;
  21. ULONG_PTR dwExtraInfo;
  22. } KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT;
  23.  
  24. The only field of interest for us is flags. When keyboard input is generated by a program using SendInput() (amongst others, as far as I know), Windows sets a flag, which is LLKHF_INJECTED telling the hook procedure the origin of the given input is a program. The problem now is that if we want to cheat in a game, we usually want to fake input such as mouse movements or key presses. Using this hook though, the game would be able to find out we're cheating and possibly ban us. So I had to find I a way to remove this flag. There are dozens of spots where this could be done. We could for example hook the LowLevelKeyboardProc, remove the flag and pass the data on to the original proc.
  25.  
  26. The target I was writing the hack for was very careful though and checked each and every function for hooks. It hashed the memory, it made sure system DLLs (such as user32.dll which is of interest for us) weren't tampered with and so on. Although all of this took place in usermode and we could simply defeat all detection mechanisms in place, I decided to take another route. What I wanted to do is write a kernel driver to deal with this issue and thus transparently (for user mode applications) remove the flag. This way, I didn't have to deal with any detection mechanism the game implemented now or in the future.
  27.  
  28. Kernel land unfortunately was completely new to me, as I restricted my reverse engineering and hacking exclusivly to user mode so I had to find a way to get started. At this point, I'd like to point out I'm probably wrong about a few assumptions I made and I may state things that are simply wrong in this paper. Feel free to correct me, if this is the case. In the end though, my kernel driver worked, so I was satisfied.
  29.  
  30. Getting started
  31. ---------------
  32.  
  33. The goal was writing a kernel driver to globally remove the LLKHF_INJECTED flag so no game or application was enable to distinguish between regular and injected input. Because I've never done kernel stuff I had to read up on it and got the book "Rootkits: Subverting the Windows kernel" by Hoglund and Butler. There I learned how to set up my build environment, how to create the most basic driver and other important things like that. The book is what got me started and I heartily recommend it to readers wanting to do the same. Although I didn't have any hands-on practice with kernel mode code, I knew about a few things in theory, such as SSDT hooks, interrupt hooks, problems that may arise from paging (meaning memory could be paged out, that is written to the pagefile on the disk, and not be available) et cetera.
  34.  
  35. With all of this in mind, I set up my environment which consisted of a plain XP SP2 installed on a VirtualBox to which I connected with kd (the kernel debugger from Microsoft found in the Debugging Tools for Windows) using VirtualKD, which is a modification for VMWare/VirtualBox to allow for (way?) faster data transfer. You want to do the same as writing kernel code is tedious and an error usually means a bluescreen. There is no way I would have gone through with it if I didn't have a virtual machine to test my code.
  36.  
  37. In order to quickly test if I was successful, I wrote a little tool that installed a global keyboard hook and just tells us if the given input was injected (= flag set) or not, so I didn't have to fire up the game all the time.
  38.  
  39. Finding the weak spot
  40. ---------------------
  41.  
  42. Now that my setup was running, I needed to find where to "apply the wrench", i.e. where to apply hooks to patch out the LLKHF_INJECTED flag. To do so, I followed the obvious path of checking what SetWindowsHookEx() does. Following xrefs inside it, I quickly found out it jumps to the kernel counterpart of user32.dll, which happens to be win32k.sys, the kernel driver for GUI-related code.
  43.  
  44. .text:77D3E60D _NtUserSetWindowsHookEx@24 proc near ; CODE XREF: _SetWindowsHookEx(x,x,x,x,x,x)+2Ep
  45. .text:77D3E60D mov eax, 1225h
  46. .text:77D3E612 mov edx, 7FFE0300h
  47. .text:77D3E617 call dword ptr [edx]
  48. .text:77D3E619 retn 18h
  49. .text:77D3E619 _NtUserSetWindowsHookEx@24 endp
  50.  
  51. SetWindowsHookEx() goes through a few calls and ends up here. What this does is call the kernel, so I fired up IDA and checked win32k.sys. This all was purely random, I just followed xrefs, looked at the exports/functions (let IDA download symbols!) etc. In the end I found a few interesting functions, for example:
  52.  
  53. NtUserCallNextHookEx
  54. xxxCallNextHookEx
  55. xxxCallNextHookEx2
  56.  
  57. and the key function turned out to be
  58.  
  59. xxxHkCallHook
  60.  
  61. There I found several calls that looked interesting, and one in particular was:
  62.  
  63. .text:BF9073E5 call fnHkINLPKBDLLHOOKSTRUCT(x,x,x,x,x)
  64.  
  65. KBDLLHOOKSTRUCT? Now that sounds exactly like the struct we want to modify! The function itself is rather short and doesn't really do more than calling KeUserModeCallback(). KeUserModeCallback() is the function the kernel uses to call (as the name implies) callbacks in the user mode.
  66.  
  67. The call:
  68.  
  69. .text:BF92A693 lea eax, [ebp+output_len]
  70. .text:BF92A696 push eax ; output_len
  71. .text:BF92A697 lea eax, [ebp+output]
  72. .text:BF92A69A push eax ; output
  73. .text:BF92A69B push 36 ; inputlen
  74. .text:BF92A69D lea eax, [ebp+input]
  75. .text:BF92A6A0 push eax ; input
  76. .text:BF92A6A1 push 2Dh ; Call 77D5894D @ User32.dll
  77. .text:BF92A6A1 ; = DispatchHook()
  78. .text:BF92A6A3 call ds:KeUserModeCallback(x,x,x,x,x)
  79.  
  80. Thanks to some paper at UNINFORMED, I found the parameters for this function. 0x2D is the number of the user mode callback and it turned out to be user32!DispatchHook(). So if you feel like going the easy way, just modify this function in user mode to patch out the flag and you're done. Of course, this would be to easy.
  81.  
  82. So what do we do now? There are a handful of functions used in calling hooks. One function, fnHkINLPKBDLLHOOKSTRUCT() most probably is passed a KBDLLHOOKSTRUCT which should contain our flag. If this is the case, we found the weak spot where the actual hook struct is passed through to the user mode. That means we could hook this function, patch out the flag and the structure would end up in user mode with the LLKHF_INJECTED flag removed. To verify this idea, I fired up WinDbg and tried to look at the code.
  83.  
  84. kd> u BF92A6A3
  85. win32k!fnHkINLPKBDLLHOOKSTRUCT+0x4c:
  86. bf92a6a3 ?? ???
  87. ^ Memory access error in 'u BF92A6A3'
  88.  
  89. Huh? But this is inside win32k.sys, just show me the call already? As it turns out, this wouldn't be as easy as that. After googling my fingers sore, asking lots of sources and more, someone in /r/reverseengineering enlightened me. The problem is that win32k.sys isn't mapped into all processes. So if I happen to break in a process that doesn't have a GUI, there will be no win32k.sys mapped into the process' memory and I can't disassemble it. The solution is simple. Find a GUI process, set the process context to it and you're good. This is how it's done:
  90.  
  91. kd> !process 0 0
  92. **** NT ACTIVE PROCESS DUMP ****
  93.  
  94. ...
  95.  
  96. PROCESS 86565020 SessionId: 0 Cid: 0250 Peb: 7ffdf000 ParentCid: 0178
  97. DirBase: 0bc7e000 ObjectTable: e100d928 HandleCount: 334.
  98. Image: csrss.exe
  99.  
  100. !process 0 0 shows the running processes. From what I learned later on, csrss.exe is a GUI process, so we can just use its context. To switch, we simply do
  101.  
  102. kd> .process 86565020
  103. Implicit process is now 86565020
  104. WARNING: .cache forcedecodeuser is not enabled
  105.  
  106. And try disassembling the call again:
  107.  
  108. kd> u BF92A6A3
  109. win32k!fnHkINLPKBDLLHOOKSTRUCT+0x4c:
  110. bf92a6a3 ff1534aa98bf call dword ptr [win32k!_imp__KeUserModeCallback (bf98aa34)]
  111. bf92a6a9 8bf0 mov esi,eax
  112. bf92a6ab e88a63edff call win32k!EnterCrit (bf800a3a)
  113. ...
  114.  
  115. Nice! So now, let's place a bp (which can be done without changing the context to a GUI process, btw.) and examine the input buffer passed.
  116.  
  117. kd> bp bf92a6a3
  118. WARNING: Software breakpoints on session addresses can cause bugchecks.
  119. Use hardware execution breakpoints (ba e) if possible.
  120. kd> g
  121. Breakpoint 1 hit
  122. win32k!fnHkINLPKBDLLHOOKSTRUCT+0x4c:
  123. bf92a6a3 ff1534aa98bf call dword ptr [win32k!_imp__KeUserModeCallback (bf98aa34)]
  124.  
  125. Alright, right before the call. We know KeUserModeCallback() gets 5 arguments, so let's see what is on the stack. The first should be 0x2D for DispatchHook(), followed by the address to the input buffer.
  126.  
  127. kd> dd esp L5
  128. ed63faec 0000002d ed63fb0c 00000024 ed63fb38
  129. ed63fafc ed63fb34
  130.  
  131. which is true. So now let's see what is in the input buffer.
  132.  
  133. kd> dd ed63fb0c L8
  134. ed63fb0c 000d0000 00000100 00401320 77d26df4
  135. ed63fb1c 00000025 00000012 00000010 00019816
  136.  
  137. I don't know what the first 4 DWORDs are but the next 4 DWORDs look promising. My tool sends an injected VK_LEFT which is 0x25. 0x12 is the scancode (which can differ, it depends on quite a few things from what I read) and the 3rd DWORD should be the flags then. If we search for the value of LLKHF_INJECTED, we will see that it is the value 0x10, which is awesome. Obviously, we know the 7th DWORD in the input buffer is the flags field. So now we know where to apply our hook and where to find the data we need to modify.
  138.  
  139. Writing the kernel driver
  140. -------------------------
  141.  
  142. Writing the kernel driver was obviously the most difficult part for me. Finding the weak spot was (even though it probably was not more than educated guesses) rather easy. Writing code that does the patching though is more difficult. The first issue that arises is the one we encountered before: If our kernel driver needs to modify code inside win32k.sys, we have to run inside a process context of a GUI process. Thus, we need to be able to find a GUI process and eventually attach to it.
  143.  
  144. Finding a GUI process
  145. ---------------------
  146.  
  147. In theory, finding a GUI process is easy. The process list on Windows is a double linked list of EPROCESS structs. If you want to see what it looks like, type
  148.  
  149. kd> dt nt!_EPROCESS
  150. +0x000 Pcb : _KPROCESS
  151. ...
  152. +0x088 ActiveProcessLinks : _LIST_ENTRY
  153. ...
  154. +0x130 Win32Process : Ptr32 Void
  155.  
  156. Two important fields of the structure are listed. The first one being ActiveProcessLinks which is a structure of two pointers, flink (front link) and blink (back link). One thing I had to learn by mistake was that the points inside the LIST_ENTRY structure point to the ActiveProcessLinks field, and not at the beginning of the _EPROCESS structure. More on that later.
  157. The second important field is Win32Process which points to some Win32Process structure or something if (and only if!) the process is a GUI process. Now we just need to find some handle (any handle) to an _EPROCESS structure, walk the double linkest list until we find one with a Win32Process field different from 0. Getting a handle to an _EPROCESS structure is easy, we can just use PsGetCurrentProcess().
  158.  
  159. Without further ado, here is the code:
  160.  
  161. PEPROCESS FindGUIProcess()
  162. {
  163. ULONG CurProc;
  164.  
  165. CurProc = PsGetCurrentProcess();
  166. DbgPrint("Starting at %08x\n",CurProc);
  167.  
  168. while(*(ULONG*)(CurProc+EPROCESS_WIN32PROC_OFFSET) == 0)
  169. {
  170. CurProc = *(ULONG*)(CurProc+EPROCESS_LINK_OFFSET);
  171. CurProc -= EPROCESS_LINK_OFFSET;
  172. DbgPrint("Next proc at %08x\n",CurProc);
  173. }
  174.  
  175. //CurProc is a GUI proc!
  176. return CurProc;
  177. }
  178.  
  179. We get ourselves a handle to the current process and then start the while loop. EPROCESS_LINK_OFFSET is 0x88 for XP SP2 as we could see above. EPROCESS_WIN32PROC_OFFSET is 0x130 as we've seen above, too. If it is 0, the process is not a GUI process, so we need to go to the next entry. We do so by walking through the flink field of ActiveProcessLinks to the next process and then subtract the offset of it because as said above, flink points to the next process' ActiveProcessLinks and NOT to the start of the _EPROCESS structure. We do this as long as Win32Process stays 0. This could possibly lead to an infinite loop but I doubt this scenario ever happens, as csrss.exe is a GUI process, so I skipped adding checks for the case where we traversed the entire linked list without finding a GUI process. It works, that's what matters.
  180.  
  181. Now that we have a GUI process, we need to attach. This is simple:
  182.  
  183. KAPC_STATE ApcState;
  184.  
  185. KeStackAttachProcess(GUIProcess,&ApcState);
  186.  
  187. //do stuff
  188.  
  189. KeUnstackDetachProcess(&ApcState);
  190.  
  191. The next thing to do is "do stuff".
  192.  
  193. Applying the hook
  194. -----------------
  195.  
  196. Now that we made sure we're running in a GUI process' context, we have to apply the actual hook. To make life easy, I decided not to hook KeUserModeCallback() (could be done with a SSDT hook) because then I'd have to check if it's a call for DispatchHook() and more important, we'd be called all the time because this is the single point for the kernel to call the user mode. I didn't want to interfer with that. I wanted to do the inline hook.
  197.  
  198. If we look at the disassembly again, the call reads:
  199.  
  200. kd> u BF92A6A3
  201. win32k!fnHkINLPKBDLLHOOKSTRUCT+0x4c:
  202. bf92a6a3 ff1534aa98bf call dword ptr [win32k!_imp__KeUserModeCallback (bf98aa34)]
  203.  
  204. It's a total of 6 bytes. FF 15 being the opcode for call, and the following 4 bytes, 0xBF98AA34 (remember, little endian) being the pointer to the location to call. We want to hijack this call.
  205.  
  206. To do so, we'd fill it with the bytes "E8 DD CC BB AA", which is a call to AddrOfNextInstruction+0xAABBCCDD. But this call is only 5 bytes long, the original is 6 bytes. Therefore, we have to insert a nop, and the offset we have to stamp in for 0xAABBCCDD is relative to this nop.
  207.  
  208. The hook would be:
  209.  
  210. char hook[6] = "\xE8\x00\x00\x00\x00\x90";
  211.  
  212. And the code to calculate the offset relative to the nop and the stamping in then is:
  213.  
  214. const ULONG HookAddr = 0xBF92A6A3;
  215. *(ULONG*)(hook+1) = (ULONG)WrappedKeUserModeCallback-(HookAddr+5);
  216.  
  217. Now we have the 6 bytes which will overwrite the existing six bytes at 0xBF92A6A3 (=HookAddr). But first, we have to gain rights to write to this address. To gain these rights, there are several ways, one being the evil cr0 trick which is a csr (CPU specific register, or something like that) containing information regarding the memory protection. There, you simply unset a flag and may write to wherever you desire. This is dirty (I've been told, it globally removes write protection!) and because the cr0 register belongs to a CPU, you will run into trouble with multi-cores (because each core has its own context), so we don't use that.
  218.  
  219. We do it the proper way:
  220.  
  221. PMDL pmdlHook;
  222. PVOID *MappedHook;
  223.  
  224. pmdlHook = MmCreateMdl(NULL,HookAddr,6);
  225. MmProbeAndLockPages(pmdlHook,KernelMode,IoModifyAccess);
  226. MappedHook = MmGetSystemAddressForMdlSafe(pmdlHook,HighPagePriority);
  227.  
  228. MappedHook now points to the same memory as 0xBF92A6A3, the location where we want to write 6 bytes, but with writing rights. Before writing them, we save the original 6 bytes, in case we want to unload the driver (which I recommend for development):
  229.  
  230. //Save original data
  231. RtlCopyMemory(SavedCode,MappedHook,6);
  232.  
  233. //Write in our hook
  234. RtlCopyMemory(MappedHook,hook,6);
  235.  
  236. What's left is implementing the WrappedKeUserModeCallback() to remove the evil LLKHF_INJECTED flag:
  237.  
  238. NTSTATUS WrappedKeUserModeCallback (
  239. IN ULONG ApiNumber,
  240. IN PVOID InputBuffer,
  241. IN ULONG InputLength,
  242. OUT PVOID *OutputBuffer,
  243. IN PULONG OutputLength
  244. )
  245. {
  246. PULONG p = InputBuffer;
  247. DbgPrint("Flags: %08x\n",p[6]);
  248.  
  249. //Remove the injected flag
  250. p[6] &= ~LLKHF_INJECTED;
  251.  
  252. return OrigKeUserModeCallback(ApiNumber,InputBuffer,InputLength,OutputBuffer,OutputLength);
  253. }
  254.  
  255. Because this function is only called at the single location we hooked, we don't have to make sure the caller is right and just can assume the input buffer is as specified above. The flags are the 7th DWORD, where we simply patch out the LLKHF_INJECTED and pass it on to the original KeUserModeCallback() which will then call DispatchHook() and pass the modified structure to all LowLevelKeyboardProcs. The address of the original KeUserModeCallback() can be read from 0xBF98AA34 as this is the address in win32k.sys' import table. I just hardcoded the address. This is bad practice, but again, it works (for XP SP2).
  256.  
  257. That's it. Before any low level keyboard hook is called, it will go through our code which makes sure, no LLKHF_INJECTED flag is set, so: Mission Accomplished!
  258.  
  259.  
  260. Conclusion
  261. ----------
  262.  
  263. We now have a functioning kernel driver to remove LLKHF_INJECTED flags, so no user land program should be able to distinguish injected input from regular input and therefore we can cheat as much as we want. To do this, we reverse engineered user32.dll as well as its kernel counterpart, win32k.sys to find a weak spot where the KBDLLHOOKSTRUCT is passed through, so we can patch out the injected flag. After we found the weak sport, we implemented the kernel driver to do just that. To be able to write our hook, we had to find a GUI process and attach to it, otherwise we wouldn't be able to access win32k.sys. Having solved this issue, we wrote a wrapper for KeuserModeCallback() that removes the injected flag and made sure the code flow went through at the right time.
  264.  
  265. Last words
  266. ----------
  267.  
  268. Kernel driver development is frustrating. The debugging cycles are longer, because a mistake means a reboot. Documentation is sparse and finding the information you want is difficult. If you spent 30% of your time researching before coding hacks in user mode, you could at least double the research time for kernel mode, from my experience.
  269.  
  270. Regarding the final code: It's not nice. We use magic numbers, that only work for a single release (in this case, again, XP SP2) and we are most probably not thread safe. Especially the RtlCopyMemory() to write hook may lead to a bluescreen with the "right" timing. Thing is, it works and it's only a single point of failure. If loading the kernel driver works without a bluescreen, you're good. The above mentioned Rootkit book has a chapter on synchronisation issues and how to handle them, but to be honest, I was to lazy to write it as of yet. If you want to port this code to Win 7, you just need to check WinDbg for the offsets and disassemble win32k.sys for the new weak spot.
  271.  
  272. I decided against releasing full source code. I'm confident that readers that want to get it done can get it done now. If there are questions or corrections, feel free to contact me!
  273.  
  274. References
  275. ----------
  276.  
  277. - [1] http://www.awarenetwork.org/etc/beta/?x=1
  278. - "Rootkit: Subverting the Windows kernel" by Hoglund and James Butler
  279. - sourcecode for other rootkits
  280. - A lot of Google-fu
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement