今日内容

1 车智赢破解目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 目标:破解app :车智赢 最新版 3.37.0版本---》官方下载
- 登录功能---》python实现登录功能

# 破解app流程
1 找到合适版本的apk---》官网下载:3.37.0
2 apk安装到手机---》真机
3 打开app,使用charles抓包---》破解登录,抓登录包
4 分析抓包数据,找到要破解的字段---》密码,sign。。。
5 使用jadx反编译apk,得到java代码---》读逻辑
6 通过寻找关键字+Hook验证---》确认位置
7 使用python还原算法,实现目标


# 下载app
# 官网:https://icloud.che168.com/login.html 扫码想下载
# 官网:直接下载到电脑
https://appdownload.che168.com/usedcar/csy/index.html?pvareaid=106101
# 老师提供的软件:车智赢v3.37.0.apk

# 安装到手机上
- 手机连接电脑,打开调试模式
- 电脑端执行命令:adb install 拖动app到cmd窗口上

2 抓包

2.1 抓包配置

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
# 0 确保手机和电脑,处于同一个路由器--》处于同一个网络
# 1 电脑端,启动charles,确认本机电脑ip地址:192.168.71.80 8888

# 2 手机端:代理设置-->设置好电脑的ip和端口


# 3 打开app,开始抓包

# 4 点击登录后,使用charles抓包,如下图
-请求地址:https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx
-请求方式:POST
-请求头:没有特殊的,不需要破解
-请求体:
_appid:atc.android # 固定值
_sign:4BF30EFCAD46F660CDA593B52CC6CD5F # 像加密,需要破解
appversion:3.37.0 # app版本,固定的
channelid:csy # 固定值,尝试删除,再发包试一下
pwd:d1999a2caf49c63987d19e2ee981ead8# 密码加密了,需要破解
udid:iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/18++KdUhUZAsp67uehV JYrMWyshzg5LIXpcpKDL2C4EKg==# 需要破解
username:18953675222 # 不需要破解


# 5 改包,再发送尝试删除某些字段试试
# 5 总结:
pwd加密
_sign 破解
udid 破解

image-20240517170047654

image-20240517170104348

image-20240517170142132

image-20240517170152915

image-20240517170200394

image-20240517170211196

3 反编译app

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
# 1 使用jadx打开 app,把app拖入

# 2 根据关键字搜索:根据地址搜索--》搜索 login/login.ashx
https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx
# 3 搜到一个结果--》双击进入
找到了: UserModel---》常量--》LOGIN_URL

# 4 看看谁用了这个常量---》再常量上点右键---》查找用例
找到一个位置,双击进入

# 5 定位到了代码位置
public static void loginByPassword(String str, String str2, String str3, 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("pwd", SecurityUtil.encodeMD5(str3));
doRequest(builder, responseCallback, new TypeToken<BaseResult<UserBean>>() { // from class: com.che168.autotradercloud.user.model.UserModel.5
}.getType());
}
# 6 看到了密码加密位置:SecurityUtil.encodeMD5(str3)
点进去查看,看到是md5加密


# 7 如果不确定,咱们可以用代码确认是不是这个位置
# python把明文密码--》使用md5加密--》看看跟抓包抓到的是否一样
import hashlib
md5=hashlib.md5()
md5.update(b"2234567")
print(md5.hexdigest())
# d1999a2caf49c63987d19e2ee981ead8
#抓包抓到的--》密码加密就是md5
# d1999a2caf49c63987d19e2ee981ead8


# 8 如果不确定,通过hook确认是不是这个位置
-写hook脚本--》只要点击登录,hook脚本有输出,说明位置是正确的

image-20240517170238808

image-20240517170247192

image-20240517170255561

4 逆向

4.1 逆向密码加密

1
# 上述反编译中已经分析完了

4.1.1 hook确认密码加密的位置

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
# 1 手机端启动 frida-server
adb shell
su
cd /data/local/tmp/
ls
./frida-server-16.1.7-an
# 2 做端口转发
import subprocess
subprocess.getoutput("adb forward tcp:27042 tcp:27042")
subprocess.getoutput("adb forward tcp:27043 tcp:27043")


