Unidbg补环境实战

Unidbg

Unidbg的作用是模拟执行so中的函数,处于Native层.而Java层的函数可以通过JNI调用Native层函数,那么Native层也可以通过JNI去调用Java层的函数.

在Native层调用Java层函数的时候,因为Unidbg中没有这些函数的实现,模拟执行必然失败.故我们需要在Unidbg中补充这些Java层函数,让Native层的函数去调用.

补环境入门

测试Apk

DogPro.apk

dogproJava代码

模拟执行

使用Unidbg模拟执行so中的getHash函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.getHash();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogPro.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("dogpro", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void getHash() {
        DvmObject<?> dvmObject = vm.resolveClass("com/example/dogpro/MainActivity").newObject(null);
        String input = "unidbg-android/src/test/java/com/example/luodst/files/dogpro.apk";
        DvmObject<?> ret = dvmObject.callJniMethodObject(emulator, "getHash(Ljava/lang/String;)Ljava/lang/String;", input);
        System.out.println("result ==> "+ret.getValue());
    }

}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/util/zip/ZipFile-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:803)
	at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:758)
	at com.github.unidbg.linux.android.dvm.DvmMethod.newObjectV(DvmMethod.java:214)
	at com.github.unidbg.linux.android.dvm.DalvikVM$26.handle(DalvikVM.java:415)

这是一个构造方法,传入的参数类型为String,可以用下述代码去进行对象构造.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "java/util/zip/ZipFile-><init>(Ljava/lang/String;)V":{
            String name = (String) vaList.getObjectArg(0).getValue();
            try {
                ZipFile zipFile = new ZipFile(name);
                return vm.resolveClass("java/util/zip/ZipFile").newObject(zipFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    return super.newObjectV(vm, dvmClass, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/util/zip/ZipFile->entries()Ljava/util/Enumeration;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
	at com.github.unidbg.linux.android.dvm.DalvikVM$32.handle(DalvikVM.java:553)

上述代码提示,缺少了ZipFile的entries方法,修补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/util/zip/ZipFile->entries()Ljava/util/Enumeration;": {
            //拿操作的对象
            ZipFile zipFile = (ZipFile) dvmObject.getValue();
            //通过对象来调用方法
            Enumeration<? extends ZipEntry> entries =  zipFile.entries();
            return vm.resolveClass("java/util/Enumeration").newObject(entries);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.ClassCastException: class com.github.unidbg.linux.android.dvm.DvmObject cannot be cast to class com.github.unidbg.linux.android.dvm.Enumeration (com.github.unidbg.linux.android.dvm.DvmObject and com.github.unidbg.linux.android.dvm.Enumeration are in unnamed module of loader 'app')
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:610)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)
	at com.github.unidbg.linux.android.dvm.DalvikVM$35.handle(DalvikVM.java:630)

上述提示xxx.DvmObject cannot be cast to xxx.Enumeration,即类型转换失败.出现这种问题,首先在Unidbg中搜索,看有无相关处理逻辑.通过搜索,可知在Unidbg中处理逻辑如下:

Enumeration处理逻辑

进而可查看Enumeration在Unidbg中的实现如下:

Enumeration实现逻辑

Enumeration在Java中同样有实现,Unidbg对Java中的基本数据类型做了封装,并优先使用它们.

比如要返回一个String对象,写法如下:

1
return new StringObject(vm, "");

而不是使用resolveClass的方式:

1
return vm.resolveClass("java/lang/String").newObject("");

如果Unidbg封装了基本数据类型,那就要使用它们.查看Enumeration在Unidbg中的构造方法如下:

1
2
3
4
public Enumeration(VM vm, List<? extends DvmObject<?>> value) {
    super(vm.resolveClass("java/util/Enumeration"), value);
    this.iterator = value == null ? null : value.iterator();
}

可以看到需要传入一个List类型的对象,故最终代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/util/zip/ZipFile->entries()Ljava/util/Enumeration;": {
            ZipFile zipFile = (ZipFile) dvmObject.getValue();
            Enumeration<? extends ZipEntry> entries =  zipFile.entries();
            //return vm.resolveClass("java/util/Enumeration").newObject(entries);
            List<DvmObject<?>> objs = new ArrayList<>();
            while (entries.hasMoreElements()){
                ZipEntry zipEntry = entries.nextElement();
                objs.add(vm.resolveClass("java/util/zip/ZipEntry").newObject(zipEntry));
            }
            return new com.github.unidbg.linux.android.dvm.Enumeration(vm, objs);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/util/zip/ZipEntry->getName()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:95)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

同样是在调用对象的方法,我们需要先获取对象,再用对象调用对应的方法.故补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/util/zip/ZipEntry->getName()Ljava/lang/String;":{
            ZipEntry zipEntry = (ZipEntry) dvmObject.getValue();
            String name = zipEntry.getName();
            return new StringObject(vm, name);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/lang/String->endsWith(Ljava/lang/String;)Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)
	at com.github.unidbg.linux.android.dvm.DalvikVM$35.handle(DalvikVM.java:630)

