App信息
包名:com.che168.autotradercloud

抓包分析
输入手机号和密码进行登录(1888888888,12345678),出现账号和密码错误
分析数据包

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| url: https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx method: POST headers: { ":authority": "dealercloudapi.che168.com", ":method": "POST", ":path": "/tradercloud/sealed/login/login.ashx", ":scheme": "https", "cache-control": "public, max-age=0", "traceid": "atc.android_75ca194d-982a-4c62-9c0a-eb647fcfff77", "content-type": "application/x-www-form-urlencoded", "content-length": "275", "accept-encoding": "gzip", "user-agent": "okhttp/3.14.9" } body: { "_appid": "atc.android", "_sign": "821E3885B2FC914FBFAD687399B650E7", "appversion": "3.71.0", "channelid": "csy", "pwd": "25d55ad283aa400af464c76d713c07ad", "signkey": "", "type": "", "udid": "aYU5PJ/U/aR1iOFu+Rd2HB7mst8DN5YX+4ft9CvXkL/YCpKYH4gg9+MiTNly tCkk7g9hVsWqMGbruaIqoS6K5A==", "username": "18888888888" }
|
这里面需要逆向的参数有_sign、pwd、udid
jadx反编译分析
pwd参数还原
搜索接口

双击跳转

它是一个字段,右键字段名查找用例,然后跳转至调用的地方,如下,它是url方法的参数
loginByPassword()
1 2 3 4 5 6
| 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>>() { }.getType()); }
|
在这里就可以看到传递的参数了
这里出现了一个参数pwd,并进行了MD5哈希算法签名
可以把输入的密码进行MD5签名,与抓包的数据进行对比,如下,是一样的

_sign参数还原
搜索"_sign"

当不确定是哪一个的时候,可以点进去看看上下文有没有其他相关的参数,或者可疑性比较大的方法进行hook,打印参数和返回值。

发现这几个sign的值都是通过toSign()方法得到的
toSign()

