Skip to content

Instantly share code, notes, and snippets.

@buzzer-re
Created September 16, 2022 02:09
Show Gist options
  • Save buzzer-re/e4d8b09501127ceb15d72dcc2059d421 to your computer and use it in GitHub Desktop.
Save buzzer-re/e4d8b09501127ceb15d72dcc2059d421 to your computer and use it in GitHub Desktop.
Unicorn Engine Documentation English (from translate)

Unicorn-Engine API Documentation

Version 2.0.0

Official API document by kabeor

PDF File

Unicorn Engine is a lightweight, multi-platform, multi-architecture CPU simulator framework, the current version is based on [Qemu](https://www.qemu. org/) 5.0.1 development, it can replace the execution of CPU simulation code, commonly used in program virtualization, malicious code analysis, Fuzzing, etc. This project is used in [Qiling Virtual Framework] (https://github.com/qilingframework/ qiling), Radare2 Reverse Analysis Framework, GEF (pwn analysis plugin for gdb), [Pwndbg] (https://github.com/pwndbg/pwndbg), Angr Symbol Execution Framework and many other famous projects.

0x0 Development preparation

Unicorn official website: http://www.unicorn-engine.org

git clone https://github.com/unicorn-engine/unicorn.git

compile

Linux & MacOS

Ubuntu

sudo apt install cmake pkg-config

MacOS

brew install cmake pkg-config

Compile with the following command

mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
Windows

Compiling with Microsoft MSVC compiler

//Install cmake and Microsoft Visual Studio (>=16.8)
//Compile using the following command in Visual Studio Command Prompt
mkdir build; cd build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
nmake
Compile lib and dll (old) in VS GUI

Source code: https://github.com/unicorn-engine/unicorn/archive/master.zip

Unzip after download

The file structure is as follows:

. <- main engine core engine + README + compilation documentation COMPILE.TXT etc.
├── bindings <- bindings
│ ├── dotnet <- .Net bindings + test code
│ ├── go <- go bindings + test code
│ ├── haskell <- Haskell bindings + test code
│ ├── java <- Java bindings + test code
│ ├── pascal <- Pascal bindings + test code
│ ├── python <- Python bindings + test code
│ ├── ruby ​​<- Ruby bindings + test code
│ ├── rust <- Rust bindings + test code
│ └── vb6 <- VB6 binding + test code
├── docs <- docs
├── glib_compat <- Compatibility library modified from glib 2.64.4
├── include <- C header files
├── msvc <- Microsoft Visual Studio Support (Windows)
├── qemu <- qemu (modified) source code
├── samples <- Unicorn usage example
└── tests <- C language test case

The following demonstrates Windows10 compiled with Visual Studio 2019

Open the msvc folder, the internal structure is as follows

image.png

VS opens the unicorn.sln project file, the solution automatically loads these

image.png

If you need all of them, just compile them directly. You only need a few of them. Right-click Solution -> Properties -> Configuration Properties -> Build Options and check the support items you need.

Multiple project actions can also be configured in the startup project as follows

image.png

After compilation, the unicorn.lib static compilation library and the unicorn.dll dynamic library will be generated in the Debug directory of the current folder, so that you can start developing with Unicorn

The latest compiled version currently provided by the official is 1.0.3 version, you can edit the source code of the latest version yourself to obtain more available APIs.

Win32: https://github.com/unicorn-engine/unicorn/releases/download/2.0.0/unicorn-2.0.0-win32.zip

Win64: https://github.com/unicorn-engine/unicorn/releases/download/2.0.0/unicorn-2.0.0-win64.zip

Note: Choosing x32 or x64 will affect the architecture developed later

Click Compile, go to unicorn\msvc\x32 or x64\Debug or Release to find unicorn.dll and unicorn.lib

Click [here] for other compilation methods (https://github.com/unicorn-engine/unicorn/blob/master/docs/COMPILE.md)

Install

  • Python module
pip install unicorn

//If the 1.x version has been installed, you can upgrade directly by the following command
pip install unicorn --upgrade
  • MacOS HomeBrew Pack
brew install unicorn

Engine call test

(Take Windows VS2019 as an example) Create a new VS project, copy all the header files and compiled lib and dll files in ..\unicorn-master\include\unicorn to the main directory of the new project

image.png

In the VS solution, add the existing item unicorn.h to the header file, add unicorn.lib to the resource file, rebuild the solution

image.png

Next test our generated Unicorn engine

The main file code is as follows

Code
#include <iostream>
#include "unicorn/unicorn.h"

// command to simulate
#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx

// initial address
#define ADDRESS 0x1000000

int main()
{
    uc_engine* uc;
    uc_err err;
    int r_ecx = 0x1234; // ECX register
    int r_edx = 0x7890; // EDX register

    printf("Emulate i386 code\n");

    // X86-32bit mode initialization simulation
    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;
    }

    // Request 2MB of memory for the emulator
    uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

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

    // initialize registers
    uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx);
    uc_reg_write(uc, UC_X86_REG_EDX, &r_edx);

    printf(">>> ECX = 0x%x\n", r_ecx);
    printf(">>> EDX = 0x%x\n", r_edx);

     // mock code
    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));
    }

    // print register value
    printf("Emulation done. Below is the CPU context\n");

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

    return 0;
}

The running result is as follows

image.png

ecx+1 and edx-1 simulate successfully.

0x1 data type

Index

uc_arch

uc_mode

uc_err

uc_mem_type

uc_hook_type

Hook Types

uc_mem_region

uc_query_type

uc_control_type

uc_context

uc_prot


uc_arch

Architecture choice

Code
typedef enum uc_arch {
    UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
    UC_ARCH_ARM64, // ARM-64, also known as AArch64
    UC_ARCH_MIPS, // Mips architecture
    UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
    UC_ARCH_PPC, // PowerPC architecture
    UC_ARCH_SPARC, // Sparc schema
    UC_ARCH_M68K, // M68K architecture
    UC_ARCH_RISCV, // RISCV architecture
    UC_ARCH_S390X, // S390X architecture
    UC_ARCH_TRICORE, // TriCore architecture
    UC_ARCH_MAX,
} uc_arch;

uc_mode

Mode selection

