unicorn的使用(python+c)

安装

安装分为两种,一种是从源安装,一种是从源码构建,从源安装支持python使用,但是如果是c的话就要使用从源码构建

从源安装

pip install unicorn

从源码构建

点这里下载安装包

./make.sh
sudo ./make.sh install

unicorn的使用

简单介绍

首先简单介绍一下unicorn,它基于qemu开发,是一个轻量级,多平台,多架构的CPU模拟器框架,让我们更好地关注CPU操作,忽略机器设备的差异。

代码编写思路

对于利用unicorn编写的代码思路一般为:设置好参数->加载模拟的代码->添加hook->开始跑

python用法

1、设置参数并运行调试代码

首先先看头文件

from unicorn import*#调用unicorn库
from unicorn.x86_const import*#我们构造的是x86寄存器,所以还需要使用一些x86寄存器的常量,所以还要调用这个库,同理如果是x64的话就改成unicorn,x64_const

使用Uc类初始化

mu=Uc(UC_ARCH_X86,UC_MODE_32)
#这个Uc类接受两个参数分别是硬件架构和硬件模式,在这个样例中我们选用的是X86体系结构和32位代码

下面给出相关的常量:

arch:UC_ARCH_ARM、UC_ARCH_ARM64、UC_ARCH_M68K、UC_ARCH_MAX、UC_ARCH_MIPS、UC_ARCH_PPC、UC_ARCH_SPARC、UC_ARCH_X86
mode:UC_MODE_16、UC_MODE_32、UC_MODE_64、UC_MODE_ARM、UC_MODE_BIG_ENDIAN、UC_MODE_LITTLE_ENDIAN、UC_MODE_MCLASS、UC_MODE_MICRO、UC_MODE_MIPS3、UC_MODE_MIPS32、UC_MODE_MIPS32R6、UC_MODE_MIPS64、UC_MODE_PPC32、UC_MODE_PPC64、UC_MODE_QPX、UC_MODE_SPARC32、UC_MODE_SPARC64、UC_MODE_THUMB、UC_MODE_V8、UC_MODE_V9

定义虚拟地址:

ADDRESS=0x1000000#注意一定要与0x1000对齐

映射代码内存,所有CPU操作都只能访问此内存,默认权限为READ、WRITE、EXECUTE

mu.mem_map(ADDRESS,2*1024*1024)#接受两个参数,分别是地址和大小,要注意地址与大小一定都要是0x1000对齐

把要模拟的代码加载到我们刚刚映射的内存上

#这个有两种实现方式第一种是直接把调试代码写在此代码中,还有一种就是调用此代码外的二进制代码
#第一种:
x86_CODE=b'\x41\x4a'#这两个x86的指令为“INC ecx”(+1指令)和“DEC dex”(-1指令)
mu.mem_write(ADDRESS,x86_CODE)

#第二种:
mu.mem_write(ADDRESS,open('./test').read())

开始运行

mu.emu_start(ADDRESS,ADDRESS+len(x86_CODE))
#这个函数本来是有四个参数的,分别是需要模拟代码的初始地址、结束地址、模拟的时间、模拟的指令数量,我们通常忽略后面两个参数,这样就会在无限的时间中模拟无限数量的指令

2、获取和修改寄存器内容

当然对于我们要调试,那么最重要的就是可以查看的修改寄存器的值,所以unicorn也提供了这样的功能

mu.reg_write(UC_X86_REG_ECX,0x1234)
mu.reg_write(UC_X86_REG_EDX,0x7890)
#这个函数接受两个参数,分别是寄存器地址(这个在常量中有)、要写入的内容
r_ecx=mu.reg_read(UC_X86_REG_ECX)
r_edx=mu.reg_read(UC_X86_REG_EDX)
#这个函数只接受一个参数,那就是寄存器地址,返回这个寄存器的内容

3、上述样例的完整代码

from unicorn import*
from unicorn.x86_const import*
mu=Uc(UC_ARCH_X86,UC_MODE_32)
ADDRESS=0x1000000
mu.mem_map(ADDRESS,2*1024*1024)
x86_CODE=b'\x41\x4a'
r_ecx=mu.reg_read(UC_X86_REG_ECX)
r_edx=mu.reg_read(UC_X86_REG_EDX)
print('ecx:',r_ecx)
print('edx:',r_edx)
mu.reg_write(UC_X86_REG_ECX,0x1234)
mu.reg_write(UC_X86_REG_EDX,0x7890)
r_ecx=mu.reg_read(UC_X86_REG_ECX)
r_edx=mu.reg_read(UC_X86_REG_EDX)
print('ecx:',r_ecx)
print('edx:',r_edx)
mu.mem_write(ADDRESS,x86_CODE)
mu.emu_start(ADDRESS,ADDRESS+len(x86_CODE))
r_ecx=mu.reg_read(UC_X86_REG_ECX)
r_edx=mu.reg_read(UC_X86_REG_EDX)
print('ecx:',r_ecx)
print('edx:',r_edx)

运行截图:

c的用法

整体的思路还是一样的,不过在一些函数调用的方式会有所不同

1、设置参数并且运行调试代码

头文件

#include<unicorn/unicorn.h>

定义程序地址

#define ADDRESS 0x1000000
//同样还是注意0x1000对齐

