客路旅行X-Signature协议分析

2021-12-22  本文已影响0人  ever_hu

客路旅行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函数的时候不要用偏移,而是直接找导出函数的地址。

代码仅供把玩。

上一篇下一篇

猜你喜欢

热点阅读