SO逆向入门实战教程十:SimpleSign

一、前言

这是系列的第十篇,通过该样本可以充分学习如何在Unidbg中补充环境。朋友zh3nu11和我共同完成了这篇内容,感谢。

二、准备

在这里插入图片描述
首先我们发现了init函数,它应该就是SO的初始化函数,其余的函数看名字也都很易懂。

三、Init 模拟执行

我们首先用Unidbg跑通Init初始化函数,先搭建基本框架

package com.article10;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class SecurityUtil extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public SecurityUtil(){
        emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("base.apk"));

        DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
        module = dm.getModule();

        vm.setJni(this);
        vm.setVerbose(true);

        dm.callJNI_OnLoad(emulator);
    }

    public static void main(String[] args) {
        SecurityUtil test = new SecurityUtil();
    }
}

运行产生如下报错
在这里插入图片描述
红框即报错原因,内存错误,箭头所指是根本原因,样本中open了这两个文件,但找不到,所以我们需要补充这两个文件。

我们需要逐一做理解

1.proc/23638/xxx 是什么

proc 文件系统由内核提供,它是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。系统中当前运行的每一个进程都有对应的一个目录在 proc 下,以进程的 PID 号为目录名,它们是读取进程信息的接口。

此处的23638就是Unidbg中APP的当前进程,每次运行,Unidbg都会随机化给一个进程PID,就像Linux系统中一样。

在这里插入图片描述
我修改了src/main/java/com/github/unidbg/AbstractEmulator.java 中的如图位置,使PID固定,因为PID不停变动可能会影响后续分析,但这不是必须的操作。
在这里插入图片描述
2.此处读取cmdline做什么

在Android系统中,进程的cmdline返回应用的进程名。那么此处的目的就很明显了,验证环境是否是”自己“的环境,防止应用被重打包。

3.在Unidbg中如何填补

proc 文件系统是伪文件系统,其目录下的所有文件,读写和正常文件没差别,所以在Unidbg中做好文件的重定向就行了。

三步走,实现IOResolver接口,注册接口,实现resolve方法并补充上我们的逻辑。

package com.article10;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class SecurityUtil extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public SecurityUtil(){
        emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        emulator.getSyscallHandler().addIOResolver(this);
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("base.apk"));

        DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
        module = dm.getModule();

        int pid = emulator.getPid();
        System.out.println("APP pid:"+pid);

        vm.setJni(this);
        vm.setVerbose(true);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {
        SecurityUtil test = new SecurityUtil();
    }

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
        }
        return null;
    }
}

那status呢?该文件包含该进程的众多信息:可执行文件名、当前状态、PID 和 PPID、实际及有效的 UID 和 GID、内存使用情况、以及其他。我们可以随便看一个

root@hammerhead:/proc/9884 # cat status
Name:	adb
State:	S (sleeping)
Tgid:	9884
Pid:	9884
PPid:	1
TracerPid:	0
Uid:	2000	2000	2000	2000
Gid:	2000	2000	2000	2000
FDSize:	32
Groups:	1003 1004 1007 1011 1015 1028 3001 3002 3003 3006 
VmPeak:	    4012 kB
VmSize:	    2992 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	     880 kB
VmRSS:	     880 kB
VmData:	    1196 kB
VmStk:	     136 kB
VmExe:	     104 kB
VmLib:	    1304 kB
VmPTE:	       8 kB
VmSwap:	       0 kB
Threads:	2
SigQ:	1/12274
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000001000
SigCgt:	000000000000a4f8
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	fffffff0000000c0
Cpus_allowed:	f
Cpus_allowed_list:	0-3
voluntary_ctxt_switches:	7
nonvoluntary_ctxt_switches:	46

一般而言,样本检测status是为了其中的TracerPid字段,TracerPid反调试的原理就是检测这个字段是否为0,为0说明没有被调试,不为0说明正在被调试,检测调试器直接退出就可以达到反调试的效果。

所以我们常常这么处理——只返回TracerPid字段

@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
        return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
    }
    if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
        return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "TracerPid:\t0\n".getBytes()));

    }
    return null;
}

有人可能会担心不保险,万一其他字段也被用到了呢,样本找不到这些字段导向错误逻辑怎么办?这个情况是存在的,我们马上就说,先运行看看效果,emm,似乎跑通了。

