首页
社区
课程
招聘
[原创]AndroidNativeEmu使用笔记
发表于: 2022-9-26 15:14 7199

[原创]AndroidNativeEmu使用笔记

2022-9-26 15:14
7199

目录

前言

鉴于AndroidNativeEmu是一个强大的native层模拟工具同时官方文档并不完整,故在此记录一下一些使用技巧。因为笔者本身也没有完全掌握,故本篇文章将持续更新且有不少疏漏,欢迎大家评论区补充指正。
本篇文章基于maiyao1998作者改进的AndroidNativeEmu进行解析,如有疑问可翻阅源码。

Native层

Emulator初始化

1
def __init__(self, vfs_root="vfs", config_path="emu_cfg/default.json", vfp_inst_set=True, arch=emu_const.ARCH_ARM32, muti_task=False)

vfs_root:虚拟文件系统,用于进行文件读写,一般使用项目自带的vfs文件夹
config_path:加载包名等配置,样例如下

1
2
3
4
5
6
7
8
9
{
    "__pkg_name":"com.netease.cloudmusic",
    "pkg_name":"com.ss.android.ugc.aweme",
    "pid": 4386,
    "uid": 10023,
    "android_id": "39cc04a2ae83db0b",
    "ip":"192.168.31.52",
    "mac":[204, 250, 166, 0, 138, 169]
}

对于纯粹的so库模拟执行而言,暂时没发现具体作用
arch:so库架构,有arm32和arm64

Emulator相关方法

load_library
1
def load_library(self, filename, do_init=True)

输入so库路径,以及是否初始化(调用.init_array函数列表),返回lib_module,用于函数调用

call_symbol

根据函数名调用函数

1
def call_symbol(self, module, symbol_name, *argv)

module为前文load_library的返回值,symbol_name为symbol表中存在的函数名,后面跟不定长函数参数,样例

1
emulator.call_symbol(lib_module, 'Java_com_ctf_mobile_MainActivity_check',emulator.java_vm.jni_env.address_ptr, 0x00, String('flag{0c27a573-a9aa-42c4-8a3e-c79179ff4f8a}'))

后文将详细讲解参数传入

call_native

根据函数地址调用函数

1
def __call_native32(self, addr, *argv)

call_symbol思路一致,但不需要输入lib_module

Emulator相关属性

mu

模拟执行的核心,一个unicorn模拟器。要想对指令hook调试、读取寄存器、patch内存等操作,可以直接调用mu(多线程可能不太一样?)

1
2
3
4
emulator = Emulator(
    vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs")
)
emulator.mu.hook_add(UC_HOOK_CODE, hook_code, emulator)
java_classloader

java类载入器,这是AndroidNativeEmu最强大的特性之一,即将写好的python类转化为java类,方便native层直接调用,或许可以叫做PNI,使用示例

1
2
3
4
class MainActivity(metaclass=JavaClassDef, jvm_name='com/crackme/bbbbutton/MainActivity'):
    def __init__(self):
        pass
emulator.java_classloader.add_class(MainActivity)
java_vm

JNIOnload函数的第一个参数,同时也包含JNIEnv指针,当函数中没有调用GetEnv时,获取JNIEnv可用emulator.java_vm.jni_env.address_ptr来表示JNIEnv*类型的参数,反之则直接传入emulator.java_vm.address_ptr(比如在JNI_Onload函数中)

native函数传参方法

参数类型统一定义在androidemu.java.classes中,除了基础类型以外作者还添加了许多其他类型如list、Hashmap等,此处浅述一下各个基础类型

1
2
3
4
5
6
7
Boolean(value:bool)
Integer(value:int32)
Long(value:int64)
Float(value:float)
String(value:str)
ByteArray(value:bytearray)
...

举例

1
2
String('flag{0c27a573-a9aa-42c4-8a3e-c79179ff4f8a}')
ByteArray(bytearray(b'\x03\x01\x00\x02\x02\x01\x02\x01\x02\x01\x03\x00\x03\x01\x00\x02'[::-1]))

Java层

类定义

使用作者提供的一些修饰器即可将java类用python语法定义出来

1
def java_method_def(name, signature, native=False, args_list=None, modifier=None, ignore=False)
  • name为java类中的方法名,主要是方便native层识别
  • signature为Java method signature,语法链接,不在此过多赘述
  • native表示是否是由JNIEnv注册的方法
  • args_list表示参数类型,有如下几种:
    "jint", "jchar", "jbyte", "jboolean", "jlong", "jdouble", "jstring", "jobject"
    需要注意的是,数组类型如ByteArray同样应被定义为jobject
  • modifier暂未使用过
  • ignore暂未使用过
 

若为静态方法,则可以使用staticmethod修饰器@staticmethod,然后python方法同样修改为静态(没有self参数)

 

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class MainActivity(metaclass=JavaClassDef, jvm_name='com/crackme/bbbbutton/MainActivity'):
 
    @java_method_def(name='<init>', signature='()V')
    def __init__(self, *args, **kwargs):
        self.flagRight = String("flag输入正确")
        self.flagWrong = String("flag输入错误")
 
    @staticmethod
    @java_method_def(name='getBytes', signature='([B)[B', args_list=['jobject'])
    def getBytes(mu, *args, **kwargs):
        bArr = args[0]
        print(bArr, type(bArr))
        bArr2 = ByteArray(bytearray(b'\0'*(len(bArr)//4)))
        i = 0
        for i2 in range(len(bArr)//4):
            i3 = i2 * 4
            bArr2[i] = ((bArr[i3] << 6) + (bArr[i3 + 1] << 4) +
                        (bArr[i3 + 2] << 2) + bArr[i3 + 3]) & 0xff
            i += 1
        return bArr2
 
    @java_method_def(name='add', signature='(II)I', args_list=['jint', 'jint'])
    def add(self, mu, *args, **kwargs):
        print(len(args))
        return args[1] ^ args[0]
 
    @java_method_def(name='sub', signature='(II)I', args_list=['jint', 'jint'])
    def sub(self, mu, *args, **kwargs):
        return (args[1] + args[0]) & 0xff
 
    @java_method_def(name='xor', signature='(II)I', args_list=['jint', 'jint'])
    def xor(self, mu, *args, **kwargs):
        return (args[0] - args[1]) & 0xff
 
    def test(self):
        pass

构造函数不存在mu参数

JNI

JNI_Onload

声明类方法时,可以将参数native置True以让JNI_OnLoad函数动态注册(或静态调用),使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
 
#   JNI_OnLoad will call 'RegisterNatives'.
emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00)
 
# Do native stuff.
main_activity = MainActivity()
logger.info("Response from JNI call: %s" % main_activity.string_from_jni(emulator))

...

许多作者暂未实现的JNI方法我都还没弄清楚原理,欢迎大家补充


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 1993
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
谢谢分享
2022-9-26 15:20
0
雪    币: 12
活跃值: (390)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
不如unidbg
2022-9-26 16:38
0
游客
登录 | 注册 方可回帖
返回
//