密码学初步认识

xiaoeryu Lv5

接下来几篇文章主要是对常见加解密算法的认识和逆向分析,如果不了解加解密算法的话我们直接去分析app是比较困难的。

接下来几篇文章要达到的目的:

  1. 掌握常见的加解密算法的基本原理
  2. 掌握对app逆向分析过程中快算识别,使用的标准加解密算法的方法

算法简单分类

  1. 对称密码
    1. 序列密码(流密码):将明文消息按字符逐位进行加密。(RC4)
    2. 分组密码:将明文消息分组(每组有多个字符可能非常多,可以有几M甚至几个G),逐组进行加密。(ABS、AES)
  2. 非对称密码
  3. 散列算法(消息摘要)

BASE64编码简介

像BASE16、BASE32、BASE64、BASE85、BASE36、BASE58、BASE91、BASE92、BASE62,这些都是编码方式,对应有各自的一套编码算法。类似的还有url编码。

有些时候也会称这些为加密,例如经常有人说BASE64加密。其实这种说法不太严谨,这些都是对信息的一种编码表示形式,并不涉及到加密密钥key。因此,只要得到了对应的编码算法,也就是拿到了对应的密钥。

BASE64编码是网络上最常用的用于传输字节信息的编码方式之一,BASE64就是一种基于64个可打印字符来表示二进制数据的方法。其它BASE家族的编码也是同理。

BASE64编码原理:用64个可见字符来表示8bite位二进制(A-Z、a-z、0-9、“+”、“/”、还有“=”用于填充)。
BASE32编码原理:用32个可见字符来表示8bite位二进制(A-Z、2-7)。

Android中的BASE64编码使用android.util包下提供的Base64类,其中提供了Base64编码和解码相关的API可以直接使用。

BASE64在反汇编后的表现形式

在代码没有加混淆的时候,我们用GDA反编译看一下它的java代码

  • 此时反编译出来的都是明文,我们可以直接看到代码调用了Base64.encodeToString()

  • 如果是自己实现的base64或者在有名称混淆的情况下,就没有这个容易分辨出来了

    遇到这种情况,可以考虑使用hook的方法去主动调用其中的方法,给它传入一个参数,然后看它的返回结果是什么

so文件中的base64

将so文件提取出来放在ida中分析,可以通过字符串(CTRL+F12)中的base64编码表定位到其被调用的位置

  • 这些也是在没有加壳也没有混淆的情况下,直接就可以定位到。然后F5就可以很清晰的反编译出来

加了ollvm后so文件中的base64

  • 此时虽然还是相同的代码,但是反编译结果的流程就已经完全被打乱看不出来是什么函数了。整个流程看起来非常的复杂。想要直接分析这个是非常困难的
  • 对于这种情况我们可以考虑使用重放攻击,也就是我们再前面说的主动去调用这个函数,给他传入参数看它的输出结果

尝试分析

要使用重放攻击,首先我们要搞明白目标函数是哪一个它的地址在哪,然后分析出它的传参是什么

首先,我们照常根据字符串中的编码表定位到调用函数,然后F5查看这个函数的交叉引用

  • 看到有一个交叉引用,我们跟进去看一下
  • 进来之后发现这个函数经过ollvm后还是相对比较简单的,是一个JNI函数。这里我们可以去用GDA或者GADX反编译一下看看它的定义。根据定义看看是否需要修改一下ida的识别结果

    • 首先它的参数是一个str类型
  • 根据定义修改函数的变量

    • 第三个参数是我们输入的字符日,前面两个是隐藏参数(参数1:JNIEnv*,参数2:调用者:静态为jclass、非静态为:jobject)

    • sub_19D6会接收我们输入的字符串,然后返回一个值。进入查看一下这个函数

      • 函数的返回值类型是const char*,函数的参数类型识别错误了手动修改一下。返回值是C格式的UTF-8字符串指针
    • 再返回上一级分析循环代码可知在下一轮循环中就进入了sub_834

      • 弄清楚了这个函数的大概作用和接收的参数后,可以写frida脚本来验证一下这个函数的输入和返回

Frida脚本

function activeinvokesub_834(content){
    var offset = 0x834 + 1;		// 直接通过硬偏移调用,这个地址为:函数所在偏移+1
    var nativelibmodule = Process.getModuleByName("libnative-lib.so");
    var addr = nativelibmodule.base.add(offset);	// 基址加上偏移

    var arg0 = Memory.allocUtf8String(content);

    var sub_834 = new NativeFunction(addr, 'pointer', ['pointer']);
    var result = sub_834(arg0);
    console.log("result: ",hexdump(result, {length: 16, header: true, ansi: true}));
}
  • 打开手机端frida-server,执行frida命令附加我们的脚本执行

    frida -U com.kanxue.encrypt01 -l base64.js

    • 上图可以看到我们的脚本被正确的附加且执行了

