环境:
- 粉笔考研 v6.3.15:https://www.wandoujia.com/apps/1220941/history_v6031500
- 雷电9 模拟器:https://www.ldmnq.com/
- 安装 magisk:https://blog.csdn.net/Ruaki/article/details/135580772
- 安装 Dia 插件 (作用:禁用弹窗):https://github.com/Xposed-Modules-Repo/dialog.box
Dia 插件 功能
- 取消弹窗。(取消 app 的强制升级。例如:得物app、粉笔考研app)
- 禁用退出(退出+完成)(可以与键控制组合)
- 按关键字禁用弹出框(悬停窗口+对话框)(可与按键控制结合使用)
- 版本自定义(版本号+版本名称)
- 伪装系统时间(系统时间+ GPS时间+本地访问服务器时间)
- 此模块的反检测,Xposed,Root
- 启动时禁用网络(可设置禁用时间)
- 强制结束当前活动(Activity)(通过组合键控制)
- 反禁用Xposed(简单禁用)
直接搜索关键字 "major/school_score"
major/major_score
分析后发现是 rsa 加密方式,rsa 是非对称加密,需要一个公钥、一个私钥。
通过 hook 验证,发现 函数a 就是用来解密的。
- 方法 1:逆向算法,直接 python 实现
- 方法 2:用 java 实现,然后打包成 jar 包,python 调用 jar 包
- 方法 3:通过 rpc 方式
下面 通过 rpc 方式实现。
模拟器中执行 frida-server,并设置端口转发。
rcp 方式调用
import time
import frida
import uvicorn
from pathlib import Path
from fastapi import FastAPI, Request
js_code = """
console.log("Script loaded successfully ");
function callDecryptFunc(mi_str) { //定义导出函数
let local_result;
Java.perform(function x() {
console.log("hook 成功");
console.log(mi_str);
let li9 = Java.use("li9");
let result = li9.a(mi_str);
console.log(`result ---> ${result}`);
local_result = result
return result;
});
return local_result;
}
rpc.exports = {
// 导出名不可以有大写字母或者下划线
calldecrypyfunc: callDecryptFunc
};
"""
def my_message_handler(message, payload):
print(message)
print(payload)
def get_session():
device = frida.get_usb_device()
time.sleep(2) # 睡眠2秒, 防止程序运行过快从而导致附加不上
session = device.attach("粉笔考研")
return session
g_session = get_session()
script = g_session.create_script(js_code)
script.on("message", my_message_handler)
script.load()
app = FastAPI()
@app.get("/decrypt_data")
@app.post("/decrypt_data")
async def root(request: Request):
data_dict = await request.json()
encrypt_data = data_dict['encrypt_data']
global script
decrypt_data = script.exports_sync.calldecrypyfunc(encrypt_data)
ret_val = {
'encrypt_data': encrypt_data,
'decrypt_data': decrypt_data
}
return ret_val
if __name__ == '__main__':
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=6666)
pass
执行结果
测试脚本。"Cookie": "换成自己的cookies"
import json
import requests
requests.packages.urllib3.disable_warnings()
headers = {
"User-Agent": "fenbi-android",
"Host": "schoolapi.fenbi.com",
"Cookie": "换成自己的cookies"
}
def get_decrypt_data(data_dict=None):
url = "http://127.0.0.1:6666/decrypt_data"
resp_2 = requests.post(url, json=data_dict, verify=False)
ret_val = {
'name': data_dict['name'],
'decrypt_data': resp_2.json()['decrypt_data'],
}
return ret_val
def main():
url_1 = "https://schoolapi.fenbi.com/kaoyan/android/kyzz/major/school_score"
url_2 = "https://schoolapi.fenbi.com/kaoyan/android/kyzz/major/major_score"
college_info = [
{"dm": "10001", "name": "北京大学"},
{"dm": "10003", "name": "清华大学"},
]
for item in college_info:
dm = item['dm']
name = item['name']
querystring_1 = {
# "school": "10007", "type": "01", "year": "0",
"school": dm, "type": "01", "year": "0",
# "client_context_id": "2F3B0BCDA482C2DE2D23",
# "version": "6.3.15", "vendor": "Huawei",
# "app": "kaoyan", "av": "69", "kav": "27", "hav": "4",
# "deviceId": "AKJWMHTPfy/pM1yprG3inw==",
# "quizId": "0", "imei": "", "oaid": "",
}
querystring_2 = {
"school": dm, "type": "01", "year": "0", "department": "",
# "client_context_id": "A28D05AA5A8943BF6D8F",
# "version": "6.3.15", "vendor": "Huawei",
# "app": "kaoyan", "av": "69", "kav": "27", "hav": "4",
# "deviceId": "AKJWMHTPfy/pM1yprG3inw==",
# "quizId": "0", "imei": "", "oaid": "",
}
resp = requests.get(url_1, headers=headers, params=querystring_1, verify=False)
resp_json = resp.json()
post_data = {
'name': name,
'encrypt_data': resp_json['data']
}
data_1 = get_decrypt_data(post_data)
resp = requests.get(url_2, headers=headers, params=querystring_2, verify=False)
resp_json = resp.json()
post_data = {
'name': name,
'encrypt_data': resp_json['data']
}
data_2 = get_decrypt_data(post_data)
data = {
'name': name,
'school_score': data_1['decrypt_data'],
'major_score': data_2['decrypt_data']
}
print(data)
if __name__ == '__main__':
main()
pass
执行结果
app 显示结果