Code
typedef enum uc_mode {
    UC_MODE_LITTLE_ENDIAN = 0, // little endian mode (default)
    UC_MODE_BIG_ENDIAN = 1 << 30, // big endian mode

    //arm/arm64
    UC_MODE_ARM = 0, // ARM mode
    UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
    
    // Deprecated, use UC_ARM_CPU_* and uc_ctl instead
    UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series
    UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM
    UC_MODE_ARMBE8 = 1 << 10, // Big endian data and little endian code are only compatible with UC1 version

    // arm (32bit) cpu type
    // Deprecated, use UC_ARM_CPU_* and uc_ctl instead
    UC_MODE_ARM926 = 1 << 7, // ARM926 CPU type
    UC_MODE_ARM946 = 1 << 8, // ARM946 CPU type
    UC_MODE_ARM1176 = 1 << 9, // ARM1176 CPU type

    // mips
    UC_MODE_MICRO = 1 << 4, // MicroMips mode (not supported yet)
    UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (not supported yet)
    UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (not supported yet)
    UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
    UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA

    //x86/x64
    UC_MODE_16 = 1 << 1, // 16-bit mode
    UC_MODE_32 = 1 << 2, // 32-bit mode
    UC_MODE_64 = 1 << 3, // 64-bit mode

    // ppc
    UC_MODE_PPC32 = 1 << 2, // 32-bit mode
    UC_MODE_PPC64 = 1 << 3, // 64-bit mode (not supported yet)
    UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (not supported yet)

    // sparc
    UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
    UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
    UC_MODE_V9 = 1 << 4, // SparcV9 mode (not supported yet)

    // riscv
    UC_MODE_RISCV32 = 1 << 2, // 32-bit mode
    UC_MODE_RISCV64 = 1 << 3, // 64-bit mode

    // m68k
} uc_mode;

uc_err

Error type, which is the return value of uc_errno()

Code
typedef enum uc_err {
    UC_ERR_OK = 0, // no error
    UC_ERR_NOMEM, // out of memory: uc_open(), uc_emulate()
    UC_ERR_ARCH, // unsupported architecture: uc_open()
    UC_ERR_HANDLE, // unavailable handle
    UC_ERR_MODE, // Unavailable/unsupported architecture: uc_open()
    UC_ERR_VERSION, // unsupported version (or language binding)
    UC_ERR_READ_UNMAPPED, // Exit simulation due to reading on unmapped memory: uc_emu_start()
    UC_ERR_WRITE_UNMAPPED, // Exit simulation due to writing on unmapped memory: uc_emu_start()
    UC_ERR_FETCH_UNMAPPED, // Exit simulation due to fetching data in unmapped memory: uc_emu_start()
    UC_ERR_HOOK, // invalid hook type: uc_hook_add()
    UC_ERR_INSN_INVALID, // Exit simulation due to invalid instruction: uc_emu_start()
    UC_ERR_MAP, // invalid memory map: uc_mem_map()
    UC_ERR_WRITE_PROT, // Stop simulation due to conflict with UC_MEM_WRITE_PROT: uc_emu_start()
    UC_ERR_READ_PROT, // Stop simulation due to conflict with UC_MEM_READ_PROT: uc_emu_start()
    UC_ERR_FETCH_PROT, // Stop simulation due to conflict with UC_MEM_FETCH_PROT: uc_emu_start()
    UC_ERR_ARG, // invalid argument supplied to uc_xxx function
    UC_ERR_READ_UNALIGNED, // unaligned read
    UC_ERR_WRITE_UNALIGNED, // unaligned write
    UC_ERR_FETCH_UNALIGNED, // unaligned fetch
    UC_ERR_HOOK_EXIST, // Hook for this event already exists
    UC_ERR_RESOURCE, // Insufficient resources: uc_emu_start()
    UC_ERR_EXCEPTION, // unhandled CPU exception
} uc_err;

uc_mem_type

All memory access types for UC_HOOK_MEM_*

Code
typedef enum uc_mem_type {
    UC_MEM_READ = 16, // memory is read from..
    UC_MEM_WRITE, // memory write to..
    UC_MEM_FETCH, // memory is fetched
    UC_MEM_READ_UNMAPPED, // unmapped memory read from ..
    UC_MEM_WRITE_UNMAPPED, // Unmapped memory is written to..
    UC_MEM_FETCH_UNMAPPED, // unmapped memory is fetched
    UC_MEM_WRITE_PROT, // Memory write protected, but mapped
    UC_MEM_READ_PROT, // Memory read protected, but mapped
    UC_MEM_FETCH_PROT, // memory not executable, but mapped
    UC_MEM_READ_AFTER, // memory is read from (address successfully accessed)
} uc_mem_type;

uc_hook_type

All hook type parameters of uc_hook_add()

Code
typedef enum uc_hook_type {
    // Hook all interrupt/syscall events
    UC_HOOK_INTR = 1 << 0,
    // Hook a specific instruction - only a very small subset of instructions is supported
    UC_HOOK_INSN = 1 << 1,
    // Hook a piece of code
    UC_HOOK_CODE = 1 << 2,
    // Hook basic block
    UC_HOOK_BLOCK = 1 << 3,
    // Hook for reading memory on unmapped memory
    UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
    // Hook invalid memory write event
    UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
    // invalid memory for Hook execution event
    UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
    // Hook read protected memory
    UC_HOOK_MEM_READ_PROT = 1 << 7,
    // Hook write-protected memory
    UC_HOOK_MEM_WRITE_PROT = 1 << 8,
    // Hook memory on non-executable memory
    UC_HOOK_MEM_FETCH_PROT = 1 << 9,
    // Hook memory read event
    UC_HOOK_MEM_READ = 1 << 10,
    // Hook memory write event
    UC_HOOK_MEM_WRITE = 1 << 11,
    // Hook memory acquisition execution event
    UC_HOOK_MEM_FETCH = 1 << 12,
    // Hook memory read events, only addresses that can be successfully accessed are allowed
    // Callback will be triggered after successful read
    UC_HOOK_MEM_READ_AFTER = 1 << 13,
    // Hook invalid instruction exception
    UC_HOOK_INSN_INVALID = 1 << 14,
    // Hook new (execution flow) edge generating events. May be useful in program analysis.
    // Note: This Hook differs from UC_HOOK_BLOCK in two ways:
    // 1. This Hook is called before the instruction is executed.
    // 2. This Hook is only called when the generated event is triggered.
    UC_HOOK_EDGE_GENERATED = 1 << 15,
    // Hook specific tcg opcode. Usage is similar to UC_HOOK_INSN.
    UC_HOOK_TCG_OPCODE = 1 << 16,
} uc_hook_type;

hook_types

Macro definition Hook type

