RevEngX - Reverse Engineering Extensions

Copyright (c) 2015 Andrew L. Sandoval - All rights reserved

Home License Download Tutorial

RevEngX Tutorial

Validating Installation, Selecting a Target, and Getting Help

To validate proper installation of RevEngX, start windbg.exe and select a live target to debug. The target may be one of your own 32-bit or 64-bit Windows executables, or where legally allowed a third-party executable. (Please note that this article does not cover legal issues related to Reverse Engineering. It is the author's believe that United States copyright law allows Reverse Engineering for many legitimate, non-competitive purposes. Nevertheless the reader is urged to seek legal counsel before debugging or reverse engineering any third party components without permission.) For the purpose of example only, the tutorial will show a typical Windows process being debugged.

The first thing to do is to load RevEngX, by issuing .load RevEngX at the debugger prompt:

!load RevEngX

If the extension was succesfully loaded you should see the license text:

.load RevEngX - shows license on load

If the license text was not displayed verify that you have installed RevEngX.dll in the correct winext directory for the Debugging Tools for Windows package you are using to launch windbg.exe.

To obtain basic help on the extensions offered by RevEngX, issue !help or !RevEngX.help at the debugger prompt:

!RevEngX.help

This will display the following help information: (Note that some extensions are obsoleted by !callfn.)

0:000:x86> !RevEngX.help

IAT = Import Address Table, EAT = Export Address Table

-compare options require matching DLLs and paths on the debugger machine

RevEngX extension help

!iatentry [module!]symbol [search_module][-compare][-set newaddr]

Displays IAT entries for symbol in search_module or all module, optionally

compares to expected (on-disk) address, and optionally sets a new value (hook)

!iathooks [module module module ...]

Displays IAT hooks on symbols in specified modules, or on all exported symbols

WARNING: The search may take a very long time depending on the # of modules

!eatentry [module!]symbol [-compare][-set newaddr]

Displays the EAT entry for symbol, optionally comparing it to the expected value

and optionally sets a new value (hook)

!eathooks [module module module ...]

Displays EAT hooks on symbols in specified module, or on all exported symbols

WARNING: The search may take a long time depending on the # of modules

!virtualalloc addr, size, type, protections

Allocates memory in live target, same parameters as VirtualAlloc API

!virtualfree addr, size, type

Frees or Decommits memory in live target, same parameters as VirtualFree API

!loadlibrary fullpath_to_dll.dll

Loads a DLL into the target process

!freelibrary MODULE

Decrements the reference count on MODULE (Base Address or name)

!define const CONSTANT_NAME [-replace] [value]

Display, set, or replace a defined constant value such as MB_YESNO

!callfn [-cdecl|-stdcall][-retainvm] function(arg0...argn)

Execute a call to the named function/address, args can be values, strings, buffers

!dle [loader_entry_address]

Displays the specified loader entry, or all loader entries

!help

Displays this help screen

Written by Andrew L. Sandoval, (c) 2010 Andrew L. Sandoval

Call A Function in the Target

Perhaps the most useful extension in RevEngX is !callfn which allows you to execute any function in the target process, using many of the constants defined in standard system headers (e.g. MB_OK, HWND_TOP, CREATE_NEW, PROCESS_ALL_ACCESS, etc.) For examples of how to use !callfn, issue !callfn at the prompt without any arguments:

!callfn

This will produce the following output:

!callfn output

Try each of the examples inside of something like calc.exe to see what happens. Notice that for faster operation you should give fully qualified symbol names to the debugger. For example, instead of using MessageBoxW, use user32!MessageBoxW.

For a call to something like memset (in the CRT for example), you will need to use the -cdecl flag to ensure proper stack clean-up and to prevent crashing the target process.

RevEngX will convert constants such as MB_YESNOCANCEL to their numerical value where possible. To see if a value is known, issue !define const NAME, where NAME is the constant you wish to inspect:

!define const

!define const is a short form of !define constant. Either form is acceptable. It is also possible (as shown) to create new constants. Use the -replace flag to change the value of a known constant.

When passing arguments to !callfn the following may be used:

  • Numeric values (using expressions known to the debug engine)
  • Symbols that can be resolved to a numeric value by the debug engine
  • Constants defined in RevEngX
  • via !define const, or built-in constants
  • created as a result of a call to !callfn, either as a return value name, or a pArg# value created by RevEngX to identify data created by buffer parameters, when the -retainvm flag is used
  • Buffers
  • To create a buffer use `SIZE (back-hypen followed by the size of the buffer in bytes), e.g. `2048 will create a 2048 byte buffer

The -retainvm flag is used when buffers need to be retained for other calls. Be sure to eventually free the virtual memory created by RevEngX using .dvfree or !callfn with correct parameters to VirtualFree, or by using !VirtualFree addr, size, type where addr, size, and type match the arguments documented in the MSDN library for VirtualFree. Note that without -retainvm, all virtual memory allocated by RevEngX for the !callfn will be released when the call completes. Virtual memory for !callfn is divided into two sections separated by one page. The first page is for data and the second is for code.

The -noexec option, with or without the -64 option can be used to see the code and data generated by !callfn without executing it. This can be combined with -retainvm so that the generated code can be used a later time.

When executing a call to a cdecl function under WOW64 or 32-bit Windows be sure to use the -cdecl flag so that stack clean-up code will be added to the generated code. Executing a cdecl function without the -cdecl flag will cause the debugger thread in the target process to crash, bringing down the entire application.

As you can see, !callfn is very powerful and far more easier to use than the built-in .call command.

When the command !callfn mbAnswer = user32!MessageBoxW(HWND_TOP, L"Do you like this?", L"Test", MB_TOPMOST|MB_YESNO) is issued the following results are displayed before the call returns:

!callfn MessageBox

And after the click on Yes:

!callfn MessageBox after

If you issue !define constant IDYES you will see that it matches the value in mbAnswer (!define const mbAnswer).

Always remember that !callfn executes code from within the target process, NOT the debugger. If you pass bad arguments to the call you will crash the target process potentially losing important data!

Find Import or Export Address Table Hooks

[More to come... This extension may not be working properly on x64 builds.]

Setting an IAT or EAT Hook

[More to come... This extension may not be working properly on x64 builds.]

Tips for Writing Good Hooking and Injection Code

Start by reading Yariv Kaplan's article on API Spying Techniques here. Yariv and I worked closely together for many years on Code Injection, API hooking, COM hooking, RPC hooking, and other forms of injection, hooking and security related code. Along with a few other great Windows Internals engineers we discovered many additional techniques for quality code injection and hooking as well as many pit falls. There are numerous products in the market that use forms of the techniques we discovered. Some do a poor job. This makes everyone's life harder, because another implementation that is thoughtless may break your application -- and may cause customers to point the finger of blame at you. Here are a few important considerations:

  • ALWAYS include a disassembler in your hooking code. If you can't figure out how to write this or repurpose a public domain or open source disassembler, then use someone else's hooking library that has one built-in. This is not strictly necessary for IAT/EAT hooks, but these hooks are less useful than code-patch hooks because they are easier to bypass.
  • NEVER assume that the code you are hooking looks like it does on your system! Some products will overwrite bytes of code expecting the prologue they saw in their disassembler. What happens then is that your product works fine without any other hooks on the same target functions, and if you (with disassembly based code relocation for trampolines) run last everything is good, but if you run first and the blissfully ignorant hooker runs second, your code will be overwritten, most likely in a way that will lead to a crash. Best case scenario is that your code is overwritten with a new hook that bypasses yours. Good hooking code can always successfully relocate another hook and keep it working.
  • HANDLE relative instructions! When creating trampolines, be careful to correctly move relative instructions. Years ago a popular A/V vendor used code-patch hooks that placed a CALL instruction as the first instruction in the hooked API. (I highly discourage this approach! It means more hand-written assembly code that will probably be buggy. C++ and STL and RAII are your friends for clean code!) When we moved their CALL instruction, we had construct a trampoline that could fix the stack properly so that they could look at the return address and still know what was hooked.
  • Where possible use hotpatch-type code patching that can easily be set atomically. If you are willing to go to the work, it is possible to set hooks while only one thread is running. It requires either an external process setting hooks, or a thourough knowledge of how processes are created. I've written demo code that can create a new process and inject a DLL which sets hooks while guaranteeing that only the hooking thread is running. It works great, but certain A/V products heurestics mark my test executables as viruses.
  • Use reference counting! Unhooking is much harder than hooking! Reference counting carefully and thoughtfully employed can help. I can't disclose other techniques for preventing crashes when unhooking because there are patents pending on those techniques that I helped to create.
  • Beware of reentrancy. Write TLS based code that will allow you to bypass your hooks and call the original function on reentrancy if necesary.
  • Be careful with the load addresses of injected DLLs. You can't imagine how many seasoned engineers don't really understand Virtual Memory, and may try to reserve gobs of it. If you segment their address space it may takes weeks of explaining to them how Virtual Memory has no real relationship to Physical RAM... (Long story, but just be thoughtful about where your DLLs will load and what impact it may have on address spaces.)
  • When searching for other's hooks, don't forget that it is easy to write a "touchless hook" -- one in which no code is overwritten in the target function, but in which a hook function still gets called... (I have sample code for this, but would prefer not to release it to the public. Just think about how certain debugger functionality works and you will figure it out.) Hint: rM 20
  • Remember that the standard injection code where CreateRemoteThread is called with kernel32!LoadLibrary as the function and a pointer to memory allocated by VirtualAllocEx and filled in with a library name by WriteProcess Memory is really lazy. Yes, it works fine under most circumstances, and it is even easier to write for x64 than x86, but be creative. If someone out there can write a compiler and linker, you can write code injection that is far more ingenious than just calling LoadLibrary!
  • Beware of deadlocks! You are going to change the flow of a program by using hooks and/or injection. Know how to analyze critical sections. Avoid calling CoInitialize (etc.) in threads you don't create, etc.,
  • There are many more tips, but time prevents me from listing them all. Consider contact a contractor with substantial hooking and injection and Windows Internals experience before releasing your product. Drop me an e-mail and I can put you in touch with knowledgable contractors.
  • Use C++ and RAII to prevent data and lock leaks.  For details see the following article from the InfoSec Institute: Exceptions In Injected Code