Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editing an instruction before being called doesn't do anything. #1561

Closed
KitMurdock opened this issue Mar 2, 2022 · 22 comments
Closed

Editing an instruction before being called doesn't do anything. #1561

KitMurdock opened this issue Mar 2, 2022 · 22 comments

Comments

@KitMurdock
Copy link

I'm emulating some ARM code.

My program changes the next instruction in memory to a NOP. (mov r8,r8).
It displays the correct op codes - but it runs the old instruction.

Instruction
Address: 0x08001064 opcodes: c3 71 : strb r3, [r0, #7]
value of r3 is 0x000000ce

Next instruction - (modified to do nothing once the uc_emu_start is called)
Address: 0x08001066 opcode: c0 46 : mov r8, r8
Value of r3 is 0xe00000a0

r3 isn't touched - so it looks like it's using the old code.

I guess my question is this: should it work??

@wtdcode
Copy link
Member

wtdcode commented Mar 2, 2022

You have to clear translation cache via uc_ctl_remove_cache

@KitMurdock
Copy link
Author

Thanks for the quick reply.
I've added the code below after making the memory change - but r3 is still being modified.

#define TB_COUNT (8)
#define TCG_MAX_INSNS (512) // from tcg.h
#define CODE_LEN TB_COUNT *TCG_MAX_INSNS

    for (int i = 0; i < TB_COUNT; i++) 
    {
        uc_ctl_remove_cache(
                uc, 
                code_start_address  + i * TCG_MAX_INSNS, 
                code_start_address  + i * TCG_MAX_INSNS + 1);
    }

@wtdcode
Copy link
Member

wtdcode commented Mar 2, 2022 via email

@KitMurdock KitMurdock reopened this Mar 3, 2022
@KitMurdock
Copy link
Author

#include <unicorn/unicorn.h>
#include <string.h>

#define ARM_CODE  "\x0b\x10\xa0\xe3\x16\x20\xa0\xe3\x01\x20\x82\xe0"
// mov r1, 0xb
// mov r2, 0x16  <-- going to replace this to do nothing
// add r2, r2, r1

#define ADDRESS 0x10000

void remove_cache(uc_engine *uc)
{
#define TB_COUNT (8)
#define TCG_MAX_INSNS (512) // from tcg.h
    printf ("removing cache\n");
    for (int i = 0; i < TB_COUNT; i++)
    {
        uc_ctl_remove_cache( uc,ADDRESS  + i * TCG_MAX_INSNS, ADDRESS  + i * TCG_MAX_INSNS + 1);
    }
}

static void hook_code_change_second_instruction_to_do_nothing(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
#define NEW_ARM_CODE "\x08\x80\xa0\xe1"

    printf("\nChanging instruction at 0x10004 to do nothing\n");
    printf("Was:     mov r2, #22  [16 20 a0 e3]\n");
    printf("Becomes: mov r8, r8   [08 80 a0 e1]\n");

    uc_mem_write(uc,  0x10004 ,NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);
    remove_cache(uc);

}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r2;
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2 = 0x%x\n\n", r2);

    printf("\n>>> Tracing instruction at 0x%" PRIx64
           ", instruction size = 0x%x\n",
           address, size);


    uint8_t* tmp;
    tmp=malloc(sizeof(uint8_t)*size);

    //Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        for (int i=0;i<size;i++)
        {
            printf ("%02x ", tmp[i]);
        }
        printf("\n");
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook trace1, trace2;

    int r2 = 0xbeef; // R2 register

    printf("\n - Emulate ARM code - \n");

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    if (err) {
        printf("Failed on uc_open() with error returned: %u (%s)\n", err,
               uc_strerror(err));
        return;
    }

    // map 2MB memory for this emulation
    uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

    // write machine code to be emulated to memory
    uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);

    // initialize machine registers
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code_change_second_instruction_to_do_nothing, NULL, 0x10000,0x10000);
    // tracing one instruction at ADDRESS with customized callback
    uc_hook_add(uc, &trace1, UC_HOOK_CODE, hook_code, NULL, 1,0);
    // emulate machine code in infinite time (last param = 0), or when
    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    if (err) {
        printf("Failed on uc_emu_start() with error returned: %u\n", err);
    }

    // now print out some registers
    printf("\n>>> Emulation done. Below is the CPU context\n");
    printf(">>> R2 should NOT 0x21 \n");

    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2 = 0x%x\n", r2);

    uc_close(uc);
}



