ksjsb/tests/test_extract_app_login_fields.py
2026-07-30 20:25:56 +08:00

209 lines
9.3 KiB
Python

import json
import sys
import tempfile
import unittest
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from unittest.mock import patch
from tools import extract_app_login_fields
from tools.extract_app_login_fields import extract_fields
class ExtractAppLoginFieldsTests(unittest.TestCase):
def test_cli_preserves_cached_weapon_kaw_when_new_log_has_none(self):
"""只抓到登录字段的新日志不能清空版本级 KAW 缓存。"""
event = {
"tag": "REQUEST_BUILD",
"method": "POST",
"url": (
"https://az2-api.ksapisrv.com/rest/nebula/user/login/mobileVerifyCode?"
"did=ANDROID_APP_DID&sig=OLD"
),
"body": "code=123456&mobile=LOGIN_ENC&passport_account_image=VIMG_LOGIN",
}
with tempfile.TemporaryDirectory() as td:
root = Path(td)
log = root / "probe_login.log"
out = root / "app_login_fields_latest.json"
log.write_text("@@LOGIN " + json.dumps(event), encoding="utf-8")
out.write_text(
json.dumps({"weapon_kaw": "CACHED_VERSION_KAW", "source_log": "older.log"}),
encoding="utf-8",
)
argv = [
"extract_app_login_fields.py",
"--log",
str(log),
"--out",
str(out),
]
with patch.object(sys, "argv", argv), redirect_stdout(StringIO()):
self.assertEqual(extract_app_login_fields.main(), 0)
fields = json.loads(out.read_text(encoding="utf-8"))
self.assertEqual(fields["weapon_kaw"], "CACHED_VERSION_KAW")
def test_extract_fields_keeps_mobile_checker_fields(self):
"""登录日志里有 mobile/checker 时,提取 checker 专用密文和票据。"""
checker_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/mobile/checker?"
"did=ANDROID_APP_DID&sig=OLD&__NS_sig3=OLD3&__NS_xfalcon=OLDX"
)
request_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/requestMobileCode?"
"did=ANDROID_APP_DID&sig=OLD&__NS_sig3=OLD3&__NS_xfalcon=OLDX"
)
login_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/login/mobileVerifyCode?"
"did=ANDROID_APP_DID&sig=OLD&__NS_sig3=OLD3&__NS_xfalcon=OLDX"
)
events = [
{
"tag": "LOGINHELPER_B_PHONE",
"in_value": "13800000000",
"out_value": "CHECKER+ENC==",
},
{
"tag": "REQUEST_BUILD",
"method": "POST",
"url": checker_url,
"body": (
"mobileCountryCode=+86&mobile=CHECKER+ENC==&cs=false&client_key=2ac2a76d"
"&videoModelCrowdTag=&os=android&uQaTag=&passport_account_image=VIMG_CHECKER$AI_1111"
),
},
{
"tag": "REQUEST_BUILD",
"method": "POST",
"url": request_url,
"body": (
"mobileCountryCode=+86&mobile=REQUEST+ENC==&type=27&useVoice=false"
"&needCheck=false&prefetchPhoneNumber=&requestSource=1"
"&passport_account_image=VIMG_REQUEST$AI_2222"
),
},
{
"tag": "REQUEST_BUILD",
"method": "POST",
"url": login_url,
"body": (
"isDegraded=false&code=123456&mobileCountryCode=+86&deviceMode=OnePlus(PJZ110)"
"&mobile=LOGIN+ENC==&prefetchPhoneNumber=&raw=1785044999746"
"&publicKey=PUBLIC&secret=SECRET&type=27&deviceName=OnePlus(PJZ110)"
"&passport_account_image=VIMG_LOGIN$AI_3333"
),
},
]
with tempfile.TemporaryDirectory() as td:
log = Path(td) / "probe.log"
log.write_text(
"\n".join("@@LOGIN " + json.dumps(event, ensure_ascii=False) for event in events),
encoding="utf-8",
)
fields = extract_fields(log)
self.assertEqual(fields["checker_host"], "az2-api.ksapisrv.com")
self.assertEqual(fields["checker_path"], "/rest/nebula/user/mobile/checker")
self.assertEqual(fields["query_params"], {"did": "ANDROID_APP_DID"})
self.assertEqual(fields["source_mobile"], "13800000000")
self.assertEqual(fields["checker_encrypted_mobile"], "CHECKER+ENC==")
self.assertEqual(fields["request_encrypted_mobile"], "REQUEST+ENC==")
self.assertEqual(fields["encrypted_mobile"], "LOGIN+ENC==")
self.assertEqual(fields["checker_passport_account_image"], "VIMG_CHECKER$AI_1111")
self.assertEqual(fields["request_passport_account_image"], "VIMG_REQUEST$AI_2222")
self.assertEqual(fields["passport_account_image"], "VIMG_LOGIN$AI_3333")
self.assertEqual(
fields["checker_body_keys"],
[
"mobileCountryCode",
"mobile",
"cs",
"client_key",
"videoModelCrowdTag",
"os",
"uQaTag",
"passport_account_image",
],
)
def test_extract_fields_from_all_layers_begin_chunk_log(self):
"""probe_nebula_all_layers 的 BEGIN/CHUNK 日志也能提取三段字段。"""
checker_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/mobile/checker?"
"did=ANDROID_APP_DID&sig=OLD&__NS_sig3=OLD3"
)
request_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/requestMobileCode?"
"did=ANDROID_APP_DID&sig=OLD&__NS_xfalcon=OLDX"
)
login_url = (
"https://az2-api.ksapisrv.com/rest/nebula/user/login/mobileVerifyCode?"
"did=ANDROID_APP_DID&sig=OLD"
)
def begin(seq, tag, payload):
return f"@@BEGIN\t{seq}\t{tag}\t" + json.dumps(payload, ensure_ascii=False)
def chunk(seq, name, value):
return f"@@CHUNK\t{seq}\t{name}\t0\t" + json.dumps(value, ensure_ascii=False)
lines = [
begin(10, "OKHTTP_CHAIN_BEFORE_REQ", {"method": "POST", "url": checker_url, "tid": 7}),
begin(11, "OKHTTP_CHAIN_BEFORE_REQ_BODY", {"tid": 7}),
chunk(11, "utf8", "mobileCountryCode=%2B86&mobile=CHECKER%2BENC%3D%3D&passport_account_image=VIMG_CHECKER%24AI_1111"),
begin(20, "OKHTTP_CHAIN_BEFORE_REQ", {"method": "POST", "url": request_url, "tid": 7}),
begin(21, "OKHTTP_CHAIN_BEFORE_REQ_BODY", {"tid": 7}),
chunk(21, "utf8", "mobileCountryCode=%2B86&mobile=REQUEST%2BENC%3D%3D&type=27&prefetchPhoneNumber=&passport_account_image=VIMG_REQUEST%24AI_2222"),
begin(30, "OKHTTP_CHAIN_BEFORE_REQ", {"method": "POST", "url": login_url, "tid": 7}),
begin(31, "OKHTTP_CHAIN_BEFORE_REQ_BODY", {"tid": 7}),
chunk(31, "utf8", "code=123456&mobileCountryCode=%2B86&mobile=LOGIN%2BENC%3D%3D&prefetchPhoneNumber=&deviceName=OnePlus(PJZ110)&passport_account_image=VIMG_LOGIN%24AI_3333"),
begin(32, "CRONET_ADD_HEADER", {"name": "kaw", "tid": 7}),
chunk(32, "value", "KAW_VERSION_CONFIG"),
]
with tempfile.TemporaryDirectory() as td:
log = Path(td) / "probe_login_stable.log"
log.write_text("\n".join(lines), encoding="utf-8")
fields = extract_fields(log)
self.assertEqual(fields["checker_host"], "az2-api.ksapisrv.com")
self.assertEqual(fields["checker_path"], "/rest/nebula/user/mobile/checker")
self.assertEqual(fields["query_params"], {"did": "ANDROID_APP_DID"})
self.assertEqual(fields["checker_encrypted_mobile"], "CHECKER+ENC==")
self.assertEqual(fields["request_encrypted_mobile"], "REQUEST+ENC==")
self.assertEqual(fields["encrypted_mobile"], "LOGIN+ENC==")
self.assertEqual(fields["checker_passport_account_image"], "VIMG_CHECKER$AI_1111")
self.assertEqual(fields["request_passport_account_image"], "VIMG_REQUEST$AI_2222")
self.assertEqual(fields["passport_account_image"], "VIMG_LOGIN$AI_3333")
self.assertEqual(fields["weapon_kaw"], "KAW_VERSION_CONFIG")
def test_extract_fields_reads_mixed_utf8_utf16le_all_layers_log(self):
"""Start-Job/Tee 混合编码时仍能识别 BEGIN/CHUNK。"""
url = "https://az2-api.ksapisrv.com/rest/nebula/user/login/mobileVerifyCode?did=ANDROID_APP_DID&sig=OLD"
line1 = "@@BEGIN\t1\tOKHTTP_CHAIN_BEFORE_REQ\t" + json.dumps({"method": "POST", "url": url, "tid": 9})
line2 = "@@BEGIN\t2\tOKHTTP_CHAIN_BEFORE_REQ_BODY\t" + json.dumps({"tid": 9})
line3 = "@@CHUNK\t2\tutf8\t0\t" + json.dumps("mobileCountryCode=%2B86&mobile=LOGIN%2BENC%3D%3D&passport_account_image=VIMG_LOGIN%24AI_3333")
with tempfile.TemporaryDirectory() as td:
log = Path(td) / "probe_mixed.log"
body = "\r\n".join([line1, line2, line3]) + "\r\n"
log.write_bytes(b"\xef\xbb\xbfJOB_BEGIN pid=1\r\n" + body.encode("utf-16-le"))
fields = extract_fields(log)
self.assertEqual(fields["encrypted_mobile"], "LOGIN+ENC==")
self.assertEqual(fields["passport_account_image"], "VIMG_LOGIN$AI_3333")
if __name__ == "__main__":
unittest.main()