同理,我们需要先拿到对象,而且endsWith中有参数传递,我们也需要把参数取出来.故补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/lang/String->endsWith(Ljava/lang/String;)Z":{
            String strObj = (String) dvmObject.getValue();
            String suffix = (String) vaList.getObjectArg(0).getValue();
            return strObj.endsWith(suffix);
        }
    }
    return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:100)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

同理,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
     switch (signature) {
         case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;":{
             ZipFile zipFile = (ZipFile) dvmObject.getValue();
             ZipEntry zipEntry = (ZipEntry) vaList.getObjectArg(0).getValue();
             try {
                 InputStream inputStream = zipFile.getInputStream(zipEntry);
                 return vm.resolveClass("java/io/InputStream").newObject(inputStream);
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
         }
     }
     return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/InputStream->read([B)I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethodV(DvmMethod.java:109)
	at com.github.unidbg.linux.android.dvm.DalvikVM$47.handle(DalvikVM.java:827)

需要补的是InputStream中的read方法,根据参数和返回值,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/InputStream->read([B)I":{
            InputStream inputStream = (InputStream) dvmObject.getValue();
            byte[] byteArray = (byte[]) vaList.getObjectArg(0).getValue();
            try {
                return inputStream.read(byteArray);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/security/MessageDigest->update([B)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodV(DvmMethod.java:229)
	at com.github.unidbg.linux.android.dvm.DalvikVM$59.handle(DalvikVM.java:1051)

这里调用了Java SDK中的MessageDigest类中的update方法,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature){
        case "java/security/MessageDigest->update([B)V":{
            MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
            byte[] byteArray = (byte[]) vaList.getObjectArg(0).getValue();
            messageDigest.update(byteArray);
            return;
        }
    }
    super.callVoidMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/security/MessageDigest->digest()[B
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:112)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

还缺少MessageDigest类中的digest方法,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/security/MessageDigest->digest()[B":{
            MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
            byte[] digest = messageDigest.digest();
            return new ByteArray(vm, digest);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

再次运行代码,成功输出如下:

1
result ==> D3E550889725A6A7C5E834ECCDB4B73E

JNI_OnLoad

测试Apk

BossLast.apk

模拟执行

使用Unidbg模拟调用so中的JNI_OnLoad函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/boss_last.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("yzwg", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }
}

运行上述代码,报错如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[main]D/YZWG: JNI_OnLoad called
Exception in thread "main" java.lang.IllegalStateException: Illegal JNI version: 0xffffffff
	at com.github.unidbg.linux.android.dvm.BaseVM.checkVersion(BaseVM.java:228)
	at com.github.unidbg.linux.android.dvm.DalvikModule.callJNI_OnLoad(DalvikModule.java:39)
	at com.example.luodst.MainActivity.<init>(MainActivity.java:49)
	at com.example.luodst.MainActivity.main(MainActivity.java:17)
[14:37:26 484]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:540) - handleInterrupt intno=2, NR=0, svcNumber=0x18d, PC=unidbg@0xfffe0964, LR=RX@0x4000a87b[libyzwg.so]0xa87b, syscall=null
java.lang.UnsupportedOperationException: com/twl/signer/YZWG->gContext:Landroid/content/Context;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)
	at com.github.unidbg.linux.android.dvm.DalvikVM$142.handle(DalvikVM.java:2276)

报错内容中有Illegal JNI version,是因为JNi_OnLoad调用失败,没有返回JNI版本信息.故Unidbg报非法的JNI版本.

报错内容中还包含Context,Context是维持Android程序中各个组件正常工作的核心功能类.

Unidbg运行在Java环境下,Context是Android中的类,我们不能像MessageDigest(Java SDK提供的工具类)那样去补这类环境,但是我们可以遵循一个补环境的准测:新建一个这种类,将类的对象置空.

与此同时,我们需要知道这里的报错是在做什么事情,以及它所构造参数的数据类型是什么,以便下次再遇到时,能给对象一个合理的值.

这里补的Context代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature) {
        case "com/twl/signer/YZWG->gContext:Landroid/content/Context;":{
            return vm.resolveClass("android/content/Context").newObject(null);
        }
    }
    return super.getStaticObjectField(vm, dvmClass, signature);
}