int main(int argc, char **argv, char **envp)
{
    test_arm();

    return 0;
}

@KitMurdock
Copy link
Author

Thanks for offering to look at a minimal reproduction.

Once the emulation starts I'm trying to change an instruction to do nothing!

@KitMurdock
Copy link
Author

@wtdcode do you have any suggestions? I would be very grateful!

@wtdcode
Copy link
Member

wtdcode commented Mar 9, 2022

@wtdcode do you have any suggestions? I would be very grateful!

Sorry at this moment I'm a bit busy. Spare me some time and I would have a look.

@wtdcode
Copy link
Member

wtdcode commented Apr 16, 2022

I see why it doesn't work. The uc_mem_write can't really invalidate the codepage as it writes to the physical address. The only workaround for now is that you change the code before execute OR
you modify the code by executing another code (like allocate a small page, write some shell code to modify the target code).

Another workaround:

  1. hook the address before the instruction you would like to modify
  2. modify the address in the hook
  3. stop emulation in the hook
  4. clear cache on the address of the target instruction (this ensures your modification works)
  5. restart emulation where we just stopped

@KitMurdock
Copy link
Author

I might have found another workaround.

  1. Create a hook at the instruction you want to change
  2. When the hook is called modify the instruction in memory
  3. Set the PC to the same address
  4. Delete the hook you are currently in.

This then seems to go back and execute the new code.

Is this working 'as expected' ?

@KitMurdock
Copy link
Author

I'm also going to try your workaround now too. Stop/clear cache/start the emulation.

@wtdcode
Copy link
Member

wtdcode commented Apr 17, 2022

I might have found another workaround.

  1. Create a hook at the instruction you want to change
  2. When the hook is called modify the instruction in memory
  3. Set the PC to the same address
  4. Delete the hook you are currently in.

This then seems to go back and execute the new code.

Is this working 'as expected' ?

This sounds interesting, it in fact indeed "soft restarts" the emulation. But if you don't clear cache, you probably still execute the same old instruction. Could you post a small sample?

@KitMurdock
Copy link
Author

KitMurdock commented Apr 17, 2022

#include <unicorn/unicorn.h>
#include <string.h>
#include <capstone/capstone.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"


enum do_what_t {e_nothing, e_change_pc} ;
enum do_what_t do_what;
uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

int disass(char *code, uint64_t size, uint64_t address)
{
    csh handle;
    cs_insn *insn;
    size_t count;

    if (cs_open(CS_ARCH_ARM, CS_MODE_ARM, &handle) != CS_ERR_OK)
        return -1;
    count = cs_disasm(handle, code, size, address, 0, &insn);
    if (count > 0)
    {
        size_t j;
        for (j = 0; j < count; j++)
        {
            printf(" 0x%" PRIx64 " ", insn[j].address);
            printf("%s ", insn[j].mnemonic);
            printf("%s\n", insn[j].op_str);
        }

        cs_free(insn, count);
    }
    else
    {
        printf("ERROR: Failed to disassemble given code!\n");
    }
    cs_close(&handle);

    return 0;
}

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
#define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    // changing the instruction here
    uc_mem_write(uc, address, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    if (do_what == e_change_pc)
    {
        uc_reg_write(uc, ARM_REG_PC, &address);
    }
    uc_hook_del(uc, hk_change_instruction);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r2;
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n\n", r2);

    printf(">>> Tracing instruction at 0x%" PRIx64 ", instruction size=0x%x ", address, size);

    uint8_t *tmp;
    tmp = malloc(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
        disass(tmp, size, address);
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    if (err)
    {
        printf("Failed on uc_open() with error returned: %u (%s)\n", err, uc_strerror(err));
        return;
    }

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);
    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);
    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    // now print out some registers
    printf("\n>>> Emulation done. R2 should NOT be 0x12  \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n ----- Do nothing -----\n");
    do_what = e_nothing;
    test_arm();
    printf("\n\n\n\n ----- RESETING PC -----\n");
    do_what = e_change_pc;
    test_arm();
    return 0;
}