Code
// Hook events for all unmapped memory accesses
#define UC_HOOK_MEM_UNMAPPED (UC_HOOK_MEM_READ_UNMAPPED + UC_HOOK_MEM_WRITE_UNMAPPED + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook all illegal access events to protected memory
#define UC_HOOK_MEM_PROT (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_FETCH_PROT)
// Hook all events that read memory illegally
#define UC_HOOK_MEM_READ_INVALID (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_READ_UNMAPPED)
// Hook all events that are illegally written to memory
#define UC_HOOK_MEM_WRITE_INVALID (UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_WRITE_UNMAPPED)
// Hook all illegal memory acquisition events
#define UC_HOOK_MEM_FETCH_INVALID (UC_HOOK_MEM_FETCH_PROT + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook all illegal memory access events
#define UC_HOOK_MEM_INVALID (UC_HOOK_MEM_UNMAPPED + UC_HOOK_MEM_PROT)
// Hook events for all valid memory accesses
// Note: UC_HOOK_MEM_READ fires before UC_HOOK_MEM_READ_PROT and UC_HOOK_MEM_READ_UNMAPPED,
// So this Hook might trigger some invalid reads.
#define UC_HOOK_MEM_VALID (UC_HOOK_MEM_READ + UC_HOOK_MEM_WRITE + UC_HOOK_MEM_FETCH)

uc_mem_region

Memory area mapped by uc_mem_map() and uc_mem_map_ptr() Use uc_mem_regions() to retrieve a list of this memory region

Code
typedef struct uc_mem_region {
    uint64_t begin; // area start address (inclusive)
    uint64_t end; // area end address (inclusive)
    uint32_t perms; // memory permissions for the region
} uc_mem_region;

uc_query_type

All query type parameters of uc_query()

Code
typedef enum uc_query_type {
    // Dynamically query the current hardware mode
    UC_QUERY_MODE = 1,
    UC_QUERY_PAGE_SIZE, // pagesize of query engine instance
    UC_QUERY_ARCH, // the schema type of the query engine instance
    UC_QUERY_TIMEOUT, // Query whether to stop simulation due to timeout (if result = True it means yes)
} uc_query_type;

uc_control_type

All query type parameters of uc_ctl()

Code
// uc_ctl implementation is similar to Linux ioctl but slightly different
//
// uc_control_type is organized in uc_ctl as follows:
//
// R/W NR Reserved Type
// [ ] [ ] [ ] [ ]
// 31 30 29 26 25 16 15 0
//
// @R/W: Whether the operation is a read/write access.
// @NR: Number of parameters.
// @Reserved: 0, reserved for future extensions.
// @Type: enum in uc_control_type.

// No input and output parameters.
#define UC_CTL_IO_NONE (0)
// Only input parameters for a write operation.
#define UC_CTL_IO_WRITE (1)
// Only output parameters for a read operation.
#define UC_CTL_IO_READ (2)
// The parameter contains both read and write operations.
#define UC_CTL_IO_READ_WRITE (UC_CTL_IO_WRITE | UC_CTL_IO_READ)

#define UC_CTL(type, nr, rw) \
    (uc_control_type)((type) | ((nr) << 26) | ((rw) << 30))
#define UC_CTL_NONE(type, nr) UC_CTL(type, nr, UC_CTL_IO_NONE)
#define UC_CTL_READ(type, nr) UC_CTL(type, nr, UC_CTL_IO_READ)
#define UC_CTL_WRITE(type, nr) UC_CTL(type, nr, UC_CTL_IO_WRITE)
#define UC_CTL_READ_WRITE(type, nr) UC_CTL(type, nr, UC_CTL_IO_READ_WRITE)
// The control chain is organized in a tree structure.
// If a control state does not have `Set` or `Get` filled in for @args, then r/o or w/o.
typedef enum uc_control_type {
    // current mode.
    // Read: @args = (int*)
    UC_CTL_UC_MODE = 0,
    // current page size.
    // Write: @args = (uint32_t)
    // Read: @args = (uint32_t*)
    UC_CTL_UC_PAGE_SIZE,
    // Current schema.
    // Read: @args = (int*)
    UC_CTL_UC_ARCH,
    // Current timeout.
    // Read: @args = (uint64_t*)
    UC_CTL_UC_TIMEOUT,
    // Multiple exit points are allowed.
    // Without this control state, read/set exit points will not work.
    // Write: @args = (int)
    UC_CTL_UC_USE_EXITS,
    // Current input number.
    // Read: @args = (size_t*)
    UC_CTL_UC_EXITS_CNT,
    // current input.
    // Write: @args = (uint64_t* exits, size_t len)
    // @len = UC_CTL_UC_EXITS_CNT
    // Read: @args = (uint64_t* exits, size_t len)
    // @len = UC_CTL_UC_EXITS_CNT
    UC_CTL_UC_EXITS,

    // Set the cpu mode of the uc instance.
    // Note this option can only be set before any Unicorn
    // API is called except for uc_open.
    // Write: @args = (int)
    // Read: @args = (int*)
    UC_CTL_CPU_MODEL,
    // Query the tb (translation block) cache for a specific address
    // Read: @args = (uint64_t, uc_tb*)
    UC_CTL_TB_REQUEST_CACHE,
    // disable tb (translation block) cache for specific address
    // Write: @args = (uint64_t, uint64_t)
    UC_CTL_TB_REMOVE_CACHE,
    // disable all tb (translation blocks)
    // no parameters
    UC_CTL_TB_FLUSH

} uc_control_type;

uc_context

Used with uc_context_*() to manage opaque storage of CPU contexts

Code
struct uc_context;
typedef struct uc_context uc_context;

uc_prot

Permissions for the newly mapped area

Code
typedef enum uc_prot {
   UC_PROT_NONE = 0, //None
   UC_PROT_READ = 1, //read
   UC_PROT_WRITE = 2, //write
   UC_PROT_EXEC = 4, //executable
   UC_PROT_ALL = 7, //all permissions
} uc_prot;

0x2 API

Index

uc_version

uc_arch_supported

uc_open

uc_close

uc_query

uc_errno

uc_strerror

uc_reg_write

uc_reg_read

uc_reg_write_batch

uc_reg_read_batch

uc_mem_write

uc_mem_read

uc_emu_start

uc_emu_stop

uc_hook_add

uc_hook_del

uc_mem_map

uc_mem_map_ptr

uc_mem_unmap

uc_mem_protect

uc_mem_regions

uc_free

uc_context_alloc

uc_context_save

uc_context_restore

uc_context_size

uc_context_free


uc_version

unsigned int uc_version(unsigned int *major, unsigned int *minor);

Used to return the major and minor version information of the Unicorn API

@major: API major version number
@minor: API minor version number
@return hexadecimal number, calculation method (major << 8 | minor)

Hint: The return value can be compared with the macro UC_MAKE_VERSION
source code implementation
unsigned int uc_version(unsigned int *major, unsigned int *minor)
{
    if (major != NULL && minor != NULL) {
        *major = UC_API_MAJOR;  //macro
        *minor = UC_API_MINOR;  //macro
    }

    return (UC_API_MAJOR << 8) + UC_API_MINOR;   //(major << 8 | minor)
}

Unchangeable after compilation, custom versions are not accepted