我们将setVerbose的参数置为true,运行代码,来看下JNI的执行流.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//查找类
JNIEnv->FindClass(com/twl/signer/YZWG) was called from RX@0x4000a561[libyzwg.so]0xa561
//获取类中的Context字段
JNIEnv->GetStaticFieldID(com/twl/signer/YZWG.gContextLandroid/content/Context;) => 0x200e062f was called from RX@0x4000a577[libyzwg.so]0xa577
//获取类中的Context对象
JNIEnv->GetStaticObjectField(class com/twl/signer/YZWG, gContext Landroid/content/Context; => android.content.Context@14ec4505) was called from RX@0x4000a87b[libyzwg.so]0xa87b
//获取Context中的getPackageManager方法ID
JNIEnv->GetMethodID(android/content/Context.getPackageManager()Landroid/content/pm/PackageManager;) => 0x3acc78f0 was called from RX@0x40027d63[libyzwg.so]0x27d63
//调用getPackageManager方法
JNIEnv->CallObjectMethod(android.content.Context@14ec4505, getPackageManager() => android.content.pm.PackageManager@4b168fa9) was called from RX@0x40027d71[libyzwg.so]0x27d71
//调用getPackagesForUid方法
JNIEnv->GetMethodID(android/content/pm/PackageManager.getPackagesForUid(I)[Ljava/lang/String;) => 0x3fc7d353 was called from RX@0x40027d93[libyzwg.so]0x27d93
[17:20:46 663]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:540) - handleInterrupt intno=2, NR=-130448, svcNumber=0x11e, PC=unidbg@0xfffe0274, LR=RX@0x40027da5[libyzwg.so]0x27da5, syscall=null
java.lang.UnsupportedOperationException: android/content/pm/PackageManager->getPackagesForUid(I)[Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:933)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:867)
    at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:69)
	at com.github.unidbg.linux.android.dvm.DalvikVM$31.handle(DalvikVM.java:527)

从JNI的执行流可以我们,前面获取的Context只是为了能够调用getPackageManager方法,进而调用getPackagesForUid方法.执行到了getPackagesForUid这里就报错.可以通过下述网址来看下这个方法是做什么的.

https://developer.android.com/reference/

通过查阅资料可知它是通过UID获取包名,故补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    switch (signature) {
        case "android/content/pm/PackageManager->getPackagesForUid(I)[Ljava/lang/String;":{
            return new ArrayObject(new StringObject(vm, vm.getPackageName()));
        }
    }
    return super.callObjectMethod(vm, dvmObject, signature, varArg);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/lang/String->hashCode()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethod(AbstractJni.java:965)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethod(AbstractJni.java:938)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethod(DvmMethod.java:129)
	at com.github.unidbg.linux.android.dvm.DalvikVM$46.handle(DalvikVM.java:801)

根据异常信息,补充的环境代码如下:

1
2
3
4
5
6
7
8
9
public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    switch (signature) {
        case "java/lang/String->hashCode()I":{
            String strObj = (String) dvmObject.getValue();
            return strObj.hashCode();
        }
    }
    return super.callIntMethod(vm, dvmObject, signature, varArg);
}

再次运行代码,成功输出如下:

1
2
[main]D/YZWG: JNI_OnLoad called
[main]D/YZWG: register method, method count:5

文件标识

测试Apk

DogLite.apk

dogliteJava代码

detectFile

使用Unidbg模拟执行so中的detectFile函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.detectFile();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogLite.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("doglite", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void detectFile() {
        DvmObject obj = vm.resolveClass("com/example/doglite/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "detectFile()V");
    }
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/File-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:803)
	at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:758)
	at com.github.unidbg.linux.android.dvm.DvmMethod.newObjectV(DvmMethod.java:214)
	at com.github.unidbg.linux.android.dvm.DalvikVM$26.handle(DalvikVM.java:415)

按惯例,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/File-><init>(Ljava/lang/String;)V":{
            String path = (String) vaList.getObjectArg(0).getValue();
            //System.out.println("path: " +path);
            File file = new File(path);
            return vm.resolveClass("java/io/File").newObject(file);
        }
    }
    return super.newObjectV(vm, dvmClass, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/File->exists()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)
	at com.github.unidbg.linux.android.dvm.DalvikVM$35.handle(DalvikVM.java:630)

按惯例,补代码如下:

detectFileExists方法补充1

从上述路径来看,主要是检测电池的电量和模拟器.按照一般逻辑,电池的电量需要被检测,模拟器不能被检测.故我们需要根据不同文件标识做不同的处理,修正代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature , VaList vaList) {
     switch (signature) {
         case "java/io/File->exists()Z":{
             File file = (File) dvmObject.getValue();
             String path = file.getPath();
          /*   System.out.println("path: " +path);
             return file.exists();*/
             switch (path){
                 case "\\sys\\class\\power_supply\\battery\\voltage_now":
                     return true;
                 case "\\data\\local\\tmp\\Nox":
                     return false;
             }
         }
     }
     return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
}

detectFileNew

使用Unidbg模拟执行so中的detectFileNew函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.detectFileNew();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogLite.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("doglite", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void detectFileNew() {
        DvmObject obj = vm.resolveClass("com/example/doglite/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "detectFileNew()V");
    }
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/File->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)
	at com.github.unidbg.linux.android.dvm.DalvikVM$24.handle(DalvikVM.java:366)
	at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:133)

从上述报错来看是调用了allocObject函数,这里把newObject拿来做对比,两者的共同点都是用于构建新的类对象.两者区别如下:

allocObject:只构建新的类对象(仅仅为类对象分配内存空间),既不初始化成员变量,也不调用构造方法.

newObject:需要指明调用的构造方法,构建新的类对象,并初始化成员变量,调用指定的构造方法.