@wtdcode
Copy link
Member

wtdcode commented Apr 17, 2022

Okay, I got it.

When you set PC, you are in fact triggering a restart on the current PC. Since the translating block is identified by its start, if the current PC is not the start of the translation block, unicorn will re-translate from the current PC since it's not cached.

The only problem is that, if the instruction you are going to modify is the first instruction of a translation block (in most cases, equal to a basic block), then this workaround won't work.

@KitMurdock
Copy link
Author

I've tried your workaround - but I absolutely cannot get it working. It seems to go around again and try and emulate the code a second time.

  • hook the address before the instruction you would like to modify
  • modify the address in the hook
  • stop emulation in the hook
  • clear cache on the address of the target instruction (this ensures your modification works)
  • restart emulation where we just stopped

@KitMurdock
Copy link
Author

#include <string.h>
#include <capstone/capstone.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3  mov r1, #0xb     
// 0x10004 f0 20 a0 e3  mov r2, #0x6  <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2  add r2,1
// 0x1000c 01 20 82 e0  add r2, r2, r1 
// 0x10010 08 80 a0 e1  mov r8, r8


uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

int disass(char *code, uint64_t size, uint64_t address)
{
    csh handle;
    cs_insn *insn;
    size_t count;

    if (cs_open(CS_ARCH_ARM, CS_MODE_ARM, &handle) != CS_ERR_OK)
        return -1;
    count = cs_disasm(handle, code, size, address, 0, &insn);
    if (count > 0)
    {
        size_t j;
        for (j = 0; j < count; j++)
        {
            printf(" 0x%" PRIx64 " ", insn[j].address);
            printf("%s ", insn[j].mnemonic);
            printf("%s\n", insn[j].op_str);
        }

        cs_free(insn, count);
    }
    else
    {
        printf("ERROR: Failed to disassemble given code!\n");
    }
    cs_close(&handle);

    return 0;
}

void remove_cache(uc_engine *uc)
{
    uc_tb tb;
    uc_err err;
    err = uc_ctl_request_cache(uc, ADDRESS, &tb);
    printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
    UC_ERR_CHECK(err);

    printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
    err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
    UC_ERR_CHECK(err);
}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
    uc_err err;
    #define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    printf ("Writing new memory \n");
    uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    err=uc_hook_del(uc, hk_change_instruction);
    UC_ERR_CHECK(err);

    printf ("Stopping emulation now\n");
    err=uc_emu_stop(uc);
    UC_ERR_CHECK(err);
    
    remove_cache(uc);

    printf ("Starting emulation again. Address range: 0x%x-0x%lx\n", ADDRESS_TO_CHANGE,ADDRESS + sizeof(ARM_CODE) - 1 );
    err = uc_emu_start(uc,ADDRESS_TO_CHANGE, ADDRESS + sizeof(ARM_CODE) - 1,0,0);
    UC_ERR_CHECK(err);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r1, r2;
    uc_reg_read(uc, UC_ARM_REG_R1, &r1);
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R1=0x%x R2=0x%x\n\n", r1, r2);
    printf(">>> Tracing instruction at 0x%" PRIx64, address);

    uint8_t *tmp;
    tmp = alloca(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
        disass(tmp, size, address);
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    UC_ERR_CHECK(err);

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    // initialize machine registers
    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    printf("\n>>> Emulation done.\n");
    printf(">>> R2 should NOT 0x12 \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n\n\n ----- START/STOP emulation -----\n");
    test_arm();
    return 0;
}

@wtdcode
Copy link
Member

