Unicorn-CPU模拟框架数据类型及API分析与示例(三)

Author Avatar
kabeor 2月 13, 2020

Unicorn-CPU模拟框架数据类型及API分析与示例(三)

0x2 API分析

uc_reg_write

uc_err uc_reg_write(uc_engine *uc, int regid, const void *value);

将值写入寄存器

@uc: uc_open()返回的句柄
@regid: 将被修改的寄存器ID
@value: 指向寄存器将被修改成的值的指针

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_reg_write(uc_engine *uc, int regid, const void *value)
{
return uc_reg_write_batch(uc, &regid, (void *const *)&value, 1);
}

uc_err uc_reg_write_batch(uc_engine *uc, int *ids, void *const *vals, int count)
{
int ret = UC_ERR_OK;
if (uc->reg_write)
ret = uc->reg_write(uc, (unsigned int *)ids, vals, count); //结构体中写入
else
return UC_ERR_EXCEPTION;

return ret;
}

使用示例:

#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;

int main()
{
uc_engine* uc;
uc_err err;

err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc实例创建成功" << endl;

int r_eax = 0x12;
err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
if (!err)
cout << "写入成功: " << r_eax << endl;

err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc实例关闭成功" << endl;

return 0;
}

输出

image.png

uc_reg_read

uc_err uc_reg_read(uc_engine *uc, int regid, void *value);

读取寄存器的值

@uc: uc_open()返回的句柄
@regid: 将被读取的寄存器ID
@value: 指向保存寄存器值的指针

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_reg_read(uc_engine *uc, int regid, void *value)
{
return uc_reg_read_batch(uc, &regid, &value, 1);
}

uc_err uc_reg_read_batch(uc_engine *uc, int *ids, void **vals, int count)
{
if (uc->reg_read)
uc->reg_read(uc, (unsigned int *)ids, vals, count);
else
return -1;

return UC_ERR_OK;
}

使用示例:

#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;

int main()
{
uc_engine* uc;
uc_err err;

err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc实例创建成功" << endl;

int r_eax = 0x12;
err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
if (!err)
cout << "写入成功: " << r_eax << endl;

int recv_eax;
err = uc_reg_read(uc, UC_X86_REG_ECX, &recv_eax);
if (!err)
cout << "读取成功: " << recv_eax << endl;

err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc实例关闭成功" << endl;

return 0;
}

输出

image.png

uc_reg_write_batch

uc_err uc_reg_write_batch(uc_engine *uc, int *regs, void *const *vals, int count);

同时将多个值写入多个寄存器

@uc: uc_open()返回的句柄
@regid: 存储将被写入的多个寄存器ID的数组
@value: 指向保存多个值的数组的指针
@count: *regs 和 *vals 数组的长度

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_reg_write_batch(uc_engine *uc, int *ids, void *const *vals, int count)
{
int ret = UC_ERR_OK;
if (uc->reg_write)
ret = uc->reg_write(uc, (unsigned int *)ids, vals, count);
else
return UC_ERR_EXCEPTION;

return ret;
}

使用示例:

#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;

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];

int main()
{
int i;
uc_err err;
uc_engine* uc;

// set up register pointers
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;
}

// reg_write_batch
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;
}

// reg_read_batch
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");

uint64_t var[7] = { 0 };
for (int i = 0; i < 7; i++)
{
cout << syscall_abi[i] << " ";
printf("%" PRIu64, vals[i]);
cout << endl;
}

return 0;
}

输出

image.png

uc_reg_read_batch

uc_err uc_reg_read_batch(uc_engine *uc, int *regs, void **vals, int count);

同时读取多个寄存器的值。

@uc: uc_open()返回的句柄
@regid: 存储将被读取的多个寄存器ID的数组
@value: 指向保存多个值的数组的指针
@count: *regs 和 *vals 数组的长度

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_reg_read_batch(uc_engine *uc, int *ids, void **vals, int count)
{
if (uc->reg_read)
uc->reg_read(uc, (unsigned int *)ids, vals, count);
else
return -1;

return UC_ERR_OK;
}

使用示例同uc_reg_write_batch()。

uc_mem_write

uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t size);

在内存中写入一段字节码。

 @uc: uc_open() 返回的句柄
@address: 写入字节的起始地址
@bytes: 指向一个包含要写入内存的数据的指针
@size: 要写入的内存大小。

注意: @bytes 必须足够大以包含 @size 字节。

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, size_t size)
{
size_t count = 0, len;
const uint8_t *bytes = _bytes;

if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}

if (!check_mem_area(uc, address, size))
return UC_ERR_WRITE_UNMAPPED;

// 内存区域可以重叠相邻的内存块
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
uint32_t operms = mr->perms;
if (!(operms & UC_PROT_WRITE)) // 没有写保护
// 标记为可写
uc->readonly_mem(mr, false);

len = (size_t)MIN(size - count, mr->end - address);
if (uc->write_mem(&uc->as, address, bytes, len) == false)
break;

if (!(operms & UC_PROT_WRITE)) // 没有写保护
// 设置写保护
uc->readonly_mem(mr, true);

count += len;
address += len;
bytes += len;
} else // 此地址尚未被映射
break;
}

if (count == size)
return UC_ERR_OK;
else
return UC_ERR_WRITE_UNMAPPED;
}

使用示例:

#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;

#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx
#define ADDRESS 0x1000

int main()
{
uc_engine* uc;
uc_err err;

err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}

uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}

uint32_t code;

if(uc_mem_read(uc,ADDRESS,&code, sizeof(code))) {
printf("Failed to read emulation code to memory, quit!\n");
return -1;
}

cout << hex << code << endl;

err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
return 0;
}

输出

image.png

uc_mem_read

uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *bytes, size_t size);

从内存中读取字节。

 @uc: uc_open() 返回的句柄
@address: 读取字节的起始地址
@bytes: 指向一个包含要读取内存的数据的指针
@size: 要读取的内存大小。

注意: @bytes 必须足够大以包含 @size 字节。

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, size_t size)
{
size_t count = 0, len;
uint8_t *bytes = _bytes;

if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}

if (!check_mem_area(uc, address, size))
return UC_ERR_READ_UNMAPPED;

// 内存区域可以重叠相邻的内存块
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
len = (size_t)MIN(size - count, mr->end - address);
if (uc->read_mem(&uc->as, address, bytes, len) == false)
break;
count += len;
address += len;
bytes += len;
} else // 此地址尚未被映射
break;
}

if (count == size)
return UC_ERR_OK;
else
return UC_ERR_READ_UNMAPPED;
}

使用示例同uc_mem_write()

uc_emu_start

uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count);

在指定的时间内模拟机器码。

@uc: uc_open() 返回的句柄
@begin: 开始模拟的地址
@until: 模拟停止的地址 (当到达该地址时)
@timeout: 模拟代码的持续时间(以微秒计)。当这个值为0时,将在无限时间内模拟代码,直到代码完成。
@count: 要模拟的指令数。当这个值为0时,将模拟所有可用的代码,直到代码完成

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count)
{
// 重制计数器
uc->emu_counter = 0;
uc->invalid_error = UC_ERR_OK;
uc->block_full = false;
uc->emulation_done = false;
uc->timed_out = false;

switch(uc->arch) {
default:
break;
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K:
uc_reg_write(uc, UC_M68K_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86:
switch(uc->mode) {
default:
break;
case UC_MODE_16: {
uint64_t ip;
uint16_t cs;

uc_reg_read(uc, UC_X86_REG_CS, &cs);
// 抵消后面增加的 IP 和 CS
ip = begin - cs*16;
uc_reg_write(uc, UC_X86_REG_IP, &ip);
break;
}
case UC_MODE_32:
uc_reg_write(uc, UC_X86_REG_EIP, &begin);
break;
case UC_MODE_64:
uc_reg_write(uc, UC_X86_REG_RIP, &begin);
break;
}
break;
#endif
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM:
uc_reg_write(uc, UC_ARM_REG_R15, &begin);
break;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64:
uc_reg_write(uc, UC_ARM64_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_MIPS
case UC_ARCH_MIPS:
// TODO: MIPS32/MIPS64/BIGENDIAN etc
uc_reg_write(uc, UC_MIPS_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC:
// TODO: Sparc/Sparc64
uc_reg_write(uc, UC_SPARC_REG_PC, &begin);
break;
#endif
}

uc->stop_request = false;

uc->emu_count = count;
// 如果不需要计数,则移除计数挂钩hook
if (count <= 0 && uc->count_hook != 0) {
uc_hook_del(uc, uc->count_hook);
uc->count_hook = 0;
}
// 设置计数hook记录指令数
if (count > 0 && uc->count_hook == 0) {
uc_err err;
// 对计数指令的回调必须在所有其他操作之前运行,因此必须在hook列表的开头插入hook,而不是附加hook
uc->hook_insert = 1;
err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, NULL, 1, 0);
// 恢复到 uc_hook_add()
uc->hook_insert = 0;
if (err != UC_ERR_OK) {
return err;
}
}

uc->addr_end = until;

if (timeout)
enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds

if (uc->vm_start(uc)) {
return UC_ERR_RESOURCE;
}

// 模拟完成
uc->emulation_done = true;

if (timeout) {
// 等待超时
qemu_thread_join(&uc->timer);
}

if(uc->timed_out)
return UC_ERR_TIMEOUT;

return uc->invalid_error;
}

使用示例:

#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;

#define X86_CODE32 "\x33\xC0" // xor eax, eax
#define ADDRESS 0x1000

int main()
{
uc_engine* uc;
uc_err err;

int r_eax = 0x111;

err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}

uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}

uc_reg_write(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> before EAX = 0x%x\n", r_eax);

err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}

uc_reg_read(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> after EAX = 0x%x\n", r_eax);

err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}

return 0;
}

输出

image.png

uc_emu_stop

uc_err uc_emu_stop(uc_engine *uc);

停止模拟

通常是从通过 tracing API注册的回调函数中调用。

@uc: uc_open() 返回的句柄

@return 成功则返回UC_ERR_OK , 否则返回 uc_err 枚举的其他错误类型

源码实现

uc_err uc_emu_stop(uc_engine *uc)
{
if (uc->emulation_done)
return UC_ERR_OK;

uc->stop_request = true;

if (uc->current_cpu) {
// 退出当前线程
cpu_exit(uc->current_cpu);
}

return UC_ERR_OK;
}

使用示例:

uc_emu_stop(uc);

From https://kabeor.github.io/Unicorn-CPU模拟框架数据类型及API分析与示例(三)//) bye

This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接:https://kabeor.github.io/Unicorn-CPU模拟框架数据类型及API分析与示例(三)/