【2026春节】全自动AI做题的实现及初8逆向AIAgent对话记录及wp

admin 2026-04-02 04:08:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了一个全自动AI做题项目的实现,该项目能模拟解题思路,自动完成从分析、调用工具到提交答案的全过程。它基于Go和React开发,特别适用于CTF比赛中的Web、Pwn、逆向等题目,也可用于代码审计和渗透测试。项目核心在于将整个解题流程智能化,并通过多模型协作、上下文压缩和反思总结等机制来提升效率和准确性。文中还分享了一个安卓逆向题目的完整AI解题对话记录。 综合评分: 85 文章分类: CTF,AI安全,WEB安全,逆向分析,渗透测试


[cases] 初二题目 PE32 XOR password recovery (relevance: 0.41)

Windows PE32 reverse challenge where hardcoded DWORDs were XORed to recover password. Binary had Chinese filename causing path issues. Solution extracted 6 DWORD values from binary and XORed them sequentially.


[cases] Windows PE32 crackme with XOR-obfuscated password and weighted checksum validation (relevance: 0.41)

52pojie Spring Festival CTF ‘Day 2’ challenge: PE32 console app with fake flag hint, 31-char password requirement, XOR-deobfuscation (key 0x42), and weighted checksum (sum of position*value = 44709). Key insight: ignore fake flag, reverse sub_401620’s DWORD buffer XOR logic.


[cases] reverse1: Simple character replacement validation in PE x64 binary (relevance: 0.40)

Challenge: PE x64 Windows executable that validates user input against a hardcoded flag. Key insight: The flag string is stored in the binary’s data section and can be extracted directly via IDA Pro. The validation logic replaces all ‘o’ characters with ‘0’ before comparison. Solution: Use IDA Pro headless to decompile main function, identify the global string variable (Str2), extract its value, apply the character replacement transformation, and wrap with flag{} format.


[cases] SUCTF2018 single dog – AAEncode in MISC image (relevance: 0.38)

Misc image steganography challenge. Used binwalk to extract embedded zip+txt from image file. Hidden content was AAEncoded, decoded to get flag. Key insight: check hex for file inclusion anomalies, use binwalk for extraction.

🤖 助手 – 第 2 轮 [14:00:31]

💭 思考过程

Let me extract the 7z file and analyze the contents.

📦 解压: D:\AI\AICTF\workdir\52pojie\chu8\chu8.7z

📋 extract_archive 输出 [14:00:31]

 复制代码 隐藏代码
$ 7z x D:\AI\AICTF\workdir\52pojie\chu8\chu8.7z -> D:\AI\AICTF\workdir\52pojie\chu8\extracted

7-Zip 24.06 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-05-26

Scanning the drive for archives:
1 file, 2955939 bytes (2887 KiB)

Extracting archive: D:\AI\AICTF\workdir\52pojie\chu8\chu8.7z
--
Path = D:\AI\AICTF\workdir\52pojie\chu8\chu8.7z
Type = 7z
Physical Size = 2955939
Headers Size = 275
Method = LZMA2:6m 7zAES
Solid = -
Blocks = 1

Everything is Ok

Size:       4459647
Compressed: 2955939

Extracted 1 files:
  【2026春节】解题领红包之八 {Android 中级题} 出题老师:正己.apk

🤖 助手 – 第 3 轮 [14:00:37]

💭 思考过程

Good, extracted an APK file. Let me examine it and create a plan.

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\extracted (模式: *)

📋 查找结果 [14:00:37]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\extracted
D:\AI\AICTF\workdir\52pojie\chu8\extracted\【2026春节】解题领红包之八 {Android 中级题} 出题老师:正己.apk

🤖 助手 – 第 4 轮 [14:00:41]

🔨 执行命令

 复制代码 隐藏代码
$ copy "D:\AI\AICTF\workdir\52pojie\chu8\extracted\【2026春节】解题领红包之八 {Android 中级题} 出题老师:正己.apk""D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk"

📋 命令输出 [14:00:42]

 复制代码 隐藏代码

### 🤖 助手 - 第 5 轮 [14:00:46]

#### 🔨 执行命令

 复制代码 隐藏代码 $ file “D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk”


📋 命令输出 [14:00:47]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk: Zip archive data, at least v0.0 to extract, compression method=deflate

🤖 助手 – 第 6 轮 [14:00:53]

好,这是一个 APK 文件(Android 应用)。让我先解压看看结构,然后用 jadx 反编译。

🔨 执行命令

 复制代码 隐藏代码
$ mkdir"D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped" && cd /d "D:\AI\AICTF\workdir\52pojie\chu8" && 7z x chu8.apk -oapk_unzipped -y 2>&1 | head -30

📋 命令输出 [14:00:54]

 复制代码 隐藏代码

    Directory: D:\AI\AICTF\workdir\52pojie\chu8

[32;1mMode   [0m[32;1m              LastWriteTime[0m [32;1;3m        Length[0m[32;1m Name[0m
[32;1m----   [0m [32;1m             -------------[0m [32;1m        ------[0m [32;1m----[0m
d----           2026/2/24    14:00                [44;1mapk_unzipped[0m
[31;1mSet-Location: [31;1mA positional parameter cannot be found that accepts argument 'D:\AI\AICTF\workdir\52pojie\chu8'.[0m

[exit code: exit status 1]

🤖 助手 – 第 7 轮 [14:00:58]

📦 解压: D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk

📋 extract_archive 输出 [14:00:58]

 复制代码 隐藏代码
$ 7z x D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk -> D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (fallback)

7-Zip 24.06 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-05-26

Scanning the drive for archives:
1 file, 4459647bytes (4356 KiB)

Extracting archive: D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk
--
Path = D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk
Type = zip
Physical Size = 4459647

Everything is Ok

Files:161
Size:       5290531
Compressed:4459647

Extracted 50 files:
AndroidManifest.xml
  DebugProbesKt.bin
  META-INF
  META-INF\androidx.activity_activity-compo
... [truncated 3011bytes] ...
  META-INF\androidx.profileinstaller_profileinstaller.version
  META-INF\androidx.savedstate_savedstate-ktx.version
  META-INF\androidx.savedstate_savedstate.version
  META-INF\androidx.startup_startup-runtime.version
  META-INF\androidx.tracing_tracing.version
  META-INF\androidx.versionedparcelable_versionedparcelable.version
  META-INF\com
  META-INF\com\android
  META-INF\com\android\build
  META-INF\com\android\build\gradle
  META-INF\com\android\build\gradle\app-metadata.properties
  META-INF\kotlinx_coroutines_android.version
  META-INF\kotlinx_coroutines_core.version
  ... (truncated)

🤖 助手 – 第 8 轮 [14:01:03]

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *.dex)

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *.so)

📋 查找结果 [14:01:03]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\classes.dex

📋 查找结果 [14:01:03]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\arm64-v8a\libhajimi.so
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\armeabi-v7a\libhajimi.so
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\x86\libhajimi.so
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\x86_64\libhajimi.so

🤖 助手 – 第 9 轮 [14:01:11]

💭 思考过程

This is an Android reverse engineering challenge. It has:

  1. classes.dex file (Java/Kotlin code)
  2. A native library libhajimi.so for multiple architectures

Let me first decompile the DEX to understand the Java/Kotlin layer, then analyze the native library. Let me use jadx for the DEX and IDA for the SO.

有 DEX 和 native SO 库。让我先用 jadx 反编译 DEX,再用 IDA 分析 SO。

🔨 执行命令

 复制代码 隐藏代码
$ where jadx 2>nul || echo"jadx not found"

🔨 执行命令

 复制代码 隐藏代码
$ file "D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\x86_64\libhajimi.so"

📋 命令输出 [14:01:12]

 复制代码 隐藏代码

📋 命令输出 [14:01:12]

 复制代码 隐藏代码 D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so: ELF 64-bit LSB sharedobject, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=270f490cb71fbaa3b51330dd72f8d9bc7738ffe0, stripped


🤖 助手 – 第 10 轮 [14:01:18]

🔨 工具调用: todolist

  • action:

    reset

  • tasks:

    [1. 用jadx反编译APK,分析Java/Kotlin层逻辑,找到验证入口和native方法调用 2. 用IDA分析libhajimi.so (x86_64),找到JNI函数和核心验证逻辑 3. 分析加密/验证算法,提取密钥和常量 4. 编写脚本逆向算法,恢复flag 5. 提交flag]

📋 todolist 输出 [14:01:18]

 复制代码 隐藏代码
TodoList reset with 5task(s):
⬜ 1. 用jadx反编译APK,分析Java/Kotlin层逻辑,找到验证入口和native方法调用
⬜ 2. 用IDA分析libhajimi.so (x86_64),找到JNI函数和核心验证逻辑
⬜ 3. 分析加密/验证算法,提取密钥和常量
⬜ 4. 编写脚本逆向算法,恢复flag
⬜ 5. 提交flag

🤖 助手 – 第 11 轮 [14:01:23]

🔨 工具调用: todolist

  • action:

    update

  • id:

    1

  • status:

    in_progress

📋 todolist 输出 [14:01:23]

 复制代码 隐藏代码
Updated task #1. Current TodoList:
🔄 1. 用jadx反编译APK,分析Java/Kotlin层逻辑,找到验证入口和native方法调用
⬜ 2. 用IDA分析libhajimi.so (x86_64),找到JNI函数和核心验证逻辑
⬜ 3. 分析加密/验证算法,提取密钥和常量
⬜ 4. 编写脚本逆向算法,恢复flag
⬜ 5. 提交flag

🤖 助手 – 第 12 轮 [14:01:28]

🔨 执行命令

 复制代码 隐藏代码
$ jadx -d "D:\AI\AICTF\workdir\52pojie\chu8\jadx_out""D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk" --no-res 2>&1 | tail -10

📋 命令输出 [14:01:29]

 复制代码 隐藏代码
[31;1mjadx: [31;1mThe term 'jadx' is not recognized as a name of a cmdlet, function, script file, or executable program.[0m
[31;1m[31;1mCheck the spelling of the name, or if a path was included, verify that the path is correct and try again.[0m

[exit code: exit status 1]

🤖 助手 – 第 13 轮 [14:01:35]

🔨 执行命令

 复制代码 隐藏代码
$ dir"D:\AI\AICTF\Tools" 2>nul && dir"D:\AI\AICTF\Tools\jadx*" 2>nul

📋 命令输出 [14:01:35]

 复制代码 隐藏代码

    Directory: D:\AI\AICTF\Tools

[32;1mMode   [0m[32;1m              LastWriteTime[0m [32;1;3m        Length[0m[32;1m Name[0m
[32;1m----   [0m [32;1m             -------------[0m [32;1m        ------[0m [32;1m----[0m
d----           2026/2/14     9:19                [44;1mIDA[0m
d----           2026/2/19    14:46                [44;1mjadx[0m
d----           2026/2/19    14:53                [44;1mphp[0m
-a---           2026/2/19    14:40       25926183 apktool_2.12.1.jar
-a---           2026/2/24    13:49         524288 [31;1mphp.zip[0m
-a---            2025/5/6    10:19         599040 [32;1mupx.exe[0m
-a---           2026/2/11    16:46        5927424 [32;1mwsrx.exe[0m
d----           2026/2/19    14:46                [44;1mjadx[0m

🤖 助手 – 第 14 轮 [14:01:39]

🔨 执行命令

 复制代码 隐藏代码
$ dir"D:\AI\AICTF\Tools\jadx" 2>nul

📋 命令输出 [14:01:40]

 复制代码 隐藏代码

    Directory: D:\AI\AICTF\Tools\jadx

[32;1mMode   [0m[32;1m              LastWriteTime[0m [32;1;3m        Length[0m[32;1m Name[0m
[32;1m----   [0m [32;1m             -------------[0m [32;1m        ------[0m [32;1m----[0m
d----           2026/2/19    14:46                [44;1mbin[0m
d----           2026/2/19    14:46                [44;1mlib[0m
-----            1980/2/1     0:00          11357 LICENSE
-----            1980/2/1     0:00          17870 README.md

🤖 助手 – 第 15 轮 [14:01:44]

🔨 执行命令

 复制代码 隐藏代码
$ "D:\AI\AICTF\Tools\jadx\bin\jadx.bat" -d "D:\AI\AICTF\workdir\52pojie\chu8\jadx_out""D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk" --no-res 2>&1 | tail -10

📋 命令输出 [14:01:45]

 复制代码 隐藏代码
[31;1mParserError: [0m
[31;1m[36;1mLine |[0m
[31;1m[36;1m[36;1m   1 | [0m … ext.Encoding]::UTF8; "D:\AI\AICTF\Tools\jadx\bin\jadx.bat" [36;1m-d[0m "D:\AI\ …[0m
[31;1m[36;1m[36;1m[0m[36;1m[0m[36;1m     | [31;1m                                                              ~~[0m
[31;1m[36;1m[36;1m[0m[36;1m[0m[36;1m[31;1m[31;1m[36;1m     | [31;1mUnexpected token '-d' in expression or statement.[0m

[exit code: exit status 1]

👤 用户 [14:01:45]

[Progress Checkpoint – Round 16/200] Briefly assess your progress:

  1. What have you accomplished so far?
  2. Are you making progress toward the flag? Continue with your current approach if it shows promise.

🤖 助手 – 第 16 轮 [14:01:53]

💭 思考过程

Let me assess and continue. I’ve downloaded and extracted the APK, found it has a native library libhajimi.so. I need to use jadx to decompile the Java layer and IDA to analyze the SO. The jadx command is failing due to PowerShell parsing issues with the -d flag. Let me fix the command.

Progress: Downloaded APK, extracted it, found libhajimi.so native library and classes.dex. Jadx command failing due to PowerShell parsing. Let me fix that and continue.

🔨 执行命令

 复制代码 隐藏代码
$ cmd /c "D:\AI\AICTF\Tools\jadx\bin\jadx.bat" -d "D:\AI\AICTF\workdir\52pojie\chu8\jadx_out""D:\AI\AICTF\workdir\52pojie\chu8\chu8.apk" --no-res 2>&1

📋 命令输出 [14:01:59]

 复制代码 隐藏代码
INFO  - loading ...
INFO  - processing ...
INFO  - progress: 0 of 1394 (0%)
INFO  - progress: 242 of 1394 (17%)
INFO  - progress: 451 of 1394 (32%)
INFO  - progress: 706 of 1394 (50%)
INFO  - progress: 936 of 1394 (67%)
INFO  - progress: 1262 of 1394 (90%)

ERROR - finished with errors, count: 16

[exit code: exit status 1]

👤 用户 [14:01:59]

[TodoList Reminder – Round 17] Stats: 4 pending, 0 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 17 轮 [14:02:05]

💭 思考过程

jadx completed with some errors but should have output. Let me find the relevant source files.

jadx 反编译完成了(有些错误但应该有输出)。让我找到关键的 Java/Kotlin 源码。

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out (模式: *.java)

📋 查找结果 [14:02:05]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\AbstractC0154a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\b.java
。。。。

🤖 助手 – 第 18 轮 [14:02:10]

  • case_insensitive:

    true

  • max_matches:

    50

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    hajimi|native|loadLibrary|System\.load|check|verify|flag|52pojie

📋 strings_search 输出 [14:02:10](较长,点击展开)

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java:11:>import com.zj.wuaipojie2026_2.NativeBridge;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java:57:>            case NativeBridge.SCORE_GOOD /* 1 */:
.....

🤖 助手 – 第 19 轮 [14:02:16]

关键类是 NativeBridge。让我查看它。

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources (模式: NativeBridge*)

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources (模式: *wuaipojie*)

📋 查找结果 [14:02:16]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java

📋 查找结果 [14:02:16]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2

🤖 助手 – 第 20 轮 [14:02:21]

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2 (模式: *)

📋 查找结果 [14:02:21]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\R.java

🤖 助手 – 第 21 轮 [14:02:27]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java

📋 文件内容 [14:02:27]

 复制代码 隐藏代码
package com.zj.wuaipojie2026_2;

import f1.h;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassNativeBridge {
    publicstaticfinalint$stable=0;
    publicstaticfinalintERR_CHEAT= -7;
    publicstaticfinalNativeBridgeINSTANCE=newNativeBridge();
    publicstaticfinalintSCORE_GOOD=1;
    publicstaticfinalintSCORE_MISS=0;
    publicstaticfinalintSCORE_PERFECT=2;

    static {
        System.loadLibrary("hajimi");
    }

    privateNativeBridge() {
    }

    privatefinalnativevoidstartSessionBytes(long j2, byte[] bArr, int i2);

    publicfinalnativeintcheckRhythm(long j2, int i2, long j3, int i3);

    publicfinalnativebyte[] decryptFrames(byte[] bArr, long j2);

    publicfinalnativevoidsetDebugBypass(boolean z2);

    publicfinalvoidstartSession(long j2, int[] iArr, int i2) {
        h.e(iArr, "beatMapMs");
        ByteBufferbyteBufferOrder= ByteBuffer.allocate(iArr.length * 4).order(ByteOrder.LITTLE_ENDIAN);
        for (int i3 : iArr) {
            byteBufferOrder.putInt(i3);
        }
        byte[] bArrArray = byteBufferOrder.array();
        h.d(bArrArray, "array(...)");
        startSessionBytes(j2, bArrArray, i2);
    }

    publicfinalnativelongupdateExp(int i2, int i3, long j2);

    publicfinalnativebyte[] verifyAndDecrypt(byte[] bArr, String str);
}

📋 文件内容 [14:02:27]

 复制代码 隐藏代码
package com.zj.wuaipojie2026_2;

import A.e;
import Q0.AbstractC0080d;
import a.AbstractC0154a;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.activity.A;
import androidx.activity.B;
import androidx.activity.l;
import androidx.activity.m;
import androidx.activity.n;
import androidx.activity.o;
import androidx.activity.p;
import androidx.activity.q;
import androidx.compose.ui.platform.C0204f0;
import androidx.lifecycle.E;
import f1.h;
import h1.a;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassMainActivityextendsl {
    @Override// androidx.activity.l, w0.AbstractActivityC0756a, android.app.Activity
    publicfinalvoidonCreate(Bundle bundle) {
        super.onCreate(bundle);
        inti2= m.f1921a;
        Aa2= A.f1870j;
        Bb2=newB(0, 0, a2);
        Bb3=newB(m.f1921a, m.f1922b, a2);
        ViewdecorView= getWindow().getDecorView();
        h.d(decorView, "window.decorView");
        Resourcesresources= decorView.getResources();
        h.d(resources, "view.resources");
        booleanzBooleanValue= ((Boolean) a2.d0(resources)).booleanValue();
        Resourcesresources2= decorView.getResources();
        h.d(resources2, "view.resources");
        booleanzBooleanValue2= ((Boolean) a2.d0(resources2)).booleanValue();
        inti3= Build.VERSION.SDK_INT;
        qpVar= i3 >= 29 ? newp() : i3 >= 26 ? newo() : newn();
        Windowwindow= getWindow();
        h.d(window, "window");
        pVar.a(b2, b3, window, decorView, zBooleanValue, zBooleanValue2);
        eeVar= AbstractC0080d.f1133b;
        ViewGroup.LayoutParamslayoutParams= AbstractC0154a.f1861a;
        ViewchildAt= ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
        C0204f0c0204f0= childAt instanceof C0204f0 ? (C0204f0) childAt : null;
        if (c0204f0 != null) {
            c0204f0.setParentCompositionContext(null);
            c0204f0.setContent(eVar);
            return;
        }
        C0204f0c0204f02=newC0204f0(this);
        c0204f02.setParentCompositionContext(null);
        c0204f02.setContent(eVar);
        ViewdecorView2= getWindow().getDecorView();
        if (E.c(decorView2) == null) {
            E.g(decorView2, this);
        }
        if (E.d(decorView2) == null) {
            decorView2.setTag(R.id.view_tree_view_model_store_owner, this);
        }
        if (a.A(decorView2) == null) {
            a.a0(decorView2, this);
        }
        setContentView(c0204f02, AbstractC0154a.f1861a);
    }
}

🤖 助手 – 第 22 轮 [14:02:37]

很好!NativeBridge 类加载了 libhajimi.so,有几个关键 native 方法:

  • checkRhythm

  • 检查节奏

  • decryptFrames

  • 解密帧

  • verifyAndDecrypt

  • 验证并解密

  • updateExp

  • 更新经验值

  • startSessionBytes

  • 开始会话

  • setDebugBypass

  • 调试绕过

这看起来是一个音乐节奏游戏。让我看看 Compose UI 层怎么调用这些方法的。

🔨 工具调用: strings_search

  • case_insensitive:

    true

  • max_matches:

    100

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    verifyAndDecrypt|decryptFrames|checkRhythm|updateExp|startSession|setDebugBypass

📋 strings_search 输出 [14:02:37]

 复制代码 隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:23:>    privatefinalnativevoidstartSessionBytes(long j2, byte[] bArr, int i2);
。。。。

🤖 助手 – 第 23 轮 [14:02:43]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\r.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\z.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\v.java

📋 文件内容 [14:02:43]

 复制代码 隐藏代码
package Q0;

import com.zj.wuaipojie2026_2.NativeBridge;
import o1.InterfaceC0603z;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassrextendsY0.i implementse1.e {

    /* JADX INFO: renamed from: m, reason: collision with root package name */
    publicfinal/* synthetic */byte[] f1186m;

    /* JADX INFO: renamed from: n, reason: collision with root package name */
    publicfinal/* synthetic */ O f1187n;

    /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
    publicr(byte[] bArr, O o2, W0.d dVar) {
        super(2, dVar);
        this.f1186m = bArr;
        this.f1187n = o2;
    }

    @Override// e1.e
    publicfinal Object N(Object obj, Object obj2) {
        return ((r) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
    }

    @Override// Y0.a
    publicfinal W0.d c(W0.d dVar, Object obj) {
        returnnewr(this.f1186m, this.f1187n, dVar);
    }

    @Override// Y0.a
    publicfinal Object g(Object obj)throws Throwable {
        R.c.Y(obj);
        return NativeBridge.INSTANCE.decryptFrames(this.f1186m, this.f1187n.f1107a);
    }
}

📋 文件内容 [14:02:43]

 复制代码 隐藏代码
package Q0;

import com.zj.wuaipojie2026_2.NativeBridge;
import java.util.List;
import o1.InterfaceC0603z;
import t.InterfaceC0680W;

/* JADX INFO: loaded from: classes.dex */
publicfinalclasszextendsY0.i implementse1.e {

    /* JADX INFO: renamed from: m, reason: collision with root package name */
    publicfinal/* synthetic */byte[] f1217m;

    /* JADX INFO: renamed from: n, reason: collision with root package name */
    publicfinal/* synthetic */ InterfaceC0680W f1218n;

    /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
    publicz(byte[] bArr, InterfaceC0680W interfaceC0680W, W0.d dVar) {
        super(2, dVar);
        this.f1217m = bArr;
        this.f1218n = interfaceC0680W;
    }

    @Override// e1.e
    publicfinal Object N(Object obj, Object obj2) {
        return ((z) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
    }

    @Override// Y0.a
    publicfinal W0.d c(W0.d dVar, Object obj) {
        returnnewz(this.f1217m, this.f1218n, dVar);
    }

    @Override// Y0.a
    publicfinal Object g(Object obj)throws Throwable {
        R.c.Y(obj);
        NativeBridgenativeBridge= NativeBridge.INSTANCE;
        Listlist= N.f1106a;
        return nativeBridge.verifyAndDecrypt(this.f1217m, n1.k.j0((String) this.f1218n.getValue()).toString());
    }
}
📋 文件内容 [14:02:43](较长,点击展开)

 复制代码 隐藏代码
package Q0;

import E0.C0021h;
import android.os.SystemClock;
import com.zj.wuaipojie2026_2.NativeBridge;
import t.C0691d0;

/* JADX INFO: loaded from: classes.dex */
publicfinal/* synthetic */classvextendsf1.f implementse1.a {

    /* JADX INFO: renamed from: q, reason: collision with root package name */
    publicfinal/* synthetic */int f1199q;

    /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
    public/* synthetic */ v(int i2, Object obj, Class cls, String str, String str2, int i3, int i4) {
        super(i2, obj, cls, str, str2, i3);
        this.f1199q = i4;
    }

    @Override// e1.a
    publicfinal Object o() {
        int i2;
        O o2;
        switch (this.f1199q) {
            case0:
                C0082fc0082f= (C0082f) this.f3686j;
                if (!((O) c0082f.f1144f.getValue()).f1109c) {
                    longjElapsedRealtimeNanos= SystemClock.elapsedRealtimeNanos();
                    longj2= (jElapsedRealtimeNanos - c0082f.f1143e) / 1000000;
                    C0021hc0021h= c0082f.f1141c;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(j2 <&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0021h.getClass();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i2 =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0021h.getClass();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inti3=&nbsp;(int) (j2 % ((long)&nbsp;1000));
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int[] iArr = (int[]) c0021h.f318b;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intlength=&nbsp;iArr.length;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inti4=&nbsp;Integer.MAX_VALUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inti5=0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(inti6=0; i6 < length; i6++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intiAbs=&nbsp;Math.abs(i3 - iArr[i6]);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intiMin=&nbsp;Math.min(iAbs,&nbsp;1000&nbsp;- iAbs);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(iMin < i4) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i5 = i6;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i4 = iMin;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i2 = i5;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;longj3=&nbsp;(((long) i2) <<&nbsp;32) ^ jElapsedRealtimeNanos;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;longj4=&nbsp;j3 ^ (j3 <<&nbsp;13);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;longj5=&nbsp;j4 ^ (j4 >>&nbsp;7);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;longj6=&nbsp;j5 ^ (j5 <<&nbsp;17);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;C0691d0c0691d0=&nbsp;c0082f.f1144f;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inti7=&nbsp;((O) c0691d0.getValue()).f1107a >=&nbsp;50&nbsp;?&nbsp;1&nbsp;:&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NativeBridgenativeBridge=&nbsp;NativeBridge.INSTANCE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intiCheckRhythm=&nbsp;nativeBridge.checkRhythm(jElapsedRealtimeNanos, i2, j6, i7);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;longjUpdateExp=&nbsp;nativeBridge.updateExp(iCheckRhythm, i2, j6);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(jUpdateExp <&nbsp;0&nbsp;|| iCheckRhythm == -7) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; o2 =&nbsp;newO(((O) c0691d0.getValue()).f1107a, Q.Cheat,&nbsp;true);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Oo3=&nbsp;(O) c0691d0.getValue();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Qq2=&nbsp;iCheckRhythm !=&nbsp;0&nbsp;? iCheckRhythm !=&nbsp;1&nbsp;? iCheckRhythm !=&nbsp;2&nbsp;? Q.None : Q.Perfect : Q.Good : Q.Miss;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; o3.getClass();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; o2 =&nbsp;newO(jUpdateExp, q2,&nbsp;false);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0691d0.setValue(o2);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;NativeBridge.SCORE_GOOD&nbsp;/* 1 */:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((androidx.activity.x)&nbsp;this.f3686j).d();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((androidx.activity.x)&nbsp;this.f3686j).d();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;S0.k.f1286a;
&nbsp; &nbsp; }
}

🤖 助手 – 第 24 轮 [14:02:49]

🔨 工具调用: todolist

  • action:

    update

  • id:

    1

  • status:

    done

  • result:

    找到NativeBridge类,加载libhajimi.so,关键native方法:checkRhythm, updateExp, decryptFrames, verifyAndDecrypt, startSessionBytes, setDebugBypass。这是音乐节奏游戏,需要分析SO中的验证逻辑。

📋 todolist 输出 [14:02:49]

&nbsp;复制代码&nbsp;隐藏代码
Updated&nbsp;task&nbsp;#1. Current TodoList:
✅&nbsp;1. 用jadx反编译APK,分析Java/Kotlin层逻辑,找到验证入口和native方法调用 → 找到NativeBridge类,加载libhajimi.so,关键native方法:checkRhythm, updateExp, decryptFrames, verifyAndDecrypt, startSessionBytes, setDebugBypass。这是音乐节奏游戏,需要分析SO中的验证逻辑。
⬜&nbsp;2. 用IDA分析libhajimi.so (x86_64),找到JNI函数和核心验证逻辑
⬜&nbsp;3. 分析加密/验证算法,提取密钥和常量
⬜&nbsp;4. 编写脚本逆向算法,恢复flag
⬜&nbsp;5. 提交flag

👤 用户 [14:02:49]

[TodoList Reminder – Round 25] Stats: 4 pending, 1 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 25 轮 [14:02:56]

💭 思考过程

Let me now analyze the SO file with IDA. I’ll use the x86_64 version since it’s easier to analyze.

现在用 IDA 分析 SO 文件。这是核心逆向工作,让我委派给 reverse 子 agent。

🔨 工具调用: todolist

  • action:

    update

  • id:

    2

  • status:

    in_progress

📋 todolist 输出 [14:02:56]

&nbsp;复制代码&nbsp;隐藏代码
Updated&nbsp;task&nbsp;#2. Current TodoList:
✅&nbsp;1. 用jadx反编译APK,分析Java/Kotlin层逻辑,找到验证入口和native方法调用 → 找到NativeBridge类,加载libhajimi.so,关键native方法:checkRhythm, updateExp, decryptFrames, verifyAndDecrypt, startSessionBytes, setDebugBypass。这是音乐节奏游戏,需要分析SO中的验证逻辑。
🔄&nbsp;2. 用IDA分析libhajimi.so (x86_64),找到JNI函数和核心验证逻辑
⬜&nbsp;3. 分析加密/验证算法,提取密钥和常量
⬜&nbsp;4. 编写脚本逆向算法,恢复flag
⬜&nbsp;5. 提交flag

🤖 助手 – 第 26 轮 [14:03:02]

Let me also look for encrypted data in the APK (assets, raw resources) that might be the flag ciphertext, while I start IDA analysis.

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *)

📋 查找结果 [14:03:02](较长,点击展开)

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\AndroidManifest.xml
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\DebugProbesKt.bin
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity-compose.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.annotation_annotation-experimental.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.arch.core_core-runtime.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.autofill_autofill.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.animation_animation-core.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.animation_animation.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.foundation_foundation-layout.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.foundation_foundation.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.material3_material3.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.material_material-icons-core.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.material_material-ripple.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.runtime_runtime-saveable.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.runtime_runtime.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-geometry.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-graphics.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-text.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-tooling-preview.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-unit.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui-util.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.compose.ui_ui.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.core_core-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.core_core.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.customview_customview-poolingcontainer.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.emoji2_emoji2.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.interpolator_interpolator.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-livedata-core.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-process.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-runtime-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-runtime.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-viewmodel-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-viewmodel-savedstate.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.lifecycle_lifecycle-viewmodel.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.profileinstaller_profileinstaller.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.savedstate_savedstate-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.savedstate_savedstate.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.startup_startup-runtime.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.tracing_tracing.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.versionedparcelable_versionedparcelable.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\com
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\com\android
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\com\android\build
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\com\android\build\gradle
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\com\android\build\gradle\app-metadata.properties
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\kotlinx_coroutines_android.version

🤖 助手 – 第 27 轮 [14:03:08]

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *.bin)

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *.dat)

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped (模式: *.enc)

📋 查找结果 [14:03:08]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\DebugProbesKt.bin
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin

📋 查找结果 [14:03:08]

&nbsp;复制代码&nbsp;隐藏代码
No&nbsp;files found.

📋 查找结果 [14:03:08]

&nbsp;复制代码&nbsp;隐藏代码
No&nbsp;files found.

🤖 助手 – 第 28 轮 [14:03:15]

assets/hjm_pack.bin 看起来很关键!让我检查它。

🔨 执行命令

&nbsp;复制代码&nbsp;隐藏代码
$ file&nbsp;"D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin"

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

withopen(r"D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin",&nbsp;"rb")&nbsp;as&nbsp;f:
&nbsp; &nbsp; data = f.read()
print(f"Size:&nbsp;{len(data)}&nbsp;bytes")
print(f"First 64 bytes hex:&nbsp;{data[:64].hex()}")
print(f"First 64 bytes:&nbsp;{data[:64]}")
print(f"Last 32 bytes hex:&nbsp;{data[-32:].hex()}")

📋 命令输出 [14:03:16]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin:&nbsp;data

📋 Python 输出 [14:03:16]

&nbsp;复制代码&nbsp;隐藏代码
Size:&nbsp;564bytes
First&nbsp;64byteshex: 484a4d3102000000010000004000000040000000780000000f1e2d3c4b5a69788796a5b4c3d2e1f0112233445566778899aabbcc9641cd80feda2af5fb95dd2c
First&nbsp;64bytes:&nbsp;b'HJM1\x02\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00@\x00\x00\x00x\x00\x00\x00\x0f\x1e-<KZix\x87\x96\xa5\xb4\xc3\xd2\xe1\xf0\x11"3DUfw\x88\x99\xaa\xbb\xcc\x96A\xcd\x80\xfe\xda*\xf5\xfb\x95\xdd,'
Last&nbsp;32byteshex: d4c4d122d0f243d6770260c018b66ed6db8a1b903acbaa215ca816690497ccc1

🤖 助手 – 第 29 轮 [14:03:21]

HJM1 是自定义格式头。现在让我开 IDA 分析 SO 文件,这是核心。

🔨 工具调用: mcp_ida-pro-headless_idalib_open

  • input_path:

    D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\x86_64\libhajimi.so

📋 mcp_ida-pro-headless_idalib_open 输出 [14:03:23]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"success":&nbsp;true,
"session": {
&nbsp; &nbsp;&nbsp;"session_id":&nbsp;"2769f686",
&nbsp; &nbsp;&nbsp;"input_path":&nbsp;"D:\\AI\\AICTF\\workdir\\52pojie\\chu8\\apk_unzipped\\lib\\x86_64\\libhajimi.so",
&nbsp; &nbsp;&nbsp;"filename":&nbsp;"libhajimi.so",
&nbsp; &nbsp;&nbsp;"created_at":&nbsp;"2026-02-24T14:03:23.380456",
&nbsp; &nbsp;&nbsp;"last_accessed":&nbsp;"2026-02-24T14:03:23.380456",
&nbsp; &nbsp;&nbsp;"is_analyzing":&nbsp;false,
&nbsp; &nbsp;&nbsp;"metadata": {}
&nbsp; },
"message":&nbsp;"Binary opened successfully: libhajimi.so"
}

🤖 助手 – 第 30 轮 [14:03:28]

🔨 工具调用: mcp_ida-pro-headless_list_funcs

  • queries:

    {"filter": "Java_*", "count": 50}

📋 mcp_ida-pro-headless_list_funcs 输出 [14:03:28]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"data":[],
&nbsp; &nbsp;&nbsp;"next_offset":null
&nbsp;&nbsp;}
]

👤 用户 [14:03:28]

[Progress Checkpoint – Round 31/200] Briefly assess your progress:

  1. What have you accomplished so far?
  2. Are you making progress toward the flag? Continue with your current approach if it shows promise.

🤖 助手 – 第 31 轮 [14:03:36]

💭 思考过程

No JNI functions with standard naming. The SO likely uses dynamic registration. Let me look at JNI_OnLoad and exported functions.

No standard JNI naming — likely uses dynamic registration. Let me find JNI_OnLoad and exports.

🔨 工具调用: mcp_ida-pro-headless_list_funcs

  • queries:

    {"filter": "*JNI*", "count": 50}

🔨 工具调用: mcp_ida-pro-headless_list_funcs

  • queries:

    {"filter": "*OnLoad*", "count": 50}

📋 mcp_ida-pro-headless_list_funcs 输出 [14:03:36]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"data":[
&nbsp; &nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x237c0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":"JNI_OnLoad",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":"0x8e"
&nbsp; &nbsp; &nbsp;&nbsp;}
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"next_offset":null
&nbsp;&nbsp;}
]

📋 mcp_ida-pro-headless_list_funcs 输出 [14:03:36]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"data":[
&nbsp; &nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x237c0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":"JNI_OnLoad",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":"0x8e"
&nbsp; &nbsp; &nbsp;&nbsp;}
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"next_offset":null
&nbsp;&nbsp;}
]

🤖 助手 – 第 32 轮 [14:03:41]

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x237c0

📋 mcp_ida-pro-headless_decompile 输出 [14:03:41]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x237c0",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall JNI_OnLoad(__int64 a1)\n{\n&nbsp; unsigned int v1; // ebx\n&nbsp; __int64 v2; // rax\n&nbsp; _QWORD v4[3]; // [rsp+0h] [rbp-18h] BYREF\n\n&nbsp; v4[1] = __readfsqword(0x28u); /*0x237ce*/\n&nbsp; v4[0] = 0; /*0x237d3*/\n&nbsp; v1 = -1; /*0x237e9*/\n&nbsp; if ( !(*(unsigned int (__fastcall **)(__int64, _QWORD *, __int64))(*(_QWORD *)a1 + 48LL))(a1, v4, 65542) )\n&nbsp; {\n&nbsp; &nbsp; v2 = (*(__int64 (__fastcall **)(_QWORD, const char *))(*(_QWORD *)v4[0] + 48LL))( /*0x23800*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v4[0],\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"com/zj/wuaipojie2026_2/NativeBridge\");\n&nbsp; &nbsp; if ( v2 )\n&nbsp; &nbsp; &nbsp; return (*(unsigned int (__fastcall **)(_QWORD, __int64, char **, __int64))(*(_QWORD *)v4[0] + 1720LL))(\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v4[0],\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v2,\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;off_5CCA0,\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;6) != 0\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;? -1\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: 0x10006;\n&nbsp; }\n&nbsp; return v1; /*0x23843*/\n}"
}

👤 用户 [14:03:41]

[TodoList Reminder – Round 33] Stats: 3 pending, 1 done, 0 failed. You have 3 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 33 轮 [14:03:47]

动态注册表在 off_5CCA0,注册了 6 个方法。让我读取这个表。

🔨 工具调用: mcp_ida-pro-headless_get_bytes

  • regions:

    map[addr:0x5CCA0 size:192]

📋 mcp_ida-pro-headless_get_bytes 输出 [14:03:47]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"addr":"0x5CCA0",
&nbsp; &nbsp;&nbsp;"data":"0x81 0x67 0x1 0x0 0x0 0x0 0x0 0x0 0xda 0x58 0x1 0x0 0x0 0x0 0x0 0x0 0xa0 0x38 0x2 0x0 0x0 0x0 0x0 0x0 0xb1 0x5c 0x1 0x0 0x0 0x0 0x0 0x0 0x3e 0x4e 0x1 0x0 0x0 0x0 0x0 0x0 0x50 0x3e 0x2 0x0 0x0 0x0 0x0 0x0 0xca 0x56 0x1 0x0 0x0 0x0 0x0 0x0 0x20 0x4d 0x1 0x0 0x0 0x0 0x0 0x0 0x60 0x3f 0x2 0x0 0x0 0x0 0x0 0x0 0xd4 0x6d 0x1 0x0 0x0 0x0 0x0 0x0 0x4 0x5e 0x1 0x0 0x0 0x0 0x0 0x0 0x20 0x45 0x2 0x0 0x0 0x0 0x0 0x0 0x9a 0x55 0x1 0x0 0x0 0x0 0x0 0x0 0xab 0x55 0x1 0x0 0x0 0x0 0x0 0x0 0x50 0x48 0x2 0x0 0x0 0x0 0x0 0x0 0xc7 0x54 0x1 0x0 0x0 0x0 0x0 0x0 0xe2 0x6d 0x1 0x0 0x0 0x0 0x0 0x0 0xa0 0x4c 0x2 0x0 0x0 0x0 0x0 0x0 0x0 0x11 0x5 0x0 0x0 0x0 0x0 0x0 0x90 0x92 0x3 0x0 0x0 0x0 0x0 0x0 0xc0 0x93 0x3 0x0 0x0 0x0 0x0 0x0 0xae 0x67 0x1 0x0 0x0 0x0 0x0 0x0 0x10 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0x0"
&nbsp;&nbsp;}
]

🤖 助手 – 第 34 轮 [14:03:58]

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct

data =&nbsp;bytes([0x81,0x67,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0xda,0x58,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0xa0,0x38,0x2,0x0,0x0,0x0,0x0,0x0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xb1,0x5c,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x3e,0x4e,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x50,0x3e,0x2,0x0,0x0,0x0,0x0,0x0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xca,0x56,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x20,0x4d,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x60,0x3f,0x2,0x0,0x0,0x0,0x0,0x0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xd4,0x6d,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x04,0x5e,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x20,0x45,0x2,0x0,0x0,0x0,0x0,0x0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x9a,0x55,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0xab,0x55,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0x50,0x48,0x2,0x0,0x0,0x0,0x0,0x0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0xc7,0x54,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0xe2,0x6d,0x1,0x0,0x0,0x0,0x0,0x0,&nbsp;0xa0,0x4c,0x2,0x0,0x0,0x0,0x0,0x0])

# JNINativeMethod: { const char* name, const char* signature, void* fnPtr } - 3 pointers each
for&nbsp;i&nbsp;inrange(6):
&nbsp; &nbsp; off = i *&nbsp;24
&nbsp; &nbsp; name_ptr = struct.unpack_from('<Q', data, off)[0]
&nbsp; &nbsp; sig_ptr = struct.unpack_from('<Q', data, off+8)[0]
&nbsp; &nbsp; fn_ptr = struct.unpack_from('<Q', data, off+16)[0]
&nbsp; &nbsp;&nbsp;print(f"Method&nbsp;{i}: name=0x{name_ptr:x}, sig=0x{sig_ptr:x}, fn=0x{fn_ptr:x}")

📋 Python 输出 [14:03:58]

&nbsp;复制代码&nbsp;隐藏代码
Method&nbsp;0: name=0x16781, sig=0x158da, fn=0x238a0
Method&nbsp;1: name=0x15cb1, sig=0x14e3e, fn=0x23e50
Method&nbsp;2: name=0x156ca, sig=0x14d20, fn=0x23f60
Method&nbsp;3: name=0x16dd4, sig=0x15e04, fn=0x24520
Method&nbsp;4: name=0x1559a, sig=0x155ab, fn=0x24850
Method&nbsp;5: name=0x154c7, sig=0x16de2, fn=0x24ca0

🤖 助手 – 第 35 轮 [14:04:04]

🔨 工具调用: mcp_ida-pro-headless_get_string

  • addrs:

    ["0x16781", "0x158da", "0x15cb1", "0x14e3e", "0x156ca", "0x14d20", "0x16dd4", "0x15e04", "0x1559a", "0x155ab", "0x154c7", "0x16de2"]

📋 mcp_ida-pro-headless_get_string 输出 [14:04:04]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"addr":"0x16781",
&nbsp; &nbsp;&nbsp;"value":"startSessionBytes"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x158da",
&nbsp; &nbsp;&nbsp;"value":"(J[BI)V"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x15cb1",
&nbsp; &nbsp;&nbsp;"value":"checkRhythm"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x14e3e",
&nbsp; &nbsp;&nbsp;"value":"(JIJI)I"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x156ca",
&nbsp; &nbsp;&nbsp;"value":"updateExp"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x14d20",
&nbsp; &nbsp;&nbsp;"value":"(IIJ)J"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x16dd4",
&nbsp; &nbsp;&nbsp;"value":"decryptFrames"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x15e04",
&nbsp; &nbsp;&nbsp;"value":"([BJ)[B"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x1559a",
&nbsp; &nbsp;&nbsp;"value":"verifyAndDecrypt"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x155ab",
&nbsp; &nbsp;&nbsp;"value":"([BLjava/lang/String;)[B"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x154c7",
&nbsp; &nbsp;&nbsp;"value":"setDebugBypass"
},
{
&nbsp; &nbsp;&nbsp;"addr":"0x16de2",
&nbsp; &nbsp;&nbsp;"value":"(Z)V"
}
]

🤖 助手 – 第 36 轮 [14:04:11]

现在我有了完整的 JNI 方法映射。让我反编译所有关键函数:

🔨 工具调用: mcp_ida-pro-headless_rename

  • batch:

    map[func:[map[addr:0x238a0 name:startSessionBytes] map[addr:0x23e50 name:checkRhythm] map[addr:0x23f60 name:updateExp] map[addr:0x24520 name:decryptFrames] map[addr:0x24850 name:verifyAndDecrypt] map[addr:0x24ca0 name:setDebugBypass]]]

📋 mcp_ida-pro-headless_rename 输出 [14:04:11]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"func":[
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x238a0",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"startSessionBytes",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x23e50",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"checkRhythm",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x23f60",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"updateExp",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x24520",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"decryptFrames",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x24850",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"verifyAndDecrypt",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;"addr":"0x24ca0",
&nbsp; &nbsp; &nbsp;&nbsp;"name":"setDebugBypass",
&nbsp; &nbsp; &nbsp;&nbsp;"ok":true,
&nbsp; &nbsp; &nbsp;&nbsp;"error":null,
&nbsp; &nbsp; &nbsp;&nbsp;"dir":"vibe",
&nbsp; &nbsp; &nbsp;&nbsp;"dir_error":null
&nbsp; &nbsp;&nbsp;}
]
}

👤 用户 []

[Context compacted — previous conversation summary follows]

→ Tool call: mcp_ida-pro-headless_decompile({“addr”: “0x24850”})

“addr”: “0x24850”, “code”: “int64fastcall verifyAndDecrypt(int64 a1,int64 a2, int64 a3,int64 a4)\n{\n  int64 v4; // rax\nint64 v5; // rax\n  int64 v6; // rax\nint64 v620; // rax\n  int64 v621; // rax\nint64 v622; // r…

🤖 助手 – 第 37 轮 [14:04:17]

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x24850

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x24520

📋 mcp_ida-pro-headless_decompile 输出 [14:04:17]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24850",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall verifyAndDecrypt(__int64 *a1, __int64 a2, __int64 a3, __int64 a4)\n{\n&nbsp; __int64 v5; // rax\n&nbsp; int v8; // eax\n&nbsp; unsigned int v9; // ebp\n&nbsp; unsigned __int64 v10; // rbx\n&nbsp; char *v11; // r12\n&nbsp; size_t v12; // rdx\n&nbsp; char *v13; // rbx\n&nbsp; __int64 v14; // rax\n&nbsp; __int64 v15; // r15\n&nbsp; __int64 v17; // rax\n&nbsp; __int64 v18; // rcx\n&nbsp; __int64 v19; // rsi\n&nbsp; __int64 v20; // rdx\n&nbsp; __int64 v21; // rax\n&nbsp; u...

📋 mcp_ida-pro-headless_decompile 输出 [14:04:17]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24520",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall decryptFrames(__int64 a1, __int64 a2, __int64 a3, __int64 a4)\n{\n&nbsp; int v7; // eax\n&nbsp; __int64 v8; // rdx\n&nbsp; unsigned int v9; // ebp\n&nbsp; size_t v10; // r12\n&nbsp; _DWORD *v11; // rbx\n&nbsp; __int64 v12; // rax\n&nbsp; __int64 v13; // r15\n&nbsp; __int64 v15; // rax\n&nbsp; __int64 v16; // rcx\n&nbsp; __int64 v17; // rsi\n&nbsp; __in...

🤖 助手 – 第 38 轮 [14:04:25]

这很复杂。让我也看看其他关键子函数和 startSessionBytescheckRhythmupdateExp

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x238a0

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x23e50

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x23f60

📋 mcp_ida-pro-headless_decompile 输出 [14:04:25]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x238a0",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall startSessionBytes(__int64 a1, __int64 a2, __int64 a3, __int64 a4, int a5)\n{\n&nbsp; int v9; // eax\n&nbsp; unsigned int v10; // ecx\n&nbsp; unsigned int v11; // ecx\n&nbsp; unsigned int v12; // ebx\n&nbsp; __int64 v13; // r14\n&nbsp; unsigned __int64 v14; // rcx\n&nbsp; __int64 v15; // rsi\n&nbsp; __int64 v16; // rdi\n&nbsp; unsigned __int64 v17; // rdx\n&nbsp; __int64 v....

📋 mcp_ida-pro-headless_decompile 输出 [14:04:25]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x23e50",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall checkRhythm(__int64 a1, __int64 a2, __int64 a3, int a4, __int64 a5, int a6)\n{\n&nbsp; __int64 result; // rax\n&nbsp; unsigned __int64 v7; // rdx\n&nbsp; unsigned __int64 v8; // rsi\n&nbsp; unsigned int v9; // eax\n&nbsp; int v10; // esi\n&nbsp; int v11; // eax\n&nbsp; int v12; // edi\n&nbsp; char v13; // r8\n&nbsp; int v14; // ecx\n&nbsp; int v15; // r10d\n\n&nbsp; result = 0; /*0x23...

📋 mcp_ida-pro-headless_decompile 输出 [14:04:25]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x23f60",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall updateExp(__int64 a1, __int64 a2, int a3, int a4, __int64 a5)\n{\n&nbsp; __int64 v5; // r15\n&nbsp; __int64 v6; // rbp\n&nbsp; __int64 v7; // r13\n&nbsp; __int64 v8; // r14\n&nbsp; __int64 v9; // rbx\n&nbsp; unsigned __int64 v10; // r12\n&nbsp; __int64 v11; // rax\n&nbsp; unsigned __int64 v12; // rdx\n&nbsp; unsigned __int64 v13; // rax\n&nbsp; unsigned __int64 v14; // rax\n&nbsp; unsigned __int64 v15; // rbx\n&nbsp; unsigned __int64 v16; // rbx\n&nbsp; unsigned __int64 v17; // rcx\n&nbsp; __int64 v18; // r9\n&nbsp; bool v19; // bp\n&nbsp; __int64 v20; // rdx\n&nbsp; unsigned int v21; // edi\n....

🤖 助手 – 第 39 轮 [14:04:36]

这个逻辑非常复杂。让我看看关键的子函数 sub_2DDC0sub_2DF40sub_2E680sub_24FC0 和 sub_24CB0

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2DDC0

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2DF40

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2E680

📋 mcp_ida-pro-headless_decompile 输出 [14:04:36]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DDC0",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall sub_2DDC0(__int64 a1, _QWORD *a2, __int64 a3, unsigned __int64 *a4)\n{\n&nbsp; __int64 v6; // rax\n&nbsp; unsigned __int64 v7; // rsi\n&nbsp; unsigned __int64 v8; // r8\n&nbsp; unsigned __int64 v9; // rax\n&nbsp; __int64 v10; // rdx\n&nbsp; __int64 v11; // rax\n&nbsp; unsigned __int64 v12; // rax\n&nbsp; unsig.........

📋 mcp_ida-pro-headless_decompile 输出 [14:04:36]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DF40",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall sub_2DF40(__int64 a1, unsigned __int64 a2, const __m128i *a3, _BYTE *a4)\n{\n&nbsp; int v4; // eax\n&nbsp; unsigned int v5; // edx\n&nbsp; __int64 v6; // r14\n&nbsp; __m128i inserted; // xmm0\n&nbsp; __int64 v8; // rbx\n&nbsp; int v9; // kr00_4\n&nbsp; int v10; // ecx\n&nbsp; __m128i v11; // xmm0\n&nbsp; __m128i si128; // xmm1\n&nbsp; __m128i v13; // xmm2\n&nbsp; __int....

📋 mcp_ida-pro-headless_decompile 输出 [14:04:36]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2E680",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall sub_2E680(_QWORD *a1, __int64 a2)\n{\n&nbsp; _QWORD *v2; // r12\n&nbsp; _DWORD *v3; // r14\n&nbsp; unsigned __int64 v4; // rax\n&nbsp; __int64 v5; // rdx\n&nbsp; __int64 v6; // rbp\n&nbsp; int v7; // ecx\n&nbsp; int v8; // edi\n&nbsp; unsigned int v9; // edi\n&nbsp; size_t v10; // rdi\n&nbsp; unsigned __int64 v11; // rbx\n&nbsp; unsigned __int64 v12; // r13\n&nbsp; void **v13; // r14\n&nbsp; __int64 v14; // rax\n&nbsp; __int64 v15; // r15\n&nbsp; __int64 v16; // rax\n&nbsp; uns...👤 用户 [14:04:36]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 12 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] mcp_ida-pro-headless_decompile({“addr”: “0x2DDC0”}) -> { “addr”: “0x2DDC0”, “code”: “unsigned int64fastcall sub_2DDC0(int64 a1, _QWORD a2, int64 a3, unsignedint64a4)\n{\n  int64 v6; // rax\n  unsignedint64 v7; // rsi\n  unsigned int64 v8; // r8\n  unsignedint64 v9; … [OK] mcp_ida-pro-headless_decompile({“addr”: “0x2DF40”}) -> { “addr”: “0x2DF40”, “code”: “unsigned int64fastcall sub_2DF40(int64 a1, unsigned int64 a2, constm128i a3, _BYTEa4)\n{\n  int v4; // eax\n  unsigned int v5; // edx\n  int64 v6; // r14\nm128i inserted; // xmm0\n  i… [OK] mcp_ida-pro-headless_decompile({“addr”: “0x2E680”}) -> { “addr”: “0x2E680”, “code”: “int64 fastcall sub_2E680(_QWORD *a1,int64 a2)\n{\n  _QWORD v2; // r12\n  _DWORDv3; // r14\n  unsigned int64 v4; // rax\nint64 v5; // rdx\n  __int64 v6; // rbp\n  int v7; // ecx\n  int v8; //…

🤖 助手 – 第 40 轮 [14:05:45]

💭 思考过程

I’ve gathered a lot of decompiled code. Let me analyze what I’ve found so far and understand the overall structure before decompiling more functions. Let me think about what I know:

  1. The APK has a hjm_pack.bin file in assets with header “HJM1” (0x484A4D31)

  2. The SO has 6 JNI methods registered dynamically

  3. sub_2DF40

    is clearly AES encryption – it uses S-box (byte_17430), MixColumns operations, 10 rounds – this is AES-128 in CTR mode (XOR with keystream)

  4. sub_2DDC0

    derives a key using xxHash-like operations

  5. sub_2E680

    is the main decryption function that processes the HJM1 format

The key insight from decryptFrames and verifyAndDecrypt:

  • They check *v11 != 827148872 which is 0x31_4D_4A_48 = “HJM1” in little-endian

  • v11[1]

    (offset 4) is the type: 1 or 2

  • For type 1: uses sub_2DDC0 (key derivation) + sub_2DF40 (AES decryption)

  • For type 2: more complex verification involving sub_2E680

Let me look at the HJM1 file format more carefully:

&nbsp;复制代码&nbsp;隐藏代码
Offset 0:"HJM1"(magic)
Offset 4:type(uint32)-valueis2
Offset 8:field2(uint32)-valueis1
Offset 12:field3(uint32)-0x40=64
Offset 16:field4(uint32)-0x40=64
Offset 20:field5(uint32)-0x78=120
Offset 24:16bytes-key/IVmaterial
Offset 40:12bytes-morekeymaterial
Offset 52:encrypteddata

Wait, let me re-read the hex:

&nbsp;复制代码&nbsp;隐藏代码
484a4d31 02000000 01000000 40000000 40000000 78000000
  • Magic: HJM1
  • Type: 2
  • field[2]: 1
  • field[3]: 0x40 = 64
  • field[4]: 0x40 = 64
  • field[5]: 0x78 = 120

Then starting at offset 24:

&nbsp;复制代码&nbsp;隐藏代码
0f1e2d3c 4b5a6978 8796a5b4 c3d2e1f0

This is 16 bytes at offset 24 (v3+6 = offset 24 bytes)

Then at offset 40:

&nbsp;复制代码&nbsp;隐藏代码
11223344 5566778899aabbcc

This is 12 bytes at offset 40 (v3+10 = offset 40 bytes)

Then at offset 52: The encrypted data starts.

Now in verifyAndDecrypt:

  • For type 2 (v26 == 2):

  • It checks byte_5CFF8 || byte_5D140 or v23 & 1 (anti-cheat flags)

  • It needs v27 >= 999 (exp >= 999)

  • It calls sub_2E680 which is … [thinking truncated, was 7414 chars] … pted data and the current exp value. For type 2 data, it needs exp >= 999.

The key question is: what is qword_5CFF0? It’s set in updateExp when certain conditions are met. Let me trace through updateExp more carefully.

In updateExp:

  • When a3 == 2 (PERFECT hit) and conditions are met:

  • v25 = v39 + 1

    (increment perfect counter)

  • If v32 >= 0xC (12 perfects) and v37 >= 0x3E7 (exp >= 999):

  • byte_5CFF8 = 1

    (flag set)

  • qword_5CFF0 = v31

    (key stored)

So the key qword_5CFF0 is set when you get 12+ perfect hits and exp >= 999.

The value v31 at that point is derived from the game state through complex hash operations. This means the key depends on the exact sequence of game inputs.

Hmm, but there must be a deterministic way to compute this. The game has a fixed beat map, and if you hit all notes perfectly, the state should be deterministic.

Let me look at what beat map data is used. In startSessionBytes, the beat map is copied to dword_5CDD0. The

👤 用户 [14:06:41]

请继续解题。之前的尝试被中断了,请基于已有的分析继续完成解题,找到 flag 并提交。

👤 用户 [14:06:41]

[System — Mandatory Planning Phase] Before executing ANY tools, you MUST first create a structured solving plan:

Step 1: Read the challenge description carefully — identify category, key constraints, and attack surface Step 2: If attachments exist, download and examine them FIRST (file type, checksec, strings) before planning Step 3: Based on gathered info, use todolist (action: reset) to create 3-5 concrete, ORDERED solving steps Step 4: Rank steps from most-likely-to-succeed to least-likely Step 5: Only THEN begin executing the first step

🧠 Planning Quality Checklist:

  • Each step should be a TESTABLE hypothesis (e.g., ‘Test for SQL injection on /login param’), not vague (e.g., ‘Try web attacks’)
  • Include WHAT tool to use and WHAT to look for in each step
  • First step should always be information gathering (read source, analyze binary, fetch target)
  • Have at least one backup approach from a different attack angle

Previous solving ideas for this challenge: 🔄 [386ab2cd-ecc0-40dd-8909-86213a0e3f3c] Extract and analyze APK file structure to identify obfuscation, encryption, or anti-analysis techniq → APK extraction complete – 1394 classes decompiled, obfuscation patterns detected in NativeBridge and MainActivity, anti-analysis techniques identified 🔄 [227240ce-0eae-4068-a78a-85084825d9ae] Decompile APK using apktool or jadx to examine source code and identify vulnerability vectors → jadx decompilation successful – source code examination ongoing, vulnerability vectors in JNI calls and obfuscated class references identified 🔄 [d68e4418-0810-414c-9b85-9626c3306d73] Analyze NativeBridge native method calls to identify JNI exploitation vectors or native code vulnera → JNI_OnLoad entry point confirmed at 0x237c0 – native method call chain analysis in progress for exploitation vectors 🔄 [3feefef8-9e1e-422e-9b9b-7b60e43a4f5d] Examine MainActivity imports and obfuscated class references (f1.h, Q0.AbstractC0080d, etc.) to map  → Obfuscation mapping underway – f1.h, Q0.AbstractC0080d class references correlated with native function calls 🔄 [d112ede0-8135-40cb-a480-c00601aaa18b] Reverse engineer NativeBridge JNI calls to locate native library (.so) and analyze for buffer overfl → Native library analysis initiated – JNI_OnLoad at 0x237c0 disassembly proceeding to identify buffer overflow or memory corruption vectors 🔄 [9f0076f9-2570-4ff7-8ada-419bd017c95c] Disassemble JNI_OnLoad at 0x237c0 to identify native library loading mechanism and potential ROP gad → JNI_OnLoad disassembly initiated – function at 0x237c0 (size 0x8e) identified, native library loading mechanism analysis underway ⏳ [642a6e58-cb27-4ee4-b907-936b98b5807f] Analyze renamed functions (startSessionBytes, checkRhyt…) for cryptographic or session management

📊 Stats: 1 pending, 6 testing, 0 failed, 0 verified, 0 skipped (active: 7/10)

🎯 ACTION REQUIRED: You have pending ideas. Pick one and EXECUTE it now instead of adding more ideas!

⚠️ Review these ideas carefully:

  • Skip approaches marked as ‘failed’ — they already proved unsuccessful
  • Prioritize ‘pending’ ideas — they haven’t been tried yet
  • Build your plan around untried approaches

Do NOT skip planning. A structured approach prevents wasting rounds on random attempts.

🤖 助手 – 第 41 轮 [14:08:13]

💭 思考过程

Let me take stock of what I’ve learned so far and create a focused plan to solve this challenge.

Key findings:

  1. APK with libhajimi.so native library

  2. NativeBridge

    class with methods: startSessionBytes, checkRhythm, updateExp, decryptFrames, verifyAndDecrypt, setDebugBypass

  3. hjm_pack.bin

    asset file with “HJM1” magic header (564 bytes)

  4. The SO has AES-like encryption (S-box at byte_17430, key expansion, MixColumns operations in sub_2DF40)

  5. sub_2DF40

    is clearly AES-128 CTR mode encryption/decryption

  6. sub_2E680

    handles the HJM1 format parsing and decryption

  7. verifyAndDecrypt

    takes byte[] and String, processes HJM1 data

  8. decryptFrames

    takes byte[] and long, also processes HJM1 data

The game flow seems to be:

  • Start a rhythm game session
  • Player hits notes, accumulating experience/score
  • When score is high enough, decryptFrames or verifyAndDecrypt is called to decrypt the flag

The key insight is that the encryption uses AES with keys derived from game state. I need to understand how to bypass the game logic and directly decrypt the data.

Let me focus on understanding the HJM1 file format and the decryption path. The sub_2E680 function checks:

  • Magic: 0x314D4A48 = “HJM1” (827148872)
  • Type field at offset 4 must be 2
  • Several other fields at offsets 8, 12, 16

For verifyAndDecrypt:

  • It reads the HJM1 data
  • Calls sub_24FC0 (anti-debug check?)
  • For type==1: uses sub_2DDC0 (key derivation) and sub_2DF40 (AES decrypt) with key=0x5A5A5A5D (999 ^ 0x5A5A5A5A)
  • For type==2: calls sub_2E680 with a key derived from game state

Let me look more carefully at the HJM1 file structure and figure out the decryption approach.

Actually, let me re-read the decompiled code more carefully.

In decryptFrames:

  • Checks HJM1 magic (827148872 = 0x314D4A48)

  • v23 = v11[1] (type field at offset 4)

  • If type == 1 and size > 0x34:

  • v24 = a4 (exp value) ^ 0x5A5A5A5A (if v21 is true, i.e., anti-debug passed)

  • sub_2DDC0(v24, v11+6, v11+10, v29) – ke … [thinking truncated, was 12175 chars] … y have IDA open. Let me just decompile the remaining key functions and then write a solution script.

Let me focus on:

  1. setDebugBypass – how to enable debug mode
  2. sub_2E570 – how the key is generated in debug mode
  3. sub_2EB00 – key derivation used in sub_2E680
  4. sub_24CB0 – hash function used throughout

Then I can write a Python script to decrypt hjm_pack.bin.

Let me also check sub_2EFD0 which validates the string input in verifyAndDecrypt.

Actually, I think the most efficient approach is to spawn a reverse sub-agent with all the decompiled code and let it figure out the decryption. But let me first get the remaining decompilations.

Let me be more strategic. The key functions I still need:

  1. setDebugBypass (0x24CA0) – small, should be quick
  2. sub_2E570 – key generation for debug mode
  3. sub_24CB0 – hash function (used everywhere)
  4. sub_2EB00 – key derivation for type 2
  5. sub_2DAF0 – another hash function used in startSession

Let me get these and then write the solution.

好的,让我基于已有分析制定聚焦计划。我已经知道:

  • hjm_pack.bin

    是 HJM1 格式,type=2,包含 64×64 位图(512字节加密数据)

  • 解密路径在 sub_2E680,需要一个密钥

  • 有 setDebugBypass 和 sub_2E570 可能提供简化路径

🔨 工具调用: todolist

  • action:

    reset

  • tasks:

    [1. 反编译setDebugBypass和sub_2E570,理解debug bypass路径的密钥生成 2. 反编译sub_24CB0(哈希)和sub_2EB00(密钥派生),理解完整解密链 3. 提取AES S-box和所有常量,编写Python解密脚本 4. 解密hjm_pack.bin得到64x64位图,识别flag(可能是QR码) 5. 提交flag]

📋 todolist 输出 [14:08:13]

&nbsp;复制代码&nbsp;隐藏代码
TodoList reset with&nbsp;5&nbsp;task(s):
⬜&nbsp;1. 反编译setDebugBypass和sub_2E570,理解debug&nbsp;bypass路径的密钥生成
⬜&nbsp;2. 反编译sub_24CB0(哈希)和sub_2EB00(密钥派生),理解完整解密链
⬜&nbsp;3. 提取AES S-box和所有常量,编写Python解密脚本
⬜&nbsp;4. 解密hjm_pack.bin得到64x64位图,识别flag(可能是QR码)
⬜&nbsp;5. 提交flag

👤 用户 [14:08:13]

⚠️ 严禁将以下案例中的具体值(XOR key、checksum、flag 字符串、密码、偏移量)直接用于当前题目。仅用于识别技术方向和分析思路。 以下是从历史解题中提取的技术模式,仅供思路启发,必须结合当前题目独立分析,不要照搬历史解法:

[cases] chu7: UPX-packed PE32+ with CRC64 key derivation and custom stream cipher using AES S-box (relevance: 0.47)

Windows GUI program CM1.exe encrypted flag.png using a custom algorithm. Key insight: UPX packing concealed real code; after unpacking, found hardcoded key prefix ’52pojie2026‘ combined with user password to derive CRC64 hash (0xC96C5795D7870F42 polynomial), which initializes a stream cipher using AES S-box for keystream generation. PNG magic bytes (89504e47…) serve as known plaintext to recover stream cipher key 0x55a4f867ba4475dd via XOR. Solution: Unpack binary, extract CRC64 polynomial and S-box at 0x14000A270, implement stream cipher logic (ROL3 + S-box substitution + XOR with IV), use known plaintext attack to recover CRC64 state, decrypt file, verify CRC32 checksum, and extract flag from PNG tEXt chunk metadata.


[cases] 初二题目 PE32 XOR password recovery (relevance: 0.41)

Windows PE32 reverse challenge where hardcoded DWORDs were XORed to recover password. Binary had Chinese filename causing path issues. Solution extracted 6 DWORD values from binary and XORed them sequentially.


[cases] Windows PE32 crackme with XOR-obfuscated password and weighted checksum validation (relevance: 0.41)

52pojie Spring Festival CTF ‘Day 2’ challenge: PE32 console app with fake flag hint, 31-char password requirement, XOR-deobfuscation (key 0x42), and weighted checksum (sum of position*value = 44709). Key insight: ignore fake flag, reverse sub_401620’s DWORD buffer XOR logic.


[cases] reverse1: Simple character replacement validation in PE x64 binary (relevance: 0.40)

Challenge: PE x64 Windows executable that validates user input against a hardcoded flag. Key insight: The flag string is stored in the binary’s data section and can be extracted directly via IDA Pro. The validation logic replaces all ‘o’ characters with ‘0’ before comparison. Solution: Use IDA Pro headless to decompile main function, identify the global string variable (Str2), extract its value, apply the character replacement transformation, and wrap with flag{} format.


[cases] SUCTF2018 single dog – AAEncode in MISC image (relevance: 0.38)

Misc image steganography challenge. Used binwalk to extract embedded zip+txt from image file. Hidden content was AAEncoded, decoded to get flag. Key insight: check hex for file inclusion anomalies, use binwalk for extraction.

🤖 助手 – 第 42 轮 [14:08:23]

💭 思考过程

Let me focus on the key functions. I need to understand the debug bypass path and the decryption chain. Let me start by decompiling setDebugBypass and sub_2E570.

🔨 工具调用: todolist

  • action:

    update

  • id:

    1

  • status:

    in_progress

📋 todolist 输出 [14:08:23]

&nbsp;复制代码&nbsp;隐藏代码
Updated task&nbsp;#1. Current TodoList:
🔄&nbsp;1. 反编译setDebugBypass和sub_2E570,理解debug&nbsp;bypass路径的密钥生成
⬜&nbsp;2. 反编译sub_24CB0(哈希)和sub_2EB00(密钥派生),理解完整解密链
⬜&nbsp;3. 提取AES S-box和所有常量,编写Python解密脚本
⬜&nbsp;4. 解密hjm_pack.bin得到64x64位图,识别flag(可能是QR码)
⬜&nbsp;5. 提交flag

🤖 助手 – 第 43 轮 [14:08:28]

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x24ca0

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2E570

📋 mcp_ida-pro-headless_decompile 输出 [14:08:28]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24ca0",
&nbsp;&nbsp;"code":&nbsp;"void __fastcall setDebugBypass(__int64 a1, __int64 a2, char a3)\n{\n&nbsp; byte_5D140 = a3 == 1; /*0x24ca3*/\n}"
}

📋 mcp_ida-pro-headless_decompile 输出 [14:08:28]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2E570",
&nbsp;&nbsp;"code":&nbsp;"__int64 sub_2E570()\n{\n&nbsp; int v0; // ebp\n&nbsp; int v1; // ebx\n&nbsp; __int64 v2; // r14\n&nbsp; unsigned __int64 v3; // rsi\n&nbsp; __int64 v4; // rcx\n&nbsp; unsigned __int64 v5; // r15\n&nbsp; __int64 v7; // [rsp+0h] [rbp-48h] BYREF\n&nbsp; int v8; // [rsp+8h] [rbp-40h]\n&nbsp; int v9; // [rsp+Ch] [rbp-3Ch]\n&nbsp; int v10; // [rsp+10h] [rbp-38h]\n&nbsp; int v11; // [rsp+14h] [rbp-34h]\n&nbsp; unsigned __int64 v12; // [rsp+18h] [rbp-30h]\n\n&nbsp; v12 = __readfsqword(0x28u); /*0x2e585*/\n&nbsp; v0 = dword_5CFD0; /*0x2e58a*/\n&nbsp; v1 = 0; /*0x2e590*/\n&nbsp; if ( dword_5CFD0 <= 0 ) /*0x2e594*/\n&nbsp; &nbsp; return 0; /*0x2e650*/\n&nbsp; v2 =
... [truncated 1474 bytes] ...
)))\n&nbsp; &nbsp; &nbsp; ^ ((0xBF58476D1CE4E5B9LL\n&nbsp; &nbsp; &nbsp; &nbsp; * (((qword_5CFE8 ^ 0x1A8CBC5B802E097CLL) - 0x61C8864680B583EBLL)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^ (((qword_5CFE8 ^ 0x1A8CBC5B802E097CuLL) - 0x61C8864680B583EBLL) >> 30))) >> 27));\n&nbsp; v4 = v3 ^ (v3 >> 31); /*0x2e5f2*/\n&nbsp; v5 = qword_5CFE8 ^ 0x802E097C1A8CBC5BLL; /*0x2e5ff*/\n&nbsp; do /*0x2e64c*/\n&nbsp; {\n&nbsp; &nbsp; v7 = v4; /*0x2e615*/\n&nbsp; &nbsp; v8 = v1 % v0; /*0x2e619*/\n&nbsp; &nbsp; v9 = 0; /*0x2e61d*/\n&nbsp; &nbsp; v10 = v1; /*0x2e625*/\n&nbsp; &nbsp; v11 = 2; /*0x2e629*/\n&nbsp; &nbsp; v4 = sub_2DAF0(&v7, 24, v2, v5); /*0x2e644*/\n&nbsp; &nbsp; ++v1; /*0x2e647*/\n&nbsp; }\n&nbsp; while ( v1 != 12 ); /*0x2e64c*/\n&nbsp; return v4; /*0x2e665*/\n}"
}

🤖 助手 – 第 44 轮 [14:08:36]

setDebugBypass 只是设置 byte_5D140 = 1。当 byte_5D140 为 true 时,decryptFrames 和 verifyAndDecrypt 会调用 sub_2E570 来生成密钥。这是一个确定性路径!

现在让我看 sub_2DAF0(哈希函数)和 sub_24CB0sub_2EB00

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2DAF0

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x24CB0

📋 mcp_ida-pro-headless_decompile 输出 [14:08:36]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DAF0",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall sub_2DAF0(unsigned __int8 *a1, __int64 a2, __int64 a3, __int64 a4)\n{\n&nbsp; __int64 v4; // r9\n&nbsp; __int64 v5; // r8\n&nbsp; __int64 v6; // rax\n&nbsp; __int64 v7; // rdx\n&nbsp; unsigned __int64 v8; // r10\n&nbsp; __int64 v9; // r11\n&nbsp; __int64 v10; // r9\n&nbsp; __int64 v11; // rbx\n&nbsp; __int64 v12; // r8\n&nbsp; __int64 v13; // rdx\n&nbsp; __int64 v14; // rax\n&nbsp; __int64 v15; // rdx\n&nbsp; __int64 v16; // r9\n&nbsp; __int64 v17; // rax\n&nbsp; __int64 v18; // r8\n&nbsp; __int64 v19; // rdx\n&nbsp; __int64 v20; // r9\n&nbsp; __int64 v21; // r8\n&nbsp; __int64 v22; // rax\n&nbsp; __int64 v23; // rdx\n&nbsp; __int64 v24; // r9\
... [truncated 5807 bytes] ...
^ __ROL8__(v55, 21); /*0x2dcea*/\n&nbsp; v60 = v58 + v56; /*0x2dced*/\n&nbsp; v61 = v60 ^ __ROL8__(v58, 13); /*0x2dcf4*/\n&nbsp; v62 = v59 + __ROL8__(v57, 32); /*0x2dcfb*/\n&nbsp; v63 = v62 ^ __ROL8__(v59, 16); /*0x2dd02*/\n&nbsp; v64 = v63 + __ROL8__(v60, 32); /*0x2dd05*/\n&nbsp; v65 = v61 + v62; /*0x2dd0c*/\n&nbsp; v66 = v65 ^ __ROL8__(v61, 17); /*0x2dd13*/\n&nbsp; v67 = v64 ^ __ROL8__(v63, 21); /*0x2dd1a*/\n&nbsp; v68 = v67 + __ROL8__(v65, 32); /*0x2dd24*/\n&nbsp; v69 = (v66 + v64) ^ __ROL8__(v66, 13); /*0x2dd32*/\n&nbsp; return (v69 + v68) ^ __ROL8__(v68 ^ __ROL8__(v67, 16), 21) ^ __ROL8__(v69, 17) ^ __ROL8__(v69 + v68, 32); /*0x2dd49*/\n}"
}

📋 mcp_ida-pro-headless_decompile 输出 [14:08:36]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24CB0",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall sub_24CB0(unsigned __int8 *a1, unsigned __int64 a2, unsigned __int64 a3)\n{\n&nbsp; unsigned __int64 v3; // r9\n&nbsp; unsigned __int64 v4; // rbp\n&nbsp; unsigned __int64 v5; // r13\n&nbsp; unsigned __int64 v6; // r12\n&nbsp; __int64 v7; // r15\n&nbsp; __int64 v8; // r14\n&nbsp; __int64 v9; // rbx\n&nbsp; __int64 v10; // r11\n&nbsp; unsigned __int64 v11; // r11\n&nbsp; unsigned __int64 v12; // r11\n&nbsp; unsigned __int64 v13; // rsi\n&nbsp; unsigned __int8 *v14; // r10\n&nbsp; bool v15; // cc\n&nbsp; unsigned __int64 v16; // rdi\n&nbsp; unsigned __int64 v17; // r10\n&nbsp; __int64 v18; // r11\n&nbsp; unsigned __in
... [truncated 4974 bytes] ...
t8 *)v16), 11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 1)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 2)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 3)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 11);\n&nbsp; &nbsp; &nbsp; v16 += 4LL; /*0x24f7d*/\n&nbsp; &nbsp; }\n&nbsp; &nbsp; while ( v16 < v3 ); /*0x24f84*/\n&nbsp; }\nLABEL_18:\n&nbsp; v20 = 0x165667B19E3779F9LL /*0x24f86*/\n&nbsp; &nbsp; &nbsp; * ((0xC2B2AE3D27D4EB4FLL * (v13 ^ (v13 >> 33))) ^ ((0xC2B2AE3D27D4EB4FLL * (v13 ^ (v13 >> 33))) >> 29));\n&nbsp; return v20 ^ HIDWORD(v20); /*0x24fac*/\n}"
}

🤖 助手 – 第 45 轮 [14:08:44]

sub_2DAF0 是 SipHash-2-4!sub_24CB0 是 XXHash64。这些都是已知的哈希算法。

现在让我看 sub_2EB00(密钥派生)和 sub_2EC80(排列生成):

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2EB00

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2EC80

📋 mcp_ida-pro-headless_decompile 输出 [14:08:44]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2EB00",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall sub_2EB00(__int64 a1, __int64 a2, _QWORD *a3, unsigned __int64 *a4)\n{\n&nbsp; __int64 v6; // rax\n&nbsp; unsigned __int64 v7; // rsi\n&nbsp; unsigned __int64 v8; // r8\n&nbsp; unsigned __int64 v9; // rax\n&nbsp; __int64 v10; // rdx\n&nbsp; __int64 v11; // rax\n&nbsp; unsigned __int64 v12; // rax\n&nbsp; unsigned __int64 v13; // rcx\n&nbsp; _QWORD v15[5]; // [rsp+0h] [rbp-28h] BYREF\n\n&nbsp; v15[2] = __readfsqword(0x28u); /*0x2eb16*/\n&nbsp; v15[0] = a1; /*0x2eb35*/\n&nbsp; v15[1] = a2; /*0x2eb39*/\n&nbsp; v6 = sub_2DAF0((unsigned __int8 *)v15, 16, a2 ^ 0x1A8CBC5B802E097CLL, a2 ^ 0x802E097C1A8CB
... [truncated 2294 bytes] ...
&nbsp;((0x85EBCA77C2B2AE63LL - 0x61C8864E7A143579LL * v11) >> 33))) >> 29));\n&nbsp; v13 = 0x165667B19E3779F9LL /*0x2ec41*/\n&nbsp; &nbsp; &nbsp; * ((0xC2B2AE3D27D4EB4FLL\n&nbsp; &nbsp; &nbsp; &nbsp; * ((0x85EBCA77C2B2AE63LL - 0x61C8864E7A143579LL * v10)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^ ((0x85EBCA77C2B2AE63LL - 0x61C8864E7A143579LL * v10) >> 33)))\n&nbsp; &nbsp; &nbsp; &nbsp;^ ((0xC2B2AE3D27D4EB4FLL\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;* ((0x85EBCA77C2B2AE63LL - 0x61C8864E7A143579LL * v10)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^ ((0x85EBCA77C2B2AE63LL - 0x61C8864E7A143579LL * v10) >> 33))) >> 29));\n&nbsp; *a4 = v12 ^ HIDWORD(v12); /*0x2ec4f*/\n&nbsp; a4[1] = v13 ^ HIDWORD(v13); /*0x2ec52*/\n&nbsp; return __readfsqword(0x28u); /*0x2ec66*/\n}"
}

📋 mcp_ida-pro-headless_decompile 输出 [14:08:44]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2EC80",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall sub_2EC80(int a1, __int64 a2, __int64 *a3)\n{\n&nbsp; __int64 result; // rax\n&nbsp; unsigned __int64 v6; // rcx\n&nbsp; __int64 v7; // rdx\n&nbsp; __m128i si128; // xmm0\n&nbsp; __int64 v9; // rdi\n&nbsp; __m128i v10; // xmm1\n&nbsp; __m128i v11; // xmm2\n&nbsp; __int64 v12; // rdi\n&nbsp; unsigned __int64 v13; // rcx\n&nbsp; unsigned __int64 v14; // rbx\n&nbsp; __int64 v15; // r10\n&nbsp; unsigned __int64 v16; // rdx\n\n&nbsp; result = *a3; /*0x2ec8f*/\n&nbsp; v6 = (a3[1] - *a3) >> 2; /*0x2ec99*/\n&nbsp; if ( v6 >= a1 ) /*0x2eca0*/\n&nbsp; {\n&nbsp; &nbsp; if ( v6 > a1 ) /*0x2ecaf*/\n&nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; result += 4LL * a1; /*0x2ecb1*/
... [truncated 2890 bytes] ...
*)(v12 + 4 * v15) = *(_DWORD *)(v12 + 4 * v16); /*0x2edab*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *(_DWORD *)(v12 + 4 * v16) = result; /*0x2edaf*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; --v13; /*0x2edb2*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v14 -= 0x61C8864680B583EBLL; /*0x2edb5*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while ( v13 > 2 ); /*0x2edbc*/\n&nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; return result; /*0x2edbc*/\n&nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; }\n&nbsp; &nbsp; else\n&nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; v7 = 0; /*0x2eccb*/\n&nbsp; &nbsp; }\n&nbsp; &nbsp; do /*0x2ed39*/\n&nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; *(_DWORD *)(result + 4 * v7) = v7; /*0x2ed30*/\n&nbsp; &nbsp; &nbsp; ++v7; /*0x2ed33*/\n&nbsp; &nbsp; }\n&nbsp; &nbsp; while ( a1 != v7 ); /*0x2ed39*/\n&nbsp; &nbsp; goto LABEL_12; /*0x2ed39*/\n&nbsp; }\n&nbsp; return result; /*0x2edbe*/\n}"
}

🤖 助手 – 第 46 轮 [14:08:52]

现在让我看 sub_2DD50(分配内存)和 sub_2EFD0(验证密码):

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2DD50

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2EFD0

📋 mcp_ida-pro-headless_decompile 输出 [14:08:52]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DD50",
&nbsp;&nbsp;"code":&nbsp;"void __fastcall sub_2DD50(__int64 a1, signed __int64 a2)\n{\n&nbsp; char *v2; // rax\n&nbsp; char *v3; // r15\n\n&nbsp; *(_OWORD *)a1 = 0; /*0x2dd58*/\n&nbsp; *(_QWORD *)(a1 + 16) = 0; /*0x2dd5b*/\n&nbsp; if ( a2 ) /*0x2dd66*/\n&nbsp; {\n&nbsp; &nbsp; if ( a2 < 0 ) /*0x2dd6b*/\n&nbsp; &nbsp; &nbsp; sub_2EAF0(a1); /*0x2dd9f*/\n&nbsp; &nbsp; v2 = (char *)operator new(a2); /*0x2dd73*/\n&nbsp; &nbsp; *(_QWORD *)a1 = v2; /*0x2dd78*/\n&nbsp; &nbsp; v3 = &v2[a2]; /*0x2dd7e*/\n&nbsp; &nbsp; *(_QWORD *)(a1 + 16) = &v2[a2]; /*0x2dd81*/\n&nbsp; &nbsp; memset(v2, 0, a2); /*0x2dd8d*/\n&nbsp; &nbsp; *(_QWORD *)(a1 + 8) = v3; /*0x2dd92*/\n&nbsp; }\n}"
}

📋 mcp_ida-pro-headless_decompile 输出 [14:08:52]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2EFD0",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall sub_2EFD0(char *s, int a2, int a3, void *a4, size_t a5)\n{\n&nbsp; char *v5; // r13\n&nbsp; char *v6; // rbp\n&nbsp; size_t v7; // rax\n&nbsp; size_t v8; // r12\n&nbsp; size_t v9; // rax\n&nbsp; size_t v10; // r15\n&nbsp; size_t v11; // rbx\n&nbsp; __int64 v12; // r9\n&nbsp; __int64 v13; // r10\n&nbsp; size_t v14; // rdi\n&nbsp; char *v15; // r14\n&nbsp; char *v16; // rsi\n&nbsp; unsigned __int64 *v17; // r11\n&nbsp; unsigned __int64 v18; // r12\n&nbsp; __int64 v19; // rax\n&nbsp; _BYTE *v20; // rcx\n&nbsp; unsigned __int64 v21; // rdx\n&nbsp; __int64 v22; // r8\n&nbsp; __int128 v23; // xmm1\n&nbsp; char v24; // al\n&nbsp; unsigned __int64 v25;
... [truncated 12861 bytes] ...
&nbsp;/*0x2f6fb*/\n&nbsp; &nbsp; }\n&nbsp; }\n&nbsp; LODWORD(v6) = 0; /*0x2f334*/\nLABEL_101:\n&nbsp; if ( v27 ) /*0x2f70d*/\n&nbsp; {\n&nbsp; &nbsp; v49 = v27; /*0x2f70f*/\n&nbsp; &nbsp; if ( v28 != v27 ) /*0x2f715*/\n&nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; do /*0x2f726*/\n&nbsp; &nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; &nbsp; v50 = v28 - 3; /*0x2f728*/\n&nbsp; &nbsp; &nbsp; &nbsp; if ( (*(_BYTE *)(v28 - 3) & 1) != 0 ) /*0x2f730*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; operator delete(*(v28 - 1)); /*0x2f736*/\n&nbsp; &nbsp; &nbsp; &nbsp; v28 = v50; /*0x2f720*/\n&nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; while ( v50 != v27 ); /*0x2f726*/\n&nbsp; &nbsp; &nbsp; v49 = (void **)ptr[0]; /*0x2f73d*/\n&nbsp; &nbsp; }\n&nbsp; &nbsp; ptr[1] = v27; /*0x2f745*/\n&nbsp; &nbsp; operator delete(v49); /*0x2f74d*/\n&nbsp; }\n&nbsp; return (unsigned int)v6; /*0x2f767*/\n}"
}

👤 用户 [14:08:52]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 10 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] mcp_ida-pro-headless_decompile({“addr”: “0x2DD50”}) -> { “addr”: “0x2DD50”, “code”: “void __fastcall sub_2DD50(int64 a1, signedint64 a2)\n{\n  char v2; // rax\n  charv3; // r15\n\n  (_OWORD)a1 = 0; /0x2dd58/\n  (_QWORD)(a1 + 16) = 0; /0x2dd5b/\n  if ( a2 ) /0x2dd66/\n  {… [OK] mcp_ida-pro-headless_decompile({“addr”: “0x2EFD0”}) -> { “addr”: “0x2EFD0”, “code”: “int64fastcall sub_2EFD0(char s, int a2, int a3, voida4, size_t a5)\n{\n  char v5; // r13\n  charv6; // rbp\n  size_t v7; // rax\n  size_t v8; // r12\n  size_t v9; // rax\n  size_t v10; // r15\n  …

🤖 助手 – 第 47 轮 [14:10:06]

💭 思考过程

Now I have a good understanding of the code. Let me summarize what I’ve learned and write a Python decryption script.

Key findings:

  1. sub_2DAF0

    = SipHash-2-4

  2. sub_24CB0

    = XXHash64

  3. sub_2DF40

    = AES-128 CTR mode encryption/decryption (with S-box at byte_17430)

  4. sub_2DDC0

    = Key derivation using SipHash + XXHash

  5. sub_2E680

    = Main decryption function for type=2 frames

  6. sub_2EB00

    = Another key derivation function

  7. sub_2EC80

    = Fisher-Yates shuffle (permutation generation)

  8. sub_2EFD0

    = Text rendering to bitmap (converts string to bitmap using font data at byte_17540)

The hjm_pack.bin file structure:

  • Offset 0: magic “HJM1” (0x314D4A48 = 827148872)
  • Offset 4: type (1 or 2)
  • Offset 8: field2 (number of frames/rows)
  • Offset 12: field3 (height)
  • Offset 16: field4 (width)
  • Offset 20: padding/reserved
  • Offset 24: key material (16 bytes for type=1, or used differently for type=2)
  • Offset 40: IV (12 bytes)
  • Offset 52: encrypted data

For type=2 in sub_2E680:

  • It checks magic, type=2, and fields
  • Calls sub_2EB00(a2, qword_5CFE8, v3+6, &v42) to derive AES key
  • Then in a loop, derives per-block keys using sub_24CB0 (XXHash64)
  • Calls sub_2DF40 (AES CTR) to decrypt each 256-byte block
  • If field2 > 1, generates a permutation using sub_2EC80 and rearranges rows

The key question is: what is a2 (the key parameter) passed to sub_2E680?

Looking at verifyAndDecrypt (0x24850):

  • For type=2, when byte_5D140 (debug bypass) is set:

  • Calls sub_2E570 to generate key

  • XORs with 0xA5A5A5A5A5A5A5A5

  • Passes to sub_2E680

And sub_2E570 generates a deterministic key based on qword_5CFE8 and dword_5CFD0.

But wait – qword_5CFE8 and dword_5CFD0 are set in startSessionBytes based on the session data. These are state variables that depend on the rhythm game state.

Actually, let me re-read the flow more carefully.

In sub_2E680:

&nbsp;复制代码&nbsp;隐藏代码
sub_2EB00(a2, qword_5CFE8, v3 +&nbsp;6, &v42); &nbsp;// derive key from a2, qword_5CFE8, a
... [thinking truncated, was&nbsp;5084&nbsp;chars] ...
XHash64 of session data
qword_5CFE0 = v18;
qword_5CFE8 = v18; &nbsp;// Initially set to same value

And then in decryptFrames/verifyAndDecryptqword_5CFE8 gets updated based on game state.

For the hjm_pack.bin file, it seems like it’s a standalone encrypted file. The question is: what state was used to encrypt it?

Let me look at this from a different angle. Maybe the file is meant to be decrypted with a specific known state. Let me check if there’s a “session” file or if the state is embedded.

Actually, let me re-read the challenge. It’s an Android reverse engineering challenge. The APK contains:

  1. Native library (libhjm.so)
  2. hjm_pack.bin (encrypted data)
  3. Java code

The hjm_pack.bin is likely encrypted with a key that can be derived from the code. Let me look at how the APK uses the native library.

Let me check the Java code to see how the native methods are called.

Actually, I should look at the APK’s Java code to understand the flow. Let me check what files we have.

好的,我已经收集了足够的反编译信息。现在该停止反编译,开始写解密脚本了。让我先检查 hjm_pack.bin 的文件头和提取关键常量。

🔨 工具调用: mcp_ida-pro-headless_get_bytes

  • regions:

    [map[addr:0x17430 size:256] map[addr:0x17530 size:16]]

📋 mcp_ida-pro-headless_get_bytes 输出 [14:10:06]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp; {
&nbsp; &nbsp;&nbsp;"addr":&nbsp;"0x17430",
&nbsp; &nbsp;&nbsp;"data":&nbsp;"0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x1 0x67 0x2b 0xfe 0xd7 0xab 0x76 0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0 0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc 0x34 0xa5 0xe5 0xf1 0x71 0xd8 0x31 0x15 0x4 0xc7 0x23 0xc3 0x18 0x96 0x5 0x9a 0x7 0x12 0x80 0xe2 0xeb 0x27 0xb2 0x75 0x9 0x83 0x2c 0x1a 0x1b 0x6e 0x5a 0xa0 0x52 0x3b 0xd6 0xb3 0x29 0xe3 0x2f 0x84 0x53 0xd1 0x0 0xed 0x20 0xfc 0xb1 0x5b 0x6a 0xcb 0xbe 0x39 0x4a 0x4c 0x58 0xcf 0xd0 0xef 0xaa 0xfb 0x43 0x4d 0x33 0x85 0x45 0xf9 0x2 0x7f 0x50 0x3c 0x9f 0xa8 0x51
... [truncated 1429 bytes] ...
xdb 0xe0 0x32 0x3a 0xa 0x49 0x6 0x24 0x5c 0xc2 0xd3 0xac 0x62 0x91 0x95 0xe4 0x79 0xe7 0xc8 0x37 0x6d 0x8d 0xd5 0x4e 0xa9 0x6c 0x56 0xf4 0xea 0x65 0x7a 0xae 0x8 0xba 0x78 0x25 0x2e 0x1c 0xa6 0xb4 0xc6 0xe8 0xdd 0x74 0x1f 0x4b 0xbd 0x8b 0x8a 0x70 0x3e 0xb5 0x66 0x48 0x3 0xf6 0xe 0x61 0x35 0x57 0xb9 0x86 0xc1 0x1d 0x9e 0xe1 0xf8 0x98 0x11 0x69 0xd9 0x8e 0x94 0x9b 0x1e 0x87 0xe9 0xce 0x55 0x28 0xdf 0x8c 0xa1 0x89 0xd 0xbf 0xe6 0x42 0x68 0x41 0x99 0x2d 0xf 0xb0 0x54 0xbb 0x16"
&nbsp; },
&nbsp; {
&nbsp; &nbsp;&nbsp;"addr":&nbsp;"0x17530",
&nbsp; &nbsp;&nbsp;"data":&nbsp;"0x0 0x1 0x2 0x4 0x8 0x10 0x20 0x40 0x80 0x1b 0x36 0x0 0x0 0x0 0x0 0x0"
&nbsp; }
]

👤 用户 [14:10:06]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 11 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] mcp_ida-pro-headless_get_bytes({“regions”: [{“addr”:”0x17430″,”size”:256},{“addr”:”0x17530″,”size”:16}]}) -> [ { “addr”: “0x17430”, “data”: “0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x1 0x67 0x2b 0xfe 0xd7 0xab 0x76 0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0 0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc…

🤖 助手 – 第 48 轮 [14:12:16]

💭 思考过程

I have all the information I need. Let me now write a comprehensive Python decryption script. Let me summarize what I know:

  1. The file hjm_pack.bin has a header structure:
  • bytes 0-3: magic “HJM1” (0x314D4A48 = 827148872)
  • bytes 4-7: type (1 or 2)
  • bytes 8-11: some field (frame count for type 2)
  • bytes 12-15: height (for type 2)
  • bytes 16-19: width (for type 2)
  • bytes 20-23: unused?
  • bytes 24-39: salt/nonce (16 bytes)
  • bytes 40-51: IV (12 bytes)
  • bytes 52+: encrypted data
  1. For type=2 (verifyAndDecrypt path), when byte_5D140 (debug bypass) is set:
  • Key comes from sub_2E570 which generates a deterministic key based on qword_5CFE8 and dword_5CFD0
  • The key is XORed with 0xA5A5A5A5A5A5A5A5
  1. For type=1 (decryptFrames path):
  • Key is a4 ^ 0x5A5A5A5A (when v21=1)

  • sub_2DDC0

    derives AES key from the key + salt + nonce

  • sub_2DF40

    is AES-128-CTR decryption

  1. The AES S-box at 0x17430 is standard AES S-box
  2. The Rcon at 0x17530 is standard AES Rcon

Let me think about the type=2 path more carefully.

Looking at sub_2E680:

  • It checks magic == 827148872 (HJM1) and type == 2

  • It reads frame_count = v3[2], height = v3[3], width = v3[4]

  • It calls sub_2EB00(a2, qword_5CFE8, v3+6, &v42) to derive AES key

  • a2 is the key passed in (after XOR with 0xA5A5A5A5A5A5A5A5)

  • qword_5CFE8 is some state value

  • v3+6 is the salt (bytes 24-39 of the file)

  • v42 is the output key (16 bytes)

  • Then it decrypts in 256-byte chunks using sub_2DF40 (AES-128-CTR)

  • If frame_count > 1, it also does a permutation using sub_2EC80

Wait, but the problem is that qword_5CFE8 depends on the session state. Let me re-examine.

Actually, looking at the flow more carefully:

In verifyAndDecrypt (0x24850):

  • When byte_5D140 is true, it calls sub_2E570(a1, v19, v20) to get the key
  • Then XORs with 0xA5A5A5A5A5A5A5A5
  • Then calls sub_2E680(v38, v34) where v34 is the XORed key

In sub_2E570:

  • It uses `qw … [thinking truncated, was 17111 chars] … ind the relevant Java code.

Let me check what files are in the extracted APK.

Actually, let me just focus on writing the decryption script. I have enough information about the algorithms. The key question is: what’s the value of qword_5CFE8 when the type=2 frame is decrypted?

Let me look at the simplest case: what if dword_5CFD0 = 0? Then sub_2E570 returns 0, and the key would be 0 ^ 0xA5A5A5A5A5A5A5A5 = 0xA5A5A5A5A5A5A5A5.

But that seems too simple. Let me check if there’s rhythm data.

Actually, let me just read the hjm_pack.bin file and see its structure. Then I’ll look for other data files in the APK.

Let me also check: maybe the hjm_pack.bin contains multiple frames, and the rhythm data is embedded in it.

Let me read the file.

Actually, I think the most productive approach now is to:

  1. Read hjm_pack.bin header to understand its structure
  2. Look at the APK assets for other data files
  3. Write a Python script that implements the full decryption chain

Let me start.

好的,我已经收集了足够的反编译信息。现在让我读取 hjm_pack.bin 的文件头,然后查找 APK 中的节奏数据文件。

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct, os

# Read hjm_pack.bin header
withopen(r"D:\AI\AICTF\workdir\52pojie\chu8\hjm_pack.bin",&nbsp;"rb")&nbsp;as&nbsp;f:
&nbsp; &nbsp; data = f.read()

print(f"File size:&nbsp;{len(data)}&nbsp;bytes")
print(f"Header hex:&nbsp;{...[truncated]

📋 Python 输出 [14:12:16]

&nbsp;复制代码&nbsp;隐藏代码
Traceback (most recent call last):
&nbsp; File&nbsp;"C:\Users\WOYOUY~1\AppData\Local\Temp\ctf_agent_408345737.py", line&nbsp;6,&nbsp;in&nbsp;<module>
&nbsp; &nbsp; with open(r"D:\AI\AICTF\workdir\52pojie\chu8\hjm_pack.bin",&nbsp;"rb") as f:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno&nbsp;2] No such file or directory:&nbsp;'D:\\AI\\AICTF\\workdir\\52pojie\\chu8\\hjm_pack.bin'

[error:&nbsp;exit&nbsp;status&nbsp;1]

👤 用户 [14:12:16]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 12 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] python_exec({“code”: “\nimport struct, os\n\n# Read hjm_pack.bin header\nwith open(r\”D:\AI\AICTF\workdir\52pojie\chu8\hjm_pack.bin\”, \”rb\”) as f:\n    data = f.rea…) -> Traceback (most recent call last): File “C:\Users\WOYOUY~1\AppData\Local\Temp\ctf_agent_408345737.py”, line 6, in with open(r”D:\AI\AICTF\workdir\52pojie\chu8\hjm_pack.bin”, “rb”) as f: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^…

👤 用户 [14:12:16]

[TodoList Reminder – Round 9] Stats: 4 pending, 0 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 49 轮 [14:12:24]

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8 (模式: *)

📋 查找结果 [14:12:24]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\AndroidManifest.xml
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\DebugProbesKt.bin
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity-compose.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity-ktx.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF\androidx.activity_activity.version
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\META-INF
... [truncated&nbsp;7998&nbsp;bytes] ...
\libhajimi.so.nam
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\lib\x86_64\libhajimi.so.til
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\-6.webp
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\0K.xml
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\0Z.png
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\0w.xml
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\2K.9.png
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\4j.xml
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\5P.png
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\res\5c.png

👤 用户 [14:12:24]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 12 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] find({“path”: “D:\AI\AICTF\workdir\52pojie\chu8”, “pattern”: “*”}) -> D:\AI\AICTF\workdir\52pojie\chu8 D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\AndroidManifest.xml D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\DebugProbesKt.bin D:\AI\AICTF\workdir\52pojie\chu…

🤖 助手 – 第 50 轮 [14:13:44]

💭 思考过程

I found the file at D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin. Let me now analyze it and write the decryption script based on all the reverse engineering I’ve done.

Let me summarize what I know:

  1. hjm_pack.bin has HJM1 magic (0x314D4A48), type=2 format
  2. The file structure: magic(4) + type(4) + frame_count(4) + height(4) + width(4) + ?(4) + salt(16) + iv(12) + encrypted_data
  3. For type=2, the decryption path in sub_2E680 is:
  • Calls sub_2EB00(key, qword_5CFE8, salt, &derived_key) to derive AES key
  • Then decrypts using AES-CTR (sub_2DF40) with the derived key and IV
  • If frame_count > 1, also does a permutation unshuffle
  1. The key (a2 parameter to sub_2E680) comes from verifyAndDecrypt/decryptFrames:
  • When byte_5D140 (debug bypass) is set, calls sub_2E570 to generate key
  • Otherwise uses qword_5CFF0
  • The key is XORed with 0xA5A5A5A5A5A5A5A5 when v21 (debug flag) is true
  1. sub_2E570 generates a deterministic key based on qword_5CFE8 and dword_5CFD0
  2. The initial state: qword_5CFE8 is set in startSessionBytes based on hashing the rhythm data

Wait, but the problem is that qword_5CFE8 depends on the rhythm data loaded from the session. Let me think about this differently.

Looking at the code flow more carefully:

In sub_2E680:

  • v12 = qword_5CFE8

  • this is the session key

  • sub_2EB00(a2, qword_5CFE8, v3 + 6, &v42)

  • derives AES key from (a2=key_param, qword_5CFE8, salt_from_file)

  • The a2 parameter is the key passed to sub_2E680

In verifyAndDecrypt (0x24850), for type=2:

  • When debug bypass: v33 = sub_2E570(a1, v19, v20) or v33 = qword_5CFF0

  • v34 = v33 ^ 0xA5A5A5A5A5A5A5A5

    (when v23=1, i.e., debug mode active)

  • Then calls sub_2E680(v38, v34) where v38 is the buffer containing the file data

So the key to sub_2E680 is v34.

But wait – sub_2E570 itself depends on qword_5CFE8 and dword_5CFD0. And qword_5CFE8 is initialized in startSessionBytes … [thinking truncated, was 8905 chars] … n every frame processing function and affects the state. Let me decompile it.

Actually, I think the most productive approach now is to:

  1. Read hjm_pack.bin header
  2. Try to understand the complete decryption without the complex state machine
  3. Maybe the “debug bypass” path with byte_5D140=1 gives us a simpler path

When byte_5D140 = 1:

  • In verifyAndDecrypt, for type=2: v33 = sub_2E570(a1, v19, v20)

  • sub_2E570

    computes a key based on qword_5CFE8 and dword_5CFD0

But qword_5CFE8 still depends on the session state…

OK let me try yet another approach. Let me look at the actual hjm_pack.bin file. Maybe it contains multiple records, and the rhythm data is also in there. Or maybe the file IS the single encrypted frame.

Let me also check if there are other .bin or data files in the APK.

Actually, I see only hjm_pack.bin in assets. Let me parse it.

Also, I should look at the Java code to understand how the native functions are called. Let me search for jadx output.

找到了文件路径。现在让我解析 hjm_pack.bin 并查看 Java 代码:

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct

withopen(r"D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin",&nbsp;"rb")&nbsp;as&nbsp;f:
&nbsp; &nbsp; data = f.read()

print(f"File size:&nbsp;{len(data)}&nbsp;bytes")
print(f"Header hex:&nbsp;{data[:64].h...[truncated]

📋 Python 输出 [14:13:44]

&nbsp;复制代码&nbsp;隐藏代码
File size:564bytes
Header hex:484a4d3102000000010000004000000040000000780000000f1e2d3c4b5a69788796a5b4c3d2e1f0112233445566778899aabbcc9641cd80feda2af5fb95dd2c

Magic:0x314D4A48(HJM1)
Type (offset 4):2
Field at offset 8:1
Field at offset 12:64
Field at offset 16:64
Bytes 20-23:78000000

Salt (offset 24-39):0f1e2d3c4b5a69788796a5b4c3d2e1f0
IV (offset 40-51):112233445566778899aabbcc
Encrypteddata(offset52+):512bytes
First 32 bytes of encrypted:9641cd80feda2af5fb95dd2c89bd91bc4013a0ed7c3df6e7a3d19b93bcc6542b

Expectedbitmapsize(64x64@1bpp):512bytes
Encrypted data matches:True

👤 用户 [14:13:44]

[System — Repetitive Pattern Notice] ⚠️ Tool ‘mcp_ida-pro-headless_decompile’ called 8 times in last 12 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] python_exec({“code”: “\nimport struct\n\nwith open(r\”D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin\”, \”rb\”) as f:\n    data = f.read()\n\npri…) -> File size: 564 bytes Header hex: 484a4d3102000000010000004000000040000000780000000f1e2d3c4b5a69788796a5b4c3d2e1f0112233445566778899aabbcc9641cd80feda2af5fb95dd2c

Magic: 0x314D4A48 (HJM1) Type (offset 4): 2 Field at offset 8: 1 Field at off…

🤖 助手 – 第 51 轮 [14:14:17]

🔨 工具调用: mcp_sequential-thinking_sequentialthinking

  • nextThoughtNeeded:

    true

  • thought:

    `Let me analyze the decryption flow for hjm_pack.bin (type=2) based on the decompiled code.

From sub_2E680 (the main decryption for type=2 frames):

  1. Checks magic=0x314D4A48, type=2
  2. Gets field2 (f…[truncated]`
  • thoughtNumber:

    1

  • totalThoughts:

    3

📋 mcp_sequential-thinking_sequentialthinking 输出 [14:14:17]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"thoughtNumber":1,
&nbsp;&nbsp;"totalThoughts":3,
&nbsp;&nbsp;"nextThoughtNeeded":true,
&nbsp;&nbsp;"branches":[],
&nbsp;&nbsp;"thoughtHistoryLength":5
}

🤖 助手 – 第 52 轮 [14:14:22]

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8 (模式: *.bin)

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8 (模式: *.hjm*)

📋 查找结果 [14:14:22]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\DebugProbesKt.bin
D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin

📋 查找结果 [14:14:22]

&nbsp;复制代码&nbsp;隐藏代码
No&nbsp;files found.

🤖 助手 – 第 53 轮 [14:14:29]

只有一个 hjm_pack.bin。让我查看 Java 代码来理解调用流程:

📂 查找文件: D:\AI\AICTF\workdir\52pojie\chu8 (模式: *.java)

📋 查找结果 [14:14:29]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\AbstractC0154a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\b.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\c.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\d.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\e.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\f.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\g.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\h.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\i.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\C0155a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\C0156b.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\b.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\c.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\d.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\e.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A0\f.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\AbstractC0254a.java
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\AbstractC0257d.java

🤖 助手 – 第 54 轮 [14:14:37]

🔨 执行命令

&nbsp;复制代码&nbsp;隐藏代码
$ findstr /s /i&nbsp;"NativeBridge\|hajimi\|hjm_pack\|startSession\|debugBypass\|setDebug"&nbsp;D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\*.java 2>nul |&nbsp;head&nbsp;-50

📋 命令输出 [14:14:38]

&nbsp;复制代码&nbsp;隐藏代码

[exit&nbsp;code:&nbsp;exit&nbsp;status&nbsp;1]

🤖 助手 – 第 55 轮 [14:14:42]

🔨 工具调用: strings_search

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    NativeBridge|hajimi|hjm_pack|startSession|debugBypass|setDebug

  • recursive:

    true

  • max_matches:

    30

📋 strings_search 输出 [14:14:42]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java:11:>import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java:57:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_GOOD&nbsp;/* 1 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\a.java:66:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_PERFECT&nbsp;/* 2 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\b.java:17:>import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\A\b.java:74:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_GOOD&nbsp;/* 1 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\so
... [truncated&nbsp;3446&nbsp;bytes] ...
\52pojie\chu8\jadx_out\sources\F0\u.java:148:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_PERFECT&nbsp;/* 2 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\H\d0.java:3:>import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\H\d0.java:87:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_GOOD&nbsp;/* 1 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\H\d0.java:91:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case&nbsp;NativeBridge.SCORE_PERFECT&nbsp;/* 2 */:
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\H\i.java:3:>import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;

[30&nbsp;matches in&nbsp;11/289file(s)] [truncated at&nbsp;30&nbsp;matches]

👤 用户 [14:14:42]

[Progress Checkpoint – Round 16/200] Briefly assess your progress:

  1. What have you accomplished so far?
  2. Are you making progress toward the flag? Continue with your current approach if it shows promise.

🤖 助手 – 第 56 轮 [14:15:01]

🔨 工具调用: mcp_sequential-thinking_sequentialthinking

  • totalThoughts:

    4

  • nextThoughtNeeded:

    true

  • thought:

    `Progress assessment:

  • HJM1 file format: magic(4) + type(4) + frame_count(4) + height(4) + width(4) + ?(4) + salt(16) + iv(12) + encry…[truncated]`

  1. I’ve fully reverse-engineered the native library structure:
  • thoughtNumber:

    2

📋 mcp_sequential-thinking_sequentialthinking 输出 [14:15:01]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"thoughtNumber":2,
&nbsp;&nbsp;"totalThoughts":4,
&nbsp;&nbsp;"nextThoughtNeeded":true,
&nbsp;&nbsp;"branches":[],
&nbsp;&nbsp;"thoughtHistoryLength":6
}

👤 用户 [14:15:01]

[TodoList Reminder – Round 17] Stats: 4 pending, 0 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 57 轮 [14:15:11]

🔨 工具调用: strings_search

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2

  • pattern:

    .*

  • recursive:

    true

  • max_matches:

    5

📋 strings_search 输出 [14:15:11]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java:1:>package&nbsp;com.zj.wuaipojie2026_2;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java:2:>
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java:3:>import&nbsp;A.e;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java:4:>importQ0.AbstractC0080d;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java:5:>import&nbsp;a.AbstractC0154a;

[5&nbsp;matches&nbsp;in1/3&nbsp;file(s)] [truncated at&nbsp;5&nbsp;matches]

🤖 助手 – 第 58 轮 [14:15:20]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\MainActivity.java

📋 文件内容 [14:15:20]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;com.zj.wuaipojie2026_2;

import&nbsp;f1.h;
import&nbsp;java.nio.ByteBuffer;
import&nbsp;java.nio.ByteOrder;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassNativeBridge&nbsp;{
&nbsp; &nbsp;&nbsp;publicstaticfinalint$stable=0;
&nbsp; &nbsp;&nbsp;publicstaticfinalintERR_CHEAT=&nbsp;-7;
&nbsp; &nbsp;&nbsp;publicstaticfinalNativeBridgeINSTANCE=newNativeBridge();
&nbsp; &nbsp;&nbsp;publicstaticfinalintSCORE_GOOD=1;
&nbsp; &nbsp;&nbsp;publicstaticfinalintSCORE_MISS=0;
&nbsp; &nbsp;&nbsp;publicstaticfinalintSCORE_PERFECT=2;

&nbsp; &nbsp;&nbsp;static&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; System.loadLibrary("hajimi");
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;privateNativeBridge()&nbsp;{
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;privatefinal
... [truncated&nbsp;1458&nbsp;bytes] ...
boolean&nbsp;z2);

&nbsp; &nbsp;&nbsp;publicfinalvoidstartSession(long&nbsp;j2,&nbsp;int[] iArr,&nbsp;int&nbsp;i2)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; h.e(iArr,&nbsp;"beatMapMs");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ByteBufferbyteBufferOrder=&nbsp;ByteBuffer.allocate(iArr.length *&nbsp;4).order(ByteOrder.LITTLE_ENDIAN);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i3 : iArr) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byteBufferOrder.putInt(i3);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte[] bArrArray = byteBufferOrder.array();
&nbsp; &nbsp; &nbsp; &nbsp; h.d(bArrArray,&nbsp;"array(...)");
&nbsp; &nbsp; &nbsp; &nbsp; startSessionBytes(j2, bArrArray, i2);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;publicfinalnativelongupdateExp(int&nbsp;i2,&nbsp;int&nbsp;i3,&nbsp;long&nbsp;j2);

&nbsp; &nbsp;&nbsp;publicfinalnativebyte[] verifyAndDecrypt(byte[] bArr, String str);
}

📋 文件内容 [14:15:20]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;com.zj.wuaipojie2026_2;

import&nbsp;A.e;
import&nbsp;Q0.AbstractC0080d;
import&nbsp;a.AbstractC0154a;
import&nbsp;android.content.res.Resources;
import&nbsp;android.os.Build;
import&nbsp;android.os.Bundle;
import&nbsp;android.view.View;
import&nbsp;android.view.ViewGroup;
import&nbsp;android.view.Window;
import&nbsp;androidx.activity.A;
import&nbsp;androidx.activity.B;
import&nbsp;androidx.activity.l;
import&nbsp;androidx.activity.m;
import&nbsp;androidx.activity.n;
import&nbsp;androidx.activity.o;
import&nbsp;androidx.activity.p;
import&nbsp;androidx.activity.q;
import&nbsp;androidx.compose.ui.platform.C0204f0;
import&nbsp;androidx.lifecycle.E;
import&nbsp;f1.h
... [truncated&nbsp;2753&nbsp;bytes] ...
ar);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; C0204f0 c0204f02 = new C0204f0(this);
&nbsp; &nbsp; &nbsp; &nbsp; c0204f02.setParentCompositionContext(null);
&nbsp; &nbsp; &nbsp; &nbsp; c0204f02.setContent(eVar);
&nbsp; &nbsp; &nbsp; &nbsp; View decorView2 = getWindow().getDecorView();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(E.c(decorView2) ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; E.g(decorView2,&nbsp;this);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(E.d(decorView2) ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; decorView2.setTag(R.id.view_tree_view_model_store_owner,&nbsp;this);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(a.A(decorView2) ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a.a0(decorView2,&nbsp;this);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; setContentView(c0204f02, AbstractC0154a.f1861a);
&nbsp; &nbsp; }
}

🤖 助手 – 第 59 轮 [14:15:30]

现在我看到了完整的 API。关键方法:

  • startSession(timestamp, beatMapMs, interval)

  • 初始化会话

  • checkRhythm(timestamp, noteIndex, currentTime, mode)

  • 检查节奏

  • updateExp(score, noteIndex, timestamp)

  • 更新经验

  • decryptFrames(data, key)

  • 解密帧

  • verifyAndDecrypt(data, password)

  • 验证并解密

  • setDebugBypass(true)

  • 设置调试绕过

让我找到调用这些方法的 Java 代码:

🔨 工具调用: strings_search

  • max_matches:

    50

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    startSession|verifyAndDecrypt|decryptFrames|setDebugBypass|beatMap|hjm_pack

  • recursive:

    true

📋 strings_search 输出 [14:15:30]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:23:> &nbsp; &nbsp;privatefinalnativevoidstartSessionBytes(long&nbsp;j2,&nbsp;byte[] bArr,&nbsp;int&nbsp;i2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:27:> &nbsp; &nbsp;publicfinalnativebyte[] decryptFrames(byte[] bArr,&nbsp;long&nbsp;j2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:29:> &nbsp; &nbsp;publicfinalnativevoidsetDebugBypass(boolean&nbsp;z2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:31:> &nbsp; &nbsp;publicfinal
... [truncated&nbsp;1744&nbsp;bytes] ...
3q.java:33:> &nbsp; &nbsp; &nbsp; &nbsp;InputStreaminputStreamOpen=this.f1185m.getAssets().open("hjm_pack.bin");
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\r.java:35:> &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;NativeBridge.INSTANCE.decryptFrames(this.f1186m,&nbsp;this.f1187n.f1107a);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\y.java:32:> &nbsp; &nbsp; &nbsp; &nbsp;InputStreaminputStreamOpen=this.f1216m.getAssets().open("hjm_pack.bin");
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\z.java:39:> &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;nativeBridge.verifyAndDecrypt(this.f1217m, n1.k.j0((String)&nbsp;this.f1218n.getValue()).toString());

[11&nbsp;matches in&nbsp;5/2191&nbsp;file(s)]

🤖 助手 – 第 60 轮 [14:15:41]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0093q.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\z.java

📋 文件内容 [14:15:41]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;android.content.Context;
import&nbsp;java.io.InputStream;
import&nbsp;o1.InterfaceC0603z;

/* JADX INFO: renamed from: Q0.q, reason: case insensitive filesystem */
/* JADX INFO: loaded from: classes.dex */
publicfinalclassC0093qextendsY0.i&nbsp;implementse1.e {

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: m, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */&nbsp;Context f1185m;

&nbsp; &nbsp;&nbsp;/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
&nbsp; &nbsp;&nbsp;publicC0093q(Context context, W0.d dVar)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(2, dVar);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1185m = context;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// e1.e
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;N(Object obj, Object obj2)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;((C0093q) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;W0.d&nbsp;c(W0.d dVar, Object obj)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnewC0093q(this.f1185m, dVar);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;g(Object obj)throws&nbsp;Throwable {
&nbsp; &nbsp; &nbsp; &nbsp; R.c.Y(obj);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;InputStreaminputStreamOpen=this.f1185m.getAssets().open("hjm_pack.bin");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f1.h.b(inputStreamOpen);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte[] bArrJ = b0.g.J(inputStreamOpen);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0.d.p(inputStreamOpen,&nbsp;null);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;bArrJ;
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;finally&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

📋 文件内容 [14:15:41]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
import&nbsp;java.util.List;
import&nbsp;o1.InterfaceC0603z;
import&nbsp;t.InterfaceC0680W;

/* JADX INFO: loaded from: classes.dex */
publicfinalclasszextendsY0.i&nbsp;implementse1.e {

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: m, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */byte[] f1217m;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: n, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */&nbsp;InterfaceC0680W f1218n;

&nbsp; &nbsp;&nbsp;/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
&nbsp; &nbsp;&nbsp;publicz(byte[] bArr, InterfaceC0680W interfaceC0680W, W0.d dVar)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(2, dVar);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1217m = bArr;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1218n = interfaceC0680W;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// e1.e
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;N(Object obj, Object obj2)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;((z) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;W0.d&nbsp;c(W0.d dVar, Object obj)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnewz(this.f1217m,&nbsp;this.f1218n, dVar);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;g(Object obj)throws&nbsp;Throwable {
&nbsp; &nbsp; &nbsp; &nbsp; R.c.Y(obj);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NativeBridgenativeBridge=&nbsp;NativeBridge.INSTANCE;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Listlist=&nbsp;N.f1106a;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;nativeBridge.verifyAndDecrypt(this.f1217m, n1.k.j0((String)&nbsp;this.f1218n.getValue()).toString());
&nbsp; &nbsp; }
}

🤖 助手 – 第 61 轮 [14:15:47]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\r.java

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\y.java

📋 文件内容 [14:15:47]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
import&nbsp;o1.InterfaceC0603z;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassrextendsY0.i&nbsp;implementse1.e {

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: m, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */byte[] f1186m;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: n, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */&nbsp;O f1187n;

&nbsp; &nbsp;&nbsp;/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
&nbsp; &nbsp;&nbsp;publicr(byte[] bArr, O o2, W0.d dVar)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(2, dVar);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1186m = bArr;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1187n = o2;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// e1.e
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;N(Object obj, Object obj2)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;((r) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;W0.d&nbsp;c(W0.d dVar, Object obj)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnewr(this.f1186m,&nbsp;this.f1187n, dVar);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;g(Object obj)throws&nbsp;Throwable {
&nbsp; &nbsp; &nbsp; &nbsp; R.c.Y(obj);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;NativeBridge.INSTANCE.decryptFrames(this.f1186m,&nbsp;this.f1187n.f1107a);
&nbsp; &nbsp; }
}

📋 文件内容 [14:15:47]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;android.content.Context;
import&nbsp;java.io.InputStream;
import&nbsp;o1.InterfaceC0603z;

/* JADX INFO: loaded from: classes.dex */
publicfinalclassyextendsY0.i&nbsp;implementse1.e {

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: m, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */&nbsp;Context f1216m;

&nbsp; &nbsp;&nbsp;/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
&nbsp; &nbsp;&nbsp;publicy(Context context, W0.d dVar)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(2, dVar);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1216m = context;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// e1.e
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;N(Object obj, Object obj2)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;((y) c((W0.d) obj2, (InterfaceC0603z) obj)).g(S0.k.f1286a);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;W0.d&nbsp;c(W0.d dVar, Object obj)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnewy(this.f1216m, dVar);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;@Override// Y0.a
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Object&nbsp;g(Object obj)throws&nbsp;Throwable {
&nbsp; &nbsp; &nbsp; &nbsp; R.c.Y(obj);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;InputStreaminputStreamOpen=this.f1216m.getAssets().open("hjm_pack.bin");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f1.h.b(inputStreamOpen);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte[] bArrJ = b0.g.J(inputStreamOpen);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0.d.p(inputStreamOpen,&nbsp;null);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;bArrJ;
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;finally&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

🤖 助手 – 第 62 轮 [14:15:53]

现在让我找到调用 startSession 和 setDebugBypass 的地方,以及 beatMap 数据:

🔨 工具调用: strings_search

  • max_matches:

    50

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    startSession|setDebugBypass|beatMap|checkRhythm|updateExp

  • recursive:

    true

📋 strings_search 输出 [14:15:53]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:23:> &nbsp; &nbsp;privatefinalnativevoidstartSessionBytes(long&nbsp;j2,&nbsp;byte[] bArr,&nbsp;int&nbsp;i2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:25:> &nbsp; &nbsp;publicfinalnativeintcheckRhythm(long&nbsp;j2,&nbsp;int&nbsp;i2,&nbsp;long&nbsp;j3,&nbsp;int&nbsp;i3);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:29:> &nbsp; &nbsp;publicfinalnativevoidsetDebugBypass(boolean&nbsp;z2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:31:> &nbsp; &nbsp;publi
... [truncated&nbsp;1427&nbsp;bytes] ...
ources\com\zj\wuaipojie2026_2\NativeBridge.java:39:> &nbsp; &nbsp; &nbsp; &nbsp;startSessionBytes(j2, bArrArray, i2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:42:> &nbsp; &nbsp;publicfinalnativelongupdateExp(int&nbsp;i2,&nbsp;int&nbsp;i3,&nbsp;long&nbsp;j2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\v.java:58:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;intiCheckRhythm=&nbsp;nativeBridge.checkRhythm(jElapsedRealtimeNanos, i2, j6, i7);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\v.java:59:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;longjUpdateExp=&nbsp;nativeBridge.updateExp(iCheckRhythm, i2, j6);

[9&nbsp;matches in&nbsp;2/2191&nbsp;file(s)]

🤖 助手 – 第 63 轮 [14:16:00]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\v.java

📋 文件内容 [14:16:00]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;E0.C0021h;
import&nbsp;android.os.SystemClock;
import&nbsp;com.zj.wuaipojie2026_2.NativeBridge;
import&nbsp;t.C0691d0;

/* JADX INFO: loaded from: classes.dex */
publicfinal/* synthetic */classvextendsf1.f&nbsp;implementse1.a {

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: q, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal/* synthetic */int&nbsp;f1199q;

&nbsp; &nbsp;&nbsp;/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
&nbsp; &nbsp;&nbsp;public/* synthetic */&nbsp;v(int&nbsp;i2, Object obj, Class cls, String str, String str2,&nbsp;int&nbsp;i3,&nbsp;int&nbsp;i4) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super(i2, obj, cls, s
... [truncated&nbsp;3492&nbsp;bytes] ...
q2 = iCheckRhythm !=&nbsp;0&nbsp;? iCheckRhythm !=&nbsp;1&nbsp;? iCheckRhythm !=&nbsp;2&nbsp;? Q.None : Q.Perfect : Q.Good : Q.Miss;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; o3.getClass();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; o2 =&nbsp;newO(jUpdateExp, q2,&nbsp;false);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c0691d0.setValue(o2);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;NativeBridge.SCORE_GOOD&nbsp;/* 1 */:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((androidx.activity.x)&nbsp;this.f3686j).d();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((androidx.activity.x)&nbsp;this.f3686j).d();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;S0.k.f1286a;
&nbsp; &nbsp; }
}

🤖 助手 – 第 64 轮 [14:16:07]

现在让我找到 startSession 的调用和 beatMap 数据:

🔨 工具调用: strings_search

  • recursive:

    true

  • max_matches:

    50

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    startSession\(|f1107a|C0082f|C0021h|f318b

📋 strings_search 输出 [14:16:07]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\C0256c.java:3:>import E0.C0021h;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\C0256c.java:13:>public&nbsp;final class C0256c extends&nbsp;C0021h&nbsp;{
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\C0256c.java:19:> &nbsp; &nbsp;@Override // E0.C0021h
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\C0256c.java:42:> &nbsp; &nbsp;@Override // E0.C0021h
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\d.java:3:>import E0.C0021h;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\B0\d.java:38:> &nbsp; &nbsp;public&nbsp;d(C0021hc0021h) {
D:\AI\AICTF\workdir\52pojie\chu8\ja
... [truncated&nbsp;6009&nbsp;bytes] ...
hu8\jadx_out\sources\E0\C0021h.java:419:> &nbsp; &nbsp;publicC0021h(AndroidComposeView androidComposeView) {
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\E0\C0021h.java:427:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;c0021h&nbsp;= new&nbsp;C0021h(7, androidComposeView);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\E0\C0021h.java:429:> &nbsp; &nbsp; &nbsp; &nbsp;this.f318b =&nbsp;c0021h;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\E0\C0021h.java:432:> &nbsp; &nbsp;publicC0021h(Matcher matcher, String&nbsp;str) {
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\E0\C0021h.java:434:> &nbsp; &nbsp; &nbsp; &nbsp;this.f318b = matcher;

[50&nbsp;matches&nbsp;in3/127&nbsp;file(s)] [truncated&nbsp;at50&nbsp;matches]

👤 用户 [14:16:07]

[TodoList Reminder – Round 25] Stats: 4 pending, 0 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 65 轮 [14:16:13]

🔨 工具调用: strings_search

  • pattern:

    startSession|beatMap|setDebug|decryptFrames|verifyAndDecrypt

  • recursive:

    true

  • max_matches:

    30

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0

📋 strings_search 输出 [14:16:13]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\r.java:35:> &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;NativeBridge.INSTANCE.decryptFrames(this.f1186m,&nbsp;this.f1187n.f1107a);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\z.java:39:> &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;nativeBridge.verifyAndDecrypt(this.f1217m, n1.k.j0((String)&nbsp;this.f1218n.getValue()).toString());

[2&nbsp;matches&nbsp;in2/63&nbsp;file(s)]

🤖 助手 – 第 66 轮 [14:16:20]

🔨 工具调用: strings_search

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    \.startSession\(

  • recursive:

    true

  • max_matches:

    30

📋 strings_search 输出 [14:16:20]

&nbsp;复制代码&nbsp;隐藏代码

[0 matches in 0/2191 file(s)]

🤖 助手 – 第 67 轮 [14:16:24]

🔨 工具调用: strings_search

  • max_matches:

    30

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources

  • pattern:

    startSession

  • recursive:

    true

📋 strings_search 输出 [14:16:25]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:23:> &nbsp; &nbsp;privatefinalnativevoidstartSessionBytes(long&nbsp;j2,&nbsp;byte[] bArr,&nbsp;int&nbsp;i2);
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:31:> &nbsp; &nbsp;publicfinalvoidstartSession(long&nbsp;j2,&nbsp;int[] iArr,&nbsp;int&nbsp;i2)&nbsp;{
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\com\zj\wuaipojie2026_2\NativeBridge.java:39:> &nbsp; &nbsp; &nbsp; &nbsp;startSessionBytes(j2, bArrArray, i2);

[3&nbsp;matches in&nbsp;1/2191&nbsp;file(s)]

🤖 助手 – 第 68 轮 [14:16:37]

startSession 只在 NativeBridge 中定义但没有在 Java 代码中被调用——这意味着它可能是通过 Compose 框架间接调用的。让我换个策略,直接用动态方法——编写一个 C 程序加载 .so 并调用这些函数:

🔨 工具调用: todolist

  • action:

    reset

  • tasks:

    [1. 用Python重新实现native库的解密逻辑:SipHash + XXHash64 + AES-CTR 2. 确定初始状态:找到beatMap数据和startSession参数 3. 模拟debug bypass路径(sub_2E570)生成密钥 4. 解密hjm_pack.bin并渲染64x64位图 5. 提交flag]

📋 todolist 输出 [14:16:37]

&nbsp;复制代码&nbsp;隐藏代码
TodoList reset with&nbsp;5&nbsp;task(s):
⬜&nbsp;1. 用Python重新实现native库的解密逻辑:SipHash + XXHash64 + AES-CTR
⬜&nbsp;2. 确定初始状态:找到beatMap数据和startSession参数
⬜&nbsp;3. 模拟debugbypass路径(sub_2E570)生成密钥
⬜&nbsp;4. 解密hjm_pack.bin并渲染64x64位图
⬜&nbsp;5. 提交flag

🤖 助手 – 第 69 轮 [14:16:44]

让我先找到 beatMap 数据。搜索 Compose 中的调用:

🔨 工具调用: strings_search

  • max_matches:

    30

  • paths:

    D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0

  • pattern:

    C0082f|f1143e|f1141c|f1144f

  • recursive:

    true

📋 strings_search 输出 [14:16:44]

&nbsp;复制代码&nbsp;隐藏代码
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:12:>publicfinalclassC0082f&nbsp;{
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:21:> &nbsp; &nbsp;publicfinal&nbsp;C0021h f1141c;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:27:> &nbsp; &nbsp;publiclong&nbsp;f1143e;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:30:> &nbsp; &nbsp;publicfinal&nbsp;C0691d0 f1144f;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:32:> &nbsp; &nbsp;publicC0082f(Context context)&nbsp;{
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java:37:> &nbsp; &nbsp; &nbsp; &nbsp;this.f1141c =&nbsp;newC0021h
... [truncated&nbsp;2179&nbsp;bytes] ...
long&nbsp;j3, O o2, k0.l lVar, k0.l lVar2,&nbsp;int[] iArr, C0082f c0082f,&nbsp;long&nbsp;j4,&nbsp;long&nbsp;j5, InterfaceC0680W interfaceC0680W, InterfaceC0680W interfaceC0680W2,&nbsp;int&nbsp;i2, C0311D c0311d, InterfaceC0680W interfaceC0680W3) {
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\x.java:181:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;C0082fc0082f=this.f1208p;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\x.java:182:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;longj4=&nbsp;c0082f.f1143e;
D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\x.java:261:> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;vvVar=newv(0, c0082f, C0082f.class,&nbsp;"onFeedTap",&nbsp;"onFeedTap()V",&nbsp;0,&nbsp;0);

[17&nbsp;matches in&nbsp;3/63&nbsp;file(s)]

🤖 助手 – 第 70 轮 [14:16:49]

📖 读取文件: D:\AI\AICTF\workdir\52pojie\chu8\jadx_out\sources\q0\C0082f.java

📋 文件内容 [14:16:49]

&nbsp;复制代码&nbsp;隐藏代码
package&nbsp;Q0;

import&nbsp;E0.C0021h;
import&nbsp;android.content.Context;
import&nbsp;android.media.MediaPlayer;
import&nbsp;t.AbstractC0713v;
import&nbsp;t.C0677T;
import&nbsp;t.C0691d0;

/* JADX INFO: renamed from: Q0.f, reason: case insensitive filesystem */
/* JADX INFO: loaded from: classes.dex */
publicfinalclassC0082f&nbsp;{

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: a, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;Context f1139a;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: b, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinalint[] f1140b;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: c, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;C0021h f1141c;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: d, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;public&nbsp;MediaPlayer f1142d;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: e, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publiclong&nbsp;f1143e;

&nbsp; &nbsp;&nbsp;/* JADX INFO: renamed from: f, reason: collision with root package name */
&nbsp; &nbsp;&nbsp;publicfinal&nbsp;C0691d0 f1144f;

&nbsp; &nbsp;&nbsp;publicC0082f(Context context)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; f1.h.e(context,&nbsp;"context");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1139a = context;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int[] iArr = {0,&nbsp;250,&nbsp;500,&nbsp;750};
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1140b = iArr;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1141c =&nbsp;newC0021h(4, iArr);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this.f1144f = AbstractC0713v.E(newO(0L, Q.None,&nbsp;false), C0677T.f6486m);
&nbsp; &nbsp; }
}

👤 用户 [14:16:49]

[Progress Checkpoint – Round 31/200] Briefly assess your progress:

  1. What have you accomplished so far?
  2. Are you making progress toward the flag? Continue with your current approach if it shows promise.

🤖 助手 – 第 71 轮 [14:17:26]

🔨 工具调用: mcp_sequential-thinking_sequentialthinking

  • totalThoughts:

    3

  • thought:

    `CRITICAL FINDING: beatMap = {0, 250, 500, 750} (4 entries, interval 250ms)

Now I have all the pieces:

  1. beatMap = [0, 250, 500, 750] as int[] -> converted to bytes (little-endian int32s) = 16 bytes
  2. startSession is called with (timestamp, beatMapBytes, interval=250)
  3. In startSessionBytes:
  • dword_5CFD0 = count of beats = 4

  • dword_5CFD4 = interval = 250

  • qword_5CFD8 = timestamp

  • qword_5CFE0 = XXHash64(beatMapBytes, 16, 0x1A8CBC5B802E097C)

  • qword_5CFE8 = qword_5CFE0 (initi…`

  • nextThoughtNeeded:

    false

  • thoughtNumber:

    3

📋 mcp_sequential-thinking_sequentialthinking 输出 [14:17:26]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"thoughtNumber":3,
&nbsp;&nbsp;"totalThoughts":3,
&nbsp;&nbsp;"nextThoughtNeeded":false,
&nbsp;&nbsp;"branches":[],
&nbsp;&nbsp;"thoughtHistoryLength":7
}

🤖 助手 – 第 72 轮 [14:17:36]

关键发现:beatMap = {0, 250, 500, 750},密钥完全由 beatMap 的 XXHash64 决定,不依赖时间戳!现在开始写完整的解密脚本:

🔨 工具调用: todolist

  • action:

    update

  • id:

    2

  • status:

    done

  • result:

    beatMap=[0,250,500,750], interval=250, 密钥不依赖时间戳

📋 todolist 输出 [14:17:36]

&nbsp;复制代码&nbsp;隐藏代码
Updated task&nbsp;#2. Current TodoList:
⬜&nbsp;1. 用Python重新实现native库的解密逻辑:SipHash + XXHash64 + AES-CTR
✅&nbsp;2. 确定初始状态:找到beatMap数据和startSession参数&nbsp;→&nbsp;beatMap=[0,250,500,750],&nbsp;interval=250, 密钥不依赖时间戳
⬜&nbsp;3. 模拟debugbypass路径(sub_2E570)生成密钥
⬜&nbsp;4. 解密hjm_pack.bin并渲染64x64位图
⬜&nbsp;5. 提交flag

👤 用户 [14:17:36]

[TodoList Reminder – Round 33] Stats: 4 pending, 1 done, 0 failed. You have 4 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

👤 用户 []

[Context compacted — previous conversation summary follows]

→ Tool call: todolist({“action”: “update”, “id”: 1, “status”: “in_progress”}) [Tool todolist]: Updated task #1. Current TodoList: 🔄 1. 用Python重新实现native库的解密逻辑:SipHash + XXHash64 + AES-CTR ✅ 2. 确定初始状态:找到beatMap数据和startSession参数 → beatMap=[0,250,500,750], interval=250, 密钥不依赖时间戳 ⬜ 3. 模拟debug bypass路径(sub_2E570)生成密钥 ⬜ 4. 解密hjm_pack.bin并渲染64×64位图 ⬜ 5. 提交flag

[Assistant]: → Tool call: fsWrite({“path”: “D:\AI\AICTF\workdir\52pojie\chu8\decrypt.py”, “contents”: “#!/usr/bin/env python3\nimport struct\nimport sys\nfrom Crypto.Cipher import AES\nfrom Crypto.Util import Counter\n\n# AES S-box (from 0x17430)\nAES_SBOX = bytes([\n    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,\n    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,\n    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,\n    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,\n    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,\n    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,\n    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,\n    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,\n    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,\n    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,\n    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5f, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,\n    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,\n    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,\n    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,\n    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,\n    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16\n])\n\ndef xxhash64(data, seed):\n    \”\”\”XXHash64 implementation\”\”\”\n    PRIME64_1 = 0x9E3779B185EBCA87\n    PRIME64_2 = 0xC2B2AE3D27D4EB4F\n    PRIME64_5 = 0x27D4EB2D165667C5\n    \n    h64 = seed + PRIME64_5\n    \n    # Process 8-byte chunks\n    for i in range(0, len(data) – 7, 8):\n        chunk = struct.unpack(‘chunk\n        h64 = ((h64 << 31) | (h64 >> 33)) & 0xFFFFFFFFFFFFFFFF\n        h64= PRIME64_1\n        h64 &= 0xFFFFFFFFFFFFFFFF\n    \n    # Process remaining bytes\n    remaining = len(data) % 8\n    if remaining >= 4:\n        chunk = struct.unpack(‘chunk\n        h64 = ((h64 << 11) | (h64 >> 53)) & 0xFFFFFFFFFFFFFFFF\n        h64= PRIME64_1\n        h64 &= 0xFFFFFFFFFFFFFFFF\n    \n    if remaining & 2:\n        chunk = struct.unpack(‘chunk\n        h64 = ((h64 << 11) | (h64 >> 53)) & 0xFFFFFFFFFFFFFFFF\n        h64= PRIME64_1\n        h64 &= 0xFFFFFFFFFFFFFFFF\n    \n    if remaining & 1:\n        chunk = data[-1]\n        h64 ^= PRIME64_5 chunk\n        h64 = ((h64 << 11) | (h64 >> 53)) & 0xFFFFFFFFFFFFFFFF\n        h64= PRIME64_1\n        h64 &= 0xFFFFFFFFFFFFFFFF\n    \n    h64 ^= h64 >> 33\n    h64 *= PRIME642\n    h64 &= 0xFFFFFFFFFFFFFFFF\n    h64 ^= h64 >> 29\n    return h64\n\ndef siphash24(key, msg):\n    \”\”\”SipHash-2-4 implementation\”\”\”\n    # Initialize state\n    k0, k1 = struct.unpack(‘in range(2):\n            v0 = (v0 + v1) & 0xFFFFFFFFFFFFFFFF\n            v1 = ((v1 << 13) | (v1 >> 51)) & 0xFFFFFFFFFFFFFFFF\n            v1 ^= v0\n            v0 = ((v0 << 32) | (v0 >> 32)) & 0xFFFFFFFFFFFFFFFF\n            v2 = (v2 + v3) & 0xFFFFFFFFFFFFFFFF\n            v3 = ((v3 << 16) | (v3 >> 48)) & 0xFFFFFFFFFFFFFFFF\n            v3 ^= v2\n            v0 = (v0 + v3) & 0xFFFFFFFFFFFFFFFF\n            v3 = ((v3 << 21) | (v3 >> 43)) & 0xFFFFFFFFFFFFFFFF\n            v3 ^= v0\n            v2 = (v2 + v1) & 0xFFFFFFFFFFFFFFFF\n            v1 = ((v1 << 17) | (v1 >> 47)) & 0xFFFFFFFFFFFFFFFF\n            v1 ^= v2\n            v2 = ((v2 << 32) | (v2 >> 32)) & 0xFFFFFFFFFFFFFFFF\n        v0 ^= m\n    \n    # Finalization\n    b = (len(msg) & 0xFF) << 56\n    v3 ^= b\n    for _ in range(4):\n        v0 = (v0 + v1) & 0xFFFFFFFFFFFFFFFF\n        v1 = ((v1 << 13) | (v1 >> 51)) & 0xFFFFFFFFFFFFFFFF\n        v1 ^= v0\n        v0 = ((v0 << 32) | (v0 >> 32)) & 0xFFFFFFFFFFFFFFFF\n        v2 = (v2 + v3) & 0xFFFFFFFFFFFFFFFF\n        v3 = ((v3 << 16) | (v3 >> 48)) & 0xFFFFFFFFFFFFFFFF\n        v3 ^= v2\n        v0 = (v0 + v3) & 0xFFFFFFFFFFFFFFFF\n        v3 = ((v3 << 21) | (v3 >> 43)) & 0xFFFFFFFFFFFFFFFF\n        v3 ^= v0\n        v2 = (v2 + v1) & 0xFFFFFFFFFFFFFFFF\n        v1 = ((v1 << 17) | (v1 >> 47)) & 0xFFFFFFFFFFFFFFFF\n        v1 ^= v2\n        v2 = ((v2 << 32) | (v2 >> 32)) & 0xFFFFFFFFFFFFFFFF\n    \n    return (v0 ^ v1 ^ v2 ^ v3) & 0xFFFFFFFFFFFFFFFF\n\ndef decrypt_hjm(encrypted_data, salt, iv):\n    \”\”\”Decrypt HJM1 frame\”\”\”\n    # beatMap = [0, 250, 500, 750]\n    beatmap_bytes = struct.pack(‘<IIII’, 0, 250, 500, 750)\n    \n    # Compute XXHash64 of beatmap\n    xxhash_seed = 0x1A8CBC5B802E097C\n    xxhash_result = xxhash64(beatmap_bytes, xxhash_seed)\n    print(f\”XXHash64(beatmap) = 0x{xxhash_result:016x}\”)\n    \n    # Compute SipHash-2-4 with salt as key\n    siphash_result = siphash24(salt, beatmap_bytes)\n    print(f\”SipHash24(salt, beatmap) = 0x{siphash_result:016x}\”)\n    \n    # Derive AES key from both hashes\n    key_material = struct.pack(‘<QQ’, xxhash_result, siphash_result)\n    print(f\”Key material: {key_material.hex()}\”)\n    \n    # Use AES-128-CTR with IV\n    cipher = AES.new(key_material[:16], AES.MODE_CTR, nonce=iv[:8], initial_value=iv[8:16])\n    decrypted = cipher.decrypt(encrypted_data)\n    \n    return decrypted\n\nif name == \”main\”:\n    # Read hjm_pack.bin\n    with open(r\”D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin\”, \”rb\”) as f:\n        data = f.read()\n    \n    # Parse header\n    magic = struct.unpack(‘<I’, data[0:4])[0]\n    frame_type = struct.unpack(‘<I’, data[4:8])[0]\n    frame_count = struct.unpack(‘<I’, data[8:12])[0]\n    height = struct.unpack(‘<I’, data[12:16])[0]\n    width = struct.unpack(‘<I’, data[16:20])[0]\n    unknown = struct.unpack(‘<I’, data[20:24])[0]\n    salt = data[24:40]\n    iv = data[40:52]\n    encrypted = data[52:]\n    \n    print(f\”Magic: 0x{magic:08x}\”)\n    print(f\”Type: {frame_type}\”)\n    print(f\”Count: {frame_count}\”)\n    print(f\”Height: {height}, Width: {width}\”)\n    print(f\”Salt: {salt.hex()}\”)\n    print(f\”IV: {iv.hex()}\”)\n    print(f\”Encrypted size: {len(encrypted)} bytes\”)\n    \n    # Decrypt\n    decrypted = decrypt_hjm(encrypted, salt, iv)\n    \n    # Save decrypted bitmap\n    with open(r\”D:\AI\AICTF\workdir\52pojie\chu8\bitmap.bin\”, \”wb\”) as f:\n        f.write(decrypted)\n    \n    print(f\”Decrypted bitmap saved to bitmap.bin\”)\n    print(f\”First 32 bytes: {decrypted[:32].hex()}\”)\n”}) [Tool fsWrite]: File written successfully: D:\AI\AICTF\workdir\52pojie\chu8\decrypt.py

[Assistant]: → Tool call: exec({“command”: “cd D:\AI\AICTF\workdir\52pojie\chu8 && python decrypt.py”}) [Tool exec]: XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) calls: 5 XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x2d3c4b5a69788796 SipHash24(salt, beatmap) = 0xa5b4c3d2e1f01122 Magic: 0x314d4a48 Type: 2 Count: 1 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: 64 Width: 64 Height: unset Width: unset Magic: 0x314d4a48 Type: 2 Count: 1 Height: 64 Width: 64 Unknown: 120 Salt: 0f1e2d3c4b5a69788796a5b4c3d2e1f0 IV: 112233445566778899aabbcc Encrypted size: 512 bytes XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e SipHash24(salt, beatmap) = 0xa5b4c3d2e1f01122 Key material: 3e8e3e8e3e8e3e8ea5b4c3d2e1f01122 Decrypted bitmap saved to bitmap.bin First 32 bytes: 0000000000000000000000000000000000000000000000000000000000000000

[User]: [System — Repetitive Pattern Notice] ⚠️ Tool ‘exec’ called 2 times in last 3 calls — likely stuck in a loop

You may be repeating the same approach without making progress.

Suggested actions:

  1. Check the error messages carefully — is there a specific fix you’re missing?
  2. If the current approach keeps failing for the same reason, consider adjusting your method
  3. If the errors are different each time, you’re still making progress — keep iterating
  4. Use ideas tool (action: list) to review alternative approaches if needed

Do NOT abandon a working approach just because of minor failures — debug and fix instead.

Recent tool results (evidence from last round): [OK] exec({“command”: “cd D:\AI\AICTF\workdir\52pojie\chu8 && python decrypt.py”}) -> XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e3e8e3e8e3e XXHash64(beatmap) = 0x8e3e8e_…

[Assistant: 解密结果全是零,说明密钥不对。问题在于我的 XXHash64 实现有误。让我用标准库重新实现: → Tool call: fsWrite({“path”: “D:\AI\AICTF\workdir\52pojie\chu8\decrypt2.py”, “contents”: “#!/usr/bin/env python3\nimport struct\nimport sys\nfrom Crypto.Cipher import AES\n\ndef xxhash64_correct(data, seed):\n    \”\”\”Correct XXHash64 implementation\”\”\”\n    PRIME64_1 = 0x9E3779B185EBCA87\n    PRIME64_2 = 0xC2B2AE3D27D4EB4F\n    PRIME64_5 = 0x27D4EB2D165667C5\n    \n    if len(data) >= 32:\n        # Process 4 lanes\n        v1 = seed + PRIME64_1 + PRIME64_2\n        v2 = seed + PRIME64_2\n        v3 = seed\n        v4 = seed – PRIME64_1\n        \n        for i in range(0, len(data) – 31, 32):\n            v1 = (v1 + struct.unpack(‘PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n            v1 = ((v1 << 31) | (v1 >> 33)) & 0xFFFFFFFFFFFFFFFF\n            v1 = (v1PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n            \n            v2 = (v2 + struct.unpack(‘PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n            v2 = ((v2 << 31) | (v2 >> 33)) & 0xFFFFFFFFFFFFFFFF\n            v2 = (v2PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n            \n            v3 = (v3 + struct.unpack(‘PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n            v3 = ((v3 << 31) | (v3 >> 33)) & 0xFFFFFFFFFFFFFFFF\n            v3 = (v3PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n            \n            v4 = (v4 + struct.unpack(‘PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n            v4 = ((v4 << 31) | (v4 >> 33)) & 0xFFFFFFFFFFFFFFFF\n            v4 = (v4PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n        \n        h64 = ((v1 << 1) | (v1 >> 63)) & 0xFFFFFFFFFFFFFFFF\n        h64 = (h64 + ((v2 << 7) | (v2 >> 57))) & 0xFFFFFFFFFFFFFFFF\n        h64 = (h64 + ((v3 << 12) | (v3 >> 52))) & 0xFFFFFFFFFFFFFFFF\n        h64 = (h64 + ((v4 << 18) | (v4 >> 46))) & 0xFFFFFFFFFFFFFFFF\n    else:\n        h64 = seed + PRIME64_5\n    \n    h64 = (h64 + len(data)) & 0xFFFFFFFFFFFFFFFF\n    \n    # Process remaining 8-byte chunks\n    offset = (len(data) // 32) 32\n    while offset + 8 <= len(data):\n        chunk = struct.unpack('PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n        h64 = (((h64 << 31) | (h64 >> 33)) PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n        offset += 8\n    \n    # Process remaining bytes\n    remaining = len(data) – offset\n    if remaining >= 4:\n        chunk = struct.unpack(‘PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n        h64 = (((h64 << 23) | (h64 >> 41)) PRIME64_2) & 0xFFFFFFFFFFFFFFFF\n        offset += 4\n    \n    while offset < len(data):\n        chunk = data[offset]\n        h64 ^= (chunkPRIME64_5) & 0xFFFFFFFFFFFFFFFF\n        h64 = (((h64 << 11) | (h64 >> 53)) PRIME64_1) & 0xFFFFFFFFFFFFFFFF\n        offset += 1\n    \n    h64 ^= h64 >> 33\n    h64 = (h64PRIME642) & 0xFFFFFFFFFFFFFFFF\n    h64 ^= h64 >> 29\n    return h64\n\ndef siphash24(key, msg):\n    \”\”\”SipHash-2-4 implementation\”\”\”\n    k0, k1 = struct.unpack(‘> 51)) & 0xFFFFFFFFFFFFFFFF\n        v1 ^= v0\n        v0 = ((v0 << 32) | (v0 >> 32)) & 0xFFFFFFFFFFFFFFFF\n        v2 = (v2 + v3) & 0xFFFFFFFFFFFFFFFF\n        v3 = ((v3 << 16) | (v3 >> 48)) & 0xFFFFFFFFFFFFFFFF\n        v3 ^= v2\n        v0 = (v0 + v3) & 0xFFFFFFFFFFFFFFFF\n        v3 = ((v3 << 21) | (v3 >> 43)) & 0xFFFFFFFFFFFFFFFF\n        v3 ^= v0\n        v2 = (v2 + v1) & 0xFFFFFFFFFFFFFFFF\n        v1 = ((v1 << 17) | (v1 >> 47)) & 0xFFFFFFFFFFFFFFFF\n        v1 ^= v2\n        v2 = ((v2 << 32) | (v2 >> 32)) & 0xFFFFFFFFFFFFFFFF\n    \n    # Process message\n    for i in range(0, len(msg) – 7, 8):\n        m = struct.unpack(‘in range(4):\n        sipround()\n    \n    return (v0 ^ v1 ^ v2 ^ v3) & 0xFFFFFFFFFFFFFFFF\n\nif name == \”main\”:\n    # Read hjm_pack.bin\n    with open(r\”D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin\”, \”rb\”) as f:\n        data = f.read()\n    \n    # Parse header\n    magic = struct.unpack(‘<I’, data[0:4])[0]\n    frame_type = struct.unpack(‘<I’, data[4:8])[0]\n    frame_count = struct.unpack(‘<I’, data[8:12])[0]\n    height = struct.unpack(‘<I’, data[12:16])[0]\n    width = struct.unpack(‘<I’, data[16:20])[0]\n    unknown = struct.unpack(‘<I’, data[20:24])[0]\n    salt = data[24:40]\n    iv = data[40:52]\n    encrypted = data[52:]\n    \n    print(f\”Magic: 0x{magic:08x}\”)\n    print(f\”Type: {frame_type}\”)\n    print(f\”Count: {frame_count}\”)\n    print(f\”Height: {height}, Width: {width}\”)\n    print(f\”Salt: {salt.hex()}\”)\n    print(f\”IV: {iv.hex()}\”)\n    print(f\”Encrypted size: {len(encrypted)} bytes\”)\n    \n    # beatMap = [0, 250, 500, 750]\n    beatmap_bytes = struct.pack(‘<IIII’, 0, 250, 500, 750)\n    print(f\”\nBeatmap bytes: {beatmap_bytes.hex()}\”)\n    \n    # Compute XXHash64 of beatmap\n    xxhash_seed = 0x1A8CBC5B802E097C\n    xxhash_result = xxhash64_correct(beatmap_bytes, xxhash_seed)\n    print(f\”XXHash64(beatmap, 0x{xxhash_seed:016x}) = 0x{xxhash_result:016x}\”)\n    \n    # Compute SipHash-2-4 with salt as key\n    siphash_result = siphash24(salt, beatmap_bytes)\n    print(f\”SipHash24(salt, beatmap) = 0x{siphash_result:016x}\”)\n    \n    # Derive AES key from both hashes\n    key_material = struct.pack(‘

🤖 助手 – 第 73 轮 [14:17:42]

🔨 工具调用: todolist

  • action:

    update

  • id:

    1

  • status:

    in_progress

📋 todolist 输出 [14:17:42]

&nbsp;复制代码&nbsp;隐藏代码
Updated task&nbsp;#1. Current TodoList:
🔄&nbsp;1. 用Python重新实现native库的解密逻辑:SipHash + XXHash64 + AES-CTR
✅&nbsp;2. 确定初始状态:找到beatMap数据和startSession参数&nbsp;→&nbsp;beatMap=[0,250,500,750],&nbsp;interval=250, 密钥不依赖时间戳
⬜&nbsp;3. 模拟debugbypass路径(sub_2E570)生成密钥
⬜&nbsp;4. 解密hjm_pack.bin并渲染64x64位图
⬜&nbsp;5. 提交flag

🤖 助手 – 第 74 轮 [14:17:49]

Let me implement the full decryption pipeline in one script. First, let me re-examine the critical functions more carefully to get the exact logic right:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • max_instructions:

    200

  • addr:

    0x2E570

📋 mcp_ida-pro-headless_disasm 输出 [14:17:49]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2E570",
&nbsp;&nbsp;"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_2E570",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x2e570",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_2E570 (.text @ 0x2e570):\n2e570 &nbsp;push &nbsp; &nbsp;rbp\n2e571 &nbsp;push &nbsp; &nbsp;r15\n2e573 &nbsp;push &nbsp; &nbsp;r14\n2e575 &nbsp;push &nbsp; &nbsp;r12\n2e577 &nbsp;push &nbsp; &nbsp;rbx\n2e578 &nbsp;sub &nbsp; &nbsp; rsp, 20h\n2e57c &nbsp;mov &nbsp; &nbsp; rax, fs:28h\n2e585 &nbsp;mov &nbsp; &nbsp; [rsp+48h+var_30], rax\n2e58a &nbsp;mov &nbsp; &nbsp; ebp, cs:dword_5CFD0\n2e590 &nbsp;xor &nbsp; &nbsp; ebx, ebx\n2e592 &nbsp;test &nbsp; &nbsp;ebp, ebp\n2e594 &nbsp;jle &nbsp; &nbsp; loc_2E650\n2e59a &nbsp;mov &nbsp; &nbsp; rax, cs:qword_5CFE8\n2e5a1 &nbsp;mov &nbsp; &nbsp; r14, 1A8CBC5B802E097Ch\n2e5ab &nbsp;xor &nbsp; &nbsp; r14, rax\n2e5ae &nbsp;mov &nbsp; &nbsp; rcx, 9E3779B97F4A7C15h\n2e5b8 &nbsp;add &nbsp; &nbsp; rcx, r14\n2e5
... [truncated 2915 bytes] ...
&nbsp; &nbsp; &nbsp;"offset": "0x10",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x4",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_DWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "var_34",
&nbsp; &nbsp; &nbsp; &nbsp; "offset": "0x14",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x4",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_DWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "var_30",
&nbsp; &nbsp; &nbsp; &nbsp; "offset": "0x18",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x8",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "__return_address",
&nbsp; &nbsp; &nbsp; &nbsp; "offset": "0x48",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x8",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_UNKNOWN&nbsp;*"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp; "return_type": "__int64",
&nbsp; &nbsp; "arguments": []
&nbsp; },
&nbsp; "instruction_count": 65,
&nbsp; "total_instructions": null,
&nbsp; "cursor": {
&nbsp; &nbsp; "done": true
&nbsp; }
}

🤖 助手 – 第 75 轮 [14:17:55]

Now let me get the exact layout of sub_2E680 and sub_2EB00 to understand the decryption:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x2EB00

  • max_instructions:

    200

📋 mcp_ida-pro-headless_disasm 输出 [14:17:55]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2EB00",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_2EB00",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x2eb00",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_2EB00 (.text @ 0x2eb00):\n2eb00 &nbsp;push &nbsp; &nbsp;r14\n2eb02 &nbsp;push &nbsp; &nbsp;rbx\n2eb03 &nbsp;sub &nbsp; &nbsp; rsp, 18h\n2eb07 &nbsp;mov &nbsp; &nbsp; rbx, rcx\n2eb0a &nbsp;mov &nbsp; &nbsp; r14, rdx\n2eb0d &nbsp;mov &nbsp; &nbsp; rax, fs:28h\n2eb16 &nbsp;mov &nbsp; &nbsp; [rsp+28h+var_18], rax\n2eb1b &nbsp;mov &nbsp; &nbsp; rdx, 1A8CBC5B802E097Ch\n2eb25 &nbsp;xor &nbsp; &nbsp; rdx, rsi\n2eb28 &nbsp;mov &nbsp; &nbsp; rcx, 802E097C1A8CBC5Bh\n2eb32 &nbsp;xor &nbsp; &nbsp; rcx, rsi\n2eb35 &nbsp;mov &nbsp; &nbsp; [rsp+28h+var_28], rdi\n2eb39 &nbsp;mov &nbsp; &nbsp; [rsp+28h+var_20], rsi\n2eb3e &nbsp;mov &nbsp; &nbsp; rdi, rsp\n2eb41 &nbsp;mov &nbsp; &nbsp; esi, 10h\n2eb46 &nbsp;call &nbsp; &nbsp;sub_2DAF0\n2eb4b &nbsp;mo
... [truncated 3453 bytes] ...
":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x28",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"unsigned __int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int64 *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;89,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"done":&nbsp;true
&nbsp; }
}

🤖 助手 – 第 76 轮 [14:18:04]

Now let me look at the decryption loop in sub_2E680 more carefully – specifically how the AES key is derived and used:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x2E680

  • max_instructions:

    400

📋 mcp_ida-pro-headless_disasm 输出 [14:18:04]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2E680",
&nbsp;&nbsp;"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_2E680",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x2e680",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_2E680 (.text @ 0x2e680):\n2e680 &nbsp;push &nbsp; &nbsp;rbp\n2e681 &nbsp;push &nbsp; &nbsp;r15\n2e683 &nbsp;push &nbsp; &nbsp;r14\n2e685 &nbsp;push &nbsp; &nbsp;r13\n2e687 &nbsp;push &nbsp; &nbsp;r12\n2e689 &nbsp;push &nbsp; &nbsp;rbx\n2e68a &nbsp;sub &nbsp; &nbsp; rsp, 0B8h\n2e691 &nbsp;mov &nbsp; &nbsp; rax, fs:28h\n2e69a &nbsp;mov &nbsp; &nbsp; [rsp+0E8h+var_38], rax\n2e6a2 &nbsp;test &nbsp; &nbsp;rsi, rsi\n2e6a5 &nbsp;jz &nbsp; &nbsp; &nbsp;loc_2EA89\n2e6ab &nbsp;mov &nbsp; &nbsp; r12, rdi\n2e6ae &nbsp;mov &nbsp; &nbsp; r14, [rdi]\n2e6b1 &nbsp;mov &nbsp; &nbsp; rax, [rdi+8]\n2e6b5 &nbsp;sub &nbsp; &nbsp; rax, r14\n2e6b8 &nbsp;cmp &nbsp; &nbsp; rax, 34h ; '4'\n2e6bc &nbsp;jb &nbsp; &nbsp; &nbsp;loc_2EA89\n2e6c2 &nbsp;cmp &nbsp; &nbsp; dword ptr [r14], 314D4A48h\n
... [truncated 10360 bytes] ...
&nbsp; &nbsp; &nbsp; &nbsp;"size": "0x10",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_OWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "var_38",
&nbsp; &nbsp; &nbsp; &nbsp; "offset": "0xb0",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x8",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "__return_address",
&nbsp; &nbsp; &nbsp; &nbsp; "offset": "0xe8",
&nbsp; &nbsp; &nbsp; &nbsp; "size": "0x8",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_UNKNOWN&nbsp;*"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp; "return_type": "__int64",
&nbsp; &nbsp; "arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "arg0",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "_QWORD&nbsp;*"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "name": "arg1",
&nbsp; &nbsp; &nbsp; &nbsp; "type": "__int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
&nbsp; "instruction_count": 250,
&nbsp; "total_instructions": null,
&nbsp; "cursor": {
&nbsp; &nbsp; "done": true
&nbsp; }
}

🤖 助手 – 第 77 轮 [14:19:14]

Now I have all the assembly details. Let me write the complete decryption script:

🐍 执行 Python 脚本

依赖: pycryptodome

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defu32(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

# SipHash-2-4
defsip...[truncated]

📋 Python 输出 [14:19:17]

&nbsp;复制代码&nbsp;隐藏代码
qword_5CFE0 = qword_5CFE8 =&nbsp;0x810a3647628f0212
sub_2E570 result (debug_key) =&nbsp;0xd2c13a26769ce7e0
v34 (key to sub_2E680) =&nbsp;0x77649f83d3394245
Salt:&nbsp;0f1e2d3c4b5a69788796a5b4c3d2e1f0
IV:&nbsp;112233445566778899aabbcc
Encrypted size:&nbsp;512
SipHash result&nbsp;in&nbsp;sub_2EB00 =&nbsp;0x0ea9de92c914b094
key_part0 =&nbsp;0xf456e2c7ee0af52c
key_part1 =&nbsp;0x18ff86584363a443
var_68 (derived key material): 2cf50aeec7e256f443a463435886ff18

🤖 助手 – 第 78 轮 [14:20:03]

Now let me implement the AES-CTR decryption loop from sub_2E680:

🐍 执行 Python 脚本

依赖: pycryptodome

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defu32(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

defxxhash64(data, se...[truncated]

📋 Python 输出 [14:20:05]

&nbsp;复制代码&nbsp;隐藏代码
foldedqword_5CFE8=0xe3853455
Chunk 0:key=32d64809c01fd6eec54ddff78e80aa78,nonce=112233440052f26b99aabbcc,first_dec=805e6a74557f1cdaaa356dc35ebd35af
Chunk 1:key=8610330511564bbe75b9fff53f5b6851,nonce=102233440052f26b99aabbcc,first_dec=4d9d53f018fbfb7d0e8b33a72b8fbad8

Decrypted512bytes
First 32 bytes:805e6a74557f1cdaaa356dc35ebd35aff827c44616a2b4f525b1e38e9e70d0ed
Last 32 bytes:9733382e37cc3934c373ba95401066edddc2132e3ea1be30ab63387055f97b8a
Bits set:2124/4096(51%)

🤖 助手 – 第 79 轮 [14:20:16]

The decrypted data looks random (51% bits set, no clear pattern). This suggests either the decryption is wrong or I need to check the verifyAndDecrypt path more carefully. Let me re-examine whether the XOR with 0xA5… is actually applied:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x24850

  • max_instructions:

    100

📋 mcp_ida-pro-headless_disasm 输出 [14:20:16]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24850",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"verifyAndDecrypt",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x24850",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"verifyAndDecrypt (.text @ 0x24850):\n24850 &nbsp;push &nbsp; &nbsp;rbp\n24851 &nbsp;push &nbsp; &nbsp;r15\n24853 &nbsp;push &nbsp; &nbsp;r14\n24855 &nbsp;push &nbsp; &nbsp;r13\n24857 &nbsp;push &nbsp; &nbsp;r12\n24859 &nbsp;push &nbsp; &nbsp;rbx\n2485a &nbsp;sub &nbsp; &nbsp; rsp, 58h\n2485e &nbsp;mov &nbsp; &nbsp; r14, rdi\n24861 &nbsp;mov &nbsp; &nbsp; rax, fs:28h\n2486a &nbsp;mov &nbsp; &nbsp; [rsp+88h+var_38], rax\n2486f &nbsp;mov &nbsp; &nbsp; rax, [rdi]\n24872 &nbsp;test &nbsp; &nbsp;rdx, rdx\n24875 &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24937\n2487b &nbsp;mov &nbsp; &nbsp; r15, rcx\n2487e &nbsp;test &nbsp; &nbsp;rcx, rcx\n24881 &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24937\n24887 &nbsp;mov &nbsp; &nbsp; r13, rdx\n2488a &nbsp;mov &nbsp; &nbsp; rdi, r14\n2488d &nbsp;mov &nbsp; &nbsp; r
... [truncated 4522 bytes] ...
0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x88",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"__int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64 *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;100,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"next":&nbsp;100
&nbsp; }
}

🤖 助手 – 第 80 轮 [14:20:23]

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x24850

  • offset:

    100

  • max_instructions:

    200

📋 mcp_ida-pro-headless_disasm 输出 [14:20:23]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24850",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"verifyAndDecrypt",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x24850",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"verifyAndDecrypt (.text @ 0x24850):\n249be &nbsp;jnz &nbsp; &nbsp; short loc_24A30\n249c0 &nbsp;shl &nbsp; &nbsp; rcx, 20h\n249c4 &nbsp;movsxd &nbsp;rdx, edx\n249c7 &nbsp;mov &nbsp; &nbsp; rsi, 1A8CBC5B802E097Ch\n249d1 &nbsp;xor &nbsp; &nbsp; rsi, rcx\n249d4 &nbsp;xor &nbsp; &nbsp; rsi, rdx\n249d7 &nbsp;mov &nbsp; &nbsp; rcx, 9E3779B97F4A7C15h\n249e1 &nbsp;add &nbsp; &nbsp; rcx, rsi\n249e4 &nbsp;mov &nbsp; &nbsp; rdx, rcx\n249e7 &nbsp;shr &nbsp; &nbsp; rdx, 1Eh\n249eb &nbsp;xor &nbsp; &nbsp; rdx, rcx\n249ee &nbsp;mov &nbsp; &nbsp; rcx, 0BF58476D1CE4E5B9h\n249f8 &nbsp;imul &nbsp; &nbsp;rcx, rdx\n249fc &nbsp;mov &nbsp; &nbsp; rdx, rcx\n249ff &nbsp;shr &nbsp; &nbsp; rdx, 1Bh\n24a03 &nbsp;xor &nbsp; &nbsp; rdx, rcx\n24a06
... [truncated 7164 bytes] ...
x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x88",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"__int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64 *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;184,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"done":&nbsp;true
&nbsp; }
}

👤 用户 [14:20:23]

[TodoList Reminder – Round 41] Stats: 3 pending, 1 done, 0 failed. You have 3 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 81 轮 [14:21:33]

Now I can see the exact flow at 0x24b8b-0x24bfa:

&nbsp;复制代码&nbsp;隐藏代码
24b8b:&nbsp;al&nbsp;= byte_5D140 (debugBypass flag)
24b92:&nbsp;testal,&nbsp;al
24b94:&nbsp;setnzcl
24b97:&nbsp;orcl, byte_5CFF8
24b9d:&nbsp;orcl,&nbsp;r13b
24ba0:&nbsp;testcl,&nbsp;1
24ba3:&nbsp;jz&nbsp;24bb4 (fail)
24ba5: ...
24ba9:&nbsp;testal,&nbsp;al&nbsp; (test&nbsp;debugBypass again)
24bab:&nbsp;jz&nbsp;24bda (if&nbsp;NOT&nbsp;debug, use qword_5CFF0)
24bad:&nbsp;call&nbsp;sub_2E570 (if debug, compute key)
24bb2:&nbsp;jmp&nbsp;24be1
24bda:&nbsp;movrax, qword_5CFF0 (use game-earned key)
24be1:&nbsp;movrsi,&nbsp;0xA5A5A5A5A5A5A5A5
24beb:&nbsp;xorrsi,&nbsp;rax
24bee:&nbsp;testr13b,&nbsp;r13b&nbsp; (r13b&nbsp;= password verification passed?)
24bf1:&nbsp;cmovzrsi,&nbsp;rax&nbsp; (if password&nbsp;NOT&nbsp;verified, DON'T XOR)
24bf5: lea rdi, [rsp+var_70]
24bfa: call sub_2E680

Key insight: When debugBypass=trueal=1, so it calls sub_2E570. Then rsi = result ^ 0xA5A5.... But r13b depends on password verification. If r13b=0 (password not verified), cmovz replaces rsi with rax (no XOR). If r13b=1, the XOR stays.

I need to figure out what r13b is. Looking back at 0x24a1e: r13b = 1 when the splitmix64 result is non-zero (which it almost certainly is). But at 0x24a30: r13d = 0 when byte_5D00C != 1.

The byte_5D00C is set to 1 when dword_5D008 >= 4. This is the “experience” counter. For debug bypass, we need to check if the password path succeeds.

Let me try both variants – with and without the XOR:

🐍 执行 Python 脚本

依赖: pycryptodome,Pillow

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES
from&nbsp;PIL&nbsp;import&nbsp;Image

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defu32(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))
...[truncated]

📋 Python 输出 [14:21:38]

&nbsp;复制代码&nbsp;隐藏代码
debug_key ^&nbsp;0xA5&nbsp;(r13b=1):&nbsp;bits=2124/4096&nbsp;(51%), row0=805e6a74557f1cda
&nbsp; Saved&nbsp;bitmap_debug_key_^_0xA5_r13b1.png
debug_key raw (r13b=0):&nbsp;bits=1959/4096&nbsp;(47%), row0=0000000000000000
&nbsp; Saved&nbsp;bitmap_debug_key_raw_r13b0.png
qfe8 ^&nbsp;0xA5&nbsp;(game path):&nbsp;bits=2090/4096&nbsp;(51%), row0=a70cb53fdecafaae
&nbsp; Saved&nbsp;bitmap_qfe8_^_0xA5_game_path.png
qfe8 raw:&nbsp;bits=2053/4096&nbsp;(50%), row0=2a9a67989b338d3f
&nbsp; Saved&nbsp;bitmap_qfe8_raw.png

🤖 助手 – 第 82 轮 [14:21:45]

The “debug_key raw (r13b=0)” variant has row0 = all zeros, which looks promising for a QR code! Let me check it:

🔨 工具调用: vision

  • image:

    D:\AI\AICTF\workdir\52pojie\chu8\bitmap_debug_key_raw_r13b0.png

  • prompt:

    Is this a QR code or any recognizable pattern? Describe what you see in detail.

📋 vision 输出 [14:22:01]

&nbsp;复制代码&nbsp;隐藏代码
This&nbsp;is&nbsp;**not&nbsp;a valid, scannable QR code nor a standard machine-readable pattern**&nbsp;like&nbsp;a barcode, Data Matrix, etc. Here's a detailed breakdown of what this is:

1. &nbsp;**Overall Type**: It&nbsp;is&nbsp;a dense, high-contrast monochrome black-and-white chaotic texture, specifically a distorted, overlapping&nbsp;"soup"of&nbsp;stylized, warped small characters/symbols.
2. &nbsp;**Detailed Elements**:
&nbsp; &nbsp; The frame&nbsp;is&nbsp;filled&nbsp;with&nbsp;tightly packed, deformed, overlapping small glyphs: they resemble squashed, curved, angular lowercase letters, modified punctuation,&nbsp;or&nbsp;abstract tiny shapes, all mashed together&nbsp;with&nbsp;no consistent spacing, alignment,&nbsp;ororder.
3. &nbsp;**Lack&nbsp;of&nbsp;Functional Pattern&nbsp;Structure**:
&nbsp; &nbsp; Unlike QR codes, which have a&nbsp;strict&nbsp;square grid, clear corner finder markers,&nbsp;and&nbsp;uniform square modules, this has zero consistent grid, no reference points&nbsp;for&nbsp;scanning,&nbsp;and&nbsp;no organized&nbsp;structure. It has no scannable&nbsp;or&nbsp;machine-readable&nbsp;function.
4. &nbsp;**Visual Effect**: It looks&nbsp;liketext&nbsp;run through an extreme warp/distortion filter, turning readable characters&nbsp;into&nbsp;an unreadable, dense, chaotic texture that fills the entire image space.

🤖 助手 – 第 83 轮 [14:23:00]

Not a QR code. Let me look at the image more carefully and also check if the bit ordering might be different:

🐍 执行 Python 脚本

依赖: Pillow

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct
from&nbsp;PIL&nbsp;import&nbsp;Image

# Read the decrypted data for the "debug_key raw" variant
# Let me regenerate it quickly
defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defu32(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFF...[truncated]

📋 Python 输出 [14:23:02]

&nbsp;复制代码&nbsp;隐藏代码
Imagesize: (64,&nbsp;64)
Imagemode:&nbsp;1

█ ██ █ &nbsp;█ &nbsp; &nbsp;█ ████ █ &nbsp; ██ &nbsp; ████ &nbsp; &nbsp;████████ █ &nbsp; &nbsp;█ ████ &nbsp;██ ██
&nbsp; █ &nbsp; ████████ █████ &nbsp; ██ █ &nbsp; &nbsp;███ &nbsp; █ &nbsp;█ &nbsp;█ █ ██████ █████ █ &nbsp;█
██████ █ &nbsp; &nbsp; █ ██ █ &nbsp; █ ██ &nbsp;█ ███ &nbsp; █ &nbsp; ██ &nbsp;██ &nbsp;██ &nbsp;█ &nbsp; █ ████
█ ███ &nbsp; ██ █ &nbsp; █ �
...&nbsp;[truncated 8112 bytes]&nbsp;...
&nbsp;████ ████ █████ ██ ███ &nbsp;██ █ ███ ██
&nbsp;█ █ &nbsp; ██ &nbsp; █ █ █ █ &nbsp;█ █ ██ &nbsp; &nbsp; &nbsp; &nbsp; █ &nbsp;██ ████ ███ &nbsp;█ &nbsp; ██ &nbsp;█ &nbsp;█
&nbsp; &nbsp;█ &nbsp;██ ██ █ &nbsp;███ █ █████ █ &nbsp;█ &nbsp; ███ &nbsp;█ &nbsp; █ █ &nbsp;█ &nbsp;██ &nbsp;█ &nbsp;██ ███
█ &nbsp; █ &nbsp;███ &nbsp; ███ ███ ██ ██ ███ &nbsp;████ &nbsp; &nbsp; ██ &nbsp; &nbsp;██ ███ █ &nbsp; &nbsp;███ █
█ ███ █ ██ &nbsp; █ &nbsp; ██ &nbsp;████ &nbsp;█ █ █ █ ███ ███ &nbsp; &nbsp;█ █ █ &nbsp;█ &nbsp;███ &nbsp; █

🤖 助手 – 第 84 轮 [14:23:09]

This doesn’t look like a QR code. Let me reconsider the approach. Maybe I need to look at the sub_2DAF0 function more carefully – it’s the SipHash used in sub_2E570, and I might have the wrong SipHash variant:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x2DAF0

  • max_instructions:

    200

📋 mcp_ida-pro-headless_disasm 输出 [14:23:09]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DAF0",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_2DAF0",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x2daf0",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_2DAF0 (.text @ 0x2daf0):\n2daf0 &nbsp;push &nbsp; &nbsp;rbx\n2daf1 &nbsp;mov &nbsp; &nbsp; r9, 736F6D6570736575h\n2dafb &nbsp;xor &nbsp; &nbsp; r9, rdx\n2dafe &nbsp;mov &nbsp; &nbsp; r8, 646F72616E646F6Dh\n2db08 &nbsp;xor &nbsp; &nbsp; r8, rcx\n2db0b &nbsp;mov &nbsp; &nbsp; rax, 6C7967656E657261h\n2db15 &nbsp;xor &nbsp; &nbsp; rax, rdx\n2db18 &nbsp;mov &nbsp; &nbsp; rdx, 7465646279746573h\n2db22 &nbsp;xor &nbsp; &nbsp; rdx, rcx\n2db25 &nbsp;mov &nbsp; &nbsp; r10, rsi\n2db28 &nbsp;and &nbsp; &nbsp; r10, 0FFFFFFFFFFFFFFF8h\n2db2c &nbsp;jz &nbsp; &nbsp; &nbsp;loc_2DBB6\n2db32 &nbsp;lea &nbsp; &nbsp; rcx, [rdi+r10]\n2db36 &nbsp;xor &nbsp; &nbsp; r11d, r11d\n2db39 &nbsp;nop &nbsp; &nbsp; dword ptr [rax+00000000h]\n2db40
... [truncated 5389 bytes] ...
4a &nbsp;retn",
&nbsp; &nbsp;&nbsp;"stack_frame": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"__int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int8 *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;168,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"done":&nbsp;true
&nbsp; }
}

🤖 助手 – 第 85 轮 [14:25:18]

🔨 工具调用: mcp_sequential-thinking_sequentialthinking

  • nextThoughtNeeded:

    true

  • thought:

    `Let me carefully trace the SipHash implementation in sub_2DAF0.

Arguments: rdi=data_ptr, rsi=length, rdx=k0, rcx=k1

Initialization: r9 = 0x736f6d6570736575 ^ rdx  (v0 = k0 ^ magic0) r8 = 0x646f72616…[truncated]`

  • thoughtNumber:

    1

  • totalThoughts:

    3

📋 mcp_sequential-thinking_sequentialthinking 输出 [14:25:18]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"thoughtNumber":1,
&nbsp;&nbsp;"totalThoughts":3,
&nbsp;&nbsp;"nextThoughtNeeded":true,
&nbsp;&nbsp;"branches":[],
&nbsp;&nbsp;"thoughtHistoryLength":8
}

👤 用户 [14:25:18]

[Progress Checkpoint – Round 46/200] Briefly assess your progress:

  1. What have you accomplished so far?
  2. Are you making progress toward the flag? Continue with your current approach if it shows promise.

🤖 助手 – 第 86 轮 [14:25:31]

💭 思考过程

Let me assess where I am:

  1. I’ve identified the APK’s native library decryption flow: SipHash-2-4 + XXHash64 + AES-CTR
  2. I’ve computed the key derivation chain but the decrypted output doesn’t look like a valid QR code/bitmap
  3. The “debug_key raw” variant has row0=all zeros which is promising but the rest is noise

The issue is likely in either:

  • My AES-CTR implementation (how the IV/nonce is constructed)
  • My understanding of sub_2DF40 (the actual decryption function)
  • The key derivation path I’m following

Let me look at sub_2DF40 more carefully – that’s the AES-CTR decryption function. Also sub_24CB0 which generates the per-chunk AES keys.

Progress assessment:

  1. Key derivation chain computed (SipHash + XXHash64), 4 key variants tried
  2. “debug_key raw” variant shows row0=all zeros (promising) but rest is noise
  3. Likely issue: AES-CTR implementation details in sub_2DF40 or sub_24CB0

Let me examine the actual AES-CTR function and the XXHash key generation function:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x2DF40

  • max_instructions:

    300

📋 mcp_ida-pro-headless_disasm 输出 [14:25:31]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2DF40",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_2DF40",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x2df40",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_2DF40 (.text @ 0x2df40):\n2df40 &nbsp;push &nbsp; &nbsp;rbp\n2df41 &nbsp;push &nbsp; &nbsp;r15\n2df43 &nbsp;push &nbsp; &nbsp;r14\n2df45 &nbsp;push &nbsp; &nbsp;r13\n2df47 &nbsp;push &nbsp; &nbsp;r12\n2df49 &nbsp;push &nbsp; &nbsp;rbx\n2df4a &nbsp;sub &nbsp; &nbsp; rsp, 108h\n2df51 &nbsp;mov &nbsp; &nbsp; [rsp+138h+var_118], rdi\n2df56 &nbsp;mov &nbsp; &nbsp; rax, fs:28h\n2df5f &nbsp;mov &nbsp; &nbsp; [rsp+138h+var_38], rax\n2df67 &nbsp;movdqu &nbsp;xmm0, xmmword ptr [rdx]\n2df6b &nbsp;movdqa &nbsp;[rsp+138h+var_E8], xmm0\n2df71 &nbsp;mov &nbsp; &nbsp; eax, 1\n2df76 &nbsp;mov &nbsp; &nbsp; edx, 0Fh\n2df7b &nbsp;mov &nbsp; &nbsp; r14d, 10h\n2df81 &nbsp;lea &nbsp; &nbsp; rbp, byte_17430\n2df88 &nbsp;lea &nbsp; &nbsp; rdi, byte_17530\
... [truncated 13870 bytes] ...
WORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x138",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"unsigned __int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"const __m128i *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_BYTE *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;300,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"next":&nbsp;300
&nbsp; }
}

🤖 助手 – 第 87 轮 [14:25:40]

This is a custom AES-CTR implementation, not using standard library. The key expansion happens at the top, then it does AES encryption of a counter and XORs with plaintext. The critical thing I notice is:

  1. The AES key is loaded from [rdx] (16 bytes) – this is the key pointer
  2. The counter/IV is loaded from [rcx] (12 bytes) – this is the IV pointer
  3. The counter is a simple incrementing 32-bit value in the first 4 bytes

Let me look at how the IV/counter is constructed. The first 12 bytes of the AES state are XORed with the IV, and the last 4 bytes are the counter. Let me re-examine the calling code in sub_2E680:

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x2E680

📋 mcp_ida-pro-headless_decompile 输出 [14:25:40]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x2E680",
&nbsp;&nbsp;"code":&nbsp;"__int64 __fastcall sub_2E680(_QWORD *a1, __int64 a2)\n{\n&nbsp; _QWORD *v2; // r12\n&nbsp; _DWORD *v3; // r14\n&nbsp; unsigned __int64 v4; // rax\n&nbsp; __int64 v5; // rdx\n&nbsp; __int64 v6; // rbp\n&nbsp; int v7; // ecx\n&nbsp; int v8; // edi\n&nbsp; unsigned int v9; // edi\n&nbsp; size_t v10; // rdi\n&nbsp; unsigned __int64 v11; // rbx\n&nbsp; unsigned __int64 v12; // r13\n&nbsp; void **v13; // r14\n&nbsp; __int64 v14; // rax\n&nbsp; __int64 v15; // r15\n&nbsp; __int64 v16; // rax\n&nbsp; unsigned __int64 v17; // rbp\n&nbsp; void **v18; // r13\n&nbsp; _QWORD *v19; // r14\n&nbsp; void *v20; // r12\n&nbsp; void *v21; // rax\n&nbsp; __int64 v22; // rax\n&nbsp; __int6
... [truncated 7703 bytes] ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ptr[1] = ptr[0]; /*0x2ea54*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; operator delete(ptr[0]); /*0x2ea59*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (unsigned int)v6; /*0x2ea5e*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if ( !src[0] || (src[1] = src[0], operator delete(src[0]), (v25 = ptr[0]) != 0) ) /*0x2ea7d*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ptr[1] = v25; /*0x2ea7f*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; operator delete(v25); /*0x2ea84*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; goto LABEL_27; /*0x2ea84*/\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; }\n&nbsp; }\n&nbsp; return (unsigned int)v6; /*0x2eaa0*/\n}"
}

🤖 助手 – 第 88 轮 [14:26:06]

Now I can see the structure clearly. The key issue is how sub_2DF40 is called. Let me look at the arguments:

  • arg0 = v39 + *v2

    = offset into output buffer

  • arg1 = v38

    = chunk size (min(256, remaining))

  • arg2 = ptr

    = AES key (16 bytes, two 8-byte values from sub_24CB0)

  • arg3 = src

    = IV/nonce (12 bytes)

The IV construction is complex. Let me look at sub_24CB0 which generates the AES key:

🔨 工具调用: mcp_ida-pro-headless_decompile

  • addr:

    0x24CB0

📋 mcp_ida-pro-headless_decompile 输出 [14:26:06]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24CB0",
&nbsp;&nbsp;"code":&nbsp;"unsigned __int64 __fastcall sub_24CB0(unsigned __int8 *a1, unsigned __int64 a2, unsigned __int64 a3)\n{\n&nbsp; unsigned __int64 v3; // r9\n&nbsp; unsigned __int64 v4; // rbp\n&nbsp; unsigned __int64 v5; // r13\n&nbsp; unsigned __int64 v6; // r12\n&nbsp; __int64 v7; // r15\n&nbsp; __int64 v8; // r14\n&nbsp; __int64 v9; // rbx\n&nbsp; __int64 v10; // r11\n&nbsp; unsigned __int64 v11; // r11\n&nbsp; unsigned __int64 v12; // r11\n&nbsp; unsigned __int64 v13; // rsi\n&nbsp; unsigned __int8 *v14; // r10\n&nbsp; bool v15; // cc\n&nbsp; unsigned __int64 v16; // rdi\n&nbsp; unsigned __int64 v17; // r10\n&nbsp; __int64 v18; // r11\n&nbsp; unsigned __in
... [truncated 4974 bytes] ...
t8 *)v16), 11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 1)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 2)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;11))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^ (0x27D4EB2F165667C5LL * *(unsigned __int8 *)(v16 + 3)),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 11);\n&nbsp; &nbsp; &nbsp; v16 += 4LL; /*0x24f7d*/\n&nbsp; &nbsp; }\n&nbsp; &nbsp; while ( v16 < v3 ); /*0x24f84*/\n&nbsp; }\nLABEL_18:\n&nbsp; v20 = 0x165667B19E3779F9LL /*0x24f86*/\n&nbsp; &nbsp; &nbsp; * ((0xC2B2AE3D27D4EB4FLL * (v13 ^ (v13 >> 33))) ^ ((0xC2B2AE3D27D4EB4FLL * (v13 ^ (v13 >> 33))) >> 29));\n&nbsp; return v20 ^ HIDWORD(v20); /*0x24fac*/\n}"
}

👤 用户 [14:26:06]

[TodoList Reminder – Round 49] Stats: 3 pending, 1 done, 0 failed. You have 3 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 89 轮 [14:26:20]

This is XXHash64 but the return value is v20 ^ HIDWORD(v20) – it returns a 32-bit value folded from 64-bit! That’s different from standard XXHash64. Also the seed is passed as a3 (third argument), not second. Let me also check: the function signature is sub_24CB0(data, size, seed) and it returns a folded 32-bit result.

This means my AES key generation is wrong – each call to sub_24CB0 returns a 32-bit value, not 64-bit. The AES key is built from two 32-bit values stored as ptr[0] and ptr[1], but those are 8-byte pointers… Wait, looking at the decompiled code:

&nbsp;复制代码&nbsp;隐藏代码
v20 = (void&nbsp;*)sub_24CB0(src,&nbsp;32,&nbsp;4951); &nbsp;// ptr[0] = 8 bytes but only low 32 meaningful?
v21 = (void&nbsp;*)sub_24CB0(src,&nbsp;32,&nbsp;9320); &nbsp;// ptr[1]
ptr[0] = v20;
ptr[1] = v21;

Actually ptr is void *[2] – two 8-byte slots. But sub_24CB0 returns v20 ^ HIDWORD(v20) which is a 32-bit value. So ptr contains two 8-byte values where only the low 32 bits matter. But then ptr is passed to sub_2DF40 as the AES key (16 bytes). So the AES key is actually [low32_of_hash1, 0, low32_of_hash2, 0]? That seems wrong.

Let me re-read sub_24CB0’s return more carefully:

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x24F86

  • max_instructions:

    20

📋 mcp_ida-pro-headless_disasm 输出 [14:26:20]

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x24F86",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"sub_24CB0",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x24f86",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"sub_24CB0 (.text @ 0x24f86):\n24f86 &nbsp;mov &nbsp; &nbsp; rcx, rsi\n24f89 &nbsp;shr &nbsp; &nbsp; rcx, 21h\n24f8d &nbsp;xor &nbsp; &nbsp; rcx, rsi\n24f90 &nbsp;imul &nbsp; &nbsp;rcx, rax\n24f94 &nbsp;mov &nbsp; &nbsp; rsi, rcx\n24f97 &nbsp;shr &nbsp; &nbsp; rsi, 1Dh\n24f9b &nbsp;xor &nbsp; &nbsp; rsi, rcx\n24f9e &nbsp;imul &nbsp; &nbsp;rsi, rdx\n24fa2 &nbsp;mov &nbsp; &nbsp; rax, rsi\n24fa5 &nbsp;shr &nbsp; &nbsp; rax, 20h\n24fa9 &nbsp;xor &nbsp; &nbsp; rax, rsi\n24fac &nbsp;pop &nbsp; &nbsp; rbx\n24fad &nbsp;pop &nbsp; &nbsp; r12\n24faf &nbsp;pop &nbsp; &nbsp; r13\n24fb1 &nbsp;pop &nbsp; &nbsp; r14\n24fb3 &nbsp;pop &nbsp; &nbsp; r15\n24fb5 &nbsp;pop &nbsp; &nbsp; rbp\n24fb6 &nbsp;retn",
&nbsp; &nbsp;&nbsp;"stack_frame": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x30",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"unsigned __int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int8 *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"unsigned __int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;18,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"done":&nbsp;true
&nbsp; }
}

🤖 助手 – 第 90 轮 [14:26:50]

The final steps: rsi ^ (rsi >> 32) → returns in rax. This is a 64-bit value where the top 32 bits are XORed into the bottom 32 bits. But rax is still 64 bits – the high 32 bits will be rsi >> 32 and the low 32 bits will be rsi ^ (rsi >> 32). Actually no:

&nbsp;复制代码&nbsp;隐藏代码
rax&nbsp;=&nbsp;rsi
rax&nbsp;>>=&nbsp;32&nbsp; (rax&nbsp;= high32 of&nbsp;rsi,&nbsp;zero-extended)
rax&nbsp;^=&nbsp;rsi&nbsp; (rax&nbsp;=&nbsp;rsi&nbsp;^ (rsi&nbsp;>>&nbsp;32))

So rax = full 64-bit value rsi ^ (rsi >> 32). The high 32 bits are NOT zero. Let me verify: if rsi = 0xAABBCCDDEEFF0011, then rsi >> 32 = 0x00000000AABBCCDD, and rsi ^ (rsi >> 32) = 0xAABBCCDD44443CCC. So it’s still 64 bits.

Wait, the decompiler said return v20 ^ HIDWORD(v20) which would be 32-bit. But looking at the assembly, it’s xor rax, rsi which is 64-bit XOR. The decompiler is misleading. The function returns a full 64-bit value.

So my original approach was correct for the hash function. The issue must be elsewhere. Let me use the xxhash library to verify my implementation:

🐍 执行 Python 脚本

依赖: xxhash

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

defxxhash64(data, seed):
&nbsp; &nbsp; PRIME1, PRIME2, PRIME3, PRIME4, PRIME5 =&nbsp;0x9E3779B185EB...[truncated]

📋 Python 输出 [14:26:53]

&nbsp;复制代码&nbsp;隐藏代码
My XXHash64: &nbsp;0x4a688cc8e32bcc0b
Lib&nbsp;XXHash64:&nbsp;0x4a688cc8e32bcc0b
Match:True

32-byte&nbsp;test:
My:&nbsp;&nbsp;0xcbf59c5116ff32b4
Lib:0xcbf59c5116ff32b4
Match:True

Beatmap hash:
My:&nbsp;&nbsp;0x810a3647628f0212
Lib:0x810a3647628f0212
Match:True

🤖 助手 – 第 91 轮 [14:27:54]

XXHash64 is correct. Now let me verify the sub_24CB0 function – it’s XXHash64 but with a fold at the end:

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct, xxhash

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF

defsub_24CB0(data, seed):
&nbsp; &nbsp;&nbsp;"""XXHash64 with final fold: result ^ (result >> 32)"""
&nbsp; &nbsp; h = xxhash.xxh64(data, seed=seed).intdigest()
&nbsp; &nbsp;&nbsp;return&nbsp;u64(h ^ (h >>&nbsp;32))

# Now let me also check: in sub_2E680, the 'src' buffer passed to sub_24CB0 is 32 bytes:
# [0:16] = var_68 (key material from sub_2EB00)
# [16:20] = r15d (chunk counter as u32)
# [20:24] = r13d (low 32 of qword_5CFE8)
# [24:32] = var_E0 ^ r13 (where var_E0 = chunk_idx * 0x100000000)

# Wait, looking at the decompiled code more carefully:
# v44.m128i_i64[0] = __PAIR64__(v12, v15) &nbsp;-> this is (v12 << 32) | v15
# v44.m128i_i64[1] = v12 ^ v31

# v12 = qword_5CFE8 (r13 in asm)
# v15 = chunk counter (r15 in asm, starts at 0)
# v31 = var_E0 (starts at 0, increments by 0x100000000)

# So the 32-byte src buffer is:
# [0:16] = var_68 (from sub_2EB00)
# [16:24] = __PAIR64__(qword_5CFE8, chunk_counter) = (qfe8 << 32) | counter
# [24:32] = qfe8 ^ var_E0

# Wait, __PAIR64__(v12, v15) means high=v12, low=v15
# So bytes 16-23 = v15 (low 32) | v12 (high 32) as a 64-bit LE value
# That means: struct.pack('<Q', (v12 << 32) | (v15 & 0xFFFFFFFF))
# Which is: struct.pack('<II', v15, v12) &nbsp;(little-endian: low word first)

# And bytes 24-31 = v12 ^ v31 as 64-bit LE

# Let me verify: in the asm at 0x2e7ef:
# mov dword ptr [rsp+var_48], r15d &nbsp; &nbsp; -> offset 0 of var_48 = r15d (low 32)
# mov dword ptr [rsp+var_48+4], r13d &nbsp; -> offset 4 of var_48 = r13d (high 32)
# So var_48[0:8] = (r13 << 32) | r15 = __PAIR64__(r13, r15)

# And at 0x2e807:
# mov rax, var_E0
# xor rax, r13
# mov qword ptr [rsp+var_48+8], rax &nbsp; &nbsp;-> var_48[8:16] = var_E0 ^ r13

# So the full 32-byte buffer for sub_24CB0 is:
# src[0:16] = var_68 (key from sub_2EB00)
# src[16:20] = r15d (chunk counter, 32-bit)
# src[20:24] = r13d (low 32 of qfe8)
# src[24:32] = var_E0 ^ r13 (64-bit)

# This matches what I had before. Good.

# Now let me check the IV construction more carefully.
# From the decompiled code, the IV (src in the call to sub_2DF40) is built as:
# 1. src[0:8] = *v13 (first 8 bytes of IV from file, at offset 0x28 = 40)
# 2. src[8:12] = *(v13 + 8) (next 4 bytes of IV from file)
# 3. Then XOR first 8 bytes with counter info

# The XOR mask is built from xmm0:
# byte 0: r15 & 0xFF
# byte 1: (r15 >> 8) & 0xFF
# byte 2: (r15 >> 16) & 0xFF
# byte 3: (r15 >> 24) & 0xFF
# byte 4: folded & 0xFF
# byte 5: (folded >> 8) & 0xFF
# byte 6: (folded >> 16) & 0xFF
# byte 7: (folded >> 24) & 0xFF

# Where folded = qfe8 ^ (qfe8 >> 32)

# Then: src[0:8] ^= xmm0[0:8]

# So the 12-byte IV passed to sub_2DF40 is:
# [0:8] = file_iv[0:8] ^ pack('<II', chunk_counter, folded_qfe8)
# [8:12] = file_iv[8:12] (unchanged)

# This is what I had. Let me now check the AES-CTR counter construction in sub_2DF40.

# Looking at sub_2DF40:
# At 0x2e023-0x2e0d2: The first 12 bytes of the expanded key state are XORed with the IV
# Bytes 0-11 of var_E8 are XORed with rcx[0:12]
# Bytes 12-15 of var_E8 are NOT XORed (they stay as the key schedule)

# Wait no. Let me re-read:
# var_E8 is the AES key schedule (expanded from the 16-byte key in [rdx])
# Then at 0x2e023: the FIRST 12 bytes of var_E8 are XORed with rcx[0:12]
# And bytes 12-15 are stored as-is (var_131 through var_134)

# Then in the main loop (0x2e113 onwards):
# A counter 'ecx' starts at 0 and increments
# The counter bytes are XORed with bytes 12-15 of the modified state
# Then AES encryption is performed on this 16-byte block
# The result is XORed with the plaintext

# So this is AES-CTR where:
# - The AES key is derived from two xxhash64 values
# - The initial counter block is: [IV_xored(12 bytes)] + [counter(4 bytes)]
# - The counter increments for each 16-byte block

# But wait - looking more carefully at the code:
# var_131 = var_E8[12] (byte 12 of expanded key, NOT XORed with IV)
# var_132 = var_E8[13]
# var_133 = var_E8[14]
# var_134 = var_E8[15]

# Then in the loop:
# r9b = var_131 ^ cl (counter byte 0)
# dl = var_132 ^ (counter >> 8)
# bl = var_133 ^ (counter >> 16)
# r12b = var_134 ^ (counter >> 24)

# So the counter is XORed with the LAST 4 bytes of the first round key.
# This is NOT standard AES-CTR!

# In standard AES-CTR, you'd have a 16-byte counter block that gets encrypted.
# Here, the "counter block" is:
# [first_round_key[0:12] ^ IV[0:12]] + [first_round_key[12:16] ^ counter_bytes]

# And this gets fed through the AES rounds (starting from round 1, not round 0).

# Actually wait - let me re-read. The key expansion at the top of sub_2DF40:
# var_E8 is loaded with the key (16 bytes from [rdx])
# Then key expansion happens (the loop at 0x2df8f-0x2e018)
# This expands to 11 round keys (176 bytes = 0xB0)

# Then the first round key (var_E8[0:16]) is XORed with the IV:
# var_125 = var_E8[0] ^ IV[0]
# var_129 = var_E8[1] ^ IV[1] &nbsp;(wait, the naming is confusing)

# Actually the var names are at negative offsets. Let me map them:
# var_125 is at offset 0x13, var_126 at 0x12, etc.
# var_E8 is at offset 0x50

# The XOR at 0x2e02d: al = var_E8[0] ^ rcx[0], stored to var_125
# This creates a modified first block that combines the round key with the IV.

# Then in the AES encryption loop, this modified block goes through SubBytes + MixColumns
# for 9 rounds, then a final SubBytes, then XOR with the last round key.

# So the encryption is: AES_encrypt(round_key_0 ^ IV_extended)
# where IV_extended = [IV(12 bytes) + counter(4 bytes)]

# This IS standard AES-CTR! The first step of AES is AddRoundKey (XOR with round key 0),
# and here they pre-compute that XOR with the IV.

# So the full picture:
# counter_block = IV(12 bytes) || counter(4 bytes, big-endian? little-endian?)
# keystream = AES_encrypt(key, counter_block)
# plaintext = ciphertext ^ keystream

# The counter format: looking at the XOR at 0x2e136:
# r9b = var_131 ^ cl &nbsp;(cl = counter & 0xFF)
# This means byte 12 of the block = round_key[12] ^ (counter & 0xFF)
# Which means the counter is in LITTLE-ENDIAN format in bytes 12-15

# But in standard AES-CTR (like NIST), the counter is big-endian.
# PyCryptodome's AES.MODE_CTR with nonce uses big-endian counter by default.

# So I need to use little-endian counter!

print("Key insight: AES-CTR with 12-byte nonce and 4-byte LITTLE-ENDIAN counter")
print("The nonce is: file_IV[0:8] ^ counter_info || file_IV[8:12]")
print("Counter starts at 0 and increments for each 16-byte block within a 256-byte chunk")

📋 Python 输出 [14:27:54]

&nbsp;复制代码&nbsp;隐藏代码
Key&nbsp;insight:&nbsp;AES-CTR with&nbsp;12-byte&nbsp;nonce&nbsp;and&nbsp;4-byte&nbsp;LITTLE-ENDIAN counter
The nonce is: file_IV[0:8] ^ counter_info&nbsp;||&nbsp;file_IV[8:12]
Counter starts&nbsp;at0and&nbsp;increments for each&nbsp;16-byte&nbsp;block&nbsp;within a&nbsp;256-byte&nbsp;chunk

🤖 助手 – 第 92 轮 [14:28:45]

The counter is little-endian! PyCryptodome’s default CTR mode uses big-endian. Let me fix this and also verify the fold in sub_24CB0:

🐍 执行 Python 脚本

依赖: pycryptodome,xxhash,Pillow

&nbsp;复制代码&nbsp;隐藏代码

import&nbsp;struct, xxhash
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES
from&nbsp;Crypto.Util&nbsp;import&nbsp;Counter
from&nbsp;PIL&nbsp;import&nbsp;Image

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defu32(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

defsiphash_2_4(data, k0, k1):
&nbsp; &nbsp; v0 = u64(k0 ^&nbsp;0x736f6d6570736575)
&nbsp; &nbsp; v1 = u64(k1 ^&nbsp;0x646f72616e646f6d)
&nbsp; &nbsp; v2 = u64(k0 ^&nbsp;0x6c7967656e657261)
&nbsp; &nbsp; v3 = u64(k1 ^&nbsp;0x7465646279746573)
&nbsp; &nbsp; length =&nbsp;len(data); blocks = length //&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(blocks):
&nbsp; &nbsp; &nbsp; &nbsp; m = struct.unpack_from('<Q', data, i*8)[0]
&nbsp; &nbsp; &nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; &nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; last =&nbsp;bytearray(8)
&nbsp; &nbsp; rem = length %&nbsp;8; start = blocks *&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(rem): last[i] = data[start+i]
&nbsp; &nbsp; last[7] = length &&nbsp;0xFF
&nbsp; &nbsp; m = struct.unpack('<Q',&nbsp;bytes(last))[0]
&nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; v2 ^=&nbsp;0xFF
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(4):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp;&nbsp;return&nbsp;u64(v0^v1^v2^v3)

defsplitmix64(state):
&nbsp; &nbsp; state = u64(state +&nbsp;0x9E3779B97F4A7C15)
&nbsp; &nbsp; z = state
&nbsp; &nbsp; z = u64((z ^ (z >>&nbsp;30)) *&nbsp;0xBF58476D1CE4E5B9)
&nbsp; &nbsp; z = u64((z ^ (z >>&nbsp;27)) *&nbsp;0x94D049BB133111EB)
&nbsp; &nbsp;&nbsp;return&nbsp;state, z ^ (z >>&nbsp;31)

defsub_24CB0(data, seed):
&nbsp; &nbsp; h = xxhash.xxh64(data, seed=seed).intdigest()
&nbsp; &nbsp;&nbsp;return&nbsp;u64(h ^ (h >>&nbsp;32))

defderive_key_sub_2EB00(key_val, qfe8, salt):
&nbsp; &nbsp; eb_k0 = u64(qfe8 ^&nbsp;0x1A8CBC5B802E097C)
&nbsp; &nbsp; eb_k1 = u64(qfe8 ^&nbsp;0x802E097C1A8CBC5B)
&nbsp; &nbsp; eb_buf = struct.pack('<QQ', key_val, qfe8)
&nbsp; &nbsp; sip = siphash_2_4(eb_buf, eb_k0, eb_k1)
&nbsp; &nbsp; salt_q0, salt_q1 = struct.unpack_from('<QQ', salt,&nbsp;0)
&nbsp; &nbsp; PRIME1,PRIME2,PRIME4 =&nbsp;0x9E3779B185EBCA87,&nbsp;0xC2B2AE3D27D4EB4F,&nbsp;0x85EBCA77C2B2AE63
&nbsp; &nbsp; PRIME3_xxh, r10 =&nbsp;0x165667B19E3779F9,&nbsp;0x27D4EB2F165726CC
&nbsp; &nbsp; s1 = u64(salt_q1*PRIME2); s1=rol64(s1,31); s1=u64(s1*PRIME1)
&nbsp; &nbsp; s0 = u64(salt_q0*PRIME2); s0=rol64(s0,31); s0=u64(s0*PRIME1)
&nbsp; &nbsp; sa = u64(sip*PRIME2); sa=rol64(sa,31); sa=u64(sa*PRIME1)
&nbsp; &nbsp; rax2 = sa ^ r10
&nbsp; &nbsp; rax2=rol64(rax2,27); rax2=u64(rax2*PRIME1); rax2=u64(rax2+PRIME4)
&nbsp; &nbsp; rax2^=s0; rax2=rol64(rax2,27); rax2=u64(rax2*PRIME1); rax2=u64(rax2+PRIME4)
&nbsp; &nbsp; rax2^=s1; rax2=rol64(rax2,27); rax2=u64(rax2*PRIME1); rax2=u64(rax2+PRIME4)
&nbsp; &nbsp; rax2^=rax2>>33; rax2=u64(rax2*PRIME2); rax2^=rax2>>29; rax2=u64(rax2*PRIME3_xxh); rax2^=rax2>>32
&nbsp; &nbsp; rdx = u64(r10+0x1EF) ^ sa
&nbsp; &nbsp; rdx=rol64(rdx,27); rdx=u64(rdx*PRIME1); rdx=u64(rdx+PRIME4)
&nbsp; &nbsp; rdx^=s0; rdx=rol64(rdx,27); rdx=u64(rdx*PRIME1); rdx=u64(rdx+PRIME4)
&nbsp; &nbsp; rdx^=s1; rdx=rol64(rdx,27); rdx=u64(rdx*PRIME1); rdx=u64(rdx+PRIME4)
&nbsp; &nbsp; rdx^=rdx>>33; rdx=u64(rdx*PRIME2); rdx^=rdx>>29; rdx=u64(rdx*PRIME3_xxh); rdx^=rdx>>32
&nbsp; &nbsp;&nbsp;return&nbsp;rax2, rdx

defaes_ctr_decrypt_le(key, nonce_12, data):
&nbsp; &nbsp;&nbsp;"""AES-128-CTR with 12-byte nonce and 4-byte little-endian counter"""
&nbsp; &nbsp; result =&nbsp;bytearray()
&nbsp; &nbsp; counter =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(0,&nbsp;len(data),&nbsp;16):
&nbsp; &nbsp; &nbsp; &nbsp; block = nonce_12 + struct.pack('<I', counter)
&nbsp; &nbsp; &nbsp; &nbsp; cipher = AES.new(key, AES.MODE_ECB)
&nbsp; &nbsp; &nbsp; &nbsp; keystream = cipher.encrypt(block)
&nbsp; &nbsp; &nbsp; &nbsp; chunk = data[i:i+16]
&nbsp; &nbsp; &nbsp; &nbsp; result.extend(bytes(a ^ b&nbsp;for&nbsp;a, b&nbsp;inzip(chunk, keystream[:len(chunk)])))
&nbsp; &nbsp; &nbsp; &nbsp; counter +=&nbsp;1
&nbsp; &nbsp;&nbsp;returnbytes(result)

# Setup
beatmap = struct.pack('<4I',&nbsp;0,&nbsp;250,&nbsp;500,&nbsp;750)
qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest()

# sub_2E570: debug key generation
state_r14 = u64(qfe8 ^&nbsp;0x1A8CBC5B802E097C)
_, v2 = splitmix64(state_r14)
r15_key = u64(qfe8 ^&nbsp;0x802E097C1A8CBC5B)
rcx = v2
for&nbsp;i&nbsp;inrange(12):
&nbsp; &nbsp; buf = struct.pack('<QiiII', rcx, i %&nbsp;4,&nbsp;0, i,&nbsp;2)
&nbsp; &nbsp; rcx = siphash_2_4(buf, state_r14, r15_key)
debug_key = rcx

withopen(r"D:\AI\AICTF\workdir\52pojie\chu8\apk_unzipped\assets\hjm_pack.bin",&nbsp;"rb")&nbsp;as&nbsp;f:
&nbsp; &nbsp; hjm_data = f.read()

salt = hjm_data[24:40]
iv_bytes = hjm_data[40:52]
encrypted = hjm_data[52:]

# Try both key variants
for&nbsp;name, key_val&nbsp;in&nbsp;[("debug_key_raw", debug_key), ("debug_key_xor", u64(debug_key ^&nbsp;0xA5A5A5A5A5A5A5A5))]:
&nbsp; &nbsp; kp0, kp1 = derive_key_sub_2EB00(key_val, qfe8, salt)
&nbsp; &nbsp; var_68 = struct.pack('<QQ', kp0, kp1)

&nbsp; &nbsp; folded = u32(qfe8 ^ (qfe8 >>&nbsp;32))
&nbsp; &nbsp; total =&nbsp;len(encrypted)
&nbsp; &nbsp; num_chunks = (total +&nbsp;255) //&nbsp;256
&nbsp; &nbsp; decrypted =&nbsp;bytearray(total)
&nbsp; &nbsp; var_E0 =&nbsp;0

&nbsp; &nbsp;&nbsp;for&nbsp;ci&nbsp;inrange(num_chunks):
&nbsp; &nbsp; &nbsp; &nbsp; cs_ = ci *&nbsp;256
&nbsp; &nbsp; &nbsp; &nbsp; csz =&nbsp;min(256, total - cs_)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Build 32-byte buffer for sub_24CB0
&nbsp; &nbsp; &nbsp; &nbsp; src = var_68 &nbsp;# 16 bytes
&nbsp; &nbsp; &nbsp; &nbsp; src += struct.pack('<II', ci, u32(qfe8)) &nbsp;# r15d, r13d
&nbsp; &nbsp; &nbsp; &nbsp; src += struct.pack('<Q', u64(var_E0 ^ qfe8)) &nbsp;# var_E0 ^ r13

&nbsp; &nbsp; &nbsp; &nbsp; h1 = sub_24CB0(src,&nbsp;0x1357)
&nbsp; &nbsp; &nbsp; &nbsp; h2 = sub_24CB0(src,&nbsp;0x2468)
&nbsp; &nbsp; &nbsp; &nbsp; aes_key = struct.pack('<QQ', h1, h2)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Build 12-byte nonce
&nbsp; &nbsp; &nbsp; &nbsp; counter_bytes = struct.pack('<II', ci, folded)
&nbsp; &nbsp; &nbsp; &nbsp; iv_mod =&nbsp;bytearray(iv_bytes[:8])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;inrange(8): iv_mod[j] ^= counter_bytes[j]
&nbsp; &nbsp; &nbsp; &nbsp; nonce =&nbsp;bytes(iv_mod) + iv_bytes[8:12]

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Decrypt with LE counter
&nbsp; &nbsp; &nbsp; &nbsp; chunk_dec = aes_ctr_decrypt_le(aes_key, nonce, encrypted[cs_:cs_+csz])
&nbsp; &nbsp; &nbsp; &nbsp; decrypted[cs_:cs_+csz] = chunk_dec
&nbsp; &nbsp; &nbsp; &nbsp; var_E0 = u64(var_E0 +&nbsp;0x100000000)

&nbsp; &nbsp;&nbsp;# Render as image
&nbsp; &nbsp; img = Image.new('1', (64,&nbsp;64))
&nbsp; &nbsp;&nbsp;for&nbsp;y&nbsp;inrange(64):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;x&nbsp;inrange(64):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte_idx = y *&nbsp;8&nbsp;+ x //&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bit_idx =&nbsp;7&nbsp;- (x %&nbsp;8)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pixel = (decrypted[byte_idx] >> bit_idx) &&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; img.putpixel((x, y), pixel)
&nbsp; &nbsp; img.save(rf"D:\AI\AICTF\workdir\52pojie\chu8\bitmap_le_{name}.png")

&nbsp; &nbsp; bits =&nbsp;sum(bin(b).count('1')&nbsp;for&nbsp;b&nbsp;in&nbsp;decrypted)
&nbsp; &nbsp;&nbsp;print(f"{name}: bits={bits}/{total*8}&nbsp;({bits*100//(total*8)}%), first16={decrypted[:16].hex()}")

&nbsp; &nbsp;&nbsp;# Also try with standard big-endian CTR
&nbsp; &nbsp; decrypted2 =&nbsp;bytearray(total)
&nbsp; &nbsp; var_E0 =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;ci&nbsp;inrange(num_chunks):
&nbsp; &nbsp; &nbsp; &nbsp; cs_ = ci *&nbsp;256
&nbsp; &nbsp; &nbsp; &nbsp; csz =&nbsp;min(256, total - cs_)
&nbsp; &nbsp; &nbsp; &nbsp; src = var_68 + struct.pack('<II', ci, u32(qfe8)) + struct.pack('<Q', u64(var_E0 ^ qfe8))
&nbsp; &nbsp; &nbsp; &nbsp; h1 = sub_24CB0(src,&nbsp;0x1357)
&nbsp; &nbsp; &nbsp; &nbsp; h2 = sub_24CB0(src,&nbsp;0x2468)
&nbsp; &nbsp; &nbsp; &nbsp; aes_key = struct.pack('<QQ', h1, h2)
&nbsp; &nbsp; &nbsp; &nbsp; counter_bytes = struct.pack('<II', ci, folded)
&nbsp; &nbsp; &nbsp; &nbsp; iv_mod =&nbsp;bytearray(iv_bytes[:8])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;inrange(8): iv_mod[j] ^= counter_bytes[j]
&nbsp; &nbsp; &nbsp; &nbsp; nonce =&nbsp;bytes(iv_mod) + iv_bytes[8:12]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Use pycryptodome CTR (big-endian counter)
&nbsp; &nbsp; &nbsp; &nbsp; ctr = Counter.new(32, prefix=nonce, initial_value=0, little_endian=False)
&nbsp; &nbsp; &nbsp; &nbsp; cipher = AES.new(aes_key, AES.MODE_CTR, counter=ctr)
&nbsp; &nbsp; &nbsp; &nbsp; decrypted2[cs_:cs_+csz] = cipher.decrypt(encrypted[cs_:cs_+csz])
&nbsp; &nbsp; &nbsp; &nbsp; var_E0 = u64(var_E0 +&nbsp;0x100000000)

&nbsp; &nbsp; bits2 =&nbsp;sum(bin(b).count('1')&nbsp;for&nbsp;b&nbsp;in&nbsp;decrypted2)
&nbsp; &nbsp;&nbsp;print(f" &nbsp;BE-CTR: bits={bits2}/{total*8}&nbsp;({bits2*100//(total*8)}%), first16={decrypted2[:16].hex()}")

📋 Python 输出 [14:28:52]

&nbsp;复制代码&nbsp;隐藏代码
debug_key_raw:bits=1991/4096&nbsp;(48%), first16=5741b9c6e56997f1873b4a0fe840f085
&nbsp;&nbsp;BE-CTR:&nbsp;bits=1996/4096&nbsp;(48%), first16=5741b9c6e56997f1873b4a0fe840f085
debug_key_xor:bits=2078/4096&nbsp;(50%), first16=69437da3956ce847aa29e45c54dc24ca
&nbsp;&nbsp;BE-CTR:&nbsp;bits=2004/4096&nbsp;(48%), first16=69437da3956ce847aa29e45c54dc24ca

🤖 助手 – 第 93 轮 [14:29:31]

Hmm, the LE and BE CTR give the same first 16 bytes – that’s because the first block has counter=0 which is the same in both endiannesses. But neither result looks meaningful. The issue might be in the sub_24CB0 fold. Let me check: the AES key is 16 bytes built from two 8-byte values from sub_24CB0. But sub_24CB0 returns h ^ (h >> 32) which is 64 bits. So the key IS 16 bytes.

Wait – let me re-examine the key more carefully. In the decompiled code:

&nbsp;复制代码&nbsp;隐藏代码
v20 = (void&nbsp;*)sub_24CB0(src,&nbsp;32,&nbsp;4951);
v21 = (void&nbsp;*)sub_24CB0(src,&nbsp;32,&nbsp;9320);
ptr[0] = v20;
ptr[1] = v21;

ptr is void *[2] at offset 0x60. Then ptr is passed to sub_2DF40 as rdx (the key pointer). So the AES key is [ptr[0](8 bytes), ptr[1](8 bytes)] = 16 bytes. This is correct.

But wait – the src buffer is MODIFIED between the two calls! Looking at the asm:

&nbsp;复制代码&nbsp;隐藏代码
2e819:&nbsp;leardi, [rsp+src] &nbsp;; same buffer
2e821:&nbsp;call&nbsp;sub_24CB0 &nbsp; &nbsp; &nbsp;&nbsp;; first call
2e832:&nbsp;leardi, [rsp+src] &nbsp;; same buffer again
2e844:&nbsp;call&nbsp;sub_24CB0 &nbsp; &nbsp; &nbsp;&nbsp;; second call

The buffer is the same. But does sub_24CB0 modify it? Looking at the function – it only reads from a1, never writes. So both calls use the same 32-byte buffer. Good.

The problem must be in the key derivation or the nonce. Let me try a completely different approach – use the native library directly via emulation:

🐍 执行 Python 脚本

&nbsp;复制代码&nbsp;隐藏代码

# Let me verify my SipHash against a known test vector
import&nbsp;struct

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

defsiphash_2_4(data, k0, k1):
&nbsp; &nbsp; v0 = u64(k0 ^&nbsp;0x736f6d6570736575)
&nbsp; &nbsp; v1 = u64(k1 ^&nbsp;0x646f72616e646f6d)
&nbsp; &nbsp; v2 = u64(k0 ^&nbsp;0x6c7967656e657261)
&nbsp; &nbsp; v3 = u64(k1 ^&nbsp;0x7465646279746573)
&nbsp; &nbsp; length =&nbsp;len(data); blocks = length //&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(blocks):
&nbsp; &nbsp; &nbsp; &nbsp; m = struct.unpack_from('<Q', data, i*8)[0]
&nbsp; &nbsp; &nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; &nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; last =&nbsp;bytearray(8)
&nbsp; &nbsp; rem = length %&nbsp;8; start = blocks *&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(rem): last[i] = data[start+i]
&nbsp; &nbsp; last[7] = length &&nbsp;0xFF
&nbsp; &nbsp; m = struct.unpack('<Q',&nbsp;bytes(last))[0]
&nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; v2 ^=&nbsp;0xFF
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(4):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp;&nbsp;return&nbsp;u64(v0^v1^v2^v3)

# Standard SipHash-2-4 test vectors from the paper
# Key: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
# k0 = 0x0706050403020100, k1 = 0x0f0e0d0c0b0a0908
k0 =&nbsp;0x0706050403020100
k1 =&nbsp;0x0f0e0d0c0b0a0908

# Test vector for empty message: expected 0x726fdb47dd0e0e31
result_empty = siphash_2_4(b"", k0, k1)
print(f"SipHash(empty): 0x{result_empty:016x}&nbsp;(expected: 0x726fdb47dd0e0e31)")

# Test vector for message = 00: expected 0x74f839c593dc67fd
result_0 = siphash_2_4(bytes([0]), k0, k1)
print(f"SipHash([0]): &nbsp; 0x{result_0:016x}&nbsp;(expected: 0x74f839c593dc67fd)")

# Test vector for message = 00 01: expected 0x0d6c8009d9a94f5a
result_01 = siphash_2_4(bytes([0,&nbsp;1]), k0, k1)
print(f"SipHash([0,1]): 0x{result_01:016x}&nbsp;(expected: 0x0d6c8009d9a94f5a)")

# Test vector for message = 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e
result_15 = siphash_2_4(bytes(range(15)), k0, k1)
print(f"SipHash([0..14]): 0x{result_15:016x}&nbsp;(expected: 0xa129ca6149be45e5)")

📋 Python 输出 [14:29:31]

&nbsp;复制代码&nbsp;隐藏代码
SipHash(empty):&nbsp;0x726fdb47dd0e0e31&nbsp;(expected:&nbsp;0x726fdb47dd0e0e31)
SipHash([0]): &nbsp;&nbsp;0x74f839c593dc67fd&nbsp;(expected:&nbsp;0x74f839c593dc67fd)
SipHash([0,1]):&nbsp;0x0d6c8009d9a94f5a&nbsp;(expected:&nbsp;0x0d6c8009d9a94f5a)
SipHash([0..14]):&nbsp;0xa129ca6149be45e5&nbsp;(expected:&nbsp;0xa129ca6149be45e5)

🤖 助手 – 第 94 轮 [14:30:03]

SipHash is correct. Let me now verify the sub_2DAF0 function against my implementation by checking the actual assembly more carefully. The function takes (rdi=data, rsi=length, rdx=k0, rcx=k1):

🐍 执行 Python 脚本

依赖: xxhash

&nbsp;复制代码&nbsp;隐藏代码

# SipHash is verified correct. Let me now trace through sub_2E570 step by step
# to make sure the debug key computation is right.

import&nbsp;struct

defu64(x):&nbsp;return&nbsp;x &&nbsp;0xFFFFFFFFFFFFFFFF
defrol64(x, n):&nbsp;return&nbsp;u64((x << n) | (x >> (64&nbsp;- n)))

defsiphash_2_4(data, k0, k1):
&nbsp; &nbsp; v0 = u64(k0 ^&nbsp;0x736f6d6570736575)
&nbsp; &nbsp; v1 = u64(k1 ^&nbsp;0x646f72616e646f6d)
&nbsp; &nbsp; v2 = u64(k0 ^&nbsp;0x6c7967656e657261)
&nbsp; &nbsp; v3 = u64(k1 ^&nbsp;0x7465646279746573)
&nbsp; &nbsp; length =&nbsp;len(data); blocks = length //&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(blocks):
&nbsp; &nbsp; &nbsp; &nbsp; m = struct.unpack_from('<Q', data, i*8)[0]
&nbsp; &nbsp; &nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; &nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; last =&nbsp;bytearray(8)
&nbsp; &nbsp; rem = length %&nbsp;8; start = blocks *&nbsp;8
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(rem): last[i] = data[start+i]
&nbsp; &nbsp; last[7] = length &&nbsp;0xFF
&nbsp; &nbsp; m = struct.unpack('<Q',&nbsp;bytes(last))[0]
&nbsp; &nbsp; v3 ^= m
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(2):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp; v0 ^= m
&nbsp; &nbsp; v2 ^=&nbsp;0xFF
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(4):
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v1); v1=rol64(v1,13); v1^=v0; v0=rol64(v0,32)
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v3); v3=rol64(v3,16); v3^=v2
&nbsp; &nbsp; &nbsp; &nbsp; v0=u64(v0+v3); v3=rol64(v3,21); v3^=v0
&nbsp; &nbsp; &nbsp; &nbsp; v2=u64(v2+v1); v1=rol64(v1,17); v1^=v2; v2=rol64(v2,32)
&nbsp; &nbsp;&nbsp;return&nbsp;u64(v0^v1^v2^v3)

defsplitmix64(state):
&nbsp; &nbsp; state = u64(state +&nbsp;0x9E3779B97F4A7C15)
&nbsp; &nbsp; z = state
&nbsp; &nbsp; z = u64((z ^ (z >>&nbsp;30)) *&nbsp;0xBF58476D1CE4E5B9)
&nbsp; &nbsp; z = u64((z ^ (z >>&nbsp;27)) *&nbsp;0x94D049BB133111EB)
&nbsp; &nbsp;&nbsp;return&nbsp;state, z ^ (z >>&nbsp;31)

# sub_2E570 trace:
# ebp = dword_5CFD0 (number of beats = 4)
# rax = qword_5CFE8
# r14 = 0x1A8CBC5B802E097C ^ rax
# splitmix64(r14) -> rcx
# r15 = 0x802E097C1A8CBC5B ^ rax

import&nbsp;xxhash
beatmap = struct.pack('<4I',&nbsp;0,&nbsp;250,&nbsp;500,&nbsp;750)
qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest()
print(f"qfe8 = 0x{qfe8:016x}")

rax = qfe8
r14 = u64(0x1A8CBC5B802E097C&nbsp;^ rax)
print(f"r14 = 0x{r14:016x}")

# splitmix64
rcx_state = u64(r14 +&nbsp;0x9E3779B97F4A7C15)
print(f"rcx after add = 0x{rcx_state:016x}")
rdx = rcx_state
rdx = u64((rdx ^ (rdx >>&nbsp;0x1E)) *&nbsp;0xBF58476D1CE4E5B9)
print(f"after first mix = 0x{rdx:016x}")
rdx2 = rdx
rdx2 = u64((rdx2 ^ (rdx2 >>&nbsp;0x1B)) *&nbsp;0x94D049BB133111EB)
print(f"after second mix = 0x{rdx2:016x}")
rsi = rdx2
rcx_val = rsi
rcx_final = rcx_val ^ (rcx_val >>&nbsp;0x1F)
print(f"splitmix64 result = 0x{rcx_final:016x}")

# Verify with my function
_, sm_result = splitmix64(r14)
print(f"splitmix64 verify = 0x{sm_result:016x}")
print(f"Match:&nbsp;{rcx_final == sm_result}")

r15 = u64(0x802E097C1A8CBC5B&nbsp;^ rax)
print(f"r15 = 0x{r15:016x}")

# Loop 12 times
ebp =&nbsp;4# dword_5CFD0
rcx = rcx_final
for&nbsp;i&nbsp;inrange(12):
&nbsp; &nbsp;&nbsp;# Build 24-byte buffer on stack:
&nbsp; &nbsp;&nbsp;# [0:8] = rcx (var_48)
&nbsp; &nbsp;&nbsp;# [8:12] = i % ebp (var_40, as signed int)
&nbsp; &nbsp;&nbsp;# [12:16] = 0 (var_3C)
&nbsp; &nbsp;&nbsp;# [16:20] = i (var_38, as unsigned int)
&nbsp; &nbsp;&nbsp;# [20:24] = 2 (var_34, as unsigned int)

&nbsp; &nbsp;&nbsp;# sub_2DAF0(rdi=stack_buf, rsi=0x18, rdx=r14, rcx=r15)
&nbsp; &nbsp; buf = struct.pack('<q', rcx&nbsp;if&nbsp;rcx <&nbsp;2**63else&nbsp;rcx -&nbsp;2**64) &nbsp;# as signed? No, just raw bytes
&nbsp; &nbsp; buf = struct.pack('<Q', rcx)
&nbsp; &nbsp; buf += struct.pack('<i', i % ebp) &nbsp;# signed int
&nbsp; &nbsp; buf += struct.pack('<i',&nbsp;0)
&nbsp; &nbsp; buf += struct.pack('<I', i)
&nbsp; &nbsp; buf += struct.pack('<I',&nbsp;2)

&nbsp; &nbsp;&nbsp;assertlen(buf) ==&nbsp;24
&nbsp; &nbsp; rcx = siphash_2_4(buf, r14, r15)
&nbsp; &nbsp;&nbsp;print(f" &nbsp;iter&nbsp;{i}: buf={buf.hex()}, siphash=0x{rcx:016x}")

debug_key = rcx
print(f"\ndebug_key = 0x{debug_key:016x}")

📋 Python 输出 [14:30:05]

&nbsp;复制代码&nbsp;隐藏代码
qfe8 =&nbsp;0x810a3647628f0212
r14&nbsp;=&nbsp;0x9b868a1ce2a10b6e
rcx&nbsp;after&nbsp;add&nbsp;=&nbsp;0x39be03d661eb8783
after first mix =&nbsp;0x34cbd80bd0b0e78a
after second mix =&nbsp;0xc74999944c72ee50
splitmix64 result =&nbsp;0xc7499995c2e1dd78
splitmix64 verify =&nbsp;0xc7499995c2e1dd78
Match:&nbsp;True
r15&nbsp;=&nbsp;0x01243f3b7803be49
&nbsp; iter&nbsp;0: buf=78dde1c2959949c700000000000000000000000002000000, siphash=0x85cdb77357a99e24
&nbsp; iter&nbsp;1: buf=249ea95773b7cd8501000000000000000100000002000000, siphash=0xb2c4c086d15850af
&nbsp; iter&nbsp;2: buf=af5058d186c0c4b202000000000000000200000002000000, siphash=0x95a754caa4f216ce
&nbsp; iter&nbsp;3: buf=ce16f2a4ca54a79503000000000000000300000002000000, siphash=0x0e5cd4fb7f0d5c57
&nbsp; iter&nbsp;4: buf=575c0d7ffbd45c0e00000000000000000400000002000000, siphash=0x0e10802628e4256b
&nbsp; iter&nbsp;5: buf=6b25e4282680100e01000000000000000500000002000000, siphash=0x0d9b5a82357db3e4
&nbsp; iter&nbsp;6: buf=e4b37d35825a9b0d02000000000000000600000002000000, siphash=0x25c8374871df43ce
&nbsp; iter&nbsp;7: buf=ce43df714837c82503000000000000000700000002000000, siphash=0x9716de1f0fb6f76d
&nbsp; iter&nbsp;8: buf=6df7b60f1fde169700000000000000000800000002000000, siphash=0x410fe950e5dc8422
&nbsp; iter&nbsp;9: buf=2284dce550e90f4101000000000000000900000002000000, siphash=0x08d5fd605a986d2d
&nbsp; iter&nbsp;10: buf=2d6d985a60fdd50802000000000000000a00000002000000, siphash=0xb0a8da5912961744
&nbsp; iter&nbsp;11: buf=4417961259daa8b003000000000000000b00000002000000, siphash=0xd2c13a26769ce7e0

debug_key =&nbsp;0xd2c13a26769ce7e0

🤖 助手 – 第 95 轮 [14:30:13]

Now let me look at the verifyAndDecrypt flow more carefully. I need to understand what happens with qword_5CFE0 vs qword_5CFE8:

🔨 工具调用: mcp_ida-pro-headless_find

  • type:

    data_ref

  • targets:

    ["0x5CFE0", "0x5CFE8", "0x5CFF0", "0x5CFF8", "0x5D140"]

📋 mcp_ida-pro-headless_find 输出 [14:30:13]

&nbsp;复制代码&nbsp;隐藏代码
[
&nbsp;&nbsp;{
&nbsp; &nbsp;&nbsp;"query":"0x5CFE0",
&nbsp; &nbsp;&nbsp;"matches":[
&nbsp; &nbsp; &nbsp;&nbsp;"0x23a63",
&nbsp; &nbsp; &nbsp;&nbsp;"0x23c1e",
&nbsp; &nbsp; &nbsp;&nbsp;"0x23c2c",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24662",
&nbsp; &nbsp; &nbsp;&nbsp;"0x2466b",
&nbsp; &nbsp; &nbsp;&nbsp;"0x249a7",
&nbsp; &nbsp; &nbsp;&nbsp;"0x249b0"
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"count":7,
&nbsp; &nbsp;&nbsp;"cursor":{
&nbsp; &nbsp; &nbsp;&nbsp;"done":true
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;"error":null
},
{
&nbsp; &nbsp;&nbsp;"query":"0x5CFE8",
&nbsp; &nbsp;&nbsp;"matches":[
&nbsp; &nbsp; &nbsp;&nbsp;"0x23a6a",
&nbsp; &nbsp; &nbsp;&nbsp;"0x23c88",
&nbsp; &nbsp; &nbsp;&nbsp;"0x241b5",
&nbsp; &nbsp; &nbsp;&nbsp;"0x246ee",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24a33",
&nbsp; &nbsp; &nbsp;&nbsp;"0x2e59a",
&nbsp; &nbsp; &nbsp;&nbsp;"0x2e734"
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"count":7,
&nbsp; &nbsp;&nbsp;"cursor":{
&nbsp; &nbsp; &nbsp;&nbsp;"done":true
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;"error":null
},
{
&nbsp; &nbsp;&nbsp;"query":"0x5CFF0",
&nbsp; &nbsp;&nbsp;"matches":[
&nbsp; &nbsp; &nbsp;&nbsp;"0x23a71",
&nbsp; &nbsp; &nbsp;&nbsp;"0x243f0",
&nbsp; &nbsp; &nbsp;&nbsp;"0x247b7",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24bda"
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"count":4,
&nbsp; &nbsp;&nbsp;"cursor":{
&nbsp; &nbsp; &nbsp;&nbsp;"done":true
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;"error":null
},
{
&nbsp; &nbsp;&nbsp;"query":"0x5CFF8",
&nbsp; &nbsp;&nbsp;"matches":[
&nbsp; &nbsp; &nbsp;&nbsp;"0x23a7c",
&nbsp; &nbsp; &nbsp;&nbsp;"0x243a6",
&nbsp; &nbsp; &nbsp;&nbsp;"0x243e9",
&nbsp; &nbsp; &nbsp;&nbsp;"0x2475d",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24b97"
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"count":5,
&nbsp; &nbsp;&nbsp;"cursor":{
&nbsp; &nbsp; &nbsp;&nbsp;"done":true
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;"error":null
},
{
&nbsp; &nbsp;&nbsp;"query":"0x5D140",
&nbsp; &nbsp;&nbsp;"matches":[
&nbsp; &nbsp; &nbsp;&nbsp;"0x24756",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24b8b",
&nbsp; &nbsp; &nbsp;&nbsp;"0x24ca3"
&nbsp; &nbsp;&nbsp;],
&nbsp; &nbsp;&nbsp;"count":3,
&nbsp; &nbsp;&nbsp;"cursor":{
&nbsp; &nbsp; &nbsp;&nbsp;"done":true
&nbsp; &nbsp;&nbsp;},
&nbsp; &nbsp;&nbsp;"error":null
}
]

🤖 助手 – 第 96 轮 [14:30:19]

🔨 工具调用: mcp_ida-pro-headless_disasm

  • addr:

    0x249a0

  • max_instructions:

    50

📋 mcp_ida-pro-headless_disasm 输出 [14:30:19](较长,点击展开)

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"addr":&nbsp;"0x249a0",
"asm": {
&nbsp; &nbsp;&nbsp;"name":&nbsp;"verifyAndDecrypt",
&nbsp; &nbsp;&nbsp;"start_ea":&nbsp;"0x249a0",
&nbsp; &nbsp;&nbsp;"lines":&nbsp;"verifyAndDecrypt (.text @ 0x249a0):\n249a0 &nbsp;mov &nbsp; &nbsp; cs:byte_5D00C, 1\n249a7 &nbsp;mov &nbsp; &nbsp; rax, cs:qword_5CFE0\n249ae &nbsp;jmp &nbsp; &nbsp; short loc_249C0\n249b0 &nbsp;mov &nbsp; &nbsp; rax, cs:qword_5CFE0\n249b7 &nbsp;cmp &nbsp; &nbsp; cs:byte_5D00C, 1\n249be &nbsp;jnz &nbsp; &nbsp; short loc_24A30\n249c0 &nbsp;shl &nbsp; &nbsp; rcx, 20h\n249c4 &nbsp;movsxd &nbsp;rdx, edx\n249c7 &nbsp;mov &nbsp; &nbsp; rsi, 1A8CBC5B802E097Ch\n249d1 &nbsp;xor &nbsp; &nbsp; rsi, rcx\n249d4 &nbsp;xor &nbsp; &nbsp; rsi, rdx\n249d7 &nbsp;mov &nbsp; &nbsp; rcx, 9E3779B97F4A7C15h\n249e1 &nbsp;add &nbsp; &nbsp; rcx, rsi\n249e4 &nbsp;mov &nbsp; &nbsp; rdx, rcx\n249e7 &nbsp;shr &nbsp; &nbsp; rdx, 1Eh\n249eb &nbsp;xor &nbsp; &nbsp; rdx, rcx\n249ee &nbsp;mov &nbsp; &nbsp; rcx, 0BF58476D1CE4E5B9h\n249f8 &nbsp;imul &nbsp; &nbsp;rcx, rdx\n249fc &nbsp;mov &nbsp; &nbsp; rdx, rcx\n249ff &nbsp;shr &nbsp; &nbsp; rdx, 1Bh\n24a03 &nbsp;xor &nbsp; &nbsp; rdx, rcx\n24a06 &nbsp;mov &nbsp; &nbsp; rcx, 94D049BB133111EBh\n24a10 &nbsp;imul &nbsp; &nbsp;rcx, rdx\n24a14 &nbsp;mov &nbsp; &nbsp; rdx, rcx\n24a17 &nbsp;shr &nbsp; &nbsp; rdx, 1Fh\n24a1b &nbsp;xor &nbsp; &nbsp; rdx, rcx\n24a1e &nbsp;mov &nbsp; &nbsp; r13b, 1\n24a21 &nbsp;jz &nbsp; &nbsp; &nbsp;short loc_24A33\n24a23 &nbsp;shrd &nbsp; &nbsp;rcx, rdx, 23h\n24a28 &nbsp;xor &nbsp; &nbsp; rax, rdx\n24a2b &nbsp;xor &nbsp; &nbsp; rax, rcx\n24a2e &nbsp;jmp &nbsp; &nbsp; short loc_24A33\n24a30 &nbsp;xor &nbsp; &nbsp; r13d, r13d\n24a33 &nbsp;mov &nbsp; &nbsp; cs:qword_5CFE8, rax\n24a3a &nbsp;cmp &nbsp; &nbsp; dword ptr [rbx+8], 0\n24a3e &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24BB4\n24a44 &nbsp;mov &nbsp; &nbsp; r12d, [rbx+0Ch]\n24a48 &nbsp;test &nbsp; &nbsp;r12d, r12d\n24a4b &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24BB4\n24a51 &nbsp;mov &nbsp; &nbsp; edx, [rbx+10h]\n24a54 &nbsp;test &nbsp; &nbsp;edx, edx\n24a56 &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24BB4\n24a5c &nbsp;mov &nbsp; &nbsp; eax, [rbx+4]\n24a5f &nbsp;cmp &nbsp; &nbsp; eax, 2\n24a62 &nbsp;jz &nbsp; &nbsp; &nbsp;loc_24B8B\n24a68 &nbsp;cmp &nbsp; &nbsp; eax, 1\n24a6b &nbsp;jnz &nbsp; &nbsp; loc_24BB4\n24a71 &nbsp;mov &nbsp; &nbsp; [rsp+88h+var_80], edx\n24a75 &nbsp;lea &nbsp; &nbsp; rsi, [rbx+18h]\n24a79 &nbsp;lea &nbsp; &nbsp; rdx, [rbx+28h]",
&nbsp; &nbsp;&nbsp;"stack_frame": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_80",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x4",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_DWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_7C",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0xc",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x4",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_DWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_78",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x10",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_70",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x18",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_68",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x20",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_60",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x28",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"s2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x30",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"void *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_50",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x38",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"var_38",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x50",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_QWORD"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"__return_address",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"offset":&nbsp;"0x88",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"size":&nbsp;"0x8",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"_UNKNOWN *"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"return_type":&nbsp;"__int64",
&nbsp; &nbsp;&nbsp;"arguments": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg0",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64 *"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg2",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"name":&nbsp;"arg3",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"__int64"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
&nbsp; },
"instruction_count":&nbsp;50,
"total_instructions": null,
"cursor": {
&nbsp; &nbsp;&nbsp;"next":&nbsp;50
&nbsp; }
}

👤 用户 [14:30:19]

[TodoList Reminder – Round 57] Stats: 3 pending, 1 done, 0 failed. You have 3 pending items — execute them before adding new ones. Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

🤖 助手 – 第 97 轮 [14:31:12]

💭 思考过程

Let me step back and think about this more carefully. I’ve been trying to reverse-engineer the native library’s decryption logic, but I’m getting stuck on details. Let me reconsider the approach.

The key insight I’m missing might be about qword_5CFE8. Looking at the code:

  1. At 0x249a7/0x249b0: rax = qword_5CFE0 (which is the XXHash64 of beatmap)
  2. At 0x249c0-0x24a2b: A splitmix64-like transformation is applied to modify rax
  3. At 0x24a33: qword_5CFE8 = rax (the modified value)

So qword_5CFE8 is NOT the same as qword_5CFE0! It gets modified by the splitmix64 step. But in my code, I set qfe8 = qfe0 = xxhash64(beatmap, seed). This is wrong!

Let me trace the exact transformation:

  • rax = qword_5CFE0

    (the original xxhash)

  • rcx = dword_5D004 << 32

    (some game state)

  • rdx = sign_extend(dword_5D008)

    (experience counter)

  • rsi = 0x1A8CBC5B802E097C ^ rcx ^ rdx

  • Then splitmix64 on rsi -> gives rcx, rdx

  • If result is non-zero (r13b=1):

  • shrd rcx, rdx, 0x23

    (shift right double)

  • rax ^= rdx

  • rax ^= rcx

  • qword_5CFE8 = rax

So for a fresh install where dword_5D004=0 and dword_5D008=0:

  • rcx = 0 << 32 = 0
  • rdx = 0
  • rsi = 0x1A8CBC5B802E097C ^ 0 ^ 0 = 0x1A8CBC5B802E097C
  • splitmix64(0x1A8CBC5B802E097C) -> some value
  • Then rax gets XORed with the splitmix result

But wait, for a fresh install, byte_5D00C = 0, so at 0x249b7-0x249be:

  • cmp byte_5D00C, 1
  • jnz 0x24a30 (jump to xor r13d, r13d)

So r13d = 0, and we skip the XOR modification. qword_5CFE8 = qword_5CFE0 unchanged.

But wait, what about the first time? The flow is:

  1. sub_24FC0 is called, returns some value
  2. The return value updates dword_5D004 and dword_5D008
  3. If dword_5D008 >= 4, byte_5D00C = 1

For the FIRST call ever, dword_5D008 starts at 0. sub_24FC0 returns something, and the high 32 bits are added to dword_5D008. If the result is < 4, byte_5D00C stays 0.

Actually, let me look at this differently. The challenge is about decrypting hjm_pack.bin. The file has type=2 … [thinking truncated, was 4285 chars] … 24bf1: cmovz rsi, rax  (if r13b=0, use raw key) 24bf5: … 24bfa: call sub_2E680

&nbsp;复制代码&nbsp;隐藏代码

So the&nbsp;key&nbsp;passed&nbsp;to&nbsp;sub_2E680 depends&nbsp;on&nbsp;r13b:
- r13b=1:&nbsp;key&nbsp;= rax ^&nbsp;0xA5A5A5A5A5A5A5A5
- r13b=0:&nbsp;key&nbsp;= rax (raw)

Now,&nbsp;for&nbsp;the debug bypass path&nbsp;with&nbsp;byte_5D140=1:
- rax = sub_2E570() = debug_key
- r13b depends&nbsp;on&nbsp;the password verification at&nbsp;0x249c0-0x24a2b

For&nbsp;a fresh install&nbsp;with&nbsp;dword_5D008 <&nbsp;4and&nbsp;byte_5D00C =&nbsp;0:
- At&nbsp;0x249b7: byte_5D00C !=&nbsp;1, so jump&nbsp;to0x24a30
- r13d =&nbsp;0
- qword_5CFE8 = qword_5CFE0 (unchanged)

So r13b =&nbsp;0,&nbsp;and&nbsp;the&nbsp;keyis&nbsp;raw (no&nbsp;XOR).

But wait - I already tried this ("debug_key raw")&nbsp;and&nbsp;got noise.&nbsp;Letme&nbsp;reconsider.

Actually, maybe the issue&nbsp;is&nbsp;that qword_5CFE8&nbsp;IS&nbsp;modified.&nbsp;Letme&nbsp;look at&nbsp;where&nbsp;qword_5CFE8&nbsp;isset. At&nbsp;0x24a33: `mov cs:qword_5CFE8, rax`.&nbsp;And&nbsp;rax comes&nbsp;from&nbsp;qword_5CFE0 at&nbsp;0x249a7&nbsp;or0x249b0.

But wait - there's also 0x23a6a and 0x23c88 and 0x241b5 and 0x246ee that reference qword_5CFE8. Let me check if it's set elsewhere.

Actually,&nbsp;letme&nbsp;look at&nbsp;0x246ee:

</details>

Letmetake&nbsp;a different approach. Instead&nbsp;of&nbsp;trying&nbsp;to&nbsp;perfectly replicate the native code,&nbsp;letme&nbsp;use Unicorn&nbsp;to&nbsp;emulate the critical functions directly:

### 🐍 执行 Python 脚本

python

 Letme check where qword_5CFE8 is written (not just read)

Looking at the xrefs, letme check 0x23a6a and0x246ee

import struct

First, letme re-examine the initialization. qword5CFE0 and qword5CFE8

might be setin the startSession function.

 Letme look at the function that sets these values.

Actually, letmetry a completely different approach.

Instead of reimplementing everything, letme use unicorn to emulate the native code.

First, letme read the .so file

with open(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhjm.so”, “rb”) as f:     so_data = f.read()

print(f”SO file size: {len(so_data)} bytes”)

Check ELF header

magic = sodata[:4] print(f”Magic: {magic}”) eiclass = sodata[4]  # 1=32bit, 2=64bit print(f”Class: {’64-bit’ if eiclass == 2 else ’32-bit’}”)

 Get section headers to find .text

import struct eshoff = struct.unpackfrom(‘<Q’, sodata, 0x28)[0] eshentsize = struct.unpackfrom(‘data, 0x3A)[0] eshnum = struct.unpackfrom(‘<H’, sodata, 0x3C)[0] eshstrndx = struct.unpackfrom(‘data, 0x3E)[0]

print(f”Section headers at: 0x{eshoff:x}, count: {eshnum}, size: {e_shentsize}”)

 Get program headers for loading

ephoff = struct.unpackfrom(‘<Q’, sodata, 0x20)[0] ephentsize = struct.unpackfrom(‘data, 0x36)[0] ephnum = struct.unpackfrom(‘<H’, so_data, 0x38)[0]

print(f”\nProgram headers at: 0x{ephoff:x}, count: {ephnum}”) for i in range(ephnum):     off = ephoff + i * ephentsize     ptype = struct.unpackfrom(‘data, off)[0]     pflags = struct.unpackfrom(‘<I’, sodata, off+4)[0]     poffset = struct.unpackfrom(‘data, off+8)[0]     pvaddr = struct.unpackfrom(‘<Q’, sodata, off+16)[0]     pfilesz = struct.unpackfrom(‘data, off+32)[0]     pmemsz = struct.unpackfrom(‘<Q’, sodata, off+40)[0]     if ptype == 1:  # PTLOAD         print(f”  LOAD: offset=0x{poffset:x} vaddr=0x{pvaddr:x} filesz=0x{pfilesz:x} memsz=0x{pmemsz:x} flags=0x{pflags:x}”)

**📋 Python 输出**&nbsp;[14:31:12]

 复制代码 隐藏代码 Traceback (most recent call last):   File “C:\Users\WOYOUY~1\AppData\Local\Temp\ctfagent1289445898.py”, line 15, in     with open(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhjm.so”, “rb”) as f:          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: ‘D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhjm.so’

[error: exit status 1]

### 🤖 助手 - 第 98 轮 [14:31:30]

#### 📂 查找文件:&nbsp;`D:\AI\AICTF\workdir\52pojie\chu8`&nbsp;(模式:&nbsp;`libhjm*`)

**📋 查找结果**&nbsp;[14:31:30]

 复制代码 隐藏代码 No files found.

### 🤖 助手 - 第 99 轮 [14:31:34]

#### 📂 查找文件:&nbsp;`D:\AI\AICTF\workdir\52pojie\chu8`&nbsp;(模式:&nbsp;`*.so`)

**📋 查找结果**&nbsp;[14:31:34]

 复制代码 隐藏代码 D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\arm64-v8a\libhajimi.so D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\armeabi-v7a\libhajimi.so D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x86\libhajimi.so D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x86_64\libhajimi.so

### 🤖 助手 - 第 100 轮 [14:31:45]

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

import struct

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

Get program headers for loading

ephoff = struct.unpackfrom(‘<Q’, sodata, 0x20)[0] ephentsize = struct.unpackfrom(‘data, 0x36)[0] ephnum = struct.unpackfrom(‘<H’, so_data, 0x38)[0]

segments = [] for i inrange(ephnum):     off = ephoff + i * ephentsize     ptype = struct.unpackfrom(‘data, off)[0]     pflags = struct.unpackfrom(‘<I’, sodata, off+4)[0]     poffset = struct.unpackfrom(‘data, off+8)[0]     pvaddr = struct.unpackfrom(‘<Q’, sodata, off+16)[0]     pfilesz = struct.unpackfrom(‘data, off+32)[0]     pmemsz = struct.unpackfrom(‘<Q’, sodata, off+40)[0]     if ptype == 1:  # PTLOAD         segments.append((pvaddr, poffset, pfilesz, pmemsz, pflags))         print(f”LOAD: vaddr=0x{pvaddr:x} offset=0x{poffset:x} filesz=0x{pfilesz:x} memsz=0x{pmemsz:x}”)

print(f”\nFile size: {len(so_data)}”)

**📋 Python 输出**&nbsp;[14:31:45]

 复制代码 隐藏代码 LOAD: vaddr=0x0 offset=0x0 filesz=0x56570 memsz=0x56570 LOAD: vaddr=0x57570 offset=0x56570 filesz=0x4728 memsz=0x4728 LOAD: vaddr=0x5cca0 offset=0x5aca0 filesz=0xe8 memsz=0xfc0

File size: 374392

### 👤 用户 [14:31:45]

[Progress Checkpoint - Round 61/200]
Briefly assess your progress:

1. What have you accomplished so far?
2. Are you making progress toward the flag?
   Continue with your current approach if it shows promise.

### 🤖 助手 - 第 101 轮 [14:32:20]

<details>
<summary>💭 思考过程</summary>

Let me take stock. I've been trying to reimplement the native crypto functions in Python but getting wrong results. The Unicorn emulation approach is the right call - let me emulate the critical functions directly from the .so binary instead of trying to manually reimplement everything.

Let me set up Unicorn to emulate the x86\_64 .so file and call the key functions directly.

</details>

Progress: SipHash/XXHash verified correct, but full decryption chain still produces noise. Switching to Unicorn emulation to avoid reimplementation errors.

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`unicorn`

 复制代码 隐藏代码

from unicorn import * from unicorn.x86_const import * import struct

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 mu = Uc(UCARCHX86, UCMODE64)

Map segments

segments = [(0x0, 0x0, 0x56570, 0x56570), (0x57570, 0x56570, 0x4728, 0x4728), (0x5cca0, 0x5aca0, 0xe8, 0xfc0)] for vaddr, foff, fsz, msz in segments:     alignedstart = (BASE + vaddr) & ~0xFFF     alignedend = ((BASE + vaddr + msz) + 0xFFF) & ~0xFFF     try:         mu.memmap(alignedstart, alignedend – alignedstart)     except:         pass     mu.memwrite(BASE + vaddr, sodata[foff:foff+fsz])

Stack

STACKBASE = 0x7FFF0000 STACKSIZE = 0x100000 mu.memmap(STACKBASE, STACKSIZE) mu.regwrite(UCX86REGRSP, STACKBASE + STACK_SIZE – 0x1000)

Heap for data

HEAP = 0x20000000 mu.mem_map(HEAP, 0x100000)

Set up FS segment for stack canary (fs:0x28)

FSBASE = 0x30000000 mu.memmap(FSBASE, 0x1000) mu.memwrite(FSBASE + 0x28, struct.pack(‘write(0xC0000100, FS_BASE)  # FS base MSR

Test: call sub_2DAF0 (SipHash) with known test vector

sub_2DAF0(rdi=data, rsi=length, rdx=k0, rcx=k1)

testkey = bytes(range(16)) k0 = struct.unpackfrom(‘<Q’, testkey, 0)[0] k1 = struct.unpackfrom(‘<Q’, testkey, 8)[0] testmsg = bytes(range(15)) mu.memwrite(HEAP, testmsg)

mu.regwrite(UCX86REGRDI, HEAP) mu.regwrite(UCX86REGRSI, len(testmsg)) mu.regwrite(UCX86REGRDX, k0) mu.regwrite(UCX86REG_RCX, k1)

Push return address

retaddr = 0x40000000 mu.memmap(retaddr, 0x1000) mu.memwrite(retaddr, b’\xf4′)  # HLT rsp = mu.regread(UCX86REGRSP) – 8 mu.memwrite(rsp, struct.pack(‘<Q’, retaddr)) mu.regwrite(UCX86REG_RSP, rsp)

try:     mu.emustart(BASE + 0x2DAF0, retaddr, timeout=5000000)     result = mu.regread(UCX86REGRAX)     print(f”SipHash emulated: 0x{result:016x}”)     print(f”Expected:         0xa129ca6149be45e5″) except UcError as e:     rip = mu.regread(UCX86REGRIP)     print(f”Error at 0x{rip:x}: {e}”)

**📋 Python 输出**&nbsp;[14:32:22]

 复制代码 隐藏代码 SipHash emulated: 0xa129ca6149be45e5 Expected:         0xa129ca6149be45e5

### 🤖 助手 - 第 102 轮 [14:33:19]

Unicorn works. Now let me emulate the full key derivation chain:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`unicorn,xxhash`

 复制代码 隐藏代码

from unicorn import * from unicorn.x86_const import * import struct

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 STACKBASE = 0x7FFF0000; STACKSIZE = 0x100000 HEAP = 0x20000000; FSBASE = 0x30000000 RETADDR = 0x40000000

defsetupemu():     mu = Uc(UCARCHX86, UCMODE64)     segs = [(0x0,0x0,0x56570,0x56570),(0x57570,0x56570,0x4728,0x4728),(0x5cca0,0x5aca0,0xe8,0xfc0)]     mapped = set()     for va,fo,fs,ms in segs:         s = (BASE+va)&~0xFFF; e = ((BASE+va+ms)+0xFFF)&~0xFFF         for pg inrange(s,e,0x1000):             if pg notin mapped:                 try: mu.memmap(pg, 0x1000)                 except: pass                 mapped.add(pg)         mu.memwrite(BASE+va, sodata[fo:fo+fs])     mu.memmap(STACKBASE, STACKSIZE)     mu.memmap(HEAP, 0x100000)     mu.memmap(FSBASE, 0x1000)     mu.memwrite(FSBASE+0x28, struct.pack(‘<Q’, 0xDEADBEEFCAFEBABE))     mu.msrwrite(0xC0000100, FSBASE)     mu.memmap(RETADDR, 0x1000)     mu.memwrite(RETADDR, b’\xf4′)     return mu

defcallfunc(mu, addr, argsregs=None):     if argsregs:         regorder = [UCX86REGRDI, UCX86REGRSI, UCX86REGRDX, UCX86REGRCX, UCX86REGR8, UCX86REGR9]         for i, v inenumerate(argsregs):             mu.regwrite(regorder[i], v)     rsp = STACKBASE + STACKSIZE – 0x1000     mu.memwrite(rsp, struct.pack(‘<Q’, RETADDR))     mu.regwrite(UCX86REGRSP, rsp)     mu.emustart(BASE + addr, RETADDR, timeout=10000000)     return mu.regread(UCX86REG_RAX)

Step 1: Set up global state

beatMap = [0, 250, 500, 750], dword_5CFD0 = 4

qword5CFE0 and qword5CFE8 need to be set

import xxhash beatmap = struct.pack(‘<4I’, 0, 250, 500, 750) qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest() print(f”qfe8 = 0x{qfe8:016x}”)

mu = setup_emu()

Set globals

mu.memwrite(BASE + 0x5CFD0, struct.pack(‘5CFD0 = 4 (beat count) mu.memwrite(BASE + 0x5CFE0, struct.pack(‘5CFE0 mu.memwrite(BASE + 0x5CFE8, struct.pack(‘5CFE8 mu.memwrite(BASE + 0x5D140, struct.pack(‘5D140 = 1 (debug bypass)

Step 2: Call sub_2E570 to get debug key

debugkey = callfunc(mu, 0x2E570) print(f”debugkey = 0x{debugkey:016x}”)

Step 3: Now simulate verifyAndDecrypt path

At 0x24be1: rsi = 0xA5A5A5A5A5A5A5A5 ^ rax (if r13b=1)

or rsi = rax (if r13b=0)

Then call sub2E680(rdi=&filedata, rsi=key_val)

But first I need to understand what qword_5CFE0 is.

Looking at 0x249a7: rax = qword_5CFE0

Then at 0x249c0: shl rcx, 0x20; movsxd rdx, edx

rcx and edx come from the caller (arg2, arg3 of verifyAndDecrypt)

rsi = 0x1A8CBC5B802E097C ^ rcx ^ rdx

Then splitmix64(rsi) -> rdx

If rdx != 0: r13b = 1, and rax ^= rdx, rax ^= shrd(rcx, rdx, 0x23)

Then qword_5CFE8 = rax

So qword_5CFE8 depends on the password verification!

The password args (rcx, edx) modify qfe8 via splitmix64 + XOR.

For the debug path, we skip password verification, so qfe8 = qfe0 (unchanged).

Let me check: in the debug path at 0x24b8b:

al = byte_5D140 (=1 for debug)

test al, al -> setnz cl -> cl=1

or cl, byte5CFF8 -> cl |= byte5CFF8

or cl, r13b -> cl |= r13b

test cl, 1 -> jz fail (won’t jump since cl=1)

test al, al -> jz 24bda (won’t jump since al=1)

call sub2E570 -> rax = debugkey

rsi = 0xA5A5A5A5A5A5A5A5

xor rsi, rax -> rsi = debug_key ^ 0xA5A5A5A5A5A5A5A5

test r13b, r13b -> cmovz rsi, rax

If r13b=0: rsi = rax (raw debug_key)

If r13b=1: rsi = debug_key ^ 0xA5A5A5A5A5A5A5A5

r13b is set at 0x24a1e or 0x24a30

At 0x24a30: xor r13d, r13d (r13b=0) when byte_5D00C != 1

byte5D00C is set to 1 at 0x249a0 when dword5D008 >= 4

dword_5D008 is the experience counter. For debug bypass, it might be 0.

So byte_5D00C = 0, and we go to 0x24a30: r13b = 0

Therefore: rsi = rax = debug_key (NO XOR)

BUT WAIT: at 0x249a0, byte_5D00C is set to 1 BEFORE the splitmix64 code.

The flow is:

0x24898: cmp dword_5D008, 4

0x2489f: jge 0x249a0 (if exp >= 4, set byte_5D00C=1, then do splitmix)

0x248a5: … (if exp < 4, different path)

For debug bypass with exp=0: we DON’T go to 0x249a0.

Instead we go through the other path. Let me check what happens.

Actually, let me just check: what is byte_5D00C initially? It’s in BSS, so 0.

And dword_5D008 is also 0.

So at 0x24898: 0 < 4, so we DON’T jump to 0x249a0.

We continue at 0x248a5.

Let me look at 0x248a5:

print(“\nNow checking the flow when exp < 4…”)

**📋 Python 输出**&nbsp;[14:33:23]

 复制代码 隐藏代码 qfe8 = 0x810a3647628f0212 debug_key = 0xd2c13a26769ce7e0

Now checking the flow when exp < 4…

### 🤖 助手 - 第 103 轮 [14:33:39]

#### 🔨 工具调用:&nbsp;`mcp_ida-pro-headless_disasm`

* **addr:**

  `0x24890`
* **max\_instructions:**

  `30`

**📋&nbsp;`mcp_ida-pro-headless_disasm`&nbsp;输出**&nbsp;[14:33:39]

 复制代码 隐藏代码 {   “addr”: “0x24890”, “asm”: {     “name”: “verifyAndDecrypt”,     “startea”: “0x24890”,     “lines”: “verifyAndDecrypt (.text @ 0x24890):\n24890  call    qword ptr [rax+558h]\n24896  test    eax, eax\n24898  jle     loc24934\n2489e  mov     ebp, eax\n248a0  mov     ebx, eax\n248a2  mov     rdi, rbx; size\n248a5  call    Znwm; operator new(ulong)\n248aa  mov     [rsp+88h+var70], rax\n248af  lea     r12, [rax+rbx]\n248b3  mov     [rsp+88h+var60], r12\n248b8  mov     rdi, rax; s\n248bb  xor     esi, esi; c\n248bd  mov     [rsp+88h+var78], rbx\n248c2  mov     rdx, rbx; n\n248c5  mov     rbx, rax\n248c8  call    memset\n248cd  mov     [rsp+88h+var68], r12\n248d2  mov     rax, [r14]\n248d5  mov     rdi, r14\n248d8  mov     rsi, r13\n248db  xor     edx, edx\n248dd  mov     ecx, ebp\n248df  mov     r8, rbx\n248e2  call    qword ptr [rax+640h]\n248e8  cmp     ebp, 33h ; ‘3’\n248eb  jbe     short loc248F5\n248ed  cmp     dword ptr [rbx], 314D4A48h\n248f3  jz      short loc24967\n248f5  mov     rax, [r14]\n248f8  mov     rdi, r14″,     “stackframe”: [       {         “name”: “var80″,         “offset”: “0x8”,         “size”: “0x4”,         “type”: “DWORD”       },       {         “name”: “var7C”,         “offset”: “0xc”,         “size”: “0x4”,         “type”: “DWORD”       },       {         “name”: “var78″,         “offset”: “0x10”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var70″,         “offset”: “0x18”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var68″,         “offset”: “0x20”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var60″,         “offset”: “0x28”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “s2”,         “offset”: “0x30”,         “size”: “0x8”,         “type”: “void *”       },       {         “name”: “var50″,         “offset”: “0x38”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var38″,         “offset”: “0x50”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “returnaddress”,         “offset”: “0x88”,         “size”: “0x8”,         “type”: “UNKNOWN *”       }     ],     “returntype”: “int64″,     “arguments”: [       {         “name”: “arg0”,         “type”: “int64 *”       },       {         “name”: “arg1”,         “type”: “int64″       },       {         “name”: “arg2”,         “type”: “int64″       },       {         “name”: “arg3”,         “type”: “int64″       }     ]   }, “instructioncount”: 30, “total_instructions”: null, “cursor”: {     “next”: 30   } }

### 🤖 助手 - 第 104 轮 [14:34:20]

I see - this function calls virtual methods (vtable calls at&nbsp;`[rax+558h]`&nbsp;and&nbsp;`[rax+640h]`). These are JNI calls to Java. The flow is complex. Let me instead focus on emulating just&nbsp;`sub_2E680`&nbsp;which is the actual decryption function, and pass it the right key:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`unicorn,xxhash`

 复制代码 隐藏代码

from unicorn import * from unicorn.x86_const import * import struct, xxhash

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 STACKBASE = 0x7FFF0000; STACKSIZE = 0x200000 HEAP = 0x20000000; FSBASE = 0x30000000; RETADDR = 0x40000000

mu = Uc(UCARCHX86, UCMODE64) mapped = set() segs = [(0x0,0x0,0x56570,0x56570),(0x57570,0x56570,0x4728,0x4728),(0x5cca0,0x5aca0,0xe8,0xfc0)] for va,fo,fs,ms in segs:     s=(BASE+va)&~0xFFF; e=((BASE+va+ms)+0xFFF)&~0xFFF     for pg inrange(s,e,0x1000):         if pg notin mapped:             mu.memmap(pg,0x1000); mapped.add(pg)     mu.memwrite(BASE+va, sodata[fo:fo+fs]) mu.memmap(STACKBASE, STACKSIZE) mu.memmap(HEAP, 0x200000) mu.memmap(FSBASE, 0x1000) mu.memwrite(FSBASE+0x28, struct.pack(‘write(0xC0000100, FSBASE) mu.memmap(RETADDR, 0x1000) mu.memwrite(RET_ADDR, b’\xcc’)

Read hjm_pack.bin

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\assets\hjmpack.bin”, “rb”) as f:     hjm_data = f.read()

Set up globals

beatmap = struct.pack(‘<4I’, 0, 250, 500, 750) qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest() mu.memwrite(BASE + 0x5CFD0, struct.pack(‘write(BASE + 0x5CFE0, struct.pack(‘<Q’, qfe8)) mu.mem_write(BASE + 0x5CFE8, struct.pack(‘<Q’, qfe8))

sub2E680 signature: sub2E680(_QWORD *a1, __int64 a2)

a1 = pointer to a structure containing the file data (like a std::vector)

a2 = the key value (rsi)

a1 points to a structure where:

a1[0] = pointer to start of data

a1[1] = pointer to end of data

The data must be >= 0x34 bytes and start with magic 0x314D4A48

Set up the file data in heap

FILEDATA = HEAP + 0x10000 mu.memwrite(FILEDATA, hjmdata)

Set up the a1 structure (two pointers: start, end)

A1STRUCT = HEAP + 0x20000 mu.memwrite(A1STRUCT, struct.pack(‘DATA, FILEDATA + len(hjmdata)))

The key value – try debug_key (raw, no XOR)

debug_key = 0xd2c13a26769ce7e0

Hook operator new and operator delete to provide heap memory

NEWHEAP = HEAP + 0x30000 newoffset = [0]

defhookcode(uc, address, size, userdata):     pass

We need to handle calls to operator new, operator delete, memcpy, memset

Let me patch the PLT entries to return immediately

Find PLT entries – let me just hook specific addresses

sub_2E680 calls:

– sub_2EB00 (key derivation)

– sub_24CB0 (xxhash fold)

– sub_2DF40 (AES-CTR)

– operator new, operator delete, memcpy, memset (via PLT)

Let me check what external calls sub_2E680 makes

print(“Testing emulation of sub_2E680…”)

Set up call

rsp = STACKBASE + STACKSIZE – 0x2000 mu.memwrite(rsp, struct.pack(‘ADDR)) mu.regwrite(UCX86REGRSP, rsp) mu.regwrite(UCX86REGRDI, A1STRUCT) mu.regwrite(UCX86REGRSI, debugkey)

Hook to intercept PLT calls

calllog = [] defhookmeminvalid(uc, access, address, size, value, userdata):     rip = uc.regread(UCX86REGRIP)     call_log.append(f”Invalid mem access at RIP=0x{rip:x}, addr=0x{address:x}, access={access}”)     returnFalse

mu.hookadd(UCHOOKMEMUNMAPPED, hookmeminvalid)

try:     mu.emustart(BASE + 0x2E680, RETADDR, timeout=10000000, count=50000)     rip = mu.regread(UCX86REGRIP)     if rip == RETADDR:         result = mu.regread(UCX86REGRAX)         print(f”sub2E680 returned: 0x{result:x}”)     else:         print(f”Stopped at RIP=0x{rip:x}”) except UcError as e:     rip = mu.regread(UCX86REGRIP)     print(f”Error at RIP=0x{rip:x}: {e}”)     if calllog:         for l in calllog[-5:]: print(f”  {l}”)

**📋 Python 输出**&nbsp;[14:34:24]

 复制代码 隐藏代码 Testing emulation of sub2E680… Error at RIP=0x1002e86f: Invalid instruction (UCERRINSNINVALID)

### 👤 用户 [14:34:24]

[TodoList Reminder - Round 65]
Stats: 3 pending, 1 done, 0 failed.
You have 3 pending items — execute them before adding new ones.
Do NOT repeat failed approaches. If stuck, reset your TodoList with a new strategy.

### 🤖 助手 - 第 105 轮 [14:34:44]

#### 🔨 工具调用:&nbsp;`mcp_ida-pro-headless_disasm`

* **addr:**

  `0x2E860`
* **max\_instructions:**

  `20`

<details>
<summary>📋&nbsp;`mcp_ida-pro-headless_disasm`&nbsp;输出 [14:34:44](较长,点击展开)</summary>

 复制代码 隐藏代码 {   “addr”: “0x2E860”, “asm”: {     “name”: “sub2E680″,     “startea”: “0x2e860”,     “lines”: “sub2E680 (.text @ 0x2e860):\n2e861  mov     rax, [r14]\n2e864  mov     ecx, r15d\n2e867  shr     ecx, 8\n2e86a  movd    xmm0, r15d\n2e86f  pinsrb  xmm0, ecx, 1\n2e875  mov     ecx, r15d\n2e878  shr     ecx, 10h\n2e87b  pinsrb  xmm0, ecx, 2\n2e881  mov     ecx, r15d\n2e884  shr     ecx, 18h\n2e887  pinsrb  xmm0, ecx, 3\n2e88d  mov     [rsp+0E8h+src], rax\n2e895  movzx   ecx, byte ptr [rsp+0E8h+varA8]\n2e89a  pinsrb  xmm0, ecx, 4\n2e8a0  movzx   ecx, byte ptr [rsp+0E8h+varB0]\n2e8a5  pinsrb  xmm0, ecx, 5\n2e8ab  mov     ecx, [r14+8]\n2e8af  mov     dword ptr [rsp+0E8h+src+8], ecx\n2e8b6  movzx   ecx, byte ptr [rsp+0E8h+varB8]\n2e8bb  pinsrb  xmm0, ecx, 6″,     “stackframe”: [       {         “name”: “varE8″,         “offset”: “0x0”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varE0″,         “offset”: “0x8”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “n”,         “offset”: “0x10”,         “size”: “0x8”,         “type”: “sizet”       },       {         “name”: “varD0″,         “offset”: “0x18”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varC8″,         “offset”: “0x20”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varC0″,         “offset”: “0x28”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varB8″,         “offset”: “0x30”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varB0″,         “offset”: “0x38”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varA8″,         “offset”: “0x40”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “varA0″,         “offset”: “0x48”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var98″,         “offset”: “0x50”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var90″,         “offset”: “0x58”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “ptr”,         “offset”: “0x60”,         “size”: “0x10”,         “type”: “void *[2]”       },       {         “name”: “var78″,         “offset”: “0x70”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “var68″,         “offset”: “0x80”,         “size”: “0x10”,         “type”: “OWORD”       },       {         “name”: “src”,         “offset”: “0x90”,         “size”: “0x10”,         “type”: “void *[2]”       },       {         “name”: “var48″,         “offset”: “0xa0”,         “size”: “0x10”,         “type”: “OWORD”       },       {         “name”: “var38″,         “offset”: “0xb0”,         “size”: “0x8”,         “type”: “QWORD”       },       {         “name”: “returnaddress”,         “offset”: “0xe8”,         “size”: “0x8”,         “type”: “UNKNOWN *”       }     ],     “returntype”: “int64″,     “arguments”: [       {         “name”: “arg0”,         “type”: “QWORD *”       },       {         “name”: “arg1”,         “type”: “_int64″       }     ]   }, “instructioncount”: 20, “total_instructions”: null, “cursor”: {     “next”: 20   } }

</details>

### 🤖 助手 - 第 106 轮 [14:35:19]

The&nbsp;`pinsrb`&nbsp;instruction is SSE4.1 - Unicorn might not support it. Let me check the error address and try a different approach - emulate with Qiling or just carefully trace the IV construction:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`unicorn,xxhash`

 复制代码 隐藏代码

The pinsrb at 0x2e86f is the issue. Let me look at what it’s doing:

It’s building an XMM0 register byte-by-byte from r15d (chunk counter) and

the folded qfe8 value. Then XORing with the IV.

#

xmm0[0] = r15d & 0xFF

xmm0[1] = (r15d >> 8) & 0xFF

xmm0[2] = (r15d >> 16) & 0xFF

xmm0[3] = (r15d >> 24) & 0xFF

xmm0[4] = var_A8 byte (from folded qfe8)

xmm0[5] = var_B0 byte

xmm0[6] = var_B8 byte

xmm0[7] = var_C0 byte

#

So xmm0[0:4] = r15d (chunk counter as LE bytes)

xmm0[4:8] = folded qfe8 as LE bytes

#

This is exactly what I had: struct.pack(‘<II’, chunkcounter, foldedqfe8)

XORed with the first 8 bytes of the IV.

#

My implementation was correct for this part. The issue must be in sub_2EB00

(key derivation) or sub_24CB0 (xxhash fold for AES key).

#

Let me try emulating sub_2EB00 with Unicorn, avoiding the SSE4.1 issue.

sub_2EB00 doesn’t use pinsrb.

from unicorn import * from unicorn.x86_const import * import struct, xxhash

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 STACKBASE = 0x7FFF0000; STACKSIZE = 0x200000 HEAP = 0x20000000; FSBASE = 0x30000000; RETADDR = 0x40000000

mu = Uc(UCARCHX86, UCMODE64) mapped = set() segs = [(0x0,0x0,0x56570,0x56570),(0x57570,0x56570,0x4728,0x4728),(0x5cca0,0x5aca0,0xe8,0xfc0)] for va,fo,fs,ms in segs:     s=(BASE+va)&~0xFFF; e=((BASE+va+ms)+0xFFF)&~0xFFF     for pg inrange(s,e,0x1000):         if pg notin mapped:             mu.memmap(pg,0x1000); mapped.add(pg)     mu.memwrite(BASE+va, sodata[fo:fo+fs]) mu.memmap(STACKBASE, STACKSIZE) mu.memmap(HEAP, 0x200000) mu.memmap(FSBASE, 0x1000) mu.memwrite(FSBASE+0x28, struct.pack(‘write(0xC0000100, FSBASE) mu.memmap(RETADDR, 0x1000) mu.memwrite(RET_ADDR, b’\xf4′)

beatmap = struct.pack(‘<4I’, 0, 250, 500, 750) qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest() debug_key = 0xd2c13a26769ce7e0

sub2EB00(arg0=keyval, arg1=qfe8, arg2=outptr, arg3=outptr2)

rdi=keyval, rsi=qfe8, rdx=outptr (for first 8 bytes), rcx=out_ptr2 (for second 8 bytes)

Wait, let me re-check the signature from the decompiled code

sub2EB00 args: (arg0=int64, arg1=int64, arg2=QWORD, arg3=unsigned __int64)

rdi=keyval, rsi=qfe8, rdx=saltptr, rcx=out_ptr

Actually looking at the asm at 0x2eb00:

rbx = rcx (arg3 = out_ptr)

r14 = rdx (arg2 = salt_ptr)

rdx = 0x1A8CBC5B802E097C ^ rsi (rsi=arg1=qfe8)

rcx = 0x802E097C1A8CBC5B ^ rsi

[rsp] = rdi (arg0=key_val)

[rsp+8] = rsi (arg1=qfe8)

Then calls sub_2DAF0(rdi=rsp, rsi=0x10, rdx, rcx) – SipHash of 16-byte buffer

So sub2EB00(keyval, qfe8, saltptr, outptr)

It computes SipHash of [key_val, qfe8] with keys derived from qfe8

Then does some XXHash-like mixing with the salt

Let me set up the salt from the file

salt = bytes.fromhex(‘0f1e2d3c4b5a69788796a5b4c3d2e1f0’)  # from hjm_pack.bin offset 24

OUT1 = HEAP + 0x1000 OUT2 = HEAP + 0x1008

rsp = STACKBASE + STACKSIZE – 0x2000 mu.memwrite(rsp, struct.pack(‘ADDR)) mu.regwrite(UCX86REGRSP, rsp) mu.regwrite(UCX86REGRDI, debugkey) mu.regwrite(UCX86REGRSI, qfe8) mu.regwrite(UCX86REGRDX, HEAP + 0x2000)  # salt pointer mu.regwrite(UCX86REG_RCX, HEAP + 0x3000)  # output pointer

Write salt

mu.mem_write(HEAP + 0x2000, salt)

Output buffer

mu.mem_write(HEAP + 0x3000, b’\x00′ * 16)

try:     mu.emustart(BASE + 0x2EB00, RETADDR, timeout=10000000)     rip = mu.regread(UCX86REGRIP)     if rip == RETADDR:         outdata = mu.memread(HEAP + 0x3000, 16)         print(f”sub2EB00 output: {bytes(outdata).hex()}”)         q0, q1 = struct.unpack(‘data))         print(f”  q0 = 0x{q0:016x}”)         print(f”  q1 = 0x{q1:016x}”)     else:         print(f”Stopped at RIP=0x{rip:x}”) except UcError as e:     rip = mu.regread(UCX86REGRIP)     print(f”Error at RIP=0x{rip:x}: {e}”)

**📋 Python 输出**&nbsp;[14:35:23]

 复制代码 隐藏代码 sub_2EB00 output: 7f721557831c2dbd7f70756f448aee26   q0 = 0xbd2d1c835715727f   q1 = 0x26ee8a446f75707f

### 🤖 助手 - 第 107 轮 [14:35:54]

Now I have the correct output from sub\_2EB00 via emulation. Let me also emulate sub\_24CB0 and then do the full decryption:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`unicorn,xxhash,pycryptodome`

 复制代码 隐藏代码

from unicorn import * from unicorn.x86_const import * import struct, xxhash from Crypto.Cipher import AES

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\lib\x8664\libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 STACKBASE = 0x7FFF0000; STACKSIZE = 0x200000 HEAP = 0x20000000; FSBASE = 0x30000000; RETADDR = 0x40000000

defmakeemu():     mu = Uc(UCARCHX86, UCMODE64)     mapped = set()     segs = [(0x0,0x0,0x56570,0x56570),(0x57570,0x56570,0x4728,0x4728),(0x5cca0,0x5aca0,0xe8,0xfc0)]     for va,fo,fs,ms in segs:         s=(BASE+va)&~0xFFF; e=((BASE+va+ms)+0xFFF)&~0xFFF         for pg inrange(s,e,0x1000):             if pg notin mapped:                 mu.memmap(pg,0x1000); mapped.add(pg)         mu.memwrite(BASE+va, sodata[fo:fo+fs])     mu.memmap(STACKBASE, STACKSIZE)     mu.memmap(HEAP, 0x200000)     mu.memmap(FSBASE, 0x1000)     mu.memwrite(FSBASE+0x28, struct.pack(‘<Q’, 0xDEADBEEFCAFEBABE))     mu.msrwrite(0xC0000100, FSBASE)     mu.memmap(RETADDR, 0x1000)     mu.memwrite(RETADDR, b’\xf4′)     return mu

defcallfunc(mu, addr, rdi=0, rsi=0, rdx=0, rcx=0, r8=0, r9=0):     rsp = STACKBASE + STACKSIZE – 0x2000     mu.memwrite(rsp, struct.pack(‘<Q’, RETADDR))     mu.regwrite(UCX86REGRSP, rsp)     mu.regwrite(UCX86REGRDI, rdi)     mu.regwrite(UCX86REGRSI, rsi)     mu.regwrite(UCX86REGRDX, rdx)     mu.regwrite(UCX86REGRCX, rcx)     mu.regwrite(UCX86REGR8, r8)     mu.regwrite(UCX86REGR9, r9)     mu.emustart(BASE + addr, RETADDR, timeout=10000000)     return mu.regread(UCX86REG_RAX)

Read file

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\apkunzipped\assets\hjmpack.bin”, “rb”) as f:     hjm = f.read()

beatmap = struct.pack(‘<4I’, 0, 250, 500, 750) qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest() debug_key = 0xd2c13a26769ce7e0

salt = hjm[24:40] iv_bytes = hjm[40:52] encrypted = hjm[52:]

Step 1: sub2EB00(debugkey, qfe8, saltptr, outptr) -> var_68

mu = makeemu() mu.memwrite(HEAP + 0x2000, salt) mu.memwrite(HEAP + 0x3000, b’\x00′ * 16) callfunc(mu, 0x2EB00, rdi=debugkey, rsi=qfe8, rdx=HEAP+0x2000, rcx=HEAP+0x3000) var68 = bytes(mu.memread(HEAP + 0x3000, 16)) print(f”var68 = {var_68.hex()}”)

Step 2: For each chunk, compute AES key via sub_24CB0

folded = (qfe8 ^ (qfe8 >> 32)) & 0xFFFFFFFF total = len(encrypted) numchunks = (total + 255) // 256 decrypted = bytearray(total) varE0 = 0

for ci inrange(numchunks):     # Build 32-byte src buffer     src = var68  # 16 bytes     src += struct.pack(‘<II’, ci, qfe8 & 0xFFFFFFFF)  # r15d, r13d (low 32 of qfe8)     src += struct.pack(‘<Q’, (var_E0 ^ qfe8) & 0xFFFFFFFFFFFFFFFF)

    mu2 = makeemu()     mu2.memwrite(HEAP + 0x4000, src)

    # sub24CB0(dataptr, length, seed)     h1 = call_func(mu2, 0x24CB0, rdi=HEAP+0x4000, rsi=32, rdx=0x1357)

    mu3 = makeemu()     mu3.memwrite(HEAP + 0x4000, src)     h2 = call_func(mu3, 0x24CB0, rdi=HEAP+0x4000, rsi=32, rdx=0x2468)

    aes_key = struct.pack(‘<QQ’, h1, h2)

    # Build 12-byte nonce     counterbytes = struct.pack(‘mod = bytearray(ivbytes[:8])     for j inrange(8): ivmod[j] ^= counterbytes[j]     nonce = bytes(ivmod) + iv_bytes[8:12]

    # AES-CTR decrypt (LE counter)     cs = ci * 256     csz = min(256, total – cs)     for bi inrange(0, csz, 16):         block = nonce + struct.pack(‘<I’, bi // 16)         cipher = AES.new(aeskey, AES.MODEECB)         ks = cipher.encrypt(block)         for k inrange(min(16, csz – bi)):             decrypted[cs + bi + k] = encrypted[cs + bi + k] ^ ks[k]

    varE0 += 0x100000000     if ci == 0:         print(f”Chunk 0: key={aeskey.hex()}, nonce={nonce.hex()}”)         print(f”  h1=0x{h1:016x}, h2=0x{h2:016x}”)         print(f”  first16={decrypted[:16].hex()}”)

Check result

bits = sum(bin(b).count(‘1’) for b in decrypted) print(f”\nTotal bits set: {bits}/{total8} ({bits100//(total*8)}%)”) print(f”First 32 bytes: {decrypted[:32].hex()}”) print(f”Last 32 bytes: {decrypted[-32:].hex()}”)

Save

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “wb”) as f:     f.write(decrypted)

**📋 Python 输出**&nbsp;[14:36:01]

 复制代码 隐藏代码 var_68=7f721557831c2dbd7f70756f448aee26 Chunk 0:key=16d6d0d72e99938d72d9bcd5616451ef,nonce=112233440052f26b99aabbcc   h1=0x8d93992ed7d0d616,h2=0xef516461d5bcd972   first16=00000000000000000000000000000000

Total bits set:314/4096(7%) First 32 bytes:0000000000000000000000000000000000000000000000000000000000000000 Last 32 bytes:0000000000000000000000000000000000000000000000000000000000000000

### 🤖 助手 - 第 108 轮 [14:36:16]

Only 7% bits set and lots of zeros! This looks like it could be a QR code or sparse bitmap. Let me render it:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`Pillow`

 复制代码 隐藏代码

from PIL import Image

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

64×64 bitmap, 1 bit per pixel = 512 bytes

Try different bit orderings

for name, getpixel in [     (“MSBfirst”, lambda y,x: (data[y8 + x//8] >> (7 – x%8)) & 1),     (“LSBfirst”, lambda y,x: (data[y8 + x//8] >> (x%8)) & 1),     (“MSBinv”, lambda y,x: 1 – ((data[y8 + x//8] >> (7 – x%8)) & 1)),     (“LSBinv”, lambda y,x: 1 – ((data[y8 + x//8] >> (x%8)) & 1)), ]:     img = Image.new(‘1’, (64, 64))     for y inrange(64):         for x inrange(64):             img.putpixel((x, y), getpixel(y, x))     fn = rf”D:\AI\AICTF\workdir\52pojie\chu8\qr{name}.png”     imgscaled = img.resize((640, 640), Image.NEAREST)     img_scaled.save(fn)

Print ASCII art for MSB_inv (white=space, black=█)

print(“MSB_inv (inverted, likely QR):”) for y inrange(64):     row = “”     for x inrange(64):         bit = (data[y*8 + x//8] >> (7 – x%8)) & 1         row += ” “if bit else”█”     print(row)

<details>
<summary>📋 Python 输出 [14:36:18](较长,点击展开)</summary>

 复制代码 隐藏代码 MSB_inv (inverted, likely QR): ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ██     █ ██████   ███   ████  ██ ███ ███   █ ███ █ ███ ██   ████ ██ █████ █████ ███ █ ███ ███ ███ ███ ████ ██  █  █ ███ █ ███ ███ ██    ██ █████ ███ █ ███████ ███     ████ ██ █ █ █ ███ █ ███ ███ ██ █████ █████     █ █   ██ ████ ███ ████ ██ ███ █ █ █ █     ███ ██ █████ █████ ███ █ ███ ███ ███ ███ ████ ██ ███ █ █ █ █ ███ ███ ██ █████ █████ ███ █ ███ ███ ███ ███ █ ██ ██ ███ █  █  █ ███ ███ ██ █████     █ ███ ██   ████  ██ ███ ██  ███ ███ █ ███ █ ███ ███ ████████████████████████████████████████████████████████████████ ██    ████   ██   ███   ███   ███   ██ ███ █    ██ █████    ████ ██ ███ ████ ██ ███ █ ███ █ ███ █ █████  ██ █ ███ █ █████ ███ ███ ██ ███ ████ ██████ █ ██  █████ █ █████ █ █ █ ███ █ █████ ███ ███ ██    █████ █████ ██ █ █ ████ ██    ██ ██  █    ██ █████ ███ ███ ██ ████████ ████ ███  ██ ███ ███ ███ █ ███ █ ███ █ █████ ███ ███ ██ █████ ██ ███ ████ ███ ██ ████ ███ █ ███ █ ███ █ █████ ███ ███ ██ ██████  ███     ██   ██     ██   ██ ███ █    ██     █    ████ ████████████████████████████████████████████████████████████████ ██████████████████████████████  ████████████████████████████████ ███████████████████████████████ ████████████████████████████████ ███████████████████████████████ ████████████████████████████████ ████████████████████████████████ ███████████████████████████████ ███████████████████████████████ ████████████████████████████████ ███████████████████████████████ ████████████████████████████████ ██████████████████████████████  ████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████████████

</details>

### 🤖 助手 - 第 109 轮 [14:36:37]

I can see a QR code pattern in rows 20-35! But it's only partially decrypted - the rest is all 1s (encrypted data still). The issue is that only the first chunk (256 bytes) decrypted correctly, but the second chunk didn't. Wait - 512 bytes total, 2 chunks. Let me check chunk 1:

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

The bitmap shows QR-like patterns in rows 20-35 (bytes 160-280 roughly)

But rows 0-19 and 36-63 are all white (all 1s when inverted = all 0s in data)

This means the data IS mostly zeros with a QR code in the middle.

But the QR code seems incomplete/corrupted.

Wait – looking more carefully, the QR code IS there but it’s only in the middle portion.

The rest being all zeros is correct – it’s a 64×64 image with a QR code centered in it.

The QR code appears to be roughly 44×16 pixels, which is unusual.

Actually, looking at the pattern more carefully:

Rows 20-35 have the QR-like pattern (16 rows)

Rows 36-42 have a few scattered bits

This looks like a valid QR code that’s been placed in the center of a 64×64 canvas

Let me try to scan it

import struct withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

Count non-zero bytes per row

for y inrange(64):     rowdata = data[y8:(y+1)8]     ifany(b != 0for b in rowdata):         bits = ”.join(str((row_data[x//8] >> (7-x%8)) & 1) for x inrange(64))         print(f”Row {y:2d}: {bits}”)

**📋 Python 输出**&nbsp;[14:36:37]

 复制代码 隐藏代码 Row 20:0011111010000001110001110000110010001000111010001010001001110000 Row 21:0010000010000010001010001000100010001000010011011010001010001000 Row 22:0011110010000010001010000000100011111000010010101010001010001000 Row 23:0010000010000011111010111001000010001000010010001010101011111000 Row 24:0010000010000010001010001000100010001000010010001010101010001000 Row 25:0010000010000010001010001000100010001010010010001011011010001000 Row 26:0010000011111010001001110000110010001001100010001010001010001000 Row 28:0011110000111001110001110001110001110010001011110010000011110000 Row 29:0010001000010010001010001010001010000011001010001010000010001000 Row 30:0010001000010000001010011000001010000010101010001010000010001000 Row 31:0011110000010000010010101000010011110010011011110010000010001000 Row 32:0010000000010000100011001000100010001010001010001010000010001000 Row 33:0010000010010001000010001001000010001010001010001010000010001000 Row 34:0010000001100011111001110011111001110010001011110011111011110000 Row 36:0000000000000000000000000000001100000000000000000000000000000000 Row 37:0000000000000000000000000000000100000000000000000000000000000000 Row 38:0000000000000000000000000000000100000000000000000000000000000000 Row 39:0000000000000000000000000000000010000000000000000000000000000000 Row 40:0000000000000000000000000000000100000000000000000000000000000000 Row 41:0000000000000000000000000000000100000000000000000000000000000000 Row 42:0000000000000000000000000000001100000000000000000000000000000000

### 🤖 助手 - 第 110 轮 [14:36:55]

This is NOT a QR code - it looks like ASCII art rendered as a bitmap! The patterns in rows 20-34 look like text characters. Let me try to interpret them:

#### 🐍 执行 Python 脚本

**依赖:**&nbsp;`Pillow`

 复制代码 隐藏代码

The bitmap has text rendered in it. Let me try to read it.

Each character appears to be about 8 pixels wide and 7 pixels tall (rows 20-26, 28-34)

There’s a gap at row 27 (separator between two lines of text)

Let me extract the two text blocks and try OCR or visual analysis

from PIL import Image

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

Create a clean image of just the text area, scaled up

img = Image.new(‘L’, (64, 64), 255) for y inrange(64):     for x inrange(64):         bit = (data[y*8 + x//8] >> (7 – x%8)) & 1         if bit:             img.putpixel((x, y), 0)  # black for 1-bits

Scale up 10x for readability

imgbig = img.resize((640, 640), Image.NEAREST) imgbig.save(r”D:\AI\AICTF\workdir\52pojie\chu8\text_bitmap.png”)

Crop just the text area (rows 20-34, all 64 columns)

imgtext = img.crop((0, 20, 64, 35)) imgtextbig = imgtext.resize((640, 150), Image.NEAREST) imgtextbig.save(r”D:\AI\AICTF\workdir\52pojie\chu8\text_cropped.png”)

Also the vertical line in rows 36-42

print(“Vertical line area (rows 36-42):”) for y inrange(36, 43):     rowdata = data[y8:(y+1)8]     bits = ”.join(‘█’if (rowdata[x//8] >> (7-x%8)) & 1else’ ‘for x inrange(64))     print(f”  {bits}”)

print(“\nText area rows 20-26 (line 1):”) for y inrange(20, 27):     rowdata = data[y8:(y+1)8]     bits = ”.join(‘█’if (rowdata[x//8] >> (7-x%8)) & 1else’ ‘for x inrange(64))     print(f”  {bits}”)

print(“\nText area rows 28-34 (line 2):”) for y inrange(28, 35):     rowdata = data[y8:(y+1)8]     bits = ”.join(‘█’if (rowdata[x//8] >> (7-x%8)) & 1else’ ‘for x inrange(64))     print(f”  {bits}”)

**📋 Python 输出**&nbsp;[14:36:57]

 复制代码 隐藏代码 Vertical line area (rows 36-42):                                 ██                                  █                                  █                                   █                                  █                                  █                                 ██

Textarea rows 20-26 (line 1):     █████ █      ███   ███    ██  █   █   ███ █   █ █   █  ███     █     █     █   █ █   █   █   █   █    █  ██ ██ █   █ █   █     ████  █     █   █ █       █   █████    █  █ █ █ █   █ █   █     █     █     █████ █ ███  █    █   █    █  █   █ █ █ █ █████     █     █     █   █ █   █   █   █   █    █  █   █ █ █ █ █   █     █     █     █   █ █   █   █   █   █ █  █  █   █ ██ ██ █   █     █     █████ █   █  ███    ██  █   █  ██   █   █ █   █ █   █

Textarea rows 28-34 (line 2):     ████    ███  ███   ███   ███   ███  █   █ ████  █     ████     █   █    █  █   █ █   █ █   █ █     ██  █ █   █ █     █   █     █   █    █      █ █  ██     █ █     █ █ █ █   █ █     █   █     ████     █     █  █ █ █    █  ████  █  ██ ████  █     █   █     █        █    █   ██  █   █   █   █ █   █ █   █ █     █   █     █     █  █   █    █   █  █    █   █ █   █ █   █ █     █   █     █      ██   █████  ███  █████  ███  █   █ ████  █████ ████

### 🤖 助手 - 第 111 轮 [14:37:09]

I can read the text! It's rendered in a pixel font:

Line 1:&nbsp;`FLAG{HIMAJIMWA`
Wait, let me read more carefully...

Line 1:&nbsp;`FLAG{HIMAJIMWA`&nbsp;→ Actually: `FLAG HIMU

### 👤 用户 [14:37:31]

继续

### 👤 用户 [14:37:31]

[System — Mandatory Planning Phase]
Before executing ANY tools, you MUST first create a structured solving plan:

Step 1: Read the challenge description carefully — identify category, key constraints, and attack surface
Step 2: If attachments exist, download and examine them FIRST (file type, checksec, strings) before planning
Step 3: Based on gathered info, use&nbsp;`todolist`&nbsp;(action: reset) to create 3-5 concrete, ORDERED solving steps
Step 4: Rank steps from most-likely-to-succeed to least-likely
Step 5: Only THEN begin executing the first step

🧠 Planning Quality Checklist:

* Each step should be a TESTABLE hypothesis (e.g., 'Test for SQL injection on /login param'), not vague (e.g., 'Try web attacks')
* Include WHAT tool to use and WHAT to look for in each step
* First step should always be information gathering (read source, analyze binary, fetch target)
* Have at least one backup approach from a different attack angle

Previous solving ideas for this challenge:
✅ [386ab2cd-ecc0-40dd-8909-86213a0e3f3c] Extract and analyze APK file structure to identify obfuscation, encryption, or anti-analysis techniq → APK structure extraction complete with obfuscation and anti-analysis patterns confirmed
✅ [227240ce-0eae-4068-a78a-85084825d9ae] Decompile APK using apktool or jadx to examine source code and identify vulnerability vectors → jadx decompilation successful, vulnerability vectors in JNI calls identified
🔄 [d68e4418-0810-414c-9b85-9626c3306d73] Analyze NativeBridge native method calls to identify JNI exploitation vectors or native code vulnera → verifyAndDecrypt at 0x249a0 disassembled; beatMap timing correlation validation ongoing
🔄 [3feefef8-9e1e-422e-9b9b-7b60e43a4f5d] Examine MainActivity imports and obfuscated class references (f1.h, Q0.AbstractC0080d, etc.) to map &nbsp;→ Obfuscated class mapping progressing; verifyAndDecrypt context extends analysis
🔄 [d112ede0-8135-40cb-a480-c00601aaa18b] Reverse engineer NativeBridge JNI calls to locate native library (.so) and analyze for buffer overfl → Buffer overflow vectors identified; race condition window timing requires validation
🔄 [9f0076f9-2570-4ff7-8ada-419bd017c95c] Disassemble JNI\_OnLoad at 0x237c0 to identify native library loading mechanism and potential ROP gad → JNI\_OnLoad disassembly obtained; verifyAndDecrypt analysis extends native library context
🔄 [642a6e58-cb27-4ee4-b907-936b98b5807f] Analyze renamed functions (startSessionBytes, checkRhyt...) for cryptographic or session management &nbsp;→ Crypto/session functions mapped; sub\_2E680 cross-reference with beatMap intervals requires validation
🔄 [7b22170f-d1cc-4d97-b9e9-d4b0021b7665] Analyze setDebugBypass at 0x24ca0 to determine if byte\_5D140 flag can be exploited to disable runtim → setDebugBypass flag enforcement identified; beatMap timing correlation analysis ongoing
🔄 [61c6ba93-bdd1-4e3d-a8bc-12024b29ec18] Analyze call sites of sub\_2EFD0 (0x2EFD0) to determine buffer size validation and identify potential → hjm\_pack.bin decryption flow analysis ongoing; sub\_2E680 buffer processing correlation in progress
❌ [67eacfbe-fb16-4383-820c-402005c0d570] Trace Q0.r and Q0.y instantiation paths to identify entry points for NativeBridge method injection o → Q0.r/Q0.y instantiation search returned 0 matches; likely dynamically generated or requires binary instrumentation approach
❌ [e0e7a83d-f515-4101-bd5b-754a009b9eb8] Analyze InterfaceC0603z implementation in Q0.r and Q0.y to determine if interface methods can be hoo → InterfaceC0603z search returned 0 matches; interface appears dynamically generated or obfuscated beyond static analysis
🔄 [77e77496-7d3d-4e2b-b182-f935e38d202c] Exploit beatMap timing intervals (250ms) as race condition window to inject code during NativeBridge → beatMap 250ms intervals confirmed; native code instrumentation needed to validate race condition
🔄 [84069d04-ee99-441a-ab12-33357e0432bd] Disassemble sub\_2E680 (0x2E680) buffer operations to correlate with beatMap 250ms intervals and iden → sub\_2E680 disassembly obtained; buffer operation correlation with beatMap 250ms intervals in progress
🔄 [282bf5e8-8b34-4a55-8825-24710a0bed13] Instrument setDebugBypass flag enforcement at 0x24ca0 to measure timing correlation with beatMap int → setDebugBypass flag at 0x24ca0 identified; SipHash emulation validated; timing instrumentation context extended

📊 Stats: 0 pending, 10 testing, 2 failed, 2 verified, 0 skipped (active: 10/10)

⚠️ Review these ideas carefully:

* Skip approaches marked as 'failed' — they already proved unsuccessful
* Prioritize 'pending' ideas — they haven't been tried yet
* Build your plan around untried approaches

Do NOT skip planning. A structured approach prevents wasting rounds on random attempts.

### 🤖 助手 - 第 112 轮 [14:37:53]

I can clearly read the bitmap text! Let me carefully decode it:

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

Let me carefully read each character from the bitmap

Line 1 (rows 20-26): Each char is ~5-6 pixels wide with 1px spacing

Let me extract columns for each character

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

Print line 1 with column numbers

print(“Line 1 (rows 20-26):”) print(“Col: “, end=””) for x inrange(64):     print(x % 10, end=””) print() for y inrange(20, 27):     rowdata = data[y8:(y+1)8]     print(f”R{y}: “, end=””)     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         print(‘█’if bit else’ ‘, end=””)     print()

print(“\nLine 2 (rows 28-34):”) print(“Col: “, end=””) for x inrange(64):     print(x % 10, end=””) print() for y inrange(28, 35):     rowdata = data[y8:(y+1)8]     print(f”R{y}: “, end=””)     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         print(‘█’if bit else’ ‘, end=””)     print()

**📋 Python 输出**&nbsp;[14:37:53]

 复制代码 隐藏代码 Line 1 (rows 20-26): Col: 0123456789012345678901234567890123456789012345678901234567890123 R20:   █████ █      ███   ███    ██  █   █   ███ █   █ █   █  ███ R21:   █     █     █   █ █   █   █   █   █    █  ██ ██ █   █ █   █ R22:   ████  █     █   █ █       █   █████    █  █ █ █ █   █ █   █ R23:   █     █     █████ █ ███  █    █   █    █  █   █ █ █ █ █████ R24:   █     █     █   █ █   █   █   █   █    █  █   █ █ █ █ █   █ R25:   █     █     █   █ █   █   █   █   █ █  █  █   █ ██ ██ █   █ R26:   █     █████ █   █  ███    ██  █   █  ██   █   █ █   █ █   █

Line 2 (rows 28-34): Col: 0123456789012345678901234567890123456789012345678901234567890123 R28:   ████    ███  ███   ███   ███   ███  █   █ ████  █     ████ R29:   █   █    █  █   █ █   █ █   █ █     ██  █ █   █ █     █   █ R30:   █   █    █      █ █  ██     █ █     █ █ █ █   █ █     █   █ R31:   ████     █     █  █ █ █    █  ████  █  ██ ████  █     █   █ R32:   █        █    █   ██  █   █   █   █ █   █ █   █ █     █   █ R33:   █     █  █   █    █   █  █    █   █ █   █ █   █ █     █   █ R34:   █      ██   █████  ███  █████  ███  █   █ ████  █████ ████

### 👤 用户 [14:37:57]

<historical\_experience>
⚠️ 严禁将以下案例中的具体值(XOR key、checksum、flag 字符串、密码、偏移量)直接用于当前题目。仅用于识别技术方向和分析思路。
以下是从历史解题中提取的技术模式,仅供思路启发,必须结合当前题目独立分析,不要照搬历史解法:

#### [cases] chu8: Android game with native HJM1 bitmap decryption via XXHash64+SipHash+AES-CTR (relevance: 0.56)

Problem: Decrypt a 64x64 bitmap from hjm\_pack.bin (HJM1 format) in Android game. Key insight: beatMap data (rhythm notes) is the only input needed; key derivation is deterministic and doesn't depend on runtime state. Solution: (1) extract beatMap=[0,250,500,750] from Java code, (2) implement XXHash64+SipHash-2-4+AES-CTR pipeline, (3) parse HJM1 file format to extract salt/IV/ciphertext, (4) decrypt to get bitmap, (5) render as QR code or image to find flag. Pitfall: initially tried to trace complex runtime state (startSession, checkRhythm, updateExp) but these are red herrings; the debug bypass path (sub\_2E570) shows key generation is purely mathematical.

---

#### [cases] chu7: UPX-packed PE32+ with CRC64 key derivation and custom stream cipher using AES S-box (relevance: 0.47)

Windows GUI program CM1.exe encrypted flag.png using a custom algorithm. Key insight: UPX packing concealed real code; after unpacking, found hardcoded key prefix '52pojie*2026*' combined with user password to derive CRC64 hash (0xC96C5795D7870F42 polynomial), which initializes a stream cipher using AES S-box for keystream generation. PNG magic bytes (89504e47...) serve as known plaintext to recover stream cipher key 0x55a4f867ba4475dd via XOR. Solution: Unpack binary, extract CRC64 polynomial and S-box at 0x14000A270, implement stream cipher logic (ROL3 + S-box substitution + XOR with IV), use known plaintext attack to recover CRC64 state, decrypt file, verify CRC32 checksum, and extract flag from PNG tEXt chunk metadata.

---

#### [cases] 初二题目 PE32 XOR password recovery (relevance: 0.41)

Windows PE32 reverse challenge where hardcoded DWORDs were XORed to recover password. Binary had Chinese filename causing path issues. Solution extracted 6 DWORD values from binary and XORed them sequentially.

---

#### [cases] Windows PE32 crackme with XOR-obfuscated password and weighted checksum validation (relevance: 0.41)

52pojie Spring Festival CTF 'Day 2' challenge: PE32 console app with fake flag hint, 31-char password requirement, XOR-deobfuscation (key 0x42), and weighted checksum (sum of position\*value = 44709). Key insight: ignore fake flag, reverse sub\_401620's DWORD buffer XOR logic.

---

#### [cases] reverse1: Simple character replacement validation in PE x64 binary (relevance: 0.40)

Challenge: PE x64 Windows executable that validates user input against a hardcoded flag. Key insight: The flag string is stored in the binary's data section and can be extracted directly via IDA Pro. The validation logic replaces all 'o' characters with '0' before comparison. Solution: Use IDA Pro headless to decompile main function, identify the global string variable (Str2), extract its value, apply the character replacement transformation, and wrap with flag{} format.

</historical\_experience>

### 👤 用户 [14:38:18]

已经出了正确答案是:FLAG{HJMWAPJ2026NBLD} 启动验证流程

### 🤖 助手 - 第 113 轮 [14:38:24]

Now I can clearly read the text from the bitmap:

Line 1:&nbsp;`FLAG{HIMAJIMWA`&nbsp;→ Let me read more carefully...

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

Let me carefully identify each character by column ranges

Line 1 characters (cols 2-57 approximately):

Looking at the pixel patterns:

Cols 2-6: F (█████, █, ████, █, █, █, █)

Cols 8-12: L (█, █, █, █, █, █, █████)

Cols 14-18: A (███, █   █, █   █, █████, █   █, █   █, █   █) -> wait

Actually let me look at it differently

Col 2-6:

R20: █████  -> F

R21: █

R22: ████

R23: █

R24: █

R25: █

R26: █

Col 8-12:

R20: █      -> L

R21: █

R22: █

R23: █

R24: █

R25: █

R26: █████

Col 14-18:

R20:  ███   -> A

R21: █   █

R22: █   █

R23: █████

R24: █   █

R25: █   █

R26: █   █

Col 20-24:

R20:  ███   -> G

R21: █   █

R22: █

R23: █ ███

R24: █   █

R25: █   █

R26:  ███

Col 26-29:

R20:  ██    -> {

R21:  █

R22:  █

R23: █

R24:  █

R25:  █

R26:  ██

Col 31-35:

R20: █   █  -> H

R21: █   █

R22: █████

R23: █   █

R24: █   █

R25: █   █

R26: █   █

Col 37-39:

R20:  ███   -> I? No…

R21:  █

R22:  █

R23:  █

R24:  █

R25: █  █   -> wait, that’s J? No…

R26:  ██

Hmm, let me look at this more carefully with exact column positions

for y inrange(20, 27):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

print() for y inrange(28, 35):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

**📋 Python 输出**&nbsp;[14:38:24]

 复制代码 隐藏代码 Traceback (most recent call last):   File “C:\Users\WOYOUY~1\AppData\Local\Temp\ctfagent2198965434.py”, line 78, in      row_data = data[y8:(y+1)8]                ^^^^ NameError: name ‘data’ is not defined

[error: exit status 1]

### 🤖 助手 - 第 114 轮 [14:38:45]

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

print(“Line 1:”) for y inrange(20, 27):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

print(“\nLine 2:”) for y inrange(28, 35):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

Now let me try OCR on the scaled image

**📋 Python 输出**&nbsp;[14:38:45]

 复制代码 隐藏代码 Line 1: R20:··█████·█······███···███····██··█···█···███·█···█·█···█··███···· R21:··█·····█·····█···█·█···█···█···█···█····█··██·██·█···█·█···█··· R22:··████··█·····█···█·█·······█···█████····█··█·█·█·█···█·█···█··· R23:··█·····█·····█████·█·███··█····█···█····█··█···█·█·█·█·█████··· R24:··█·····█·····█···█·█···█···█···█···█····█··█···█·█·█·█·█···█··· R25:··█·····█·····█···█·█···█···█···█···█·█··█··█···█·██·██·█···█··· R26:··█·····█████·█···█··███····██··█···█··██···█···█·█···█·█···█···

Line 2: R28:··████····███··███···███···███···███··█···█·████··█·····████···· R29:··█···█····█··█···█·█···█·█···█·█·····██··█·█···█·█·····█···█··· R30:··█···█····█······█·█··██·····█·█·····█·█·█·█···█·█·····█···█··· R31:··████·····█·····█··█·█·█····█··████··█··██·████··█·····█···█··· R32:··█········█····█···██··█···█···█···█·█···█·█···█·█·····█···█··· R33:··█·····█··█···█····█···█··█····█···█·█···█·█···█·█·····█···█··· R34:··█······██···█████··███··█████··███··█···█·████··█████·████····

### 🤖 助手 - 第 115 轮 [14:38:58]

Now let me carefully read each character:

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

Line 1 character analysis (7 rows high, reading column by column):

Cols 2-6: F L A G { H I M W A  (reading the pixel font)

Let me segment by looking for blank columns (all dots)

line1rows = [] line2rows = [] withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

for y inrange(20, 27):     row = []     for x inrange(64):         bit = (data[y*8 + x//8] >> (7 – x%8)) & 1         row.append(bit)     line1_rows.append(row)

for y inrange(28, 35):     row = []     for x inrange(64):         bit = (data[y*8 + x//8] >> (7 – x%8)) & 1         row.append(bit)     line2_rows.append(row)

Find character boundaries by looking for blank columns

deffindchars(rows):     chars = []     inchar = False     start = 0     for x inrange(64):         colhaspixel = any(rows[y][x] for y inrange(len(rows)))         if colhaspixel andnot inchar:             start = x             inchar = True         elifnot colhaspixel and inchar:             chars.append((start, x))             inchar = False     if in_char:         chars.append((start, 64))     return chars

chars1 = findchars(line1rows) chars2 = findchars(line2rows)

print(“Line 1 characters:”) for i, (s, e) inenumerate(chars1):     print(f”  Char {i}: cols {s}-{e} (width {e-s})”)     for y inrange(7):         row = ”.join(‘█’if line1_rows[y][x] else’·’for x inrange(s, e))         print(f”    {row}”)     print()

print(“\nLine 2 characters:”) for i, (s, e) inenumerate(chars2):     print(f”  Char {i}: cols {s}-{e} (width {e-s})”)     for y inrange(7):         row = ”.join(‘█’if line2_rows[y][x] else’·’for x inrange(s, e))         print(f”    {row}”)     print()

<details>
<summary>📋 Python 输出 [14:38:58](较长,点击展开)</summary>

 复制代码 隐藏代码 Line 1 characters:   Char 0:cols2-7(width5)     █████     █····     ████·     █····     █····     █····     █····

Char 1:cols8-13(width5)     █····     █····     █····     █····     █····     █····     █████

Char 2:cols14-19(width5)     ·███·     █···█     █···█     █████     █···█     █···█     █···█

Char 3:cols20-25(width5)     ·███·     █···█     █····     █·███     █···█     █···█     ·███·

Char 4:cols27-30(width3)     ·██     ·█·     ·█·     █··     ·█·     ·█·     ·██

Char 5:cols32-37(width5)     █···█     █···█     █████     █···█     █···█     █···█     █···█

Char 6:cols38-43(width5)     ··███     ···█·     ···█·     ···█·     ···█·     █··█·     ·██··

Char 7:cols44-49(width5)     █···█     ██·██     █·█·█     █···█     █···█     █···█     █···█

Char 8:cols50-55(width5)     █···█     █···█     █···█     █·█·█     █·█·█     ██·██     █···█

Char 9:cols56-61(width5)     ·███·     █···█     █···█     █████     █···█     █···█     █···█

Line 2 characters: Char 0:cols2-7(width5)     ████·     █···█     █···█     ████·     █····     █····     █····

Char 1:cols8-13(width5)     ··███     ···█·     ···█·     ···█·     ···█·     █··█·     ·██··

Char 2:cols14-19(width5)     ·███·     █···█     ····█     ···█·     ··█··     ·█···     █████

Char 3:cols20-25(width5)     ·███·     █···█     █··██     █·█·█     ██··█     █···█     ·███·

Char 4:cols26-31(width5)     ·███·     █···█     ····█     ···█·     ··█··     ·█···     █████

Char 5:cols32-37(width5)     ·███·     █····     █····     ████·     █···█     █···█     ·███·

Char 6:cols38-43(width5)     █···█     ██··█     █·█·█     █··██     █···█     █···█     █···█

Char 7:cols44-49(width5)     ████·     █···█     █···█     ████·     █···█     █···█     ████·

Char 8:cols50-55(width5)     █····     █····     █····     █····     █····     █····     █████

Char 9:cols56-61(width5)     ████·     █···█     █···█     █···█     █···█     █···█     ████·

</details>

### 🤖 助手 - 第 116 轮 [14:39:18]

Now I can read each character clearly:

Line 1: F, L, A, G, {, H, J, M, W, A
Line 2: P, J, 2, 0 (wait - that's a special 0 with a line through it... no, it's just the font), 2, 6 (wait...), N, B, L, D

Wait, let me re-read Line 2 more carefully:

* Char 0: P (████, █···█, █···█, ████, █, █, █)
* Char 1: J (··███, ···█, ···█, ···█, ···█, █··█, ·██)
* Char 2: 2 (·███, █···█, ····█, ···█, ··█, ·█, █████)
* Char 3: 0 (·███, █···█, █··██, █·█·█, ██··█, █···█, ·███) - this is 0 with a slash
* Char 4: 2 (same pattern as char 2)
* Char 5: 6 (·███, █, █, ████, █···█, █···█, ·███)
* Char 6: N (█···█, ██··█, █·█·█, █··██, █···█, █···█, █···█)
* Char 7: B (████, █···█, █···█, ████, █···█, █···█, ████)
* Char 8: L (█, █, █, █, █, █, █████)
* Char 9: D (████, █···█, █···█, █···█, █···█, █···█, ████)

So the full text is:&nbsp;`FLAG{HJMWAPJ2026NBLD}`

Wait - but char 3 in line 2 looks like it could be 0 or G. Let me check: ·███·, █···█, █··██, █·█·█, ██··█, █···█, ·███· - this has diagonal pixels inside, which is characteristic of a slashed zero (0) or the number 0 in some fonts. But actually looking at it again: row 3 is █··██, row 4 is █·█·█, row 5 is ██··█ - this is a 0 with a diagonal slash through it, confirming it's the digit 0.

So:&nbsp;`FLAG{HJMWAPJ2026NBLD}`

But the hint says the answer is&nbsp;`FLAG{HJMWAPJ2026NBLD}`. Let me verify:

#### 🐍 执行 Python 脚本

 复制代码 隐藏代码

Let me also check if there’s a closing brace }

The vertical line in rows 36-42 might be the closing brace

withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

print(“Rows 36-42 (potential closing brace):”) for y inrange(36, 43):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(28, 36):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

That vertical pattern at cols 30-31:

R36: ██

R37: ·█

R38: ·█

R39: ··█ (shifted right = this is })

R40: ·█

R41: ·█

R42: ██

This is indeed a closing brace }

print(“\nFull flag: FLAG{HJMWAPJ2026NBLD}”)

**📋 Python 输出**&nbsp;[14:39:18]

 复制代码 隐藏代码 Rows 36-42 (potential closing brace): R36:··██···· R37:···█···· R38:···█···· R39:····█··· R40:···█···· R41:···█···· R42:··██····

Full flag:FLAG{HJMWAPJ2026NBLD}

### 👤 用户 [14:39:18]

[System — Flag Detection]
在工具输出中检测到可能的 flag:
FLAG{HJMWAPJ2026NBLD}

请立即验证并使用对应的提交工具(flag\_submit / ctfd\_submit\_flag / gzctf\_submit\_flag)提交。不要继续其他操作,先提交 flag!

### 🤖 助手 - 第 117 轮 [14:39:36]

The flag is clearly derived from the decrypted bitmap - each character was rendered in a pixel font and I can trace every letter. Let me submit it:

#### 🔨 工具调用:&nbsp;`flag_submit`

* **flag:**

  `FLAG{HJMWAPJ2026NBLD}`

**📋&nbsp;`flag_submit`&nbsp;输出**&nbsp;[14:39:36]

 复制代码 隐藏代码 已捕获 flag,已在界面弹窗中提供复制与人工确认入口(正确/错误)。

### 👤 用户 [14:39:37]

[System] 用户已确认 flag 正确: FLAG{HJMWAPJ2026NBLD}

---

> 导出时间:2026-03-06 21:54:11 | 共 294 条消息,117 轮对话
> [/quote]

### 完整WP

---

### title: [52pojie] chu8 category: reverse tags: [reverse, rev, APK, JNI, IDA, Unicorn, AES-CTR, SipHash]

## 初8 — Android 原生库逆向与 HJM1 位图解密

### 题目概述

本题为逆向工程(Reverse)类题目,核心考点是 Android APK 中 JNI 原生库的密码学逆向分析。需要逆向&nbsp;`libhajimi.so`&nbsp;中的多层密钥派生链(XXHash64 → SipHash-2-4 → splitmix64 → AES-CTR),解密自定义 HJM1 格式的加密位图文件,最终从 64×64 像素字体渲染的位图中读取 flag。难度中高,涉及多种哈希算法和对称加密的组合逆向。

### 初始分析

#### APK 结构探索

题目提供一个 Android APK 文件,解压后发现关键组件:

* `lib/x86_64/libhajimi.so`

  :核心原生库,包含所有加解密逻辑
* `assets/hjm_pack.bin`

  :HJM1 格式的加密位图文件(待解密目标)
* Java 层代码经过混淆(`f1.h`、`Q0.AbstractC0080d`&nbsp;等),但通过 jadx 反编译可识别关键 JNI 调用

#### 关键发现

1. Java 层的&nbsp;`NativeBridge`&nbsp;类暴露了多个 JNI 方法:`startSession`、`checkRhythm`、`updateExp`、`verifyAndDecrypt`
2. `beatMap`

   数据硬编码为&nbsp;`[0, 250, 500, 750]`,`interval = 250`——这是一个节奏游戏的节拍数据
3. `hjm_pack.bin`

   文件头为&nbsp;`HJM1`(`0x314D4A48`),后跟 salt(16字节)、IV(12字节)和 512 字节密文
4. 原生库中存在一条 debug bypass 路径(`sub_2E570`),密钥生成完全基于数学运算,不依赖运行时状态

#### 漏洞/算法类型判断

通过 IDA 反汇编&nbsp;`verifyAndDecrypt`(0x24850)发现:

* 地址 0x24b8b 处检查&nbsp;`byte_5D140`(debugBypass 标志)
* 若 debug 模式开启,调用&nbsp;`sub_2E570`&nbsp;计算密钥,否则使用游戏过程中积累的&nbsp;`qword_5CFF0`
* 密钥经过可选的&nbsp;`0xA5A5A5A5A5A5A5A5`&nbsp;XOR 变换后传入&nbsp;`sub_2E680`&nbsp;进行 AES-CTR 解密

### 解题思路

#### 整体思路形成

解题的核心在于:**beatMap 是唯一的外部输入,整个密钥派生链是确定性的**。

最初尝试追踪复杂的运行时状态(`startSession`、`checkRhythm`、`updateExp`),但这些是红鲱鱼。关键突破点是发现 debug bypass 路径&nbsp;`sub_2E570`,它表明密钥生成是纯数学计算,只需要&nbsp;`qword_5CFE8`(beatMap 的 XXHash64 哈希值)。

#### 尝试过的方向

1. **纯 Python 重写所有算法**

   :先验证 SipHash-2-4 和 XXHash64 的正确性(通过标准测试向量),再实现完整派生链。初次解密结果为随机数据(51% 比特置位),说明某个环节有误。
2. **多种密钥变体尝试**

   :测试了 debug\_key raw、debug\_key ^ 0xA5、qfe8 raw、qfe8 ^ 0xA5 四种组合,均未得到有意义输出。
3. **Unicorn 模拟执行**

   :由于纯 Python 实现可能在细节上出错,转而使用 Unicorn 引擎直接模拟原生库函数。遇到 SSE4.1 指令(`pinsrb`)不支持的问题,但成功模拟了不含 SSE4.1 的子函数。
4. **最终成功路径**

   :用 Unicorn 模拟&nbsp;`sub_2EB00`(密钥派生)和&nbsp;`sub_24CB0`(XXHash64 折叠),结合 PyCryptodome 的 AES-CTR 完成解密。

#### 关键转折

纯 Python 实现失败的根本原因是&nbsp;`sub_2EB00`&nbsp;中 SipHash 的密钥构造细节——XOR 掩码&nbsp;`0x1A8CBC5B802E097C`&nbsp;和&nbsp;`0x802E097C1A8CBC5B`&nbsp;的应用顺序在反汇编中容易看错。通过 Unicorn 直接执行原生代码绕过了这个问题。

### 详细步骤

#### 第一阶段:提取 beatMap 与计算基础哈希

从 Java 反编译代码中提取 beatMap 数据&nbsp;`[0, 250, 500, 750]`,使用 XXHash64 计算其哈希值作为后续密钥派生的种子:

 复制代码 隐藏代码 import struct, xxhash

beatmap = struct.pack(‘<4I’, 0, 250, 500, 750) qfe8 = xxhash.xxh64(beatmap, seed=0x1A8CBC5B802E097C).intdigest()

qfe8 = 0x810a3647628f0212

这里&nbsp;`0x1A8CBC5B802E097C`&nbsp;是从&nbsp;`startSession`&nbsp;函数(反汇编地址 0x2E570 附近)中提取的 XOR 常量,用作 XXHash64 的种子。`qword_5CFE8`&nbsp;存储了这个哈希结果,是整个密钥派生链的起点。

#### 第二阶段:debug bypass 密钥计算(sub\_2E570)

`sub_2E570`&nbsp;实现了 debug 模式下的密钥生成逻辑。通过反汇编分析,其流程为:

1. 读取&nbsp;`dword_5CFD0`(节拍数 = 4)和&nbsp;`qword_5CFE8`
2. 计算&nbsp;`r14 = 0x1A8CBC5B802E097C ^ qfe8`
3. 对&nbsp;`r14`&nbsp;执行 splitmix64 变换
4. 循环 4 次(对应 4 个节拍),每次用 SipHash-2-4 混合结果
5. 最终输出 debug\_key

 复制代码 隐藏代码 defsplitmix64(state):     state = u64(state + 0x9E3779B97F4A7C15)     z = state     z = u64((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9)     z = u64((z ^ (z >> 27)) * 0x94D049BB133111EB)     return state, z ^ (z >> 31)

r14 = 0x1A8CBC5B802E097C ^ qfe8

r14 = u64(0x1A8CBC5B802E097C ^ qfe8) state, sm_result = splitmix64(r14)

循环4次 SipHash 混合…

debug_key = 0xd2c13a26769ce7e0

#### 第三阶段:verifyAndDecrypt 中的密钥选择逻辑

反汇编&nbsp;`verifyAndDecrypt`(0x24850)中地址 0x24be1-0x24bf5 的关键代码:

 复制代码 隐藏代码 24be1: mov rsi, 0xA5A5A5A5A5A5A5A5 24beb: xor rsi, rax          ; rsi = debugkey ^ 0xA5… 24bee: test r13b, r13b       ; r13b = 密码验证是否通过 24bf1: cmovz rsi, rax        ; 若密码未验证,不做 XOR 24bf5: call sub2E680         ; 传入最终密钥进行解密

这段代码的含义是:

* 当&nbsp;`r13b = 0`(密码未验证,即&nbsp;`byte_5D00C != 1`),`cmovz`&nbsp;生效,`rsi = rax`(原始 debug\_key,不做 XOR)
* 当&nbsp;`r13b = 1`(密码已验证),保持&nbsp;`rsi = debug_key ^ 0xA5A5A5A5A5A5A5A5`

经过测试,**不做 XOR 的 debug\_key raw 路径**才是正确的解密密钥。这对应 debug bypass 场景下密码未经验证的情况。

#### 第四阶段:解析 HJM1 文件格式

 复制代码 隐藏代码 withopen(“hjm_pack.bin”, “rb”) as f:     hjm = f.read()

HJM1 文件格式:

[0:4]   = magic “HJM1” (0x314D4A48)

[4:8]   = version/flags

[8:16]  = encrypted data size (8 bytes LE)

[16:24] = reserved

[24:40] = salt (16 bytes)

[40:52] = IV/nonce (12 bytes)

[52:]   = encrypted data (512 bytes = 64×64 bitmap, 1 bit/pixel)

salt = hjm[24:40]   # 0f1e2d3c4b5a69788796a5b4c3d2e1f0 iv = hjm[40:52]      # 112233445566778899aabbcc ciphertext = hjm[52:52+512]

#### 第五阶段:Unicorn 模拟 sub\_2EB00 密钥派生

由于纯 Python 实现&nbsp;`sub_2EB00`&nbsp;的 SipHash 密钥构造细节容易出错,改用 Unicorn 引擎直接模拟执行原生代码:

 复制代码 隐藏代码 from unicorn import * from unicorn.x86_const import *

加载 libhajimi.so 到 Unicorn

withopen(“libhajimi.so”, “rb”) as f:     so_data = f.read()

BASE = 0x10000000 mu = Uc(UCARCHX86, UCMODE64)

映射 ELF 段

segs = [     (0x0, 0x0, 0x56570, 0x56570),     (0x57570, 0x56570, 0x4728, 0x4728),     (0x5cca0, 0x5aca0, 0xe8, 0xfc0) ] for va, fo, fs, ms in segs:     s = (BASE + va) & ~0xFFF     e = ((BASE + va + ms) + 0xFFF) & ~0xFFF     mu.memmap(s, e – s)     mu.memwrite(BASE + va, so_data[fo:fo+fs])

设置栈、堆、FS段

STACKBASE = 0x7FFF0000; STACKSIZE = 0x200000 mu.memmap(STACKBASE, STACKSIZE) mu.memmap(0x20000000, 0x200000)  # heap mu.memmap(0x30000000, 0x1000)    # FS base mu.memwrite(0x30000000 + 0x28, struct.pack(‘<Q’, 0xDEADBEEFCAFEBABE)) mu.msr_write(0xC0000100, 0x30000000)

调用 sub2EB00(rdi=debugkey, rsi=qfe8, rdx=outbuf, rcx=outsize)

… 设置参数并执行

`sub_2EB00`&nbsp;的核心逻辑是:

1. 用&nbsp;`0x1A8CBC5B802E097C ^ qfe8`&nbsp;和&nbsp;`0x802E097C1A8CBC5B ^ qfe8`&nbsp;作为 SipHash 的两个密钥
2. 对&nbsp;`[debug_key, qfe8]`&nbsp;这 16 字节数据执行 SipHash-2-4
3. 输出 16 字节的密钥材料&nbsp;`var_68`

模拟执行得到:

 复制代码 隐藏代码 var_68 = 7f721557831c2dbd7f70756f448aee26   q0 = 0xbd2d1c835715727f   q1 = 0x26ee8a446f75707f

#### 第六阶段:AES-CTR 密钥与 Nonce 生成

`sub_2E680`&nbsp;将 512 字节密文分为 2 个 256 字节的 chunk,每个 chunk 独立生成 AES 密钥和 nonce。

对于每个 chunk,构造 32 字节的哈希输入缓冲区:

 复制代码 隐藏代码

src[0:16]  = var68 (来自 sub2EB00 的密钥材料)

src[16:20] = chunk_counter (uint32 LE)

src[20:24] = qfe8 的低 32 位

src[24:32] = qfe8 ^ (chunk_idx * 0x100000000)

然后调用&nbsp;`sub_24CB0`(XXHash64 + 折叠)两次生成 AES-128 密钥的两个 64 位半部分:

 复制代码 隐藏代码 defsub_24CB0(data, seed):     “””XXHash64 with final fold: result ^ (result >> 32)”””     h = xxhash.xxh64(data, seed=seed).intdigest()     return u64(h ^ (h >> 32))

AES key = [sub24CB0(src, 4951), sub24CB0(src, 9320)]

h1 = sub24CB0(srcbuf, 4951)  # 低 8 字节 h2 = sub24CB0(srcbuf, 9320)  # 高 8 字节 aes_key = struct.pack(‘<QQ’, h1, h2)  # 16 字节 AES-128 密钥

同样通过 Unicorn 模拟&nbsp;`sub_24CB0`&nbsp;确保结果正确:

 复制代码 隐藏代码 Chunk 0: key = 16d6d0d72e99938d72d9bcd5616451ef          nonce = 112233440052f26b99aabbcc

Nonce 的构造方式是将文件中的 12 字节 IV 与 chunk 计数器和折叠后的&nbsp;`qfe8`&nbsp;进行 XOR:

 复制代码 隐藏代码 folded_qfe8 = u32(qfe8 ^ (qfe8 >> 32))  # 0xe3853455

nonce[0:4] = IV[0:4] ^ struct.pack(‘<I’, chunk_counter)

nonce[4:8] = IV[4:8] ^ struct.pack(‘<I’, folded_qfe8)

nonce[8:12] = IV8:12

#### 第七阶段:AES-CTR 解密

使用 PyCryptodome 执行 AES-128-CTR 解密:

 复制代码 隐藏代码 from Crypto.Cipher import AES from Crypto.Util import Counter

对每个 256 字节 chunk:

ctr = Counter.new(32, prefix=nonce, initialvalue=0, littleendian=True) cipher = AES.new(aeskey, AES.MODECTR, counter=ctr) decryptedchunk = cipher.decrypt(ciphertextchunk)

解密后得到 512 字节数据,仅 7% 的比特被置位(314/4096),说明这是一个稀疏位图——大部分区域为空白,中间区域包含像素字体渲染的文本。

#### 第八阶段:位图渲染与字符识别

解密后的 512 字节是 64×64 的单色位图(1 bit/pixel,MSB first)。通过逐行渲染发现:

* 行 0-19:全零(空白)
* 行 20-26:第一行文本(7 像素高的字符)
* 行 27:空白分隔行
* 行 28-34:第二行文本
* 行 35:空白
* 行 36-42:闭合花括号&nbsp;`}`
* 行 43-63:全零(空白)

通过列分割算法识别字符边界,逐个字符比对像素字体:

 复制代码 隐藏代码 Line 1 字符识别:   Char 0 (cols 2-7):  F  — █████/█/████/█/█/█/█   Char 1 (cols 8-13):  L  — █/█/█/█/█/█/█████   Char 2 (cols 14-19): A  — ·███·/█···█/█···█/█████/█···█/█···█/█···█   Char 3 (cols 20-25): G  — ·███·/█···█/█/█·███/█···█/█···█/·███·   Char 4 (cols 27-30): {  — ·██/·█/·█/█/·█/·█/·██   Char 5 (cols 32-37): H  — █···█/█···█/█████/█···█/█···█/█···█/█···█   Char 6 (cols 38-43): J  — ··███/···█/···█/···█/···█/█··█/·██   Char 7 (cols 44-49): M  — █···█/██·██/█·█·█/█···█/█···█/█···█/█···█   Char 8 (cols 50-55): W  — █···█/█···█/█···█/█·█·█/█·█·█/██·██/█···█   Char 9 (cols 56-61): A  — ·███·/█···█/█···█/█████/█···█/█···█/█···█

Line 2 字符识别:   Char 0: P  — ████/█···█/█···█/████/█/█/█   Char 1: J  — ··███/···█/···█/···█/···█/█··█/·██   Char 2: 2  — ·███/█···█/····█/···█/··█/·█/█████   Char 3: 0  — ·███/█···█/█··██/█·█·█/██··█/█···█/·███ (斜线零)   Char 4: 2  — 同 Char 2   Char 5: 6  — ·███/█/█/████/█···█/█···█/·███   Char 6: N  — █···█/██··█/█·█·█/█··██/█···█/█···█/█···█   Char 7: B  — ████/█···█/█···█/████/█···█/█···█/████   Char 8: L  — █/█/█/█/█/█/█████   Char 9: D  — ████/█···█/█···█/█···█/█···█/█···█/████

闭合花括号 } (rows 36-42):   ██/·█/·█/··█/·█/·█/██

拼合结果:`FLAG{HJMWAPJ2026NBLD}`

### 关键代码/命令

#### 位图渲染与字符分割

 复制代码 隐藏代码 withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

print(“Line 1:”) for y inrange(20, 27):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

print(“\nLine 2:”) for y inrange(28, 35):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(64):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

#### 字符边界自动检测

 复制代码 隐藏代码 line1rows = [] withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decryptedemu.bin”, “rb”) as f:     data = f.read()

for y inrange(20, 27):     row = []     for x inrange(64):         bit = (data[y*8 + x//8] >> (7 – x%8)) & 1         row.append(bit)     line1_rows.append(row)

deffindchars(rows):     chars = []     inchar = False     start = 0     for x inrange(64):         colhaspixel = any(rows[y][x] for y inrange(len(rows)))         if colhaspixel andnot inchar:             start = x             inchar = True         elifnot colhaspixel and inchar:             chars.append((start, x))             inchar = False     if in_char:         chars.append((start, 64))     return chars

chars1 = findchars(line1rows) for i, (s, e) inenumerate(chars1):     print(f”  Char {i}: cols {s}-{e} (width {e-s})”)     for y inrange(7):         row = ”.join(‘█’if line1_rows[y][x] else’·’for x inrange(s, e))         print(f”    {row}”)

#### 闭合花括号验证

 复制代码 隐藏代码 withopen(r”D:\AI\AICTF\workdir\52pojie\chu8\decrypted_emu.bin”, “rb”) as f:     data = f.read()

print(“Rows 36-42 (potential closing brace):”) for y inrange(36, 43):     rowdata = data[y8:(y+1)8]     s = “”     for x inrange(28, 36):         bit = (rowdata[x//8] >> (7 – x%8)) & 1         s += ‘█’if bit else’·’     print(f”R{y}: {s}”)

输出:

R36: ··██····

R37: ···█····

R38: ···█····

R39: ····█···  ← 向右缩进,确认是 }

R40: ···█····

R41: ···█····

R42: ··██····

### Flag

 复制代码 隐藏代码 FLAG{HJMWAPJ2026NBLD}

### 总结与收获

#### 核心漏洞原理

本题的核心不是传统意义上的"漏洞",而是对多层密码学密钥派生链的完整逆向。密钥派生路径为:

 复制代码 隐藏代码 beatMap [0,250,500,750]   → XXHash64(seed=0x1A8CBC5B802E097C) → qfe8   → splitmix64 + SipHash-2-4 循环 → debugkey   → SipHash-2-4(debugkey, qfe8) → var68 (16字节密钥材料)   → XXHash64fold(var_68 || counter, seed=4951/9320) → AES-128 密钥   → AES-CTR 解密 → 64×64 位图 → 像素字体文本 “`

利用技巧

  1. Unicorn 模拟执行是逆向复杂原生函数的利器:当纯 Python 重写容易在细节上出错时(如 SipHash 密钥构造中 XOR 掩码的字节序),直接模拟执行原生代码可以绕过实现错误。但需注意 Unicorn 对 SSE4.1 等高级指令集的支持有限。
  2. debug bypass 路径是突破口:Android 原生库中常保留调试路径,这些路径通常简化了密钥生成逻辑,使其不依赖复杂的运行时状态。识别 byte_5D140(debugBypass 标志)和 cmovz 条件选择是关键。
  3. XXHash64 的折叠变体sub_24CB0 返回 h ^ (h >> 32),虽然反编译器显示为 v20 ^ HIDWORD(v20) 暗示 32 位结果,但实际汇编 xor rax, rsi 是 64 位操作,返回值仍为完整 64 位。

易错点

  • verifyAndDecrypt

    中的 cmovz 指令决定是否对 debug_key 做 ^ 0xA5A5A5A5A5A5A5A5,取决于 r13b(密码验证状态)。在 debug bypass 场景下应使用原始 debug_key(不做 XOR)。

  • AES-CTR 的计数器是小端序(little-endian),PyCryptodome 默认使用大端序,需要显式指定 little_endian=True

  • 解密后的位图不是 QR 码,而是像素字体渲染的文本,需要通过列分割和字形比对来识别字符。

由于文章过长,文中部分代码精简,可以点击左下角进入原文阅读。

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《【2026春节】全自动AI做题的实现及初8逆向AIAgent对话记录及wp》

评论:0   参与:  0