wtdcode commented Apr 25, 2022

#include <string.h>
#include <capstone/capstone.h>

#define ARM_CODE "\x0b\x10\xa0\xe3"
"\x06\x20\xa0\xe3"
"\x01\x20\x82\xE2"
"\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3 mov r1, #0xb
// 0x10004 f0 20 a0 e3 mov r2, #0x6 <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2 add r2,1
// 0x1000c 01 20 82 e0 add r2, r2, r1
// 0x10010 08 80 a0 e1 mov r8, r8

uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
if (err)
{
fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
exit(1);
}
return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

int disass(char *code, uint64_t size, uint64_t address)
{
csh handle;
cs_insn *insn;
size_t count;

if (cs_open(CS_ARCH_ARM, CS_MODE_ARM, &handle) != CS_ERR_OK)
    return -1;
count = cs_disasm(handle, code, size, address, 0, &insn);
if (count > 0)
{
    size_t j;
    for (j = 0; j < count; j++)
    {
        printf(" 0x%" PRIx64 " ", insn[j].address);
        printf("%s ", insn[j].mnemonic);
        printf("%s\n", insn[j].op_str);
    }

    cs_free(insn, count);
}
else
{
    printf("ERROR: Failed to disassemble given code!\n");
}
cs_close(&handle);

return 0;

}

void remove_cache(uc_engine *uc)
{
uc_tb tb;
uc_err err;
err = uc_ctl_request_cache(uc, ADDRESS, &tb);
printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
UC_ERR_CHECK(err);

printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
UC_ERR_CHECK(err);

}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
void *user_data)
{
uc_err err;
#define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

printf ("Writing new memory \n");
uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

err=uc_hook_del(uc, hk_change_instruction);
UC_ERR_CHECK(err);

printf ("Stopping emulation now\n");
err=uc_emu_stop(uc);
UC_ERR_CHECK(err);

remove_cache(uc);

printf ("Starting emulation again. Address range: 0x%x-0x%lx\n", ADDRESS_TO_CHANGE,ADDRESS + sizeof(ARM_CODE) - 1 );
err = uc_emu_start(uc,ADDRESS_TO_CHANGE, ADDRESS + sizeof(ARM_CODE) - 1,0,0);
UC_ERR_CHECK(err);

}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
void *user_data)
{
int r1, r2;
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
uc_reg_read(uc, UC_ARM_REG_R2, &r2);
printf(">>> R1=0x%x R2=0x%x\n\n", r1, r2);
printf(">>> Tracing instruction at 0x%" PRIx64, address);

uint8_t *tmp;
tmp = alloca(sizeof(uint8_t) * size);

// Read the line of code (opcode)
if (!uc_mem_read(uc, address, tmp, size))
{
    printf("\t\t\t");
    for (int i = 0; i < size; i++)
    {
        printf("%02x ", tmp[i]);
    }
    disass(tmp, size, address);
}

}

static void test_arm(void)
{
uc_engine *uc;
uc_err err;
uc_hook hk_trace1;

// Initialize emulator in ARM mode
err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
UC_ERR_CHECK(err);

// map 2MB memory for this emulation
err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
UC_ERR_CHECK(err);

// write machine code to be emulated to memory
err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
UC_ERR_CHECK(err);

// initialize machine registers
int r2 = 0xbeef; // R2 register
uc_reg_write(uc, UC_ARM_REG_R2, &r2);

uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

// finishing all the code.
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
UC_ERR_CHECK(err);

printf("\n>>> Emulation done.\n");
printf(">>> R2 should NOT 0x12 \n");
uc_reg_read(uc, UC_ARM_REG_R2, &r2);
printf(">>> R2=0x%x\n", r2);

uc_close(uc);

}

int main(int argc, char **argv, char **envp)
{
printf("\n\n\n ----- START/STOP emulation -----\n");
test_arm();
return 0;
}

I slightly change your sample to make it work.

