记某一汽车比价App的网络请求逆向分析

查看 65|回复 10
作者:wzvideni   
本文仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
包名:V1RJNWRFeHVXbTlaVjJocFkwTTFiMXB0VW1oaE1tYzk=
首先使用HttpCanary+TrustMeAlready模块抓包的同时使用算法助手选中算法分析这四个选项,抓包的同时进行算法分析


Screenshot_20240807-213824_Pixel 启动器.png (69.19 KB, 下载次数: 0)
下载附件
2024-8-9 21:17 上传

通过在HttpCanary中搜索过滤,可以找到首页和二级页面和更多页面的的Host,过滤后全部保存到本地,并且把算法助手分析的日志保存到本地,一同复制到电脑上分析。
1. 首页分析
首先打开首页的request.hcy文件,分析cipherTxt参数,也就是加密文本


2024.08.07_230952.png (61.24 KB, 下载次数: 0)
下载附件
2024-8-9 21:18 上传

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


2024.08.07_231253.png (64.94 KB, 下载次数: 0)
下载附件
2024-8-9 21:18 上传

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


2024.08.07_231439.png (45.92 KB, 下载次数: 0)
下载附件
2024-8-9 21:18 上传

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


2024.08.07_231531.png (67.55 KB, 下载次数: 0)
下载附件
2024-8-9 21:18 上传

然后就是timestamp参数:timestamp=1723037695328,明显是个时间戳
然后其他参数在多次请求后都保持不变,所以不用更改
2. 二级页面分析
打开request.hcy,对比首页的request.hcy文件,发现只多了一个seriesId参数,其他参数都一样,而这个参数就来自于首页请求返回的json数据中


2024.08.09_212259.png (66.01 KB, 下载次数: 0)
下载附件
2024-8-9 21:54 上传



2024.08.09_212420.png (59.63 KB, 下载次数: 0)
下载附件
2024-8-9 21:31 上传

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


2024.08.09_212850.png (69.8 KB, 下载次数: 0)
下载附件
2024-8-9 21:31 上传



2024.08.09_213111.png (77.12 KB, 下载次数: 0)
下载附件
2024-8-9 21:31 上传

然后用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)

下载次数, 参数

wzvideni
OP
  


zzyzy 发表于 2024-8-13 10:52
大佬想向app逆向学习该怎么规划学习?

跟着论坛里的教程学就行了:https://www.52pojie.cn/thread-408645-1-1.html
我就跟着这个学的,可能需要有安卓设备Root和lsp模块的使用的相关经验
uLY3M   

很详细,感谢楼主
ly123voo   

学习一下
chenyong2020   

大佬就是厉害,我只能膜拜你的技术了。
arctan1   

学无止境
254688198   

向大佬学习
chenyaolong   

很详细,感谢楼主
hopecolor514   

感谢大佬,厉害啊
bxw00004   

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

返回顶部