# 3 写hook脚本,运行
import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("车智赢+") # app名字



scr = """
Java.perform(function () {
// 包.类
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");
// Hook,替换
SecurityUtil.encodeMD5.implementation = function(str1){
console.log("传入的参数为:",str1); //明文密码
var res = this.encodeMD5(str1); // 执行原来的方法
console.log("加密后为:",res);
return res;
}

});
"""


script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()


## 右键运行脚本,手机上操作--》等待hook脚本执行
'''
传入的参数为: 1234567
加密后为: fcea920f7412b5da7be0cf42b8c93759
'''

image-20240517170306431

image-20240517170313951

image-20240517170321998

4.1.2 破解密码加密–md5

1
2
3
4
5
6
7
import  hashlib
md5=hashlib.md5()
md5.update(b"2234567")
print(md5.hexdigest())
# d1999a2caf49c63987d19e2ee981ead8
#抓包抓到的--》密码加密就是md5
# d1999a2caf49c63987d19e2ee981ead8

4.2 逆向签名 _sign

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
# 1 搜索  _sign  "_sign"   "_sign     _sign"--->我用的  "_sign
# 2 搜出 5个结果,但本质其实是俩
1 常量
2 toSign # 1和2 在一个类中,其实是一个
3 常量


# 3 研究是否是toSign加密了---》不是---》通过hook验证,不会走这里--》不是

# 4 继续找:LaunchModel--》常量--》public static final String KEY_SIGN = "_sign";
-查看用例
-定位到了:lambda$initRequestCommonParams$0
-源代码如下
-hook:lambda$initRequestCommonParams$0
public static 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);
treeMap.put(KEY_SIGN, SignManager.INSTANCE.signByType(i, treeMap));
return treeMap;
}

image-20240517170332758

4.2.1 hook–toSign确认位置是否正确(错误位置)

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
import frida
import sys

# 连接手机设备
rdev = frida.get_remote_device()

# Hook手机上的那个APP(app的包名字)
# 注意事项:在运行这个代码之前,一定要先在手机上启动app
session = rdev.attach("车智赢+")



scr = """
Java.perform(function () {
// 包.类
var AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper");
// Hook,替换
AHAPIHelper.toSign.implementation = function(context,treeMap){
// 执行原来的方法
var res = this.toSign(context,treeMap);
console.log("sign签名=",res);
return res;
}

});
"""



script = session.create_script(scr)
def on_message(message, data):
print(message, data)

script.on("message", on_message)
script.load()
sys.stdin.read()

image-20240517170343654

image-20240517170351002

image-20240517170403576

4.2.2 hook-sign加密的真正位置

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
import frida
import sys

rdev = frida.get_remote_device()

session = rdev.attach("车智赢+")

scr = """
Java.perform(function () {
// 包.类
var LaunchModel = Java.use("com.che168.autotradercloud.launch.model.LaunchModel");

// Hook,替换
LaunchModel.lambda$initRequestCommonParams$0.implementation = function(i,treeMap){
console.log("执行了,参数:",treeMap);
console.log("执行了,参数,转成字符串,更好看:",treeMap.toString());
console.log("执行了,参数,i:",i);
// 执行原来的方法
var res = this.lambda$initRequestCommonParams$0(i,treeMap);
console.log("执行了,返回值:",res);
console.log("执行了,参数:",res.toString());
return res;
}
});
"""

script = session.create_script(scr)

script.load()
sys.stdin.read()


