对于某app生成xsign的方案
【方案一】脱机模拟执行生成xsign
- 优点:生成效率高,无需真机
- 缺点:绕过风控能力较弱
【方案二】基于真机改机和群控的RPC调用方式
- 优点:行为更拟人,绕过风控能力更强,也是之前给其他团队提供的Tiktok抓取商品的方案
- 缺点:需维护真机,稳定性稍弱
方案一
脱机模拟执行生成xsign调研xsign
具体表现为请求中的header字段x-sign,通过JNICLibrary#doCommandNative(70102,…)返回,输入参数例如
YtjwSHtVyiYDAL4DmOeGaaAI&&&23867946&08028451c53cb47dda9d3641248ec098&%s&mtop.lazada.detail.getdetailinfo&2.0&&600000@lazada_android_6.51.0&&&&&27&&&&&&&
各字段通过&分割,分别为utdid&&&appKey&x-t&api&appver&x-ttid&x-devid&x-features&
而且xsign的输入参数就是协议请求的参数,除了x-sign之外,有效的协议请求还包括了其他x系列header,其中需要动态计算的主要为x-umt/x-sgext/x-mini-wua/x-t,x-CID和x-c-traceid含义尚未明确,但不在优先考虑范围内。
x-t为秒级时间戳,会参与x-sign的计算,因此需要与输入参数中的x-t字段一致。
x-umt/x-sgext/x-mini-wua则为doCommandNative(70102,…)与xsign一起返回。
可以分三步:
- x-sign/x-mini-wua/x-umt/x-sgext模拟执行生成
- x-mini-wua参数构造
- x-umt参数构造
unidbg模拟执行生成xsign
x-sign主要为hmac和aes白盒的hash签名,目前已基于unidbg生成可用的xsign等header

在构造模拟执行环境过程中发现x-umt与x-mini-wua需要从外界com/alibaba/one/android/sdk/OneMain->play获取信息才能生成,OneMain#play方法又会进入到另一个JNI方法中。

