官方示例在scripts文件夹下,示例的说明已经很详细了
pyrebox脚本功能:
- 给pyrebox环境定义新的命令
- 定义回调函数(会在每个受监视进程的不同事件发生时调用的函数)
- 给回调函数增加触发器
- 使用pyrebox提供的python API允许:
- 查询进程、模块
- 查询symbols(API名字解析)
- 读取和操作寄存器和内存
- 启动shell
- 利用volatility库
脚本生命周期
pyrebox的脚本可以在虚拟机运行期间动态加载和卸载,pyrebox.conf配置文件指定在启动时需要加载的所有脚本
虚拟机运行起来后,可以在QEMU提示符下用命令import_module和unload_module动态加载和卸载脚本;
如果已加载的脚本的代码被修改了,可以使用命令reload_module重新加载脚本
任何pyrebox的脚本需要具备2个基础的函数:
- initialize_callbacks:
- 当脚本被加载的时候就会被调用
- 可以注册想监听的回调函数
- clean:
- 当脚本卸载的时候调用
- 注销回调函数
- CallbackManager类中的clean()函数将注销以前为了CallbackManager实例注册的所有活动的回调函数
脚本中另一个可选的成员:requirements:
- 包含一系列的附加脚本列表(用python的模块表示法),这些附加脚本应该在加载脚本之前加载
执行初始化函数后,只有以下情况会执行脚本:
- 脚本中的一个自定义的命令被执行
- 系统中的某个事件触发了回调函数
monitored processes:
通常回调函数只会被正在监控的进程触发,这样一来限制了事件数量,这些事件可以触发自定义脚本调用。
要设置或者取消设置进程监控,一方面可以通过pyrebox提供的python API,另一方面可以通过交互式shell直接设置。
可以在shell中用ps命令查看正在监控的进程
规则中的一些例外:
- Block begin/Instruction begin:如果为回调函数指定了地址和pgd(示例中有),这个回调函数只会被该地址和进程触发,无论进程是否受到监控
- Keystroke(击键)callback:无论当前系统中的所有进程和任何进程的上下文如何,这个回调函数都会被调用,即不限于监控进程
- NIC 发送/接收:同上
- Opcode range callback:在指令端通过指定的操作码调用,仅适用于受监视的进程
- Triggers(触发器):触发器是与一个给定的回调函数相关联的C/C++编译共享对象。这段代码会在调用python回调函数之前执行,并且决定是否将该回调传递给python函数。这个方法通过设置任意回调函数的条件来提高整体性能。当触发器附加到回调函数上时,将对每个事件都执行触发器(无论是否正在监视进程),并且开发人员有责任检查回调函数是否发生在适当的上下文环境中(通常通过PGD检查,PGD确定了当前地址空间)。
触发器相当于在事件和回调函数之间增加了一层,可以筛选触发执行回调函数的事件。
定义一个新命令
要定义一个新命令,只需要在脚本中按下面的原型声明一个函数即可:
def do_command(self, line):
上面的command是想要创建的命令名字,line是包含所有命令所需参数的参数。当该脚本加载时,就可以在shell中使用这个命令。
要使用这个命令,需要用关键字custom,接着自己的命令名字和参数。
可以使用标准的基于python的docstring文档来给自定义的命令添加说明文档,该文档会被pyrebox自动加载
回调函数类型
给出回调函数类型、描述和希望的参数。
其中回调函数共同的数据类型是:
参数 | 描述 |
cpu | 代表cpu状态的对象。它将为CPU的每个寄存器都包含一个对应成员(field) |
tb | 一个元组,包含有关QEMU的转换块(translation block,一次能够转换的一套指令)信息,在概念上和基本块类似。该元组包含3个值:(pc, size, icount),其中pc是第一条指令的程序计数器,size是块的大小,icount是其中的指令数。QEMU仿真器将一条条指令反汇编,直到找到控制流指令或者无法静态猜测下一个地址的点。所有指令都要符合转换块。注意,某些情况下(比如特殊指令),转换块可能不一定和基本块匹配。 |
下面是具体如何编写的部分:
块开始
在转换块的开始处,在被监视进程的上下文中为每个执行的转换块触发回调函数。
它对跟踪转换块很有用。它允许指定地址和PGD。在这种情况下,无论是否监视进程,都只会在该地址和进程地址空间触发回调函数。
回调函数类型:CallbackManager.BLOCK_BEGIN_CB
示例:
cm.add_callback(CallbackManager.BLOCK_BEGIN_CB,my_function)
cm.add_callback(CallbackManager.BLOCK_BEGIN_CB,my_function,address=address,pgd=pgd)
回调函数接口:
def my_function(cpu_index,cpu,tb):
...
块结束
在转换块结束时,在被监视进程的上下文中为每个已执行的转换块触发回调。
它对跟踪转换块很有用。cur_pc参数表示当前指令指针,而next_pc表示要执行的下一条指令。触发回调时,仿真的cpu已经在下一条指令的开头。
回调函数类型:CallbackManager.BLOCK_END_CB
示例:
cm.add_callback(CallbackManager.BLOCK_END_CB,my_function)
回调函数接口:
def my_function(cpu_index,cpu,tb,cur_pc,next_pc):
...
指令开始
与之前的回调类似,但在指令级别。用于跟踪单个指令。它允许指定地址和pgd。在这种情况下,无论是否监控进程,都只会在该地址触发。
回调函数类型:CallbackManager.INSN_BEGIN_CB
示例:
cm.add_callback(CallbackManager.INSN_BEGIN_CB,my_function)
cm.add_callback(CallbackManager.INSN_BEGIN_CB,my_function,addr=addr,pgd=pgd)
回调函数接口:
def my_function(cpu_index,cpu):
...
指令结束
与之前的回调类似,但在指令级别。用于跟踪单个指令。触发回调时,仿真的cpu已经在下一条指令的开头
回调函数类型:CallbackManager.INSN_END_CB
示例:
cm.add_callback(CallbackManager.INSN_END_CB,my_function)
回调函数接口:
def my_function(cpu_index,cpu):
...
内存读
监视的任何进程进行读取任何内存地址时触发。参数vaddr表示修改的虚拟地址。 haddr是相应的物理地址,size是修改的大小。
回调函数类型:CallbackManager.MEM_READ_CB
示例:
cm.add_callback(CallbackManager.MEM_READ_CB,my_function)
回调函数接口:
def my_function(cpu_index,vaddr,size,haddr):
...
内存写
监视的任何进程进行写入任何内存地址时触发。参数vaddr表示修改的虚拟地址。 haddr是相应的物理地址,size是修改的大小。
写入内存之后调用回调函数。 data参数包含写入的内存值。
回调函数类型:CallbackManager.MEM_WRITE_CB
示例:
cm.add_callback(CallbackManager.MEM_WRITE_CB,my_function)
回调函数接口:
def my_function(cpu_index,vaddr,size,haddr,data):
...
击键事件
每当在系统中按下一个键时触发。
回调函数类型:CallbackManager.KEYSTROKE_CB
示例:
cm.add_callback(CallbackManager.KEYSTROKE_CB,my_function)
回调函数接口:
def my_function(keycode):
...
NIC发送
每当通过网络接口发送数据时触发。此事件需要以这种方式配置网卡:-device ne2k_pci,netdev=network0
参数addr表示缓冲区的地址,size表示其大小,buffer是发送的内容。
回调函数类型:CallbackManager.NIC_SEND_CB
示例:
cm.add_callback(CallbackManager.NIC_SEND_CB,my_function)
回调函数接口:
def my_function(addr,size,buf):
...
NIC接收
每当通过网络接口接收数据时触发。此事件需要以这种方式配置网卡:-device ne2k_pci,netdev=network0
参数size表示其大小,buffer是发送的内容。
回调函数类型:CallbackManager.NIC_REC_CB
示例:
cm.add_callback(CallbackManager.NIC_REC_CB,my_function)
回调函数接口:
def my_function(buf,size,cur_pos,start,stop):
...
Opcode range callback
执行具有在指定范围内的操作码的指令时触发。例如:针对受监控的流程,所有执行call指令的时候触发回调函数。
这个回调代表了一些特殊性:
- 在执行指令后调用回调。 cpu参数对应于此执行后的新状态。除了中断指令是一个例外,在那些例外情况下,回调函数发生在指令开始。
- pc参数对应于所涉及的指令所在的PC。
- next_pc参数对应于下一条指令。如果指令中没有提供地址,则可能为0(例如:中断或返回指令)。
回调函数类型:CallbackManager.OPCODE_RANGE_CB
示例:
cm.add_callback(CallbackManager.OPCODE_RANGE_CB,my_function,start_opcode=0xE8,end_opcode=0xE9)
回调函数接口:
def my_function(cpu_index,cpu,pc,next_pc):
...
TLB回调
每个TLB刷新时触发回调。
回调函数类型:CallbackManager.TLB_EXEC_CB
示例:
cm.add_callback(CallbackManager.TLB_EXEC_CB,my_function)
回调函数接口:
def my_function(cpu,vaddr):
...
上下文改变
针对每个上下文更改触发该类回调函数。
回调函数类型:CallbackManager.CONTEXTCHANGE_CB
示例:
cm.add_callback(CallbackManager.CONTEXTCHANGE_CB,my_function)
回调函数接口:
def my_function(old_pgd, new_pgd):
...
创建进程
只要在系统中创建新进程,就会触发该类回调函数。参数是自描述的。
回调函数类型:CallbackManager.CREATEPROC_CB
示例:
cm.add_callback(CallbackManager.VMI_CREATEPROC_CB,my_function)
回调函数接口:
def my_function(pid,cr3,name):
...
移除进程
在系统中终止进程时触发。参数是自描述的。
回调函数类型:CallbackManager.REMOVEPROC_CB
示例:
cm.add_callback(CallbackManager.REMOVEPROC_CB,my_function)
回调函数接口:
def my_function(pid,cr3,name):
...
加载模块
只要在进程的地址空间中加载库或驱动程序,就会触发该类回调函数。参数是自描述的。
回调函数类型:CallbackManager.LOADMODULE_CB
示例:
cm.add_callback(CallbackManager.LOADMODULE_CB, my_function, pgd = cpu.CR3)
回调函数接口:
def my_function(pid, pgd, base, size, name, fullname):
...
移除模块
每当从进程的地址空间中删除库或驱动程序时触发。参数是自描述的。
回调函数类型:CallbackManager.REMOVEMODULE_CB
示例:
cm.add_callback(CallbackManager.REMOVEMODULE_CB, my_function, pgd = cpu.CR3)
回调函数接口:
def my_function(pid, pgd, base, size, name, fullname):
...
触发器
触发器是用C/C ++开发的库,它们被编译成本机代码并在运行时加载。
这些触发器定义了一个名为trigger的函数,它可以执行任何必要的计算并使用qemu_glue.h提供的API。
然后,此trigger函数将决定是否应执行附加的python回调。如果函数返回1,则将执行python回调。如果函数返回0,则不执行python回调。
当触发器添加到回调函数时,将为任何进程上下文中发生的每个事件(不仅是受监视的进程)调用触发器。
请注意,这与某些回调类型中的默认行为不同。例如,如果我们添加一个块开始回调并为其附加一个触发器,则每次在系统上的任何进程中执行一个块时都会调用该触发器。然后,触发器应通过检查进程上下文或任何其他相关值来决定该事件是否执行python回调函数调用,或者忽略事件,不执行回调函数。
触发器可以访问与回调函数相关的变量(触发器变量)。一旦加载了触发器,就可以在python脚本中设置触发器变量。
在triggers/文件夹下有一些触发器的例子。
每个触发器必须实现3个函数(使用extern“C”子句):get_type,trigger和clean。
- get_type:应该返回它可以加载进去的回调函数的类型。系统不允许我们将触发器加载到类型不兼容的回调函数中。
- trigger:返回1时执行回调函数,返回0时不执行。
- clean:清除所有变量(并释放内存),并且只有在卸载触发器时才会调用它。
这些触发器允许我们:
- 预先计算一些条件并决定是否调用python回调函数(减少运行时开销)。
- 预先快速计算一些值并将其存储在一些可以用python读取的变量中。
为了访问变量,我们需要使用函数get_var()和set_var()。
void* get_var(callback_handle_t handle, const char* key_str);
void set_var(callback_handle_t handle, const char* key_str,void* val);
在所有情况下,该值都是指针。创建变量时,应分配一些内存并将分配的内存地址传递给函数。
如果我们为已经存在的变量调用set_var(),它将自动通过在指针上调用free()来释放前一个已经存在变量指向的内存。
小心使用复杂的数据结构,因为set_var()只能释放指针指向的内存块。使用这些变量时,注意避免内存泄漏。
为了在触发器中创建可从python代码中访问到的变量(在其触发的python回调中访问),可以参阅提供的示例并注意引用计数和垃圾回收(scripts / getset_var_example.py)。
callback_params_t类型的定义在下面:
typedef struct block_begin_params {
int cpu_index;
qemu_cpu_opaque_t cpu;
qemu_tb_opaque_t tb;
} block_begin_params_t;
typedef struct block_end_params {
int cpu_index;
qemu_cpu_opaque_t cpu;
qemu_tb_opaque_t tb;
pyrebox_target_ulong cur_pc;
pyrebox_target_ulong next_pc;
} block_end_params_t;
typedef struct insn_begin_params {
int cpu_index;
qemu_cpu_opaque_t cpu;
} insn_begin_params_t;
typedef struct insn_end_params {
int cpu_index;
qemu_cpu_opaque_t cpu;
} insn_end_params_t;
typedef struct mem_read_params {
int cpu_index;
pyrebox_target_ulong vaddr;
pyrebox_target_ulong paddr;
pyrebox_target_ulong size;
} mem_read_params_t;
typedef struct mem_write_params {
int cpu_index;
pyrebox_target_ulong vaddr;
pyrebox_target_ulong paddr;
pyrebox_target_ulong size;
} mem_write_params_t;
typedef struct keystroke_params {
unsigned int keycode;
} keystroke_params_t;
typedef struct nic_rec_params {
unsigned char* buf;
uint64_t size;
uint64_t cur_pos;
uint64_t start;
uint64_t stop;
} nic_rec_params_t;
typedef struct nic_send_params {
unsigned char* buf;
uint64_t size;
uint64_t address;
} nic_send_params_t;
typedef struct opcode_range_params {
int cpu_index;
qemu_cpu_opaque_t cpu;
pyrebox_target_ulong cur_pc;
pyrebox_target_ulong next_pc;
uint16_t opcode;
} opcode_range_params_t;
typedef struct tlb_exec_params {
qemu_cpu_opaque_t cpu;
pyrebox_target_ulong vaddr;
} tlb_exec_params_t;
typedef struct vmi_create_proc_params {
pyrebox_target_ulong pid;
pyrebox_target_ulong pgd;
char* name;
} vmi_create_proc_params_t;
typedef struct vmi_remove_proc_params {
pyrebox_target_ulong pid;
pyrebox_target_ulong pgd;
char* name;
} vmi_remove_proc_params_t;
typedef struct vmi_context_change_params {
pyrebox_target_ulong old_pgd;
pyrebox_target_ulong new_pgd;
} vmi_context_change_params_t;
//Params for the qemu->pyrebox callback (native)
typedef struct callback_params {
union {
block_begin_params_t block_begin_params;
block_end_params_t block_end_params;
insn_begin_params_t insn_begin_params;
insn_end_params_t insn_end_params;
mem_read_params_t mem_read_params;
mem_write_params_t mem_write_params;
keystroke_params_t keystroke_params;
nic_rec_params_t nic_rec_params;
nic_send_params_t nic_send_params;
opcode_range_params_t opcode_range_params;
tlb_exec_params_t tlb_exec_params;
vmi_create_proc_params_t vmi_create_proc_params;
vmi_remove_proc_params_t vmi_remove_proc_params;
vmi_context_change_params_t vmi_context_change_params;
};
} callback_params_t;
为了测试触发器是否正确编译,请cd到PyREBox目录并运行以下命令。根据需要调整目标体系结构和插件名称。
make triggers/trigger_template-i386-softmmu.so