这里面对map中的内容通过字符串进行拼接,然后进行MD5签名,然后转为大写
可以通过hook toSign()方法查看参数和返回值
1 2 3 4 5 6 7 8 9 10 11
| function hook_toSign(){ Java.perform(function(){ let AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper"); AHAPIHelper["toSign"].implementation = function (context, map) { console.log(`AHAPIHelper.toSign is called: context=${context}, map=${map}`); let result = this["toSign"](context, map); console.log(`AHAPIHelper.toSign result=${result}`); return result; }; }) }
|
hook 之后点击登录没反应,可能是找错函数了,加密并没有走toSign()函数
接着找,看这几个函数

如下,这里的参数跟请求体中的参数也对得上

_sign的值是signByType,它的值是signByType()方法返回的
signByType()

这个方法根据signType的值来设置str的值,然后拼接paraMap的键和值,最后在拼接一个str,然后把字符串进行MD5签名
hook signByType()
1 2 3 4 5 6 7 8 9 10 11
| function hook_sign(){ Java.perform(function(){ let SignManager = Java.use("com.che168.atclibrary.base.SignManager"); SignManager["signByType"].implementation = function (signType, paramMap) { console.log(`SignManager.signByType is called: signType=${signType}, paramMap=${paramMap}`); let result = this["signByType"](signType, paramMap); console.log(`SignManager.signByType result=${result}`); return result; }; }) }
|
hook结果
1 2
| SignManager.signByType is called: signType=1, paramMap={_appid=atc.android, appversion=3.71.0, channelid=csy, pwd=25d55ad283aa400af464c76d713c07ad, signkey=, type=, udid=aYU5PJ/U/aR1iOFu+Rd2HB7mst8DN5YX+4ft9CvXkL/YCpKYH4gg968SLlht BKxj7FEpfjd4+7OOqUly74+vGQ==, username=18888888888} SignManager.signByType result=21DD339870BDF9DAE15131F6D01B9BB2
|
signTpe的值是1,在源码中找到对应的str是"W@oC!AH_6Ew1f6%8"
hook encodeMD5()
可以hook encodeMD5查看MD5签名的参数
1 2 3 4 5 6 7 8 9 10 11
| function hook_encodeMD5(){ Java.perform(function(){ let SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil"); SecurityUtil["encodeMD5"].implementation = function (str) { console.log(`SecurityUtil.encodeMD5 is called: str=${str}`); let result = this["encodeMD5"](str); console.log(`SecurityUtil.encodeMD5 result=${result}`); return result; }; }) }
|
hook 结果
1 2
| SecurityUtil.encodeMD5 is called: str=W@oC!AH_6Ew1f6%8_appidatc.androidappversion3.71.0channelidcsypwd25d55ad283aa400af464c76d713c07adsignkeytypeudidaYU5PJ/U/aR1iOFu+Rd2HB7mst8DN5YX+4ft9CvXkL/YCpKYH4gg9/rOGiHZ fplHNBBIgv34WQSSYDABqqyELA==username18888888888W@oC!AH_6Ew1f6%8 SecurityUtil.encodeMD5 result=d0bf664cc12b402592b7bbf926087bcc
|
udid参数还原

udid是通过getUDID()方法获取的
1 2 3
| public static String getUDID(Context context) { return SecurityUtil.encode3Des(context, getIMEI(context) + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + (System.currentTimeMillis() / 1000) + ".000000|" + SPUtils.getDeviceId()); }
|
可以看出udid是通过3Des加密得到的
分析3Des加密的明文,它是由getIMEI(context) + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + (System.currentTimeMillis() / 1000) + ".000000|" + SPUtils.getDeviceId()拼接而成的
getIMEI()
首先分析getIMEI(),我在jadx里反编译不了这个函数,换用Jeb进行反编译

分析代码,生成IMEI有不同的方式
1 2 3 4 5 6 7
| 1.((TelephonyManager)context0.getSystemService("phone")).getDeviceId(); 返回DeviceId 2.AppUtils.getIMEIbyAndroidIDandUUID(context0); 返回uuid 3.((WifiManager)context0.getSystemService("wifi")).getConnectionInfo().getMacAddress(); UUID.nameUUIDFromBytes(s1.getBytes("utf8")).toString(); 先获取wifi mac地址,然后根据这个mac地址生成一个uuid,然后返回
|
这里面最简单的方式就是直接生成uuid的方式了。
getIMEIbyAndroidIDandUUID()

直接随机生成一个uuid就可以了。
REPORT_VAL_SEPARATOR

REPORT_VAL_SEPARATOR = "|"
getDeviceId()

它是从内存中获取的,需要找到这个值是什么时候如何存进去的
saveDeviceId()

这个方法里面传入了一个字符串,通过查找用例来找传入的字符串是什么
regDevice()

向服务器发送请求返回一个RegDeviceResult对象,然后RegDeviceResult.deviceid字段作为saveDeviceId()的参数
deviceid是从服务器返回的,而且是程序第一次启动的时候设置的。

请求的url是/tradercloud/v100/push/regdevice.ashx
可以通过抓包获取deviceid的值。

返回的deviceid值是436586。
Python生成加密参数
1 2 3 4 5 6 7 8 9 10 11 12
| import uuid import time def g_imei(): u4 = uuid.uuid4() return str(u4)
def g_data(): REPORT_VAL_SEPARATOR = "|" deviceid = "436586" return g_imei()+REPORT_VAL_SEPARATOR+str(int(time.time()))+".000000|"+deviceid
print(g_data())
|
encode3Des()

获取key
getDesKey()

getSignKey

getSignDesKey()

get3desKey()

get3desKey()是一个native函数,需要到so中分析,它在native-lib.so里
IDA打开native-lib.so反编译,现在静态函数里找

直接就找到了,跳转过去

分析代码,查看DES3_KEY的值

kei的值是appapiche168comappapiche168comap
hook getDesKey()
也可以通过hook 获取
1 2 3 4 5 6 7 8 9 10 11 12
| function hook_getDesKey(){ Java.perform(function(){ let AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper"); AHAPIHelper["getDesKey"].implementation = function (context) { console.log(`AHAPIHelper.getDesKey is called: context=${context}`); let result = this["getDesKey"](context); console.log(`AHAPIHelper.getDesKey result=${result}`); return result; }; }) } hook_getDesKey()
|
hook 结果
1 2
| AHAPIHelper.getDesKey is called: context=com.che168.autotradercloud.ATCApplication@ddbd62d AHAPIHelper.getDesKey result=appapiche168comappapiche168comap
|
iv向量

iv的值为appapich
hook encode3Des
1 2 3 4 5 6 7 8 9 10 11 12
| function hook_encode3Des(){ Java.perform(function(){ let SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil"); SecurityUtil["encode3Des"].implementation = function (context, str) { console.log(`SecurityUtil.encode3Des is called: context=${context}, str=${str}`); let result = this["encode3Des"](context, str); console.log(`SecurityUtil.encode3Des result=${result}`); return result; }; }) } hook_encode3Des()
|
hook 结果
1 2
| SecurityUtil.encode3Des is called: context=com.che168.autotradercloud.ATCApplication@77f5244, str=01f27aed_7242_412e_80fe_d77895dd8c6c|1739375229.000000|436588 SecurityUtil.encode3Des result=dtRLGew6PDK+XUyKHax77H3+dDRC6VqmOHCbYsmzYEtRxFWu5cuowjF1r6Jy DRO/48UWBBgl5RF4+L8WGxjENg==
|
Python生成uuid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import uuid import time from Crypto.Cipher import DES3 from Crypto.Util.Padding import pad,unpad import base64
def g_imei(): u4 = uuid.uuid4() return str(u4)
def g_data(): REPORT_VAL_SEPARATOR = "|" deviceid = "436586" return g_imei()+REPORT_VAL_SEPARATOR+str(int(time.time()))+".000000|"+deviceid
def des_encrypt_cbc(plaintext,key,iv): cipher = DES3.new(key,DES3.MODE_CBC,iv) padded_text = pad(plaintext,DES3.block_size) encrypted_data = cipher.encrypt(padded_text) return encrypted_data
data = g_data().encode("utf-8") key = b"appapiche168comappapiche" iv = b"appapich"
udid = base64.b64encode(des_encrypt_cbc(data,key,iv)) print(udid)
|
登陆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| import requests import hashlib import uuid import time from Crypto.Cipher import DES3 from Crypto.Util.Padding import pad,unpad import base64
def md5_call(data): md5 = hashlib.md5() md5.update(data.encode("utf-8")) md5_hex = md5.hexdigest() return md5_hex
def g_pwd(pwd): return md5_call(pwd)
def g_imei(): u4 = uuid.uuid4() return str(u4)
def g_data(): REPORT_VAL_SEPARATOR = "|" deviceid = "436586" return g_imei()+REPORT_VAL_SEPARATOR+str(int(time.time()))+".000000|"+deviceid
def des_encrypt_cbc(plaintext,key,iv): cipher = DES3.new(key,DES3.MODE_CBC,iv) padded_text = pad(plaintext,DES3.block_size) encrypted_data = cipher.encrypt(padded_text) return encrypted_data
def g_udid(): data = g_data().encode("utf-8") key = b"appapiche168comappapiche" iv = b"appapich" return base64.b64encode(des_encrypt_cbc(data,key,iv)).decode("utf-8")
def g_sign(body): str = "W@oC!AH_6Ew1f6%8" sign_str ="" sign_str+=str sign_str+="_appid" sign_str+=body["_appid"] sign_str+="appversion" sign_str+=body["appversion"] sign_str+="channelid" sign_str+=body["channelid"] sign_str+="pwd" sign_str+=body["pwd"] sign_str+="signkey" sign_str+="type" sign_str+="udid" sign_str+=body["udid"] sign_str+="username" sign_str+=body["username"] sign_str+=str sign = md5_call(sign_str) return sign.upper()
url = "https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx"
headers = { "cache-control": "public, max-age=0", "traceid": "atc.android_75ca194d-982a-4c62-9c0a-eb647fcfff77", "content-type": "application/x-www-form-urlencoded", "content-length": "275", "accept-encoding": "gzip", "user-agent": "okhttp/3.14.9" }
body = { "_appid": "atc.android",
"appversion": "3.71.0", "channelid": "csy",
"signkey": "", "type": "",
}
username = input("Please input username:") pwd = input("Please input pwd:") body["username"] = username body["pwd"] = g_pwd(pwd) body["udid"] = g_udid() sign = g_sign(body) body["_sign"] = sign
response = requests.post(url=url,headers=headers,data=body) print(response.status_code) print(response.text)
|
Unidbg
调用get3desKey()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class MainActivity extends AbstractJni { private final AndroidEmulator emulator; private final Memory memory; private final VM vm; private String process = "";
public MainActivity() { emulator = AndroidEmulatorBuilder .for64Bit() .setProcessName(process) .addBackendFactory(new Unicorn2Factory(true)) .build();
memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/chezhiying/getkey/chezhiying_3.71.0.apk")); vm.setJni(this); vm.loadLibrary("native-lib", true); }
public void getkey() { DvmClass CheckSignUtilClass = vm.resolveClass("com.autohome.ahkit.jni.CheckSignUtil"); String method = "get3desKey(Landroid/content/Context;)Ljava/lang/String;"; StringObject stringObject = CheckSignUtilClass.callStaticJniMethodObject(emulator, method, vm.resolveClass("android/content/Context").newObject(null)); String resultstring = stringObject.getValue(); System.out.println(resultstring); }
public static void main(String[] args) { MainActivity main = new MainActivity(); main.getkey(); } }
|
输出
1
| appapiche168comappapiche168comap
|