吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2621|回复: 24
收起左侧

[Android 原创] 记某一汽车比价App的网络请求逆向分析

  [复制链接]
wzvideni 发表于 2024-8-9 21:51
本帖最后由 wzvideni 于 2024-8-9 21:58 编辑

本文仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!


包名:V1RJNWRFeHVXbTlaVjJocFkwTTFiMXB0VW1oaE1tYzk=


首先使用HttpCanary+TrustMeAlready模块抓包的同时使用算法助手选中算法分析这四个选项,抓包的同时进行算法分析


Screenshot_20240807-213824_Pixel 启动器.png


通过在HttpCanary中搜索过滤,可以找到首页和二级页面和更多页面的的Host,过滤后全部保存到本地,并且把算法助手分析的日志保存到本地,一同复制到电脑上分析。


1. 首页分析


首先打开首页的request.hcy文件,分析cipherTxt参数,也就是加密文本


2024.08.07_230952.png


在算法助手的日志里搜索nonceStr的值WQDS23,可以发现搜索到了加密内容、密钥和算法类型,为什么搜索这个值,因为直接搜索加密文本搜不到,多次请求可以发现,nonceStr这个参数是一个六位数的,包含字母大写和数字的随机数


2024.08.07_231253.png


再搜索base64格式的加密结果,又得到了一个md5加密的结果,但是需要注意的是,在做md5加密前,末尾加了一段字符串:BC56EAAB76C5492E,多次请求发现,这个字符串是固定的


2024.08.07_231439.png


再把Hex格式的加密结果到请求的request.hcy文件中搜索,发现在sign这个参数这里用到了这个值,其实真实的操作应该是搜索这个sign的值,发现这个值来源的数据和前面的加密结果很相似,来得知该值是由前面的加密结果+BC56EAAB76C5492E再计算md5得到的


2024.08.07_231531.png


然后就是timestamp参数:timestamp=1723037695328,明显是个时间戳

然后其他参数在多次请求后都保持不变,所以不用更改


2. 二级页面分析

打开request.hcy,对比首页的request.hcy文件,发现只多了一个seriesId参数,其他参数都一样,而这个参数就来自于首页请求返回的json数据中


2024.08.09_212259.png 2024.08.09_212420.png


3. 详细参数页面分析

同样打开request.hcy,对比首页的request.hcy文件,发现多了一段json数据,其他参数也都一样,而这段json数据中的id则来自于二级页面请求返回的json数据中


2024.08.09_212850.png 2024.08.09_213111.png


然后用python模拟请求进行批量获取数据,代码如下:

[Python] 纯文本查看 复制代码
import base64
import hashlib
import json
import os
import random
import string
import time
import uuid
from datetime import datetime

import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding

# Base64格式的公钥
base64_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOHifICBXyxzDSj9yOg9HzBMs/D0C0YS1ZrF95j6HQrrQu4zOzDyJc5hLgwcEmHE/A6k39phkSpeRqb9a+5AONCz6q0mP7z7xdzOwVXu7KZX+Ch0QU4NZutgi0IWwzCBAcOJ5+O2FxAj+O4z3Q45JtIlGWKNn+YPIixjxVsypN4QIDAQAB"

token = '202408051001001370193195139083'


def rsa_encrypt_long_data(plaintext):
    # 解码公钥
    public_key = serialization.load_der_public_key(base64.b64decode(base64_public_key))
    # 计算chunk_size
    key_size_in_bytes = public_key.key_size // 8  # 公钥大小,以字节为单位
    chunk_size = key_size_in_bytes - 11

    encrypted_chunks = []
    for i in range(0, len(plaintext), chunk_size):
        chunk = plaintext[i:i + chunk_size]
        encrypted_chunk = public_key.encrypt(
            chunk.encode(),
            padding.PKCS1v15()
        )
        encrypted_chunks.append(encrypted_chunk)
    encrypted_data = b''.join(encrypted_chunks)
    return base64.b64encode(encrypted_data).decode()


# 获取以毫秒为单位的Unix时间戳
def get_milliseconds_timestamp():
    timestamp = int(time.time() * 1000)
    return timestamp


def calculate_md5_string(input_string):
    # 创建一个MD5 hash对象
    md5_hash = hashlib.md5()

    # 更新hash对象
    md5_hash.update(input_string.encode('utf-8'))

    # 获取16进制表示的MD5值
    md5_digest = md5_hash.hexdigest()
    return md5_digest