重放测试发现,x-umt可固定不变,但x-mini-wua需要每次计算,同时若723456不设置正确的值或置空,则会生成短的x-mini-wua,会导致请求失败。
API入口:com.taobao.seccurity.LazadaXSign#generateXSign
具体调用方式可参考com.taobao.seccurity.LazadaXSign#main方法
x-mini-wua
x-mini-wua格式为HHnB_加一串base64,生成流程为:
- base64_decode(oneMain#play(61501799,723456))
- aes_decrypt
- xor
- aes_encrypt
- base64_encode
反混淆
1.类似这种修改PC跳转的,但是无法识别具体跳转到哪儿的可以在unidbg里下断点,查看PC寄存器的值,然后d命令查看:

2.遇到jumpout可以patch 对应的跳转指令为固定地址然后删除重建函数并反编译,IDA可识别部分JUMPOUT地址,若无法识别,需要动态调试获取对应地址。
3.遇到未识别为指令代码段的可以强制C成Code,然后根据堆栈回溯分析函数开始手动创建函数
因此oneMain#play(61501799,723456)的值为x-mini-wua的关键,目前尚未分析得到,不同sgmain版本中该值来源也不同,已知的有为saveWb接口返回值中的ee:
b'{"code":200,"data":{"ee":"M1gAoWrca8vx120N2zysp5nJDxvckXlhiZM2cntm/PuoxNy2svgDmAw1Baalg9ybqtSOngGCbTDIesksAS3CqV1g","trace_ip_addr":"0","rmdata":"eyJybWlkIjoibkI3MEFkYWpKdlIzaktSNHQxdFNsVnFqMmdteEdTWTVRNmxVTVlDWUVnST0iLCJiaW5hcnkiOiIwIiwidmVyc2lvbiI6ImQwNDJmODdhZjI3MzZmNDFlYTRkMjYwNWYxMTUzMTBkIn0=","um_changed":false,"exactid":"AAABgp/16kyqQqINsC3DVlg6Ze1PcpvKh5X5xAIcIHQAAAGC4a4JuWQSULHxYMbjWDpl7X133e6vR9EHtZvUeUs6BQEB","stid":"cM0A/INLPD/ddAOCWIzGIORAOmVPG/c8"},"message":"\xe5\xa4\x84\xe7\x90\x86\xe6\x88\x90\xe5\x8a\x9f","secevent":1101}\x04\x04\x04\x04'
x-umt
经分析,x-umt的取值逻辑与https://acs-m.lazada.sg/gw/mtop.alibaba.cro.umid.reped/1.0/
接口有关,在应用首次启动时,会发起多次reped请求,在该请求发送之前,x-umt值与utdid一致,在请求reped接口之后,x-umt方会改变。
get http request:Request{ url=https://acs-m.lazada.sg/gw/mtop.alibaba.cro.umid.reped/1.0/, method=POST, appKey=23867946, authCode=null, headers={appVersion=2, x-sgext=JAHF7KiWDJ8%2BAnUbh816dg%3D%3D, X-CID=858ff2b55155dc79|44084cfa-0b06-4959-aa1c-a133bb28c69f, x-i18n-regionID=sg, x-sign=azwfNj002xAAIT5n41qQ1fUyxhY%2BcT5hMxTjlxQqQDodUBRph%2FDsJ%2BIuj4O4aObssyXw3u5kw%2BMVAwodblB61Z8ln%2FE%2BcT5hPnE%2BYT, x-nettype=NET_NO, x-pv=6.3, x-nq=NET_NO, adid=, x-features=27, x-i18n-language=en-SG, x-app-conf-v=0, x-mini-wua=HHnB_GWv2mZrcyhfajE8L8b3rsPqWh8nKmV6lGbujqwXWpW0%3D, content-type=application/x-www-form-urlencoded;charset=UTF-8, x-t=1658805961, x-bx-version=6.5.4, f-refer=mtop, utdid=Yt9dzeT09CEDAL4DmOclT2bd, x-ttid=600000%40lazada_android_6.51.0, x-app-ver=6.51.0, x-c-traceid=null16588059612800001130996, x-umt=Yt9dzeT09CEDAL4DmOclT2bd, x-utdid=Yt9dzeT09CEDAL4DmOclT2bd, c-launch-info=0,0,1658805961279,1658805958940,0, x-appkey=23867946, x-umidtoken=Yt9dzeT09CEDAL4DmOclT2bd, user-agent=MTOPSDK%2F3.1.1.7+%28Android%3B10%3BGoogle%3BPixel%29}, body=mtopsdk.network.domain.ParcelableRequestBodyImpl@75e804a, seqNo=MTOP1, connectTimeoutMills=5000, readTimeoutMills=5000, retryTimes=1, bizId=0, env=0, reqContext=null, api=mtop.alibaba.cro.umid.reped}
07-26 11:26:01.411 30996 31138 I lazada : url:https://acs-m.lazada.sg/gw/mtop.alibaba.cro.umid.reped/1.0/
07-26 11:26:01.411 30996 31138 I lazada : content-type:application/x-www-form-urlencoded;charset=UTF-8,content:data={"request":"{\"sv\":\"2.0.77\",\"pt\":\"1\",\"os\":\"0\",\"e\":\"1000\",\"pv\":\"6.51.0\",\"lt\":\"1658805961\",\"nv\":\"3.0\",\"body\":\"msAMQG9KGcN0cPwYnHbrvowogcYMceFSU51iB14Uk7EGVdjlZO9WdZA/I2/J1SkXYOnYjX3gvoq4HLU+rZTxHRzPYjyWdfB69mUmpPUGuRW8JIyiY3ZbB4V2a5i5MqG8P4QYzq4NWxJeKHuUImjzWDDNf+MtWmKqPWuyfCtRF+Z+oFmiHUXHvHoDpF8PzLAVVC7D1E8+DEnIFAfJxRYi/dAF0+eqPhAh2m32ChDFWWM26LDQTAUxVWsdVvzARYjUF5xKwww2S/+9vtPCdMNi/dpRjiJLPyKGE/ZhZuiX+c+Z396msL1GFuMAoaqjbQ/bjMTMNuN3Fk5MkRcRw18OH3nJg0tdGUSIGcY9h0BksCauX5TrgBmZXKHyKxZOfNyWctAfhA0PKbj80tieF+XL35gbosInurvB4xnq/qJAl1zkeQiQsqhncVQasDszhW29j5rDxuPy5x0ZRFN50jBupba+9McPDKynUF6JRFruI0UtQv/UAZDqsGlpoQJ+qBk2xcQscmtIWp4nD+cffRhsQth4jmpkkTPW3Z54V/S8Wg+p5cXphZcd7Vjo24L+XeRRKYBj3B/19AoszfFfuFyjuO/s36SbbBLboq938ytU5cmD3IQzH/NXbAJtc37krW1q8ltrdb8OhyO2AwggWhY3v7baumk/p9EzeZlmr3/UV/yg8frGebuTOuJ16oMOD1Jin/oj7TOIE8L68CMLFTKqnFjbacqVZCuqUpXEeE9qSoRsMj2GlVk1SOBhIft9bTlaKktf4nu51tICkdlfHcUAqsngE2OyWRnSXVRWxyNuBJBf5xYpfdMiGPXbIRmYM8deX8bmZKDDJZB5GEUABCkt8AyQOcGiStCKOJUmEfJAibqmxNzEHt6nAK5r1ZnSORdGoZDHZwm5aRciIwfvX0aMnEJ2ljr+2AszeICR+2NvThiIhNOQFBMZBC2y9gGiQUuzrv2Qst7IPCZXFZdja5NJPeKaZO/TkkMpaCyFeloBECi+8JZEAZ8qVhig5WR7K7sjO7+mIGmUESVDfKMVwEn4HOMTuwCp1FtCa+7gbWedq0s3kKu1rQjLdelXMRCp+ooSIVJ5BbH7khe+eM4nqH4xzfdzaLa6RB2SxPPGrgLzQdWhiSfAKA0fKYzW0CUiwqxYyoBfPWraqZ9XyjhUa9nFEZshM7xDZAkT5xRSmeXSWRfbK7ThD/5J2b7MqUDJlVjYtEdX1qW2L0ujzeOSRpxCV49VxKvEQXFSMkNTjvsgXIGB3LAP2h3lln86kUblDJ5Udydow4dyLnuS8pUyIgnMDLmwXjZ1bTSVLQWyIKyb7XsU/nQt4eTWfxjfFAYE45qD69lT2qWkU1HZSDMnO1U6xAvRIbTRhtECr7qg3mlSOQWZtilCSGkVgrYcWGDSHWvgdNf5bULBrl1QX6zowbHMmDYjXT0H8t6mQvjFEavgJBE/uqx6/47fqlBupBJsX5ZAQ83bPqt4sNhTnCkRG5pJEQ==\",\"pn\":\"com.lazada.android\"}"} 07-26 14:18:16.919 16241 16385 I lazada : java.lang.Throwable
07-26 14:18:16.919 16241 16385 I lazada : at com.virjar.ratel.demoapp.crack.hook.LazadaHooker$2.afterHookedMethod(LazadaHooker.java:64)
07-26 14:18:16.919 16241 16385 I lazada : at com.virjar.ratel.api.rposed.RC_MethodHook.callAfterHookedMethod(RC_MethodHook.java:70)
07-26 14:18:16.919 16241 16385 I lazada : at com.virjar.ratel.hook.sandcompat.hookstub.HookStubManager.hookBridge(HookStubManager.java:319)
07-26 14:18:16.919 16241 16385 I lazada : at com.virjar.ratel.hook.sandcompat.hookstub.MethodHookerStubs32.stub_hook_0(MethodHookerStubs32.java:378)
07-26 14:18:16.919 16241 16385 I lazada : at mtopsdk.network.impl.ANetworkCallImpl.<init>(SourceFile:36)
07-26 14:18:16.919 16241 16385 I lazada : at com.lazada.android.mtop.ANetWorkSecurityCallFactory.newCall(SourceFile:51)
07-26 14:18:16.919 16241 16385 I lazada : at mtopsdk.framework.filter.before.ExecuteCallBeforeFilter.doBefore(SourceFile:55)
07-26 14:18:16.919 16241 16385 I lazada : at mtopsdk.framework.manager.impl.AbstractFilterManager.start(SourceFile:60)
07-26 14:18:16.919 16241 16385 I lazada : at mtopsdk.mtop.intf.MtopBuilder.asyncRequest(SourceFile:810)
07-26 14:18:16.919 16241 16385 I lazada : at mtopsdk.mtop.intf.MtopBuilder.syncRequest(SourceFile:726)
07-26 14:18:16.919 16241 16385 I lazada : at java.lang.reflect.Method.invoke(Native Method)
07-26 14:18:16.919 16241 16385 I lazada : at com.alibaba.one.android.inner.DataReportJniBridge.sendReportBridgeMtop(Unknown Source:186)
get http response:{"api":"mtop.alibaba.cro.umid.reped","data":{"ck":"0163bb04f7299dcc768c22cc4f1c09c5","dt":"XoasjheQJiva3aDBGB8QeLm6YJUvIK92WQObJFCas5hkYgOPRLqD1t4zScu3kq9I5mP2kQ5WfkCu+wAobiam1TBtcbB55v5grQ1bH0mIJz60gaqaXpkcVeCh15tKIccdsTDC26IqHSY//5lJHU2vA3pVEK1G7WuYvF3mtBU2n8tRzKDnW2tDFuBF2SgxRmk4QAf/3uYLHf1l37jEL681Kma4qTJENrErolsZFuryg5DaBYRlAGYpsuosJYFJvuL1WiNnbZGq9OgM/QWgVvfNZYrfmO/wfFaRPJVBxVvxZQ1ZAn1kteQenzdL/0X4wT4ZivWbMsjEZG7pz9NR4xdRC7/FqNvAVqn52gK2nxmploCsfimQi0j7r4gffR4gzl425jKYtkrnSsuE8yIUPL48rU6gSrNTkk8eHjqWp3K+lK4=","ec":"200"},"ret":["SUCCESS::调用成功"],"v":"1.0"}
reped请求与响应参数以及调用堆栈如上所示,DataReportJniBridge.sendReportBridgeMtop在so中通过JNI调用,hook对应CallStaticObjectMethod打印堆栈发现在libsgsecuritybodyso.so中,但该so与libsgmain一样,有大量的静态花指令保护,IDA无法正常分析出各函数开始和结束地址,甚至部分函数无法识别是代码段,需要手动识别以及patch跳转,为防止有一些SMC或加固,从运行内存中dump一份so进行分析,同时lazada只有arm32的so,trace也行不通,因此分析起来比较麻烦。
若要继续完整还原x-umt算法,需要还原出reped响应内容,以及后续解析逻辑,同时还需解密请求参数中的body分析各字段含义,并模拟发送该请求交换获取x-umt。
因此xsign系列header并非简单参数签名,如果要走纯协议脱机过风控,还涉及到很多前置初始化请求和加密算法分析。
方案二
基于真机改机和群控的RPC调用方式
基于真机的方案有两种路线,一种是通过直接调用真机上请求方法或者doCommand方法生成xsign等header,该方案的优点在于无需考虑各header逻辑,只需保证设备指纹和代理IP正常即可,但需要和手机同步配合,以及每台手机的消费能力有限。
另一种则是通过真机异步生成上述模拟执行所需的OneMain#play方法的返回值并回传服务端,服务端通过模拟执行生成xsign等header,该方案相比于第一种的优点在于手机端只需生成环境信息,与业务请求解耦,相当于手机端的功能为生成deviceId,若deviceId储备充足,则服务端生成xsign速度较快。
逆向分析&Hook脚本
So层主要分析方式为unidbg trace和frida,通过unidbg分析数据来源和堆栈,结合frida和真机确定参数和返回值是否正确
Java层Hook可参考:
import android.util.Log;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import dalvik.system.PathClassLoader;
/**
* @author alienhe
* @date 2022/6/16
* @Description lazada 6.51.0 xsign
*/
public class LazadaHooker implements Hooker {
public static final String TAG = "lazada";
@Override
public boolean willHook(RC_LoadPackage.LoadPackageParam lpparam) {
return lpparam.packageName.equals("com.lazada.android");
}
@Override
public void hook(RC_LoadPackage.LoadPackageParam lpparam) {
try {
DemoAppHooker.registerSekiro("lazada");
// 淘宝系抓包强制走Https
ClassLoadMonitor.findAndHookMethod("mtopsdk.mtop.global.SwitchConfig", "isGlobalSpdySwitchOpen", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "switch to https!");
param.setResult(false);
}
});
// or mtopsdk.network.impl.ANetworkCallImpl#request
// ref: https://blog.csdn.net/haoren_xhf/article/details/90449978
RposedBridge.hookAllConstructors(RposedHelpers.findClass("mtopsdk.network.AbstractCallImpl", RatelToolKit.hostClassLoader), new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (param.args.length <= 0) {
return;
}
Object request = param.args[0];
Log.i(TAG, "get http request:" + request);
String url = RposedHelpers.getObjectField(request, "url");
// reped接口返回值中包括了x-umt
if (url.contains("mtop.alibaba.cro.umid.reped")) {
try {
Object requestBody = RposedHelpers.getObjectField(request, "body");
Log.i(TAG, "content-type:" + RposedHelpers.getObjectField(requestBody, "contentType") + ",content:" + new String(RposedHelpers.getObjectField(requestBody, "content"), StandardCharsets.UTF_8));
Log.i(TAG, "backtrace:", new Throwable());
} catch (Throwable e) {
Log.i(TAG, "parse request body error:", e);
}
}
}
});
RposedHelpers.findAndHookMethod("mtopsdk.mtop.domain.MtopResponse", RatelToolKit.hostClassLoader, "getBytedata", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "get http response:" + new String((byte[]) param.getResult(), StandardCharsets.UTF_8));
}
});
Log.i(TAG, "http network hook finish");
RposedHelpers.findAndHookMethod("mtopsdk.mtop.intf.MtopBuilder", RatelToolKit.hostClassLoader, "syncRequest", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
try{
Object request = RposedHelpers.getObjectField(param.thisObject,"request");
Object response = param.getResult();
String retCode = (String) RposedHelpers.callMethod(response,"getRetCode");
JSONObject dataJsonObject = (JSONObject) RposedHelpers.callMethod(response,"getDataJsonObject");
Log.i(TAG, "mtopsdk.mtop.intf.MtopBuilder#syncRequest request:"+ request+ "result:" + retCode + "|" + dataJsonObject);
}catch (Throwable e){
Log.e(TAG,"mtopsdk.mtop.intf.MtopBuilder#syncRequest error:" ,e);
}
}
});
//ClassLoadMonitor.hookAllMethod("com.taobao.wireless.security.adapter.datareport.DataReportJniBridge", "sendReportBridgeMtop", new RC_MethodHook() {
ClassLoadMonitor.hookAllMethod("com.taobao.wireless.security.adapter.common.d", "a", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
try{
Log.i(TAG,"mtop DataReportJniBridge send reped request:" + param.args.length);
if (param.args != null) {
Log.i(TAG, "mtop DataReportJniBridge send reped request:" + Arrays.toString(param.args) + ",result:" + param.getResult());
Log.i(TAG, "backtrace:", new Throwable());
}
}catch(Throwable e){
Log.e(TAG,"mtop DataReportJniBridge send reped request error:",e);
}
}
});
// 不Hook这个方法会出现JNICLibrary引用回收导致找不到JNICLibrary的情况
RposedBridge.hookAllConstructors(PathClassLoader.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "PathClassLoader create:" + param.thisObject);
}
});
ClassLoadMonitor.addClassLoadMonitor(new ClassLoadMonitor.OnClassLoader() {
@Override
public void onClassLoad(Class<?> clazz) {
if (clazz.getName().equals("com.taobao.wireless.security.adapter.JNICLibrary")) {
Log.i(TAG, "find class JNICLibrary clazz:" + clazz + ",classloader:" + clazz.getClassLoader());
LazadaHandler.JNICLibraryClassLoader = clazz.getClassLoader();
} else if (clazz.getName().equals("com.alibaba.one.android.sdk.OneMain")) {
Log.i(TAG, "find class com.alibaba.one.android.sdk.OneMain clazz:" + clazz + ",classloader:" + clazz.getClassLoader());
} else if (clazz.getName().equals("com.taobao.wireless.security.adapter.datareport.DataReportJniBridge")) {
Log.i(TAG, "find class com.taobao.wireless.security.adapter.datareport.DataReportJniBridge clazz:" + clazz + ",classloader:" + clazz.getClassLoader());
}
}
});
ClassLoadMonitor.hookAllMethod("com.taobao.dp.DeviceSecuritySDKImpl", "initUMIDNative", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "com.taobao.dp.DeviceSecuritySDKImpl invoked:" + param.args[0]);
}
});
ClassLoadMonitor.findAndHookMethod("com.taobao.wireless.security.adapter.JNICLibrary", "doCommandNative", int.class, Object.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
StringBuilder paramString = new StringBuilder();
int code = (int) param.args[0];
// filter
if (code == 12606) {
return;
}
paramString.append(param.args[0]);
if (param.args[1] instanceof Object[]) {
for (Object p : (Object[]) param.args[1]) {
paramString.append(",").append(p);
}
}
Log.i(TAG, "JNICLibrary#doCommandNative params: " + paramString + ",result:" + JSON.toJSONString(param.getResult()));
}
});
ClassLoadMonitor.findAndHookMethod("com.taobao.wireless.security.adapter.common.SPUtility2", "readFromSPUnified", String.class, String.class, String.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "SPUtility2#readFromSPUnified key:" + param.args[0] + ",value:" + param.getResult());
}
});
ClassLoadMonitor.findAndHookMethod("com.taobao.wireless.security.adapter.datacollection.DeviceInfoCapturer", "doCommandForString", int.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "DeviceInfoCapturer#doCommandForString key:" + param.args[0] + ",value:" + param.getResult());
}
});
ClassLoadMonitor.findAndHookMethod("com.alibaba.one.android.sdk.OneMain", "play", int.class, int.class, int.class, Object.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "OneMain#play key:" + Arrays.toString(param.args) + ",value:" + param.getResult());
try {
int i3 = (int) param.args[2];
int i4 = (int) param.args[3];
// x-umt来源
if (i3 == 61501799 && i4 == 136803) {
//Log.i(TAG,"backtrace:",new Throwable());
}
} catch (Throwable e) {
Log.d(TAG, "ignore:" + e.getMessage());
}
}
});
ClassLoadMonitor.findAndHookMethod("com.alibaba.wireless.security.securitybody.SecurityBodyAdapter", "doAdapter", int.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i(TAG, "SecurityBodyAdapter#doAdapter param:" + param.args[0] + ",value:" + param.getResult());
}
});
Log.i(TAG, "lazada hook finish");
} catch (Throwable e) {
Log.e(TAG, "lazada hook error:", e);
}
}
}