'''
执行了,参数,转成字符串,更好看: {pwd=fcea920f7412b5da7be0cf42b8c93759, username=18953675222}
执行了,参数,i: 1
执行了,参数: {_appid=atc.android, _sign=A440920EEAA62E8275643B417B370EE8, appversion=3.37.0, channelid=csy, pwd=fcea920f7412b5da7be0cf42b8c93759, udid=iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19ClE+3LfYj23uQsd2i 3/hlcXoUytJ2Px89TxWA/PbLgw==, username=18953675222}

# 一开始只有:
pwd=fcea920f7412b5da7be0cf42b8c93759,
username=18953675222
#####经过方法后,放入了####
_appid=atc.android,
appversion=3.37.0,
channelid=csy,
udid=iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19ClE+3LfYj23uQsd2i 3/hlcXoUytJ2Px89TxWA/PbLgw==,
###把上面这一堆,使用 SignManager.INSTANCE.signByType(i, treeMap) 加密了,得到结果,放到了字典中 i 是1,treeMap是上面所有,不包含_sign
_sign=A440920EEAA62E8275643B417B370EE8,


'''

4.2.3 _sign加密总结

1
2
3
4
5
6
7
8
9
10
11
# 1 通过如下字典数据--》就是咱们抓包抓到的请求体除了 _sign以外的
pwd=fcea920f7412b5da7be0cf42b8c93759,
username=18953675222
_appid=atc.android,
appversion=3.37.0,
channelid=csy,
udid=iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19ClE+3LfYj23uQsd2i 3/hlcXoUytJ2Px89TxWA/PbLgw==,

# 2 调用某个加密SignManager.INSTANCE.signByType(i, treeMap) 得到秘钥串
破解SignManager.INSTANCE.signByType 如何加密的
udid 不知道怎么来的

