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 这里 我就要推一推白龙的星球了

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




感谢各位大佬观看

感谢大佬们的文章分享

如有错误 还请海涵

共同进步 带带弟弟

[完]

相关文章
|
2月前
|
移动开发 小程序 开发工具
微信支付的类型分析(JSAPI+APP+H5+NATIVE+付款码+合单)
微信支付的类型分析(JSAPI+APP+H5+NATIVE+付款码+合单)
104 1
|
2月前
|
数据采集 JSON 算法
使用Python爬取华为市场APP应用进行分析
这个网站也是作者最近接触到的一个APP应用市场类网站。讲实话,还是蛮适合新手朋友去动手学习的。毕竟爬虫领域要想进步,还是需要多实战、多分析!该网站中的一些小细节也是能够锻炼分析能力的,也有反爬虫处理。甚至是下载APP的话在Web端是无法拿到APK下载的直链,需要去APP端接口数据获取
|
1月前
4. 解决uni-app开发过程中view、image等标签出现诸如“出现错误:类型“{ class: string; }”的参数不能赋给类型“.......”
4. 解决uni-app开发过程中view、image等标签出现诸如“出现错误:类型“{ class: string; }”的参数不能赋给类型“.......”
85 0
|
3月前
|
数据采集 小程序 网络安全
云擎技术---分析工信部APP备案的“传闻”
APP备案并非新事物,自2005年起已有非经营性互联网信息服务备案制度。备案针对的是网站主办者,而非用户,不涉及个人用户网络访问。网络接入服务提供者包括ISP和IDC,不限于三大运营商。通知要求不为未备案网站提供接入,但不影响国外软件使用。个人开发者不能涉及经营性内容,备案审核时长1-20个工作日。境内服务器和国内应用商店需备案,境外则无需。手机厂商不会开启白名单制,仅实行黑名单制。APP备案与民营经济发展壮大意见不冲突,工信部有权颁布相关规定。该政策不存在逐步试探底线情况,所有解读均有法律依据。
88 3
云擎技术---分析工信部APP备案的“传闻”
|
3月前
|
移动开发 JavaScript 前端开发
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
APP的HTML5页面经过运营商网络被植入手机管家问题及分析,解决方案见新文章
39 0
|
3月前
|
网络协议 算法 Android开发
安卓逆向 -- 实战某峰窝APP(动态分析)
安卓逆向 -- 实战某峰窝APP(动态分析)
85 4
|
3月前
|
算法
某圈app算法分析
某圈app算法分析
45 0
|
3月前
|
算法 安全 数据安全/隐私保护
某影视APP算法逆向分析
某影视APP算法逆向分析
43 0
|
3月前
|
算法 Java
某江app算法分析
某江app算法分析
26 0
|
3月前
|
算法 数据挖掘 数据安全/隐私保护
某合伙人app算法分析
某合伙人app算法分析
34 0

热门文章

最新文章