#include <string.h>
#include <unicorn/unicorn.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3  mov r1, #0xb     
// 0x10004 f0 20 a0 e3  mov r2, #0x6  <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2  add r2,1
// 0x1000c 01 20 82 e0  add r2, r2, r1 
// 0x10010 08 80 a0 e1  mov r8, r8


uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

void remove_cache(uc_engine *uc)
{
    uc_tb tb;
    uc_err err;
    err = uc_ctl_request_cache(uc, ADDRESS, &tb);
    printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
    UC_ERR_CHECK(err);

    printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
    err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
    UC_ERR_CHECK(err);
}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
    uc_err err;
    #define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    printf ("Writing new memory \n");
    uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    err=uc_hook_del(uc, hk_change_instruction);
    UC_ERR_CHECK(err);

    printf ("Stopping emulation now\n");
    err=uc_emu_stop(uc);
    UC_ERR_CHECK(err);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r1, r2;
    uc_reg_read(uc, UC_ARM_REG_R1, &r1);
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R1=0x%x R2=0x%x\n\n", r1, r2);
    printf(">>> Tracing instruction at 0x%" PRIx64, address);

    uint8_t *tmp;
    tmp = alloca(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    UC_ERR_CHECK(err);

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    // initialize machine registers
    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    remove_cache(uc);

    printf ("Starting emulation again. Address range: 0x%x-0x%lx\n", ADDRESS_TO_CHANGE,ADDRESS + sizeof(ARM_CODE) - 1 );
    err = uc_emu_start(uc,ADDRESS_TO_CHANGE, ADDRESS + sizeof(ARM_CODE) - 1,0,0);
    UC_ERR_CHECK(err);

    printf("\n>>> Emulation done.\n");
    printf(">>> R2 should NOT 0x12 \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n\n\n ----- START/STOP emulation -----\n");
    test_arm();
    return 0;
}

@wtdcode
Copy link
Member

wtdcode commented Apr 25, 2022

I think maybe #1580 is the proper way to do this. Let me think a bit how to make life easier...

@wtdcode
Copy link
Member

wtdcode commented Apr 25, 2022

Another workaround

#include <string.h>
#include <unicorn/unicorn.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3  mov r1, #0xb     
// 0x10004 f0 20 a0 e3  mov r2, #0x6  <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2  add r2,1
// 0x1000c 01 20 82 e0  add r2, r2, r1 
// 0x10010 08 80 a0 e1  mov r8, r8


uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

void remove_cache(uc_engine *uc)
{
    uc_tb tb;
    uc_err err;
    err = uc_ctl_request_cache(uc, ADDRESS, &tb);
    printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
    UC_ERR_CHECK(err);

    printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
    err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
    UC_ERR_CHECK(err);
}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
    uc_err err;
    #define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    printf ("Writing new memory \n");
    uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    err=uc_hook_del(uc, hk_change_instruction);
    UC_ERR_CHECK(err);

    
    
    remove_cache(uc);

    printf ("Starting emulation again. Address range: 0x%x-0x%lx\n", ADDRESS_TO_CHANGE,ADDRESS + sizeof(ARM_CODE) - 1 );
    err = uc_emu_start(uc,ADDRESS_TO_CHANGE, ADDRESS + sizeof(ARM_CODE) - 1,0,0);
    UC_ERR_CHECK(err);

    printf ("Stopping emulation now\n");
    err=uc_emu_stop(uc);
    UC_ERR_CHECK(err);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r1, r2;
    uc_reg_read(uc, UC_ARM_REG_R1, &r1);
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R1=0x%x R2=0x%x\n", r1, r2);
    printf(">>> Tracing instruction at 0x%" PRIx64, address);

    uint8_t *tmp;
    tmp = alloca(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
        printf("\n\n");
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    UC_ERR_CHECK(err);

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    // initialize machine registers
    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    printf("\n>>> Emulation done.\n");
    printf(">>> R2 should NOT 0x12 \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n\n\n ----- START/STOP emulation -----\n");
    test_arm();
    return 0;
}

@wtdcode
Copy link
Member

