当前位置: 首页 > news >正文

MOBILE-灰签名回廊

ISCC2026 WriteUp 提交模板

MOBILE-灰签名回廊

凌晨两点,内网审计系统捕获到一台“已下线终端”的异常心跳。
它像一座被遗弃的保险库,所有入口都还在,却没有任何一把完整钥匙。
你能拿到的只有零散线索:一段来自内容接口的回声、一条只在握手后出现的广播、一扇需要特定深链唤醒的隐门,以及一层看似多余却总在最后收口的校验。
这台终端最狡猾的地方,不在于把答案藏得深,而在于它让“正确路径”看起来像“错误捷径”。
有人试图跳步,有人试图硬猜,有人只盯着终值;
最后都卡在同一个问题上:
你看到的是结果,还是结果背后的因果。

解题思路

1.追踪字符串
通过搜索ISCC定位到,package com.example.gnd.security;

image.png

image.png

数组和0x11异或后就是ISCC{前缀

image.png

这里是一段Frida检测

image.png

这里是对flag做最终hash验证。

image.png

四段flag验证。

image.png

fragment获取

image.png

所以flag,java层检验流程,把flag的body部分切成 7 + 5 + 5 + 7 四段,最后验证hash。

2.确认fragement
这个是通过分析AndroidManifest.xml文件,发现里面有三个隐藏的组件

image.png

image.png

SecretContentProvider — fragment2

// content://com.example.gnd.secret.provider/keys/fragment
int[] {100, 71, 111, 100} XOR 23 = {'s', 'P', 'x', 's'} → "sPxs"
f00.t("sPxs")       // 自定义Base64编码
→ SharedPreferences("key_cache")["fragment2"]

无需认证,直接 query 即可拿到 fragment2。

image.png

image.png

token == "ISCC2026" SP receiver_auth 写入 authenticated=true + 时间戳
需 5 分钟内认证过  part == 3 {11,16,12,110} → QKT7

image.png

byte[] {73,83,67,67,50,48,50,100,117} = "ISCC202du"
取 bArr[7]='d', bArr[8]='u' → "du" → f00.t("du")
→ SharedPreferences("key_cache")["fragment4"]

3.接着回java层的四段flag校验,开始分析native检测。

image.png

符号没混淆,直接分析

image.png

第一步分,开始是个小反调试,然后是个根据获取到的flag_length走不同分支,我们已经确认了这里length一定是7,看7的逻辑即可。

image.png

image.png

其实就是:

// 逐字节: output[i] = (input[i] + 1) ^ 7
for (int i = 0; i < 7; i++) {dest[i] = (dest[i] + 1) ^ 7;
}

image.png

输出 7 字节回到 Java 后与 a(certDigest) 对比,验证 flag 第1段。先用 apksigner 取签名:

133415c3f8aad739be8d0df905481208aa7448da7254a474028eeeddfc2849af

image.png

然后就是这段比较逆回去,就能直接得到:

part1 = JdoAXrf

image.png

逆向求解 part2(已知 key = fragment2 解码 = "sPxs"),
还原出:

part2 = _Y7Gk

image.png

这是第三段校验:
求解后:part3 = _MLKz

3.收尾

image.png

Java_com_example_gnd_security_SecureValidator_nativeComputeChainToken校验函数
会把:

  1. 第一段校验后的返回字节
  2. fragment2 = sPxs
  3. fragment3 = QKT7
    串起来算出一个 chain token

Java_com_example_gnd_security_SecureValidator_nativeVerifyPart4函数:
它用前面几段算出来的 chain token + 隐藏组件给的 fragment4,拼出一个 14 字节 RC4 key,然后只校验 part4 的前 4 个字节,最后 3 个字节完全不看。

image.png

然后把最后两个字节替换成:

fragment4[0:2] ^ "du"

同时把 chainToken 低 32 位拆成 4 个字节,再分别异或 "S" "e" "c" "u":

key_prefix = low32(chainToken) ^ "Secu"

所以最终 KSA 用的 key 实际是:

(chain_low32 ^ "Secu") || "sPxsQKT7" || (fragment4 ^ "du")

总长度正好 14 字节。

image.png

  1. 0x5e206 ~ 0x5e33d 初始化并打乱 256 字节状态表 这段就是标准 RC4 KSA:

    • 先构造 S = [0,1,2,...,255]
    • 然后做:
      j = (j + S[i] + key[i % 14]) & 0xff; swap(S[i], S[j]);

    反编译里这个很绕的表达式:*(&v34 + v13 + -14 * (v13 / 0xE))本质就是 key[i % 14]。

  2. 0x5e33f ~ 0x5e413 跑 4 轮 PRGA
    这里只生成 4 个 keystream 字节,并分别异或 part4[0..3]:
    b0 = part4[0] ^ ks0 b1 = part4[1] ^ ks1 b2 = part4[2] ^ ks2 b3 = part4[3] ^ ks3
    注意:part4[4]、part4[5]、part4[6] 根本没被读。

  3. 0x5e41b ~ 0x5e447 做最终比较
    4 个字节不会直接逐字节比,而是压成一个掩码后比较:
    (((((b0 << 6) ^ (b1 << 3) ^ b2) << 12) ^ (b3 << 9)) & 0x3FF0000) == 0x2DC0000

可以把把第四段前缀压到 _UqW...
最后通过已知hash爆破最后三位,定位到pfF

image.png

ISCC{JdoAXrf_Y7Gk_MLKz_UqWpfF}

Exp

from __future__ import annotationsimport base64
import hashlib
import itertools
import string
from dataclasses import dataclassCUSTOM_B64_ALPHABET = "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/"
STANDARD_B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
TARGET_FLAG_SHA256 = "3a52a4f84f9100272987f903da1c54ff9161813f122bac64b143ab9bb8d6a2ac"PART1_VECTOR = bytes([76, 98, 119, 69, 94, 116, 96])
PART2_MAGIC = bytes.fromhex("18094f34")
PART3_MAGIC = bytes.fromhex("9fa989b4")PART4_PREFIX = "_UqW"
BRUTEFORCE_ALPHABET = "_" + string.ascii_letters + string.digits@dataclass(frozen=True)
class HiddenFragments:fragment2: bytesfragment3: bytesfragment4: bytesdef decode_custom_b64(token: str) -> bytes:table = str.maketrans(CUSTOM_B64_ALPHABET, STANDARD_B64_ALPHABET)normalized = token.translate(table)normalized += "=" * (-len(normalized) % 4)return base64.b64decode(normalized)def collect_hidden_fragments() -> HiddenFragments:fragment2 = bytes(value ^ 0x17 for value in (100, 71, 111, 100))fragment3 = bytes((index ^ value) ^ 0x5A for index, value in enumerate((11, 16, 12, 110)))fragment4 = b"du"return HiddenFragments(fragment2, fragment3, fragment4)def recover_part1() -> str:recovered = bytes(((value ^ 0x07) - 1) & 0xFF for value in PART1_VECTOR)return recovered.decode("latin1")def recover_part2(fragment2: bytes) -> str:recovered = bytes([0x2C ^ fragment2[0],PART2_MAGIC[1] ^ fragment2[1],PART2_MAGIC[2] ^ fragment2[2],PART2_MAGIC[3] ^ fragment2[3],PART2_MAGIC[0] ^ fragment2[0],])return recovered.decode("latin1")def recover_part3(fragment3: bytes) -> str:recovered = bytes([((0x97 - fragment3[0]) & 0xFF) ^ 0x19,((PART3_MAGIC[0] - fragment3[1]) & 0xFF) ^ 0x19,((PART3_MAGIC[1] - fragment3[2]) & 0xFF) ^ 0x19,((PART3_MAGIC[2] - fragment3[3]) & 0xFF) ^ 0x19,((PART3_MAGIC[3] - fragment3[0]) & 0xFF) ^ 0x19,])return recovered.decode("latin1")def recover_part4(body_prefix: str) -> str:for tail in itertools.product(BRUTEFORCE_ALPHABET, repeat=3):candidate = PART4_PREFIX + "".join(tail)flag = f"ISCC{{{body_prefix}{candidate}}}"if hashlib.sha256(flag.encode()).hexdigest() == TARGET_FLAG_SHA256:return candidateraise RuntimeError("failed to recover part4")def validate_fragments(fragments: HiddenFragments) -> None:assert decode_custom_b64("x8Y5xd") == fragments.fragment2assert decode_custom_b64("FFgFMd") == fragments.fragment3assert decode_custom_b64("ASF") == fragments.fragment4def solve() -> str:fragments = collect_hidden_fragments()validate_fragments(fragments)part1 = recover_part1()part2 = recover_part2(fragments.fragment2)part3 = recover_part3(fragments.fragment3)body_prefix = part1 + part2 + part3part4 = recover_part4(body_prefix)return f"ISCC{{{body_prefix}{part4}}}"def main() -> None:flag = solve()print(flag)print(hashlib.sha256(flag.encode()).hexdigest())if __name__ == "__main__":main()
http://www.jsqmd.com/news/845950/

相关文章:

  • 广州代理清关公司实力排行:合规与效率双重维度解析 - 互联网科技品牌测评
  • 博尚机械木材粉碎机智能防卡技术解析|新手也能24小时稳干,故障率≤0.5% - 会飞的懒猪
  • 深圳市老蚂蚁搬家服务:罗湖专业的居民搬家公司怎么联系 - LYL仔仔
  • 基于低代码平台与4G DTU的智能垃圾桶物联网开发实战
  • STM32新手避坑指南:用L298N驱动直流电机,PWM调速的完整配置流程(附源码)
  • AI+STEAM教育方案:基于边缘计算的智能硬件与算法部署实践
  • 【IEEE 出版】第六届控制与智能机器人国际学术会议(ICCIR 2026) - 爱搞科研的小刘
  • 2026年宁夏银川B2B企业获客与网络营销完全指南:从短视频到AI-GEO优化的全链路破局方案 - 精选优质企业推荐官
  • 联发科天玑700/720/900核心板选型指南:5G物联网与智能硬件性能功耗全解析
  • 足不出户变卖手表,郑州本地正规门店上门高价回收全攻略 - 奢侈品回收测评
  • 2026大学生学数据分析对求职的价值
  • LLM工程实践指南:从RAG到微调,构建高效应用知识体系
  • STM32F303CBT6资源够用吗?实测EtherCAT从站(IO+AD+DA)的内存与Flash占用分析
  • 终极免费解决方案:Pearcleaner如何彻底清理macOS应用残留文件
  • 聚焦汽车零部件质控,西恩士全自动清洁度检测设备筑牢汽车工业安全防线 - 精密仪器科技圈
  • ret2shell靶场运维学习:把docker上传到平台
  • 苏州5家黄金奢侈品回收机构实力评级,从报价到回款全程实测 - 奢侈品回收测评
  • 智能对象替换革命:Illustrator自动化工作流的核心引擎
  • 边缘计算AI盒子4路AHD、4路千兆网、4G/5G通讯,标准API接口,兼容Modbus、DLT645、OPC UA等多种行业协议
  • 2026年北京短视频代运营与AI搜索优化服务商深度横评:如何找到真正能带来商机的合作伙伴 - 企业名录优选推荐
  • 告别时序噩梦:Vivado的report_qor_suggestions从导出RQS到导入生效全流程避坑指南
  • 想注册公司商标,找哪家服务机构比较靠谱?这份 2026 最新测评报告给你答案! - 速递信息
  • 终极指南:如何使用免费开源工具SMUDebugTool实现AMD处理器深度调试与精准控制
  • Perplexity认证黄金窗口期即将关闭:2024年Q4起将启用L3难度动态题库,现在拿证=锁定AI可信度背书
  • 对比直接使用官方api体验taotoken在api密钥管理与审计上的便利
  • 2026年北京短视频代运营与AI搜索优化全链路获客方案深度评测 - 企业名录优选推荐
  • Kafka 运维命令与监控搭建实战手册
  • 20260519 找工作感受 - 枝-致
  • 百度网盘Mac版加速完整指南:三步破解限速,免费享受SVIP极速下载
  • 2026年宁夏B2B企业短视频获客与AI-GEO推广完全指南:银川品牌策划与网络营销服务商深度横评 - 精选优质企业推荐官