def generate_random_string(length=6):
    # 大写字母和数字的字符集
    characters = string.ascii_uppercase + string.digits

    # 生成指定长度的随机字符串
    random_string = ''.join(random.choices(characters, k=length))

    return random_string


def generate_time_based_session_id():
    return str(uuid.uuid1())


def generate_session_id():
    return str(uuid.uuid4())


def get_time_now():
    # 获取当前时间
    now = datetime.now()

    # 将时间格式化为 'YYYY-MM-DD HH:MM:SS'
    formatted_time = now.strftime('%Y-%m-%d %H:%M:%S')
    return formatted_time


def requestAllCar(cipherTxt: str, nonceStr: str, sign: str, timestamp: int):
    # 设置请求的URL
    url = 'http://ipc.api.smallfeiyu.cn/aibg-car-compare-api/car/homeLoad.do'

    # 设置请求的参数
    params = {
        'productId': 'productId=746abe7c-796c-4ebb-a6c9-b287bf503da4',
        'vestId': '6dbfae2e-776e-47fd-872c-695dffc1935d',
        'channel': 'vivo',
        'osType': 'android',
        'version': '8',
        'cipherTxt': cipherTxt,
        'nonceStr': nonceStr,
        'sign': sign,
        'timestamp': timestamp,
        'token': token
    }

    data = {
        "app": "com.vhahbp.hfdakh",
        'sessionId': generate_session_id(),
        "remoteIp": "47.111.241.44",
        "remotePort": 80,
        "time": get_time_now(),
    }

    # 设置请求头
    headers = {
        'Host': 'ipc.api.smallfeiyu.cn',
        'Accept-Encoding': 'gzip',
        'User-Agent': 'okhttp/4.9.3',
    }

    # 发送POST请求
    response = requests.post(url, headers=headers, params=params, data=data)

    return response


def requestCar(seriesId: str, cipherTxt: str, nonceStr: str, sign: str, timestamp: int):
    # 设置请求的URL
    url = 'http://ipc.api.smallfeiyu.cn/aibg-car-compare-api/car/car.do'

    # 设置请求的参数
    params = {
        'seriesId': seriesId,
        'productId': 'productId=746abe7c-796c-4ebb-a6c9-b287bf503da4',
        'vestId': '6dbfae2e-776e-47fd-872c-695dffc1935d',
        'channel': 'vivo',
        'osType': 'android',
        'version': '8',
        'cipherTxt': cipherTxt,
        'nonceStr': nonceStr,
        'sign': sign,
        'timestamp': timestamp,
        'token': token
    }

    data = {
        "app": "com.vhahbp.hfdakh",
        'sessionId': generate_session_id(),
        "remoteIp": "47.111.241.44",
        "remotePort": 80,
        "time": get_time_now(),
    }

    # 设置请求头
    headers = {
        'Host': 'ipc.api.smallfeiyu.cn',
        'Accept-Encoding': 'gzip',
        'User-Agent': 'okhttp/4.9.3',
    }

    # 发送POST请求
    response = requests.post(url, headers=headers, params=params, data=data)

    # 打印响应
    return response


def requestDetailCar(request_body, cipherTxt: str, nonceStr: str, sign: str, timestamp: int):
    # 设置请求的URL
    url = 'http://ipc.api.smallfeiyu.cn/aibg-car-compare-api/car/carDetail.do'

    # 设置请求的参数
    params = {
        'productId': 'productId=746abe7c-796c-4ebb-a6c9-b287bf503da4',
        'vestId': '6dbfae2e-776e-47fd-872c-695dffc1935d',
        'channel': 'vivo',
        'osType': 'android',
        'version': '8',
        'cipherTxt': cipherTxt,
        'nonceStr': nonceStr,
        'sign': sign,
        'timestamp': timestamp,
        'token': token
    }

    # 设置请求头
    headers = {
        'Host': 'ipc.api.smallfeiyu.cn',
        'Accept-Encoding': 'gzip',
        'User-Agent': 'okhttp/4.9.3',
    }

    # 发送POST请求
    response = requests.post(url, headers=headers, params=params, json=request_body)

    # 打印响应
    return response


def delay_s(s: int):
    time.sleep(s)