也就是说我们新模拟执行的这个方法再一次调用了File类,只不过初始化操作需要我们自己来做.补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature) {
        case "java/io/File->allocObject": {
            return vm.resolveClass("java/io/File").newObject(null);
        }
    }
    return super.allocObject(vm, dvmClass, signature);
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/File-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodV(DvmMethod.java:229)
	at com.github.unidbg.linux.android.dvm.DalvikVM$59.handle(DalvikVM.java:1051)

再一次调用了init方法,既然在allocObject中创建了对象,那么我们可以通过一些技术手段来区分这些对象的创建.allocObject的修正代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private static int nCount = 0;
private int getnCount() {
    return ++nCount;
}

public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature) {
        case "java/io/File->allocObject": {
            return vm.resolveClass("java/io/File").newObject(getnCount());
        }
    }
    return super.allocObject(vm, dvmClass, signature);
}

上述我们使用了一个int类型的数值来标识不同的对象.接下来,补File的构造函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/File-><init>(Ljava/lang/String;)V": {
            String key = dvmObject.getValue().toString();
            String path = (String) vaList.getObjectArg(0).getValue();
            emulator.set(key, path);
            return;
        }
    }
    super.callVoidMethodV(vm, dvmObject, signature, vaList);
}

上述我们在模拟器上给它绑定了对象和要传入的参数.

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: java/io/File->exists()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)
	at com.github.unidbg.linux.android.dvm.DalvikVM$35.handle(DalvikVM.java:630)

同理补环境代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature){
        case "java/io/File->exists()Z":{
            String key = dvmObject.getValue().toString();
            String path = emulator.get(key);
            //System.out.println("path: " +path);
            switch (path) {
                case "/sys/class/power_supply/battery/voltage_now":
                    return true;
                case "/data/local/tmp/nox":
                    return false;
            }
        }
    }
    return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
}

再次运行代码,成功输出如下:

1
2
[main]E/引导: 可以访问电池信息
[main]E/引导: 未检测到模拟器文件

设备风控

测试Apk

DogLite.apk

SysInfo

使用Unidbg模拟执行so中的SysInfo函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

import java.io.File;
import java.util.Base64;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.SysInfo();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogLite.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("doglite", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

   private void SysInfo() {
        DvmObject obj = vm.resolveClass("com/example/doglite/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "SysInfo()V");
    }
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/app/ActivityThread->getApplication()Landroid/app/Application;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
	at com.github.unidbg.linux.android.dvm.DalvikVM$32.handle(DalvikVM.java:553)

对于Android中的类,可以先置空.故补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/app/ActivityThread->getApplication()Landroid/app/Application;":{
            return vm.resolveClass("android/app/Application").newObject(null);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

androidID获取失败

从上述代码可以看到是在获取android_id,我们可以根据返回值类型构造一个合理的数值.故补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;":{
            return new StringObject(vm, "Xia Luo Hun !");
        }
    }
    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

再次运行代码,成功输出如下:

1
[main]E/引导: android id:Xia Luo Hun !

getAppFilesDir

使用Unidbg模拟执行so中的getAppFilesDir函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

import java.io.File;
import java.util.Base64;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.getAppFilesDir();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogLite.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("doglite", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

   private void getAppFilesDir() {
        DvmObject obj = vm.resolveClass("com/example/doglite/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "getAppFilesDir()V");
    }
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/os/Environment->getExternalStorageDirectory()Ljava/io/File;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:59)
	at com.github.unidbg.linux.android.dvm.DalvikVM$113.handle(DalvikVM.java:1816)

按惯例,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
            return vm.resolveClass("java/io/File").newObject(null);
        }
    }
    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.NullPointerException: Cannot invoke "java.io.File.getAbsolutePath()" because "file" is null
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:307)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
	at com.github.unidbg.linux.android.dvm.DalvikVM$32.handle(DalvikVM.java:553)

这是因为上一步中给的File类创建的对象为空,故不能通过File实例对象来调用getAbsolutePath方法.我们需要重写的内容如下:

1
2
3
4
5
6
7
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/File->getAbsolutePath()Ljava/lang/String;":{
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

这里调用getAbsolutePath方法,我们依旧使用标识的方式来确定当前使用的是哪个对象.最终代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
            return vm.resolveClass("java/io/File").newObject(signature);
        }
    }
    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/File->getAbsolutePath()Ljava/lang/String;":{
            String tag = dvmObject.getValue().toString();
            if (tag.equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
                return new StringObject(vm, "/sdcard");
            }
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

上述在创建对象的时候使用signature作为标识,在callObjectMethodV方法中取出tag.根据tag的内容来区分对象.

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/os/Environment->getStorageDirectory()Ljava/io/File;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
	at com.example.luodst.MainActivity.callStaticObjectMethodV(MainActivity.java:66)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:59)

