美文网首页
客路旅行X-Signature协议分析

客路旅行X-Signature协议分析

作者: ever_hu | 来源:发表于2021-12-22 12:50 被阅读0次

    客路旅行X-Signature协议分析

    环境

    app:4.2.2

    Java层

    抓包

    image-20211222093844019

    jadx搜索X-Signature

    image-20211222093052133 image-20211222093122444

    com.klook.util.SignatureUtil -> sign

    image-20211222093353491

    最终是调用了native函数sign

    objection看看输入输出。

    android hooking watch class_method com.klook.util.SignatureUtil.sign --dump-args --dump-return
    
    image-20211222093815324 image-20211222093918533

    可以看出字节数组是由X-TimeStampX-PlatformX-DeviceID_pturlbody构成的,第二个则是固定字符串。

    so层

    ida函数窗口搜索Java

    image-20211222094526499

    打开Java_com_klook_util_SignatureUtil_sign

    image-20211222094618775

    修改a1JNIEnv *a1

    image-20211222094744544

    从名字看,j_klook_api_sign应该就是加密函数,进入看看

    image-20211222094837835 image-20211222094855452

    可以看到最后是调用了HMAC-SHA256

    frida hook klook_api_sign看看输入输出

    function dump(name, addr, length) {
        console.log("=============== " + name + " ====================");
        console.log(hexdump(addr, {length: length||32}));
        console.log("=================================================");
    }
    
    function hook_sign() {
        Interceptor.attach(Module.findExportByName("libklooknative.so", "klook_api_sign"), {
            onEnter: function(args) {
                console.log(args[0], args[1], args[2], args[3]);
                this.arg2 = args[2];
                dump("sign-arg0", args[0], parseInt(args[1]));
            },
            onLeave: function(){
                dump("sign-arg2-ret", this.arg2, 32);
            }
        })
    }
    
    Java.perform(function() {
        hook_sign();
    })
    
    image-20211222095639909

    可以看到输入就是Java层的字节数组,输出就是X-Signature,这样看来,Java层的第二个参数好像没有参与运算。

    可以看到klook_api_sign主要调用了j_hmac_sha256

    image-20211222100159069 image-20211222100219932

    参数数量对不上,返回j_hmac_sha256更新一下

    image-20211222100342796

    hook一下hmac_sha256

    function hook_hmac() {
        Interceptor.attach(Module.findExportByName("libklooknative.so", "hmac_sha256"), {
            onEnter: function(args) {
                this.arg4 = args[4];
                dump("hmac-arg0", args[0], parseInt(args[1]));
                dump("hmac-arg2", args[2], parseInt(args[3]));
            },
            onLeave: function() {
                dump("hmac-arg4-ret", this.arg4, 32);
            }
        })
    }
    
    image-20211222100610956

    猜测arg0就是hmac的key,arg2则是hmac的输入,在CyberChef (gchq.github.io)验证一下

    image-20211222101014328

    完全正确,接下来就是找hmac的key是怎么构成的。

    image-20211222101309116

    可以看到v9是由输入的前10位,也就是时间戳的前10位构成,然后从dword_62B0取数据,构成v20,作为hmac的key,看看dword_62B0

    image-20211222101623553

    这是一个长度为128的DWORD数组。然后根据上面的逻辑实现一下即可。

    代码实现

    import hashlib
    import hmac
    import struct
    
    arr = [
        0x8E15506B, 0xD980B378, 0x27E3D7E5, 0xBC15443B, 0xEC1EEC4,
        0x3ED623EF, 0xFF8E4274, 0xDFFAB58C, 0xC4851CFF, 0xB3AFAE51,
        0x97FB2DA, 0x65A03E6B, 0x592A9E39, 0x2BE64CC6, 0x5C1DE8D2,
        0x53B3E84B, 0x7BCCB1C5, 0xD6F0E0C, 0x6FC3AC33, 0x17AECBBD,
        0x29BC094A, 0x9BD5D76E, 0x717F4A9A, 0x6DDC6D95, 0xDD809DF0,
        0x4CD0E0C4, 0x8B231AAA, 0xA2B8F4AA, 0x53D9D857, 0x3D019C3D,
        0xB843E6E, 0x40004DF2, 0xE111BEF5, 0xC28624FD, 0xAA1976B,
        0xC6F7110F, 0x9B17E089, 0x86C4532E, 0xC1C29DE8, 0xD431594D,
        0xC32ACBB, 0x5523BF09, 0xAE6F8B5C, 0x36D2438A, 0x9AC31A58,
        0xDE199732, 0x9008A431, 0x21266C7, 0xA73AA9FE, 0x2178AF13,
        0x51F9FC55, 0x2B5690B6, 0x6C170F6, 0x2B865E52, 0x2B3E4DCC,
        0x1593CE9C, 0x9FC0C61E, 0x5617344E, 0xB6FA2ECB, 0xBCE78DF4,
        0x7BCFAADE, 0x7CE3A92C, 0xE0459893, 0xB8976C37, 0x8A84631D,
        0x74CD6D32, 0x7D4E5D0C, 0x56817C7E, 0x4F0CDEA5, 0xFE26A322,
        0x7907953E, 0x65596DC4, 0x1B02B504, 0xEC2C21CC, 0x639971FA,
        0xC5B2F4B2, 0x2FB5945C, 0xF6BBAFFA, 0xA1D32656, 0x25D0BA3C,
        0xDB61E15B, 0x6B263FDF, 0xFC795C9A, 0x86EC6D48, 0xF1BE0988,
        0x60AD9415, 0x6E169725, 0x6D0AD37A, 0x4EE31667, 0xD4A93A17,
        0xDA9651E1, 0x7E43E9F5, 0x3AD74DE6, 0x993A9A72, 0xABF8DFB3,
        0x935B8B9A, 0x385D4FB7, 0xD1C4EE8, 0x825D18E3, 0xCD6B8119,
        0xD665130D, 0x4875159D, 0x2BCAAC7D, 0xD8EF327B, 0x477113D9,
        0x91456F5, 0x21384F17, 0x9B96E12D, 0xC2E7AFBA, 0x75A824C2,
        0xCF29723C, 0x53F85CD8, 0xBBE45D3D, 0x205ACDBE, 0x9FA19C16,
        0x63054581, 0x896AB38B, 0x4296BE90, 0x1EB177FD, 0xA3F4639A,
        0x7482991A, 0x62FE098F, 0x455CD5C9, 0x97B77BC4, 0xFCDAA11A,
        0x597F2D13, 0x3E7CBF9F, 0x7F0FE0C3,
    ]
    
    def calc_hkey(v9):
        mkeys = [
            arr[(v9 % 0x409) & 0x7f],
            arr[(v9 % 0xceb) & 0x7f],
            arr[v9 & 0x7f],
            arr[v9 & 0x7f ^ 0x7f],
            arr[(v9 % 0x177b) & 0x3f],
            arr[(v9 % 0x178d) & 0x3f | 0x40],
            arr[(v9 % 0x25d9) & 0x3f | 0x40],
            arr[(v9 % 0x26b3) & 0x7f],
        ]
        hkey = struct.pack('<8I', *mkeys)
        return hkey
    
    
    def calc_sign(ts, platform, device_id, pt, url, body=''):
        hkey = calc_hkey(int(ts[:10]))
        data = ''.join((ts, platform, device_id, pt, url, body))
        sign = hmac.new(hkey, data.encode(), hashlib.sha256).hexdigest()
        return sign
    
    def test():
        ts = '1640136972383'
        platform = 'android'
        device_id = '57ebd2fd089098e5459bca2017de2330'
        pt = '57ebd2fd089098e5459bca2017de2330'
        url = '/rest/push.json?_t=1640136989733'
        body = 'uuid=008a91521a46e595bf11baf856d15464&cid=008a91521a46e595bf11baf856d15464&type=2'
        sign = calc_sign(ts, platform, device_id, pt, url, body)
        print(sign)
    
    if __name__ == '__main__':
        test()
    
    image-20211222102218957

    unidbg实现

    package com.klook;
    
    import com.github.unidbg.AndroidEmulator;
    import com.github.unidbg.Module;
    import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
    import com.github.unidbg.linux.android.AndroidResolver;
    import com.github.unidbg.linux.android.dvm.*;
    import com.github.unidbg.memory.Memory;
    
    import java.io.File;
    import java.nio.charset.StandardCharsets;
    
    public class KLook extends AbstractJni {
        private final AndroidEmulator emulator;
        private final VM vm;
        private final Module module;
    
        public static String pkgName = "com.klook";
        public static String apkPath = "unidbg-android/src/test/java/com/klook/kelulvxing422.apk";
        public static String soPath = "unidbg-android/src/test/java/com/klook/libklooknative.so";
    
        public KLook() {
            emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
            Memory memory = emulator.getMemory();
            memory.setLibraryResolver(new AndroidResolver(23));
            vm = emulator.createDalvikVM(new File(apkPath));
            vm.setJni(this);
            vm.setVerbose(true);
            DalvikModule dm = vm.loadLibrary(new File(soPath), true);
            dm.callJNI_OnLoad(emulator);
            module = dm.getModule();
        }
    
        public void call_sign() {
            DvmClass clz = vm.resolveClass("com/klook/util/SignatureUtil");
            StringObject ret = clz.callStaticJniMethodObject(emulator, "sign([BLjava/lang/String;)Ljava/lang/String;",
                    "1640136972383android57ebd2fd089098e5459bca2017de233057ebd2fd089098e5459bca2017de2330/rest/push.json?_t=1640136989733uuid=008a91521a46e595bf11baf856d15464&cid=008a91521a46e595bf11baf856d15464&type=2".getBytes(StandardCharsets.UTF_8),
                    "kMtbID/p1?eWAsQ+5A3g=");
            System.out.println(ret.getValue());
        }
    
        public static void main(String[] args) {
            KLook test = new KLook();
            test.call_sign();
        }
    }
    
    image-20211222103801567

    其他

    image-20211222113028581

    由于这个apk支持64位指令集,所以如果你的手机也支持64位指令的话,那么会优先使用v8a的so文件。这时候frida hook如果使用偏移来定位函数的话,可能会找不到函数,要注意这个问题

    image-20211222113324263 image-20211222113408919 image-20211222114945436

    你可以直接分析64位的so,也可以找个只支持32位指令的手机来运行app,这样一来,运行的就是32位的so。另外一种没试过的方法就是,解包后把64位的删了,重新打包安装,这样运行的也是32位的so。由于两个so实现的功能是一样的,在比较简单的情况下,你也可以分析32位的so,在手机运行64位的so,只不过hook函数的时候不要用偏移,而是直接找导出函数的地址。

    代码仅供把玩。

    相关文章

      网友评论

          本文标题:客路旅行X-Signature协议分析

          本文链接:https://www.haomeiwen.com/subject/lueqqrtx.html