记某一汽车比价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:38 编辑
zzyzy 发表于 2024-8-13 10:52
大佬想向app逆向学习该怎么规划学习?
跟着论坛里的教程学就行了:https://www.52pojie.cn/thread-408645-1-1.html
我就跟着这个学的,可能需要有安卓设备Root和lsp模块的使用的相关经验 很详细,感谢楼主 学习一下 大佬就是厉害,我只能膜拜你的技术了。 学无止境 向大佬学习
很详细,感谢楼主 感谢大佬,厉害啊 很详细,感谢楼主
很详细,感谢楼主