同理,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":
        case "android/os/Environment->getStorageDirectory()Ljava/io/File;":{
            return vm.resolveClass("java/io/File").newObject(signature);
        }
    }
    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "java/io/File->getAbsolutePath()Ljava/lang/String;":{
            String tag = dvmObject.getValue().toString();
            if (tag.equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
                return new StringObject(vm, "/sdcard");
            }
            if (tag.equals("android/os/Environment->getStorageDirectory()Ljava/io/File;")){
                return new StringObject(vm, "/");
            }
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

再次运行代码,成功输出如下:

1
2
[main]E/引导: /sdcard
[main]E/引导: /

补环境加强

下面以一个无障碍服务为切入点,介绍如何补这种类型的Android系统函数.同时,也强调了补环境的一个基本准测:先将流程跑通,再处理值的问题.

测试Apk

DogPlus.apk

dogPlus

模拟执行

使用Unidbg模拟执行so中的detectAccessibilityManager函数,初始化代码如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.detectAccessibilityManager();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogPlus.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("dogplus", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

   private void detectAccessibilityManager() {
        DvmObject obj = vm.resolveClass("com/example/dogplus/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "detectAccessibilityManager()V");
    }
}

运行上述代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/app/ActivityThread->getApplication()Landroid/app/Application;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
	at com.github.unidbg.linux.android.dvm.DalvikVM$32.handle(DalvikVM.java:553)

按惯例,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/app/ActivityThread->getApplication()Landroid/app/Application;":{
            return vm.resolveClass("android/app/Application").newObject(null);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/view/accessibility/AccessibilityManager->getInstalledAccessibilityServiceList()Ljava/util/List;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:64)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

通过下述网址,查询getInstalledAccessibilityServiceList方法是返回已安装辅助功能服务的AccessibilityServiceInfos

https://developer.android.com/reference

这里先不用管值的正确性,补代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/view/accessibility/AccessibilityManager->getInstalledAccessibilityServiceList()Ljava/util/List;":{
            List<DvmObject<?>> list = new ArrayList<>();
            DvmClass dvmClass = vm.resolveClass("android/accessibilityservice/AccessibilityServiceInfo");
            list.add(dvmClass.newObject(null));
            list.add(dvmClass.newObject(null));
            list.add(dvmClass.newObject(null));
            return new ArrayListObject(vm, list);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/accessibilityservice/AccessibilityServiceInfo->getResolveInfo()Landroid/content/pm/ResolveInfo;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:74)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

按惯例,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/accessibilityservice/AccessibilityServiceInfo->getResolveInfo()Landroid/content/pm/ResolveInfo;":{
            return vm.resolveClass("android/content/pm/ResolveInfo").newObject(null);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/content/pm/ResolveInfo->serviceInfo:Landroid/content/pm/ServiceInfo;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
	at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
	at com.github.unidbg.linux.android.dvm.DalvikVM$92.handle(DalvikVM.java:1409)

按惯例,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature){
    switch (signature){
        case "android/content/pm/ResolveInfo->serviceInfo:Landroid/content/pm/ServiceInfo;":{
            return vm.resolveClass("android/content/pm/ServiceInfo").newObject(null);
        }
    }
    return super.getObjectField(vm, dvmObject, signature);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/content/pm/ServiceInfo->name:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.example.luodst.MainActivity.getObjectField(MainActivity.java:86)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
	at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)

这里我们不知道name是什么值,随意给值即可.补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature){
    switch (signature){
        case "android/content/pm/ServiceInfo->name:Ljava/lang/String;":{
            return new StringObject(vm, "XiaLuoHun");
        }
    }
    return super.getObjectField(vm, dvmObject, signature);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/content/pm/ServiceInfo->packageName:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.example.luodst.MainActivity.getObjectField(MainActivity.java:89)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
	at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)

同理,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature){
    switch (signature){
        case "android/content/pm/ServiceInfo->packageName:Ljava/lang/String;":{
            return new StringObject(vm, "com.example.LuoHun");
        }
    }
    return super.getObjectField(vm, dvmObject, signature);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/content/pm/ServiceInfo->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:77)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

按惯例,补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/content/pm/ServiceInfo->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;":{
            return vm.resolveClass("java/lang/CharSequence").newObject(null);
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.NullPointerException: Cannot invoke "Object.toString()" because "dvmObject.value" is null
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:399)
	at com.example.luodst.MainActivity.callObjectMethodV(MainActivity.java:80)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)

上述报错提示dvmObject为空,是因为上述在构造CharSequence对象时,我们传了null.先暂时按当前流程进行补,代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
      switch (signature) {
          case "java/lang/CharSequence->toString()Ljava/lang/String;": {
              return new StringObject(vm, "xialuohun.top");
          }
      }
      return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行代码,报错如下:

1
2
3
4
5
java.lang.UnsupportedOperationException: android/accessibilityservice/AccessibilityServiceInfo->packageNames:[Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.example.luodst.MainActivity.getObjectField(MainActivity.java:98)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
	at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)

按惯例, 补代码如下:

1
2
3
4
5
6
7
8
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature){
    switch (signature){
        case "android/accessibilityservice/AccessibilityServiceInfo->packageNames:[Ljava/lang/String;":{
            return new ArrayObject(new StringObject(vm, "com.example.luo"));
        }
    }
    return super.getObjectField(vm, dvmObject, signature);
}

