App逆向百例|11|某咖啡App参数分析

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: App逆向百例|11|某咖啡App参数分析

观前提示:

本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除

样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMXk0UVJ3UWVaNVJiVHBfbTBRQTFyUkE/cHdkPWxpbm4=

0x1 准备工作

将样本App拖入JADX 发现有360的壳

使用FRIDA-DEXDUMP脱壳报错

经了解是双进程保护

解决方法就是使用spawn的方式就能成功hook上

App还拥有证书验证 Pass掉后即可正常抓包

本次的受害参数为signq

用JADX打开脱好的DEX 直接定位到加密位置

0x2 sign

通过上面的位置最终定位到

可以看到sign的计算最终是调用so层的md5_crypt方法

往上看加载so的文件名通过ApplicationC1081StubApp.getString2给加密了

主动写个call方法调用

function StubApp(str){
    Java.perform(function () {
        var Class = Java.use('com.stub.StubApp');
        var Method = "getString2";
        var result = Class[Method](str);
        console.log(result);
    })
}

可以看到最终加载的so是cryptoDD

顺便frida hook入参和结果

function hook_a(){
    Java.perform(function () {
        var Class = Java.use('com.xxx.safeboxlib.CryptoHelper');
        var Method = "a";
        Class[Method].overload('java.lang.String').implementation = function () {
            var result = this[Method]['apply'](this,arguments);
            console.log('----------------------');
            console.log('arg1:'+arguments[0]);
            console.log('arg2:'+arguments[1]);
            console.log('result:'+result);
            console.log('----------------------');
            return result;
        }
    })
}

得到入参和结果方便分析

arg1:cid=210101;q=8EPO3SflRwrFi_pnFiANIO_hF_ztWu_3IwwLHqKqsId6ZW44ZebJ_ymIAGvQXtvRUHc4gsPQZOlbO0gH2GW6yAM66qNVLgAzyt-U8r9_sbGSUyrnslFmwLWcRHvFiYDX;uid=28167131-ac1d-4b66-810e-5071d8868fc61663316597190
arg2:1
result:1654061269899483325923884626986060448

可以看到这个结果的长度为37并非MD5输出的32位 还需要在so里面分析

将so文件拖入IDA 搜索导出表

发现并非静态注册

那就直接上Unidbg吧 也方便了后续调试

先把轮子复制粘贴过来 而且并不用补环境

public class luckin extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private String dfastring;
    luckin() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xxx.xxx").build();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\xxx\\xxx\\xxx.apk"));
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\xxx\\xxx\\libcryptoDD.so"), true);
        module = dm.getModule();
        vm.setJni(this);
        vm.setVerbose(true)
        dm.callJNI_OnLoad(emulator);
    }
    public static void main(String[] args) {
        luckin luckin = new luckin();
    }
}

得到动态注册的偏移地址后在IDA上跳转过去跳转过来后可以看到视图这密密麻麻的区块 可以确定是一个ollvm

看伪代码看到一堆控制流 并不好找出加密的关键位置

这时刚刚造好的Unidbg就派上用场了 主动调用md5_crypt

public void md5_crypt() {
    List<Object> args = new ArrayList<>(10);
    args.add(vm.getJNIEnv());
    args.add(0);
    byte[] input = "cid=210101;q=8EPO3SflRwrFi_pnFiANIO_hF_ztWu_3IwwLHqKqsId6ZW44ZebJ_ymIAGvQXtvRUHc4gsPQZOlbO0gH2GW6yAM66qNVLgAzyt-U8r9_sbGSUyrnslFmwLWcRHvFiYDX;uid=28167131-ac1d-4b66-810e-5071d8868fc61663316597190".getBytes(StandardCharsets.UTF_8);
    args.add(vm.addLocalObject(new ByteArray(vm, input)));
    args.add(1);
    Number number = module.callFunction(emulator, 0x1a981, args.toArray());
    Inspector.inspect((byte[]) vm.getObject(number.intValue()).getValue(), "md5_crypt");
}

输出结果与hook的结果一致

根据输出的日志直接跳转到SetByteArrayRegion调用处

跳转过来后可以看到 加密逻辑在doMD5sign

看上面的伪代码可知传入的initial_msg还拼接了一个长度为20的字符串

盲猜后面部分dJLdCJiVnDvM9JUpsom9就是盐

进入doMD5sign继续观察

这里并没有经过ollvm污染 逻辑很明显 其中第一行就是MD5 让我们验证一下是否为无魔改的MD5

同样的hook住md5输出mr2的返回结果

通过对比 结果一致 所以这就是一个无魔改的MD5

拿到MD5后经过四次bytesToInt最后将所有返回的结果拼接在一起返回

进入bytesToInt能看到这里还是被污染了 但是不要慌

这里返回的是v9 而v9的赋值处仅仅只有一处

所以剩下的根据伪代码逻辑照搬翻译即可

0x3 q

通过上面的代码 最终定位到

老规矩先hook入参和结果