在这里插入图片描述

我们终于可以开始执行init函数了,但我打算先看看这个getNameByPid函数。

在这里插入图片描述
首先Frida Call 测试一下

function callgetPid(pid){
    var securityUtil = null;
    Java.perform(function () {
        Java.choose("ctrip.android.security.SecurityUtil", {
            //枚举时调用
            onMatch:function(instance){
                //打印实例
                securityUtil = instance;
                console.log("find instance")
            },
            //枚举完成后调用

            onComplete:function() {
                console.log("end")
            }});
        var result = securityUtil.getNameByPid(pid);
        console.log(result);
    })
}

看一下PID
在这里插入图片描述
在这里插入图片描述
结果是ip.android.view。Unidbg主动调用测试一下

package com.article10;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class SecurityUtil extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public SecurityUtil(){
        emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        emulator.getSyscallHandler().addIOResolver(this);
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("base.apk"));

        DalvikModule dm = vm.loadLibrary(new File(libscmain.so"), true);
        module = dm.getModule();

        int pid = emulator.getPid();
        System.out.println("APP pid:"+pid);

        vm.setJni(this);
        vm.setVerbose(true);

        dm.callJNI_OnLoad(emulator);

    }

    public void callgetNameByPid(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(emulator.getPid());
        Number number = module.callFunction(emulator, 0xee01, list.toArray())[0];
        String name = vm.getObject(number.intValue()).getValue().toString();
        System.out.println(name);
    }
    
    public static void main(String[] args) {
        SecurityUtil test = new SecurityUtil();
        test.callgetNameByPid();
    }

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
        }
        if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
            return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "TracerPid:\t0\n".getBytes()));

        }
        return null;
    }
}

在这里插入图片描述
结果是0。实际上,这就是status 没补齐全带来的锅。这是APP真实的status

2|bullhead:/proc/19929 # cat status
Name:   ip.android.view
State:  R (running)
Tgid:   19929
Pid:    19929
PPid:   17506
TracerPid:      0
Uid:    10148   10148   10148   10148
Gid:    10148   10148   10148   10148
FDSize: 512
Groups: 3002 3003 9997 20148 50148
VmPeak:  2224800 kB
VmSize:  2180604 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:    354920 kB
VmRSS:    322600 kB
VmData:   375124 kB
VmStk:      8192 kB
VmExe:        20 kB
VmLib:    209888 kB
VmPTE:      2024 kB
VmSwap:     2952 kB
Threads:        122
SigQ:   2/6517
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000000
SigCgt: 00000006400096fc
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
Seccomp:        2
Cpus_allowed:   0f
Cpus_allowed_list:      0-3
Mems_allowed:   1
Mems_allowed_list:      0
voluntary_ctxt_switches:        123395
nonvoluntary_ctxt_switches:     84141

样本正是读取其中的Name,所以Unidbg补环境一定要心细和谨慎,处处可能出问题。看一下修改后的重定向方法

 @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
        }
        if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, ("Name:   ip.android.view\n" +
                    "State:  R (running)\n" +
                    "Tgid:   "+emulator.getPid()+"\n" +
                    "Pid:    "+emulator.getPid()+"\n" +
                    "PPid:   17506\n" +
                    "TracerPid:      0\n" +
                    "Uid:    10148   10148   10148   10148\n" +
                    "Gid:    10148   10148   10148   10148\n" +
                    "FDSize: 512\n" +
                    "Groups: 3002 3003 9997 20148 50148\n" +
                    "VmPeak:  2224800 kB\n" +
                    "VmSize:  2185240 kB\n" +
                    "VmLck:         0 kB\n" +
                    "VmPin:         0 kB\n" +
                    "VmHWM:    354920 kB\n" +
                    "VmRSS:    324572 kB\n" +
                    "VmData:   379340 kB\n" +
                    "VmStk:      8192 kB\n" +
                    "VmExe:        20 kB\n" +
                    "VmLib:    209888 kB\n" +
                    "VmPTE:      2020 kB\n" +
                    "VmSwap:     3012 kB\n" +
                    "Threads:        127\n" +
                    "SigQ:   2/6517\n" +
                    "SigPnd: 0000000000000000\n" +
                    "ShdPnd: 0000000000000000\n" +
                    "SigBlk: 0000000000001204\n" +
                    "SigIgn: 0000000000000000\n" +
                    "SigCgt: 00000006400096fc\n" +
                    "CapInh: 0000000000000000\n" +
                    "CapPrm: 0000000000000000\n" +
                    "CapEff: 0000000000000000\n" +
                    "CapBnd: 0000000000000000\n" +
                    "CapAmb: 0000000000000000\n" +
                    "Seccomp:        2\n" +
                    "Cpus_allowed:   0f\n" +
                    "Cpus_allowed_list:      0-3\n" +
                    "Mems_allowed:   1\n" +
                    "Mems_allowed_list:      0\n" +
                    "voluntary_ctxt_switches:        21102\n" +
                    "nonvoluntary_ctxt_switches:     20849").getBytes()));
        }
        return null;
    }

接下来步入正题——执行init方法!增改内容如下,并运行。

public void callInit(){
    List<Object> list = new ArrayList<>(10);
    list.add(vm.getJNIEnv());
    list.add(0);
    DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
    list.add(vm.addLocalObject(context));
    module.callFunction(emulator, 0x36a85, list.toArray());
};

public static void main(String[] args) {
    SecurityUtil test = new SecurityUtil();
    test.callInit();
}

在这里插入图片描述
补JAVA环境是Unidbg中的基础活,但它是有技巧的,善用JNITrace可以补的又快又好,比如本篇的Unidbg环境,就是在1h内补好的。如果补环境有问题或者不会补,欢迎评论区或者课上交流讨论。

见招拆招补充几个后

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
                return vm.resolveClass("java/io/File").newObject(signature);
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/io/File->getPath()Ljava/lang/String;":{
                System.out.println("PATH:"+dvmObject.getValue());
                if(dvmObject.getValue().equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
                    return new StringObject(vm, "/mnt/sdcard");
                }
                if(dvmObject.getValue()=="android/content/Context->getFilesDir()Ljava/io/File;"){
                    return new StringObject(vm, "/data/data/ctrip.android.view/files");
                }
            }
            case "android/content/Context->getPackageResourcePath()Ljava/lang/String;":
                return new StringObject(vm, "/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk");
            case "android/content/Context->getFilesDir()Ljava/io/File;":
                return vm.resolveClass("java/io/File").newObject(signature);
            case "android/content/Context->getAssets()Landroid/content/res/AssetManager;":
                return new AssetManager(vm, signature);
        }

        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

报错了,这次看不出啥明显错误
在这里插入图片描述日志全开再看看,好像更迷糊了
在这里插入图片描述
但其实这个问题我们早就讲过了,Android中通过libandroid.so对Assets资源文件进行操作,日志中可以看到”getAssets“等字眼,但是由于libandroid.so的依赖SO太多了,Unidbg很难一一处理,所以资源文件相关的处理会报错。为此Unidbg做了一个折中的解决防范——可以自己注册虚拟模块,或者叫虚拟SO,libandroid.so已经由作者实现了,实现了常用的几个Assets操作的API。只用加上这一行就行,需要注意必须在样本SO之前加载或者叫注册进去

在这里插入图片描述
继续运行,为什么还报错

在这里插入图片描述
是这样的,样本在通过虚拟SO读取资源文件,资源的读取有四种模式

/** Available access modes for opening assets with {@link AAssetManager_open} */
enum {
    /** No specific information about how data will be accessed. **/
    AASSET_MODE_UNKNOWN      = 0,
    /** Read chunks, and seek forward and backward. */
    AASSET_MODE_RANDOM       = 1,
    /** Read sequentially, with an occasional forward seek. */
    AASSET_MODE_STREAMING    = 2,
    /** Caller plans to ask for a read-only buffer with all data. */
    AASSET_MODE_BUFFER       = 3
};

但作者只实现了模式1和2,而此处使用mode 0。mode 0 没有啥特殊处理要做,我们直接合并入mode 2和3的逻辑即可。
在这里插入图片描述
继续运行,即已经过了这个坑。有的人可能会困惑,Native层读写资源文件做什么?其实这是常见做法,比如把key编码进资源文件的一张图片里,Native层去读取这个资源文件获取key。

下面就是继续补JAVA环境,使用JNItrace辅助,飞速补完。

在这里插入图片描述
补完后代码整体长这样

package com.article10;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
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.api.AssetManager;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class SecurityUtil extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public SecurityUtil(){
        emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        emulator.getSyscallHandler().addIOResolver(this);
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("base.apk"));

        new AndroidModule(emulator, vm).register(memory);

        DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
        module = dm.getModule();

        int pid = emulator.getPid();
        System.out.println("APP pid:"+pid);

        vm.setJni(this);
        vm.setVerbose(true);

        dm.callJNI_OnLoad(emulator);

    }

    public void callgetNameByPid(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(emulator.getPid());
        Number number = module.callFunction(emulator, 0xee01, list.toArray())[0];
        String name = vm.getObject(number.intValue()).getValue().toString();
        System.out.println(name);
    }

    public void callInit(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
        list.add(vm.addLocalObject(context));
        module.callFunction(emulator, 0x36a85, list.toArray());
    };

    public static void main(String[] args) {
        Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
        SecurityUtil test = new SecurityUtil();
        test.callInit();
    }

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
                return vm.resolveClass("java/io/File").newObject(signature);
            }
            case "okio/zz->b(I)Ljava/lang/String;":{
                int key = varArg.getInt(0);
                switch (key){
                    case 1:{
                        return new StringObject(vm, "353626076466627");
                    }
                    case 0:{
                        return new StringObject(vm, "8cff8823cf19b5ec");
                    }
                    case 101:{
                        return new StringObject(vm, "25483");
                    }
                    case 103:{
                        return new StringObject(vm, "1920*1080");
                    }
                    case 104:{
                        return new StringObject(vm, "");
                    }
                    case 102:{
                        return new StringObject(vm, "17637");
                    }
                    case 105:{
                        return new StringObject(vm, "WIFI");
                    }
                    case 106:{
                        return new StringObject(vm, "0.0.0.0:0");
                    }
                    case 8:{
                        return new StringObject(vm, "0.0.0.0:0");
                    }
                    case 9:{
                        return new StringObject(vm, "");
                    }
                    case 10:{
                        return new StringObject(vm, "00:00:00:00:00:00");
                    }
                    case 107:{
                        return new StringObject(vm, "[full-100]");
                    }
                    case 108:{
                        return new StringObject(vm, "78");
                    }
                }
                System.out.println("okio/zz->b(I) Key:"+key);
            }
            case "java/net/NetworkInterface->getByName(Ljava/lang/String;)Ljava/net/NetworkInterface;":{
                String name = null;
                DvmObject<?> namedvm = varArg.getObject(0);
                if(namedvm!=null){
                    name = (String) namedvm.getValue();
                }
                return vm.resolveClass("java/net/NetworkInterface").newObject(name);
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/io/File->getPath()Ljava/lang/String;":{
                System.out.println("PATH:"+dvmObject.getValue());
                if(dvmObject.getValue().equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
                    return new StringObject(vm, "/mnt/sdcard");
                }
                if(dvmObject.getValue()=="android/content/Context->getFilesDir()Ljava/io/File;"){
                    return new StringObject(vm, "/data/data/ctrip.android.view/files");
                }
            }
            case "android/content/Context->getPackageResourcePath()Ljava/lang/String;":
                return new StringObject(vm, "/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk");
            case "android/content/Context->getFilesDir()Ljava/io/File;":
                return vm.resolveClass("java/io/File").newObject(signature);
            case "android/content/Context->getAssets()Landroid/content/res/AssetManager;":
                return new AssetManager(vm, signature);
            case "java/net/NetworkInterface->getHardwareAddress()[B":
                byte[] result = hexStringToByteArray("64BC0C65AA1E");
                return new ByteArray(vm, result);
        }

        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
        }
        if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, ("Name:   ip.android.view\n" +
                    "State:  R (running)\n" +
                    "Tgid:   "+emulator.getPid()+"\n" +
                    "Pid:    "+emulator.getPid()+"\n" +
                    "PPid:   17506\n" +
                    "TracerPid:      0\n" +
                    "Uid:    10148   10148   10148   10148\n" +
                    "Gid:    10148   10148   10148   10148\n" +
                    "FDSize: 512\n" +
                    "Groups: 3002 3003 9997 20148 50148\n" +
                    "VmPeak:  2224800 kB\n" +
                    "VmSize:  2185240 kB\n" +
                    "VmLck:         0 kB\n" +
                    "VmPin:         0 kB\n" +
                    "VmHWM:    354920 kB\n" +
                    "VmRSS:    324572 kB\n" +
                    "VmData:   379340 kB\n" +
                    "VmStk:      8192 kB\n" +
                    "VmExe:        20 kB\n" +
                    "VmLib:    209888 kB\n" +
                    "VmPTE:      2020 kB\n" +
                    "VmSwap:     3012 kB\n" +
                    "Threads:        127\n" +
                    "SigQ:   2/6517\n" +
                    "SigPnd: 0000000000000000\n" +
                    "ShdPnd: 0000000000000000\n" +
                    "SigBlk: 0000000000001204\n" +
                    "SigIgn: 0000000000000000\n" +
                    "SigCgt: 00000006400096fc\n" +
                    "CapInh: 0000000000000000\n" +
                    "CapPrm: 0000000000000000\n" +
                    "CapEff: 0000000000000000\n" +
                    "CapBnd: 0000000000000000\n" +
                    "CapAmb: 0000000000000000\n" +
                    "Seccomp:        2\n" +
                    "Cpus_allowed:   0f\n" +
                    "Cpus_allowed_list:      0-3\n" +
                    "Mems_allowed:   1\n" +
                    "Mems_allowed_list:      0\n" +
                    "voluntary_ctxt_switches:        21102\n" +
                    "nonvoluntary_ctxt_switches:     20849").getBytes()));
        }
        return null;
    }
}

接下来遇到报错
在这里插入图片描述
样本在读取apk文件,继续在resolve方法中做好重定向的工作

     if (("/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk").equals(pathname)) {
            return FileResult.success(new SimpleFileIO(oflags, new File("yourAPkpath\\xxx.apk"), pathname));
        }

继续运行,可能再补充一些JAVA环境,整体没有报错了,似乎init补充完了。

在这里插入图片描述

日志结尾看到样本对这些文件进行访问,虽然没有报错,但我们可能会好奇,它在做什么呢?事实上,如果存在这些文件,则说明手机已经Root,样本在试图访问这些文件,来判断系统是否被Root了。我们这里不做任何处理。

四、Unidbg 模拟执行SimpleSign

先Frida call一下,参数二固定,参数一可自行修改。

function callSimpleSign(){
    var securityUtil = null;
    Java.perform(function () {
        Java.choose("ctrip.android.security.SecurityUtil", {
            //枚举时调用
            onMatch:function(instance){
                //打印实例
                securityUtil = instance;
                console.log("find instance")
            },
            //枚举完成后调用

            onComplete:function() {
                console.log("end")
            }});
        var input1 = stringToBytes("7be9f13e7f5426d139cb4e5dbb1fdba7")
        var result = securityUtil.simpleSign(input1, "getdata");
        console.log(result);
    })
}

结果恒为75位长度,且相同输入也会有不同输出,原因未知。

接下来Unidbg中测试

    public void callSimpleSign(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        String input = "7be9f13e7f5426d139cb4e5dbb1fdba7";
        byte[] inputByte = input.getBytes(StandardCharsets.UTF_8);
        ByteArray inputByteArray = new ByteArray(vm,inputByte);
        list.add(vm.addLocalObject(inputByteArray));
        list.add(vm.addLocalObject(new StringObject(vm, "getdata")));
        Number number = module.callFunction(emulator, 0x80735, list.toArray())[0];
        System.out.println(vm.getObject(number.intValue()).getValue().toString());
    };

    public static void main(String[] args) {
        Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
        Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
        SecurityUtil test = new SecurityUtil();
        test.callInit();
        System.out.println("call SimpleSign");
        test.callSimpleSign();
    }

结果也很顺利,如果报错,则根据缺失的JAVA环境继续补即可。

在这里插入图片描述
考虑一个小问题,即使明文一致,结果也一直在变,这是为什么呢?有哪些原因可能导致这个结果呢?

五、尾声

相关程序和资料放群里,防止遇到恶意举报。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值