再次运行代码,成功输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[main]E/引导: 无障碍服务的名字:XiaLuoHun
[main]E/引导: 无障碍服务所属的包名:com.example.LuoHun
[main]E/引导: 无障碍服务所属app的标签名:xialuohun.top
[main]E/引导: 作用于App:com.example.luo
[main]E/引导: 无障碍服务的名字:XiaLuoHun
[main]E/引导: 无障碍服务所属的包名:com.example.LuoHun
[main]E/引导: 无障碍服务所属app的标签名:xialuohun.top
[main]E/引导: 作用于App:com.example.luo
[main]E/引导: 无障碍服务的名字:XiaLuoHun
[main]E/引导: 无障碍服务所属的包名:com.example.LuoHun
[main]E/引导: 无障碍服务所属app的标签名:xialuohun.top
[main]E/引导: 作用于App:com.example.luo

从上述输出来看,确实模拟执行成功了.但是输出的内容肯定不对,这是因为每次调用相应的函数获取属性时,我们都返回了同一个值,最后追根溯源,回到最开始的List问题上.

这里我们将setVerbose置为true,来看下JNI执行流.

 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
37
//调用getInstalledAccessibilityServiceList
JNIEnv->FindClass(android/view/accessibility/AccessibilityManager) was called from RX@0x4000108b[libdogplus.so]0x108b
JNIEnv->GetMethodID(android/view/accessibility/AccessibilityManager.getInstalledAccessibilityServiceList()Ljava/util/List;) => 0xac86e356 was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(android.view.accessibility.AccessibilityManager@17c386de, getInstalledAccessibilityServiceList() => java.util.ArrayList@5ef60048) was called from RX@0x400011b3[libdogplus.so]0x11b3
//获取ArrayList大小
JNIEnv->GetMethodID(java/util/ArrayList.size()I) => 0x7c07529f was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallIntMethodV(java.util.ArrayList@5ef60048, size() => 0x3) was called from RX@0x40001267[libdogplus.so]0x1267
//获取ArrayList中的元素
JNIEnv->GetMethodID(java/util/ArrayList.get(I)Ljava/lang/Object;) => 0x11309d56 was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(java.util.ArrayList@5ef60048, get(0x0) => android.accessibilityservice.AccessibilityServiceInfo@1d548a08) was called from RX@0x400011b3[libdogplus.so]0x11b3
//调用getResolveInfo
JNIEnv->GetMethodID(android/accessibilityservice/AccessibilityServiceInfo.getResolveInfo()Landroid/content/pm/ResolveInfo;) => 0x76537b64 was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(android.accessibilityservice.AccessibilityServiceInfo@1d548a08, getResolveInfo() => android.content.pm.ResolveInfo@16aa0a0a) was called from RX@0x400011b3[libdogplus.so]0x11b3
//获取ServiceInfo字段
JNIEnv->GetFieldID(android/content/pm/ResolveInfo.serviceInfo Landroid/content/pm/ServiceInfo;) => 0x7c5b124e was called from RX@0x400012b9[libdogplus.so]0x12b9
JNIEnv->GetObjectField(android.content.pm.ResolveInfo@16aa0a0a, serviceInfo Landroid/content/pm/ServiceInfo; => android.content.pm.ServiceInfo@691a7f8f) was called from RX@0x400012df[libdogplus.so]0x12df
//获取ServiceInfo.name字段
JNIEnv->GetFieldID(android/content/pm/ServiceInfo.name Ljava/lang/String;) => 0x9f6455a1 was called from RX@0x400012b9[libdogplus.so]0x12b9
JNIEnv->GetObjectField(android.content.pm.ServiceInfo@691a7f8f, name Ljava/lang/String; => "XiaLuoHun") was called from RX@0x400012df[libdogplus.so]0x12df
JNIEnv->GetStringUtfChars("XiaLuoHun") was called from RX@0x40001305[libdogplus.so]0x1305
//获取ServiceInfo.packageName字段
JNIEnv->GetFieldID(android/content/pm/ServiceInfo.packageName Ljava/lang/String;) => 0xac13d6eb was called from RX@0x400012b9[libdogplus.so]0x12b9
JNIEnv->GetObjectField(android.content.pm.ServiceInfo@691a7f8f, packageName Ljava/lang/String; => "com.example.LuoHun") was called from RX@0x400012df[libdogplus.so]0x12df
JNIEnv->GetStringUtfChars("com.example.LuoHun") was called from RX@0x40001305[libdogplus.so]0x1305
//调用getPackageManager
JNIEnv->GetMethodID(android/app/Application.getPackageManager()Landroid/content/pm/PackageManager;) => 0x630dae39 was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(android.app.Application@14a2f921, getPackageManager() => android.content.pm.PackageManager@48524010) was called from RX@0x400011b3[libdogplus.so]0x11b3
//调用ServiceInfo.loadLabel
JNIEnv->GetMethodID(android/content/pm/ServiceInfo.loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;) => 0x3425649b was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(android.content.pm.ServiceInfo@691a7f8f, loadLabel(android.content.pm.PackageManager@48524010) => java.lang.CharSequence@4b168fa9) was called from RX@0x400011b3[libdogplus.so]0x11b3
//调用CharSequence.toString
JNIEnv->GetMethodID(java/lang/CharSequence.toString()Ljava/lang/String;) => 0x13c3c453 was called from RX@0x40001161[libdogplus.so]0x1161
JNIEnv->CallObjectMethodV(java.lang.CharSequence@4b168fa9, toString() => "xialuohun.top") was called from RX@0x400011b3[libdogplus.so]0x11b3
JNIEnv->GetStringUtfChars("xialuohun.top") was called from RX@0x40001305[libdogplus.so]0x1305
//获取AccessibilityServiceInfo.packageNames字段
JNIEnv->GetFieldID(android/accessibilityservice/AccessibilityServiceInfo.packageNames [Ljava/lang/String;) => 0x81285afb was called from RX@0x400012b9[libdogplus.so]0x12b9
JNIEnv->GetObjectField(android.accessibilityservice.AccessibilityServiceInfo@1d548a08, packageNames [Ljava/lang/String; => ["com.example.luo"]) was called from RX@0x400012df[libdogplus.so]0x12df

按照上述流程,从开发角度实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//获取Application
Application application = getApplication();
//获取AccessibilityManager
AccessibilityManager accessibilityManager = (AccessibilityManager) application.getSystemService(ACCESSIBILITY_SERVICE);
//调用getInstalledAccessibilityServiceList
List<AccessibilityServiceInfo> installedAccessibilityServiceList =  accessibilityManager.getInstalledAccessibilityServiceList();
//遍历AccessibilityServiceInfo数组
for (AccessibilityServiceInfo info:installedAccessibilityServiceList){
    ResolveInfo resolveInfo = info.getResolveInfo();
    ServiceInfo serviceInfo = resolveInfo.serviceInfo;
    CharSequence charSequence = serviceInfo.loadLabel(getPackageManager());
    String s = charSequence.toString();
    String[] packageNames= info.packageNames;
    Log.d("引导", "无障碍服务的名字:" + serviceInfo.name);
    Log.d("引导", "无障碍服务的包名:" + serviceInfo.packageName);
    Log.d("引导", "无障碍服务所属app的标签名:" + s);
    if (packageNames == null) continue;
    for (String ss :packageNames){
        Log.d("引导", "作用于App:" + ss);
    }
}

为了保证值的正确性,我们可以根据真实情况的值,在Unidbg中新建类如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.example.luodst;

public class AccessibilityServiceInfo {
    public String name;
    public String packageName;
    public String lable;

    public AccessibilityServiceInfo(String name, String packageName, String lable) {
        this.name = name;
        this.packageName = packageName;
        this.lable = lable;
    }
}

在补AccessibilityServiceInfos时,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
     switch (signature) {
         case "android/view/accessibility/AccessibilityManager->getInstalledAccessibilityServiceList()Ljava/util/List;":{
             List<DvmObject<?>> list = new ArrayList<>();
             DvmClass dvmClass = vm.resolveClass("android/accessibilityservice/AccessibilityServiceInfo");
             AccessibilityServiceInfo info1 = new AccessibilityServiceInfo("TalkBackService","com.google.android.marvin.talkback","TalkBack");
             AccessibilityServiceInfo info2= new AccessibilityServiceInfo("SelectToSpeakService","com.google.android.marvin.talkback","随选朗读");
             list.add(dvmClass.newObject(info1));
             list.add(dvmClass.newObject(info2));
             return new ArrayListObject(vm, list);
         }
     }
     return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

上述构造完成后,只需在之前所有填null的地方配置为dvmObject对象即可.完整代码如下:

  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
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        MainActivity mainActivity = new MainActivity();
        mainActivity.detectAccessibilityManager();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setRootDir(new File("unidbg-android/src/test/java/com/example/luodst/rootfs"))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/DogPlus.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("dogplus", true);
        //8.将so文件对应的Module存入成员变量
        module = dm.getModule();
        //9.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

   private void detectAccessibilityManager() {
        DvmObject obj = vm.resolveClass("com/example/dogplus/MainActivity").newObject(null);
        obj.callJniMethod(emulator, "detectAccessibilityManager()V");
    }

    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "android/app/ActivityThread->getApplication()Landroid/app/Application;":{
                return vm.resolveClass("android/app/Application").newObject(null);
            }
            case "android/view/accessibility/AccessibilityManager->getInstalledAccessibilityServiceList()Ljava/util/List;":{
                List<DvmObject<?>> list = new ArrayList<>();
                DvmClass dvmClass = vm.resolveClass("android/accessibilityservice/AccessibilityServiceInfo");
                AccessibilityServiceInfo info1 = new AccessibilityServiceInfo("TalkBackService","com.google.android.marvin.talkback","TalkBack");
                AccessibilityServiceInfo info2= new AccessibilityServiceInfo("SelectToSpeakService","com.google.android.marvin.talkback","随选朗读");
                list.add(dvmClass.newObject(info1));
                list.add(dvmClass.newObject(info2));
                return new ArrayListObject(vm, list);
            }
            case "android/accessibilityservice/AccessibilityServiceInfo->getResolveInfo()Landroid/content/pm/ResolveInfo;":{
                return vm.resolveClass("android/content/pm/ResolveInfo").newObject(dvmObject.getValue());
            }
            case "android/content/pm/ServiceInfo->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;":{
                AccessibilityServiceInfo info = (AccessibilityServiceInfo) dvmObject.getValue();
                return vm.resolveClass("java/lang/CharSequence").newObject(info.lable);
            }
            case "java/lang/CharSequence->toString()Ljava/lang/String;": {
                return new StringObject(vm,dvmObject.getValue().toString());
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature){
        switch (signature){
            case "android/content/pm/ResolveInfo->serviceInfo:Landroid/content/pm/ServiceInfo;":{
                return vm.resolveClass("android/content/pm/ServiceInfo").newObject(dvmObject.getValue());
            }
            case "android/content/pm/ServiceInfo->name:Ljava/lang/String;":{
                AccessibilityServiceInfo accessibilityServiceInfo = (AccessibilityServiceInfo) dvmObject.getValue();
                return new StringObject(vm, accessibilityServiceInfo.name);
            }
            case "android/content/pm/ServiceInfo->packageName:Ljava/lang/String;":{
                AccessibilityServiceInfo accessibilityServiceInfo = (AccessibilityServiceInfo) dvmObject.getValue();
                return new StringObject(vm, accessibilityServiceInfo.packageName);
            }
            case "android/accessibilityservice/AccessibilityServiceInfo->packageNames:[Ljava/lang/String;":{
                //return new ArrayObject(new StringObject(vm, "com.example.luo"));
                return new ArrayObject();
            }
        }
        return super.getObjectField(vm, dvmObject, signature);
    }
}

最终输出内容如下:

1
2
3
4
5
6
[main]E/引导: 无障碍服务的名字:TalkBackService
[main]E/引导: 无障碍服务所属的包名:com.google.android.marvin.talkback
[main]E/引导: 无障碍服务所属app的标签名:TalkBack
[main]E/引导: 无障碍服务的名字:SelectToSpeakService
[main]E/引导: 无障碍服务所属的包名:com.google.android.marvin.talkback
[main]E/引导: 无障碍服务所属app的标签名:随选朗读

补环境通用规则

  1. 基本类型,如Byte、Short、Int、Long、Double、Float、Boolean、Char等类型,直接传递即可.
1
2
3
case "android/content/pm/PackageManager->GET_SIGNATURES:I":{
    return 0x40;
}
注意
在准备参数,发起函数调用时,Long类型的参数必须显示声明,即在整数后面加L,不能使用隐式声明.
  1. 基本类型的包装类、字符串、基本类型数组、对象数组等直接传递,对象内部会做封装,也可以自己调用new StringPbject(vm, st)、new ByteArray(vm, value)等.
1
2
3
4
case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":{
    //return ProxyDvmObject.createObject(vm, vm.getPackageName());
    return new StringObject(vm, vm.getPackageName());
}

