吾爱破解2026春节所有题WP

admin 2026-03-26 16:41:53 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档是吾爱破解2026年春节所有题目的解题方法(WP)分享。内容涵盖了Windows、Android等多个平台的初级及中级题目,详细记录了使用IDA、ResourceHacker等工具进行逆向分析、查壳脱壳、反编译以及编写解密脚本的全过程,并最终给出了各个题目的flag答案。 综合评分: 85 文章分类: CTF,逆向分析,二进制安全,WEB安全,渗透测试


得到 flag:flag{EncrypTIoN_Is_haRd_52p0jIE_2o26_m62Tc4uj78maAq1C}

Android 中级题

通过 Java 层分析确认两件事:

  1. assets/hjm_pack.bin

    是关键资源。

  2. 输入字符串会进入 NativeBridge.verifyAndDecrypt(),其返回字节后续会被 h1.a.S(...) 解析。

  3. 成功判定点是:h1.a.S((byte[]) obj) == null 时走“验证成功”。

拖入 IDA 后确认:

| Java 方法 | Native 偏移 | | — | — | | startSessionBytes | 0x247b0 | | checkRhythm | 0x24da8 | | updateExp | 0x24ea4 | | decryptFrames | 0x2541c | | verifyAndDecrypt | 0x257dc | | setDebugBypass | 0x25c90 |

进入 verifyAndDecrypt 后发现不是直接字符串比较,而是:

  1. 解包得到目标位图
  2. 将输入字符串转位图
  3. 比较位图

进一步分析得到关键函数:

  • sub_2dcdc:key 生成
  • sub_2ddf8:version=2 解包核心
  • 输出改写后的 pack 数据并还原位图读文本

Unicorn 中跑最小链路,直接执行:

 复制代码 隐藏代码
sub_2dcdc
sub_2ddf8

拿到改写后的 pack 数据。 分析前 4 个字节:48 4A 4D 31 ,即:HJM1

解析脚本:

 复制代码 隐藏代码
import struct
from pathlib import Path

FILE_PATH = "pack.dec.bin"

defunpack_1bpp(frame: bytes, w: int, h: int) -> bytes:
    out = bytearray(w * h)
    for i inrange(w * h):
        b = frame[i >> 3]
        out[i] = (b >> (7 - (i & 7))) & 1
    returnbytes(out)

defrender(bits: bytes, w: int, h: int, on="#", off=" "):
    for y inrange(h):
        row = bits[y * w:(y + 1) * w]
        print("".join(on if p else off for p in row))

defmain():
    buf = Path(FILE_PATH).read_bytes()
    if buf[:4] != b"HJM1":
        raise ValueError(f"not HJM1 file, len={len(buf)}")

&nbsp; &nbsp; ver, frames, w, h = struct.unpack("<4I", buf[4:20])
&nbsp; &nbsp; frame_bytes = (w * h +&nbsp;7) //&nbsp;8
&nbsp; &nbsp; need = frames * frame_bytes
&nbsp; &nbsp;&nbsp;iflen(buf) < need:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"file too short, len={len(buf)}, need={need}")

&nbsp; &nbsp; payload_off =&nbsp;len(buf) - need
&nbsp; &nbsp; frame0 = buf[payload_off:payload_off + frame_bytes]

&nbsp; &nbsp;&nbsp;print(f"HJM1 v{ver}, frames={frames}, w={w}, h={h}, payload_off=0x{payload_off:x}")
&nbsp; &nbsp; bits = unpack_1bpp(frame0, w, h)
&nbsp; &nbsp; render(bits, w, h)

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

得到 flag:FLAG{HJMWAPJ2026NBLD}

Web 中级题

先看 verify.js :

  1. 点击“生成验证码”会调用 wasm_bindgen.gen(uid, voice)
  2. 生成后会把 currentHash 赋值为 challenge.h。
  3. 点击“提交”时执行 checkCode(code, currentHash)
  4. checkCode 逻辑是:code 连续做 0x2026 次 SHA-256,再和 currentHash 比较。
  5. WASM 侧随机输入来自 crypto.getRandomValues,长度固定为 17 字节(可 hook 验证)。

所以核心不是听语音,而是拿到当前轮次的 code,即可构造 flag{code}。

生成验证码:

&nbsp;复制代码&nbsp;隐藏代码
const&nbsp;challenge = wasm_bindgen.gen(uid, voice)
&nbsp;currentHash = challenge.h

提交:

&nbsp;复制代码&nbsp;隐藏代码
checkCode(code, currentHash)

关键常量从 wasm.memory.buffer 读取:

  1. 映射表:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!。
  2. 14 字节 key:00 01 01 01 01 01 01 00 01 00 01 00 05 02。

一把梭:

&nbsp;复制代码&nbsp;隐藏代码
(() =>&nbsp;{
&nbsp;&nbsp;const&nbsp;keyBytes =&nbsp;Uint8Array.from([0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x02]);
&nbsp;&nbsp;const&nbsp;table =&nbsp;"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!";
&nbsp;&nbsp;let&nbsp;lastRand =&nbsp;null;

&nbsp;&nbsp;if&nbsp;(!window.__grv_hooked__) {
&nbsp; &nbsp;&nbsp;const&nbsp;orig = crypto.getRandomValues.bind(crypto);
&nbsp; &nbsp; crypto.getRandomValues&nbsp;=&nbsp;(arr) =>&nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;ret =&nbsp;orig(arr);
&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(arr && arr.length&nbsp;===&nbsp;17) lastRand =&nbsp;newUint8Array(arr);
&nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;ret;
&nbsp; &nbsp; };
&nbsp; &nbsp;&nbsp;window.__grv_hooked__&nbsp;=&nbsp;true;
&nbsp; }

&nbsp;&nbsp;functiondecodeFromJ(j) {
&nbsp; &nbsp;&nbsp;let&nbsp;a =&nbsp;0, f =&nbsp;1, b =&nbsp;0, h =&nbsp;0, c =&nbsp;0, i =&nbsp;0, l =&nbsp;0;
&nbsp; &nbsp;&nbsp;const&nbsp;out = [];
&nbsp; &nbsp;&nbsp;while&nbsp;(true) {
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;t0 = j[f -&nbsp;1];
&nbsp; &nbsp; &nbsp; l = t0;
&nbsp; &nbsp; &nbsp; h = (l | (h <<&nbsp;8));
&nbsp; &nbsp; &nbsp; c = b;
&nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(true) {
&nbsp; &nbsp; &nbsp; &nbsp; i = (h >> (b = c +&nbsp;2)) &&nbsp;63;
&nbsp; &nbsp; &nbsp; &nbsp; out.push(table[i]);
&nbsp; &nbsp; &nbsp; &nbsp; a++;
&nbsp; &nbsp; &nbsp; &nbsp; c -=&nbsp;6;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(b >&nbsp;5)&nbsp;continue;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; b = c +&nbsp;8;
&nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;cont = f !==&nbsp;37;
&nbsp; &nbsp; &nbsp; f += cont ?&nbsp;1&nbsp;:&nbsp;0;
&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!cont)&nbsp;break;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(c !== -8) out.push(table[(l << (-2&nbsp;- c)) &&nbsp;63]);
&nbsp; &nbsp;&nbsp;return&nbsp;out.join("");
&nbsp; }

&nbsp;&nbsp;asyncfunctionhmac16(first21) {
&nbsp; &nbsp;&nbsp;const&nbsp;key =&nbsp;await&nbsp;crypto.subtle.importKey("raw", keyBytes, {&nbsp;name:&nbsp;"HMAC",&nbsp;hash:&nbsp;"SHA-256"&nbsp;},&nbsp;false, ["sign"]);
&nbsp; &nbsp;&nbsp;const&nbsp;sig =&nbsp;await&nbsp;crypto.subtle.sign("HMAC", key, first21);
&nbsp; &nbsp;&nbsp;returnnewUint8Array(sig).slice(0,&nbsp;16);
&nbsp; }

&nbsp;&nbsp;window.genFlag&nbsp;=&nbsp;async&nbsp;(uid =&nbsp;2355817, voice =&nbsp;"c") => {
&nbsp; &nbsp;&nbsp;document.getElementById("uid").value&nbsp;=&nbsp;String(uid);
&nbsp; &nbsp;&nbsp;document.getElementById("voice").value&nbsp;= voice;

&nbsp; &nbsp; lastRand =&nbsp;null;
&nbsp; &nbsp;&nbsp;document.getElementById("checkbox-text").click();
&nbsp; &nbsp;&nbsp;awaitnewPromise(r&nbsp;=>setTimeout(r,&nbsp;1200));
&nbsp; &nbsp;&nbsp;if&nbsp;(!lastRand)&nbsp;thrownewError("No random number captured");

&nbsp; &nbsp;&nbsp;const&nbsp;j =&nbsp;newUint8Array(37);
&nbsp; &nbsp; j[0] = lastRand[0] ^ (uid &&nbsp;0xff);
&nbsp; &nbsp; j[1] = lastRand[1] ^ ((uid >>>&nbsp;8) &&nbsp;0xff);
&nbsp; &nbsp; j[2] = lastRand[2] ^ ((uid >>>&nbsp;16) &&nbsp;0xff);
&nbsp; &nbsp; j[3] = lastRand[3] ^ ((uid >>>&nbsp;24) &&nbsp;0xff);
&nbsp; &nbsp; j.set(lastRand.slice(0,&nbsp;8),&nbsp;4);
&nbsp; &nbsp; j.set(lastRand.slice(8,&nbsp;16),&nbsp;12);
&nbsp; &nbsp; j[20] = lastRand[16];
&nbsp; &nbsp; j.set(awaithmac16(j.slice(0,&nbsp;21)),&nbsp;21);

&nbsp; &nbsp;&nbsp;const&nbsp;code =&nbsp;decodeFromJ(j);
&nbsp; &nbsp;&nbsp;const&nbsp;flag =&nbsp;`flag{${code}}`;
&nbsp; &nbsp;&nbsp;document.getElementById("verifyInput").value&nbsp;= flag;
&nbsp; &nbsp;&nbsp;return&nbsp;{ flag, code };
&nbsp; };
})();

使用:

&nbsp;复制代码&nbsp;隐藏代码
awaitgenFlag(uid,&nbsp;"c")

Windows 高级题

最折磨我的一题。

一开始我发现有 UPX 壳,想着先自动脱壳试试,结果脱下来了。 我还纳闷,拖进 IDA 发现,原来是控制流混淆等着我呢。

打开字符串果然加密了,但是我在 Imports 中找到了 WideCharToMultiByte 函数,xref 定位到 sub_1400CD490 验证入口。

分析入口得到关键函数:

  1. sub_1400CF090:把输入 flag 的 hex 文本转字节。
  2. sub_1400CF270:长度校验(必须是 0x40 字节)。
  3. sub_1400CF910:核心校验调度(含反调试)。
  4. sub_1400FD790:目标值生成(混淆太严重)。
  5. sub_1400D3B20:最终 64 字节比较点。

sub_1400CD490

在 sub_1400CF090 汇编里面明确看到:

  1. 先把输入的长度除以 2(每两位 hex 组成 1 字节)
  2. 分支判断 0-9 / a-f / A-F。
  3. 组合方式为 (high << 4) | low 写入输出缓冲。

&nbsp;复制代码&nbsp;隐藏代码
loc_1400CF0F5:
shl&nbsp; &nbsp; &nbsp;bpl,&nbsp;4
or&nbsp; &nbsp; &nbsp;&nbsp;bpl,&nbsp;dl
cmp&nbsp; &nbsp; &nbsp;rax,&nbsp;rdi
jz&nbsp; &nbsp; &nbsp; short loc_1400CF140

sub_1400CF270 虽然有点混淆,但关键点非常直白。长度必须是 0x40 字节,结合前面分析得到:

&nbsp;复制代码&nbsp;隐藏代码
解码后长度必须是 =&nbsp;64&nbsp;字节
输入长度必须是 =&nbsp;128&nbsp;个&nbsp;hex&nbsp;字符

sub_1400CF910 汇编内可见:

  1. 多处反调试。
  2. 目标缓冲生成后进入最终比较调用。
  3. 比较前明确 mov r8d, 40h,随后 call sub_1400D3B20

结论:输入的 flag 必须是 128 个 hex 字符(解码后 64 字节)。

知道这些信息后,就无需再做静态分析,直接上 x64dbg 动态调试。 前面分析知道是有反调试的,但 ScyllaHide 基本能过掉,也不用操心。 直接在最终比较点 sub_1400D3B20 下断点。程序跑起来后输入uid 和错的 flag(128 hex),点击验证 flag,程序断在比较函数。 此时在寄存器 RCX/RDX -> 在转储中跟随,RCX是刚才输入的,RDX是真实的 flag。

比如我的 uid 是:2355817 ,那么对应 flag 就是:06401594023537c80f8cffede100b0fd4cb3bb4c6feb890bc8dabafd24f68d92b16084f2ae4ea6cc3567eea9a91e90c364e7f620407304b820ac44ccb6db6987

注册机:

&nbsp;复制代码&nbsp;隐藏代码
import&nbsp;frida
import&nbsp;glob
import&nbsp;time

# 你的 uid
uid =&nbsp;"uid"

targets = glob.glob(
&nbsp; &nbsp;&nbsp;"【2026春节】解题领红包之十 {Windows 高级题} 出题老师:Poner.exe")
ifnot&nbsp;targets:
&nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit("target exe not found")
target = targets[0]

js =&nbsp;r'''
const base = Process.enumerateModules()[0].base;
function p(off){ return base.add(off); }

const f_cd490 = new NativeFunction(p(0xCD490), 'uint64', ['pointer','pointer']);
const g_b418 = p(0x2632418);
const g_b419 = p(0x2632419);

let lastY = '';

function toHex(ptr, n){
&nbsp; const u = new Uint8Array(ptr.readByteArray(n)); &nbsp;let s = ''; &nbsp;for (let i = 0; i < u.length; i++) { &nbsp; &nbsp;let h = u[i].toString(16); &nbsp; &nbsp;if (h.length < 2) h = '0' + h; &nbsp; &nbsp;s += h; &nbsp;} &nbsp;return s;}

Interceptor.attach(p(0xD3B20), {
&nbsp; onEnter(args){ &nbsp; &nbsp;lastY = toHex(args[1], 64); &nbsp;}});

Interceptor.attach(p(0x0C1B90), { onLeave(ret){ ret.replace(ptr(0)); } });
Interceptor.attach(p(0x09B30), &nbsp;{ onLeave(ret){ ret.replace(ptr(0)); } });

rpc.exports = {
&nbsp; derive(uid){ &nbsp; &nbsp;g_b418.writeU8(0); &nbsp; &nbsp;g_b419.writeU8(0); &nbsp; &nbsp;lastY = ''; &nbsp; &nbsp;const pUid = Memory.allocUtf16String(uid); &nbsp; &nbsp;const pDummy = Memory.allocUtf16String('00'.repeat(64)); &nbsp; &nbsp;f_cd490(pUid, pDummy); &nbsp; &nbsp;return lastY; &nbsp;}, &nbsp;verify(uid, flag){ &nbsp; &nbsp;g_b418.writeU8(0); &nbsp; &nbsp;g_b419.writeU8(0); &nbsp; &nbsp;const pUid = Memory.allocUtf16String(uid); &nbsp; &nbsp;const pFlag = Memory.allocUtf16String(flag); &nbsp; &nbsp;return f_cd490(pUid, pFlag).toString(); &nbsp;}};
'''

pid = frida.spawn([target])
session = frida.attach(pid)
script = session.create_script(js)
script.load()
frida.resume(pid)
time.sleep(1)

api = script.exports_sync
flag = api.derive(uid)
ret = api.verify(uid, flag)

print("uid =", uid)
print("flag =", flag)
print("verify_ret =", ret)

frida.kill(pid)

MCP 中级题

提示词发给 AI :

&nbsp;复制代码&nbsp;隐藏代码
ctf_request&nbsp;填的是口令本身,不是 access_token;
audit_log_id 一定要用“被拒访问时返回的完整编号”,别截断别改;
而且只认“同一会话里最近那次拒绝”出来的编号,跨会话或旧编号都不行;
复核这段链路别配太杂,越单一越不容易断;
复核凭据是有时效、且一次性的,失败后要重新触发拒绝再拿新编号;
另外,复核阶段拿到的凭据只是打通流程,最终读密卷还需要后续凭据。

最终跑出 flag:flag{new_year_2026_keep_warm}

-官方论坛

www.52pojie.cn

👆👆👆

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

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


免责声明:

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

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

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

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《吾爱破解 2026 春节所有题 WP》

评论:0   参与:  0