某智赢------登录逆向分析

继续练习吧,跟着案例来走,加油!

一:绕过强制更新

从某个网站下载下来的apk,由adb install 到手机中发现居然要更新?这能忍?看看咱们怎么绕过它

方法一:断网-->打开apk--->联网

发现绕是绕过了,但是却抓不到包了。

方法二:反编译寻找更新位置,然后hook它的函数

​
  
  public static void showUpdateDialog() {
        new AHAlertDialog.Build(AppManager.getInstance().getTopActivity()).setButtonOrientation(2).setTitle(ContextProvider.getContext().getString(R.string.tip)).setTitleVisibility(0).setAutoClickDismiss(false).setMessage("亲爱的商家,您的车智赢+APP版本过低啦,建议您尽快升级,发现更多功能~").addButton("更新APP程序").setOnItemClickListener(new AHAlertDialog.AHAlertDialogListener() {
            @Override
            public void onItemClickListener(int i, String str) {
                SystemUtil.openBrowser(ContextProvider.getContext(), "https://appdownload.che168.com/usedcar/csy/index.html?pvareaid=106103");
            }
        }).create().show();//只要执行Show,就会弹窗,要求更新---hook这show方法,让它不运行
    }
 

1.1打印前台运行的包名

import frida
​
# 获取设备信息
rdev = frida.get_remote_device()
​
# 枚举所有的进程
# processes = rdev.enumerate_processes()
# for process in processes:
#     print(process)
​
# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app)  # Application(identifier="com.che168.autotradercloud", name="车智赢+", pid=22134, parameters={})  后面咱们会看包名和app名

1.2使用hook方式,让弹窗不执行(spawn方式)

# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2024/1/22 21:16
@Description:
'''
####### hook代码
import frida
import sys
​
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.che168.autotradercloud"])
session = rdev.attach(pid)
​
scr = """
Java.perform(function () {
    var AlertDialog = Java.use('androidx.appcompat.app.AlertDialog');
    AlertDialog.show.implementation = function(){
        console.log("执行了");
        //this.show();
    }
});
"""
script = session.create_script(scr)
​
​
def on_message(message, data):
    print(message, data)
​
​
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()

二:抓包分析

'''
url = https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx
请求头:
:method POST
:path   /tradercloud/sealed/login/login.ashx
:authority  dealercloudapi.che168.com
:scheme https
cache-control   public, max-age=0
traceid atc.android_f64fc887-e707-41c7-aba5-1e6f419a9ebd
content-type    application/x-www-form-urlencoded
content-length  254
accept-encoding gzip
user-agent  okhttp/3.14.9
​
非常正常
​
请求体: 全都要带
_appid  atc.android 不变
_sign   6B05FA1121E46EFA6437E97D4EC93C8D 要逆
appversion  3.29.0 不变
channelid   csy 不变
pwd e10adc3949ba59abbe56e057f20f883e 要逆
udid    /sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fztvrTEsdbBIHojsjqA1 8tI2FSWnwx7kBtHyVHFFFWcTsw== 要逆向
username    13576351684 账号名 不用逆向
​
'''

三:pwd逆向分析

搜索sealed/login/login.ashx ,发现只有一个结果

查找用例 LOGIN_UR

轻轻松松就找到了加密的位置,发现就是对输入的值进行MD5加密,而且没加盐

    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());
    }
​

四:udid逆向分析

发现有那么多结果,很明显,map.put很可疑,因为传过去的param都是先put进map字典里,然后拼接的,简单来说就是将请求体里的

sign、udid等参数获得的。

public static void appendUdid(Context context, Map<String, String> map) {
        map.put("udid", getUDID(context));
    }
​
    public static void appendUserKey(Context context, Map<String, String> map) {
        String userKey = UserInfoUtil.getUserKey(context);
        if (!TextUtils.isEmpty(userKey)) {
            map.put("userkey", userKey);
        }
    }
​
    public static synchronized String getUDID(Context context) {
        String encode3Des;
        synchronized (AHAPIHelper.class) {//往字符串里面append添加值
            StringBuilder sb = new StringBuilder();
            sb.append(AHDeviceUtil.getDeviceId(context));//随机uuid
            sb.append(HiAnalyticsConstant.REPORT_VAL_SEPARATOR);//'|'
            sb.append(System.nanoTime());//开机到现在的时间
            sb.append(HiAnalyticsConstant.REPORT_VAL_SEPARATOR);//'|'
            sb.append(UserInfoUtil.getUserDeviceId(context));//用户设备id
            encode3Des = SecurityUtil.encode3Des(context, sb.toString());//DES加密
        }
        return encode3Des;
    }
​

诺,猜的没错,这地方很可疑,咱hook一下encode3Des看看

# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : whitedog
@time : 2023/9/28 10:37
@Description:
'''
import frida
import sys
​
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("车智赢+")
​
scr = """
Java.perform(function () {
    // 包.类
    var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");
    // Hook,替换
    SecurityUtil.encode3Des.implementation = function(context,str){
        // 执行原来的方法
        console.log("传入的参数str为:",str);
        var res = this.encode3Des(context,str);
        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()
​
'''
传入的参数str为: 9382a1f0-2df2-3e1d-9faa-8e93d4cc71d4|10496729500147|367368 //猜的没错
加密后为: /sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fzsmJhgNiLKcZ2Ys9qfz HOIHuC6cOn/0W7i35iIcX6Fh+Q==
iv = appapich
​
'''

4.1 hook_des_key

 public static String getDesKey(Context context) {
        if (TextUtils.isEmpty(mDesKey)) {
            getSignDesKey(context);
        }
        return mDesKey;
    }
​
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:37
@Description:
'''
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(context){
        // 执行原来的方法
        //console.log("传入的参数context为:",context);
        var res = this.getDesKey(context);
        console.log("mDesKey:::",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()
​
#appapiche168comappapiche168comap
4.2python模拟udid
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("9382a1f0-2df2-3e1d-9faa-8e93d4cc71d4|31672388334956|367368").encode("utf-8")
​
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
res = base64.b64encode(result)
print(res)

五:_sign逆向分析

直接搜索"_sign发现有五个结果

发现前四个分别hook都找不到,那就只能干第四个了

# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:37
@Description:
'''
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,map){
        // 执行原来的方法
        console.log("传入的参数map为:",map);
        console.log("传入的参数i为:",i);
        var res = this.lambda$initRequestCommonParams$0(i,map);
        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()
​
'''
传入的参数map为: {pwd=e10adc3949ba59abbe56e057f20f883e, username=13576251688}
传入的参数i为: 1
结果为: {_appid=atc.android, _sign=A9E7B43116657D937AE0CF6E1B9BC893, appversion=3.29.0, channelid=csy, pwd=e10adc3949ba59abbe56e057f20f883e, udid=/sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fzv0oDcVAIj9xYnt6cBA FTNpKBo3gQVaUvhxkC+6d/eYog==, username=13576251688}
​
'''

然后再点进去signByte,观察观察

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";
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) { //i=1
            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); //循环获得key和value 并拼接在sb上
            sb.append(paramMap.get(str2));
        }
        sb.append(str);
        String encodeMD5 = SecurityUtil.encodeMD5(sb.toString()); //进行md5加密
        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 "";
    }

python模拟构建sign

  
  # md5加密
def md5(data_string):
    obj = hashlib.md5()
​
    obj.update(data_string.encode('utf-8'))
​
    return obj.hexdigest()
data_dict = {
    "_appid": "atc.android",
    "appversion": "3.28.0",
    "channelid": "csy",
    "pwd": md5(passwrod),
    "udid": udid,
    "username": username
}
​
result ="W@oC!AH_6Ew1f6%8"+ "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])+"W@oC!AH_6Ew1f6%8"
sign = md5(un_sign_string).upper()

如果仅仅只是想要结果,那看到这里就行了。

但是,咱为了练习,必须要思考"deskey"是从哪里搞来的?

很明显就是getSignkey()返回的,一直追踪到这里

发现竟然是在so层,那么我们直接反编译吧,libnative-lib.so

看到很混乱,导入头文件试试

看的舒服多了,说明咱的目标就是DES3_KEY

这个就是咱要的key,固定字符串了

六 代码整合

# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:10
@Description:
'''
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 = "13576251668"
    passwrod = "111111"
​
    imei = str(uuid.uuid4()) # 随机uuid
    nano_time = random.randint(5136066335773, 7136066335773)# 开机时间
    device_id = '358908'  # 可以为空,也可以358908
    udid = des3(f"{imei}|{nano_time}|{device_id}")
    print(udid)
    data = "W@oC!AH_6Ew1f6%8"
    data_dict = {
        "_appid": "atc.android",
        "appversion": "3.28.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()

运行结果:

大功告成!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值