使用unidbg解决加密在so层的app

4,671 阅读4分钟

加密在so层的app对于我这样的菜鸡简直是一座大山啊,只好学习一下了,这里推荐一下龙哥的unidbg教程,写的太好了(虽然有的地方我还是不懂)

龙giegie的csdn: blog.csdn.net/qq_38851536…

本篇文章仅供交流学习,侵权删(满满的求生欲)

本次app样本:5Lic5pa55aS05p2hdjIuOC45

一 . 抓包

使用postern转发一下,就能抓到包了

image.png

很好,那么我们去jadx里面找一下加密位置吧。

这个app没有壳,直接搜索一下url的后半部分(如果搜加密参数的Name,虽然只有4个地方,但是可能要找一会了)

image.png

直接搜索newsgzip

image.png

只有一个地方,点进去吧,然后查找一下堆栈,堆栈也只有一条,后面就是一直跟了,因为本篇文章主要介绍unidbg,就不说那么多了。

最终的加密位置

image.png

那么加密就是在libvinda.so里面,这个加密参数的值什么@018p啥的一看就不是啥正经加密!一定是做了别的处理,C代码又看不懂,上神器!

二 . unidbg

github.com/zhkl0228/un…

直接把代码clone下来,跑通如下例子,就说明没问题了

image.png

把apk和so文件放到相应的位置,直接用unidbg通用模板跑一下

// 导入通用且标准的类库
import com.github.unidbg.linux.android.dvm.AbstractJni;
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.*;
import com.github.unidbg.memory.Memory;

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


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


    public dongfang() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File(getPath()+"\src\test\java\com\目标.apk"));

        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File(getPath()+"\src\test\java\com\libvinda.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志

        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    };


    public String getPath() {
        String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
        if(System.getProperty("os.name").contains("dows"))
        {
            path = path.substring(1,path.length());
        }
        if(path.contains("jar"))
        {
            path = path.substring(0,path.lastIndexOf("."));
            return path.substring(0,path.lastIndexOf("/"));
        }
        return path.replace("/target/test-classes/", "");
    }

    public static void main(String[] args) {
        com.xxx.dongfang test = new com.xxx.dongfang();
    }
}

image.png

结果报错了,这个错简单来说就是unidbg没有帮我们定义getApplication()这个方法(我自己是这么想的,大概就是这么个意思,别听我瞎说),我们要自己来解决。

image.png

好,这就补完unidbg没有帮我们定义的环境了,再次运行

我们发现已经不报错了,并且so里面的函数和其地址也打印出来了,好耶!

image.png

我们的目标函数是ep(前面的图里),地址是0x1a6d,那么我们现在只要知道入参是什么,就可以使用unidbg模拟执行出加密参数了。

使用frida hook ep函数 java层的入参,是str,这就不用我多说了。然后直接模拟执行。

    public String calculateS(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // 第一个参数是env
        list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。
        list.add(vm.addLocalObject(new StringObject(vm, "123")));


        Number number = module.callFunction(emulator, 0x1A6C + 1, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    };

我这里直接用123代替了,因为str真的很长!细心的大佬会发现这里地址+1了,这个就涉及到Thumb 指令集和 Arm 指令集了,具体什么是指令集别问我,问谷歌!这里可以使用IDA进行判断。

1.IDA - Options - General - number of opcode bytes - 设置为 4

2.此时查看 IDA VIew 中 opcode 的长度, 如果出现 2 个字节和 4 个字节的, 说明为 thumb 指令集,如果都是 4 个字节的, 说明是 arm 指令集;

在 Thumb 指令集下, inline hook 的偏移地址需要进行 +1 操作;

image.png

回到主题

public static void main(String[] args) {
    com.xxx.dongfang test = new com.xxx.dongfang();
    System.out.println(test.calculateS());
}

打印一下就发现出结果了!

image.png

放一下完整代码和注释,注释要好好看,和下面打jar包有关,上面有的注释我就删了,只保留和打jar包有关的注释。

public dongfang() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.International").build();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        # getPath()方法是获取当前目录,打完jar包后就有用了
        vm = emulator.createDalvikVM(new File(getPath()+"\src\test\java\com\目标.apk"));

        DalvikModule dm = vm.loadLibrary(new File(getPath()+"\src\test\java\com\libvinda.so"), true); 
        module = dm.getModule();
        vm.setJni(this);
        vm.setVerbose(true);

        dm.callJNI_OnLoad(emulator); 
    };

    public String calculateS(String key,String type){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); 
        list.add(0); 
        # 这里key是每小时会传过来一个需要经过处理去发请求的key,type区分栏目,我这里就是为了演示一下具体怎么python调用jar包传值,字符串不是这样的,简化了一下
        list.add(vm.addLocalObject(new StringObject(vm, "123"+"\""+type+"\""+"456"+"\""+key+"\""+"789")));

        Number number = module.callFunction(emulator, 0x1A6C + 1, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    };

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

    public String getPath() {
        String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
        if(System.getProperty("os.name").contains("dows"))
        {
            path = path.substring(1,path.length());
        }
        if(path.contains("jar"))
        {
            path = path.substring(0,path.lastIndexOf("."));
            return path.substring(0,path.lastIndexOf("/"));
        }

        return path.replace("/target/test-classes/", "");
    }

    public static void main(String[] args) {
        # 在这里定义需要从python传过来的值
        String type = System.getProperty("type");
        String key = System.getProperty("key");
        com.*** test = new com.***();
        System.out.println(test.calculateS(key,type));
    }

三 . 打包jar,python调用

这里要求比较严格的读者就不满意了,你只是生成了加密参数,我怎么用啊。

这里一般有两种方法,一个是jar包调用,一个是使用unidbg-server 起一个服务,我这里介绍打jar包

File → Project Structure , 然后选择 Artifacts, 点加号 Add,jar → From modules with dependencies

image.png

这里需要注意下

Main Class这里 填写的是从com.开始 ,到你这个类的类名

倒数第二个填的则是unidbg-master的根目录。

OK之后

image.png

之后就会在你unidbg-master的根目录下的 \out\artifacts\unidbg_master_jar 生成一堆jar包,如果像我一样不改输出的jar包名,默认我们写的代码生成的jar包就是unidbg-master.jar,其他jar包应该都是unidbg的依赖。

然后在这个目录下依次创建目录 src\test\java\com(这里要和代码路径对照看) 将apk和so放进去

好,重头戏,python调用,这里安装一下jpype这个包

import jpype

jar_path = os.path.join(os.path.abspath(''), 'D:\work_soft\IDEA\IDEA2021.3.3\workspace\unidbg-master\out\artifacts\unidbg_master_jar\unidbg-master.jar')#第二个参数是jar包的路径

# 启动jvm
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=%s" % (jar_path))
# 通过包名,实例化JAVA对象 括号内的是包名 后面的是类名+直接实例化
EncryClass = jpype.JPackage("com.xxx").dongfang()

jiami = EncryClass.calculateS(final_key,type)
print("jiami:",jiami)

然后和请求之类的代码放到一起(请求是啥,我不知道)

完成!!(码了好多字,好累)