AndroidNativeEmu是基於Unicron實現的一個指令解析器, 讓您可以跨平臺模擬Android Native庫函數,例如JNI_OnLoad,Java_XXX_XX等函數html
JNI_OnLoad
can be called properly.項目地址java
環境要求: python 3.7 (注意必須是3.7版本, 我使用3.6裝keystone的時候踩了坑)
自測系統環境: win7
1.Clone 該項目python
git clone https://github.com/AeonLucid/AndroidNativeEmu.git
2.安裝須要的支持模塊android
pip install -r requirements.txt
安裝keystone-engine可能會失敗(反正我是沒裝上)c++
解決方案:git
- 克隆keystone倉庫: git clone https://github.com/keystone-engine/keystone.git
- 打開keystone\bindings文件夾安裝: python setup.py install
- 下載對應系統和版本dll(由於我是win), 下載連接: http://www.keystone-engine.org/download/
- 把dll複製到python的keystone目錄下: [python_path]\Lib\site-packages\keystone\
3.把androidemu
文件夾複製至sample
文件夾下,並刪除example.py
文件下的關於"samples/"的目錄訪問路徑github
如 "samples/example_binaries/libc.so" 改成 "example_binaries/libc.so"
4.運行例子oracle
python example.py
5.不出意外的話就能夠看到結果了
框架
example_binaries/ : 裏面是須要加載的so vfs/ : 裏面是虛擬的文件系統, 有須要能夠本身添加文件 androidemu/ : android虛擬機
import logging import sys from unicorn import UC_HOOK_CODE from unicorn.arm_const import * from androidemu.emulator import Emulator # 配置日誌相關設置 logging.basicConfig( stream=sys.stdout, #標準輸出流 level=logging.DEBUG, #輸出等級 format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" #輸出格式 ) logger = logging.getLogger(__name__) #實例化對象 # 實例化虛擬機 emulator = Emulator() #加載Libc庫 emulator.load_library("example_binaries/libc.so", do_init=False) #加載要模擬器的庫 lib_module = emulator.load_library("example_binaries/libnative-lib.so") #打印已經加載的模塊 logger.info("Loaded modules:") for module in emulator.modules: logger.info("[0x%x] %s" % (module.base, module.filename)) #trace 每步執行的指令, 方便調試, 其實也能夠取消 def hook_code(mu, address, size, user_data): instruction = mu.mem_read(address, size) instruction_str = ''.join('{:02x} '.format(x) for x in instruction) print('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str)) emulator.mu.hook_add(UC_HOOK_CODE, hook_code) #經過導出符號來調用函數 emulator.call_symbol(lib_module, '_Z4testv') #經過R0來獲取調用結構 print("String length is: %i" % emulator.mu.reg_read(UC_ARM_REG_R0))
新建一個jni工程, demo的代碼很簡單, 就是一個加法socket
JNIEXPORT int nativeAdd(int a, int b) { return a + b; } extern "C" JNIEXPORT jint JNICALL Java_com_mario_testunicorn_MainActivity_myAdd( JNIEnv* env, jobject /*this*/, int a, int b){ return nativeAdd(a,b); }
註釋寫的很詳細, 具體看代碼吧
import logging import posixpath import sys from unicorn import UcError, UC_HOOK_CODE, UC_HOOK_MEM_UNMAPPED from unicorn.arm_const import * from androidemu.emulator import Emulator import debug_utils # 配置日誌 logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ) logger = logging.getLogger(__name__) # 初始化模擬器 emulator = Emulator( vfp_inst_set=True, vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") ) # 加載依賴的動態庫 emulator.load_library("example_binaries/libdl.so") emulator.load_library("example_binaries/libc.so", do_init=False) emulator.load_library("example_binaries/libstdc++.so") emulator.load_library("example_binaries/libm.so") lib_module = emulator.load_library("example_binaries/libmytest.so") # 當前已經load的so logger.info("Loaded modules:") for module in emulator.modules: logger.info("=> 0x%08x - %s" % (module.base, module.filename)) try: # 運行jni onload 這裏沒有, 但不影響執行 emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) #直接調用符號1, 計算1+2 emulator.call_symbol(lib_module, '_Z9nativeAddii', 1, 2) print("_Z9nativeAddii result call: %i" % emulator.mu.reg_read(UC_ARM_REG_R0)) #直接調用符號2, 計算1000 + 1000 emulator.call_symbol(lib_module, 'Java_com_mario_testunicorn_MainActivity_myAdd', 0, 0, 1000, 1000) print("myAdd result call: %i" % emulator.mu.reg_read(UC_ARM_REG_R0)) #執行完成, 退出虛擬機 logger.info("Exited EMU.") logger.info("Native methods registered to MainActivity:") except UcError as e: print("Exit at %x" % emulator.mu.reg_read(UC_ARM_REG_PC)) raise
這個錯誤是由於沒有實現對應syscall致使的, 缺乏什麼函數, 本身寫一個函數綁定一下, 返回給他須要的值就能夠了, 好比getpid, 那麼本身寫的函數隨便返回一個整形就能夠了
在syscall_hooks.py文件裏, 能夠看到做者已經實現的函數
self._syscall_handler.set_handler(0x4E, "gettimeofday", 2, self._handle_gettimeofday) self._syscall_handler.set_handler(0xAC, "prctl", 5, self._handle_prctl) self._syscall_handler.set_handler(0xF0, "futex", 6, self._handle_futex) self._syscall_handler.set_handler(0x107, "clock_gettime", 2, self._handle_clock_gettime) self._syscall_handler.set_handler(0x119, "socket", 3, self._socket) self._syscall_handler.set_handler(0x11b, "connect", 3, self._connect) self._syscall_handler.set_handler(0x159, "getcpu", 3, self._getcpu) self._syscall_handler.set_handler(0x14e, "faccessat", 4, self._faccessat) self._syscall_handler.set_handler(0x14, "getpid", 0, self._getpid) self._syscall_handler.set_handler(0xe0, "gettid", 0, self._gettid) self._syscall_handler.set_handler(0x180,"null1",0, self._null)
set_handler函數參數: arg1: 中斷號(intno),中斷號能夠在ndk中的unistd.h中找到 arg2: 函數名 arg3: 參數數量 arg4: 綁定的自定義函數
如下信息經過分析所得, 具體分析過程不是本文重點, 這裏不贅述;
目標文件: libtest.so 目標函數: a(char* buf, int buf_len) 返回值: return_value > 0, 表示風險環境而且會在buf參數裏寫入詳細風險環境信息; return_value == 0, 表示正常環境
詳情看註釋, 寫的很詳細
import logging import posixpath import sys from unicorn import UcError, UC_HOOK_CODE, UC_HOOK_MEM_UNMAPPED from unicorn.arm_const import * from androidemu.emulator import Emulator from androidemu.java.java_class_def import JavaClassDef from androidemu.java.java_method_def import java_method_def # Create java class. import debug_utils # 配置日誌 logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ) logger = logging.getLogger(__name__) # 初始化模擬器 emulator = Emulator( vfp_inst_set=True, vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") ) # 加載依賴的動態庫 emulator.load_library("example_binaries/libdl.so") emulator.load_library("example_binaries/libc.so", do_init=False) emulator.load_library("example_binaries/libstdc++.so") emulator.load_library("example_binaries/liblog.so") emulator.load_library("example_binaries/libm.so") #目標so lib_module = emulator.load_library("example_binaries/libtest.so") # 當前已經load的so logger.info("Loaded modules:") for module in emulator.modules: logger.info("=> 0x%08x - %s" % (module.base, module.filename)) try: # 運行jni onload 這裏沒有, 但不影響執行 emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) # 增長properties, 該so或經過獲取一些properties來判斷環境 emulator.system_properties['ro.build.fingerprint'] = 'google/passion/passion:2.3.3/GRI40/102588:user/release-keys' emulator.system_properties['ro.product.cpu.abi'] = 'arm' emulator.system_properties['microvirt.vbox_dpi'] = '' #申請一塊buff, 用做參數 emulator.call_symbol(lib_module, 'malloc', 0x1000) address = emulator.mu.reg_read(UC_ARM_REG_R0) #在以前申請的buff讀取內存 detect_str = memory_helpers.read_utf8(emulator.mu, address) print("detect_str: " + detect_str) #執行完成, 退出虛擬機 logger.info("Exited EMU.") logger.info("Native methods registered to MainActivity:") except UcError as e: print("Exit at %x" % emulator.mu.reg_read(UC_ARM_REG_PC)) raise
執行結果:
能夠看見, 函數已經調用成功, 而且已經成功獲取返回值和參數, 不過檢測出風險環境了(由於個人vfs文件都是從虛擬機裏拷貝出來的), 接下來就能夠分析檢測點了!~~
1.經過執行日誌分析, 發現頻繁訪問了build.prop, maps等系統環境, 猜想多是經過這些文件來判斷的, 這裏列出個別幾個
2019-09-21 16:08:27,677 INFO androidemu.vfs.file_system | Reading 1024 bytes from '/proc/cpuinfo' 2019-09-21 16:08:27,680 DEBUG androidemu.cpu.syscall_handlers | Executing syscall read(00000005, 02089000, 00000400) at 0xcbc1ba7c 2019-09-21 16:08:27,783 INFO androidemu.vfs.file_system | Reading 1024 bytes from '/proc/self/maps' 2019-09-21 16:08:27,784 DEBUG androidemu.cpu.syscall_handlers | Executing syscall close(00000008) at 0xcbc1a854 2019-09-21 16:08:27,886 INFO androidemu.vfs.file_system | File opened '/proc/self/status' 2019-09-21 16:08:27,887 DEBUG androidemu.cpu.syscall_handlers | Executing syscall fstat64(0000000a, 000ff3e8) at 0xcbc1b314
2.經過反覆測試, 修改對應文件中的關鍵信息, 最終成功躲過該風控模塊的環境檢測
以下:
該項目是經過Unicron來實現的, Unicorn 是一款很是優秀的跨平臺模擬執行框架, 經過上帝視角來調試和調用二進制代碼, 幾乎能夠很清晰發現反調試和檢測手段, 而Unicorn的應用毫不僅僅只是個虛擬機, 能夠實現不少騷操做, 再次感謝QEMU, Unicron, AndroidNativeEmu等等這些開源大神, 是這些人的分享精神推動了整個圈子的技術迭代。
更多短視頻數據實時採集接口,請查看文檔: TiToData
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。