arg1:{"supportTakeout":"1","implSource":"1","latitude":"23.099416","deptId":"324775","appversion":"4930","longitude":"113.477171"}
arg2:0
arg3:uATCFcK8LrUJHq4kOVZ8wvTMgcA4hx57kPtQeMgFKtaNn1swuJCl3QTm1P9xOnKNzTwzVjBK4y7WYDx2M4uexlld2rupEImTvN1Z9AWiFs-5C--eNSnif7SsT-yaUqQstV5SyB_woZdtCSi6NFirksZMEAuA8_nCcBlVjw5JB0w=

使用Unidbg主动调用

public void localAESWork4Api() {
    List<Object> args = new ArrayList<>(10);
    args.add(vm.getJNIEnv());
    args.add(0);
    byte[] input = "{\"supportTakeout\":\"1\",\"implSource\":\"1\",\"latitude\":\"23.099416\",\"deptId\":\"324775\",\"appversion\":\"4930\",\"longitude\":\"113.477171\"}".getBytes(StandardCharsets.UTF_8);
    args.add(vm.addLocalObject(new ByteArray(vm, input)));
    args.add(0);
    Number number = module.callFunction(emulator, 0x1b1cd, args.toArray());
    Inspector.inspect((byte[]) vm.getObject(number.intValue()).getValue(), "localAESWork4Api");
}

查看结果

对比一致

那接下来就可以为所欲为了

根据名字可以知道加密方式为AES 接着验证是CBC还是ECB模式

将input改为1234567890abcdef1234567890abcdef 运行观察结果

根据结果可以判断为ECB模式 后面部分应该是填充部分

知道了AES ECB 那就得找KEY了 回到IDA 跳转到过去

看到wbaes一般就是白盒aes了

进入android_native_wbaes_jni  可以看到ECB的猜想是正确的

进入wbaes_encrypt_ecb 主要的逻辑在aes128_enc_wb_coff

进入aes128_enc_wb_coff 这里一堆查表操作 基本实锤白盒了

白盒情况下 就是要找轮和state 采用DFA攻击 需要在第九轮时进行修改state

先造个前提 将input改为123456使其输出结果长度为32

然后记录下未进行攻击状态下的正确明文68fe8c552b93481754881068bbc3f96b

wbShiftRows处就是一个很好的位置

final Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base + 0x14F98+1, new BreakPointCallback() {//wbShiftRows
            int count = 0;
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                count += 1;
                System.out.println("count->" + count);
                return true;
            }
        });

根据hook结果输出完全符合AES的10轮

继续写hook 修改第九轮的state

final Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base + 0x14F98+1, new BreakPointCallback() {//wbShiftRows
    int count = 0;
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        count += 1;
        System.out.println("count->" + count);
        RegisterContext context = emulator.getContext();
        final UnidbgPointer pointerArg = context.getPointerArg(0);
        //onleave
        debugger.addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                if (count % 9 == 0) {
                    pointerArg.setByte(randInt(0, 15), (byte) randInt(0, 0xff));//随机更改0-15的位置 然后附上0-0xff的差异值
                }
                return true;
            }
        });
        return true;
    }
});

运行得到一个错误密文

对比可以看到部分值被修改 此时我们就完全了一次DFA攻击

68fe8c552b93481754881068bbc3f96b//正确的
68b18c55ef93481754881019bbc3e46b//DFA攻击后的

单凭一次攻击并不能拿到KEY 这时Unidbg的好处就出来了 我们可以N次调用

这里我调用了300次 得到一堆错误密文再加上一个正确的密文 放到phoenixAES跑出第十轮的KEY

得到第十轮的KEY后就能使用aes_keyschedule逆推出KEY

Github https://github.com/SideChannelMarvels/Stark

最终算出AES的KEY为644A4C64434A69566E44764D394A5570

验证结果 结果一致 大功告成

相信有的人看到这里会出现一堆问号吧 为什么要这样做 为什么选择第九轮 为什么修改state 为什么能算出来KEY 这里 我就要推一推白龙的星球了

里面的白盒专题看完学完 让你解决以上疑惑 让我们一起来星球里面催龙龙更新吧!




感谢各位大佬观看

感谢大佬们的文章分享

如有错误 还请海涵

共同进步 带带弟弟

[完]

相关文章
|
1月前
|
UED
|
8天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
2月前
|
安全
【Azure App Service】App service无法使用的情况分析
App Service集成子网后,如果子网网段中的剩余IP地址非常少的情况下,会在App Service实例升级时( 先加入新实例,然后在移除老实例 )。新加入的实例不能被分配到正确的内网IP地址,无法成功的访问内网资源。 解决方法就是为App Service增加子网地址, 最少需要/26 子网网段地址。
|
3月前
【Azure Function App】本地运行的Function发布到Azure上无法运行的错误分析
【Azure Function App】本地运行的Function发布到Azure上无法运行的错误分析
|
3月前
|
Java
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
|
3月前
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
|
3月前
|
缓存 前端开发 Java
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
|
3月前
|
C# 开发工具
【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码渐入最源端
【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码渐入最源端
|
3月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)

热门文章

最新文章