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

简介: 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月前
|
JSON 监控 数据格式
1688 item_search_app 关键字搜索商品接口深度分析及 Python 实现
1688开放平台item_search_app接口专为移动端优化,支持关键词搜索、多维度筛选与排序,可获取商品详情及供应商信息,适用于货源采集、价格监控与竞品分析,助力采购决策。
|
1月前
|
缓存 监控 Android开发
京东 item_get_app 接口深度分析及 Python 实现
京东item_get_app接口可获取商品原始详情数据,包含更丰富的字段和细节,适用于电商分析、价格追踪等场景。需通过认证获取权限,支持字段筛选和区域化数据查询。
|
2月前
|
缓存 数据挖掘 API
淘宝 item_get_app 接口深度分析及 Python 实现
淘宝item_get_app接口是淘宝开放平台提供的移动端商品详情数据获取接口,相较PC端更贴近APP展示效果,支持获取APP专属价格、促销活动及详情页结构,适用于电商导购、比价工具、数据分析等场景。接口采用appkey+appsecret+session认证机制,需申请相应权限。本文提供Python调用示例及使用注意事项,帮助开发者高效对接移动端商品数据。
|
4月前
|
JavaScript
TypeOrmModule 从 app.module.ts 抽离到 database.module.ts 后出现错误的原因分析
本文分析了TypeORM实体元数据错误的成因,主要涉及实体注册方式、路径解析差异及模块结构变化导致的关系解析问题,并提供了具体解决方案和最佳实践建议。
132 56
|
1月前
|
缓存 供应链 开发者
1688 item_get_app 接口深度分析及 Python 实现
1688平台item_get_app接口专为移动端设计,提供商品原始详情数据,包含批发价格、起订量、供应商信息等B2B特有字段,适用于采购决策、供应链分析等场景。接口需通过appkey+access_token认证,并支持字段筛选,返回结构化数据,助力企业实现智能采购与供应商评估。
|
2月前
|
数据采集 数据可视化 API
驱动业务决策:基于Python的App用户行为分析与可视化方案
驱动业务决策:基于Python的App用户行为分析与可视化方案
|
6月前
|
数据采集 数据可视化 数据挖掘
基于Python的App流量大数据分析与可视化方案
基于Python的App流量大数据分析与可视化方案
|
11月前
|
开发框架 监控 .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
186 5

热门文章

最新文章