某某校园 App 协议与逆向分析

某某校园 App 协议与逆向分析

 次点击
29 分钟阅读

本次分析过程并非没有人受伤。

前言 — 一言难尽

新版的“某某校园”引入了大量抽象 anti‑cheat 机制,覆盖了多开、模拟器、虚拟机、so 注入、hook、调试器附加、SSL pinning 等多种检测,竭尽全力无法绕过

下面的内容以技术分析为主,请仅用于学习与研究,不要用于非法用途。

流程

抓包

该 app 启用了严格的 SSL pinning 策略。几乎无法直接绕过。

思路:使用旧版本的 APK、重写覆盖版本检测api。使用的是 reqable(自带 Py脚本支持,类似 Fiddler 的局域网抓包,还有Postman的api测试)非常推荐

17a0912e1c077e249139bd3318b70c98.png抓包后会发现大量接口返回的数据是加密过的,同时请求中带有 sign 字段 —— 于是开始逆向 app,破解签名/加解密算法。

  1. 脱壳解包

  2. 使用 jadx 等工具查看 dex

  3. 抽取加/解密密钥与算法实现,验证并复现加密/解密与签名逻辑。

下面给出部分逆向过程(旧版本 dex,但经过验证及对新版本逆向,逻辑与新版相同):

网易易盾的壳,https://56.al 可在线脱壳 有实力的🈸可自行手撕

有趣的是,这个网站根据哈希判断,我上传上去 apk 后就秒脱了,意味着有人已经脱过了,看记录是在九月份

开逆

找到添加Sign请求头的代码

Pasted image 20251011125313.png

查找相关用例 跟进

Pasted image 20251011125424.png

签名/加解密流程

Pasted image 20251011131350.png

理解完整的签名流程

e9d31e90cfd21684a32cbff4144f38ed.png

19875556797dbc5dbde9214be95fc373_720.png

获取密钥

跟进获取密钥的函数 看到判断

Pasted image 20251011140647-pPpT.png

(x7.d.b() == 9 || x7.d.b() == 11) ? "F44B0282BEA83557" : "huachenjie"

x7.d.b() 用于判断运行环境,初始化值为 9,因此选中的解密密钥为 F44B0282BEA83557

Pasted image 20251011143811-vrNJ.png

继续跟进相关函数,可以找到用于数据加密的第二个密钥与其他配套逻辑。

Pasted image 20251011141227-gWgZ.png


复现算法(Python)

根据逆向结果整理并复现核心算法:

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib
​
# ---------------- AES 工具 ----------------
IV = b"01234ABCDEF56789"
key_str = "F44B0282BEA83557"
​
​
def c(str2: str) -> bytes:
    """生成 32 字节 AES key"""
    if str2 is None:
        return None
    b = str2.encode('utf-8')
    if len(b) == 32:
        return b
    b_arr = bytearray(32)
    b_arr[:min(len(b), 32)] = b[:32]
    return bytes(b_arr)
​
​
def encrypt_aes(plaintext: str) -> str:
    key_bytes = c(key_str)
    cipher = AES.new(key_bytes, AES.MODE_CBC, IV)
    ciphertext = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size, style='pkcs7'))
    return base64.b64encode(ciphertext).decode('utf-8')
​
​
def decrypt_aes(cipher_b64: str) -> str:
    key_bytes = c(key_str)
    cipher = AES.new(key_bytes, AES.MODE_CBC, IV)
    ct_bytes = base64.b64decode(cipher_b64)
    pt_bytes = unpad(cipher.decrypt(ct_bytes), AES.block_size, style='pkcs7')
    return pt_bytes.decode('utf-8')
​
# ---------------- 哈希工具 ----------------
​
def obfuscated_hash(s: str, algo='sha256') -> str:
    """自定义 hash"""
    if not s:
        return ""
    h = hashlib.new(algo)
    h.update(s.encode('utf-8'))
    hexstr = h.hexdigest()
    if len(hexstr) < 16:
        return hexstr
    return hexstr[-8:] + hexstr[8:len(hexstr)-8] + hexstr[:8]
​
# ---------------- 签名计算 ----------------
​
def calculate_sign(data: str) -> str:
    """先哈希原文,再 AES 加密生成 sign"""
    hashed = obfuscated_hash(data)       # 先哈希
    sign = encrypt_aes(hashed)  # AES 加密 hash
    return sign

接口

抓包过程中发现若干 API

image-20251012145258692-zSYb.png

“阳光跑”接口

querySchoolFences

查询学校电子围栏信息,包括经纬度范围、开放时间、打卡点数量、面部识别要求等。

电子围栏为一个数组,具有至少三个经纬度信息点作为围栏

checkSunRunConfig

检查跑步计划配置,包括打卡方式、围栏、面部识别要求等信息

querySunRunAbstractInfoV2

获取指定阳光跑计划的汇总信息,同时下发学校要求的相关规定,包括但不限于禁止跑步时连接WIFI

uploadRunRecord

每 3 秒采样一次(包括围栏距离、经纬度、卫星信息等),并每 15 秒通过 uploadRunRecord 上报一次——一次上报包含这 15 秒内采样的 5 个点的数据。

finishSunRun

结束一次跑步,并上传跑步的最终数据(步数、距离、轨迹、配速等),用于计算成绩并生成跑步记录。

该 app 后台会通过该包上传的相关数据来进行作弊判断和数据分析,但不仅限于此

如果 app 中途异常退出数据丢失,会先通过 queryUnFinishRun 查询未完成的运动记录,然后通过 abnormalFinishSunRun 接口来结束异常运动,数据将不会被记录

其他接口大家自行探索吧,方法都摆在这里了,不过多赘述

需要注意的是,该app后台有着严格的反作弊检测,小心测试......


声明:本文仅用于技术分享与研究,禁止用于破坏、侵入、或其他违法活动。

© 本文著作权归作者所有,未经许可不得转载使用。