Android应用的白盒加密简单介绍


0x00 背景


白盒攻击
白盒攻击者对终端具有完全控制的能力,能够观测和修改程序运行时的内部数据,包括内存信息,磁盘的读写权限等。在程序运行时就可以dump出内存中的数据,从而使运行在内存中的密钥不再安全。这种攻击环境称为白盒攻击。

像拥有最高权限的Android、iOS等设备(Root或者越狱后)的情况下就是一个白盒攻击环境。
一般常用的白盒攻击分析工具有JEB,IDA Pro,OllyDbg,VMware,Hopper等

白盒密码
白盒密码是指能够在白盒环境下抵御攻击的一种特殊的加密方法。它的目是不在运行环境中出现完整的密钥。为此,需要对密钥本身做尽量复杂的混淆,同时提供函数来处理。

举个例子,比如明文密钥是abcd,混淆后的密钥变成了非常复杂的矩阵,根据算法,每次生成的4个中间密钥,aefg,hbigk,lmcn,opqrstd,根据某个算法,用这些密钥生成4个密文,然后在把4段密文用算法生成一段密文,如此可以不断重复,直到最后生成密文。整个过程中最原始的密钥一直没有出现在内存中,白盒攻击想拿到密钥难度就大大增加了。

0x01 移动端存储的痛点


在业务中,可能需要面对在 Android 本地存储密钥、用户 token、email 等敏感数据。而移动App本身是运行在一种白盒攻击的环境,可以调试,dump内存,二次修改,抓数据包分析等等。App里面的敏感信息,本地数据和传输数据的容易被逆向获取。

1.1 移动端常见的几种密钥不安全存储的方式:
1、密钥直接硬编码在Java代码中,很容易被逆向成java代码。
2、密钥直接明文存在私有目录的sharedprefs文件中,root机器很容易就导出查看。
3、将密钥分成不同的几段,有的存储在文件中、有的存储在代码中,最后将他们拼接起来,可以将整个操作写的很复杂,这因为还是在java层,逆向者只要花点时间,也很容易被逆向。
4、用ndk开发,将密钥放在so文件,加密解密操作都在so文件里,这从一定程度上提高了的安全性,挡住了一些逆向者,但是有经验的逆向者还是会使用IDA破解的。
5、在so文件中不存储密钥,so文件中对密钥进行加解密操作,将密钥加密后的密钥命名为其他普通文件,存放在assets目录下或者其他目录下,接着在so文件里面添加无关代码(花指令),虽然可以增加静态分析难度,但是可以使用动态调式的方法,追踪加密解密函数,也可以查找到密钥内容。
1.2 如何解决密钥安全存储这个问题呢?
Android提供了秘钥库系统:
Android 系统有安全机制Keystore 可以保护密钥材料免遭未经授权的使用。
https://developer.android.com/training/articles/keystore.html?hl=zh-cn

高版本的硬件安全模块TEE
运行 Android 9(API 级别 28)或更高版本的受支持设备可拥有StrongBox Keymaster,它是位于硬件安全模块中的 Keymaster HAL 的一种实现。包含以下组成部分:

自己的 CPU。
安全存储空间。
真实随机数生成器。
可抵御软件包篡改和未经授权旁加载应用的附加机制。
硬件加密破解强度高,且支持算法种类多。

但是低版本的Android Keystore有明显的不足之出:

在 Android 4.3(API 级别 18)中引入的 AndroidKeyStore 提供程序。调用的API有KeyStore 和 KeyPairGenerator 或 KeyGenerator 类等。但是支持的算法种类偏少。KeyStoreEncryDemo :demohttps://github.com/zhibuyu/KeyStoreEncryDemo

此种安全机制密钥文件还是存储在/data/misc/keystore/user_0/目录下,容易被破解和利用。

比如当攻击者有root权限的时候,可通过cp,chown命令复制其他APP的密钥,只需要把UID改成自己APP的UID,就可以盗用其他APP的密钥了。

