短視頻、直播數據實時採集接口,請查看文檔: TiToDatajava
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。數組
JNI_OnLoad 如圖目標 調用 JNI_OnLoadide
先實現 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
**
而後添加 Hook 代碼,模擬cpu 執行指針
代碼以下
toolcode
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)
**
_ _
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