unicorn的hook相关(c语言)
在上一篇文章中我们介绍了python版的hook相关,这次我们就来介绍一下c语言版的,先看一个例子
在看这个例子之前,我们要介绍两个函数,分别是uc_reg_write_batch和uc_reg_read_batch,在此之前,我们已经介绍了uc_reg_write是用来修改寄存器内容的和uc_reg_read是用来读取寄存器内容的,那么万一我们要修改很多呢,总不能一个一个修改吧,所以这个就提供了一次性修改和读取多个寄存器的函数
uc_reg_write_batch和uc_reg_read_batch
int syscall_abi[]=
{
UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX,
UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9
};
uint64_t vals[7] = { 200, 10, 11, 12, 13, 14, 15 };
err=uc_reg_write_batch(uc,syscall_abi,vals,7);
//总共有四个参数,第一个还是uc类,第二个就是我们要修改的寄存器常量,不过相比uc_reg_write函数这个变成了数组地址,这个数组放的就是寄存器常量,第三个参数就是我们要修改的值,这个也是数组地址,和第二个参数一一对应修改,第四个就是要修改的个数,这边是7,也就是说我们要修改7个寄存器的值,对应数组下标应该是[0,7)左开右闭区间
err=uc_reg_read_batch(uc,syscall_abi,vals,7);
//这个和write很像,只不过是吧第三个参数意义改了一下,变成了查询到的信息存放在这个数组里
例子
#include <stdio.h>
#include <string.h>
#include "unicorn/unicorn.h"
int syscall_abi[] =
{
UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX,
UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9
};
uint64_t vals[7] = { 200, 10, 11, 12, 13, 14, 15 };
void* ptrs[7];
void uc_perror(const char* func, uc_err err)
{
fprintf(stderr, "Error in %s(): %s\n", func, uc_strerror(err));
}
#define BASE 0x10000
// mov rax, 100; mov rdi, 1; mov rsi, 2; mov rdx, 3; mov r10, 4; mov r8, 5; mov r9, 6; syscall
#define CODE "\x48\xc7\xc0\x64\x00\x00\x00\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc6\x02\x00\x00\x00\x48\xc7\xc2\x03\x00\x00\x00\x49\xc7\xc2\x04\x00\x00\x00\x49\xc7\xc0\x05\x00\x00\x00\x49\xc7\xc1\x06\x00\x00\x00\x0f\x05"
int main()
{
int i;
uc_hook sys_hook;
uc_err err;
uc_engine* uc;
for (i = 0; i < 7; i++)
{
ptrs[i] = &vals[i];
}
if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc)))
{
uc_perror("uc_open", err);
return 1;
}
printf("reg_write_batch({200, 10, 11, 12, 13, 14, 15})\n");
if ((err = uc_reg_write_batch(uc, syscall_abi, ptrs, 7)))
{
uc_perror("uc_reg_write_batch", err);
return 1;
}
memset(vals, 0, sizeof(vals));
if ((err = uc_reg_read_batch(uc, syscall_abi, ptrs, 7)))
{
uc_perror("uc_reg_read_batch", err);
return 1;
}
printf("reg_read_batch = {");
for (i = 0; i < 7; i++)
{
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
// syscall
printf("\n");
printf("running syscall shellcode\n");
if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))
{
uc_perror("uc_mem_map", err);
return 1;
}
if ((err = uc_mem_write(uc, BASE, CODE, sizeof(CODE) - 1)))
{
uc_perror("uc_mem_write", err);
return 1;
}
if ((err = uc_emu_start(uc, BASE, BASE + sizeof(CODE) - 1, 0, 0)))
{
uc_perror("uc_emu_start", err);
return 1;
}
if ((err = uc_reg_read_batch(uc, syscall_abi, ptrs, 7)))
{
uc_perror("uc_reg_read_batch", err);
return 1;
}
printf("reg_read_batch = {");
for (i = 0; i < 7; i++)
{
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
return 0;
}
hook_add函数
对于前面文章看过的阅读这个应该问题不大,接下来我们来介绍一下hook_add函数
uc_hook sys_hook;
err=uc_hook_add(uc, &sys_hook,UC_HOOK_INTR,hook_syscall,NULL,1,0);
//这个有七个参数,第一个参数是uc类,第二个参数是回调路径,这个在下一个函数uc_hook_del中会有用到,第三个参数是hook类型,通过修改这个参数我们可以让不同时候来调用这个hook,第四个参数就是hook函数,我们每次调用hook的时候要执行的函数,第五个参数是hook函数里的user_data,也就是用户数据,第六个参数是代码起始地址,第七个参数是代码终止地址,从起始地址开始到终止地址的代码都会进行hook判断,然后根据hook类型来判断是否调用hook函数,如果起始地址大于终止地址,也就像这个样例一样,那么就会把整个范围认为是整个unicorn运行的代码范围
hook函数
接下来介绍一下我们自己写的hook函数
void hook_code(uc_engine* uc, uint64_t addr, uint32_t size, void* user_data)
{
printf("HOOK_CODE: 0x%" PRIx64 ", 0x%x\n", addr, size);
}
//这个函数一共有四个参数,第一个是uc类,第二个是触发调用hook函数的指令的地址,第三个参数对应的就是这个指令的大小,第四个参数就是用户数据,也就是hook_add提到的可以传一些数据过来
hook_del函数
既然可以添加hook,那肯定可以删除hook,这时候就要用到我们的hook_del函数
err=uc_hook_del(uc,sys_hook);
//这个有两个参数,第一个参数是uc类,第二个参数是回调路径,也就是hook_add的第二个参数
例子
#include <stdio.h>
#include <string.h>
#include "unicorn/unicorn.h"
int syscall_abi[] =
{
UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX,
UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9
};
uint64_t vals[7] = { 200, 10, 11, 12, 13, 14, 15 };
void* ptrs[7];
void uc_perror(const char* func, uc_err err)
{
fprintf(stderr, "Error in %s(): %s\n", func, uc_strerror(err));
}
#define BASE 0x10000
// mov rax, 100; mov rdi, 1; mov rsi, 2; mov rdx, 3; mov r10, 4; mov r8, 5; mov r9, 6; syscall
#define CODE "\x48\xc7\xc0\x64\x00\x00\x00\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc6\x02\x00\x00\x00\x48\xc7\xc2\x03\x00\x00\x00\x49\xc7\xc2\x04\x00\x00\x00\x49\xc7\xc0\x05\x00\x00\x00\x49\xc7\xc1\x06\x00\x00\x00\x0f\x05"
void hook_syscall(uc_engine* uc, void* user_data)
{
int i;
uc_reg_read_batch(uc, syscall_abi, ptrs, 7);
printf("syscall: {");
for (i = 0; i < 7; i++)
{
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
}
void hook_code(uc_engine* uc, uint64_t addr, uint32_t size, void* user_data)
{
printf("HOOK_CODE: 0x%" PRIx64 ", 0x%x\n", addr, size);
}
int main()
{
int i;
uc_hook sys_hook;
uc_err err;
uc_engine* uc;
for (i = 0; i < 7; i++)
{
ptrs[i] = &vals[i];
}
if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc)))
{
uc_perror("uc_open", err);
return 1;
}
printf("reg_write_batch({200, 10, 11, 12, 13, 14, 15})\n");
if ((err = uc_reg_write_batch(uc, syscall_abi, ptrs, 7)))
{
uc_perror("uc_reg_write_batch", err);
return 1;
}
memset(vals, 0, sizeof(vals));
if ((err = uc_reg_read_batch(uc, syscall_abi, ptrs, 7)))
{
uc_perror("uc_reg_read_batch", err);
return 1;
}
printf("reg_read_batch = {");
for (i = 0; i < 7; i++)
{
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
// syscall
printf("\n");
printf("running syscall shellcode\n");
if ((err = uc_hook_add(uc, &sys_hook, UC_HOOK_CODE, hook_syscall, NULL, 1, 0)))
{
uc_perror("uc_hook_add", err);
return 1;
}
if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))
{
uc_perror("uc_mem_map", err);
return 1;
}
if ((err = uc_mem_write(uc, BASE, CODE, sizeof(CODE) - 1)))
{
uc_perror("uc_mem_write", err);
return 1;
}
if ((err = uc_emu_start(uc, BASE, BASE + sizeof(CODE) - 1, 0, 0)))
{
uc_perror("uc_emu_start", err);
return 1;
}
return 0;
}
hook类型
不同hook类型,调用hook的时机也不同,像上面的例子就是每个指令执行之前调用hook函数,接下来我们来看看所有的hook类型
// All type of hooks for uc_hook_add() API.
typedef enum uc_hook_type {
// Hook all interrupt/syscall events
UC_HOOK_INTR = 1 << 0,
// Hook a particular instruction - only a very small subset of instructions supported here
UC_HOOK_INSN = 1 << 1,
// Hook a range of code
UC_HOOK_CODE = 1 << 2,
// Hook basic blocks
UC_HOOK_BLOCK = 1 << 3,
// Hook for memory read on unmapped memory
UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
// Hook for invalid memory write events
UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
// Hook for invalid memory fetch for execution events
UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
// Hook for memory read on read-protected memory
UC_HOOK_MEM_READ_PROT = 1 << 7,
// Hook for memory write on write-protected memory
UC_HOOK_MEM_WRITE_PROT = 1 << 8,
// Hook for memory fetch on non-executable memory
UC_HOOK_MEM_FETCH_PROT = 1 << 9,
// Hook memory read events.
UC_HOOK_MEM_READ = 1 << 10,
// Hook memory write events.
UC_HOOK_MEM_WRITE = 1 << 11,
// Hook memory fetch for execution events
UC_HOOK_MEM_FETCH = 1 << 12,
// Hook memory read events, but only successful access.
// The callback will be triggered after successful read.
UC_HOOK_MEM_READ_AFTER = 1 << 13,
} uc_hook_type;