if __name__ == '__main__':

    timestamp = get_milliseconds_timestamp()
    nonceStr = generate_random_string()
    # 首页请求
    plaintext1 = f'channel=vivo&nonceStr={nonceStr}&osType=android&productId=746abe7c-796c-4ebb-a6c9-b287bf503da4&sdkIntVersion=4056×tamp={timestamp}&token={token}&version=8&vestId=6dbfae2e-776e-47fd-872c-695dffc1935d&bizCodeAbc=79fa3d5f857cf66a&'
    cipherTxt1 = rsa_encrypt_long_data(plaintext1)
    sign = calculate_md5_string(cipherTxt1 + "BC56EAAB76C5492E")
    response1 = requestAllCar(cipherTxt1, nonceStr, sign, timestamp)
    if response1.status_code == 200:
        file = os.path.join('汽车比价大全', '总览.json')
        os.makedirs(os.path.dirname(file), exist_ok=True)
        with open(file, 'w', encoding='utf-8') as save_file:
            save_file.write(response1.text)
            print(f'{file} 已保存')
            delay_s(5)

        json_map = json.loads(response1.text)

        for data in json_map['data']:
            seriesId = data['id']
            timestamp = get_milliseconds_timestamp()
            nonceStr = generate_random_string()
            plaintext = f'channel=vivo&nonceStr={nonceStr}&osType=android&productId=746abe7c-796c-4ebb-a6c9-b287bf503da4&sdkIntVersion=4056&seriesId={seriesId}×tamp={timestamp}&token={token}&version=8&vestId=6dbfae2e-776e-47fd-872c-695dffc1935d&bizCodeAbc=79fa3d5f857cf66a&'

            cipherTxt = rsa_encrypt_long_data(plaintext)
            sign = calculate_md5_string(cipherTxt + "BC56EAAB76C5492E")
            response2 = requestCar(seriesId, cipherTxt, nonceStr, sign, timestamp)
            file = os.path.join('汽车比价大全', '车辆信息', f'{seriesId}.json')
            os.makedirs(os.path.dirname(file), exist_ok=True)
            with open(file, 'w', encoding='utf-8') as save_file:
                save_file.write(response2.text)
                print(f'{file} 已保存')
            delay_s(5)

            car_id_list = list()
            if response2.status_code == 200:
                detail_json = json.loads(response2.text)
                for detail in detail_json['data']:
                    car_id_list.append(detail['id'])
                car_id_map = {'carIds': car_id_list}

                response3 = requestDetailCar(car_id_map, cipherTxt, nonceStr, sign, timestamp)
                file = os.path.join('汽车比价大全', '详细参数', f'{seriesId}.json')
                os.makedirs(os.path.dirname(file), exist_ok=True)

                with open(file, 'w', encoding='utf-8') as save_file:
                    save_file.write(response3.text)
                    print(f'{file} 已保存')

            delay_s(5)


免费评分

参与人数 10威望 +1 吾爱币 +28 热心值 +9 收起 理由
lanyun86 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zzyzy + 1 + 1 谢谢@Thanks!
vmoranv + 1 热心回复!
allspark + 1 + 1 用心讨论,共获提升!
lingyun011 + 1 + 1 热心回复!
hopecolor514 + 1 谢谢@Thanks!
arctan1 + 1 + 1 谢谢@Thanks!
supercilious + 1 + 1 用心讨论,共获提升!
Arcticlyc + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| wzvideni 发表于 2024-8-13 13:36
本帖最后由 wzvideni 于 2024-8-13 13:38 编辑
zzyzy 发表于 2024-8-13 10:52
大佬想向app逆向学习该怎么规划学习?

跟着论坛里的教程学就行了:https://www.52pojie.cn/thread-408645-1-1.html

我就跟着这个学的,可能需要有安卓设备Root和lsp模块的使用的相关经验
uLY3M 发表于 2024-8-9 22:01
ly123voo 发表于 2024-8-10 01:28
chenyong2020 发表于 2024-8-10 08:06
大佬就是厉害,我只能膜拜你的技术了。
arctan1 发表于 2024-8-10 10:16
学无止境
254688198 发表于 2024-8-10 11:31
向大佬学习
chenyaolong 发表于 2024-8-10 12:55

很详细,感谢楼主
hopecolor514 发表于 2024-8-10 14:14
感谢大佬,厉害啊
bxw00004 发表于 2024-8-10 15:31
很详细,感谢楼主
Knight23 发表于 2024-8-10 16:17

很详细,感谢楼主
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 08:43

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表