4.3 逆向udid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1 udid位置--AppUtils.getUDID(ContextProvider.getContext()
if (!treeMap.containsKey("udid")) {
treeMap.put("udid", AppUtils.getUDID(ContextProvider.getContext()));
}
# 2 跳到声明 找到位置
public static String getUDID(Context context) {
return SecurityUtil.encode3Des(context, getIMEI(context) + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + System.nanoTime() + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + SPUtils.getDeviceId());
}

# 3 udid的加密过程
通过SecurityUtil.encode3Des,传入了context和 字符串(破解字符串)
1 得到getIMEI(context) + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + System.nanoTime() + HiAnalyticsConstant.REPORT_VAL_SEPARATOR + SPUtils.getDeviceId() 字符串
2 SecurityUtil.encode3Des 加密方案



#4 先看 字符串
getIMEI(context) # 手机的IMEI
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
System.nanoTime() # 手机开机时间
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
SPUtils.getDeviceId() # 设备id号

4.3.1 关于context解释

1
2
3
4
5
6
7
8
9
10
11
12
13
在安卓(Android)开发中,Context是一个非常重要的概念,它代表了应用程序的当前状态信息。每个Android应用程序都有一个Context,它允许应用程序访问系统资源和执行各种操作。Context通常是由Android系统传递给应用程序的各个组件(如Activity、Service、BroadcastReceiver等),以便它们能够与系统和其他组件进行交互。
# getContext() 获取这个对象


Context的主要作用包括:

# 访问资源:通过Context,您可以访问应用程序的资源,如布局文件、字符串、图片等。这是因为Context持有对应用程序资源的引用,使您能够在应用程序中加载和使用这些资源。

# 启动组件:通过Context,您可以启动其他组件,如Activity、Service、BroadcastReceiver等。例如,您可以使用Context启动一个新的Activity来打开新的界面。

# 获取系统服务:通过Context,您可以获取系统级别的服务,例如获取系统的传感器、网络状态、存储管理等。这些服务是通过系统提供的服务注册表(Service Registry)来获取的。

# 应用程序级别的操作:Context还可以用于执行应用程序级别的操作,如发送广播、获取应用程序包名、获取应用程序的数据目录等。

4.3.2 getIMEI(context)

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
public static String getIMEI(Context context) {
if (PermissionsCheckerUtil.hasReadPhoenStatePermission(context)) {
String imei = SPUtils.getIMEI(); # 从xml中 读取 SharedPreference中读取,一开始没有
if (imeiIsNull(imei)) {# 手机设备id和网卡的mac地址混合得到一个串
imei = ((TelephonyManager) context.getSystemService("phone")).getDeviceId();
if (imeiIsNull(imei)) {
String macAddress = ((WifiManager) context.getSystemService(NetworkUtil.NETWORK_TYPE_WIFI)).getConnectionInfo().getMacAddress();
if (macAddress != null) {
try {
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
# 如果手机拿不到网卡信息,就返回一个随机uuid
# return UUID.randomUUID().toString();

context = getIMEIbyAndroidIDandUUID(context);
}
if (macAddress.length() > 0 && !isInBlackList(macAddress)) {
context = UUID.nameUUIDFromBytes(macAddress.getBytes("utf8")).toString();
imei = context;
}
}
# 获取随机uuid
context = getIMEIbyAndroidIDandUUID(context);
imei = context;
}
if (!imeiIsNull(imei)) {
SPUtils.saveIMEI(imei); # 只要生成过以后,以后直接放到SharedPreference中xml中
}
}
return imei;
}
return "sssss";
}
#总结getIMEI:
1 随机uuid
2 通过mac+手机设备id生成的
3 直接sssss
# 我们如何生成
1 直接用uuid---》我用了这个方案
2 直接用sssss
3 取xml中找出它存储的字符串
#com.che168.autotradercloud,
# name="车智赢+"
# 去手机中找出来 :imei
# 保存到手机上:`/data/data/包名`
adb shell
su
cd /data/data
cd 包名
cd shared_prefs
ls
cat xx.xml
4 通过hook得到

4.3.3 HiAnalyticsConstant.REPORT_VAL_SEPARATOR

1
2
3
# 跳到声明  就是个 | 
public static final String REPORT_VAL_SEPARATOR = "|";

4.3.4 System.nanoTime()

1
2
3
4
5
6
7

# java中是一个前系统时间的纳秒数,但是在安卓中,这个表示手机开机时间

# android系统开机到当前的时间
# 注意,它跟java的这个函数返回值是不一样的
import random
nano_time = random.randint(5136066335773,7136066335773)

4.3.5 SPUtils.getDeviceId()

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
#1 源码是--》SharedPreference中读取的--》xml
public static String getDeviceId() {
return getSpUtil().getString(KEY_DEVICE_ID, "");
}
# 2 猜想啥时候写入的:
-程序启动时,通过某个算法,得到了这个值,写入到了xml中
-很多app是这样
-app一运行,向后端发送请求--》得到数据--》存入xml中
-不多,咱们app是这样


# 3 接下来,我们应该如何做
1 正常逆向,找到了获取的位置,找设置位置
-public static void saveDeviceId(String str) {
getSpUtil().saveString(KEY_DEVICE_ID, str);
}
-查找用例:SPUtils.saveDeviceId(regDeviceResult.deviceid);
2 测试是是否是固定值
-多个手机设备多次测试,看看值是否是固定
3 通过hook得到:getDeviceId 返回字符串,拿着正常用,如果能用,就不用再破解了
# 4 程序一开始,向后端发送请求:/tradercloud/v100/push/regdevice.ashx 拿回来存入的


# 5 最终答案:
1 可以直接为空 测试能通过
2 固定值---》hook后用了多次
-381632

image-20240517170422466

4.3.6 字符串总结

1
2
3
4
5
6
7
8
# 字符串
getIMEI(context) # 手机的IMEI uuid
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
System.nanoTime() # 手机开机时间
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
SPUtils.getDeviceId() # 设备id号固定的 381632

# 只要字符串中有uuid,它就是随机的--》后端一定没有校验这个东西---》我们可以随机生成

4.3.7 SecurityUtil.encode3Des如何加密的

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
# 1 源码encode3Des--》des加密,有des的key,有des的iv,des加密的明文:上面的字符串
public static String encode3Des(Context context, String str) {
String desKey = AHAPIHelper.getDesKey(context); # 获取des的key
byte[] bArr = null;
if (TextUtils.isEmpty(desKey)) {
return null;
}
try {
SecretKey generateSecret = SecretKeyFactory.getInstance("desede").generateSecret(new DESedeKeySpec(desKey.getBytes()));
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
cipher.init(1, generateSecret, new IvParameterSpec(iv.getBytes()));
bArr = cipher.doFinal(str.getBytes("UTF-8"));
} catch (Exception unused) {
}
return encode(bArr).toString();
}

# 2 des加密的
des-iv:appapich
des的key:AHAPIHelper.getDesKey(context)得到的
明文:上面破解的字符串

# 3 des-key值获取---AHAPIHelper.getDesKey(context)

# 4 des-key值获取---getDesKey
public static String getDesKey(Context context) {
if (TextUtils.isEmpty(mDesKey)) {
getSignDesKey(context);
}
return mDesKey;
}

# 5 getSignDesKey
private static void getSignDesKey(Context context) {
mDesKey = CheckSignUtil.get3desKey(context);
}
# 6 get3desKey--》JNI开发---通过so生成的--》逆向libnative-lib.so--->难度挺大
public class CheckSignUtil {
public static native String get3desKey(Context context);
static {
System.loadLibrary("native-lib");
}
}

# 7 一般des加密的key是固定的--》偷懒---》通过多次hook得到--》des的key值,发现是固定的,直接拿着用即可

image-20240517170437767

4.3.8 hook脚本–hook-des的key值

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
import frida
import sys

rdev = frida.get_remote_device()

session = rdev.attach("车智赢+")

scr = """
Java.perform(function () {
// 包.类
var AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper");

// Hook,替换
AHAPIHelper.getDesKey.implementation = function(ctx){
// 执行原来的方法
var res =this.getDesKey(ctx);
console.log("DesKey值:",res);
return res;
}
});
"""

script = session.create_script(scr)

script.load()
sys.stdin.read()

# 多次hook得到结果一样
# appapiche168comappapiche168comap

4.3.9 总结–代码实现

1
2
3
4
5
6
7
8
9
10
11
# 1 udid-->通过字符串 使用des加密得到
# 2 字符串:
getIMEI(context) # 手机的IMEI uuid
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
System.nanoTime() # 手机开机时间
HiAnalyticsConstant.REPORT_VAL_SEPARATOR # |
SPUtils.getDeviceId() # 设备id号固定的 381632

# 3 des加密的
iv:appapich
key:需要去so中破---》key是固定的--》通过hook得到固定值,直接用即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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("cf15599b-5e93-3be5-a705-a39403227dfd|13359325995159|381632").encode("utf-8")

# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
res = base64.b64encode(result)
print(res)


4.4 最终逆向 _sign

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
# 1 treeMap.put(KEY_SIGN, SignManager.INSTANCE.signByType(i, treeMap));
treeMap.put("_sign", SignManager.INSTANCE.signByType(i, treeMap));
# 2 treeMap的值是
pwd=fcea920f7412b5da7be0cf42b8c93759,
username=18953675222
_appid=atc.android,
appversion=3.37.0,
channelid=csy,
udid=iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19ClE+3LfYj23uQsd2i 3/hlcXoUytJ2Px89TxWA/PbLgw==,


# 3 查看signByType 如何加密的
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;
}
}
# str 因为i是1,走了str=KEY_V2 W@oC!AH_6Ew1f6%8
sb.append(str); # 字符串拼接 先把固定字符串 W@oC!AH_6Ew1f6%8 拼接上
for (String str2 : paramMap.keySet()) { # 循环字典的key和value拼接
sb.append(str2);
sb.append(paramMap.get(str2));
}
sb.append(str); # 把 W@oC!AH_6Ew1f6%8 拼到最后
# 使用md5加密,转成大写,返回了
String encodeMD5 = SecurityUtil.encodeMD5(sb.toString());
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 "";
}
# 4 最终被md5加密的字符串
W@oC!AH_6Ew1f6%8_appidatc.androidappversion3.37.0channelidcsypwdfcea920f7412b5da7be0cf42b8c93759udidiEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19voRWYoGIkhK+guA/Z qsws/DUAjJK0UsPda/rZEGw8mQ==username18953675221W@oC!AH_6Ew1f6%8
加密后为: a9ec14c2f483df45b890f203b105f5cc

