Event hooks
Welcome this second tutorial! Today we will learn how to instrument executed code using Maat's API to execute custom callbacks on events such as a register access, a memory access, a branch operation, etc.
Have fun!
Hooking events
Event hooks are one of Maat's most important features. They make it possible to control the execution, instrument code, and trigger certain actions when specific events occur.
Event hooks are managed through the engine's hooks
attribute (see
EventManager
in the
documentation). The add()
method enables to add new hooks, for example:
from maat import *
m = MaatEngine(ARCH.X64, OS.LINUX)
# Hook code execution at addr 0xdeadbeef
m.hooks.add(EVENT.EXEC, WHEN.BEFORE, filter=0xdeadbeef)
# Hook register writes
m.hooks.add(EVENT.REG_W, WHEN.AFTER)
# Hook memory accesses between 0x1000 and 0x3000
m.hooks.add(EVENT.MEM_RW, WHEN.BEFORE, filter=(0x1000,0x3000))
As we can see in the example, each hook is set on a particular event, specified with a value
of the EVENT
enum. The complete
list of events is available in the documentation, but the most common ones are listed below:
EXEC
: executing an instruction at a given addressREG_R, REG_W, REG_RW
: break when reading/writing registerMEM_R, MEM_W, MEM_RW
: break when reading/writing memoryBRANCH
: break on branch instructions
The second argument to add()
indicates whether the hook must be triggered
before (WHEN.BEFORE
) or after (WHEN.AFTER
) the event occurs.
The filter
keyword argument restricts the range of memory addresses
on whom the hook must be active. It can be used to hook only certain instructions for EVENT.EXEC
events,
and to monitor only a given memory range for EVENT.MEM_*
events.
Last but not least: event hooks can also be given a unique name, and be assigned to a group of hooks. Both the unique name and the group can then be used to enable and disable arbitrary hooks or groups of hooks:
# Add two hooks, named "hook1" and "hook2", in the "reg_hooks" group
m.hooks.add(EVENT.REG_R, WHEN.BEFORE, name="hook1", group="reg_hooks")
m.hooks.add(EVENT.REG_W, WHEN.BEFORE, name="hook2", group="reg_hooks")
# Disable hook1
m.hooks.disable("hook1")
# Disable hook1 and hook2
m.hooks.disable_group("reg_hooks")
# Enable hook1
m.hooks.enable("hook1")
# Enable hook1 and hook2
m.hooks.enable_group("reg_hooks")
Defining custom callbacks
In order to perform arbitrary actions when a particular event occurs, hooks
can be added along with event callbacks.
Event callbacks are functions that are automatically executed when
the hook is triggered. They must be registered when the hook is added to
the engine's event manager, using the callbacks
keyword argument:
m.hooks.add(EVENT.REG_R, WHEN.BEFORE, callbacks=[callback1, callback2])
Callbacks can be defined as regular functions that take a
reference to the MaatEngine
as single argument:
def print_rax_callback(engine):
print(f"Current RAX: {engine.cpu.rax}")
Callbacks can optionally return an ACTION
to
the engine to control the execution. ACTION.CONTINUE
causes execution to continue while
ACTION.HALT
causes the engine to stop once it finishes to execute the current instruction.
For convenience, returning None
is similar to returning ACTION.CONTINUE
:
def print_rax_callback(engine):
print(f"Current RAX: {engine.cpu.rax}")
return ACTION.HALT # Stop after the current instruction
Note: hooks that have no callbacks will always cause execution to stop after the current instruction, just as though they had
a callback returning ACTION.HALT
. The only exception is hooks on EVENT.EXEC
without callbacks, which
will halt the execution before the current instruction is executed.
Event specific information
Remember the info
attribute from the previous tutorial? We've learned how
it gives contextual information about the engine, such as the reason it stopped running code,
or the exit status of the program once it terminated. Well this info
attribute
will come in handy in event callbacks, because whenever a callback is executed info
is set to contain
useful event-specific information.
The Info
documentation details all the
attributes that can be used to get even info. The most common ones are addr
that gives
the address of the current executed instruction, and then reg_access
for
register accesses, mem_access
for memory accesses, and branch
for branch operations
and path constraints. Below are a few examples showing how info
can be used in callbacks:
def exec_callback(m: MaatEngine):
print(f"Exec instructon at {m.info.addr}")
def reg_written_callback(m: MaatEngine):
print(f"Writing register {m.info.reg_access.reg}")
print(f"Current value {m.info.reg_access.value}")
print(f"New value {m.info.reg_access.new_value}")
def mem_written_callback(m: MaatEngine):
print(f"Writing mem at {m.info.mem_access.addr}")
print(f"Current value {m.mem.read(m.info.mem_access.addr, m.info.mem_access.size)}"
print(f"New value {m.info.mem_access.value}")
def branch_callback(m: MaatEngine):
if m.info.branch.taken:
print(f"Branching to {m.info.branch.target}")
else:
print(f"Not branching, next inst at {m.info.branch.next}")