破解案例见:利用root权限盗用AndroidKeystore中其他APP的密钥:
https://bbs.pediy.com/thread-222890.htm

业内最佳解决方案
由于Android Keystore支持算法相对较少,且无法对低版本的Android设备进行降级支持,所以在业务的使用中无法直接使用。
而在对外业务上存储密钥暴露的这个问题基本无解,所以一般公司常选择白盒加密配合加固和OLLVM混淆等方案提升攻击者的逆向成本。

0x03 白盒加密技术简介


白盒加密技术需要抵抗白盒攻击,核心思想是把秘钥隐藏起来, 加密执行过程中, 内存中不会出现秘钥的值.

白盒密码构造白盒密码算法有两种策略,标准密码算法白盒化和构造全新密码算法。现在通用的技术是查找表技术, 即把秘钥隐藏在查找表中。

标准密码算法白盒化,在标准密码算法安全理论的基础上和不改变原算法功能的前提下,将原有密码算法通过白盒密码技术进行设计,在白盒攻击环境下能够有效保证其密钥安全。
构造全新密码算法,新算法密码分析结果不能弱于标准密码算法同时具备抵抗白盒攻击的能力。
0.密码学简介
密码算法
用于解决复杂问题的步骤,通常称为算法(algorithm)。从明文生成密文的步骤,也就是加密的步骤,称为加密算法,而解密的步骤则称为解密算法。加密、解密的算法合在一起统称为密码算法。

密钥
密码算法中需要密钥(key)。根据柯克霍夫原则,密码算法是标准的,而密钥是需要保密的,因此密钥的安全对密码系统至关重要。

对称密码算法
通信双方共享一个密钥,用于加密任意大小的数据块或数据流的内容,包括消息、文件、加密密钥和口令。常用对称密码算法有DES、AES。

非对称密码算法
非对称密码又称公钥密码,加密和解密分别使用不同的密钥,即私钥和公钥。公钥密码算法多用于加密小的数据块,如加密密钥或者数字签名中使用的Hash函数值。常用公钥密码算法包括RSA、ECC。

密码协议
密码协议是指针对密码算法的应用。常见协议有SSL/TLS、Https。

其它密码技术
hash函数、MAC、数字签名、数字证书、随机数生成器。

开发人员实用密码学: https://cryptobook.nakov.com/

1.白盒密码实现


白盒密码技术从实现方式上可以分为两类:静态白盒和动态白盒。

静态白盒是指密码算法结合特定的密钥经过白盒密码技术处理后形成特定的密码算法库,称为白盒库,白盒库具备特定的密码功能(加密、解密以及加解密),并能在白盒攻击环境下有效保护原有密钥的安全。静态白盒更新密钥,需要重新生成白盒库。

动态白盒是指白盒库生成后就不需要再更新,原始密钥经过同样的白盒密码技术转化为白盒密钥。白盒密钥传入相匹配的白盒库可以进行正常的加密或解密功能。白盒密钥是安全的,攻击者不能通过分析白盒密钥得到任何关于原始密钥的信息。

2.白盒加密案例分析


常见的白盒算法有:白盒AES,白盒SMS4。

白盒SMS4的简单实现见:https://github.com/ohhoo/White-box-Cryptographic/blob/master/wbSM4_Shang/wbSM4.cpp

白盒AES的简单实现见:https://github.com/Gr1zz/WhiteBoxAES

AES算是我们日常开发中最常用一种对称加密算法了,主要由如下四个过程实现:

1.S盒字节替换 (SubBytes)
2.行移位(ShiftRows)
3.列混淆(MixColumns)
4.轮秘钥加 (AddRoundKey)
AES-128一般通过10轮转换,加解密过程如下:


3.白盒加密对抗思路
3.1 直接黑盒调用二进制
对抗加密不一定非得逆向出算法,可以通过黑盒直接调用的方式。拿到能加密签名后的结果即可。尤其是上了o-llvm混淆加密算法,调试及逆向成本太大,一般通过Xposed,frida反射签名函数搭建RPC服务亦或者通过unicorn模拟器执行。

