吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 92|回复: 0
上一主题 下一主题
收起左侧

[Android 原创] Interlink Network APK 逆向工程笔记

  [复制链接]
跳转到指定楼层
楼主
渔歌子 发表于 2026-5-14 21:12 回帖奖励
本帖最后由 渔歌子 于 2026-5-14 21:14 编辑

目标:从 APK 出发还原 Interlink Network React-Native + Hermes 应用的 API 签名机制(x-signature),并以 /api/v1/token/claim-airdrop 为例打通调用链。版本:app v5.0.1 (build 382),2026-05 抓包基准。


1. App 基本信息

应用名 Interlink Network
包名 org.ai.interlinklabs.interlinkId
版本 5.0.1 (VERSION_CODE=0x17e=382)
框架 React Native + Hermes (Engine v96,New Architecture)
Native 架构 arm64-v8a / armeabi-v7a
入口 Activity org/ai/interlinklabs/interlinkId/MainActivity
Application org/ai/interlinklabs/interlinkId/MainApplication

BuildConfig 关键常量(smali_out/c6/.../BuildConfig.smali

字段
APP_API_URL https://prod.interlinklabs.ai/api/v1
SSE_API_URL https://prod.interlinklabs.ai/notification/api/v1
WALLET_TOKEN_API_URL / WALLET_TRANSACTION_API_URL https://wallet-be.interlinklabs.ai/api-mobile
WALLET_NOTIFICATION_API_URL https://wallet-notify.interlinklabs.ai/noti
MINI_APP_API_URL https://interlink-mini-app.interlinklabs.ai/api
KYC_API_URL https://mini-app-staging.interlinklabs.org/interlink-kyc/api
MINI_APP_KEY e97ae0aa6520499d9edf20bd5a1e13c7(公开 api-public 头,明文写死)
IS_HERMES_ENABLED true
IS_NEW_ARCHITECTURE_ENABLED true
ENVIRONMENT PRODUCTION

其他扫描出的 API / 第三方域名

  • api-curator.interlinklabs.ai/api(旧业务域,仍在用)
  • captcha.interlinklabs.ai
  • evm-rpc.test-net.interlinklabs.ai/v1(自有 EVM 测试网)
  • explorer.test-net.interlinklabs.org
  • interlink-card.interlinklabs.ai
  • api.loopspace.xyz(第三方)
  • 钱包栈:WalletConnect v2(pulse / echo / verify / rpc.walletconnect.org)
  • 集成链:ETH / BSC / Polygon / Arbitrum / Optimism / Base / Avalanche / Linea / Holesky / Sepolia + ITL(自有)

2. APK 文件结构 & 关键资产

app.apk (≈189 MB)
├── AndroidManifest.xml
├── classes.dex … classes17.dex   # 17 个 dex(启用了 multidex),合计 ≈130 MB
├── lib/
│   ├── arm64-v8a/                # 我们重点看的 native lib
│   │   ├── libhermes.so          # Hermes JS 引擎
│   │   ├── libreact-native-keys.so   # ★ 解密 RN-Keys 加密配置
│   │   └── …
│   └── armeabi-v7a/
├── assets/
│   ├── index.android.bundle      # ★ Hermes 字节码(22 MB),所有业务 JS 在这
│   ├── *.tflite                  # MLKit / KYC 模型
│   └── …
├── res/                          # 二进制 XML(resources.arsc)
└── META-INF/

Hermes bundle 不可直接运行——它是字节码。文本搜索找业务逻辑必须先反编译为伪代码。


3. Hermes Bundle 反编译步骤(hermes-dec

3.1 安装

# 已有 uv 的环境(推荐,不污染全局 Python)
uv tool install hermes-dec

# 验证
hbc-file-parser --version    # 输出 0.1.3
which hbc-disassembler hbc-decompiler

落到 ~/.local/bin/ 的可执行:

  • hbc-file-parser — 解析 header / 元信息
  • hbc-disassembler — 字节码反汇编(→ .hasm
  • hbc-decompiler — 伪代码反编译(→ 类 JS)
  • hermes-dec-regen-html / hermes-dec-regen-pydefs[-regexp] — 重建 opcode 定义

3.2 三步反编译

# 1) 从 APK 抠 bundle
unzip -p app.apk assets/index.android.bundle > bundle.hbc

# 2) header & 元信息(5 KB)
hbc-file-parser bundle.hbc > header.txt
# 关键字段示例:Hermes v96 / FunctionCount=95564 / StringCount=134487 / RegExpCount=959

# 3) 反汇编 + 反编译两件套都跑(互相对照)
hbc-disassembler bundle.hbc bundle.hasm        # opcode 级(279 MB)
hbc-decompiler   bundle.hbc bundle.pseudo.js   # 类 JS(157 MB / 4.2M 行)

3.3 输出特点 & 阅读技巧

  • 伪代码不是真 JS:寄存器形如 r0…r17、有 _closure*_slot* 槽、generator 用显式 _funXXX_ip 状态机展开。
  • 跳转case <addr>: … _funXXX_ip = N; continue _funXXX;,跟着 case 标签跑就行。
  • 闭包共享:模块作用域变量被编译为 _closureN_slotM,跨模块查值要看 r0.__d(...) 注册时的 [依赖id 数组]
  • 对象字面量完整保留,比如 axios create({ headers: {...} }) 这种关键配置仍可读。
  • **巨大 r1[i] = r0**模式 \= ES Object.defineProperty 批量定义,常见于 service 类导出。

3.4 高效检索套路

# 找 API 路径
grep -nE "'/[a-z][a-z0-9/_-]+'" bundle.pseudo.js | head

# 找方法名(hermes-dec 保留了 "Original name: xxx" 注释)
grep -n 'Original name: claimAirdrop' bundle.pseudo.js

# 找 axios interceptor 入口
grep -nE 'interceptors\.(request|response)' bundle.pseudo.js

# 字符串常量定位(hermes 字符串表会进伪代码)
grep -nE "'HMAC_|'x-(signature|date|unique)" bundle.pseudo.js

4. RN Android APK 逆向通用知识

4.1 工具链 & 选择

工具 何时用 备注
unzip / 7z / apktool 拆 APK 拿 dex / so / assets apktool d 同时解 res,慢
jadx dex → Java 伪源码(最直观) 大项目 OOM,需 JAVA_OPTS=-Xmx8g;本项目 17 dex 单机 2GB 内存装不下
baksmali dex → smali(汇编级) 内存友好 ★,本项目实战首选
apksigner / aapt 证书 / manifest 信息
Frida 运行时 hook、dump 内存常量 拿密钥/Token 最快
IDA / Ghidra .so 静态分析 提取 native 内嵌 RSA / AES key

4.2 常见混淆手法 & 应对

  1. R8 字符串/反射混淆:dex 里 strings 找不到字面量。
    → 用 baksmali 反汇编全部 dex,再 grep 类签名(如 Lokhttp3/Interceptor;)+ HmacSHA256 / SecretKeySpec 这类 API 名定位拦截器。
  2. react-native-keys 加密 secrets
    • 编译期把 secrets.json 用 RSA 公钥加密 → 字符串塞到 com.reactnativekeysjsi.PrivateKey.privatekey
    • 私钥编进 libreact-native-keys.soJava_com_reactnativekeysjsi_KeysModule_getJniJsonStringifyData JNI 解密。
    • 业务 JS 通过 secureFor("KEY_NAME") 拿明文。
      dex / so / bundle 里都搜不到字面 secret,只能 Frida 或扒 .so
  3. Hermes bytecode 不存源码字符串:要先反编译 bundle 才能搜业务 path / 字段名。
  4. OkHttp 自定义拦截器签名:典型路径
    • okhttp3.Interceptor 实现类 →
    • intercept(Chain) 里的 request.newBuilder().addHeader(...)
    • 跟踪签名字符串拼接 + Mac.getInstance("HmacSHA256") / MessageDigest.getInstance("SHA-256")
      本项目签名不在拦截器,而在 JS 层(见 §6)。

4.3 17 dex 反汇编命令模板(内存受限环境)

unzip -q -o app.apk -d apk_extracted
mkdir -p smali_out
for n in '' 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17; do
  out=smali_out/c${n:-0}
  [ -d "$out" ] && continue
  java -Xmx1500m -jar baksmali.jar d apk_extracted/classes${n}.dex -o "$out"
done

# 全局 grep 用例
grep -rln '"HmacSHA256"' smali_out/
grep -rln '\.implements Lokhttp3/Interceptor;' smali_out/

5. /api/v1/token/claim-airdrop 反编译分析

5.1 接口本体(SpinelEKYCService.claimAirdropToken

伪代码大约在 bundle.pseudo.js:2063596

// 等价还原
async function claimAirdropToken() {
  return axios.post(`${BASE_URL}/token/claim-airdrop`, /* body = */ undefined);
}

调用链

  • React Hook useClaimToken()useMutation({ mutationFn: () => SpinelEKYCService.claimAirdropToken() })
  • UI Hook useWalletClaim() 暴露 handleClaimToken,按钮 onPressmutate({})
  • onSuccess 后 invalidate:AUTH.USER_FULL / PROFILE.RANDOM_ADS_MINING / GROUP_MINING.{LIST,DETAIL} / HCS.BY_LOGIN_ID / QK_GET_RECOVERY_* 三件套
  • onError 上报埋点 CLAIM_INDIVIDUAL_ERROR

5.2 这个请求的"问题"清单

# 问题 证据 影响
1 POST body \= undefined r0 = undefined; r2.bind(r3)(r1, r0) axios 不带 Content-Type、空 body → 服务端期待 JSON 时 400/422
2 客户端收集了点击坐标但没上传 onPress_closure5_slot0..3 = nativeEvent.{locationX,locationY,pageX,pageY},下游 mutate({}) 没带 强烈疑似反作弊"真人坐标"校验,客户端漏传
3 没带 captcha token app 集成 captcha.interlinklabs.ai,但 mutationFn 不接收任何参数 服务端开启 captcha → 403
4 没带幂等 key / 风控字段 同时存在 /token/number-user-duplicate-claim-airdrop 重复检测端点,本接口请求体空 防刷字段缺失
5 前置 UI 锁 isEmailConfirmed=falsenot_defined_email 模态;isClaimable=false → 倒计时 条件不满足按钮根本不发请求
6 错误吞噬 onError 只记 CLAIM_INDIVIDUAL_ERROR,前端通用错误 modal 服务端真实报错被埋点淹没

5.3 服务端可能要求的最小请求骨架

curl -X POST 'https://prod.interlinklabs.ai/api/v1/token/claim-airdrop' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "version: 5.0.1" \
  -H "x-platform: android" \
  -H "x-bundle-id: org.ai.interlinklabs.interlinkId" \
  -H "x-system-name: Android" \
  -H "x-brand: google" \
  -H "x-model: Pixel 4" \
  -H "x-device-id: 3732b0aac88b56ab" \
  -H "x-unique-id: 3732b0aac88b56ab" \
  -H "x-date: $(node -e 'console.log(Date.now())')" \
  -H "x-signature: <见 §6 算法>" \
  -d '{}'

6. x-signature 查找过程 & 生成算法

6.1 排查路径回顾

阶段 动作 结果
1 grep 'x-signature' over JS 伪代码 1 处命中(mini-app submitScore),全局机制无
2 strings over 17 dex 字面量(被 RN-Keys 加密)
3 strings over 所有 .so 字面量
4 找 OkHttp Interceptor 实现 + HmacSHA256 候选都是第三方 SDK,业务自有拦截器不存在
5 反查 react-native-keys 模块 命中 KeysModule.getSecureFor / 加密载荷 PrivateKey.privatekey / JNI libreact-native-keys.so
6 在 JS 伪代码里找 secureFor 调用 命中 HMAC_CONSTANTS 完整 key 表 + generateHmacHeaders 函数

结论:签名在 JS 层完成,密钥与所有 header 名通过 secureFor() 在运行时解密注入。

6.2 HMAC_CONSTANTS(全部 key 名都是 secureFor 取的)

HMAC_HASH_SECRET                 // HMAC 主密钥(值未知,需 Frida)
HMAC_SIGNATURE_HEADER_NAME       // = "x-signature"
REQUEST_DATE_HEADER_NAME         // = "x-date"
REQUEST_CONTENT_HASH_HEADER_NAME // 请求体 hash 头名(仅 JSON body 时附)
MAX_REQUEST_MINUTES_ALLOW        // 服务端 x-date 容忍窗(分钟)
REQUEST_UNIQUE_ID                // = "x-unique-id"
REQUEST_MODEL                    // = "x-model"
REQUEST_BRAND                    // = "x-brand"
REQUEST_SYSTEM_NAME              // = "x-system-name"
REQUEST_DEVICE_ID                // = "x-device-id"
REQUEST_BUNDLE_ID                // = "x-bundle-id"

axios 实例创建时还固定带:

{ 'Content-Type': 'application/json', 'Accept': '*/*', 'version': '5.0.1', 'x-platform': 'android' }

6.3 generateHmacHeaders 还原

import CryptoJS from 'crypto-js';

function generateHmacHeaders({ method, url, baseURL, params, body, contentType }) {
  const SECRET = HMAC_CONSTANTS.HMAC_HASH_SECRET;     // 由 RNKeys 解出
  if (!SECRET) return {};

  const M    = method.toUpperCase();                  // GET / POST / ...
  const date = String(Date.now());                    // 毫秒时间戳字符串

  // 1) 拼完整 URL(如有 params 则 ?qs.stringify(params))
  let full = (baseURL || '') + url;
  if (params && Object.keys(params).length > 0) {
    const qs = qsStringify(params);
    if (qs) full += '?' + qs;
  }

  // 2) URL 解析(host + pathname+search)
  const u    = new URL(full);
  const host = u.host;                                // e.g. prod.interlinklabs.ai
  const path = u.pathname + u.search;                 // e.g. /api/v1/token/claim-airdrop

  // 3) body 摘要:仅当 Content-Type 含 application/json
  let bodyHash = '';
  if (body && typeof contentType === 'string' && contentType.includes('application/json')) {
    bodyHash = CryptoJS.SHA256(JSON.stringify(body))
                       .toString(CryptoJS.enc.Base64);
  }

  // 4) message = 用 ";" 拼
  const parts = [M, path, date, host];
  if (bodyHash) parts.push(bodyHash);
  const message = parts.join(';');
  // 例: "POST;/api/v1/token/claim-airdrop;1778543721362;prod.interlinklabs.ai"

  // 5) HMAC-SHA256 → Base64
  const signature = CryptoJS.HmacSHA256(message, SECRET)
                            .toString(CryptoJS.enc.Base64);

  const headers = {
    [HMAC_CONSTANTS.REQUEST_DATE_HEADER_NAME]:   date,         // x-date
    [HMAC_CONSTANTS.HMAC_SIGNATURE_HEADER_NAME]: signature,    // x-signature
  };
  if (bodyHash && HMAC_CONSTANTS.REQUEST_CONTENT_HASH_HEADER_NAME) {
    headers[HMAC_CONSTANTS.REQUEST_CONTENT_HASH_HEADER_NAME] = bodyHash;
  }
  return headers;
}

6.4 message 拼接规则一句话

message = METHOD ";" PATH+SEARCH ";" DATE_MS ";" HOST [ ";" SHA256_BASE64(JSON.stringify(body)) ]
x-signature = base64( HMAC-SHA256( message, HMAC_HASH_SECRET ) )
x-date      = String(Date.now())

服务端会校验 |now - x-date| < MAX_REQUEST_MINUTES_ALLOW * 60_000,超时就 401。

6.5 设备类 header 来源

x-unique-id / x-device-id / x-model / x-brand / x-system-name / x-bundle-idsecureFor() 解出;react-native-device-info 在运行时取,对应:

  • x-bundle-idgetBundleId()org.ai.interlinklabs.interlinkId
  • x-system-namegetSystemName()Android
  • x-brandgetBrand()google
  • x-modelgetModel() ≡ 设备型号(e.g. Pixel 4
  • x-unique-id / x-device-idgetUniqueId() / getAndroidId(),均为 16 字符 hex

7. Frida 一键 Dump 方案

7.1 前置

  • 已 root 的真机或 Android 模拟器(推荐 Genymotion / Android Studio AVD x86_64)
  • pip install frida-tools + 设备端 frida-server 启动
  • 装本 APK:adb install app.apk

7.2 dump 脚本:dump_rn_secure_keys.js

// frida -U -f org.ai.interlinklabs.interlinkId -l dump_rn_secure_keys.js
Java.perform(function () {
  const KM = Java.use('com.reactnativekeysjsi.KeysModule');

  const keys = [
    'HMAC_HASH_SECRET',
    'HMAC_SIGNATURE_HEADER_NAME',
    'REQUEST_DATE_HEADER_NAME',
    'REQUEST_CONTENT_HASH_HEADER_NAME',
    'MAX_REQUEST_MINUTES_ALLOW',
    'REQUEST_UNIQUE_ID', 'REQUEST_MODEL', 'REQUEST_BRAND',
    'REQUEST_SYSTEM_NAME', 'REQUEST_DEVICE_ID', 'REQUEST_BUNDLE_ID',
  ];

  // 等 RN bridge / native lib 起来再调
  setTimeout(() => {
    keys.forEach(k => {
      try { console.log(k, '=', KM.getSecureFor(k)); }
      catch (e) { console.log(k, 'ERR', e); }
    });
  }, 3000);
});

7.3 运行

# 1) 拉 frida-server 到设备(按 CPU 架构选)
adb push frida-server-arm64 /data/local/tmp/fs && adb shell "su -c 'chmod 755 /data/local/tmp/fs && /data/local/tmp/fs &'"

# 2) 启动 + hook
frida -U -f org.ai.interlinklabs.interlinkId -l dump_secrets.js --no-pause

预期输出(值是占位):

HMAC_HASH_SECRET = <随机字符串/十六进制>
HMAC_SIGNATURE_HEADER_NAME = x-signature
REQUEST_DATE_HEADER_NAME = x-date
REQUEST_CONTENT_HASH_HEADER_NAME = x-content-hash
MAX_REQUEST_MINUTES_ALLOW = 5
REQUEST_UNIQUE_ID = x-unique-id
REQUEST_MODEL = x-model
REQUEST_BRAND = x-brand
REQUEST_SYSTEM_NAME = x-system-name
REQUEST_DEVICE_ID = x-device-id
REQUEST_BUNDLE_ID = x-bundle-id

7.4 备选:直接 hook 出站请求(不用扒密钥)

如果你只想验证算法对不对、或绕过密钥提取:

// hook axios 包装层,直接 dump 已签名请求
Java.perform(function () {
  const NetworkingModule = Java.use('com.facebook.react.modules.network.NetworkingModule');
  NetworkingModule.sendRequestInternal.implementation = function (method, url, requestId, headers, data, responseType, useIncrementalUpdates, timeout, withCredentials) {
    console.log('[REQ]', method, url);
    const it = headers.iterator();
    while (it.hasNext()) console.log('  ', it.next().toString());
    if (data) console.log('  body:', data.toString());
    return this.sendRequestInternal.apply(this, arguments);
  };
});

7.5 拿到密钥之后

HMAC_HASH_SECRET 填到 §6.3 的 generateHmacHeaders,配合一个 Node 项目就能离线复现整套签名 → curl/axios 直接打通 claim-airdrop 等所有 prod.interlinklabs.ai 接口。


8. 风险与合规提醒

  • 自动化签到 / 领空投属于 ToS 灰色地带,账号封禁风险自负。
  • 不要把 HMAC_HASH_SECRET 公开到 GitHub / 公网;它一旦泄露足以伪造任意请求。
  • 如果只是研究学习,请仅在自己账号上测试,不要批量刷接口造成 DDoS。

附录:本次工作的产物路径

tools/rn-rev/
├── in/
│   ├── app.apk                       # 197 MB
│   └── bundle.hbc                    # 22 MB Hermes 字节码
├── apk_extracted/                    # unzip app.apk 直出
├── smali_out/c0..c17/                # 全部 dex 反汇编
├── out/
│   ├── header.txt                    # bundle 元信息
│   ├── bundle.hasm (.gz)             # 字节码反汇编
│   └── bundle.pseudo.js (.gz)        # 类 JS 伪代码(4.2M 行)
├── bin/                              # jadx + baksmali
└── docs/interlink-reverse.md         # 本文档

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-16 04:01

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表