客路旅行X-Signature协议分析
客路旅行X-Signature协议分析
环境
app:4.2.2
Java层
抓包
image-20211222093844019jadx搜索X-Signature
com.klook.util.SignatureUtil -> sign
最终是调用了native函数sign
。
objection看看输入输出。
android hooking watch class_method com.klook.util.SignatureUtil.sign --dump-args --dump-return
image-20211222093815324
image-20211222093918533
可以看出字节数组是由X-TimeStamp
,X-Platform
,X-DeviceID
,_pt
,url
,body
构成的,第二个则是固定字符串。
so层
ida函数窗口搜索Java
打开Java_com_klook_util_SignatureUtil_sign
修改a1
为JNIEnv *a1
从名字看,j_klook_api_sign
应该就是加密函数,进入看看
可以看到最后是调用了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
。
参数数量对不上,返回j_hmac_sha256
更新一下
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)验证一下
完全正确,接下来就是找hmac的key是怎么构成的。
image-20211222101309116可以看到v9
是由输入的前10位,也就是时间戳的前10位构成,然后从dword_62B0
取数据,构成v20
,作为hmac的key,看看dword_62B0
。
这是一个长度为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如果使用偏移来定位函数的话,可能会找不到函数,要注意这个问题
你可以直接分析64位的so,也可以找个只支持32位指令的手机来运行app,这样一来,运行的就是32位的so。另外一种没试过的方法就是,解包后把64位的删了,重新打包安装,这样运行的也是32位的so。由于两个so实现的功能是一样的,在比较简单的情况下,你也可以分析32位的so,在手机运行64位的so,只不过hook函数的时候不要用偏移,而是直接找导出函数的地址。
代码仅供把玩。