一、frida hook 黑盒直接调用

调用案例:https://github.com/zhaoboy9692/flask_frida_rpc

Frida代码hook饿XX的加密类函数分析:

var result;

function callEleMeFun(url_path) { //定义导出函数
    Java.perform(function () {
        var a = Java.use('class');//这边改了,需要自己去找饿XX的加密类
        var context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
        var res = a.sneer(context, "4f93e640-5c6f-4980-bd3d-c1256672a64d", url_path, "a");
        result = {"ex_r": res[0], "ex_dc": res[1], "ex_d": res[2]};
    });
    return result;
}

//frida提供的rpc服务,暴露签名函数
rpc.exports = {
    callsecretfunctioneleme: callEleMeFun,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
二、模拟执行xposed + server

以前黑盒调用一般由AndServer+Service 打造 Android 服务器实现 so 文件调用:https://links.jianshu.com/go?to=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FDo4rGrMNFGx5HqFKM9vA1g
一般需要xposed hook调用函数或者dlopen打开关键so并调用特定函数调用,且需要使用Andserver搭建server做加签名的服务。

而基于长链接和代码注入的Android private API暴露框架sekiro已搭建好基础的暴露服务我们仅需几行代码就可轻松实现一个加密服务:https://github.com/virjar/sekiro

案例代码:

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class HookMain implements IXposedHookLoadPackage {
    public static XC_LoadPackage.LoadPackageParam loadPackageParam = null;

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.packageName.equals("com.ss.XXXXX")) {
            HookMain.loadPackageParam = lpparam;
            try {

                // 在com.ss.android.ugc.aweme.splash.SplashActivity -> onCreate 注入服务

                XposedHelpers.findAndHookMethod("com.ss.XXXXX", lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        super.afterHookedMethod(param);
                        final SekiroClient sekiroClient = SekiroClient.start("sekiro.virjar.com", "client-6", "group-6");//新建Sekiro客户端
                        sekiroClient.registerHandler("user_search", new UserSearchHandler());//注册并启动服务
                        XposedBridge.log("=========== sekiro服务启动成功 ===========");
                    }
                });
            } catch (Exception e) {
                XposedBridge.log("=========== Sekiro服务启动失败 ===========");
            }

        }
    }
}
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
关键在这两句:

                        final SekiroClient sekiroClient = SekiroClient.start("sekiro.virjar.com", "client-6", "group-6");//新建Sekiro客户端
                        sekiroClient.registerHandler("user_search", new UserSearchHandler());//注册并启动服务
                        XposedBridge.log("=========== sekiro服务启动成功 ===========");
1
2
3
新建Sekiro客户端,再注册handler处理函数服务便会自动启动。

import com.example.demo.hook.HookMain;
import com.google.gson.Gson;
import com.virjar.sekiro.api.SekiroRequest;
import com.virjar.sekiro.api.SekiroRequestHandler;
import com.virjar.sekiro.api.SekiroResponse;

import de.robv.android.xposed.XposedHelpers;

public class DouYinUserSearchHandler implements SekiroRequestHandler {


    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        Gson gson = new Gson();

        String query = sekiroRequest.getString("query");
        String start = sekiroRequest.getString("start", "0");
        String count = sekiroRequest.getString("count", "10");

        long param2 = Long.parseLong(start);
        int param3 = Integer.parseInt(count);

        if (query == null || query.equals("")) {
            sekiroResponse.send("请传入必需参数:query");
        }

        Class<?> SearchApi = XposedHelpers.findClass("com.ss.XXXXXX.discover.api.SearchApi", HookMain.loadPackageParam.classLoader);
        Object object = XposedHelpers.callStaticMethod(SearchApi, "a", query, param2, param3);
        sekiroResponse.send(gson.toJson(object));
    }
}
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
handler代码逻辑,通过API传入输入函数参数,反射调用要函数处理并返回结果。

