Basis of AndroidNativeEmu

安装使用

环境要求: python 3.7 (注意必须是3.7版本, 使用3.6装keystone的时候踩了坑)

git clone https://github.com/AeonLucid/AndroidNativeEmu.git
pip install -r requirements.txt

安装keystone-engine可能会失败

解决方案

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

然后把sample文件夹里面的example.py拷贝到上一层目录

python example.py

运行结果

λ python example.py
2019-10-01 13:26:19,404   DEBUG        androidemu.internal.modules | Loading module 'samples/example_binaries/libc.so'.
2019-10-01 13:26:19,409   DEBUG         androidemu.internal.memory | => Mapping memory page 0xcbbcb000 - 0xcbc57000, size 0x0008c000, prot 5
2019-10-01 13:26:19,410   DEBUG         androidemu.internal.memory | => Mapping memory page 0xcbc58000 - 0xcbc66000, size 0x0000e000, prot 3
2019-10-01 13:26:19,417   ERROR        androidemu.internal.modules | => Undefined external symbol: dlerror
2019-10-01 13:26:19,417   ERROR        androidemu.internal.modules | => Undefined external symbol: dlclose
2019-10-01 13:26:19,418   ERROR        androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2019-10-01 13:26:19,418   ERROR        androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2019-10-01 13:26:20,023   ERROR        androidemu.internal.modules | => Undefined external symbol: dlerror
2019-10-01 13:26:20,023   ERROR        androidemu.internal.modules | => Undefined external symbol: dlclose
2019-10-01 13:26:20,026   ERROR        androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2019-10-01 13:26:20,027   ERROR        androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2019-10-01 13:26:20,326   DEBUG        androidemu.internal.modules | Loading module 'samples/example_binaries/libnative-lib.so'.
2019-10-01 13:26:20,330   DEBUG         androidemu.internal.memory | => Mapping memory page 0xcbc66000 - 0xcbc69000, size 0x00003000, prot 5
2019-10-01 13:26:20,330   DEBUG         androidemu.internal.memory | => Mapping memory page 0xcbc69000 - 0xcbc6b000, size 0x00002000, prot 3
2019-10-01 13:26:20,340    INFO                           __main__ | Loaded modules:
2019-10-01 13:26:20,340    INFO                           __main__ | [0xcbbcb000] samples/example_binaries/libc.so
2019-10-01 13:26:20,341    INFO                           __main__ | [0xcbc66000] samples/example_binaries/libnative-lib.so
# Tracing instruction at 0xcbc667c4, instruction size = 0x2, instruction = 80 b5
# Tracing instruction at 0xcbc667c6, instruction size = 0x2, instruction = 6f 46
# Tracing instruction at 0xcbc667c8, instruction size = 0x2, instruction = 82 b0
# Tracing instruction at 0xcbc667ca, instruction size = 0x2, instruction = 04 48
# Tracing instruction at 0xcbc667cc, instruction size = 0x2, instruction = 78 44
...
# Tracing instruction at 0xcbbe567a, instruction size = 0x2, instruction = 80 1e
# Tracing instruction at 0xcbbe567c, instruction size = 0x2, instruction = 70 47
# Tracing instruction at 0xcbc667d6, instruction size = 0x2, instruction = 02 b0
# Tracing instruction at 0xcbc667d8, instruction size = 0x2, instruction = 80 bd
String length is: 26

示例文件分析

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

目录及用法介绍

androidemu目录下

  • utils/memory_helpers.py为内存读写的工具,包括read_ptr,read_utf8,write_utf8,write_uints,hex_dump等函数
  • pu/syscall_hooks.py包括了各种系统调用的实现(HOOK)
 def __init__(self, mu, syscall_handler):
        self._mu = mu
        self._syscall_handler = syscall_handler
        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)
        self._clock_start = time.time()
        self._clock_offset = randint(1000, 2000)

