1548 words
8 minutes
3.72.0 车智赢登陆api
https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx
搜索url
查找用例
public static void loginByPassword(String str, String str2, String str3, String str4, String str5, ResponseCallback<UserBean> responseCallback) {
HttpUtil.Builder builder = new HttpUtil.Builder();
builder.tag(str).method(HttpUtil.Method.POST).signType(1).url(LOGIN_URL).param("username", str2).param("type", str4).param("signkey", str5).param("pwd", SecurityUtil.encodeMD5(str3));
doRequest(builder, responseCallback, new TypeToken<BaseResult<UserBean>>() { // from class: com.che168.autotradercloud.user.model.UserModel.5
}.getType());
}
pwd
输入的是123456,一眼能看出来是md5加密,略
def encode_md5_hash(input_string):
"""Encodes the input string to MD5."""
return hashlib.md5(input_string.encode('utf-8')).hexdigest()
定位
public static TreeMap<String, String> getRequestParams(TreeMap<String, String> treeMap) {
if (!treeMap.containsKey(KEY_APP_ID)) {
treeMap.put(KEY_APP_ID, Constants.APP_ID);
}
if (!treeMap.containsKey("channelid")) {
treeMap.put("channelid", AppUtils.getChannelId(ContextProvider.getContext()));
}
if (!treeMap.containsKey(KEY_APP_VERSION)) {
treeMap.put(KEY_APP_VERSION, SystemUtil.getAppVersionName(ContextProvider.getContext()));
}
if (!treeMap.containsKey("udid")) {
treeMap.put("udid", AppUtils.getUDID(ContextProvider.getContext()));
}
String userKey = UserModel.getUserKey();
if (!ATCEmptyUtil.isEmpty((CharSequence) userKey)) {
treeMap.put("userkey", userKey);
}
checkNullParams(treeMap);
treeMap.put("_sign", SignManager.INSTANCE.signByType(0, treeMap));
return treeMap;
}
貌似是这个?只是猜测,hook了之后发现点登陆没有输出。
hook map的输出
Java.perform(function () {
var TreeMap = Java.use('java.util.TreeMap');
var Map = Java.use("java.util.Map");
TreeMap.put.implementation = function (key,value) {
if(key=="_sign" || key == 'udid'){ // 根据需要 看抓包
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(key + "=" + value);
}
var res = this.put(key,value);
return res;
}
});
java.lang.Throwable
at java.util.TreeMap.put(Native Method)
at com.che168.autotradercloud.launch.model.LaunchModel.lambda$initRequestCommonParams$0(LaunchModel.java:301)
at com.che168.autotradercloud.launch.model.LaunchModel$$ExternalSyntheticLambda0.checkParams(Unknown Source:0)
at com.che168.ahnetwork.http.HttpUtil$Builder.checkParams(HttpUtil.java:554)
at com.che168.ahnetwork.http.HttpUtil$Builder.doRequest(HttpUtil.java:490)
at com.che168.ahnetwork.http.HttpUtil$Builder.doRequest(HttpUtil.java:428)
at com.che168.autotradercloud.base.httpNew.BaseModel.doRequest(BaseModel.java:104)
at com.che168.autotradercloud.user.model.UserModel.loginByPassword(UserModel.java:274)
at com.che168.autotradercloud.user.model.UserModel.login(UserModel.java:1474)
at com.che168.autotradercloud.user.LoginActivity.login(LoginActivity.java:167)
at com.che168.autotradercloud.user.view.LoginView$1.onClick(LoginView.java:150)
at android.view.View.performClick(View.java:7297)
at android.view.View.performClickInternal(View.java:7274)
at android.view.View.access$3600(View.java:819)
at android.view.View$PerformClick.run(View.java:28023)
at android.os.Handler.handleCallback(Handler.java:914)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:225)
at android.app.ActivityThread.main(ActivityThread.java:7564)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
udid=VyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZmhSWX6p rWgye4Dy6BEz3gYxEX0Qncu+PQ==
java.lang.Throwable
at java.util.TreeMap.put(Native Method)
at com.che168.autotradercloud.launch.model.LaunchModel.lambda$initRequestCommonParams$0(LaunchModel.java:311)
at com.che168.autotradercloud.launch.model.LaunchModel$$ExternalSyntheticLambda0.checkParams(Unknown Source:0)
at com.che168.ahnetwork.http.HttpUtil$Builder.checkParams(HttpUtil.java:554)
at com.che168.ahnetwork.http.HttpUtil$Builder.doRequest(HttpUtil.java:490)
at com.che168.ahnetwork.http.HttpUtil$Builder.doRequest(HttpUtil.java:428)
at com.che168.autotradercloud.base.httpNew.BaseModel.doRequest(BaseModel.java:104)
at com.che168.autotradercloud.user.model.UserModel.loginByPassword(UserModel.java:274)
at com.che168.autotradercloud.user.model.UserModel.login(UserModel.java:1474)
at com.che168.autotradercloud.user.LoginActivity.login(LoginActivity.java:167)
at com.che168.autotradercloud.user.view.LoginView$1.onClick(LoginView.java:150)
at android.view.View.performClick(View.java:7297)
at android.view.View.performClickInternal(View.java:7274)
at android.view.View.access$3600(View.java:819)
at android.view.View$PerformClick.run(View.java:28023)
at android.os.Handler.handleCallback(Handler.java:914)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:225)
at android.app.ActivityThread.main(ActivityThread.java:7564)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
_sign=C97B0C91FAB708871272894449ED99A3
之前找到的是loginByPassword,还没有_sign和udid。观察调用栈,感觉com.che168.autotradercloud.launch.model.LaunchModel.lambda$initRequestCommonParams$0(LaunchModel.java:311)
比较可疑
public static /* synthetic */ TreeMap lambda$initRequestCommonParams$0(int i, TreeMap treeMap) {
if (!treeMap.containsKey(KEY_APP_ID)) {
treeMap.put(KEY_APP_ID, Constants.APP_ID);
}
if (!treeMap.containsKey("channelid")) {
treeMap.put("channelid", AppUtils.getChannelId(ContextProvider.getContext()));
}
if (!treeMap.containsKey(KEY_APP_VERSION)) {
treeMap.put(KEY_APP_VERSION, SystemUtil.getAppVersionName(ContextProvider.getContext()));
}
if (!treeMap.containsKey("udid")) {
treeMap.put("udid", AppUtils.getUDID(ContextProvider.getContext()));
}
String userKey = UserModel.getUserKey();
if (!ATCEmptyUtil.isEmpty((CharSequence) userKey)) {
treeMap.put("userkey", userKey);
}
checkNullParams(treeMap);
String signByType = SignManager.INSTANCE.signByType(i, treeMap);
if (signByType != null) {
treeMap.put("_sign", signByType);
}
return treeMap;
}
hook一下AppUtils.getUDID
和 SignManager.INSTANCE.signByType
Java.perform(function () {
let AppUtils = Java.use("com.che168.autotradercloud.util.AppUtils");
AppUtils["getUDID"].implementation = function (context) {
console.log(`AppUtils.getUDID is called: context=${context}`);
let result = this["getUDID"](context);
console.log(`AppUtils.getUDID result=${result}`);
console.log("---------------------------------------------------")
return result;
};
let SignManager = Java.use("com.che168.atclibrary.base.SignManager");
SignManager["signByType"].implementation = function (i, paramMap) {
console.log(`SignManager.signByType is called: i=${i}, paramMap=${paramMap}`);
let result = this["signByType"](i, paramMap);
console.log(`SignManager.signByType result=${result}`);
return result;
};
});
AppUtils.getUDID is called: context=com.che168.autotradercloud.ATCApplication@d39912d
AppUtils.getUDID result=VyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZlro30Ck NN8m+Ay6omzg93gvOkT8rjjlKA==
---------------------------------------------------
SignManager.signByType is called: i=1, paramMap={_appid=atc.android, appversion=3.72.0, channelid=csy, pwd=e10adc3949ba59abbe56e057f20f883e, signkey=, type=, udid=VyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZlro30Ck NN8m+Ay6omzg93gvOkT8rjjlKA==, username=14725836987}
SignManager.signByType result=CBDD85171AB7F8F0BF9BE4105B1C7A3F
和抓包对比,可以确定是这里加密的
udid
public static String getUDID(Context context) {
return SecurityUtil.encode3Des(context, getIMEI(context) + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + (System.currentTimeMillis() / 1000) + ".000000|" + SPUtils.getDeviceId());
}
hook encode 3Des
SecurityUtil.encode3Des is called: context=com.che168.autotradercloud.ATCApplication@d39912d, str=ecff81b9-76cb-3891-8213-d16604b5b14c|1741186664.000000|350572
SecurityUtil.encode3Des result=VyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZnpD/d9f01SFd4aqwQ8e+lsp5Zi0ELe+Pw==
进入encode3Des
, hook key
AHAPIHelper.getDesKey result=appapiche168comappapiche168comap
import requests
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
import base64
import time
ENCODING = "utf-8"
IV = b'appapich'
def encode(key, data):
"""Encodes the byte array to a base64 string."""
b64_res = base64.b64encode(data).decode(ENCODING)
return b64_res[:60] + ' ' + b64_res[60:]
def encode_3des(des_key, str_to_encrypt):
if not des_key:
return None
# Ensure the key is 24 bytes long for 3DES
while len(des_key) < 24:
des_key += des_key
des_key = des_key[:24].encode(ENCODING)
try:
cipher = DES3.new(des_key, DES3.MODE_CBC, IV)
padded_data = pad(str_to_encrypt.encode(ENCODING), DES3.block_size)
encrypted_data = cipher.encrypt(padded_data)
return encode(des_key, encrypted_data)
except Exception as e:
print(f"Encryption error: {e}")
return None
def get_udid(imei, device_id, key):
timestamp = int(time.time())
data = f"{imei}|{timestamp}.000000|{device_id}"
data = 'ecff81b9-76cb-3891-8213-d16604b5b14c|1741186664.000000|350572'
return encode_3des(key, data)
if __name__ == '__main__':
# Example usage
imei = "ecff81b9-76cb-3891-8213-d16604b5b14c"
device_id = "350572"
key = "appapiche168comappapiche168comap"
udid = get_udid(imei, device_id, key)
print(udid)
结果与hook的对比,发现是对的
_sign
public final class SignManager {
public static final SignManager INSTANCE = new SignManager();
public static final String KEY_AUTOHOME = "@7U$aPOE@$";
public static final String KEY_SHARE = "moc.861ehc.relaed.bup.wyfv";
public static final String KEY_V1 = "com.che168.www";
public static final String KEY_V2 = "W@oC!AH_6Ew1f6%8";
private SignManager() {
}
public final String signByType(@SignType int i, TreeMap<String, String> paramMap) {
Intrinsics.checkNotNullParameter(paramMap, "paramMap");
StringBuilder sb = new StringBuilder();
String str = KEY_V1;
if (i != 0) {
if (i == 1) { // 走这里
str = KEY_V2;
} else if (i == 2) {
str = KEY_SHARE;
} else if (i == 3) {
str = KEY_AUTOHOME;
}
}
sb.append(str);
for (String str2 : paramMap.keySet()) {
sb.append(str2);
sb.append(paramMap.get(str2));
}
sb.append(str);
String encodeMD5 = SecurityUtil.encodeMD5(sb.toString()); // hook这里z
if (encodeMD5 != null) {
Locale ROOT = Locale.ROOT;
Intrinsics.checkNotNullExpressionValue(ROOT, "ROOT");
String upperCase = encodeMD5.toUpperCase(ROOT);
Intrinsics.checkNotNullExpressionValue(upperCase, "this as java.lang.String).toUpperCase(locale)");
if (upperCase != null) {
return upperCase;
}
}
return "";
}
}
目测是把treemap拿进来进行拼接后md5加密再转大写.
SecurityUtil.encodeMD5 is called: str=W@oC!AH_6Ew1f6%8_appidatc.androidappversion3.72.0channelidcsypwde10adc3949ba59abbe56e057f20f883esignkeytypeudidVyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZmdJ+gG9 BOq/C1gP2rFPrKOT21yJQx82xQ==username14725836987W@oC!AH_6Ew1f6%8
SecurityUtil.encodeMD5 result=3adecdd319a988d90cfc77d3fbef8280
hook的时候将str替换成”123456”看结果有没有魔改md5
promot: convert the provided java method into a python function. a standalone function without creating an object of the SignManager class,
KEY_V2 = "W@oC!AH_6Ew1f6%8"
def sign_by_type(param_map):
sb = []
sb.append(KEY_V2)
# Sort the parameters by key to maintain order
for key in sorted(param_map.keys()):
sb.append(key)
sb.append(param_map[key])
sb.append(KEY_V2)
concatenated_string = ''.join(sb)
print(concatenated_string)
encode_md5 = encode_md5_hash(concatenated_string)
if encode_md5 is not None:
return encode_md5.upper()
return ""
def encode_md5_hash(input_string):
"""Encodes the input string to MD5."""
return hashlib.md5(input_string.encode('utf-8')).hexdigest()
# Provided map as a dictionary
param_map = {
"_appid": "atc.android",
"appversion": "3.72.0",
"channelid": "csy",
"pwd": "e10adc3949ba59abbe56e057f20f883e",
"signkey": "",
"type": "",
"udid": "VyLCedZhA1IAEku0Esi4e2upmkwc8HWxoFJTXxCS+dniXLcWsfLdZlro30Ck NN8m+Ay6omzg93gvOkT8rjjlKA==",
"username": "14725836987"
}
# Call the function with the provided map
signature = sign_by_type(param_map)
print(signature)
完整代码
import requests
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
import base64
import time
import hashlib
ENCODING = "utf-8"
IV = b'appapich'
KEY_V2 = "W@oC!AH_6Ew1f6%8"
def encode(key, data):
"""Encodes the byte array to a base64 string."""
b64_res = base64.b64encode(data).decode(ENCODING)
return b64_res[:60] + ' ' + b64_res[60:]
def encode_3des(des_key, str_to_encrypt):
# Ensure the key is 24 bytes long for 3DES
while len(des_key) < 24:
des_key += des_key
des_key = des_key[:24].encode(ENCODING)
try:
cipher = DES3.new(des_key, DES3.MODE_CBC, IV)
padded_data = pad(str_to_encrypt.encode(ENCODING), DES3.block_size)
encrypted_data = cipher.encrypt(padded_data)
return encode(des_key, encrypted_data)
except Exception as e:
print(f"Encryption error: {e}")
return None
def get_udid(imei, device_id, key):
timestamp = int(time.time())
data = f"{imei}|{timestamp}.000000|{device_id}"
# data = 'ecff81b9-76cb-3891-8213-d16604b5b14c|1741258718.000000|350572'
return encode_3des(key, data)
def sign_by_type(param_map):
sb = []
sb.append(KEY_V2)
# Sort the parameters by key to maintain order
for key in sorted(param_map.keys()):
sb.append(key)
sb.append(param_map[key])
sb.append(KEY_V2)
concatenated_string = ''.join(sb)
# print(concatenated_string)
encode_md5 = encode_md5_hash(concatenated_string)
if encode_md5 is not None:
return encode_md5.upper()
return ""
def encode_md5_hash(input_string):
"""Encodes the input string to MD5."""
return hashlib.md5(input_string.encode('utf-8')).hexdigest()
if __name__ == '__main__':
# Example usage
imei = "ecff81b9-76cb-3891-8213-d16604b5b14c"
device_id = "350572"
key = "appapiche168comappapiche168comap"
udid = get_udid(imei, device_id, key)
# print(udid)
pwd = "123456"
username = '14725836900'
# Provided map as a dictionary
param_map = {
"_appid": "atc.android",
"appversion": "3.72.0",
"channelid": "csy",
"pwd": encode_md5_hash(pwd),
"signkey": "",
"type": "",
"udid": udid, # 空格可能要用%20代替。但经过测试,不用 :P
"username": username
}
# Call the function with the provided map
signature = sign_by_type(param_map)
# print(signature)
url = 'http://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx'
# 请求头
headers = {
'Cache-Control': 'public, max-age=0',
'traceid': 'atc.android_9f48f6a9-abc3-472c-a6ab-46435895ee3c',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'dealercloudapi.che168.com',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'User-Agent': 'okhttp/3.14.9'
}
param_map["_sign"] = signature
response = requests.post(url, headers=headers, data=param_map)
print(f"Response Content: {response.text}")
🐛 补充
udid - getIMEI
import uuid
imei = str(uuid.uuid4())
nanoTime
nano_time = random.randint(5136066335773, 7136066335773) # 开机时间
SPUtils.getDeviceId()
如果逆向过程中,获取XML数据,谨慎可能是会先注册设备。
发现只去XML文件中读取,那么一定会有地方先在XML中设置,此处才能读取到。
猜想:
程序启动时,通过算法或发送请求,获取数据写入到XML文件中。
概率较大,很多APP都是咋启动时
项目启动时,通过算法或发送请求,获取数据写入到XML文件中。
有可能,但是概率不大。 如果是这种情况的话,此处的方法一般会定义为:先去XML文件中获取,如果没有则用算法去生成,然后再讲生成的数据写入XML,便于后续获取。
逆向方法:需要先清空数据
- 查找用例
- hook调用栈
3des
pip install pycryptodome
import base64
from Crypto.Cipher import DES3
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
# 3DES的MODE_CBC模式下只有前24位有意义
key = b'appapiche168comappapiche168comap'[0:24]
iv = b'appapich'
plaintext = pad("xxxxxxxxx").encode("utf-8")
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
res = base64.b64encode(result)
print(res)
3.72.0 车智赢登陆api
https://zycreverse.netlify.app/posts/app-reverse/che168/