Example of use:

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

int main()
{
    unsigned int version;
    version = uc_version(NULL,NULL);
    cout << hex << version << endl;
    return 0;
}

output:

image.png

get version number 1.0.0

uc_arch_supported

bool uc_arch_supported(uc_arch arch);

Sure Unicorn Whether the current architecture is supported

 @arch: Architecture type (UC_ARCH_*)
 @return Return if supported True
source code implementation
bool uc_arch_supported(uc_arch arch)
{
    switch (arch) {
#ifdef UNICORN_HAS_ARM
        case UC_ARCH_ARM:   return true;
#endif
#ifdef UNICORN_HAS_ARM64
        case UC_ARCH_ARM64: return true;
#endif
#ifdef UNICORN_HAS_M68K
        case UC_ARCH_M68K:  return true;
#endif
#ifdef UNICORN_HAS_MIPS
        case UC_ARCH_MIPS:  return true;
#endif
#ifdef UNICORN_HAS_PPC
        case UC_ARCH_PPC:   return true;
#endif
#ifdef UNICORN_HAS_SPARC
        case UC_ARCH_SPARC: return true;
#endif
#ifdef UNICORN_HAS_X86
        case UC_ARCH_X86:   return true;
#endif
        /* invalid or disabled schema */
        default:            return false;
    }
}

Example of use:

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

int main()
{
    cout << "support UC_ARCH_X86 Architecture:" << uc_arch_supported(UC_ARCH_X86) << endl;
    return 0;
}

output:

image.png

uc_open

uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);

create new Unicorn example

@arch: Architecture type (UC_ARCH_*)
@mode: hardware mode Depend on UC_MODE_* combination
@uc: Orientation uc_enginepointer, Update when you return

@returnreturn on success UC_ERR_OK , otherwise return uc_err Other error types for enumeration
source code implementation
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **result)
{
    struct uc_struct *uc;

    if (arch < UC_ARCH_MAX) {
        uc = calloc(1, sizeof(*uc));  //申请内存
        if (!uc) {
            // 内存不足
            return UC_ERR_NOMEM;
        }

        uc->errnum = UC_ERR_OK;
        uc->arch = arch;
        uc->mode = mode;

        // 初始化
        // uc->ram_list = { .blocks = QTAILQ_HEAD_INITIALIZER(ram_list.blocks) };
        uc->ram_list.blocks.tqh_first = NULL;
        uc->ram_list.blocks.tqh_last = &(uc->ram_list.blocks.tqh_first);

        uc->memory_listeners.tqh_first = NULL;
        uc->memory_listeners.tqh_last = &uc->memory_listeners.tqh_first;

        uc->address_spaces.tqh_first = NULL;
        uc->address_spaces.tqh_last = &uc->address_spaces.tqh_first;

        switch(arch) {   // Preprocessing according to the architecture
            default:
                break;
#ifdef UNICORN_HAS_M68K
            case UC_ARCH_M68K:
                if ((mode & ~UC_MODE_M68K_MASK) ||
                        !(mode & UC_MODE_BIG_ENDIAN)) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                uc->init_arch = m68k_uc_init;
                break;
#endif
#ifdef UNICORN_HAS_X86
            case UC_ARCH_X86:
                if ((mode & ~UC_MODE_X86_MASK) ||
                        (mode & UC_MODE_BIG_ENDIAN) ||
                        !(mode & (UC_MODE_16|UC_MODE_32|UC_MODE_64))) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                uc->init_arch = x86_uc_init;
                break;
#endif
#ifdef UNICORN_HAS_ARM
            case UC_ARCH_ARM:
                if ((mode & ~UC_MODE_ARM_MASK)) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                if (mode & UC_MODE_BIG_ENDIAN) {
                    uc->init_arch = armeb_uc_init;
                } else {
                    uc->init_arch = arm_uc_init;
                }

                if (mode & UC_MODE_THUMB)uc->thumb = 1;
                break;
#endif
#ifdef UNICORN_HAS_ARM64
            case UC_ARCH_ARM64:
                if (mode & ~UC_MODE_ARM_MASK) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                if (mode & UC_MODE_BIG_ENDIAN) {
                    uc->init_arch = arm64eb_uc_init;
                } else {
                    uc->init_arch = arm64_uc_init;
                }
                break;
#endif

#if defined(UNICORN_HAS_MIPS) || defined(UNICORN_HAS_MIPSEL) || defined(UNICORN_HAS_MIPS64) || defined(UNICORN_HAS_MIPS64EL)
            case UC_ARCH_MIPS:
                if ((mode & ~UC_MODE_MIPS_MASK) ||
                        !(mode & (UC_MODE_MIPS32|UC_MODE_MIPS64))) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                if (mode & UC_MODE_BIG_ENDIAN) {
#ifdef UNICORN_HAS_MIPS
                    if (mode & UC_MODE_MIPS32)
                        uc->init_arch = mips_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64
                    if (mode & UC_MODE_MIPS64)
                        uc->init_arch = mips64_uc_init;
#endif
                } else {    // 小端序
#ifdef UNICORN_HAS_MIPSEL
                    if (mode & UC_MODE_MIPS32)
                        uc->init_arch = mipsel_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64EL
                    if (mode & UC_MODE_MIPS64)
                        uc->init_arch = mips64el_uc_init;
#endif
                }
                break;
#endif

#ifdef UNICORN_HAS_SPARC
            case UC_ARCH_SPARC:
                if ((mode & ~UC_MODE_SPARC_MASK) ||
                        !(mode & UC_MODE_BIG_ENDIAN) ||
                        !(mode & (UC_MODE_SPARC32|UC_MODE_SPARC64))) {
                    free(uc);
                    return UC_ERR_MODE;
                }
                if (mode & UC_MODE_SPARC64)
                    uc->init_arch = sparc64_uc_init;
                else
                    uc->init_arch = sparc_uc_init;
                break;
#endif
        }

        if (uc->init_arch == NULL) {
            return UC_ERR_ARCH;
        }

        if (machine_initialize(uc))
            return UC_ERR_RESOURCE;

        *result = uc;

        if (uc->reg_reset)
            uc->reg_reset(uc);

        return UC_ERR_OK;
    } else {
        return UC_ERR_ARCH;
    }
}

Notice: uc_open Will request heap memory,Must use after use uc_close freed,Otherwise leakage will occur

Example of use:

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

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

    //// initialization X86-32bit pattern simulator
    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 Engine created successfully" << endl;

    //// closure uc
    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 Engine shut down successfully" << endl;

    return 0;
}

output

image.png

uc_close

uc_err uc_close(uc_engine *uc);

Closing a uc instance will free the memory. It cannot be restored after closing.

@uc: pointer to the pointer returned by uc_open()