即可通过http://sekiro.virjar.com/invoke?group=sekiro-6&action=XXX&query=XXX直接访问函数处理的结果。

三、模拟执行

通过跨平台模拟Android Native库函数: https://github.com/zhkl0228/unidbg
模拟执行这块细节可以通过之后的文章分析。

3.2 错误注入的破解原始密钥
核心思路:
quarkslab的这篇:https://blog.quarkslab.com/differential-fault-analysis-on-white-box-aes-implementations.html

原理:虽然白盒AES程序执行中不会出现密钥,但已出现通过中间秘钥逆推出原始密钥的工程 见:https://github.com/SideChannelMarvels/Stark
比如对于白盒的AES-128,提供一个回合密钥及其索引值即可逆推得到原始的加密密钥。


问题来了,那么如何拿到中间密钥呢?
即通过DFA
静态产生Fault数据方法得到一组数据,然后调用[phoenixAES]进行秘钥还原。(原谅我太菜,DFA具体的实现细节没太弄懂) 见:https://github.com/SideChannelMarvels/Deadpool/blob/master/README_dfa.md

phoenixAES算法见:https://github.com/SideChannelMarvels/JeanGrey/tree/master/phoenixAES

通过phoenixAES执行后一般得到最后一轮的密钥。然后使用上面的aes_keyschedule还原密钥。

破解案例:
可以看雪的师傅分享的这篇:https://bbs.pediy.com/thread-254042.htm
(不通过静态DFA,使用IDA 动态Patch的方式拿到Fault数据)
1.要通过DFA还原白盒AES的秘钥,其实只需要想办法构造一组合理Fault数据,后面的输入到phoenixAES的实现中,即可还原秘钥。

2.在构造Fault数据一般有两种方式:
* 官方给的静态更改的方式,这种方式好处是全自动化,不需要分析任何二进制代码。
* IDA 动态Patch的方式,这种需要分析代码,找准patch的时机。

3.在动态Patch过程中,注意观察输出数据,如果输出只有1字节改变,说明patch太晚,如果输出有16字节不同说明patch太早,如果输出刚刚为4字节,且4字节的位置也能符合规则,则说明时机找准了。
1
2
3
4
5
6
7
大佬们推荐的另一种解法:
抓取下来关键函数的二进制,之后用unicorn去跑,随机输入明文(密文),之后抓取内存和寄存器的变化,最后通过侧信道的方法去跑出密钥。

4.白盒密码优缺点
优点:

正是由于算法和密钥的合并,所有可以有效隐藏密钥,与此同时也混淆了加密逻辑。具体而言白盒加密的一种实现思路就是将算法完全用查表来替代,因为算法已知,加密的密钥已知。所以将算法和密钥固化成查表表示,这就是白盒密钥的实现过程。

缺点:

有利必有弊,白盒加密由于需要将加密和解密的算法固化到表格中,势必要增加空间开销,因为要保存这一系列的表。具体在实现的过程中,大部分情况需要用时间换空间,需要对具体的实现算法进行优化,也就是样将大表分解成若干个小表,增加了查表的次数即时间开销但是减少了表占用的空间。

参考:
https://www.zhihu.com/question/35136485/answer/84491440
Android纯本地安全存储方案: https://richardcao.me/2019/03/14/Android-Local-Security-Storage/
android自身提供的秘钥库系统:https://www.jianshu.com/p/dc5a9f906eb8
Android keystore漏洞: https://androidvulnerabilities.org/all
白盒密码技术:https://zhuanlan.zhihu.com/p/23295431
京东加固:https://aks.jd.com/doc/mobiledoc/chan-pin-jian-jie.html
简注 - A Tutorial on White-box AES: https://blog.wxk.me/2019/07/05/wbac_tutorial/
————————————————
版权声明:本文为CSDN博主「tangsilian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tangsilian/article/details/106178660

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值