实战代码之unicorn模拟调用JNI接口函数(详细注释版,配合前面几篇食用更舒服(*^_^*))
import unicorn # 导入 Unicorn 模拟器库,用于 CPU 模拟
import capstone # 导入 Capstone 反汇编库,用于反汇编机器代码
import binascii # 导入 binascii 库,用于处理二进制数据(未在代码中使用)
import struct # 导入 struct 库,用于处理 C 语言风格的结构体(用于打包和解包二进制数据)
# 打印 ARM 32 位寄存器的值
def printArm32Regs(mu):
for i in range(66, 78): # 遍历 R66 到 R77 寄存器
print("R%d,value:%x" % (i - 66, mu.reg_read(i))) # 打印寄存器编号和其值
print("SP->value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP))) # 打印栈指针 (SP) 的值
print("PC->value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC))) # 打印程序计数器 (PC) 的值
# 从指定地址读取以 null 结尾的字符串
def readstring(mu, address):
result = '' # 初始化结果字符串
tmp = mu.mem_read(address, 1) # 从地址读取一个字节
while (tmp[0] != 0): # 当读取的字节不为 0 时
result = result + chr(tmp[0]) # 将字节转换为字符并添加到结果字符串
address = address + 1 # 移动到下一个字节
tmp = mu.mem_read(address, 1) # 继续读取下一个字节
return result # 返回读取的字符串
# 钩子函数:代码执行时调用
def hook_code(mu, address, size, user_data):
code = mu.mem_read(address, size) # 从内存中读取指定地址的代码
if address == 0x1000 + 0x088C: # 检查是否到达特定地址
print("log is called!!!") # 打印日志信息
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) # 读取 R0 寄存器的值
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) # 读取 R1 寄存器的值
r2value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2) # 读取 R2 寄存器的值
r3value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R3) # 读取 R3 寄存器的值
content1 = readstring(mu, r1value) # 从 R1 寄存器地址读取字符串
content2 = readstring(mu, r2value) # 从 R2 寄存器地址读取字符串
content3 = readstring(mu, r3value) # 从 R3 寄存器地址读取字符串
print(str(r0value) + "---" + content1 + "---" + content2 + "---" + content3) # 打印寄存器值和字符串内容
print("log is called!!!") # 再次打印日志信息
if address >= 0 and address <= 300 * 4: # 检查地址是否在特定范围内
index = address / 4 # 计算索引
if index == 169: # 检查索引是否为 169
print("call GetStringUTFChars------------------") # 打印调用信息
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) # 读取 R0 寄存器的值
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) # 读取 R1 寄存器的值
r2value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2) # 读取 R2 寄存器的值
content = readstring(mu, r1value) # 从 R1 寄存器地址读取字符串
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, r1value) # 将 R1 的值写回 R0 寄存器
print(str(r0value) + "---" + content + "---" + str(r2value)) # 打印寄存器值和字符串内容
print("call GetStringUTFChars over------------------") # 打印调用结束信息
if index == 167: # 检查索引是否为 167
print("call NewStringUTF------------------") # 打印调用信息
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) # 读取 R0 寄存器的值
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) # 读取 R1 寄存器的值
content = readstring(mu, r1value) # 从 R1 寄存器地址读取字符串
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, r1value) # 将 R1 的值写回 R0 寄存器
print(str(r0value) + "---" + content) # 打印寄存器值和字符串内容
print("call NewStringUTF over------------------") # 打印调用结束信息
print("call jni interface------------------") # 打印 JNI 接口调用信息
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB) # 创建 Capstone 反汇编器实例
for i in CP.disasm(code, 0, len(code)): # 反汇编读取的代码
print("[addr:%x]:%s %s\n" % (address, i.mnemonic, i.op_str)) # 打印反汇编指令的地址、助记符和操作数
return # 返回
# 钩子函数:内存块执行时调用
def hook_block(mu, address, size, user_data):
print("hook_block-----------------------------") # 打印钩子信息
code = mu.mem_read(address, size) # 从内存中读取指定地址的代码
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB) # 创建 Capstone 反汇编器实例
for i in CP.disasm(code, 0, len(code)): # 反汇编读取的代码
print("[hook_block addr:%x]:%s %s\n" % (address, i.mnemonic, i.op_str)) # 打印反汇编指令的地址、助记符和操作数
printArm32Regs(mu) # 打印当前寄存器状态
print("hook_block-----------------------------") # 打印钩子结束信息
return # 返回
# 钩子函数:系统调用时调用
def hook_syscall(mu, intno, user_data):
print("hook_syscall-----------------------------") # 打印钩子信息
printArm32Regs(mu) # 打印当前寄存器状态
print("hook_syscall-----------------------------") # 打印钩子结束信息
return # 返回
# 钩子函数:内存读写时调用
def hook_mem(mu, type, address, size, value, user_data):
if type == unicorn.UC_MEM_WRITE: # 检查是否为写操作
print("write addr:0x%x,size:%d,value:0x%x" % (address, size, value)) # 打印写入地址、大小和值
if type == unicorn.UC_MEM_READ: # 检查是否为读操作
print("read addr:0x%x,size:%d,value:0x%x" % (address, size, value)) # 打印读取地址、大小和值
print("hook_mem type:%d addr:0x%x,size:%d,value:0x%x" % (type, address, size, value)) # 打印内存操作信息
return # 返回
# 测试函数:模拟 JNI 调用
def testthumb_calljni():
CODE = None # 初始化代码变量
# 读取共享库文件
with open("E:/so/testcalljni.so", 'rb') as sofile:
CODE = sofile.read() # 读取文件内容到 CODE 变量
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB) # 创建 Capstone 反汇编器实例
for i in CP.disasm(CODE[0x0B58:], 0, 20): # 反汇编从偏移 0x0B58 开始的代码
print("[addr:%x]:%s %s\n" % (0x0B58 + i.address, i.mnemonic, i.op_str)) # 打印反汇编指令的地址、助记符和操作数
mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB) # 创建 Unicorn 模拟器实例,设置为 ARM Thumb 模式
# 初始化 JNI 接口
JNIFUNCTIONLISTBASE = 0x0 # JNI 函数列表基地址
JNIFUNCTIONLISTSIZE = 0x1000 # JNI 函数列表大小
JNINATIVEINTERFACE = 301 # JNI 本地接口的大小
mu.mem_map(0, 0x1000) # 映射内存区域
# 初始化 JNI 函数列表
for i in range(0, 300, 1):
mu.mem_write(i * 4 + JNIFUNCTIONLISTBASE, b'\x00\xb5\x00\xbd') # 写入默认的返回指令
# 初始化 JNINativeInterface 结构体
for i in range(300, 600, 1):
mu.mem_write(i * 4, struct.pack("I", (i - 300) * 4 + 1)) # 写入 JNI 接口函数地址
# 初始化 jnienv 指针
jnienv_pointer = 601 * 4 # 计算 jnienv 指针地址
mu.mem_write(jnienv_pointer, struct.pack("I", 300 * 4)) # 写入 JNI 函数列表的基地址
ADDRESS = 0x1000 # 设置代码加载地址
SIZE = 10 * 1024 * 1024 # 设置代码段大小
mu.mem_map(ADDRESS, SIZE) # 映射代码段内存
mu.mem_write(ADDRESS, CODE) # 将代码写入内存
mu.mem_write(ADDRESS + 0x088C, b'\x1E\xFF\x2F\xE1') # 替换特定地址的指令为 NOP
# 设置寄存器初始值
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, jnienv_pointer) # 设置 R0 为 jnienv 指针
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x0) # 设置 R1 为 jobject(空对象)
mu.mem_map(ADDRESS + SIZE + 0x1000, 1024) # 映射额外内存用于字符串
mu.mem_write(ADDRESS + SIZE + 0x1000, b'imtestfromjni') # 写入测试字符串
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2, ADDRESS + SIZE + 0x1000) # 设置 R2 为字符串地址
SP = ADDRESS + SIZE - 16 # 设置栈指针 (SP)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP) # 写入 SP 寄存器
# 添加钩子函数
mu.hook_add(unicorn.UC_HOOK_CODE, hook_code) # 添加代码执行钩子
mu.hook_add(unicorn.UC_HOOK_MEM_WRITE, hook_mem) # 添加内存写入钩子
mu.hook_add(unicorn.UC_HOOK_INTR, hook_syscall) # 添加系统调用钩子
mu.hook_add(unicorn.UC_HOOK_BLOCK, hook_block) # 添加内存块钩子
addstart = ADDRESS + 0x0B58 + 1 # 设置模拟开始地址
addend = ADDRESS + 0x0BA6 # 设置模拟结束地址
try:
mu.emu_start(addstart, addend) # 开始模拟执行
print("emulate over") # 打印模拟结束信息
printArm32Regs(mu) # 打印寄存器状态
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) # 读取 R0 寄存器的返回值
result = readstring(mu, r0value) # 从 R0 地址读取返回字符串
print("return->" + result) # 打印返回的字符串
except unicorn.UcError as e: # 捕获 Unicorn 错误
print(e) # 打印错误信息
# 主程序入口
if __name__ == '__main__':
testthumb_calljni() # 调用测试函数
字节序列 \x00\xb5\x00\xbd 对应的汇编指令是:
CBZ R0, <label> ; 如果 R0 为 0,则跳转
BX LR ; 跳转到链接寄存器中的地址
输出结果看附件叭朋友们,或者自己跑一下。
思路分享:
这段代码的整体思路是通过使用 Unicorn 模拟器来模拟 ARM Thumb 架构下的代码执行,特别是针对 JNI(Java Native Interface)函数调用的场景。代码的核心目标是实现对 ARM 代码的动态分析和调试,允许监控寄存器状态、内存操作和函数调用的细节。
首先,导入了必要的库,包括 Unicorn 和 Capstone,这些库提供了 CPU 模拟和反汇编的功能。接着,定义了一系列函数来处理寄存器的打印、字符串的读取以及钩子函数的实现。钩子函数在特定事件发生时被触发,例如代码执行、内存读写和系统调用,这些函数的设计使得开发者能够实时监控模拟过程中的关键操作。
在模拟过程中,代码首先映射了内存区域,并将目标代码和数据写入模拟的内存中。通过设置寄存器的初始值,代码为模拟执行做好了准备。特别是,R0、R1 和 R2 寄存器被设置为 JNI 调用所需的参数,这些寄存器在 ARM 架构中通常用于传递函数参数和返回值。
当模拟开始时,代码通过 emu_start
方法启动执行,并指定了代码的起始和结束地址。在执行过程中,钩子函数会被调用,打印出当前的寄存器状态、内存读写操作和反汇编指令。这种实时反馈机制使得我们能够深入理解代码的执行流程,识别潜在的问题。
最后,代码通过错误处理机制确保在模拟过程中出现问题时能够捕获并输出错误信息,而不会导致程序崩溃。
逆向之路:路漫漫其修远兮,吾将上下而求索!!!!
原文地址:https://blog.csdn.net/SXXYNHHXX/article/details/143780083
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!