4.4.1 代码实现 _sign

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
import hashlib


def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()


data = "W@oC!AH_6Ew1f6%8"


# _appid atc.android
# appversion 3.37.0
# channelid csy
# pwd fcea920f7412b5da7be0cf42b8c93759
# udidi EavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19voRWYoGIkhK+guA/Z qsws/DUAjJK0UsPda/rZEGw8mQ==
# username 18953675221
data_dict = {
"_appid": "atc.android",
"appversion": "3.37.0",
"channelid": "csy",
"pwd": "fcea920f7412b5da7be0cf42b8c93759",
"udid": "iEavSW4QycaRf6qULa18YDJEOmuw10SBwdVu62EN/19voRWYoGIkhK+guA/Z qsws/DUAjJK0UsPda/rZEGw8mQ==",
"username": "18953675221"
}

result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])

un_sign_string = f"{data}{result}{data}"
sign = md5(un_sign_string).upper()
print(sign)
#A9EC14C2F483DF45B890F203B105F5CC
#跟抓包的一样
#A9EC14C2F483DF45B890F203B105F5CC

5 代码整合

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
import hashlib
import uuid
import random
import base64
from Crypto.Cipher import DES3
import requests


# encode3Des 算法
def des3(data_string):
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(data_string).encode("utf-8")

# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
return base64.b64encode(result).decode('utf-8')

# md5加密
def md5(data_string):
obj = hashlib.md5()

obj.update(data_string.encode('utf-8'))

return obj.hexdigest()


def run():
username = "18953675221"
passwrod = "1234567"

imei = str(uuid.uuid4()) # 随机uuid
nano_time = random.randint(5136066335773, 7136066335773)# 开机时间
device_id = '381632' # 可以为空,也可以381632
udid = des3(f"{imei}|{nano_time}|{device_id}")

data = "W@oC!AH_6Ew1f6%8"
data_dict = {
"_appid": "atc.android",
"appversion": "3.37.0",
"channelid": "csy",
"pwd": md5(passwrod),
"udid": udid,
"username": username
}

result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
un_sign_string = f"{data}{result}{data}"
sign = md5(un_sign_string).upper()
data_dict['_sign'] = sign

res = requests.post(
url="https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx",
data=data_dict,
verify=False
)

print(res.text)


if __name__ == '__main__':
run()

6 简单逆向车智赢app的so文件

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
#1 3des加密的key---当时是hook到的
#2 正常应该去so文件中读:
libnative-lib.so 使用IDA反编译这个so文件,找到静态注册
java_包名_类名_方法名---》读c源码

# 3 把apk 后缀名改成zip 后解压
-取lib下找64位的 libnative-lib.so

# 4 拖入 IDA中,找exports,右键 quick filter
-找到静态注册

# 5 找到反编译后的c代码,看到很混乱

#6 引入jni的头文件,显示变量类型 jni-include.zip

#7 在IDA中--》点击file---》Load file---》Parse C Header file--》选择jni.h或者jni_md.h

# 8 引入后,把__int64 a1 做类型转换,在上面点击右键--》convert to struct *---》选择JNIenv_
# 9 在代码上 右键:hide casts 隐藏投射,发现代码的类型不见了,代码清晰一些了
int __fastcall Java_com_autohome_ahkit_jni_CheckSignUtil_get3desKey(JNIEnv_ *a1, int a2, int a3)
{
char *v5; // r5
jstring (__fastcall *v6)(JNIEnv *, const char *); // r2
char *v7; // r1
const char *v8; // r0

v5 = checkState;
if ( strstr(checkState, "yes") )
{
v6 = a1->functions->NewStringUTF;
v7 = DES3_KEY;
}
else
{
v8 = checkPackage(a1, a3);
strcpy(v5, v8);
strstr(checkState, "yes");
v7 = DES3_KEY; # 这里是v7,双击
v6 = a1->functions->NewStringUTF;
}
# v7就是返回的字符串
return v6(&a1->functions, v7); # return (*env)->NewStringUTF(env, res2);
}

# des的key是固定值
appapiche168comappapiche168comap

image-20240517170456048

image-20240517170505239

image-20240517170513207

__END__