ida模拟 unicorn
ida模拟
看大佬的博客又学到了一点东西,加密后的字符串在ida里无法被读出,那么应该怎么办呢?可以使用unicorn模拟执行脚本。
参考博客:https://xia0ji233.pro/2025/04/14/tencent-race-2025-final/
https://zhuanlan.zhihu.com/p/22633261031
侵删。
unicorn
安装
pip install unicorn
用处
用来模拟代码执行。
脚本
举个例子:
import idaapi
import idc
from unicorn import *
from unicorn.x86_const import *
import ida_name
import mmap
import sys
import idautils
import struct
base_addr = idaapi.get_imagebase()
function_start=0x1234567
function_end=0x12345678
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.emu_start(function_start,function_end)
其中需要修改function_start
和function_end
,这是个开合空间。然后它会执行中间的代码,并且记录一些东西
它还可以读写寄存器:
mu.reg_write(UC_X86_REG_RIP, function_start)
mu.reg_write(UC_X86_REG_R13, 0xFF)
rax=uc.reg_read(UC_X86_REG_RAX)
rcx=uc.reg_read(UC_X86_REG_RCX)
还可以读写内存:
uc.mem_write(0x7f1234567,data)
uc.mem_read(rsp+offset,2)
还可以开辟内存:
mu.mem_map(ADDRESS, 20 * 0x100000)
还可以进行hook,其中每一条hook指令是这样的:
Hook类型 | 说明 | 用例 |
---|---|---|
UC_HOOK_CODE | 拦截每一条指令 | 监控,反调试 |
UC_HOOK_BLOCK | 拦截每个基本块 | 统计基本块执行次数 |
UC_HOOK_INTR | 拦截中断指令 | 监控系统调用 |
UC_HOOK_MEM_READ | 读取内存前触发 | 监控变量读取 |
UC_HOOK_MEM_WRITE | 写入内存前触发 | 监控变量写入 |
UC_HOOK_MEM_FETCH | 取指令前触发 | 捕获未映射的代码执行 |
UC_HOOK_MEM_READ_UNMAPPED | 读取未映射内存 | 捕获非法读取 |
UC_HOOK_MEM_WRITE_UNMAPPED | 写入未映射内存 | 捕获非法写入 |
UC_HOOK_MEM_FETCH_UNMAPPED | 取址未映射内存 | 捕获非法指令执行 |
UC_HOOK_INSN | 拦截特定指令 | 监控syscall等 |
使用是这样的:
# Hook 回调函数
def hook_code(mu, address, size, user_data):
print(f"Executing instruction at 0x{address:X}, size={size}")
mu.hook_add(UC_HOOK_CODE, hook_code)
启动
当然,这个玩意其实只是一个模拟器,所以需要塞入代码,可以这么写:
from unicorn import *
from unicorn.x86_const import *
from elftools.elf.elffile import ELFFile
def load_elf(filename):
with open(filename, 'rb') as f:
elffile = ELFFile(f)
# 找到代码段
for segment in elffile.iter_segments():
if segment['p_type'] == 'PT_LOAD':
# 映射内存并写入代码
address = segment['p_vaddr']
size = segment['p_memsz']
f.seek(segment['p_offset'])
code = f.read(segment['p_filesz'])
# 返回地址和代码
return address, code
return None, None
# 创建 Unicorn 实例
uc = Uc(UC_ARCH_X86, UC_MODE_32)
# 加载 ELF 文件
base_address, code = load_elf('your_program.elf')
if code:
uc.mem_map(base_address, 2 * 1024 * 1024) # 映射内存
uc.mem_write(base_address, code) # 写入代码
# 设置程序计数器
uc.reg_write(UC_X86_REG_EIP, base_address)
# 启动模拟
uc.emu_start(base_address, base_address + len(code))
或者在ida里面可以:
uc.mem_map(base_address, 2 * 1024 * 1024)
data=idaapi.get_bytes(aligned_addr,PAGE_SIZE)
uc.mem_write(aligned_addr,data)
然后执行就可以了。
大佬的脚本里面有一个很神奇的写入方式:
def hook_mem_unmapped(uc, access, address, size, value, user_data):
aligned_addr = address&0xFFFFFFFFFFFFF000
try:
uc.mem_map(aligned_addr, PAGE_SIZE)
data=idaapi.get_bytes(aligned_addr,PAGE_SIZE)
uc.mem_write(aligned_addr,data)
return True # 表示错误已处理,继续执行
except Exception as e:
print(f"[-] 动态映射内存页失败: {e}")
return False
mu.hook_add(UC_HOOK_MEM_FETCH_UNMAPPED, hook_mem_unmapped)
就是没映射这个位置,就跑过去映射它,这样就可以模拟执行了。这样的话,只需要把它原来的rip
寄存器改成想要执行的函数地址,然后因为模拟执行,会没有映射,所以就会当场开始贴代码,进入的函数代码也会帖进去,直到执行到结束位置。