Handlers provide a very flexible and powerful callback mechanism to enable the reverser to observe, modify, or change certain points of execution. Eight primary handlers are exposed from PyEmu: register handlers, library handlers, exception handlers, instruction handlers, opcode handlers, memory handlers, high-level memory handlers, and the program counter handler. Let's quickly cover each, and then we'll be on our way to some real use cases.
Register handlers are used to watch for changes in a particular register. Anytime the selected register is modified, your handler will be called. To set a register handler you use the following prototype:
set_register_handler( register, register_handler_function ) set_register_handler( "eax ", eax_register_handler )
Once you have set the handler, you need to define the handler function, using the following prototype:
def register_handler_function( emu, register, value, type ):
When the handler routine is called, the current PyEmu instance is passed in first, followed by the register that you are watching and the value of the register. The type parameter is set to a string to indicate either read or write. This is an incredibly powerful way to watch a register change over time, and it also allows you to change the registers inside your handler routine if required.
Library handlers allow PyEmu to trap any calls to external libraries before the actual call takes place. This allows the emulator to change how the function call is made and the result it returns. To install a library handler, use the following prototype:
set_library_handler( function, library_handler_function ) set_library_handler( "CreateProcessA", create_process_handler )
Once the library handler is installed, the handler callback needs to be defined, like so:
def library_handler_function( emu, library, address ):
The first parameter is the current PyEmu instance. The library parameter is set to the name of the function that was called, and the address parameter is the address in memory where the imported function is mapped.
You should be fairly familiar with exception handlers from Chapter 2. They operate much the same way inside the PyEmu emulator; any time an exception occurs, the installed exception handler will be called. Currently, PyEmu supports only the general protection fault, which allows you to handle any invalid memory accesses inside the emulator. To install an exception handler, use the following prototype:
set_exception_handler( "GP", gp_exception_handler )
The handler routine needs to have the following prototype to handle any exceptions passed to it:
def gp_exception_handler( emu, exception, address ):
Again, the first parameter is the current PyEmu instance, the exception parameter is the exception code that is generated, and the address parameter is set to the address where the exception occurred.
Instruction handlers are a very powerful way to trap particular instructions after they have been executed. This can come in handy in a variety of ways. For example, as Cody points out in his BlackHat paper, you could install a handler for the CMP instruction in order to watch for branch decisions being made against the result of the CMP instruction's execution. To install an instruction handler, use the following prototype:
set_instruction_handler( instruction, instruction_handler ) set_instruction_handler( "cmp", cmp_instruction_handler )
The handler function needs the following prototype defined:
def cmp_instruction_handler( emu, instruction, opl, op2, op3 ):
The first parameter is the PyEmu instance, the instruction parameter is the instruction that was executed, and the remaining three parameters are the values of all of the possible operands that were used.
Opcode handlers are very similar to instruction handlers in that they are called when a particular opcode gets executed. This gives you a higher level of control, as each instruction may have multiple opcodes depending on the operands it is using. For example, the instruction PUSH EAX has an opcode of 0x50, whereas a PUSH 0x70 has an opcode of 0x6A, but the full opcode bytes would be 0x6A70. To install an opcode handler, use the following prototype:
set_opcode_handler( opcode, opcode_handler ) set_opcode_handler( 0x50, my_push_eax_handler ) set_opcode_handler( 0x6A70, my_push_70_handler )
You simply set the opcode parameter to the opcode you wish to trap, and set the second parameter to be your opcode handler function. You are not limited to single-byte opcodes: If the opcode has multiple bytes, you can pass in the whole set, as shown in the second example. The handler function needs to have the following prototype defined:
The first parameter is the current PyEmu instance, the opcode parameter is the opcode that was executed, and the final three parameters are the values of the operands that were used in the instruction.
Memory handlers can be used to track specific data accesses to a particular memory address. This can be very important when tracking an interesting piece of data in a buffer or global variable and watching how that value changes over time. To install a memory handler, use the following prototype:
set_memory_handler( address, memory_handler ) set_memory_handler( 0x12345678, my_memory_handler )
You simply set the address parameter to the memory address you wish to watch, and set the memory_handler parameter to your handler function. The handler function needs to have the following prototype defined:
def memory_handler( emu, address, value, size, type )
The first parameter is the current PyEmu instance, the address parameter is the address where the memory access occurred, the value parameter is the value of the data being read or written, the size parameter is the size of the data being written or read, and the type argument is set to a string value to indicate either a read or a write.
High-level memory handlers allow you to trap memory accesses beyond a particular address. By installing a high-level memory handler, you can monitor all reads and writes to any memory, the stack or the heap. This allows you to globally monitor memory accesses across the board. To install the various high-level memory handlers, use the following prototypes:
set_memory_write_handler( memory_write_handler ) set_memory_read_handler( memory_read_handler ) set_memory_access_handler( memory_access_handler )
set_stack_write_handler( stack_write_handler ) set_stack_read_handler( stack_read_handler ) set_stack_access_handler( stack_access_handler )
set_heap_write_handler( heap_write_handler ) set_heap_read_handler( heap_read_handler ) set_heap_access_handler( heap_access_handler )
For all of these handlers you are simply providing a handler function to be called when one of the specified memory access events occurs. The handler functions need to have the following prototypes:
def memory_write_handler( emu, address ):
def memory_read_handler( emu, address ):
def memory_access_handler( emu, address, type ):
The memory_write_handler and memory_read_handler functions simply receive the current PyEmu instances and the address where the read or write occurred. The access handler has a slightly different prototype because it receives a third parameter, which is the type of memory access that occurred. The type parameter is simply a string specifying read or write.
The program counter handler allows you to trigger a handler call when execution reaches a certain address in the emulator. Much like the other handlers, this allows you to trap certain points of interest when the emulator is executing. To install a program counter handler, use the following prototype:
set_pc_handler( address, pc_handler ) set_pc_handler( 0x12345678, 12345678_pc_handler )
You are simply providing the address where the callback should occur and the function that will be called when that address is reached during execution. The handler function needs the following prototype to be defined:
def pc_handler( emu, address ):
You are again receiving the current PyEmu instance and the address where the execution was trapped.
Now that we have covered the basics of using the PyEmu emulator and some of its exposed methods, let's begin using the emulator for some reallife reversing scenarios. To start we'll use IDAPyEmu to emulate a simple function call inside a binary we have loaded into IDA Pro. The second exercise will be to use PEPyEmu to unpack a binary that's been packed with the open-source executable compressor UPX.
Was this article helpful?