wtdcode commented Apr 25, 2022

And the most promising ones:

#include <string.h>
#include <unicorn/unicorn.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3  mov r1, #0xb     
// 0x10004 f0 20 a0 e3  mov r2, #0x6  <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2  add r2,1
// 0x1000c 01 20 82 e0  add r2, r2, r1 
// 0x10010 08 80 a0 e1  mov r8, r8


uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

void remove_cache(uc_engine *uc)
{
    uc_tb tb;
    uc_err err;
    err = uc_ctl_request_cache(uc, ADDRESS, &tb);
    printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
    UC_ERR_CHECK(err);

    printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
    err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
    UC_ERR_CHECK(err);
}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
    uc_err err;
    #define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    printf ("Writing new memory \n");
    uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    err=uc_hook_del(uc, hk_change_instruction);
    UC_ERR_CHECK(err);

    remove_cache(uc);

    printf ("Starting emulation again. Address range: 0x%x-0x%lx\n", ADDRESS_TO_CHANGE,ADDRESS + sizeof(ARM_CODE) - 1 );
    err = uc_emu_start(uc,address, address + 4,0,0);
    UC_ERR_CHECK(err);

    address += 4;

    err=uc_reg_write(uc, UC_ARM_REG_PC, &address);
    UC_ERR_CHECK(err);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r1, r2;
    uc_reg_read(uc, UC_ARM_REG_R1, &r1);
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R1=0x%x R2=0x%x\n", r1, r2);
    printf(">>> Tracing instruction at 0x%" PRIx64, address);

    uint8_t *tmp;
    tmp = alloca(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
        printf("\n\n");
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    UC_ERR_CHECK(err);

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    // initialize machine registers
    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    printf("\n>>> Emulation done.\n");
    printf(">>> R2 should NOT 0x12 \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n\n\n ----- START/STOP emulation -----\n");
    test_arm();
    return 0;
}

Like your previous one:

#include <string.h>
#include <unicorn/unicorn.h>

#define ARM_CODE "\x0b\x10\xa0\xe3" \
                 "\x06\x20\xa0\xe3" \
                 "\x01\x20\x82\xE2" \
                 "\x01\x20\x82\xe0\x08\x80\xa0\xe1"

// int r2=0xbeef;
// 0x10000 0b 10 a0 e3  mov r1, #0xb     
// 0x10004 f0 20 a0 e3  mov r2, #0x6  <- going to replace this with : mov r2, #0xf0
// 0x10008 01 20 82 E2  add r2,1
// 0x1000c 01 20 82 e0  add r2, r2, r1 
// 0x10010 08 80 a0 e1  mov r8, r8


uc_hook hk_change_instruction;

#define ADDRESS 0x1000
#define ADDRESS_TO_CHANGE 0x1004

static uc_err _uc_err_check(uc_err err, const char *expr)
{
    if (err)
    {
        fprintf(stderr, "Failed on %s with error: %s\n", expr, uc_strerror(err));
        exit(1);
    }
    return err;
}
#define UC_ERR_CHECK(x) _uc_err_check(x, #x)

void remove_cache(uc_engine *uc)
{
    uc_tb tb;
    uc_err err;
    err = uc_ctl_request_cache(uc, ADDRESS, &tb);
    printf(">>> TB is cached at 0x%" PRIx64 " which has %" PRIu16" instructions with %" PRIu16 " bytes.\n",tb.pc, tb.icount, tb.size);
    UC_ERR_CHECK(err);

    printf(">>> removing cache from 0x%" PRIx64 " to 0x%" PRIx64 ".\n",tb.pc, tb.pc+ tb.size);
    err = uc_ctl_remove_cache(uc, tb.pc, tb.pc + tb.size);
    UC_ERR_CHECK(err);
}