@return Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_close(uc_engine *uc)
{
    int i;
    struct list_item *cur;
    struct hook *hook;

    // Clean up internal data
    if (uc->release)
        uc->release(uc->tcg_ctx);
    g_free(uc->tcg_ctx);

    // clean up CPU.
    g_free(uc->cpu->tcg_as_listener);
    g_free(uc->cpu->thread);

    // clean up all objects.
    OBJECT(uc->machine_state->accelerator)->ref = 1;
    OBJECT(uc->machine_state)->ref = 1;
    OBJECT(uc->owner)->ref = 1;
    OBJECT(uc->root)->ref = 1;

    object_unref(uc, OBJECT(uc->machine_state->accelerator));
    object_unref(uc, OBJECT(uc->machine_state));
    object_unref(uc, OBJECT(uc->cpu));
    object_unref(uc, OBJECT(&uc->io_mem_notdirty));
    object_unref(uc, OBJECT(&uc->io_mem_unassigned));
    object_unref(uc, OBJECT(&uc->io_mem_rom));
    object_unref(uc, OBJECT(uc->root));

    // free memory
    g_free(uc->system_memory);

    // release the associated thread
    if (uc->qemu_thread_data)
        g_free(uc->qemu_thread_data);

    // free other data
    free(uc->l1_map);

    if (uc->bounce.buffer) {
        free(uc->bounce.buffer);
    }

    g_hash_table_foreach(uc->type_table, free_table, uc);
    g_hash_table_destroy(uc->type_table);

    for (i = 0; i < DIRTY_MEMORY_NUM; i++) {
        free(uc->ram_list.dirty_memory[i]);
    }

    // Free hook and hook list
    for (i = 0; i < UC_HOOK_MAX; i++) {
        cur = uc->hook[i].head;
        // hook Can exist in multiple lists, and the release time can be obtained by counting
        while (cur) {
            hook = (struct hook *)cur->data;
            if (--hook->refs == 0) {
                free(hook);
            }
            cur = cur->next;
        }
        list_clear(&uc->hook[i]);
    }

    free(uc->mapped_blocks);

    // finally release uc itself
    memset(uc, 0, sizeof(*uc));
    free(uc);

    return UC_ERR_OK;
}

The usage example is the same as uc_open()

uc_query

uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result);

Query the internal state of the engine

 @uc: the handle returned by uc_open()
 @type: the type of the enumeration in uc_query_type

 @result: pointer to hold the internal state being queried

 @return: Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result)
{
    if (type == UC_QUERY_PAGE_SIZE) {
        *result = uc->target_page_size;
        return UC_ERR_OK;
    }

    if (type == UC_QUERY_ARCH) {
        *result = uc->arch;
        return UC_ERR_OK;
    }

    switch(uc->arch) {
#ifdef UNICORN_HAS_ARM
        case UC_ARCH_ARM:
            return uc->query(uc, type, result);
#endif
        default:
            return UC_ERR_ARG;
    }

    return UC_ERR_OK;
}

Example of use:

#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
    uc_engine* uc;
    uc_err err;

    //// Initialize emulator in X86-32bit mode
    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 instance created successfully" << endl;

    size_t result[] = {0};
    err = uc_query(uc, UC_QUERY_ARCH, result); // query schema
    if (!err)
        cout << "Query success: " << *result << 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 instance closed successfully" << endl;

    return 0;
}

output

image.png

The result of the schema query is 4, which corresponds to UC_ARCH_X86

uc_errno

uc_err uc_errno(uc_engine *uc);

When an API function fails, report the last error number. Once accessed, uc_errno may not retain the original value.

@uc: the handle returned by uc_open()

@return: Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_errno(uc_engine *uc)
{
    return uc->errnum;
}

Example of use:

#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 instance created successfully" << endl;

    err = uc_errno(uc);
    cout << "Error number: " << err << 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 instance closed successfully" << endl;

    return 0;
}

output

image.png

No error, output error number is 0

uc_strerror

const char *uc_strerror(uc_err code);

Returns an explanation for the given error number

 @code: error number

 @return: string pointer to the interpretation of the given error number
source code implementation
const char *uc_strerror(uc_err code)
{
    switch(code) {
        default:
            return "Unknown error code";
        case UC_ERR_OK:
            return "OK (UC_ERR_OK)";
        case UC_ERR_NOMEM:
            return "No memory available or memory not present (UC_ERR_NOMEM)";
        case UC_ERR_ARCH:
            return "Invalid/unsupported architecture (UC_ERR_ARCH)";
        case UC_ERR_HANDLE:
            return "Invalid handle (UC_ERR_HANDLE)";
        case UC_ERR_MODE:
            return "Invalid mode (UC_ERR_MODE)";
        case UC_ERR_VERSION:
            return "Different API version between core & binding (UC_ERR_VERSION)";
        case UC_ERR_READ_UNMAPPED:
            return "Invalid memory read (UC_ERR_READ_UNMAPPED)";
        case UC_ERR_WRITE_UNMAPPED:
            return "Invalid memory write (UC_ERR_WRITE_UNMAPPED)";
        case UC_ERR_FETCH_UNMAPPED:
            return "Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)";
        case UC_ERR_HOOK:
            return "Invalid hook type (UC_ERR_HOOK)";
        case UC_ERR_INSN_INVALID:
            return "Invalid instruction (UC_ERR_INSN_INVALID)";
        case UC_ERR_MAP:
            return "Invalid memory mapping (UC_ERR_MAP)";
        case UC_ERR_WRITE_PROT:
            return "Write to write-protected memory (UC_ERR_WRITE_PROT)";
        case UC_ERR_READ_PROT:
            return "Read from non-readable memory (UC_ERR_READ_PROT)";
        case UC_ERR_FETCH_PROT:
            return "Fetch from non-executable memory (UC_ERR_FETCH_PROT)";
        case UC_ERR_ARG:
            return "Invalid argument (UC_ERR_ARG)";
        case UC_ERR_READ_UNALIGNED:
            return "Read from unaligned memory (UC_ERR_READ_UNALIGNED)";
        case UC_ERR_WRITE_UNALIGNED:
            return "Write to unaligned memory (UC_ERR_WRITE_UNALIGNED)";
        case UC_ERR_FETCH_UNALIGNED:
            return "Fetch from unaligned memory (UC_ERR_FETCH_UNALIGNED)";
        case UC_ERR_RESOURCE:
            return "Insufficient resource (UC_ERR_RESOURCE)";
        case UC_ERR_EXCEPTION:
            return "Unhandled CPU exception (UC_ERR_EXCEPTION)";
        case UC_ERR_TIMEOUT:
            return"Emulation timed out (UC_ERR_TIMEOUT)";
    }
}

