您好,登錄后才能下訂單哦!
這篇文章主要介紹“Unicorn模擬CPU執行JNI_Onload動態注冊的方法”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Unicorn模擬CPU執行JNI_Onload動態注冊的方法”文章能幫助大家解決問題。
<br>步驟:
先實現 javavm 中的 GetEnv, (與模擬 jni 過程類似)一共八個函數
初始化
# 1. 開始映射 mu.mem_map(0, 0x1000) # 初始化映射 參數1:地址 參數2:空間大小 默認初始化后默認值:0 # 1.1 初始化 java vm 中的每一個函數 java_vm_base = 700*4 # 從 700*4 開始 for i in range(0, 10, 1): # 一共8個函數(5個+3個預留) 這里我預留了10個多寫幾個預備,也就是 10*4 mu.mem_write(i*4+java_vm_base, b'\x00\xb5\x00\xbd') # 先隨便填充,保持堆棧平衡 push {lr} pop {pc} # 1.2 初始化填充 JNIInvokeInterface 結構體 for i in range(0, 10, 1): mu.mem_write(i*4+java_vm_base+40, struct.pack("I", i*4+java_vm_base+1)) # 注意第二個參數,要 pack 一下為 bytes, 而且是 thmob 指令集都要+1 # 1.3 初始化 Java vm 指針 javavm_pointer=700*4+80 mu.mem_write(javavm_pointer,struct.pack("I",java_vm_base+40)) # 內容指針,頁就是 JNIInvokeInterface 的第一個位置所以要加 40
**<br>然后添加 Hook 代碼,模擬cpu 執行
注意: 想要直接通過 R2 的地址讀出函數的信息,是有問題的
因為: linker 對加載的時候并不是直接映射的,而是分不通的段進行加載的!!所以位置是不通的。
解決: 要模擬加載 和 重定位
涉及: 依賴庫加載,符號的解析等等工作
便捷解決: AndroidNativeEmu 已經封裝好了 linker, 并且可以模擬 syscall 的執行,還提供了對函數的 hook 功能
代碼如下<br>tool
import unicorn import capstone import struct class Tool: """工具類""" def __init__(self): self.CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB) def capstone_print(self, code, offset, total=20): """ code: 代碼 offset: 偏移位置 total: 最大打印行 """ for i in self.CP.disasm(code[offset:], 0, total): print('\033[1;32m地址: 0x%x | 操作碼: %s | %s\033[0m'%(offset + i.address, i.mnemonic, i.op_str)) def readstring(self, mu,address): """讀字符串""" result='' tmp=mu.mem_read(address,1) while(tmp[0]!=0): result=result+chr(tmp[0]) address=address+1 tmp = mu.mem_read(address, 1) return result def printArm32Regs(self, mu, end=78): """打印寄存器""" for i in range(66, end): print("\033[1;30m【R%d】, value:%x\033[0m"%(i-66,mu.reg_read(i))) print("\033[1;30mSP->value:%x\033[0m" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP))) print("\033[1;30mPC->value:%x\033[0m" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC))) tl = Tool() if __name__ == "__main__": with open("so/testcalljni.so",'rb') as f: CODE=f.read() # tl.capstone_print(CODE, 0x0B58, 10)
**<br>_ _<br>core
import unicorn import struct import capstone from arm_tool import tl def init_java_vm(mu): """初始化 java vm java vm 5+3 個函數 """ # 1. 開始映射 mu.mem_map(0, 0x1000) # 初始化映射 參數1:地址 參數2:空間大小 默認初始化后默認值:0 """注意:要模擬 JNI_OnLoad 同樣也需要先初始化 JNI""" # 0.1 初始化填充 jni 函數 JniFuntionListbase=0x0 for i in range(0, 300): # 接近 300 個jni函數 (指針是 4 個字節) mu.mem_write(i*4+JniFuntionListbase, b'\x00\xb5\x00\xbd') # 先隨便填充,保持堆棧平衡 push {lr} pop {pc} # 0.2 初始化填充 JNINaviteInterface 結構體, 每一項都是,jni函數的地址 # JniNativeInterFace=301 # 前面300個用于指針了,從301個開始 for i in range(300, 600): # 4 個字節都是地址 mu.mem_write(i*4, struct.pack("I", (i-300)*4+1)) # 注意第二個參數,要 pack 一下為 bytes, 而且是 thmob 指令集都要+1 # 0.3 初始化 jnienv 指針 jnienv_pointer = 601*4 mu.mem_write(jnienv_pointer, struct.pack("I", 300*4)) # 內容指針,頁就是 JniNativeInterFace 的第一個 300 """初始化 java vm""" # 1.1 初始化 java vm 中的每一個函數 java_vm_base = 700*4 # 從 700*4 開始 for i in range(0, 10, 1): # 一共8個函數(5個+3個預留) 這里我預留了10個多寫幾個預備,也就是 10*4 mu.mem_write(i*4+java_vm_base, b'\x00\xb5\x00\xbd') # 先隨便填充,保持堆棧平衡 push {lr} pop {pc} # 1.2 初始化填充 JNIInvokeInterface 結構體 for i in range(0, 10, 1): mu.mem_write(i*4+java_vm_base+40, struct.pack("I", i*4+java_vm_base+1)) # 注意第二個參數,要 pack 一下為 bytes, 而且是 thmob 指令集都要+1 # 1.3 初始化 Java vm 指針 javavm_pointer=700*4+80 mu.mem_write(javavm_pointer,struct.pack("I",java_vm_base+40)) # 內容指針,頁就是 JNIInvokeInterface 的第一個位置所以要加 40 # 2. 將代碼片段映射到模擬器的虛擬地址 ADDRESS = 0x1000 # 映射開始地址 SIZE = 1024*1024*10 # 分配映射大小(多分一點) # 3. 開始映射 mu.mem_map(ADDRESS, SIZE) # 初始化映射 參數1:地址 參數2:空間大小 默認初始化后默認值:0 mu.mem_write(ADDRESS, CODE) # 寫入指令 參數1: 寫入位置 參數2:寫入內容 # 4. 寄存器初始化 函數2個參數 (JNI_OnLoad 有兩個參數) mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, javavm_pointer) # 參數 javavm 指針 mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x0) # 0 # 5. 初始化堆棧,因為要對內存進行操作 設置 SP SP = ADDRESS+SIZE-16 # 多減點,預留 sp 剩下兩個參數的位置 mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP,SP) # 6. 添加 hook 代碼 """注意: hook 的時候加上區間可以極大的提升hook效率!!""" mu.hook_add(unicorn.UC_HOOK_CODE, hook_code) # mu.hook_add(unicorn.UC_HOOK_MEM_WRITE, hook_mem) # 跟蹤 cpu 執行內存操作, 需要自寫回調函數 # mu.hook_add(unicorn.UC_HOOK_INTR,hook_syscall) # hook 系統調用函數 # mu.hook_add(unicorn.UC_HOOK_BLOCK,hook_block) # hook 基本塊 # 7. 開始運行 add_satrt = ADDRESS+0xc00+1 # 偏移位置 ida 查看 THUMB 指令集奇數所以要 ADDRESS +1, add_end = ADDRESS+0xC66 # 調用完 registnative 返回即可 try: mu.emu_start(add_satrt, add_end) # 參數1:起始位置,參數2:結束位置 print('-------- unicorn 執行后--------') r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) print('執行結果: ', tl.readstring(mu, r0value)) except unicorn.UcError as e: print('\033[1;31mError: %s \033[0m' % e) def hook_code(mu,address,size,user_data): """定義回調函數, 在進入匯編指令之前就會先運行這里 mu: 模擬器 address: 執行地址 size: 匯編指令大小 user_data: 通過 hook_add 添加的參數 """ code=mu.mem_read(address,size) # 讀取 if address>=700*4 and address<=710*4: index=(address-700*4)/4 print('進入 Javavm 函數: '+str(index)) if index==6: print("調用 javavm->GetEnv---------------:" + str(index)) # jint (*GetEnv)(JavaVM*, void**, jint); 第二個參數才是,返回的值,所以要用 R1 !!!!! """第二個參數才是,返回的值,所以要用 R1 !!!!!""" r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) # 將 ENV 指針 寫入 jni 第一個參數中,即可 mu.mem_write(r1value,struct.pack("I",601*4)) # 也就是我們初始化 jni 的時候的指針地址 CP=capstone.Cs(capstone.CS_ARCH_ARM,capstone.CS_MODE_THUMB) for i in CP.disasm(code,0,len(code)): print("\033[1;32mHook jni | 地址:0x%x | 指令:%s | 內容:%s\033[0m"%(address,i.mnemonic,i.op_str)) tl.printArm32Regs(mu) elif address>=0 and address<=300*4: # 返回屬于我們自己寫的 jni 函數的區域的時候 index=(address-0)/4 # 拿到第幾個 jni 函數 if index==6: # 676/4 6 = FindClass 就可以捕獲到,類的完整類名 print("------[jnienv] FindClass-------") # jclass (*FindClass)(JNIEnv*, const char*); 是第二個參數返回的值 所以是 R1 r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) classname=tl.readstring(mu,r1value) #666 com/example/unicorncourse05/MainActivity print("\033[1;33mjnienv FindClass: %s\033[0m" %classname) mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0,666) # 隨便寫一個值來代表這個引用! elif index == 215: # 第二部,調用 注冊函數 # jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint); print("------[jnienv] RegisterNatives-------") r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0) # 也就是我們前面寫好的 601 * 4 = 2404 r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1) # 也就是上面隨便寫的一個值 r2value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2) # JNINativeMethod 地址 (數組) """ 注意: 想要直接通過 R2 的地址讀出函數的信息,是有問題的 因為: linker 對加載的時候并不是直接映射的,而是分不通的段進行加載的!!所以位置是不通的。 解決: 要模擬加載 和 重定位 涉及: 依賴庫加載,符號的解析等等工作 便捷解決: AndroidNativeEmu 已經封裝好了 linker, 并且可以模擬 syscall 的執行,還提供了對函數的 hook 功能 """ # funcname_bytearray=mu.mem_read(r2value,4) # funcname_addr=struct.unpack("I",funcname_bytearray); # print(tl.readstring(mu,funcname_addr)) r3value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R3) print("\033[1;33menv地址:"+str(r0value)+" | 函數jclass:"+str(r1value)+" | JNINativeMethod[數組]地址:"+str(r2value)+" | 注冊個數:"+str(r3value)+"\033[0m") CP=capstone.Cs(capstone.CS_ARCH_ARM,capstone.CS_MODE_THUMB) for i in CP.disasm(code,0,len(code)): print("\033[1;32mHook jni | 地址:0x%x | 指令:%s | 內容:%s\033[0m"%(address,i.mnemonic,i.op_str)) return def hook_mem(mu, type, address, size, value, user_data): """ 讀和寫內存的 mem hook 回調 """ msg = None print('\033[1;32m=== Hook cpu ===\033[0m') if type==unicorn.UC_MEM_WRITE: msg = """\033[1;32m內存操作 %s 地址: 0x%x | hook_mem 類型: %s| 大小: %s | 值: 0x%x\033[0m"""%('寫入',address,type,size,value) if type==unicorn.UC_MEM_READ: msg = """\033[1;32m內存操作 %s 地址: 0x%x | hook_mem 類型: %s| 大小: %s | 值: 0x%x\033[0m"""%('讀取',address,type,size,value) print(msg) return def hook_syscall(mu,intno,user_data): print("\033[1;36mhook 系統調用 系統調用號: 0x%d"%intno) if intno==2: # 例子 2 是退出 print("系統調用退出!!") # print_result(mu) print("\033[0m") return def hook_block(mu, address, size, user_data): # code = mu.mem_read(address,size) print("\033[1;36mhook 基本塊") # print_result(mu) print("\033[0m") return if __name__ == "__main__": with open("so/unicorn05.so",'rb') as sofile: CODE=sofile.read() mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB) tl.capstone_print(CODE, 0xc00) init_java_vm(mu) # 初始化 java vm
運行效果
關于“Unicorn模擬CPU執行JNI_Onload動態注冊的方法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。