定义调试程序编码

#define X86_CODE "\x41\x4a"
//INC ecx;DEC edx

定义UC类和err

uc_engine *uc;
uc_err err;
//因为后面要判断是否创建或者运行成功,所以定义一个err来接收错误信息

初始化Uc类

err=uc_open(UC_ARCH_X86,UC_MODE_32,&uc);
//这个是接收三个参数,分别是硬件架构、硬件模式、uc类地址
if(err!=UC_ERR_OK)
{
  printf("Failed to open with error returned:%u\n",err);
  return 0;
}
//这个就是上面提到的报错信息,我们接收报错信息然后判断是否报错,是的话直接kill程序

加载地址

uc_mem_map(uc,ADDRESS,2*1024*1024,UC_PROT_ALL)
//这个的话相比于python就在前面多了一个Uc类的参数,还是一样地址和大小要0x1000对齐

把调试程序加载到地址上

if(uc_mem_write(uc,ADDRESS,X86_CODE,sizeof(X86_CODE)-1))
{
  printf("Failed to write code to memory\n");
  return 0;
}
//这个也是相对于python而言前面多了一个Uc类的参数,同样还要判断加载是否错误

运行调试程序

err=uc_emu_start(uc,ADDRESS,ADDRESS+sizeof(X86_CODE)-1,0,0);
//这个是有五个参数,分别是uc类、调试代码的起始地址、调试代码的长度、要运行时间、要运行的指令数,我们把后面两个参数设置为0,表示运行时间无穷大,运行指令数量无穷多,这个也可以像python一样省略,python也可以像在这个一样设置为0
if(err)
{
  printf("Failed to uc_emu_start with error returned %u:%s\n",err,uc_strerror(err));
  return 0;
}
//同样还是判断是否错误

关闭uc类

uc_close(uc);
//因为我们这个是申请了堆空间来运行的,所以一定要调用这个函数来关闭,否则会内存泄露

2、获取和修改寄存器内容

int r_ecx=0x1234;
int r_edx=0x7890;
uc_reg_write(uc,UC_X86_REG_ECX,&r_ecx);
uc_reg_write(uc,UC_X86_REG_EDX,&r_edx);
//这个接收三个参数,分别是Uc类、寄存器地址、存放写入内容的地址
uc_reg_read(uc,UC_X86_REG_ECX,&r_ecx);
uc_reg_read(uc,UC_X86_REG_EDX,&r_edx);
//这个接收三个参数,分别是Uc类、寄存器地址、接收变量的地址

3、上述样例的整个代码

#include<unicorn/unicorn.h>
#define ADDRESS 0x1000000
#define X86_CODE "\x41\x4a"
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 to open with error returned:%u\n",err);
      return 0;
    }
    uc_mem_map(uc,ADDRESS,2*1024*1024,UC_PROT_ALL);
    if(uc_mem_write(uc,ADDRESS,X86_CODE,sizeof(X86_CODE)-1))
    {
      printf("Failed to write code to memory\n");
      return 0;
    }
    int r_ecx;
    int r_edx;
    uc_reg_read(uc,UC_X86_REG_ECX,&r_ecx);
    uc_reg_read(uc,UC_X86_REG_EDX,&r_edx);
    printf("ecx:0x%x\n",r_ecx);
    printf("edx:0x%x\n",r_edx);
    r_ecx=0x1234;
    r_edx=0x7890;
    uc_reg_write(uc,UC_X86_REG_ECX,&r_ecx);
    uc_reg_write(uc,UC_X86_REG_EDX,&r_edx);
    uc_reg_read(uc,UC_X86_REG_ECX,&r_ecx);
    uc_reg_read(uc,UC_X86_REG_EDX,&r_edx);
    printf("ecx:0x%x\n",r_ecx);
    printf("edx:0x%x\n",r_edx);
    err=uc_emu_start(uc,ADDRESS,ADDRESS+sizeof(X86_CODE)-1,0,0);
    if(err)
    {
      printf("Failed to uc_emu_start with error returned %u:%s\n",err,uc_strerror(err));
      return 0;
    }
    uc_reg_read(uc,UC_X86_REG_ECX,&r_ecx);
    uc_reg_read(uc,UC_X86_REG_EDX,&r_edx);
    printf("ecx:0x%x\n",r_ecx);
    printf("edx:0x%x\n",r_edx);
    uc_close(uc);
}

运行截图

注意事项:
对于这个c文件的编译需要用makefile,至于为啥,咱也不知道咱也不敢问,Makefile的只用方法就是在同目录下建立一个叫做Makefile的文件,里面写:

LDFLAGS += $(shell pkg-config --libs glib-2.0) -pthread -lm -lunicorn

TESTS = test

all: $(TESTS)

clean:
        rm -f $(TESTS)

%: %.c
        $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

.PHONY: all clean

我们注意到TESTS=test这一行,这个test就是我们要编译的c文件的文件名,你是啥就改成啥,然后输入指令make就可以生成执行文件了
截图示例:

posted @ 2022-07-01 10:31  予柒  阅读(3528)  评论(0)    收藏  举报
返回顶端
Live2D /*修改地一:waifu.css*/
/*修改地二:waifu.css*/
/*修改地三:live2d.js*/ /*修改地四:waifu-tips.js*/