Example of use:

#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 instance created successfully" << endl;

    err = uc_errno(uc);
    cout << "Error number: " << err << " Error description: " << uc_strerror(err) <<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 instance closed successfully" << endl;

    return 0;
}

output

image.png

uc_reg_write

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

write value to register

@uc: the handle returned by uc_open()
@regid: the ID of the register to be modified
@value: pointer to the value to which the register will be modified

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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); //write in the structure
    else
        return UC_ERR_EXCEPTION;

    return ret;
}

Example of use:

#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 instance created successfully" << endl;

    int r_eax = 0x12;
    err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
    if (!err)
        cout << "Write successful: " << 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 instance closed successfully" << endl;

    return 0;
}

output

image.png

uc_reg_read

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

read register value

@uc: the handle returned by uc_open()
@regid: the register ID to be read
@value: pointer to the holding register value

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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;
}

Example of use:

#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 instance created successfully" << endl;

    int r_eax = 0x12;
    err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
    if (!err)
        cout << "Write successful: " << r_eax << endl;

    int recv_eax;
    err = uc_reg_read(uc, UC_X86_REG_ECX, &recv_eax);
    if (!err)
        cout << "Read successfully: " << 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 instance closed successfully" << endl;

    return 0;
}

output

image.png

uc_reg_write_batch

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

Write multiple values ​​to multiple registers at the same time

@uc: the handle returned by uc_open()
@regid: an array storing multiple register IDs that will be written to
@value: pointer to an array holding multiple values
@count: length of the *regs and *vals arrays

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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;
}

Example of use:

#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;
}

output

image.png

uc_reg_read_batch

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

Read the values ​​of multiple registers at the same time.

@uc: the handle returned by uc_open()
@regid: an array storing multiple register IDs that will be read
@value: pointer to an array holding multiple values
@count: length of the *regs and *vals arrays

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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;
}

The usage example is the same as uc_reg_write_batch().

uc_mem_write

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

Writes a piece of bytecode in memory.

@uc: the handle returned by uc_open()
@address: the starting address of the write byte
@bytes: a pointer to a pointer containing the data to be written to memory
@size: Size of memory to write.

Note: @bytes must be large enough to contain @size bytes.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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;

    // Memory regions can overlap adjacent memory blocks
    while(count < size) {
        MemoryRegion *mr = memory_mapping(uc, address);
        if (mr) {
            uint32_t operms = mr->perms;
            if (!(operms & UC_PROT_WRITE)) // no write protection
                // mark as writable
                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)) // no write protection
                // set write protection
                uc->readonly_mem(mr, true);

            count += len;
            address += len;
            bytes += len;
        } else // this address has not been mapped yet
            break;
    }

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

Example of use:

#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;
}

output

image.png

uc_mem_read

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

Read bytes from memory.

 @uc: the handle returned by uc_open()
 @address: the starting address of the read byte
 @bytes: a pointer to a pointer containing the data to read from memory
 @size: Size of memory to read.

 Note: @bytes must be large enough to contain @size bytes.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
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;

    // Memory regions can overlap adjacent memory blocks
    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 // this address has not been mapped yet
            break;
    }

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

The usage example is the same as 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);

Simulate machine code for a specified amount of time.

@uc: the handle returned by uc_open()
@begin: The address to start the simulation
@until: The address where the simulation stops (when this address is reached)
@timeout: The duration of the simulated code (in microseconds). When this value is 0, the code will be simulated with no time limit until the simulation is complete.
@count: The number of instructions to simulate. When this value is 0, all executable code will be simulated until the simulation is complete

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count)
{
    // reset the counter
    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);
                    // offset the IP and CS added later
                    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;
    // If no count is required, remove the count hook
    if (count <= 0 && uc->count_hook != 0) {
        uc_hook_del(uc, uc->count_hook);
        uc->count_hook = 0;
    }
    // Set the count hook to record the number of instructions
    if (count > 0 && uc->count_hook == 0) {
        uc_err err;
        // The callback to the count instruction must run before all other operations, so the hook must be inserted at the beginning of the hook list, not appended
        uc->hook_insert = 1;
        err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, NULL, 1, 0);
        // revert to 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;
    }

    // simulation complete
    uc->emulation_done = true;

    if (timeout) {
        // wait for timeout
        qemu_thread_join(&uc->timer);
    }

    if(uc->timed_out)
        return UC_ERR_TIMEOUT;

    return uc->invalid_error;
}

Example of use:

#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;
}

output

image.png

uc_emu_stop

uc_err uc_emu_stop(uc_engine *uc);

stop simulation

Usually called from a callback function registered through the tracing API.

@uc: the handle returned by uc_open()

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_emu_stop(uc_engine *uc)
{
    if (uc->emulation_done)
        return UC_ERR_OK;

    uc->stop_request = true;

    if (uc->current_cpu) {
        // exit the current thread
        cpu_exit(uc->current_cpu);
    }

    return UC_ERR_OK;
}

Example of use:

uc_emu_stop(uc);
```



### uc_hook_add

````c
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
        void *user_data, uint64_t begin, uint64_t end, ...);

Register the callback of the hook event, which will be called when the hook event is triggered.

 @uc: the handle returned by uc_open()
 @hh: The handle obtained by registering the hook. Used in uc_hook_del()
 @type: hook type
 @callback: the callback to run when the instruction is hit
 @user_data: User-defined data. Will be passed to the last parameter of the callback function @user_data
 @begin: The starting address of the callback effective area (inclusive)
 @end: The end address of the callback effective area (inclusive)
   Note 1: The callback will be called only if the address of the callback is in [@begin, @end]
   Note 2: If @begin > @end, the callback will be called whenever this hook type is triggered
 @...: variable arguments (depending on @type)
   Note: If @type = UC_HOOK_INSN, here is the instruction ID (eg: UC_X86_INS_OUT)

 @return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
        void *user_data, uint64_t begin, uint64_t end, ...)
{
    int ret = UC_ERR_OK;
    int i = 0;

    struct hook *hook = calloc(1, sizeof(struct hook));
    if (hook == NULL) {
        return UC_ERR_NOMEM;
    }

    hook->begin = begin;
    hook->end = end;
    hook->type = type;
    hook->callback = callback;
    hook->user_data = user_data;
    hook->refs = 0;
    *hh = (uc_hook)hook;

    // UC_HOOK_INSN has an extra parameter: the instruction ID
    if (type & UC_HOOK_INSN) {
        va_list valist;

        va_start(valist, end);
        hook->insn = va_arg(valist, int);
        va_end(valist);

        if (uc->insn_hook_validate) {
            if (!uc->insn_hook_validate(hook->insn)) {
                free(hook);
                return UC_ERR_HOOK;
            }
        }

        if (uc->hook_insert) {
            if (list_insert(&uc->hook[UC_HOOK_INSN_IDX], hook) == NULL) {
                free(hook);
                return UC_ERR_NOMEM;
            }
        } else {
            if (list_append(&uc->hook[UC_HOOK_INSN_IDX], hook) == NULL) {
                free(hook);
                return UC_ERR_NOMEM;
            }
        }

        hook->refs++;
        return UC_ERR_OK;
    }

    while ((type >> i) > 0) {
        if ((type >> i) & 1) {
            if (i < UC_HOOK_MAX) {
                if (uc->hook_insert) {
                    if (list_insert(&uc->hook[i], hook) == NULL) {
                        if (hook->refs == 0) {
                            free(hook);
                        }
                        return UC_ERR_NOMEM;
                    }
                } else {
                    if (list_append(&uc->hook[i], hook) == NULL) {
                        if (hook->refs == 0) {
                            free(hook);
                        }
                        return UC_ERR_NOMEM;
                    }
                }
                hook->refs++;
            }
        }
        i++;
    }

    if (hook->refs == 0) {
        free(hook);
    }

    return ret;
}

Example of use:

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

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

output

image.png

Hook for every instruction

uc_hook_del

uc_err uc_hook_del(uc_engine *uc, uc_hook hh);

delete a registered hook event

@uc: the handle returned by uc_open()
@hh: the handle returned by uc_hook_add()

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_hook_del(uc_engine *uc, uc_hook hh)
{
    int i;
    struct hook *hook = (struct hook *)hh;

    for (i = 0; i < UC_HOOK_MAX; i++) {
        if (list_remove(&uc->hook[i], (void *)hook)) {
            if (--hook->refs == 0) {
                free(hook);
                break;
            }
        }
    }
    return UC_ERR_OK;
}

Example of use:

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_hook_del(uc, &sys_hook))) {
    uc_perror("uc_hook_del", err);
    return 1;
}

uc_mem_map

uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);

Map a block of memory for the simulation.

@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: Permissions for the newly mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms)
{
    uc_err res;

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

    res = mem_map_check(uc, address, size, perms); //memory safety check
    if (res)
        return res;

    return mem_map(uc, address, size, perms, uc->memory_map(uc, address, size, perms));
}

The usage example is the same as uc_hook_add()

uc_mem_map_ptr

uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr);

Map existing host memory in the simulation.

@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: Permissions for the newly mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.
@ptr: Pointer to host memory backing the newly mapped memory. The size of the mapped host memory should be the same as or larger than size and at least map with PROT_READ|PROT_WRITE, otherwise no mapping is defined.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr)
{
    uc_err res;

    if (ptr == NULL)
        return UC_ERR_ARG;

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

    res = mem_map_check(uc, address, size, perms); //memory safety check
    if (res)
        return res;

    return mem_map(uc, address, size, UC_PROT_ALL, uc->memory_map_ptr(uc, address, size, perms, ptr));
}

The usage example is the same as uc_mem_map()

uc_mem_unmap

uc_err uc_mem_unmap(uc_engine *uc, uint64_t address, size_t size);

Unmap the emulated memory area

@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, size_t size)
{
    MemoryRegion *mr;
    uint64_t addr;
    size_t count, len;

    if (size == 0)
        // no regions to unmap
        return UC_ERR_OK;

    // address must be aligned to uc->target_page_size
    if ((address & uc->target_page_align) != 0)
        return UC_ERR_ARG;

    // size must be a multiple of uc->target_page_size
    if ((size & uc->target_page_align) != 0)
        return UC_ERR_ARG;

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

    // Check if the entire block requested by the user is mapped
    if (!check_mem_area(uc, address, size))
        return UC_ERR_NOMEM;

    // If this region spans adjacent regions, it may be necessary to split the region
    addr = address;
    count = 0;
    while(count < size) {
        mr = memory_mapping(uc, addr);
        len = (size_t)MIN(size - count, mr->end - addr);
        if (!split_region(uc, mr, addr, len, true))
            return UC_ERR_NOMEM;

        // unmap
        mr = memory_mapping(uc, addr);
        if (mr != NULL)
           uc->memory_unmap(uc, mr);
        count += len;
        addr += len;
    }

    return UC_ERR_OK;
}

Example of use:

if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))) {
    uc_perror("uc_mem_map", err);
    return 1;
}

if ((err = uc_mem_unmap(uc, BASE, 0x1000))) {
    uc_perror("uc_mem_unmap", err);
    return 1;
}

uc_mem_protect

uc_err uc_mem_protect(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);

Set permissions for emulated memory

@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: New permissions for the mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, size_t size, uint32_t perms)
{
    MemoryRegion *mr;
    uint64_t addr = address;
    size_t count, len;
    bool remove_exec = false;

    if (size == 0)
        // trivial case, no change
        return UC_ERR_OK;

    // address must be aligned to uc->target_page_size
    if ((address & uc->target_page_align) != 0)
        return UC_ERR_ARG;

    // size must be multiple of uc->target_page_size
    if ((size & uc->target_page_align) != 0)
        return UC_ERR_ARG;

    // check for only valid permissions
    if ((perms & ~UC_PROT_ALL) != 0)
        return UC_ERR_ARG;

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

    // check that user's entire requested block is mapped
    if (!check_mem_area(uc, address, size))
        return UC_ERR_NOMEM;

    // Now we know entire region is mapped, so change permissions
    // We may need to split regions if this area spans adjacent regions
    addr = address;
    count = 0;
    while(count < size) {
        mr = memory_mapping(uc, addr);
        len = (size_t)MIN(size - count, mr->end - addr);
        if (!split_region(uc, mr, addr, len, false))
            return UC_ERR_NOMEM;

        mr = memory_mapping(uc, addr);
        // will this remove EXEC permission?
        if (((mr->perms & UC_PROT_EXEC) != 0) && ((perms & UC_PROT_EXEC) == 0))
            remove_exec = true;
        mr->perms = perms;
        uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0);

        count += len;
        addr += len;
    }

    // if EXEC permission is removed, then quit TB and continue at the same place
    if (remove_exec) {
        uc->quit_request = true;
        uc_emu_stop(uc);
    }

    return UC_ERR_OK;
}

Example of use:

if ((err = uc_mem_protect(uc, BASE, 0x1000, UC_PROT_ALL))) { //Readable, writable and executable
    uc_perror("uc_mem_protect", err);
    return 1;
}

uc_mem_regions

uc_err uc_mem_regions(uc_engine *uc, uc_mem_region **regions, uint32_t *count);

Retrieve information about memory mapped by uc_mem_map() and uc_mem_map_ptr().

This API allocates memory for @regions, which the user must then free() to avoid memory leaks.

@uc: the handle returned by uc_open()
@regions: Pointer to an array of uc_mem_region structures. Requested by Unicorn, these memory must be freed by uc_free()
@count: pointer to the number of uc_mem_region structures contained in @regions

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err

Source code analysis

Code
uint32_t uc_mem_regions(uc_engine *uc, uc_mem_region **regions, uint32_t *count)
{
    uint32_t i;
    uc_mem_region *r = NULL;

    *count = uc->mapped_block_count;

    if (*count) {
        r = g_malloc0(*count * sizeof(uc_mem_region));
        if (r == NULL) {
            // Not enough storage
            return UC_ERR_NOMEM;
        }
    }

    for (i = 0; i < *count; i++) {
        r[i].begin = uc->mapped_blocks[i]->addr;
        r[i].end = uc->mapped_blocks[i]->end - 1;
        r[i].perms = uc->mapped_blocks[i]->perms;
    }

    *regions = r;

    return UC_ERR_OK;
}

Example of use:

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

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

    if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc))) {
        uc_perror("uc_open", err);
        return 1;
    }

    if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))) {
        uc_perror("uc_mem_map", err);
        return 1;
    }

    uc_mem_region *region;
    uint32_t count;

    if ((err = uc_mem_regions(uc, &region, &count))) {
        uc_perror("uc_mem_regions", err);
        return 1;
    }

    cout << "start address: 0x" << hex << region->begin << " end address: 0x" << hex << region->end << " memory permissions: " <<region->perms < < "Number of allocated memory blocks: " << count << endl;

    if ((err = uc_free(region))) { ////Pay attention to freeing memory
        uc_perror("uc_free", err);
        return 1;
    }

    return 0;
}

output

image.png

uc_free

uc_err uc_free(void *mem);

Free the memory allocated by uc_mem_regions()

@mem: memory allocated by uc_mem_regions (returns *regions)

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_free(void *mem)
{
    g_free(mem);
    return UC_ERR_OK;
}

void g_free(gpointer ptr)
{
   free(ptr);
}

Use the same example as uc_mem_regions()

uc_context_alloc

uc_err uc_context_alloc(uc_engine *uc, uc_context **context);

Allocate a region that can be used with uc_context_{save,restore} to perform fast save/rollback of the CPU context, including registers and internal metadata. Contexts cannot be shared between engine instances with different schemas or modes.

@uc: the handle returned by uc_open()
@context: Pointer to uc_engine*. When this function returns successfully, it will be updated with a pointer to the new context. These allocated memory must then be freed using uc_context_free().

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_context_alloc(uc_engine *uc, uc_context **context)
{
    struct uc_context **_context = context;
    size_t size = uc->cpu_context_size;

    *_context = g_malloc(size);
    if (*_context) {
        (*_context)->jmp_env_size = sizeof(*uc->cpu->jmp_env);
        (*_context)->context_size = size - sizeof(uc_context) - (*_context)->jmp_env_size;
        return UC_ERR_OK;
    } else {
        return UC_ERR_NOMEM;
    }
}
```

