其他
使用unicorn-engine开发模拟器
1
什么是unicorn引擎?
1、准备工作
2
helloworld
mov r0,1 // 将数字1送入寄存器r0mov r1,2 // 将数字2送入寄存器r1add r2,r0,r1 // 将r0+r1的运算结果送入寄存器r2
#include <stdio.h>#include <stdint.h>#include "./unicorn-1.0.2-win32/include/unicorn/unicorn.h" // 指令数据在内存中的地址,你可以放在内存地址范围内的任何一个地方,// 但是每个CPU架构都有一些特殊的内存地址区间是有特殊作用的,// 并且unicorn要求地址必需4k对齐,因此这里我用的是0x8000#define ADDRESS 0x8000 int main() { uc_engine *uc; uint32_t r2; // 汇编代码 指令 // mov r0,1 0xE3A00001 // mov r1,2 0xE3A01002 // add r2,r0,r1 0xE0802001 uint32_t code[] = {0xE3A00001, 0xE3A01002, 0xE0802001}; // 将unicorn初始化为arm架构的arm指令集模式 uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); // 申请一块内存空间,由于unicorn要求地址必需4k对齐,所以这里申请了4K的内存,权限为全部权限(可读、可写、可执行) uc_mem_map(uc, ADDRESS, 1024 * 4, UC_PROT_ALL); // 将指令数据写入到模拟器内存 uc_mem_write(uc, ADDRESS, code, sizeof(code)); // 让模拟器从指定的地址开始运行,到指定的地址停止运行 uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); // 从模拟器中读取r2寄存器的值 uc_reg_read(uc, UC_ARM_REG_R2, &r2); printf("r2 = %d\n", r2); uc_close(uc); return 0;}
gcc -g -Wall -m32 -o main 1.c ./unicorn-1.0.2-win32/unicorn.dll
$ ./main.exer2 = 3
3
大小端字节序
uint32_t code[] = {0xE3A00001, 0xE3A01002, 0xE0802001};
E3 A0 00 01 E3 A0 10 02 E0 80 20 01
01 00 A0 E3 02 10 A0 E3 01 20 80 E0
4
ARM模式和THUMB模式
5
函数
函数的参数小于等于4个,那么按顺序依次放入r0-r3寄存器
void fn(a,b,c,d){},参数a的值放入r0寄存器,参数d的值放入r3寄存器。如果函数有返回值,那么放在r0寄存器。
函数内部如果使用了额外的寄存器,那么在使用前应该备份,使用后应该恢复原本的值。
int add(int a, int b) { return a + b;}
add r0,r0,r1 // 完成a+b的操作并设置返回值bx lr // 返回(跳转到lr寄存器指向的地址)
函数参数传递:
6
位置无关代码
在gcc编译时加入-fpic选项即可生成位置无关代码
0x1000 mov r0,pc // 执行后r0的值并不是当前地址,而是0x10080x1004 mov r1,10x1008 mov r1,2
7
函数案例
int add(int a, int b) { return a + b;}add(add(11,22),33);
地址 指令1 / 0x8000 mov r2,pc // r2得到当前地址+8的值0x8008 | 0x8004 add r3,r2,8 // 由r2的值加上程序固定的偏移量就能得到add函数的相对地址 | 0x8008 add r4,r2,16 // 同理,计算得到一个能绕过add函数的相对地址 \ 0x800C bx r4 // 绕过add函数,直接去到0x8018 2 / 0x8010 add r0,r0,r1 // add 函数 \ 0x8014 bx lr // 返回到调用处的下一条指令 3 / 0x8018 mov r0,11 // 给add函数传参数a | 0x801C mov r1,22 // 给add函数传参数b \ 0x8020 blx r3 // 调用add函数 4 / 0x8024 mov r1,33 // 因为add返回值是通过r0传回的,因此第二次调用时只需传参数b \ 0x8028 blx r3 // 再次调用add函数 5 0x802C mov r0,r0 // 这句相当于什么都没做
#define ADDRESS 0x8000int main() { uc_engine *uc; uint32_t r0; // 地址 汇编代码 指令 // 0x8000 mov r2,pc 0xE1A0200F // 0x8004 add r3,r2,8 0xE2823008 // 0x8008 add r4,r2,16 0xE2824010 // 0x800C bx r4 0xE12FFF14 // 0x8010 add r0,r0,r1 0xE0800001 // 0x8014 bx lr 0xE12FFF1E // 0x8018 mov r0,11 0xE3A0000B // 0x801C mov r1,22 0xE3A01016 // 0x8020 blx r3 0xE12FFF33 // 0x8024 mov r1,33 0xE3A01021 // 0x8028 blx r3 0xE12FFF33 // 0x802C mov r0,r0 0xE1A00000 uint32_t code[] = {0xE1A0200F, 0xE2823008, 0xE2824010, 0xE12FFF14, 0xE0800001, 0xE12FFF1E, 0xE3A0000B, 0xE3A01016, 0xE12FFF33, 0xE3A01021, 0xE12FFF33, 0xE1A00000}; uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); uc_mem_map(uc, ADDRESS, 1024 * 4, UC_PROT_ALL); uc_mem_write(uc, ADDRESS, code, sizeof(code)); uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); uc_reg_read(uc, UC_ARM_REG_R0, &r0); printf("r0 = %d\n", r0); uc_close(uc); return 0;}
8
使用unicorn调用函数
#define ADDRESS 0x8000int32_t add(uc_engine *uc, int32_t a, int32_t b) { uint32_t r0, lr; // 传参数 uc_reg_write(uc, UC_ARM_REG_R0, &a); uc_reg_write(uc, UC_ARM_REG_R1, &b); // 根据函数的调用机制,要求我们必需设置一个返回点,这个返回点正是函数执行完毕的标志 // 由于函数内部会执行到内存中的什么位置我们是不确定的(在我们这个例子中我们当然知道它会执行到哪里) // 并且在uc_emu_start()中也有一个停止点,这个停止点非常强硬,如果pc指针到达这个地址程序就会立刻终止 // 因此这个地址必需是一个目标函数永远不可能执行到的点,而且这个地址又必需是在已映射的内存范围内 // 在我这个例子中add函数永远不可能执行到ADDRESS地址,所以我将停止点设置成了ADDRESS,因此当add函数内部经由bx lr返回后 // pc指针将会到达uc_emu_start()设置的停止点,模拟器才能停止运行,回到我们的代码 lr = ADDRESS; uc_reg_write(uc, UC_ARM_REG_LR, &lr); // 在unicorn 1.0.2之前uc_emu_start()在特殊情况下不会在pc==stopAddr时立即停止 uc_emu_start(uc, 0x8010, lr, 0, 0); uc_reg_read(uc, UC_ARM_REG_R0, &r0); // 获取返回值 return r0;} int main() { uc_engine *uc; uint32_t code[] = {0xE1A0200F, 0xE2823008, 0xE2824010, 0xE12FFF14, 0xE0800001, 0xE12FFF1E, 0xE3A0000B, 0xE3A01016, 0xE12FFF33, 0xE3A01021, 0xE12FFF33, 0xE1A00000}; uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); uc_mem_map(uc, ADDRESS, 1024 * 4, UC_PROT_ALL); uc_mem_write(uc, ADDRESS, code, sizeof(code)); printf("%d\n", add(uc, 24, 37)); // 输出61 printf("%d\n", add(uc, 86, 753)); // 输出839 uc_close(uc); return 0;}
拦截函数调用:
#define ADDRESS 0x8000 void add(uc_engine *uc) { int32_t a, b, ret; uint32_t lr; // 获取参数值 uc_reg_read(uc, UC_ARM_REG_R0, &a); uc_reg_read(uc, UC_ARM_REG_R1, &b); ret = a + b + 1; // 设置返回值 uc_reg_write(uc, UC_ARM_REG_R0, &ret); // 模拟实现bx lr的功能 uc_reg_read(uc, UC_ARM_REG_LR, &lr); uc_reg_write(uc, UC_ARM_REG_PC, &lr);} void hook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { if (0x8010 == (uint32_t)address) { // 当模拟器内执行到add函数地址时,进入我们的add函数进行处理 add(uc); }} int main() { uc_engine *uc; uc_hook hh; uint32_t r0; uint32_t code[] = {0xE1A0200F, 0xE2823008, 0xE2824010, 0xE12FFF14, 0xE0800001, 0xE12FFF1E, 0xE3A0000B, 0xE3A01016, 0xE12FFF33, 0xE3A01021, 0xE12FFF33, 0xE1A00000}; uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc); uc_mem_map(uc, ADDRESS, 1024 * 4, UC_PROT_ALL); uc_mem_write(uc, ADDRESS, code, sizeof(code)); // 这里我在整个代码地址范围内加上单条指令的hook,每次执行这个地址范围内的指令前都会回调我们的hook函数 // 如果你可以很明确的知道在哪个地址范围内需要hook,设置一个准确的地址范围能提升程序的运行效率 uc_hook_add(uc, &hh, UC_HOOK_CODE, hook, NULL, ADDRESS, ADDRESS + sizeof(code)); uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(code), 0, 0); uc_reg_read(uc, UC_ARM_REG_R0, &r0); printf("r0 = %d\n", r0); uc_close(uc); return 0;}
可变参数的函数调用:
栈内存:
空栈-先放数据再调指针
满栈-先调指针再放数据
满递减FD
空递减ED
满递增FA
满递增FA
#define ADDRESS 0x8000#define TOTAL_MEMORY 1024 * 4#define STACK_ADDRESS (ADDRESS+TOTAL_MEMORY) // 栈的开始地址放在代码块之后(可以是任意未使用的内存空间)#define STACK_SIZE 1024 * 4 // 栈内存的大小根据需要进行调整 uc_mem_map(uc, ADDRESS, TOTAL_MEM, UC_PROT_ALL);uc_mem_write(uc, ADDRESS, code, sizeof(code)); // 设置栈uc_mem_map(uc, STACK_ADDRESS, STACK_SIZE, UC_PROT_ALL);uint32_t value = STACK_ADDRESS + STACK_SIZE; // 由于是满递减类型,因此sp初始化为栈空间的最末尾uc_reg_write(uc, UC_ARM_REG_SP, &value); // ... 之后就可以进行函数调用了
9
内存管理(堆内存)
3、unicorn给我们提供了另一个分配内存的方式:
uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr);
#define ADDRESS 0x8000#define TOTAL_MEMORY 1024 * 1024 * 4 uint8_t *mem = malloc(TOTAL_MEMORY); // 模拟器的全部内存uc_mem_map_ptr(uc, ADDRESS, TOTAL_MEMORY, UC_PROT_ALL, mem);
// 模拟器内部地址转换成本地指针void *toPtr(uint32_t addr) { return mem + (addr - ADDRESS);} // 本地指针转换成模拟器内部地址uint32_t toAddr(void *ptr) { return ((uint8_t*)ptr - mem) + ADDRESS;}
typedef struct { uint32 next; uint32 len;} LG_mem_free_t; uint32 LG_mem_min;uint32 LG_mem_top;LG_mem_free_t LG_mem_free;char *LG_mem_base;uint32 LG_mem_len;char *Origin_LG_mem_base;uint32 Origin_LG_mem_len;char *LG_mem_end;uint32 LG_mem_left; #define realLGmemSize(x) (((x) + 7) & (0xfffffff8)) // 初始化内存管理器// baseAddress: 托管的内存的首地址,是一个模拟器内的地址// len: 内存的总长度void initMemoryManager(uint32 baseAddress, uint32 len) { printf("initMemoryManager: baseAddress:0x%X len: 0x%X\n", baseAddress, len); Origin_LG_mem_base = toPtr(baseAddress); Origin_LG_mem_len = len; LG_mem_base = (char *)((uint32)(Origin_LG_mem_base + 3) & (~3)); LG_mem_len = (Origin_LG_mem_len - (LG_mem_base - Origin_LG_mem_base)) & (~3); LG_mem_end = LG_mem_base + LG_mem_len; LG_mem_free.next = 0; LG_mem_free.len = 0; ((LG_mem_free_t *)LG_mem_base)->next = LG_mem_len; ((LG_mem_free_t *)LG_mem_base)->len = LG_mem_len; LG_mem_left = LG_mem_len;#ifdef MEM_DEBUG LG_mem_min = LG_mem_len; LG_mem_top = 0;#endif} void *my_malloc(uint32 len) { LG_mem_free_t *previous, *nextfree, *l; void *ret; len = (uint32)realLGmemSize(len); if (len >= LG_mem_left) { printf("my_malloc no memory\n"); goto err; } if (!len) { printf("my_malloc invalid memory request"); goto err; } if (LG_mem_base + LG_mem_free.next > LG_mem_end) { printf("my_malloc corrupted memory"); goto err; } previous = &LG_mem_free; nextfree = (LG_mem_free_t *)(LG_mem_base + previous->next); while ((char *)nextfree < LG_mem_end) { if (nextfree->len == len) { previous->next = nextfree->next; LG_mem_left -= len;#ifdef MEM_DEBUG if (LG_mem_left < LG_mem_min) LG_mem_min = LG_mem_left; if (LG_mem_top < previous->next) LG_mem_top = previous->next;#endif ret = (void *)nextfree; goto end; } if (nextfree->len > len) { l = (LG_mem_free_t *)((char *)nextfree + len); l->next = nextfree->next; l->len = (uint32)(nextfree->len - len); previous->next += len; LG_mem_left -= len;#ifdef MEM_DEBUG if (LG_mem_left < LG_mem_min) LG_mem_min = LG_mem_left; if (LG_mem_top < previous->next) LG_mem_top = previous->next;#endif ret = (void *)nextfree; goto end; } previous = nextfree; nextfree = (LG_mem_free_t *)(LG_mem_base + nextfree->next); } printf("my_malloc no memory\n");err: return 0;end: return ret;} void my_free(void *p, uint32 len) { LG_mem_free_t *free, *n; len = (uint32)realLGmemSize(len);#ifdef MEM_DEBUG if (!len || !p || (char *)p < LG_mem_base || (char *)p >= LG_mem_end || (char *)p + len > LG_mem_end || (char *)p + len <= LG_mem_base) { printf("my_free invalid\n"); printf("p=%d,l=%d,base=%d,LG_mem_end=%d\n", (int32)p, len, (int32)LG_mem_base, (int32)LG_mem_end); return; }#endif free = &LG_mem_free; n = (LG_mem_free_t *)(LG_mem_base + free->next); while (((char *)n < LG_mem_end) && ((void *)n < p)) { free = n; n = (LG_mem_free_t *)(LG_mem_base + n->next); }#ifdef MEM_DEBUG if (p == (void *)free || p == (void *)n) { printf("my_free:already free\n"); return; }#endif if ((free != &LG_mem_free) && ((char *)free + free->len == p)) { free->len += len; } else { free->next = (uint32)((char *)p - LG_mem_base); free = (LG_mem_free_t *)p; free->next = (uint32)((char *)n - LG_mem_base); free->len = len; } if (((char *)n < LG_mem_end) && ((char *)p + len == (char *)n)) { free->next = n->next; free->len += n->len; } LG_mem_left += len;}
void *my_mallocExt(uint32 len) { uint32 *p; if (len == 0) { return NULL; } p = my_malloc(len + sizeof(uint32)); if (p) { *p = len; return (void *)(p + 1); } return p;} void my_freeExt(void *p) { if (p) { uint32 *t = (uint32 *)p - 1; my_free(t, *t + sizeof(uint32)); }}
uint32_t copyStrToEmu(char *str) { if (!str) return 0; uint32_t len = strlen(str) + 1; void *p = my_mallocExt(len); memcpy(p, str, len); return toAddr(p);} // str 将是一个模拟器内的地址,直接传递给模拟器uint32_t str = copyStrToEmu("test.txt"); // str2 将是一个本地指针,可以直接使用char *str2 = toPtr(str);printf("%s\n", str2);
看雪ID:mrcc
https://bbs.pediy.com/user-home-566136.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!