wzvideni 发表于 2024-8-9 21:51

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

本帖最后由 wzvideni 于 2024-8-9 21:58 编辑

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


包名:V1RJNWRFeHVXbTlaVjJocFkwTTFiMXB0VW1oaE1tYzk=


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





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


1. 首页分析


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





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





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





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





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

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


2. 二级页面分析

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





3. 详细参数页面分析

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





然后用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
      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 发表于 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


很详细,感谢楼主
页: [1] 2 3
查看完整版本: 记某一汽车比价App的网络请求逆向分析