我们也可以添加自己的系统调用进去,系统调用号可以在unistd.h查到。

自写Demo测试

demo工程

JNIEXPORT int nativeAdd(int a, int b) {
    return a + b;
}

extern "C"
JNIEXPORT int wrap_getpid() {
    return getpid();
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_happy_emusotest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    int c = nativeAdd(2, 3) + wrap_getpid();
    c++;
    return env->NewStringUTF(hello.c_str());
}

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: 绑定的自定义函数

运行结果

_binaries/libdl.so
2019-10-01 14:48:58,798    INFO                           __main__ | => 0xcbbd1000 - example_binaries/libc.so
2019-10-01 14:48:58,799    INFO                           __main__ | => 0xcbc6c000 - example_binaries/libstdc++.so
2019-10-01 14:48:58,800    INFO                           __main__ | => 0xcbc72000 - example_binaries/libm.so
2019-10-01 14:48:58,800    INFO                           __main__ | => 0xcbc95000 - example_binaries/test/libnative-lib.so
2019-10-01 14:48:58,800   ERROR                androidemu.emulator | Unable to find symbol 'JNI_OnLoad' in module 'example_binaries/test/libnative-lib.so'.
_Z9nativeAddii result call: 3
2019-10-01 14:48:58,801   DEBUG    androidemu.cpu.syscall_handlers | Executing syscall getpid() at 0xcbc1ab14
my wrap_getpid: 4386
2019-10-01 14:48:58,802    INFO                           __main__ | Exited EMU.
2019-10-01 14:48:58,802    INFO                           __main__ | Native methods registered to MainActivity:

测试分控so

这里的emu处理了字符串相关的函数hook
目标:

目标文件:  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

高级用法

hook 类中函数

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.
from samples import debug_utils


class MainActivity(metaclass=JavaClassDef, jvm_name='local/myapp/testnativeapp/MainActivity'):

    def __init__(self):
        pass

    @java_method_def(name='stringFromJNI', signature='()Ljava/lang/String;', native=True)
    def string_from_jni(self, mu):
        pass

    def test(self):
        pass


# Configure logging
logging.basicConfig(
    stream=sys.stdout,
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)

logger = logging.getLogger(__name__)

# Initialize emulator
emulator = Emulator(
    vfp_inst_set=True,
    vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs")
)

# Register Java class.
emulator.java_classloader.add_class(MainActivity)

# Load all libraries.
emulator.load_library("example_binaries/libdl.so")
emulator.load_library("example_binaries/libc.so")
emulator.load_library("example_binaries/libstdc++.so")
emulator.load_library("example_binaries/libm.so")
lib_module = emulator.load_library("example_binaries/libnative-lib_jni.so")

# Show loaded modules.
logger.info("Loaded modules:")

for module in emulator.modules:
    logger.info("=> 0x%08x - %s" % (module.base, module.filename))

# Debug
# emulator.mu.hook_add(UC_HOOK_CODE, debug_utils.hook_code)
# emulator.mu.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped)
# emulator.mu.hook_add(UC_HOOK_MEM_WRITE, debug_utils.hook_mem_write)
# emulator.mu.hook_add(UC_HOOK_MEM_READ, debug_utils.hook_mem_read)

try:
    # Run JNI_OnLoad.
    #   JNI_OnLoad will call 'RegisterNatives'.
    emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00)
    emulator.mu.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped)

    # Do native stuff.
    emulator.mu.hook_add(UC_HOOK_CODE, debug_utils.hook_code)
    main_activity = MainActivity()
    logger.info("Response from JNI call: %s" % main_activity.string_from_jni(emulator))

    # Dump natives found.
    logger.info("Exited EMU.")
    logger.info("Native methods registered to MainActivity:")

    for method in MainActivity.jvm_methods.values():
        if method.native:
            logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature))
except UcError as e:
    print("Exit at %x" % emulator.mu.reg_read(UC_ARM_REG_PC))
    raise

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容