ProxyDvmObject.createObject内部做了类型判断,会根据不同的类型进行转换.

 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
37
38
39
40
41
42
43
44
45
//ProxyDvmObject.java
public static DvmObject<?> createObject(VM vm, Object value) {
    if (value == null) {
        return null;
    }
    if (value instanceof Class<?>) {
        return getObjectType(vm, (Class<?>) value);
    }
    if (value instanceof DvmObject) {
        return (DvmObject<?>) value;
    }

    if (value instanceof byte[]) {
        return new ByteArray(vm, (byte[]) value);
    }
    if (value instanceof short[]) {
        return new ShortArray(vm, (short[]) value);
    }
    if (value instanceof int[]) {
        return new IntArray(vm, (int[]) value);
    }
    if (value instanceof float[]) {
        return new FloatArray(vm, (float[]) value);
    }
    if (value instanceof double[]) {
        return new DoubleArray(vm, (double[]) value);
    }
    if (value instanceof String) {
        return new StringObject(vm, (String) value);
    }
    Class<?> clazz = value.getClass();
    if (clazz.isArray()) {
        if (clazz.getComponentType().isPrimitive()) {
            throw new UnsupportedOperationException(String.valueOf(value));
        }
        Object[] array = (Object[]) value;
        DvmObject<?>[] dvmArray = new DvmObject[array.length];
        for (int i = 0; i < array.length; i++) {
            dvmArray[i] = createObject(vm, array[i]);
        }
        return new ArrayObject(dvmArray);
    }

    return new ProxyDvmObject(vm, value);
}
  1. JDK标准库对象,如HashMap、JSONObject等,使用ProxyDvmObject.createObject(vm, value)处理.
1
2
3
case "java/util/HashMap-><init>()V":{
    return ProxyDvmObject.createObject(vm, new HashMap<>());
}
  1. 非JDK标准库对象,如Android Context、SharedPreference等,使用vm.resolveClass(vm, className).newObject(value)处理.
1
2
3
case "android/app/ActivityThread->getApplication()Landroid/app/Application;":{
    return vm.resolveClass("android/app/Application").newObject(null);
}

参考链接

<Unidbg逆向工程 原理与实践>


相关内容

0%