</details>

Example of use

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

#define ADDRESS 0x1000
#define X86_CODE32_INC "\x40"   // INC eax

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

    int r_eax = 0x1;    // EAX register

    printf("===================================\n");
    printf("Save/restore CPU context in opaque blob\n");

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

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

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

    // initialize register
    uc_reg_write(uc, UC_X86_REG_EAX, &r_eax);

    printf(">>> Running emulation for the first time\n");

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

    printf(">>> Emulation done. Below is the CPU context\n");

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

    // Allocate and save CPU context
    printf(">>> Saving CPU context\n");

    err = uc_context_alloc(uc, &context);
    if (err) {
        printf("Failed on uc_context_alloc() with error returned: %u\n", err);
        return 0;
    }

    err = uc_context_save(uc, context);
    if (err) {
        printf("Failed on uc_context_save() with error returned: %u\n", err);
        return 0;
    }

    printf(">>> Running emulation for the second time\n");

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

    printf(">>> Emulation done. Below is the CPU context\n");

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

    // restore CPU context
    err = uc_context_restore(uc, context);
    if (err) {
        printf("Failed on uc_context_restore() with error returned: %u\n", err);
        return 0;
    }

    printf(">>> CPU context restored. Below is the CPU context\n");

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

    // release the CPU context
    err = uc_context_free(context);
    if (err) {
        printf("Failed on uc_free() with error returned: %u\n", err);
        return;
    }

    uc_close(uc);
}
```

output

![image.png](https://raw.githubusercontent.com/kabeor/Unicorn-Engine-Documentation/master/API_Doc_Pic/1_21.png)



### uc_context_save

```c
uc_err uc_context_save(uc_engine *uc, uc_context *context);
```

Save the current CPU context

```
@uc: the handle returned by uc_open()
@context: the handle returned by uc_context_alloc()

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```

<details><summary> source code implementation </summary>

```c
uc_err uc_context_save(uc_engine *uc, uc_context *context)
{
    struct uc_context *_context = context;
    memcpy(_context->data, uc->cpu->env_ptr, _context->size);
    return UC_ERR_OK;
}
```

</details>

The usage example is the same as [uc_context_alloc()](#uc_context_alloc)


### uc_context_restore

```c
uc_err uc_context_restore(uc_engine *uc, uc_context *context);
```

Restore saved CPU context

```
@uc: the handle returned by uc_open()
@context: handle returned by uc_context_alloc() and saved with uc_context_save

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```

<details><summary> source code implementation </summary>

```c
uc_err uc_context_restore(uc_engine *uc, uc_context *context)
{
    struct uc_context *_context = context;
    memcpy(uc->cpu->env_ptr, _context->data, _context->size);
    return UC_ERR_OK;
}
```

</details>

The usage example is the same as [uc_context_alloc()](#uc_context_alloc)



### uc_context_size

```c
size_t uc_context_size(uc_engine *uc);
```

Returns the size required to store the cpu context. Can be used to allocate a buffer to contain the cpu context and call uc_context_save directly.

```
@uc: uc_open() the returned handle

@return The size required to store the cpu context, of type size_t.
```


<details><summary> source code implementation </summary>

```c
size_t uc_context_size(uc_engine *uc)
{
    return sizeof(uc_context) + uc->cpu_context_size + sizeof(*uc->cpu->jmp_env);
}
```

</details>

The usage example is the same as [uc_context_alloc()](#uc_context_alloc)


### uc_context_free

```c
uc_err uc_context_free(uc_context *context);
```

Free the memory allocated by [uc_context_alloc()](#uc_context_alloc)
```
@context: uc_context created by uc_context_alloc

@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```

The usage example is the same as [uc_context_alloc()](#uc_context_alloc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment