【2026春节】解题领红包【2-9】WP通杀

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

文章总结: 本文详细解析了一道逆向分析题的解题过程,通过两种方法找到了最终的flag。方法一直接分析汇编代码,发现其使用异或操作(密钥为0x42)对加密数据进行解密;方法二则利用题目中的Checksum验证机制,结合DFS和多线程爆破进行验证。两种方法都得到了相同的结果:52pojie!!!2026Happynewyear!文章还提供了相应的Python解题脚本。 综合评分: 85 文章分类: 逆向分析,CTF,WEB安全,渗透测试,红队


2. 样本初步分析

先看 CM1_unpacked.exe 基本信息:

  • PE64(0x8664
  • ImageBase = 0x140000000
  • 主要导入:USER32/GDI32/KERNEL32/msvcrt
  • 存在对话框消息处理流程(典型 Win32 GUI CrackMe)

3. 按钮点击分支定位

在对话框过程函数 0x140007AD0(消息分发)中:

  • WM_COMMAND (0x111)

  • LOWORD(wParam) == 11

    时走“解密按钮”分支

该分支关键流程:

  1. GetDlgItemTextA

    读取 3 个输入框(输入路径、输出路径、密码)

  2. 调用核心函数 sub_140008720 做校验与解密

  3. 若返回值为 0:提示成功

  4. 若返回值非 0:拼接 ERR-%03d,弹框报错

即:

  • ERR-xxx

    的 xxx 就是 sub_140008720 的返回码


4. 核心函数 sub_140008720 还原

函数原型可抽象为:

 复制代码 隐藏代码
intdecrypt_and_check(constchar* password, FILE* fin, FILE* fout);

主流程(伪代码):

 复制代码 隐藏代码
intdecrypt_and_check(pw, fin, fout) {
    ctx = init_crc64_ctx();
    update_crc64(ctx, CONST_14_BYTES, 14);
    update_crc64(ctx, pw, strlen(pw));
    seed = finalize_crc64(ctx);  // 64-bit

    read header(16 bytes);
    if (!parse_header_magic_and_init_crc32(header)) return1;

    file_len = ftell(fin);
    data_len = file_len - 16;
    if (file_len % 8 != 0) return2;

    rewind to data start;
    buf = malloc(data_len);
    if (!buf) return3;

    fread(buf, data_len, 1, fin);
    decrypt_blocks(buf, data_len, seed, header.iv);

    if (!check_crc32(buf, data_len, header.stored_crc32)) {
        free(buf);
        return4;   // ERR-004
    }

    pad = buf[data_len - 1];
    if (pad > data_len) {
        free(buf);
        return5;
    }

    fwrite(buf, data_len - pad, 1, fout);
    free(buf);
    return0;
}

错误码映射很清晰:

  • 1

    :头部不合法(magic 不对)

  • 2

    :长度非 8 对齐

  • 3

    :内存申请失败

  • 4

    :完整性校验失败(本题关键)

  • 5

    :padding 异常


5. ERR-004 的验证逻辑(关键)

ERR-004 对应函数 sub_1400082E0,其核心是:

 复制代码 隐藏代码
bool ok = (~crc32_state == stored_crc32_from_header);

其中 crc32_state 来自对“解密后数据(含 padding)”逐块更新。

也就是:

  • 密码错 -> 解密流错 -> 明文错 -> CRC32 不匹配 -> 返回 4 -> 弹 ERR-004

这正是题目要求定位的验证点。


6. 文件格式与解密算法还原

6.1 密文文件头格式(16 字节)

flag.png.encrypted 前 16 字节:

  • [0:4]

    magic:"CM26"(小端 dword 为 0x36324D43

  • [4:8]

    stored_crc32(小端)

  • [8:16]

    iv(8 字节)

样本实测:

  • stored_crc32 = 0x8D8445A2
  • iv = f5 69 73 60 01 cb 35 bc

6.2 口令到种子(seed)生成

程序用 64 位 CRC(多项式 0xC96C5795D7870F42):

  1. 初始化状态为 0xFFFFFFFFFFFFFFFF
  2. 先喂固定 14 字节常量(位于 .rdata,偏移 0xA828FB A1 FF FF 22 9D FF FF A3 A2 FF FF 83 A2
  3. 再喂密码明文字节
  4. 做末尾 4 字节长度扰动并取反,得到 64 位 seed

6.3 分组解密

每块 8 字节,使用链式异或:

 复制代码 隐藏代码
P[i] = C[i] XOR Prev XOR F(state)
Prev = (i==0 ? IV : C[i-1])
state = F(state) 每块更新一次

其中 F 为:

  1. state = rol64(state, 3)
  2. 对 state 的每个字节做 S-Box 替换

该 S-Box 位于 .rdata 0xA270,首字节 63 7C 77 7B ...,即 AES S-Box。


7. 逆推出正确密码

因为目标文件是 PNG,明文前 8 字节固定为:

89 50 4E 47 0D 0A 1A 0A

又有:

P0 = C0 XOR IV XOR F(seed)

所以可直接求出首块密钥流:

F(seed) = C0 XOR IV XOR PNG_SIG

实算得到:

  • F(seed) = 87 e4 26 b5 27 2e cc 95

再逆 F(逆 S-Box + ror64(3))可得初始 seed

  • seed = 0x55A4F867BA4475DD

用该 seed 对整文件解密后:

  • 明文前缀正确为 PNG 文件头
  • 计算 CRC32 恰好等于 0x8D8445A2
  • 校验通过,不再触发 ERR-004

随后在解出的 PNG tEXt 块中读到口令。


8. 最终答案

正确解密密码(flag)为:

 复制代码 隐藏代码
flag{EncrypTIoN_Is_haRd_52p0jIE_2o26_m62Tc4uj78maAq1C}

9. 可复现实验脚本(Python)

以下脚本可直接验证思路:从已知 PNG 头反推 seed,解密并提取 flag 文本。

 复制代码 隐藏代码
from pathlib import Path
import pefile, struct, zlib, re

enc = Path("question/flag.png.encrypted").read_bytes()
head, body = enc[:16], enc[16:]

magic = head[:4]
stored_crc = int.from_bytes(head[4:8], "little")
iv = head[8:16]
assert magic == b"CM26"

# 从程序里取 AES S-box
pe = pefile.PE("CM1_unpacked.exe")
exe = Path("CM1_unpacked.exe").read_bytes()
sbox_off = pe.get_offset_from_rva(0xA270)
sbox = exe[sbox_off:sbox_off + 256]

inv = [0] * 256
for i, v inenumerate(sbox):
    inv[v] = i

MASK = (1&nbsp;<<&nbsp;64) -&nbsp;1

defF(x:&nbsp;int) ->&nbsp;int:
&nbsp; &nbsp; x = ((x <<&nbsp;3) & MASK) | (x >>&nbsp;61) &nbsp;# rol64(x,3)
&nbsp; &nbsp; out =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(8):
&nbsp; &nbsp; &nbsp; &nbsp; out |= sbox[(x >> (8&nbsp;* i)) &&nbsp;0xFF] << (8&nbsp;* i)
&nbsp; &nbsp;&nbsp;return&nbsp;out

defFinv(y:&nbsp;int) ->&nbsp;int:
&nbsp; &nbsp; b =&nbsp;bytearray(8)
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(8):
&nbsp; &nbsp; &nbsp; &nbsp; b[i] = inv[(y >> (8&nbsp;* i)) &&nbsp;0xFF]
&nbsp; &nbsp; x =&nbsp;int.from_bytes(b,&nbsp;"little")
&nbsp; &nbsp;&nbsp;return&nbsp;((x >>&nbsp;3) | ((x &&nbsp;0x7) <<&nbsp;61)) & MASK &nbsp;# ror64(x,3)

# 已知 PNG 头反推 seed
png_sig =&nbsp;bytes.fromhex("89504e470d0a1a0a")
c0 = body[:8]
ks0 =&nbsp;bytes(a ^ b ^ c&nbsp;for&nbsp;a, b, c&nbsp;inzip(c0, iv, png_sig))
seed = Finv(int.from_bytes(ks0,&nbsp;"little"))
print(f"[+] seed = 0x{seed:016X}")

# 解密
state = seed
prev = iv
plain =&nbsp;bytearray()
for&nbsp;i&nbsp;inrange(0,&nbsp;len(body),&nbsp;8):
&nbsp; &nbsp; c = body[i:i+8]
&nbsp; &nbsp; state = F(state)
&nbsp; &nbsp; ks = state.to_bytes(8,&nbsp;"little")
&nbsp; &nbsp; p =&nbsp;bytes(c[j] ^ prev[j] ^ ks[j]&nbsp;for&nbsp;j&nbsp;inrange(8))
&nbsp; &nbsp; plain.extend(p)
&nbsp; &nbsp; prev = c

calc_crc = zlib.crc32(plain) &&nbsp;0xFFFFFFFF
print(f"[+] stored_crc = 0x{stored_crc:08X}, calc_crc = 0x{calc_crc:08X}")
assert&nbsp;calc_crc == stored_crc

# 去除 padding 并解析 PNG 文本块
pad = plain[-1]
png =&nbsp;bytes(plain[:-pad])
Path("question/decrypted_recovered.png").write_bytes(png)

pos =&nbsp;8
while&nbsp;pos +&nbsp;8&nbsp;<=&nbsp;len(png):
&nbsp; &nbsp; ln = struct.unpack(">I", png[pos:pos+4])[0]
&nbsp; &nbsp; typ = png[pos+4:pos+8]
&nbsp; &nbsp; data = png[pos+8:pos+8+ln]
&nbsp; &nbsp;&nbsp;if&nbsp;typ&nbsp;in&nbsp;(b"tEXt",&nbsp;b"iTXt",&nbsp;b"zTXt"):
&nbsp; &nbsp; &nbsp; &nbsp; m = re.search(rb"flag\\{[^}]+\\}", data)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;m:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[+] flag =", m.group().decode())
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; pos += ln +&nbsp;12

【2026春节】解题领红包之八(Android 中级)

0x00 题目信息与最终结论

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

最终可通过输入(flag):

FLAG{HJMWAPJ2026NBLD}

关键点:

  1. verifyAndDecrypt(packBytes, trim(input))

    最终是 位图渲染后 memcmp

  2. packBytes

    来自 assets/hjm_pack.bin

  3. 存在 native 作弊开关 setDebugBypass(true)(写全局 5d140=1),会切换到 debug key 分支。

  4. 在 debug 分支下,可稳定还原出正确输入:FLAG{HJMWAPJ2026NBLD}


0x01 Java/Compose 层入口与按钮事件全分支

1. 主界面按钮

主界面有两个按钮:

  • 接着奏乐接着舞
  • 投喂 flag

对应逻辑位于 Q0.vQ0.w,并由 Q0.N/Q0.h 组合函数绑定。

2. 按钮 接着奏乐接着舞 分支(Q0.v.case 0

  1. 若当前 GameState.cheatTriggered == true:直接 return(点击被锁死)。
  2. 否则计算当前点击落点(基于 0/250/500/750ms beatmap)。
  3. 调用 native:
  • checkRhythm(...)
  • updateExp(...)
  1. 分支:
  • 若 updateExp >= 0 && checkRhythm != -7:正常更新分数(Perfect/Good/Miss/None)。
  • 否则:进入作弊态 score=Cheat("嘻嘻"), cheatTriggered=true

3. 按钮 投喂 flag 分支

  • 仅设置 dialogVisible=true,弹出输入框。

4. 弹窗按钮分支

  • 验证

    :走协程 Q0.A,调用 native verifyAndDecrypt(packBytes, trim(input))

  • 取消

    :仅关闭弹窗。

  • 点击外部 dismiss:仅关闭弹窗。

5. 协程 Q0.A 校验流程

  1. 若 packBytes 缓存为空:先读取 assets/hjm_pack.bin
  2. 调 NativeBridge.verifyAndDecrypt(packBytes, trim(input))
  3. 若解出结构为空:显示 Flag 不正确
  4. 否则显示 验证成功,并更新层级状态。
  5. 异常:显示 验证出错

0x02 Native 入口与关键函数定位

目标 so:_apk/lib/x86_64/libhajimi.so

关键 native 函数地址:

  • verifyAndDecrypt

    0x24850

  • setDebugBypass

    0x24ca0

  • 24fc0

    (环境/状态采样函数):0x24fc0

  • 2e680

    (mode2 解包):0x2e680

  • 2efd0

    (输入字符串 -> 64×64 位图):0x2efd0

  • 2e570

    (debug key 生成):0x2e570

全局变量:

  • 5d140

    :debug bypass 标志(setDebugBypass 写入)

  • 5cff0 / 5cff8

    :运行态 key 与 gate

  • 5cfe8

    :参与解包的状态种子

  • 5d004/5d008/5d00c

    24fc0 更新的状态字段


0x03 verifyAndDecrypt(packBytes, trim(input)) 内部逻辑

verifyAndDecrypt 关键流程(mode=2):

  1. 从 jbyteArray packBytes 读入本地缓冲。
  2. 检查头:HJM1 + mode==2 + 宽高/帧参数合法。
  3. 调 24fc0,并基于返回值更新状态,最终更新 5cfe8
  4. 进入 mode2 分支(0x24b8b)。
  5. 选 key:
  • 如果 5d140 != 0(debug bypass 开启)=> key 来自 2e570()
  • 否则 => key 来自 5cff0(并受 r13 条件影响是否 ^0xA5..)。
  1. 调 2e680 对 pack payload 解包。

  2. 调 2efd0(input, 64, 64, out, 512) 生成输入位图。

  3. memcmp(decrypted_payload, rendered_input, 512)

    :相等即验证成功。

这意味着题目的核心本质是:

求一个 input,使 render(input) 与解包目标位图完全一致。


0x04 packBytes 的确认

Java 侧 Q0.y 明确:

  • context.getAssets().open("hjm_pack.bin")

因此 verifyAndDecrypt 第一个参数就是:

  • assets/hjm_pack.bin

文件结构确认:

  • 文件头 HJM1
  • mode = 2
  • payload 长度对应 64×64 bit(512 bytes)

0x05 作弊模式如何开启

native 里有导出接口:

  • setDebugBypass(boolean)

    (函数体在 0x24ca0

逻辑非常直接:

  • dl == 1

    时写 byte [0x5d140] = 1

一旦 5d140=1verifyAndDecrypt mode2 分支会走 debug key 路径(2e570),从而解出另一份目标位图。

实战可用两种方式:

  1. 运行时 hook 调 NativeBridge.INSTANCE.setDebugBypass(true)
  2. 直接内存改写 5d140=1

0x06 求解策略与过程

我没有直接硬啃所有 C++ STL/环境代码,而是采用“可复现仿真 + 逐层验证”的策略。

1. 先验证渲染器 2efd0

构造调用 2efd0(input,64,64,buf,512),确认它输出是稀疏文本位图(例如输入 AAAAAA 只点亮少量位)。

2. 跑 verifyAndDecrypt 抓 memcmp 两侧缓冲

在仿真中拦截 memcmp

  • 参数1:解包后目标位图
  • 参数2:2efd0(input) 渲染位图

3. 比较普通分支 vs debug 分支

  • 普通分支(5d140=0)下目标位图高熵,非文本感。
  • debug 分支(5d140=1)下目标位图变成明显文本位图(总置位约 314)。

4. 用 2efd0 做反求

把“位图距离(XOR bitcount)”作为目标函数,对候选字符串做贪心/爬山优化,长度扫描后在 N=21 收敛到 0 距离。

收敛结果:

  • FLAG{HJMWAPJ2026NBLD}

验证:

  • render(flag)

    与 debug 分支解包目标 memcmp 完全相等(bit 差异=0)。


0x07 关键复现命令

1. 查看最终分析结论

Q0.A verifyAndDecrypt(packBytes, trim(input)) 逆向分析(含作弊模式与可通过输入)
1. packBytes 来源
  • Java 协程 Q0.A -> Q0.y 从 assets/hjm_pack.bin 读取字节数组。

  • verifyAndDecrypt

    的第一个参数就是这个 packBytes

  • 文件头校验:HJM10x314D4A48),并且模式字段为 2

2. Native 主流程(0x24850

verifyAndDecrypt(JNIEnv*, ..., jbyteArray packBytes, jstring input) 关键流程:

  1. 读取 packBytes 到本地缓冲,校验头部。
  2. 调 24fc0 更新状态:5d004/5d008/5d00c,并据此更新 5cfe8
  3. 分支按 mode:本题 mode=2,走 0x24b8b 分支。
  4. 关键开关:
  • 5d140 != 0

    (debug bypass)时,key 来自 2e570()

  • 否则 key 来自 5cff0(并可能按 r13 决定是否 ^0xA5...)。

  1. 调 2e680 用 key+5cfe8 解包 hjm_pack.bin 的 payload(64×64 bit,512 bytes)。

  2. 2efd0(input,64,64,buf,512)

    生成输入位图。

  3. memcmp(decrypted_payload, rendered_input, 512)

    :相等即验证成功。

3. 作弊模式如何开启

Native 里有显式接口:

  • setDebugBypass(boolean)

    0x24ca0

  • dl==1

    时写全局 5d140=1

当 5d140=1 时,verifyAndDecrypt 强制走 debug key 路径(2e570),这是可稳定复现的“作弊模式”验证分支。

说明:Java 反编译代码里未发现普通 UI 直接调用点;实战可通过 Frida/Hook 直接调用该 native 方法或直接改写 5d140 达到同效果。

4. 真实可通过输入(Flag)

在 debug bypass=1 分支下,解包目标位图与渲染器 2efd0 逐字符匹配,得到 唯一完全匹配输入

FLAG{HJMWAPJ2026NBLD}

验证结果:

  • memcmp

    差异位数 = 0(完全一致)

  • 即可返回“验证成功”分支。

5. 结论
  • packBytes

    assets/hjm_pack.bin

  • 本题关键是 mode=2 下的位图比较链路:2e680(解包) + 2efd0(渲染) + memcmp

  • 开启作弊模式(setDebugBypass(true))后,正确 flag:

FLAG{HJMWAPJ2026NBLD}

按钮点击事件逻辑分析
1. 分析范围与入口
  • App 入口:MainActivity.onCreate 仅加载 Compose 根内容(Q0.d.b),无多 Activity 按钮分流。
  • 主界面核心在 Q0.N.a(...),主按钮容器在 Q0.N.c(...) -> Q0.h
  • 弹窗(输入 flag)在 Q0.N.a(...) 里通过 androidx.compose.material3.n.b(...) 构建。
2. 按钮总览(全部可点击按钮)
  1. 主界面按钮A:接着奏乐接着舞
  2. 主界面按钮B:投喂 flag
  3. 弹窗按钮A:验证
  4. 弹窗按钮B:取消

补充:弹窗还有 onDismissRequest(点外部/返回键关闭),虽然不是实体按钮,但属于点击/关闭交互分支。


3. 主界面双按钮绑定关系
3.1 绑定证据
  • Q0.N.c(p23, p24, ...)

    将两个回调传入 Q0.hv9_1(p23, ..., p24, ...)

  • Q0.h

    中第1个 E.a(this.j, ..., new Q0.g(..., 0, ...)),第2个 E.a(this.m, ..., new Q0.g(..., 1, ...))

  • Q0.g

    文案分支:

  • case 0

    => "接着奏乐接着舞"

  • case 1

    => "投喂 flag"

  • Q0.x

    实际传参:

  • 第1个回调是 new Q0.v(..., q=0)(节奏点击逻辑)

  • 第2个回调是 new Q0.w(..., j=0)(打开弹窗)

3.2 对应结论
  1. 接着奏乐接着舞

    -> Q0.v.case 0

  2. 投喂 flag

    -> Q0.w.case 0


4. 每个按钮点击逻辑与完整分支
4.1 按钮A:接着奏乐接着舞Q0.v.case 0

入口:Q0.v.o() 的 case 0

分支树
  1. 若 gameState.cheatTriggered == true
  • 直接返回,不再处理点击(已进入作弊态后,节奏点击被锁死)。
  1. 否则继续节奏判定
  • 取当前纳秒时间,计算本次点击相位。
  • 根据节奏点数组(0/250/500/750ms)选最近点位索引。
  • 组装扰动参数后调用 Native:
  • checkRhythm(...)
  • updateExp(...)
  1. Native 返回后分支
  • 若 updateExp >= 0 且 checkRhythm != -7
  • 正常更新 GameState(exp + score,cheat=false)
  • score 映射到 Q0.QNone/Perfect/Good/Miss
  • 否则:
  • 进入作弊分支:GameState(exp保持原值, score=Cheat("嘻嘻"), cheat=true)
可见结果
  • 正常:分数文本在 就绪/完美/良好/失误 间变化。
  • 作弊:分数变 嘻嘻,并触发作弊态展示。

4.2 按钮B:投喂 flagQ0.w.case 0

入口:Q0.w.o() 的 case 0

分支树
  1. 无条件执行:dialogVisible = true

  2. Q0.N.a(...)

    检测到 dialogVisible 后显示验证弹窗


4.3 弹窗按钮A:验证Q0.B.o() -> 协程 Q0.A.g()

入口:Q0.C 组合的 TextButton 点击回调是 Q0.B

第1层分支(点击瞬间)
  1. 输入为空(trim后为空
  • 状态文本设为:请先输入 flag
  • 不发起校验协程
  1. 输入非空
  • dialogVisible = false

    (先关弹窗)

  • 状态文本设为:验证中...

  • 启动协程 Q0.A

第2层分支(协程 Q0.A
  1. 若缓存密文包字节为空
  • 先从 assets/hjm_pack.bin 读取并缓存,再继续校验
  1. 调用 Native 校验
  • verifyAndDecrypt(packBytes, trim(input))
  1. 解包判定
  • 若 h1.a.S(decryptBytes) == null
  • 状态文本:Flag 不正确
  • 否则
  • 保存解包得到的真实层级对象(P
  • 解析输入文本对应层级 Q0.N.j(...)
  • 解析成功:用解析出的层级
  • 解析失败:回退到 a==100 的层级(lv100
  • 状态文本:验证成功
  1. 异常分支(协程中任意异常)
  • 记录日志 verify flag failed
  • 状态文本:验证出错

4.4 弹窗按钮B:取消Q0.w.default 即 j=2)

入口:Q0.D.case 0 中构建 new Q0.w(..., 2)

分支树
  1. 无条件执行:dialogVisible = false
  2. 关闭弹窗,不触发任何校验

4.5 弹窗外部关闭(非按钮,但属于点击关闭分支)

入口:Q0.N.a(...) 弹窗 onDismissRequest = new Q0.w(..., 1)

分支树
  1. 无条件执行:dialogVisible = false
  2. 与“取消”一样只关闭弹窗

5. 结果文本/颜色分支(按钮后效)

Q0.N.i(...) 根据状态文本分支颜色:

  1. 验证成功

    -> 成功色

  2. Flag 不正确

    -> 失败色

  3. 验证出错

    -> 错误色

  4. 其他/空 -> 默认色

这部分不是点击入口,但属于按钮点击后可见分支结果。


6. 完整性结论(按钮是否遗漏)

通过调用点反查可确认未遗漏:

  1. new Q0.v(...)

    仅在主界面按钮绑定处出现一次(对应 接着奏乐接着舞)。

  2. new Q0.w(...)

    仅出现三种 case:

  • j=0

    :打开弹窗(投喂 flag

  • j=1

    :弹窗 onDismissRequest

  • j=2

    :弹窗 取消

  1. 弹窗 验证 回调唯一入口是 Q0.B -> Q0.A

结论:本 APK 当前界面的所有按钮点击事件及其逻辑分支已全部覆盖。

2. 运行仿真脚本

ai脚本:

&nbsp;复制代码&nbsp;隐藏代码
import&nbsp;struct
from&nbsp;pathlib&nbsp;import&nbsp;Path
import&nbsp;lief
from&nbsp;unicorn&nbsp;import&nbsp;Uc, UcError, UC_ARCH_X86, UC_MODE_64, UC_HOOK_CODE, UC_PROT_ALL
from&nbsp;unicorn.x86_const&nbsp;import&nbsp;*

PAGE =&nbsp;0x1000
MASK64 = (1<<64)-1

defp64(x):
&nbsp; &nbsp;&nbsp;return&nbsp;struct.pack('<Q', x & MASK64)

defu64(b):
&nbsp; &nbsp;&nbsp;return&nbsp;struct.unpack('<Q', b)[0]

defi64(x):
&nbsp; &nbsp;&nbsp;return&nbsp;x&nbsp;if&nbsp;x < (1<<63)&nbsp;else&nbsp;x - (1<<64)

defi32(x):
&nbsp; &nbsp; x &=&nbsp;0xffffffff
&nbsp; &nbsp;&nbsp;return&nbsp;x&nbsp;if&nbsp;x <&nbsp;0x80000000else&nbsp;x -&nbsp;0x100000000

classEmuQ8:
&nbsp; &nbsp;&nbsp;def__init__(self, so_path):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.so_path = so_path
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.bin&nbsp;= lief.parse(str(so_path))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu = Uc(UC_ARCH_X86, UC_MODE_64)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mapped_pages =&nbsp;set()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.HEAP_BASE =&nbsp;0x10000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.HEAP_SIZE =&nbsp;0x08000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.STACK_BASE =&nbsp;0x20000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.STACK_SIZE =&nbsp;0x02000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.TLS_BASE =&nbsp;0x30000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.TLS_SIZE =&nbsp;0x1000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.STOP_ADDR =&nbsp;0x40000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.STUB_BASE =&nbsp;0x41000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.ENV_PTR =&nbsp;0x50000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.JNI_TABLE =&nbsp;0x50001000

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.heap_cur =&nbsp;self.HEAP_BASE +&nbsp;0x1000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handle_cur =&nbsp;0x60000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.load_images = []

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jbyte_arrays = {}
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstrings = {}
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstring_cptr = {}

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.memcmp_records = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.key_records = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.in_verify =&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.unknown_imports = {}

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.ret24_vals = [0,&nbsp;0]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.ret24_idx =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.popcnt_map = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x298C6: (UC_X86_REG_R14, UC_X86_REG_RBP),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x29DB8: (UC_X86_REG_R8, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2A129: (UC_X86_REG_RAX, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2A838: (UC_X86_REG_RSI, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2B34D: (UC_X86_REG_RCX, UC_X86_REG_RBP),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2B572: (UC_X86_REG_RAX, UC_X86_REG_RBP),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2B778: (UC_X86_REG_RSI, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2B92F: (UC_X86_REG_RAX, UC_X86_REG_R12),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2BA68: (UC_X86_REG_RSI, UC_X86_REG_R12),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2BFA7: (UC_X86_REG_R12, UC_X86_REG_RBX),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2C2F7: (UC_X86_REG_RAX, UC_X86_REG_RBX),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2C4A3: (UC_X86_REG_RBX, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2C7EB: (UC_X86_REG_RAX, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2CA58: (UC_X86_REG_RSI, UC_X86_REG_R14),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2CDCD: (UC_X86_REG_R8, UC_X86_REG_RDI),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2D069: (UC_X86_REG_RAX, UC_X86_REG_RBP),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2D0BE: (UC_X86_REG_R9, UC_X86_REG_RBP),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2D388: (UC_X86_REG_RDI, UC_X86_REG_R15),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x2D718: (UC_X86_REG_RSI, UC_X86_REG_R12),
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map_segments()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map_runtime_regions()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._build_import_map()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._build_jni_table()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Restrict code hooks to hot stub ranges to avoid per-instruction overhead.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=self.STOP_ADDR, end=self.STOP_ADDR)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=0x55C70, end=0x56570)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=self.STUB_BASE, end=self.STUB_BASE +&nbsp;0x2000)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=0x24FC0, end=0x24FC0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=0x2EFD0, end=0x2EFD0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=0x2E680, end=0x2E680)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;a&nbsp;inself.popcnt_map:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.hook_add(UC_HOOK_CODE,&nbsp;self._hook_code, begin=a, end=a)

&nbsp; &nbsp;&nbsp;def_align_down(self, x):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;x & ~(PAGE-1)

&nbsp; &nbsp;&nbsp;def_align_up(self, x):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(x + PAGE -&nbsp;1) & ~(PAGE-1)

&nbsp; &nbsp;&nbsp;def_map(self, addr, size, perms=UC_PROT_ALL):
&nbsp; &nbsp; &nbsp; &nbsp; a =&nbsp;self._align_down(addr)
&nbsp; &nbsp; &nbsp; &nbsp; b =&nbsp;self._align_up(addr + size)
&nbsp; &nbsp; &nbsp; &nbsp; total = b - a
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_map(a, total, perms)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;inrange(a, b, PAGE):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mapped_pages.add(p)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;UcError:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pass

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;inrange(a, b, PAGE):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p&nbsp;inself.mapped_pages:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_map(p, PAGE, perms)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mapped_pages.add(p)

&nbsp; &nbsp;&nbsp;def_map_segments(self):
&nbsp; &nbsp; &nbsp; &nbsp; raw = Path(self.so_path).read_bytes()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;s&nbsp;inself.bin.segments:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;s.type&nbsp;!= lief.ELF.Segment.TYPE.LOAD:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; va = s.virtual_address
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vsz = s.virtual_size
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fsz = s.physical_size
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; off = s.file_offset
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(va, vsz, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;fsz >&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(va, raw[off:off+fsz])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; img =&nbsp;bytearray(vsz)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;fsz >&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; img[:fsz] = raw[off:off+fsz]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.load_images.append((va,&nbsp;bytes(img)))

&nbsp; &nbsp;&nbsp;def_map_runtime_regions(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.HEAP_BASE,&nbsp;self.HEAP_SIZE, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.STACK_BASE,&nbsp;self.STACK_SIZE, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.TLS_BASE,&nbsp;self.TLS_SIZE, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.STOP_ADDR, PAGE, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.ENV_PTR,&nbsp;0x4000, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(self.STUB_BASE,&nbsp;0x4000, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._map(0x0, PAGE, UC_PROT_ALL)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(self.STOP_ADDR,&nbsp;b"\xCC")

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Unicorn build in this environment doesn't expose FS_BASE register;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# keep fs:[0x28] canary in low memory where segmented read resolves.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(0x28, p64(0x1122334455667788))

&nbsp; &nbsp;&nbsp;defreset_state(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Restore original loadable image (including zeroed bss tail).
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;va, img&nbsp;inself.load_images:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(va, img)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Reset runtime arenas and handles.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.heap_cur =&nbsp;self.HEAP_BASE +&nbsp;0x1000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handle_cur =&nbsp;0x60000000
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jbyte_arrays.clear()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstrings.clear()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstring_cptr.clear()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.memcmp_records.clear()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.key_records.clear()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.in_verify =&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.ret24_idx =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.unknown_imports.clear()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Restore canary and JNI header pointers.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(0x28, p64(0x1122334455667788))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(self.ENV_PTR, p64(self.JNI_TABLE))

&nbsp; &nbsp;&nbsp;def_build_import_map(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.imp = {}
&nbsp; &nbsp; &nbsp; &nbsp; rels =&nbsp;self.bin.pltgot_relocations
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;i, r&nbsp;inenumerate(rels):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addr =&nbsp;0x55c70&nbsp;+ i *&nbsp;0x10
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name = r.symbol.name&nbsp;if&nbsp;r.has_symbol&nbsp;elsef'idx_{i}'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.imp[addr] = name

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handlers = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55ca0:&nbsp;self._h_free,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55cb0:&nbsp;self._h_stack_fail,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55ce0:&nbsp;self._h_malloc,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55cf0:&nbsp;self._h_memset,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55d00:&nbsp;self._h_memcmp,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55e00:&nbsp;self._h_memcpy,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55e10:&nbsp;self._h_memmove,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55e60:&nbsp;self._h_strlen,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55d70:&nbsp;self._h_memchr,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55ec0:&nbsp;self._h_strlen_chk,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55d90:&nbsp;self._h_guard_acquire,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55da0:&nbsp;self._h_guard_release,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55e40:&nbsp;self._h_access,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55db0:&nbsp;self._h_clock_gettime,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55dc0:&nbsp;self._h_readlink,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55de0:&nbsp;self._h_syscall,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55df0:&nbsp;self._h_system_property_get,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x55f60:&nbsp;self._h_next_prime,
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Internal function stubs
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handlers[0x24fc0] =&nbsp;self._h_24fc0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handlers[0x2efd0] =&nbsp;self._h_2efd0

&nbsp; &nbsp;&nbsp;def_build_jni_table(self):
&nbsp; &nbsp; &nbsp; &nbsp; OFF = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'GetStringUTFChars':&nbsp;0x548,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ReleaseStringUTFChars':&nbsp;0x550,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'GetArrayLength':&nbsp;0x558,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'NewByteArray':&nbsp;0x580,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'GetByteArrayRegion':&nbsp;0x640,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'SetByteArrayRegion':&nbsp;0x680,
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jni_stub = {}
&nbsp; &nbsp; &nbsp; &nbsp; idx =&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;k, off&nbsp;in&nbsp;OFF.items():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addr =&nbsp;self.STUB_BASE + idx *&nbsp;0x100
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idx +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jni_stub[addr] = k
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handlers[addr] =&nbsp;getattr(self,&nbsp;f'_jni_{k}')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(self.JNI_TABLE + off, p64(addr))

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(self.ENV_PTR, p64(self.JNI_TABLE))

&nbsp; &nbsp;&nbsp;def_rd(self, addr, size):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnself.mu.mem_read(addr, size)

&nbsp; &nbsp;&nbsp;def_wr(self, addr, data):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.mem_write(addr, data)

&nbsp; &nbsp;&nbsp;def_rd_u64(self, addr):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;u64(self._rd(addr,&nbsp;8))

&nbsp; &nbsp;&nbsp;def_wr_u64(self, addr, v):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(addr, p64(v))

&nbsp; &nbsp;&nbsp;defalloc(self, size):
&nbsp; &nbsp; &nbsp; &nbsp; size =&nbsp;max(1, size)
&nbsp; &nbsp; &nbsp; &nbsp; size = (size +&nbsp;0xF) & ~0xF
&nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;self.heap_cur
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.heap_cur += size
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ifself.heap_cur >=&nbsp;self.HEAP_BASE +&nbsp;self.HEAP_SIZE:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;RuntimeError('heap exhausted')
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(p,&nbsp;b"\x00"&nbsp;* size)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;p

&nbsp; &nbsp;&nbsp;defnew_jbyte_array(self, b:&nbsp;bytes):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.handle_cur
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handle_cur +=&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jbyte_arrays[h] =&nbsp;bytearray(b)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;h

&nbsp; &nbsp;&nbsp;defnew_jstring(self, s:&nbsp;str):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.handle_cur
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.handle_cur +=&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstrings[h] = s
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstring_cptr[h] =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;h

&nbsp; &nbsp;&nbsp;def_ret(self):
&nbsp; &nbsp; &nbsp; &nbsp; rsp =&nbsp;self.mu.reg_read(UC_X86_REG_RSP)
&nbsp; &nbsp; &nbsp; &nbsp; ra =&nbsp;self._rd_u64(rsp)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RSP, rsp +&nbsp;8)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RIP, ra)

&nbsp; &nbsp;&nbsp;def_hook_code(self, uc, address, size, _user):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;address ==&nbsp;self.STOP_ADDR:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uc.emu_stop()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;address&nbsp;inself.popcnt_map:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dst, src =&nbsp;self.popcnt_map[address]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v = uc.reg_read(src) & MASK64
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uc.reg_write(dst, v.bit_count())
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uc.reg_write(UC_X86_REG_RIP, address +&nbsp;5)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;address ==&nbsp;0x2e680andself.in_verify:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key = uc.reg_read(UC_X86_REG_RSI) & MASK64
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.key_records.append(key)

&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.handlers.get(address)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;h&nbsp;isnotNone:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Unknown import in PLT range: fail fast for visibility
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if0x55c70&nbsp;<= address <&nbsp;0x56570:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name =&nbsp;self.imp.get(address,&nbsp;'unknown')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.unknown_imports[(address, name)] =&nbsp;self.unknown_imports.get((address, name),&nbsp;0) +&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;# ===== JNI handlers =====
&nbsp; &nbsp;&nbsp;def_jni_GetArrayLength(self):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; arr =&nbsp;self.jbyte_arrays.get(h)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;len(arr)&nbsp;if&nbsp;arr&nbsp;isnotNoneelse0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_jni_GetByteArrayRegion(self):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; start =&nbsp;self.mu.reg_read(UC_X86_REG_RDX) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RCX) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; dst =&nbsp;self.mu.reg_read(UC_X86_REG_R8)
&nbsp; &nbsp; &nbsp; &nbsp; arr =&nbsp;self.jbyte_arrays.get(h,&nbsp;bytearray())
&nbsp; &nbsp; &nbsp; &nbsp; chunk =&nbsp;bytes(arr[start:start+n])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;iflen(chunk) < n:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; chunk +=&nbsp;b"\x00"&nbsp;* (n -&nbsp;len(chunk))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(dst, chunk)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_jni_NewByteArray(self):
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RSI) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.new_jbyte_array(bytes(n))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, h)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_jni_SetByteArrayRegion(self):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; start =&nbsp;self.mu.reg_read(UC_X86_REG_RDX) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RCX) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; src =&nbsp;self.mu.reg_read(UC_X86_REG_R8)
&nbsp; &nbsp; &nbsp; &nbsp; arr =&nbsp;self.jbyte_arrays.get(h)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;arr&nbsp;isNone:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return
&nbsp; &nbsp; &nbsp; &nbsp; data =&nbsp;bytes(self._rd(src, n))
&nbsp; &nbsp; &nbsp; &nbsp; end = start + n
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;end >&nbsp;len(arr):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; arr.extend(b"\x00"&nbsp;* (end -&nbsp;len(arr)))
&nbsp; &nbsp; &nbsp; &nbsp; arr[start:end] = data
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_jni_GetStringUTFChars(self):
&nbsp; &nbsp; &nbsp; &nbsp; h =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; s =&nbsp;self.jstrings.get(h,&nbsp;"")
&nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;self.jstring_cptr.get(h,&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b = s.encode('utf-8') +&nbsp;b"\x00"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;self.alloc(len(b))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(p, b)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.jstring_cptr[h] = p
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, p)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_jni_ReleaseStringUTFChars(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;# ===== Import/internal stubs =====
&nbsp; &nbsp;&nbsp;def_h_stack_fail(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;RuntimeError('__stack_chk_fail')

&nbsp; &nbsp;&nbsp;def_h_malloc(self):
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;self.alloc(n))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_free(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_memset(self):
&nbsp; &nbsp; &nbsp; &nbsp; dst =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; c =&nbsp;self.mu.reg_read(UC_X86_REG_RSI) &&nbsp;0xff
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(dst,&nbsp;bytes([c]) * n)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, dst)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_memcpy(self):
&nbsp; &nbsp; &nbsp; &nbsp; dst =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; src =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(dst,&nbsp;bytes(self._rd(src, n)))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, dst)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_memmove(self):
&nbsp; &nbsp; &nbsp; &nbsp; dst =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; src =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp; tmp =&nbsp;bytes(self._rd(src, n))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(dst, tmp)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, dst)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_memcmp(self):
&nbsp; &nbsp; &nbsp; &nbsp; a =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; b =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp; ba =&nbsp;bytes(self._rd(a, n))
&nbsp; &nbsp; &nbsp; &nbsp; bb =&nbsp;bytes(self._rd(b, n))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.memcmp_records.append((a, b, n, ba, bb))
&nbsp; &nbsp; &nbsp; &nbsp; rv =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ba != bb:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;x, y&nbsp;inzip(ba, bb):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;x != y:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rv = -1if&nbsp;x < y&nbsp;else1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, rv & MASK64)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_strlen(self):
&nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch =&nbsp;self._rd(p+n,&nbsp;1)[0]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ch ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; n +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, n)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_memchr(self):
&nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; c =&nbsp;self.mu.reg_read(UC_X86_REG_RSI) &&nbsp;0xff
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp; data =&nbsp;bytes(self._rd(p, n))
&nbsp; &nbsp; &nbsp; &nbsp; i = data.find(bytes([c]))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;0if&nbsp;i <&nbsp;0else&nbsp;p + i)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_strlen_chk(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._h_strlen()

&nbsp; &nbsp;&nbsp;def_h_guard_acquire(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_guard_release(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_access(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_clock_gettime(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# int clock_gettime(clockid_t clk_id, struct timespec *tp)
&nbsp; &nbsp; &nbsp; &nbsp; tp =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr_u64(tp,&nbsp;1700000000)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr_u64(tp +&nbsp;8,&nbsp;123456789)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_readlink(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ssize_t readlink(const char *path, char *buf, size_t bufsiz)
&nbsp; &nbsp; &nbsp; &nbsp; buf =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDX)
&nbsp; &nbsp; &nbsp; &nbsp; s =&nbsp;b"/system/bin/app_process64"
&nbsp; &nbsp; &nbsp; &nbsp; m =&nbsp;min(len(s), n)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(buf, s[:m])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, m)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_syscall(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, -1&nbsp;& MASK64)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_system_property_get(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# int __system_property_get(const char* name, char* value)
&nbsp; &nbsp; &nbsp; &nbsp; name_p =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp; out_p =&nbsp;self.mu.reg_read(UC_X86_REG_RSI)
&nbsp; &nbsp; &nbsp; &nbsp; name =&nbsp;self._read_cstr(name_p)
&nbsp; &nbsp; &nbsp; &nbsp; props = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ro.debuggable':&nbsp;'0',
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ro.secure':&nbsp;'1',
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ro.build.tags':&nbsp;'release-keys',
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ro.build.type':&nbsp;'user',
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; v = props.get(name,&nbsp;'')
&nbsp; &nbsp; &nbsp; &nbsp; b = v.encode() +&nbsp;b"\x00"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(out_p, b)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;len(v))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_next_prime(self):
&nbsp; &nbsp; &nbsp; &nbsp; n =&nbsp;self.mu.reg_read(UC_X86_REG_RDI)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;n <=&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p =&nbsp;2
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p = n&nbsp;if&nbsp;(n &&nbsp;1)&nbsp;else&nbsp;n +&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ok =&nbsp;True
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d =&nbsp;3
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;d * d <= p:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;p % d ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ok =&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d +=&nbsp;2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ok:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p +=&nbsp;2
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, p & MASK64)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_24fc0(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ifself.ret24_idx <&nbsp;len(self.ret24_vals):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v =&nbsp;self.ret24_vals[self.ret24_idx]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v =&nbsp;self.ret24_vals[-1]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.ret24_idx +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX, v & MASK64)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_h_2efd0(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# int render(const char* in, int w, int h, uint8_t* out, size_t outlen)
&nbsp; &nbsp; &nbsp; &nbsp; out =&nbsp;self.mu.reg_read(UC_X86_REG_RCX)
&nbsp; &nbsp; &nbsp; &nbsp; outlen =&nbsp;self.mu.reg_read(UC_X86_REG_R8)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# deterministic dummy bitmap
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(out,&nbsp;b"\x00"&nbsp;* outlen)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RAX,&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._ret()

&nbsp; &nbsp;&nbsp;def_read_cstr(self, p, limit=0x1000):
&nbsp; &nbsp; &nbsp; &nbsp; bs =&nbsp;bytearray()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(limit):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c =&nbsp;self._rd(p+i,&nbsp;1)[0]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;c ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bs.append(c)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;bs.decode('utf-8', errors='ignore')

&nbsp; &nbsp;&nbsp;defcall(self, fn, args, timeout=1_000_000):
&nbsp; &nbsp; &nbsp; &nbsp; regs = [UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_RCX, UC_X86_REG_R8, UC_X86_REG_R9]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;r, v&nbsp;inzip(regs, args):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(r, v & MASK64)

&nbsp; &nbsp; &nbsp; &nbsp; rsp =&nbsp;self.STACK_BASE +&nbsp;self.STACK_SIZE -&nbsp;0x100
&nbsp; &nbsp; &nbsp; &nbsp; rsp &= ~0xF
&nbsp; &nbsp; &nbsp; &nbsp; rsp -=&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr_u64(rsp,&nbsp;self.STOP_ADDR)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RSP, rsp)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.reg_write(UC_X86_REG_RIP, fn)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.mu.emu_start(fn,&nbsp;self.STOP_ADDR, count=timeout)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;UcError&nbsp;as&nbsp;e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rip =&nbsp;self.mu.reg_read(UC_X86_REG_RIP)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rsp2 =&nbsp;self.mu.reg_read(UC_X86_REG_RSP)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;RuntimeError(f'emu error&nbsp;{e}&nbsp;rip={hex(rip)}&nbsp;rsp={hex(rsp2)}&nbsp;fn={hex(fn)}')&nbsp;from&nbsp;e
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnself.mu.reg_read(UC_X86_REG_RAX)

&nbsp; &nbsp;&nbsp;defg_u64(self, addr):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnself._rd_u64(addr)

&nbsp; &nbsp;&nbsp;defg_u32(self, addr):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;struct.unpack('<I',&nbsp;self._rd(addr,&nbsp;4))[0]

&nbsp; &nbsp;&nbsp;defg_u8(self, addr):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnself._rd(addr,&nbsp;1)[0]

&nbsp; &nbsp;&nbsp;defrender_text(self, s:&nbsp;str, w=64, h=64):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Directly call 2efd0 with C-string input and fresh heap space.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.heap_cur =&nbsp;self.HEAP_BASE +&nbsp;0x1000
&nbsp; &nbsp; &nbsp; &nbsp; b = s.encode('utf-8') +&nbsp;b"\x00"
&nbsp; &nbsp; &nbsp; &nbsp; in_ptr =&nbsp;self.alloc(len(b))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(in_ptr, b)
&nbsp; &nbsp; &nbsp; &nbsp; out_len = (w * h) //&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp; out_ptr =&nbsp;self.alloc(out_len)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self._wr(out_ptr,&nbsp;b"\x00"&nbsp;* out_len)
&nbsp; &nbsp; &nbsp; &nbsp; rv =&nbsp;self.call(0x2EFD0, [in_ptr, w, h, out_ptr, out_len], timeout=2_000_000)
&nbsp; &nbsp; &nbsp; &nbsp; data =&nbsp;bytes(self._rd(out_ptr, out_len))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;rv &&nbsp;0xFF, data

defjava_noise(idx, now_ns):
&nbsp; &nbsp; x = (((idx &&nbsp;0xffffffff) <<&nbsp;32) ^ (now_ns & MASK64)) & MASK64
&nbsp; &nbsp; x ^= ((x <<&nbsp;13) & MASK64)
&nbsp; &nbsp; x ^= (x >>&nbsp;7)
&nbsp; &nbsp; x ^= ((x <<&nbsp;17) & MASK64)
&nbsp; &nbsp;&nbsp;return&nbsp;x &&nbsp;0xffffffff

defrun_case(
&nbsp; &nbsp; ret_start,
&nbsp; &nbsp; ret_verify,
&nbsp; &nbsp; clicks=1000,
&nbsp; &nbsp; input_text='AAAAAA',
&nbsp; &nbsp; do_clicks=False,
&nbsp; &nbsp; key_override=0x661606DD8316E47D,
&nbsp; &nbsp; gate_override=1,
&nbsp; &nbsp; emu=None,
&nbsp; &nbsp; debug_bypass=None,
):
&nbsp; &nbsp;&nbsp;if&nbsp;emu&nbsp;isNone:
&nbsp; &nbsp; &nbsp; &nbsp; emu = EmuQ8('_apk/lib/x86_64/libhajimi.so')
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; emu.reset_state()
&nbsp; &nbsp; emu.ret24_vals = [ret_start, ret_verify]

&nbsp; &nbsp;&nbsp;# prepare startSession args
&nbsp; &nbsp; beats = [0,&nbsp;250,&nbsp;500,&nbsp;750]
&nbsp; &nbsp; b =&nbsp;b''.join(struct.pack('<I', x)&nbsp;for&nbsp;x&nbsp;in&nbsp;beats)
&nbsp; &nbsp; beat_arr = emu.new_jbyte_array(b)

&nbsp; &nbsp; t0 =&nbsp;1_700_000_000_000_000_000
&nbsp; &nbsp; emu.call(0x238a0, [emu.ENV_PTR,&nbsp;0, t0, beat_arr,&nbsp;1000])

&nbsp; &nbsp; exp =&nbsp;0
&nbsp; &nbsp; last_score =&nbsp;0
&nbsp; &nbsp;&nbsp;if&nbsp;do_clicks:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(clicks):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; now = t0 + i *&nbsp;250_000_000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idx = i &&nbsp;3
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; noise = java_noise(idx, now)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; over50 =&nbsp;1if&nbsp;exp >=&nbsp;50else0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score = emu.call(0x23e50, [emu.ENV_PTR,&nbsp;0, now, idx, noise, over50]) &&nbsp;0xffffffff
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; last_score = i32(score)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; exp_raw = emu.call(0x23f60, [emu.ENV_PTR,&nbsp;0, score, idx, noise])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; exp = i64(exp_raw & MASK64)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; emu._wr_u64(0x5cff0, key_override)
&nbsp; &nbsp; &nbsp; &nbsp; emu._wr(0x5cff8,&nbsp;bytes([gate_override &&nbsp;0xff]))

&nbsp; &nbsp; key = emu.g_u64(0x5cff0)
&nbsp; &nbsp; gate = emu.g_u8(0x5cff8)

&nbsp; &nbsp; pack = Path('_apk/assets/hjm_pack.bin').read_bytes()
&nbsp; &nbsp; pack_arr = emu.new_jbyte_array(pack)
&nbsp; &nbsp; jstr = emu.new_jstring(input_text)

&nbsp; &nbsp;&nbsp;if&nbsp;debug_bypass&nbsp;isnotNone:
&nbsp; &nbsp; &nbsp; &nbsp; emu._wr(0x5d140,&nbsp;bytes([debug_bypass &&nbsp;0xFF]))

&nbsp; &nbsp; emu.in_verify =&nbsp;True
&nbsp; &nbsp; rv = emu.call(0x24850, [emu.ENV_PTR,&nbsp;0, pack_arr, jstr])
&nbsp; &nbsp; emu.in_verify =&nbsp;False

&nbsp; &nbsp; out_target =&nbsp;None
&nbsp; &nbsp; out_render =&nbsp;None
&nbsp; &nbsp; cmp_len =&nbsp;None
&nbsp; &nbsp;&nbsp;if&nbsp;emu.memcmp_records:
&nbsp; &nbsp; &nbsp; &nbsp; a, b, n, ba, bb = emu.memcmp_records[-1]
&nbsp; &nbsp; &nbsp; &nbsp; out_target = ba
&nbsp; &nbsp; &nbsp; &nbsp; out_render = bb
&nbsp; &nbsp; &nbsp; &nbsp; cmp_len = n

&nbsp; &nbsp;&nbsp;return&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ret': rv,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'exp': exp,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'gate': gate,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'key': key,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'last_score': last_score,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'memcmp_len': cmp_len,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'target': out_target,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'render': out_render,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'key_records': emu.key_records,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'g_5cfe0': emu.g_u64(0x5cfe0),
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'g_5cfe8': emu.g_u64(0x5cfe8),
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'g_5d004': emu.g_u32(0x5d004),
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'g_5d008': emu.g_u32(0x5d008),
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'g_5d00c': emu.g_u8(0x5d00c),
&nbsp; &nbsp; }

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp; r = run_case(0,&nbsp;0, do_clicks=False)
&nbsp; &nbsp;&nbsp;print('exp', r['exp'],&nbsp;'gate', r['gate'],&nbsp;'key',&nbsp;hex(r['key']))
&nbsp; &nbsp;&nbsp;print('5cfe0',&nbsp;hex(r['g_5cfe0']),&nbsp;'5cfe8',&nbsp;hex(r['g_5cfe8']))
&nbsp; &nbsp;&nbsp;print('5d004',&nbsp;hex(r['g_5d004']),&nbsp;'5d008', r['g_5d008'],&nbsp;'5d00c', r['g_5d00c'])
&nbsp; &nbsp;&nbsp;print('memcmp_len', r['memcmp_len'],&nbsp;'key_records', [hex(x)&nbsp;for&nbsp;x&nbsp;in&nbsp;r['key_records']])
&nbsp; &nbsp;&nbsp;if&nbsp;r['target']:
&nbsp; &nbsp; &nbsp; &nbsp; ones =&nbsp;sum(bin(x).count('1')&nbsp;for&nbsp;x&nbsp;in&nbsp;r['target'])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print('target ones', ones)
&nbsp; &nbsp; &nbsp; &nbsp; Path('_analysis/verify_target_repro.bin').write_bytes(r['target'])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print('wrote _analysis/verify_target_repro.bin')

说明:

  • 支持调用 verifyAndDecrypt 路径并抓取 memcmp
  • 支持 debug_bypass=1 分支验证。
  • 支持直接调用 2efd0 渲染位图。

0x08 最终答案

作弊模式开启后(setDebugBypass(true)),正确 flag:

FLAG{HJMWAPJ2026NBLD}


【春节】解题领红包之九 {Web 中级题} 出题老师:Coxxs

0x00 题目与目标

题目是一个“语音验证码”页面:

  • 前端入口:Q9/index.html
  • 逻辑脚本:Q9/assets/verify.js
  • Wasm 载体:Q9/assets/verify.wasm.js(内嵌 base64 wasm)

页面校验逻辑是:输入 flag{...},取中间 code,做 0x2026 次 SHA-256,和 challenge 哈希比较。

目标:还原 wasm_bindgen.gen(uid, voice) 中 50 位 code 的生成算法,复现出 flag{50位code}


0x01 入口 JavaScript 分析

先看 Q9/assets/verify.js

  1. wasm_bindgen.gen(uid, voice)

    返回对象:

  • a

    :音频 wav bytes

  • h

    :校验哈希(hex)

  1. checkCode(code, expectedHash)

  • code

    做 0x2026 次 SHA-256

  • 结果 hex 与 expectedHash 比较

因此本题本质是:从 wasm 里拿到真实 code 生成逻辑,而不是识别语音。


0x02 Wasm 静态定位

把 wasm 转 WAT(本地已导出为 Q9/wasm.txt),在 func $gen 里定位关键块。

1) 随机数与初始缓冲

Q9/wasm.txt:14219 开始:

  • var3+80

    处申请 17 字节

  • 调用 crypto.getRandomValues 填充 17 字节随机数

2) 37 字节工作缓冲 var9

Q9/wasm.txt:14579call $func77 申请 37 字节(var9)。

前 21 字节构造:

  • var9[0..3] = random[0..3] XOR uid_le[0..3]

  • 对应 14586~14617

  • var9[4..20] = random[0..16]

  • 对应 14620~14637(连续 copy)

3) HMAC 密钥提取(题目要求的 14 字节)

Q9/wasm.txt:14701~14703

  • 从内存偏移 1295967 拷贝 14 字节
  • 作为 HMAC key

提取结果:

  • hex: 0001010101010100010001000502
  • bytes: [0,1,1,1,1,1,1,0,1,0,1,0,5,2]

4) HMAC 过程

Q9/wasm.txt:14759~14887

  • 64 字节块先 XOR 0x36(ipad)
  • 后 XOR 0x6A(把 ipad 变 opad,等价 key^0x5c)
  • 调用 func9(SHA-256 压缩流程)

最终 digest 的前 16 字节写入 var9[21..36]15519 一带写回)。

即:

var9 = (random[0..3]^uid_le) + random17 + HMAC_SHA256(key, first21)[:16]


0x03 50 位 code 生成算法

关键循环在 Q9/wasm.txt:15627~15741

  • 字符表查表基址:i32.load8_u offset=1295903
  • 表内容:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!

  • 对 var9 的 37 字节做 6-bit 拆分:

  • 37 * 8 = 296 bit

  • 296 / 6 = 49 余 2

  • 补齐后得到 50 个 6-bit 索引

  • 每个索引查字符表,得到 50 位 code

这一步本质是“自定义字符表的 base64 风格编码”。


0x04 语音拼接流程(题目重点)

语音拼接入口在 Q9/wasm.txt:16335 附近,核心是反复 call $func34 扩容并 memory.copy

整体拼接顺序:

  1. 前缀音频(按 voice 选择固定片段)
  • voice='c'

    offset=1048577, len=76762

  • voice='y'

    offset=1211415, len=84480

  • voice='e'

    offset=1125339, len=86076

  • 对应 16318~16354

  1. flag{ 五个字符逐个转语音并拼接
  • 字符来源常量区 1295898 开始(UTF-8 读取循环)
  • 对应 16389~16593
  1. 50 位 code 每个字符转语音并拼接
  • 从前面生成的 50 字符数组读取
  • 对应 16594~16805
  1. } 字符转语音并拼接
  • 直接写入 codepoint 125
  • 对应 16807~16921
  1. 封装 WAV 头 + data 段
  • 写入 RIFF/WAVE/fmt /data 常量
  • 对应 16989 之后

结论:你的判断是对的——音频就是

"flag{" + 50位code + "}"

逐字符 TTS 后拼接。


0x05 Python 还原与复现

已在 Q9/算法.py 完整实现:

  • 固定密钥(14 字节)

  • 37 字节构造

  • wasm 同款 6-bit 提取

  • 0x2026

    次 SHA-256 校验哈希

关键函数:

  • build_37_bytes(...)
  • wasm_extract_50_chars(...)
  • hash_2026(...)

运行示例(固定演示随机数 00..10):

&nbsp;复制代码&nbsp;隐藏代码
python Q9/算法.py --uid 551842 --voice c --demo-seed0

输出:

  • code50=OMOkaWabaGmebqyhcaKkcWWndG8qSSAL1wczlV4Z9PEiq5cP!q
  • flag{OMOkaWabaGmebqyhcaKkcWWndG8qSSAL1wczlV4Z9PEiq5cP!q}
  • hash_2026=1aa787ab510dae05976a5553df8fd2506e821e09061d38de4d66646678143c8e

和 wasm 生成的 challenge 哈希一致。


0x06 关键结论

  1. code 的本体只由 uid + random17 + 固定14字节密钥 决定。

  2. voice

    仅影响语音素材选择,不影响 50 位 code 与哈希。

  3. 题目不是音频识别题,核心是 wasm 算法还原题。

  4. flag 结构固定为:

flag{<50 chars from [a-zA-Z0-9?!]>}


0x07 附:可复现单次 challenge 的方法

若你想复现“某一次页面生成出来的那条语音”对应 flag:

  1. Hook 当次 crypto.getRandomValues 取到 17 字节随机数。
  2. 拿到当次 uid
  3. 运行:
&nbsp;复制代码&nbsp;隐藏代码
python Q9/算法.py --uid <uid> --voice c --rand-hex <17字节hex>

即可得到当次正确 flag{...}

Q9/算法.py

&nbsp;复制代码&nbsp;隐藏代码
import&nbsp;argparse
import&nbsp;hashlib
import&nbsp;hmac
import&nbsp;os

KEY_14 =&nbsp;bytes([0,&nbsp;1,&nbsp;1,&nbsp;1,&nbsp;1,&nbsp;1,&nbsp;1,&nbsp;0,&nbsp;1,&nbsp;0,&nbsp;1,&nbsp;0,&nbsp;5,&nbsp;2])
CHARSET =&nbsp;"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!"

defbuild_37_bytes(uid:&nbsp;int, random17:&nbsp;bytes) ->&nbsp;bytes:
&nbsp; &nbsp;&nbsp;iflen(random17) !=&nbsp;17:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("random17 must be exactly 17 bytes")

&nbsp; &nbsp; uid_le = uid.to_bytes(4,&nbsp;"little", signed=False)
&nbsp; &nbsp; first4 =&nbsp;bytes(
&nbsp; &nbsp; &nbsp; &nbsp; [
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; random17[0] ^ uid_le[0],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; random17[1] ^ uid_le[1],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; random17[2] ^ uid_le[2],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; random17[3] ^ uid_le[3],
&nbsp; &nbsp; &nbsp; &nbsp; ]
&nbsp; &nbsp; )
&nbsp; &nbsp; first21 = first4 + random17

&nbsp; &nbsp; digest = hmac.new(KEY_14, first21, hashlib.sha256).digest()
&nbsp; &nbsp;&nbsp;return&nbsp;first21 + digest[:16]

defwasm_extract_50_chars(src37:&nbsp;bytes) ->&nbsp;str:
&nbsp; &nbsp;&nbsp;iflen(src37) !=&nbsp;37:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("src37 must be exactly 37 bytes")

&nbsp; &nbsp; out = []
&nbsp; &nbsp; var0 =&nbsp;0
&nbsp; &nbsp; var5 =&nbsp;1
&nbsp; &nbsp; var1 =&nbsp;0
&nbsp; &nbsp; var4_idx =&nbsp;0
&nbsp; &nbsp; var7 =&nbsp;0
&nbsp; &nbsp; var11 =&nbsp;0

&nbsp; &nbsp;&nbsp;whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp; b = src37[var4_idx]
&nbsp; &nbsp; &nbsp; &nbsp; var11 = b
&nbsp; &nbsp; &nbsp; &nbsp; var4_idx = var5
&nbsp; &nbsp; &nbsp; &nbsp; var7 = (b | ((var7 <<&nbsp;8) &&nbsp;0xFFFFFFFF)) &&nbsp;0xFFFFFFFF
&nbsp; &nbsp; &nbsp; &nbsp; var2 = var1

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var1 = var2 +&nbsp;2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; idx = (var7 >> var1) &&nbsp;0x3F
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out.append(CHARSET[idx])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var0 +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var2 -=&nbsp;6
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;var1 <=&nbsp;5:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp; &nbsp; &nbsp; var1 = var2 +&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;var5 ==&nbsp;37:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; var5 +=&nbsp;1

&nbsp; &nbsp;&nbsp;if&nbsp;var2 != -8:
&nbsp; &nbsp; &nbsp; &nbsp; idx = (var11 << (-2&nbsp;- var2)) &&nbsp;0x3F
&nbsp; &nbsp; &nbsp; &nbsp; out.append(CHARSET[idx])

&nbsp; &nbsp;&nbsp;iflen(out) !=&nbsp;50:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;RuntimeError(f"unexpected output length:&nbsp;{len(out)}")
&nbsp; &nbsp;&nbsp;return"".join(out)

defhash_2026(code:&nbsp;str) ->&nbsp;str:
&nbsp; &nbsp; cur = code.encode("ascii")
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(0x2026):
&nbsp; &nbsp; &nbsp; &nbsp; cur = hashlib.sha256(cur).digest()
&nbsp; &nbsp;&nbsp;return&nbsp;cur.hex()

defmain() ->&nbsp;None:
&nbsp; &nbsp; parser = argparse.ArgumentParser()
&nbsp; &nbsp; parser.add_argument("--uid",&nbsp;type=int, default=551842)
&nbsp; &nbsp; parser.add_argument("--voice", default="c")
&nbsp; &nbsp; parser.add_argument(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"--rand-hex",
&nbsp; &nbsp; &nbsp; &nbsp; default=None,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help="17-byte random in hex; if omitted, uses os.urandom(17)",
&nbsp; &nbsp; )
&nbsp; &nbsp; parser.add_argument(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"--demo-seed0",
&nbsp; &nbsp; &nbsp; &nbsp; action="store_true",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help="use fixed random 00..10 (matches JS hook demo)",
&nbsp; &nbsp; )
&nbsp; &nbsp; args = parser.parse_args()

&nbsp; &nbsp;&nbsp;if&nbsp;args.demo_seed0:
&nbsp; &nbsp; &nbsp; &nbsp; random17 =&nbsp;bytes(range(17))
&nbsp; &nbsp;&nbsp;elif&nbsp;args.rand_hex&nbsp;isNone:
&nbsp; &nbsp; &nbsp; &nbsp; random17 = os.urandom(17)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; random17 =&nbsp;bytes.fromhex(args.rand_hex)

&nbsp; &nbsp; src37 = build_37_bytes(args.uid, random17)
&nbsp; &nbsp; code50 = wasm_extract_50_chars(src37)
&nbsp; &nbsp; h = hash_2026(code50)

&nbsp; &nbsp;&nbsp;print(f"uid={args.uid}, voice='{args.voice}'")
&nbsp; &nbsp;&nbsp;print(f"random17={random17.hex()}")
&nbsp; &nbsp;&nbsp;print(f"code50={code50}")
&nbsp; &nbsp;&nbsp;print(f"flag{{{code50}}}")
&nbsp; &nbsp;&nbsp;print(f"hash_2026={h}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《【2026春节】解题领红包 【2-9】WP 通杀》

评论:0   参与:  0