针对函数进行重放攻击

  1. 针对java实现的函数,可以简单编写frida脚本或xposed插件对函数进行主动调用。只需要知道类名和函数名就ok,对于加壳的app可能会出现找不到类找不到函数等问题,这些问题的根本原因还是使用的classloader不对,只有加载了这个class的classloader才可以找到这个类。
  2. 针对so中的函数,推荐使用frida脚本进行主动调用,较为简洁,当然使用xposed也可以。如果是导出函数就比较简单只需要在frida中遍历它的符号表就可以拿到函数地址。使用xposed也可以,使用dlopen、dlsym也可以快速的拿到函数地址。如果没有导出的话就需要去找到函数的基址和偏移

加壳的ollvm分析

接下来是对加壳了的app的处理流程,怎么去快速的定位加解密算法相关的类,然后对类中的函数进行调用

脱壳

分析软件,对于能脱壳的就先把壳脱掉。

这里使用frida_fart 脚本来脱壳:

脚本使用方法在脚本中有说明,这里说一下大致使用流程

  1. 将加壳app安装到手机上

  2. 运行脚本脱壳frida -U -f com.kanxue.encrypt01 -l frida_fart_hook.js --no-pause,对于没有存储权限的app把脚本中的savepath设置为app包所在的路径

  3. 调用**fart()**对codeitem进行一个dumpfart()

当然脱壳方法非常多,把pixel刷入脱壳机的rom也可以

分析dex

通过文本搜索找到包含MainActivity的文件,然后用GDA反编译

  • 反编译出来之后尝试用Java.perform() HOOK **caicaikan()**这个函数

  • 这个函数是一个非静态函数,有一个string参数。对于一个非静态的函数我们需要一个实例才能对其进行主动调用

    function activeinvokejavafunc(content){
        Java.perform(function () {
            // com.kanxue.encrypt01.MainActivity
            var mainActivityClass = Java.use("com.kanxue.encrypt01.MainActivity");
            console.log("mainActivityClass: ", mainActivityClass);
    
            // public String caicaikan(String content)
            Java.choose("com.kanxue.encrypt01.MainActivity",{
                onMatch:function(obj){
                    console.log("found obj: ", obj);
                    var result = obj.caicaikan(content);
                    console.log("result: ", result);
                },onComplete:function(){
                    console.log("search heap complete");
                }
            })
        })
    }
    
  • 对于这种情况可以直接调用就行

如果一个app使用动态加载的方式,加载了一些插件中的classloader的时候,我们就需要使用frida中枚举的方式去遍历所有的classloader。枚举到了我们就把这个classloader设置为当前的classload,来进行主动调用。

function activeinvokejavafunc(content){
    Java.perform(function () {
        Java.enumerateClassLoadersSync().forEach(function(classloader){
            console.log(classloader);
        });
        
        Java.enumerateClassLoadersSync().forEach(function(classloader){
            try {
                console.log(classloader);
                classloader.loadClass("com.kanxue.encrypt01.MainActivity");
                Java.classFactory.loader = classloader;

                // com.kanxue.encrypt01.MainActivity
                var mainActivityClass = Java.use("com.kanxue.encrypt01.MainActivity");
                console.log("mainActivityClass: ", mainActivityClass);

                // public String caicaikan(String content)
                Java.choose("com.kanxue.encrypt01.MainActivity",{
                    onMatch:function(obj){
                        console.log("found obj: ", obj);
                        var result = obj.caicaikan(content);
                        console.log("result: ", result);
                    },onComplete:function(){
                        console.log("search heap complete");
                    }
                })
            } catch (error) {
                console.log("error: ", error);
            }
        })
    })
}

总结:针对函数进行重放攻击

  1. 针对java实现的函数,可以简单编写frida脚本或者xposed插件对函数进行主动调用
  2. 针对so中的函数,推荐使用frida脚本进行主动调用,较为简单快捷;当然xposed也可以
  • 标题: 密码学初步认识
  • 作者: xiaoeryu
  • 创建于 : 2024-02-13 16:38:12
  • 更新于 : 2024-02-17 19:02:09
  • 链接: https://github.com/xiaoeryu/2024/02/13/密码学初步认识/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论