乐趣区

关于大数据:抖音数据采集教程跨平台模拟执行AndroidNativeEmu手册

装置

AndroidNativeEmu 有什么用?

AndroidNativeEmu 是基于 Unicron 实现的一个指令解析器, 让您可能跨平台模仿 Android Native 库函数,例如 JNI_OnLoad,Java_XXX_XX 等函数

个性

  • 模仿 JNI Invocation API so JNI_OnLoad can be called properly.
  • 模仿 memory、malloc、memcpy
  • 反对拦挡零碎调用(SVC #0)
  • 通过符号 Hook
  • 所有 JavaVM, JNIEnv 和 hooked functions 都能够用 python 来解决
  • 反对 VFP
  • 反对文件系统(也就是说你能够模仿 maps、status 等文件)

我的项目地址

装置过程

环境要求: python 3.7 (留神必须是 3.7 版本, 我应用 3.6 装 keystone 的时候踩了坑 )
 
自测零碎环境: win7
 
1.Clone 该我的项目

git clone https://github.com/AeonLucid/AndroidNativeEmu.git

2. 装置须要的反对模块

pip install -r requirements.txt

装置 keystone-engine 可能会失败(反正我是没装上)

 
解决方案:

  1. 克隆 keystone 仓库: git clone https://github.com/keystone-engine/keystone.git
  2. 关上 keystone\bindings 文件夹装置: python setup.py install
  3. 下载对应零碎和版本 dll(因为我是 win), 下载链接: http://www.keystone-engine.org/download/
  4. 把 dll 复制到 python 的 keystone 目录下: [python_path]\Lib\site-packages\keystone\

3. 把 androidemu 文件夹复制至 sample 文件夹下, 并删除 example.py 文件下的对于 ”samples/” 的目录拜访门路

如
"samples/example_binaries/libc.so"
改为
"example_binaries/libc.so"

4. 运行例子

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))

本人写个小 Demo 测试

Demo 代码

新建一个 jni 工程, demo 的代码很简略, 就是一个加法

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);
}

emu 代码

正文写的很具体, 具体看代码吧

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

RuntimeError: Unhandled syscall x (x) at 解决

这个谬误是因为没有实现对应 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: 绑定的自定义函数

执行后果

实战一款风控 SO

实战指标

以下信息通过剖析所得, 具体分析过程不是本文重点, 这里不赘述;

指标文件:  libtest.so
指标函数:  a(char* buf, int buf_len)
返回值: return_value > 0, 示意危险环境并且会在 buf 参数里写入具体危险环境信息;
        return_value == 0, 示意失常环境

EMU 代码

详情看正文, 写的很具体

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

免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。

退出移动版