static void hook_code_change_instruction(uc_engine *uc, uint64_t address, uint32_t size,
                                                       void *user_data)
{
    uc_err err;
    #define NEW_ARM_CODE "\xf0\x20\xA0\xE3"

    printf("\nChanging instruction at 0x%x.\n", ADDRESS_TO_CHANGE);
    printf("Was:     mov r2, #0x06  [06 20 a0 e3]\n");
    printf("Becomes: mov r2, #0xf0  [f0 20 a0 e3]\n");

    printf ("Writing new memory \n");
    uc_mem_write(uc, ADDRESS_TO_CHANGE, NEW_ARM_CODE, sizeof(NEW_ARM_CODE) - 1);

    err=uc_hook_del(uc, hk_change_instruction);
    UC_ERR_CHECK(err);

    remove_cache(uc);

    err=uc_reg_write(uc, UC_ARM_REG_PC, &address);
    UC_ERR_CHECK(err);
}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size,
                      void *user_data)
{
    int r1, r2;
    uc_reg_read(uc, UC_ARM_REG_R1, &r1);
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R1=0x%x R2=0x%x\n", r1, r2);
    printf(">>> Tracing instruction at 0x%" PRIx64, address);

    uint8_t *tmp;
    tmp = alloca(sizeof(uint8_t) * size);

    // Read the line of code (opcode)
    if (!uc_mem_read(uc, address, tmp, size))
    {
        printf("\t\t\t");
        for (int i = 0; i < size; i++)
        {
            printf("%02x ", tmp[i]);
        }
        printf("\n\n");
    }
}

static void test_arm(void)
{
    uc_engine *uc;
    uc_err err;
    uc_hook hk_trace1;

    // Initialize emulator in ARM mode
    err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
    UC_ERR_CHECK(err);

    // map 2MB memory for this emulation
    err = uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
    UC_ERR_CHECK(err);

    // write machine code to be emulated to memory
    err = uc_mem_write(uc, ADDRESS, ARM_CODE, sizeof(ARM_CODE) - 1);
    UC_ERR_CHECK(err);

    // initialize machine registers
    int r2 = 0xbeef; // R2 register
    uc_reg_write(uc, UC_ARM_REG_R2, &r2);

    uc_hook_add(uc, &hk_trace1, UC_HOOK_CODE, hook_code, NULL, 1, 0);  // to display and disassemble the code
    uc_hook_add(uc, &hk_change_instruction, UC_HOOK_CODE, hook_code_change_instruction, NULL, ADDRESS_TO_CHANGE, ADDRESS_TO_CHANGE);

    // finishing all the code.
    err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(ARM_CODE) - 1, 0, 0);
    UC_ERR_CHECK(err);

    printf("\n>>> Emulation done.\n");
    printf(">>> R2 should NOT 0x12 \n");
    uc_reg_read(uc, UC_ARM_REG_R2, &r2);
    printf(">>> R2=0x%x\n", r2);

    uc_close(uc);
}

int main(int argc, char **argv, char **envp)
{
    printf("\n\n\n ----- START/STOP emulation -----\n");
    test_arm();
    return 0;
}

@wtdcode
Copy link
Member

wtdcode commented Apr 26, 2022

In a word, you have a few workarounds:

  1. As I firstly post, stop emulation in hook, quit hook, return from uc_emu_start, clear cache, call uc_emu_start again. (hard to program)
  2. Clear cache, call uc_emu_start in your modification hook (without quitting the hook!), stop emulation. (not ideal since you probably emulate twice...)
  3. Clear cache, emulate exactly the modified instruction, skip the instruction, and resume emulation. (seems good but a bit complex and error-prone)
  4. Clear cache, set PC to the modified address, resume the emulation. ( a bit not intuitive )

Personally, I think 3 & 4 look clean enough and just documenting it somewhere should be fine for this issue. If you think it's okay, we can close this issue.

@KitMurdock
Copy link
Author

That's fantastic. I've got it working now. I've ended up using number 1. It looks cleaner and easier to follow in my final code.
Thanks so much for the effort that you've put into this.

@wtdcode
Copy link
Member

wtdcode commented May 18, 2022

I document this behavior here. Closed.

@wtdcode wtdcode closed this as completed May 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants