首页 > 其他分享 >app逆向之安卓native层安全逆向分析(二):unidbg+ida使用+过签名校验

app逆向之安卓native层安全逆向分析(二):unidbg+ida使用+过签名校验

时间:2023-04-21 11:02:17浏览次数:48  
标签:逆向 之安卓 app vm unidbg new import com emulator

前言

继续跟着龙哥的unidbg学习:SO逆向入门实战教程二:calculateS_so 逆向_白龙~的博客-CSDN博客

还是那句,我会借鉴龙哥的文章,以一个初学者的角度,加上自己的理解,把内容丰富一下,尽量做到不在龙哥的基础上画蛇添足,哈哈。感谢观看的朋友

分析

 

首先抓包分析:

 

其中,里面的s就是今天的需要逆向的加密参数了。

 

调试

老样子,打开jadx,发现没壳,可以的,直接看吧,拿着这几个参数一顿搜,直接搜【p】

 

感觉有两个地方很可疑,进去一看:

 

跟下调用栈,很快就找到这里:

 

 

 

ok,用objection hook下,发现确实调用了这里

 

 

再仔细看看这里,明显这里很奇怪了

 

 

 

ok,终于到这里了,这里就跟龙哥给的位置一致了

 

 

先不急着用unidbg,先调试下,hook下这个方法,哎哟,我擦,这直接就对上了

 

 

看看这三个参数 ,一个是context,上下文,又叫寄存器,第二个是账号密码加起来,第三个就是一个特殊的值,大概率是加的盐,刺激。

 

unidbg调试

1.ida分析

先用ida打开看看:发现是静态注册的,可以的

 

 

选中a1

然后按键盘【y】 ,把第一个参数的类型改成JNIEnv *,这样就可以更好的反编译c代码:

 

点ok,瞬间这段代码的可读性就更强了

 

 

大概看了一眼,反正前面有个if判断,然后就进入主逻辑,然后返回

选到这个函数

 

然后按【tab】 键:这个0x1E7C就是这个函数的地址了。记一下,后面会用到

 

 

 

 

2.搭架子

开始搭建unidbg的架子,新建一个文件,然后把apk和目标so文件放进去

 

先把架子搭起来,这里我们直接复制前面oasis的:

package com.weibo;

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 com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneEncoded;
import keystone.KeystoneMode;

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

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

    international() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.weibo.international").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\weibo\\sinaInternational.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\weibo\\libutility.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
//        dm.callJNI_OnLoad(emulator); // 调用JNI onl oad
    }

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

}

  

 

运行下,没啥问题

 

 

2.地址调用

方法先写好,参数还是用list,然后用vm.addLocalObject包装一下添加进去,第一个参数是context,直接给个空就行,剩下的参数直接复制hook到的放进去

 

 

 

好的,现在运行一下

 

3.找异常执行原因——签名校验,以及绕过 

 

 可以的,跟龙哥的博客一样,报错了,然后找找日志,看看上面的一个

 

 

复制这个地址,ida里,按键盘【g】输入地址跳转过去

 

跳转到这里:

 改下JNIEnv

 

 好的,根据龙哥说的,有packagemanagger之类的,大概率是签名检测

 

选中这个方法,按【x】找交叉引用

 

很有缘分的又看到了这个sub_1c60

 

 

进去看:

 

再回到这里,应该就是这个判断了

按下tab键,记住地址,就是这个0xFFF7EBFE了

 

 

 

要能返回是true才给过,ok,直接把这里hook下,也就是直接这么改下,

 

 

在java层里,我们直接hook这个方法修改返回值为1就行,但是在这里,是在so里面,怎么搞呢?根据龙哥说的,用arm指令修改就行

 

Online ARM to HEX Converter (armconverter.com)

 

 

 

用上面的地址,写个过验证的:

 

龙哥还给了另一种patch的方法:

 

public void patchverfify(){
        int patchCode = 0x4FF00100;
        emulator.getMemory().pointer(module.base+0x1E86).setInt(0,patchCode);
    }

    public void patchVerify1(){
        Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);
        assert pointer != null;
        byte[] code = pointer.getByteArray(0, 4);
        if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE })) { // BL sub_1C60
            throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));
        }
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
            KeystoneEncoded encoded = keystone.assemble("mov r0,1");
            byte[] patch = encoded.getMachineCode();
            if (patch.length != code.length) {
                throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));
            }
            pointer.write(0, patch, 0, patch.length);
        }
    }

  

然后执行下,ok,这就出来了,舒服: 

 

 

但是好像跟hook到的返回不一样:

 

 

不急,再来看看,擦,复制的时候,激动了,把逗号复制进去了

 

 

ok,这下对上了,跟hook的结果一致

 

代码

package com.weibo;

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 com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneEncoded;
import keystone.KeystoneMode;

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

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

    international() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.weibo.international").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\weibo\\sinaInternational.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\weibo\\libutility.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
//        dm.callJNI_OnLoad(emulator); // 调用JNI onl oad
    }

    public static void main(String[] args) {
        international test = new international();
        test.patchverfify();
        System.out.println(test.calculateS());
    }
    public void patchverfify(){
        int patchCode = 0x4FF00100;
        emulator.getMemory().pointer(module.base+0x1E86).setInt(0,patchCode);
    }

    public void patchVerify1(){
        Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);
        assert pointer != null;
        byte[] code = pointer.getByteArray(0, 4);
        if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE })) { // BL sub_1C60
            throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));
        }
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
            KeystoneEncoded encoded = keystone.assemble("mov r0,1");
            byte[] patch = encoded.getMachineCode();
            if (patch.length != code.length) {
                throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));
            }
            pointer.write(0, patch, 0, patch.length);
        }
    }
    public String calculateS() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // arg1,env
        list.add(0); // arg2,jobject
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        list.add(vm.addGlobalObject(context));
        list.add(vm.addLocalObject(new StringObject(vm, "135691695686123456789")));
        list.add(vm.addLocalObject(new StringObject(vm, "CypCHG2kSlRkdvr2RG1QF8b2lCWXl7k7")));
        Number number = module.callFunction(emulator, 0x1E7C + 1, list.toArray());

        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }


}

  

 

4.符号调用

 前面都是地址调用,而龙哥自己也说过,他喜欢用地址调用。不过这里,总要学习下,怎么符号调用

 

怎么找符号,首选选中这个方法:

 

然后按tab键,进入汇编页面:

 

然后再按空格,然后这个export就是要用的符号表了,注意了,这是只是刚好都一样,很多时候,这三个,不一样的,找准export的才行

 

 

 开始调用,就换了下名字,其他都没变,对比两个不同的调用方式,结果一样,没毛病

 

代码:

package com.weibo;

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 com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneEncoded;
import keystone.KeystoneMode;

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

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

    international() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.weibo.international").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\weibo\\sinaInternational.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\weibo\\libutility.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
//        dm.callJNI_OnLoad(emulator); // 调用JNI onl oad
    }

    public static void main(String[] args) {
        international test = new international();
        test.patchverfify();
        System.out.println("offset===>:" + test.calculateS());
        System.out.println("symbol===>:" + test.calculateS1());
    }

    
    
    public String calculateS1() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // arg1,env
        list.add(0); // arg2,jobject
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        list.add(vm.addGlobalObject(context));
        list.add(vm.addLocalObject(new StringObject(vm, "135691695686123456789")));
        list.add(vm.addLocalObject(new StringObject(vm, "CypCHG2kSlRkdvr2RG1QF8b2lCWXl7k7")));
        Number number = module.callFunction(emulator, "Java_com_sina_weibo_security_WeiboSecurityUtils_calculateS", list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }

    public void patchverfify() {
        int patchCode = 0x4FF00100;
        emulator.getMemory().pointer(module.base + 0x1E86).setInt(0, patchCode);
    }

    public void patchVerify1() {
        Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);
        assert pointer != null;
        byte[] code = pointer.getByteArray(0, 4);
        if (!Arrays.equals(code, new byte[]{(byte) 0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE})) { // BL sub_1C60
            throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));
        }
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
            KeystoneEncoded encoded = keystone.assemble("mov r0,1");
            byte[] patch = encoded.getMachineCode();
            if (patch.length != code.length) {
                throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));
            }
            pointer.write(0, patch, 0, patch.length);
        }
    }

    public String calculateS() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // arg1,env
        list.add(0); // arg2,jobject
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        list.add(vm.addGlobalObject(context));
        list.add(vm.addLocalObject(new StringObject(vm, "135691695686123456789")));
        list.add(vm.addLocalObject(new StringObject(vm, "CypCHG2kSlRkdvr2RG1QF8b2lCWXl7k7")));
        Number number = module.callFunction(emulator, 0x1E7C + 1, list.toArray());

        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }


}

  

部署

有没有想过,搞的这个,如果想部署成一个web服务,以供后续的业务代码调用呢?不然这么个项目,部署在服务器上,然后 每次启动都调用unidbg,说实话不是太现实,所以这里就需要把unidbg打包成jar包:

 

最实用且简单的方法:

 

1.

 2.

 3.

 

 

 4.

 

 

 

 5.

 6.

等待结果:

 

然后根目录就会多一个out目录:

 

 

7.这个文件就是最后能直接通过java执行的:

 

但是有个问题,我们写的apk和so文件,给定的地址是unidgb-android/src里的

 

 要打包的话,就得改下路径,改成相对路径

 

然后删除out文件夹,重新上面的打包操作,然后把apk和so放到跟unidbg-jar同级的目录:

 

这样就好了, 终端执行看看,很好,结果也有的,这样就可以把这个out包整个打包 ,然后部署到服务器上就行了。

 

还有更多的打包方式:

 

Unidbg打Jar包方式_unidbg打包成jar_Forgo7ten的博客-CSDN博客

 

 

知识点总结

1.ida,按y修改JNIEnv,IDA 7.5之前,JNIEnv需要导入jni.h,7.5之后不需要导入jni.h文件

2.ida,按g 输入地址跳转

3.ida,按x,查看交叉引用

4.ida,optional->generel,把这个改成4,可以查看架构模式,arm架构是固定4位,thumb架构是混合的

5. 有签名校验的需要去patch,patch的时候不能+1,只有运行和hook的时候才+1

6.setJNIload方法只有动态注册方法的时候才执行,静态注册的不用执行

7.keystone,是将汇编语言转成地址的。capstone是将地址转成汇编语言的

8.在线汇编和地址互转的网站:https://armconverter.com/

9.参数的基本类型,比如int,long等,其他的对象类型一律要手动 addLocalObject,其中context对象用:

DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
list.add(vm.addLocalObject(context))

10.找准一行汇编,Alt+G快捷键,查看架构类型,值为1则是thumb,为0则是arm,如果ida解析有误,可以手动修改这个值

11.RM调用约定,入参前四个分别通过R0-R3调用,返回值通过R0返回

12.unidbg的项目可以打包成java包执行

 

 

结语

说实话,目前来说,不难,得后续的大厂项目才会很难

 

标签:逆向,之安卓,app,vm,unidbg,new,import,com,emulator
From: https://www.cnblogs.com/Eeyhan/p/17337827.html

相关文章

  • 解决uniapp不显示showToast或一闪而过问题
    原因:showToast与其他方法冲突,以onLoad冲突为例第一种:将showToast与showLoading分开,放到mounted中mounted(){//判断是否登录 if(!this.hasLogin){ uni.showToast({ title:'登录过期', icon:'none' }) }},onLoad(option){ ......
  • Django框架——手写web框架、wsgiref模块、动静态网页、jinja2模块、主流web框架、Dja
    web应用'''通过浏览器访问的应用程序!!!'''1.两种模式c/sb/s B/S:browser---------------->server2.web应用程序的有点 2.1只需要一个浏览器即可2.2 节省资源2.3 他们不需要更新,因为所有新的特性都在服务器上执行3.web应用程序的缺点 #特别依赖服务端程序的健壮......
  • 【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.u
    问题描述在Azure的SpringCloud服务(官名为:SpringApps)中,在Metrics页面中查看AppMemoryUsage和jvm.memory.use,发现两则在下图中出现巨大差距。AppMemoryUsage还是在逐渐上升jvm.memory.use却断崖式下降  在AppMemoryUsage在逐渐上涨的情况下,是否会最终出现OO......
  • uniapp计算属性和监听属性的使用及props验证
    计算属性:定义:computed:{变量名xx(){return计算的代码}} 使用: <p>乘以2的值为{{变量名xx()}}</p>监听属性:(普通监听:无法监听到第一次绑定的变化)定义: watch:{变量名xx(newName,oldName){console.log(可以打印新老数据)}}(普通监听:可监听到第一次绑定的变化)定......
  • SQL Server Cross Join、Cross Apply和Outer Apply
    先简单了解下crossapply的语法以及会产生什么样的结果集吧!示例表:SELECT*FROMtableACROSSAPPLYtableB两张表直接连接,不需要任何的关联条件,产生的结果就是这两张表的笛卡儿集,在这里和上一篇帖子讲的crossjoin交叉连接的结果集是一样的相当于:select*fromtableA,tabl......
  • 安卓APP开发费用|安卓APP开发需要多少钱
    安卓app开发的费用因许多因素而异,如应用程序的类型、功能、复杂性、设计要求、开发团队的经验等等。一些简单的应用程序可以在较低的价格范围内开发,而较复杂的应用程序可能需要更多的时间和资源来设计和开发,因此价格可能会更高。一般来说,单个安卓应用程序的开发费用可能在数千美......
  • APP开发完成后后期维护难吗
    APP开发完成后后期维护是一个非常重要的工作,因为维护可以保持应用程序的性能和功能,提高用户体验和满意度。在应用程序开发完成后,应用程序需要持续更新和维护,以适应不断变化的用户需求和市场趋势。APP开发完成后的后期维护可能存在一些挑战,如下所述:代码的复杂性:应用程序的代码......
  • APP开发后期维护费用高吗
    APP后期维护费用的高低取决于多个因素,例如应用程序的复杂性、更新频率、维护团队的规模和地理位置等。下面是一些可能影响后期维护费用的因素:应用程序的复杂性:如果应用程序功能复杂,代码结构复杂,那么后期维护费用可能会更高。这是因为在复杂的应用程序中,发现和修复缺陷需要更多......
  • 下载App页面
    目录说明代码说明下载app页面,在微信中分享,很容易被拦截。这里作了一个处理,当在微信中打开链接,自动跳转到浏览器中打开提示下载。代码<!DOCTYPEhtml><html><head><metacharset="utf-8"/><title>XXX客户端</title><styletype="text/css">.......
  • 开发一个社交类的APP需要的成本大概是多少
       开发一个社交类的APP需要的成本大概是多少 移动互联时代,社交手段不断地翻新,社交软件也大量涌现,微信、微博、抖音、快手虽然是热门,但小众的需求永远存在,移动社交app市场火爆,关于社交APP的开发流程、成本也成了大家关心的话题。 影响社交APP开发价格的因素很多,最主要......