<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lkaros的小世界呀]]></title><description><![CDATA[哈喽~欢迎光临]]></description><link>https://lkaros.xin</link><image><url>https://lkaros.xin/favicon.ico</url><title>Lkaros的小世界呀</title><link>https://lkaros.xin</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Fri, 31 Jul 2026 09:34:33 GMT</lastBuildDate><atom:link href="https://lkaros.xin/feed" rel="self" type="application/rss+xml"/><pubDate>Fri, 31 Jul 2026 09:34:33 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[关于wp里太多图片问题]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/notes/4">https://lkaros.xin/notes/4</a></blockquote><span>在御网杯的wp中，刚点开会因为图片太多卡死，不知道为什么 擦</span><p style="text-align:right"><a href="https://lkaros.xin/notes/4#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/notes/4</link><guid isPermaLink="true">https://lkaros.xin/notes/4</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Mon, 01 Jun 2026 06:10:07 GMT</pubDate></item><item><title><![CDATA[2026御网杯WriteUp]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://lkaros.xin/posts/default/ywb">https://lkaros.xin/posts/default/ywb</a></p>]]></description><link>https://lkaros.xin/posts/default/ywb</link><guid isPermaLink="true">https://lkaros.xin/posts/default/ywb</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Sun, 31 May 2026 15:44:48 GMT</pubDate></item><item><title><![CDATA[Codex SSH配置]]></title><description><![CDATA[<link rel="preload" as="image" href="https://lkaros.xin/shiro/blog/2026/05/rxmtqdo6rdihpevugv.png"/><link rel="preload" as="image" href="https://lkaros.xin/shiro/blog/2026/05/64tuxu9g2lf5lxnk6j.png"/><link rel="preload" as="image" href="https://lkaros.xin/shiro/blog/2026/05/r58s62gvchp3c09hmk.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/1">https://lkaros.xin/posts/default/1</a></blockquote><div><h2 id="windows">Windows：</h2><p>我的虚拟机上只测试了Windows11 x64和Windows 10 x64，其他版本我没测试过应该不行，毕竟codex似乎要装在win10系统以上</p>
<h4 id="1ssh-">1.开启ssh 服务器</h4><p><img src="https://lkaros.xin/shiro/blog/2026/05/rxmtqdo6rdihpevugv.png" height="1250" width="2262"/>
<img src="https://lkaros.xin/shiro/blog/2026/05/64tuxu9g2lf5lxnk6j.png"/></p>
<h4 id="2-win10---powershell-">2.在 Win10 用 管理员 PowerShell 执行：</h4><p>开机自启与启动SSH</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Set-Service -Name sshd -StartupType Automatic
Start-Service sshd</code></pre><p>顺便放行防火墙端口 22：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">New-NetFirewallRule -Name sshd -DisplayName &quot;OpenSSH Server&quot; -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22</code></pre><p>检查状态：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">Get-Service sshd</code></pre><p>看到 Running 就说明 SSH 服务已启动，并且之后会开机自启。</p><p>这时候你就可以用Codex连接你的虚拟机了，让他给你装一下codex，Linux系统安装Codex CLI</p><h1 id="">总结</h1><p><img src="https://lkaros.xin/shiro/blog/2026/05/r58s62gvchp3c09hmk.png"/></p><p>只是近期更新了这么个东西，以防找不到命令，所以写一篇水文（嘿嘿）</p></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/1</link><guid isPermaLink="true">https://lkaros.xin/posts/default/1</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Sat, 23 May 2026 11:43:27 GMT</pubDate></item><item><title><![CDATA[第二届“云天杯”网络安全大赛部分WriteUp]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/ytb">https://lkaros.xin/posts/default/ytb</a></blockquote><div><h1 id="challenge">Challenge</h1><p>---</p><h2 id="">一、程序主逻辑分析</h2><p>IDA 反编译 <code>main</code> 后，可以看到程序核心逻辑如下：</p><pre class="language-c lang-c"><code class="language-c lang-c">int __fastcall main(int argc, const char **argv, const char **envp)
{
  char key;
  _QWORD *addr;
  int i;

  addr = mmap(0, 0x48u, 7, 34, -1, 0);
  if (addr == -1LL)
  {
    perror(&quot;mmap&quot;);
    return 1;
  }

  *addr = enc_shellcode;
  addr[1] = qword_404088;
  addr[2] = qword_404090;
  addr[3] = qword_404098;
  addr[4] = qword_4040A0;
  addr[5] = qword_4040A8;
  addr[6] = qword_4040B0;
  addr[7] = qword_4040B8;
  addr[8] = qword_4040C0;

  key = get_key();

  for (i = 0; i &lt;= 0x47; ++i)
    *((_BYTE *)addr + i) ^= key;

  ((void(*)())addr)();

  munmap(addr, 0x48u);
  return 0;
}</code></pre><p>程序先通过 <code>mmap</code> 申请一段可读、可写、可执行的内存，然后把 <code>enc_shellcode</code> 拷贝进去。</p><p>之后调用 <code>get_key()</code> 获取 key，并对整段 shellcode 做异或解密：</p><pre class="language-c lang-c"><code class="language-c lang-c">*((_BYTE *)addr + i) ^= key;</code></pre><p>最后直接执行解密后的 shellcode：</p><pre class="language-c lang-c"><code class="language-c lang-c">((void(*)())addr)();</code></pre><hr/><h2 id="-key-">二、第一层 key 分析</h2><p>继续查看 <code>get_key()</code>：</p><pre class="language-c lang-c"><code class="language-c lang-c">__int64 get_key()
{
  int n0x1337;
  int i;
  unsigned int n0xA3;

  n0xA3 = 0xA3;

  if (is_debugged())
    n0xA3 = 0xA2;

  n0x1337 = 0x1337;

  for (i = 0; i &lt;= 4; ++i)
    n0x1337 ^= n0xA3 &lt;&lt; i;

  return n0xA3;
}</code></pre><p>正常情况下：</p><pre class="language-c lang-c"><code class="language-c lang-c">n0xA3 = 0xA3;</code></pre><p>如果检测到调试器，则 key 会变成：</p><pre class="language-c lang-c"><code class="language-c lang-c">n0xA3 = 0xA2;</code></pre><p>因此程序存在反调试干扰。
不调试运行时，第一层 shellcode 解密 key 为：</p><pre class="language-text lang-text"><code class="language-text lang-text">0xA3</code></pre><p>所以第一层解密逻辑为：</p><pre class="language-python lang-python"><code class="language-python lang-python">
shellcode = bytes(x ^ 0xA3 for x in enc_shellcode)</code></pre><p>---</p><h2 id="-shellcode">三、提取出的 shellcode</h2><p>第一层解密后，得到如下 shellcode：</p><pre class="language-python lang-python"><code class="language-python lang-python">sc = b&quot;\xeb,^\xbf\x01\x00\x00\x00\xba\x14\x00\x00\x001\xc9\x8a\x04\x0e4Z\x88\x04\x0eH\xff\xc1H9\xd1u\xf0\xb8\x01\x00\x00\x00\x0f\x05\xb8&lt;\x00\x00\x001\xff\x0f\x05\xe8\xcf\xff\xff\xff\x03\x0e\x18!\x035/\x05\x1b(?\x05\r344?(&#x27;P&quot;</code></pre><hr/><h2 id="shellcode-">四、shellcode 反汇编</h2><p>可以使用 pwntools 将 shellcode 转成汇编：</p><pre class="language-python lang-python"><code class="language-python lang-python">from pwn import *

context.arch = &quot;amd64&quot;

sc = b&quot;\xeb,^\xbf\x01\x00\x00\x00\xba\x14\x00\x00\x001\xc9\x8a\x04\x0e4Z\x88\x04\x0eH\xff\xc1H9\xd1u\xf0\xb8\x01\x00\x00\x00\x0f\x05\xb8&lt;\x00\x00\x001\xff\x0f\x05\xe8\xcf\xff\xff\xff\x03\x0e\x18!\x035/\x05\x1b(?\x05\r344?(&#x27;P&quot;

print(disasm(sc[:0x34]))</code></pre><p>反汇编结果：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">0:   eb 2c                   jmp    0x2e
2:   5e                      pop    rsi
3:   bf 01 00 00 00          mov    edi, 0x1
8:   ba 14 00 00 00          mov    edx, 0x14
d:   31 c9                   xor    ecx, ecx
f:   8a 04 0e                mov    al, BYTE PTR [rsi+rcx]
12:  34 5a                   xor    al, 0x5a
14:  88 04 0e                mov    BYTE PTR [rsi+rcx], al
17:  48 ff c1                inc    rcx
1a:  48 39 d1                cmp    rcx, rdx
1d:  75 f0                   jne    0xf
1f:  b8 01 00 00 00          mov    eax, 0x1
24:  0f 05                   syscall
26:  b8 3c 00 00 00          mov    eax, 0x3c
2b:  31 ff                   xor    edi, edi
2d:  0f 05                   syscall
2f:  e8 cf ff ff ff          call   0x3</code></pre><hr/><h2 id="shellcode-">五、shellcode 逻辑分析</h2><h3 id="1-jmp-call-pop-">1. jmp-call-pop 结构</h3><p>开头：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">0: eb 2c      jmp 0x2e</code></pre><p>程序会先跳到后面的 <code>call</code> 指令：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">2f: e8 cf ff ff ff    call 0x3</code></pre><p><code>call</code> 指令会把下一条指令的地址压入栈中，然后跳回前面的代码。</p><p>后面紧接着就是加密 flag 数据，因此 <code>call</code> 压入栈中的地址就是加密 flag 的起始地址。</p><p>跳回前面后执行：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">2: 5e    pop rsi</code></pre><p>于是：</p><pre class="language-text lang-text"><code class="language-text lang-text">rsi = 加密 flag 数据地址</code></pre><hr/><h3 id="2--xor-">2. 第二层 XOR 解密</h3><p>核心循环如下：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">8:   ba 14 00 00 00          mov    edx, 0x14
d:   31 c9                   xor    ecx, ecx

f:   8a 04 0e                mov    al, BYTE PTR [rsi+rcx]
12:  34 5a                   xor    al, 0x5a
14:  88 04 0e                mov    BYTE PTR [rsi+rcx], al
17:  48 ff c1                inc    rcx
1a:  48 39 d1                cmp    rcx, rdx
1d:  75 f0                   jne    0xf</code></pre><p>对应 C 伪代码：</p><pre class="language-c lang-c"><code class="language-c lang-c">for (int i = 0; i &lt; 0x14; i++)
{
    data[i] ^= 0x5A;
}</code></pre><p>这里可以看到第二层 key 来自指令：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">34 5a    xor al, 0x5a</code></pre><p>所以第二层解密 key 是：</p><pre class="language-text lang-text"><code class="language-text lang-text">0x5A</code></pre><hr/><h3 id="3--flag">3. 输出 flag</h3><p>解密完成后，执行：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">1f:  b8 01 00 00 00          mov    eax, 0x1
24:  0f 05                   syscall</code></pre><p>在 Linux x86_64 中：</p><pre class="language-text lang-text"><code class="language-text lang-text">rax = 1  -&gt; sys_write
rdi = 1  -&gt; stdout
rsi      -&gt; flag 地址
rdx = 0x14 -&gt; 输出长度</code></pre><p>所以这部分等价于：</p><pre class="language-c lang-c"><code class="language-c lang-c">write(1, flag, 0x14);</code></pre><p>之后执行：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">26:  b8 3c 00 00 00          mov    eax, 0x3c
2b:  31 ff                   xor    edi, edi
2d:  0f 05                   syscall</code></pre><p>这对应：</p><pre class="language-c lang-c"><code class="language-c lang-c">exit(0);</code></pre><hr/><h2 id="-flag-">六、加密 flag 数据</h2><p>shellcode 偏移 <code>0x34</code> 之后是加密数据：</p><pre class="language-text lang-text"><code class="language-text lang-text">03 0e 18 21 03 35 2f 05 1b 28 3f 05 0d 33 34 34 3f 28 27 50</code></pre><p>长度为：</p><pre class="language-text lang-text"><code class="language-text lang-text">0x14 = 20</code></pre><p>解密方式：</p><pre class="language-python lang-python"><code class="language-python lang-python">flag = bytes(x ^ 0x5A for x in enc_flag)</code></pre><p>其中最后一个字节：</p><pre class="language-text lang-text"><code class="language-text lang-text">0x50 ^ 0x5A = 0x0A</code></pre><p>也就是换行符。</p><hr/><h2 id="-exp">七、完整 exp</h2><pre class="language-python lang-python"><code class="language-python lang-python"># 从程序中提取出的 enc_shellcode
exp = bytes.fromhex(
    &quot;488ffd1ca2a3a3a319b7a3a3a3926a29a7ad97f92ba7adeb5c62eb9a72d6531b&quot;
    &quot;a2a3a3a3aca61b9fa3a3a3925caca64b6c5c5c5ca0adbb82a0968ca6b88b9&quot;
    &quot;ca6ae9097979c8b84f3&quot;
)

# 第一层：main 中 get_key 正常返回 0xA3
shellcode = bytes(x ^ 0xA3 for x in exp)

# 第二层：shellcode 中 xor al, 0x5a
key2 = 0x5A

# shellcode 偏移 0x34 开始是加密 flag
enc_flag = shellcode[0x34:0x34 + 0x14]

flag = bytes(x ^ key2 for x in enc_flag)

print(flag.decode().strip())</code></pre><p>运行结果：</p><pre class="language-text lang-text"><code class="language-text lang-text">YTB{You_Are_Winner}</code></pre><h1 id="re-mid">re-mid</h1><h2 id="solution">Solution</h2><h3 id="step-1-">Step 1: 定位校验函数</h3><p>在 <code>main</code> 中可以看到程序逻辑很直接：</p><pre class="language-c lang-c"><code class="language-c lang-c">printf(&quot;Enter the flag: &quot;);
fgets(s, 100, stdin);
s[strcspn(s, &quot;\n&quot;)] = 0;
if ( check_flag(s) )
    puts(&quot;Correct! The flag is yours.&quot;);
else
    puts(&quot;Wrong flag. Try again.&quot;);</code></pre><p>继续分析 <code>check_flag</code>，首先判断输入长度：</p><pre class="language-c lang-c"><code class="language-c lang-c">n = strlen(input);
if ( n != 23 )
    return 0;</code></pre><p>随后对输入执行 4 步变换：</p><pre class="language-c lang-c"><code class="language-c lang-c">buf[i] = input[22 - i];
buf[i] = (buf[i] + i) &amp; 0xff;
buf[i] ^= 0xAA;
buf[i] = (69 * buf[i] + 51) &amp; 0xff;</code></pre><p>最后将变换后的 23 字节与栈上的目标常量比较。</p><h3 id="step-2-">Step 2: 提取目标字节</h3><p>IDA 中对应的目标常量写入如下：</p><pre class="language-asm lang-asm"><code class="language-asm lang-asm">mov rax, 43FED8123A20DB26h
mov rdx, 0BB8585E6123A7DD2h
mov [rbp+var_50], rax
mov qword ptr [rbp+var_48], rdx
mov rax, 4CC21FE7B9590FBBh
mov qword ptr [rbp+var_48+7], rax</code></pre><p>由于 x86-64 是小端序，并且第三次写入从 <code>var_48 + 7</code> 开始，会覆盖前一段的最后 1 字节，因此目标字节为：</p><pre class="language-text lang-text"><code class="language-text lang-text">26 db 20 3a 12 d8 fe 43 d2 7d 3a 12 e6 85 85 bb 0f 59 b9 e7 1f c2 4c</code></pre><h3 id="step-3--flag">Step 3: 逆向恢复 flag</h3><p>变换中乘以 <code>69</code> 是在模 256 下进行的，<code>69</code> 与 <code>256</code> 互素，逆元为 <code>141</code>。因此可以逐字节逆运算：</p><pre class="language-python lang-python"><code class="language-python lang-python">expected = bytearray(23)
expected[0:8] = (0x43FED8123A20DB26).to_bytes(8, &quot;little&quot;)
expected[8:16] = (0xBB8585E6123A7DD2).to_bytes(8, &quot;little&quot;)
expected[15:23] = (0x4CC21FE7B9590FBB).to_bytes(8, &quot;little&quot;)

inv69 = pow(69, -1, 256)

reversed_input = []
for i, c in enumerate(expected):
    x = ((c - 51) * inv69) &amp; 0xff
    x ^= 0xAA
    x = (x - i) &amp; 0xff
    reversed_input.append(x)

flag = bytes(reversed_input[::-1]).decode()
print(flag)</code></pre><p>运行结果：</p><pre class="language-text lang-text"><code class="language-text lang-text">YTB{R3v3rs1ng_1s_Fun!!}</code></pre></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/ytb#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/ytb</link><guid isPermaLink="true">https://lkaros.xin/posts/default/ytb</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Fri, 22 May 2026 10:00:10 GMT</pubDate></item><item><title><![CDATA[2026年“星芒杯”高校联盟网络安全挑战赛 WriteUp]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/smb">https://lkaros.xin/posts/default/smb</a></blockquote><div><p>具体 WriteUp 如下：</p><div style="width:100%;height:85vh;min-height:640px;border:1px solid rgba(127,127,127,.25);border-radius:8px;overflow:hidden;background:#fff"><iframe src="https://lkaros.xin/api/v2/objects/file/roa3ayxkt017slk2hv.pdf#toolbar=1&amp;navpanes=0&amp;scrollbar=1" title="星芒杯 WriteUp PDF" style="width:100%;height:100%;border:0" loading="lazy"></iframe></div><a href="https://lkaros.xin/api/v2/objects/file/roa3ayxkt017slk2hv.pdf" target="_blank" rel="noreferrer">如果 PDF 没有显示，点这里打开或下载</a></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/smb#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/smb</link><guid isPermaLink="true">https://lkaros.xin/posts/default/smb</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Sun, 26 Apr 2026 06:36:56 GMT</pubDate></item><item><title><![CDATA[2026蓝桥杯 Reverse WriteUp]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/lqb">https://lkaros.xin/posts/default/lqb</a></blockquote><div><h2 id="">题目信息</h2><p>附件包含两个文件：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">$ file veil_gate panel.dat
veil_gate: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped
panel.dat: data</code></pre><p>运行程序会读取当前目录下的 <code>panel.dat</code>，然后从标准输入读取 flag：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">$ ./veil_gate
xxx
no</code></pre><p>正确输入时输出：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">ok</code></pre><p>最终 flag：</p><pre class="language-text lang-text"><code class="language-text lang-text">flag{9d7a228ca5825bc15cd60bee0bb6d585}</code></pre><hr/><h2 id="1-">1. 初步分析</h2><p><code>strings</code> 可以看到几个关键信息：</p><pre class="language-text lang-text"><code class="language-text lang-text">TPK2
panel.dat
load failed
read failed
no
ok</code></pre><p>说明程序核心逻辑是：</p><ol start="1"><li>打开 <code>panel.dat</code></li><li>检查文件头 <code>TPK2</code></li><li>从 <code>panel.dat</code> 中读取若干参数和查表数据</li><li>读取用户输入</li><li>对输入做变换并与 <code>panel.dat</code> 内的目标值比较</li></ol><p><code>panel.dat</code> 大小为 <code>420</code> 字节，开头如下：</p><pre class="language-text lang-text"><code class="language-text lang-text">54 50 4b 32 7c fa 7c 1b ...</code></pre><p>前 4 字节是魔数：</p><pre class="language-text lang-text"><code class="language-text lang-text">TPK2</code></pre><hr/><h2 id="2-paneldat-">2. panel.dat 格式解析</h2><p>逆向 <code>veil_gate</code> 后可以看出，<code>panel.dat</code> 结构大致如下：</p><table><thead><tr><th style="text-align:right">Offset</th><th style="text-align:right">Size</th><th>含义</th></tr></thead><tbody><tr><td style="text-align:right"><code>0x00</code></td><td style="text-align:right">4</td><td>magic，固定为 <code>TPK2</code></td></tr><tr><td style="text-align:right"><code>0x04</code></td><td style="text-align:right">4</td><td>加密后的 header</td></tr><tr><td style="text-align:right"><code>0x08</code></td><td style="text-align:right">24</td><td>key material</td></tr><tr><td style="text-align:right"><code>0x20</code></td><td style="text-align:right">128</td><td>table A</td></tr><tr><td style="text-align:right"><code>0xa0</code></td><td style="text-align:right">128</td><td>table B</td></tr><tr><td style="text-align:right"><code>0x120</code></td><td style="text-align:right">64</td><td>table C</td></tr><tr><td style="text-align:right"><code>0x160</code></td><td style="text-align:right">64</td><td>target</td></tr><tr><td style="text-align:right"><code>0x1a0</code></td><td style="text-align:right">4</td><td>seed</td></tr></tbody></table><p>header 会与常量 <code>0x3d6ca55a</code> 异或：</p><pre class="language-python lang-python"><code class="language-python lang-python">header = enc_header ^ 0x3d6ca55a</code></pre><p>解析得到：</p><pre class="language-text lang-text"><code class="language-text lang-text">header = 26 5f 10 26</code></pre><p>即：</p><pre class="language-text lang-text"><code class="language-text lang-text">header[0] = 0x26
header[1] = 0x5f
key_len   = 0x10 = 16
flag_len  = 0x26 = 38
seed      = 0x7a6b5c4d</code></pre><p>所以 flag 长度应为 <code>38</code>。</p><hr/><h2 id="3-">3. 关键逻辑分析</h2><p>程序会先根据 <code>panel.dat</code> 生成一个 16 字节 key：</p><pre class="language-python lang-python"><code class="language-python lang-python">cur = header[0]

for i in range(24):
    key[i] = key_material[i] ^ cur ^ ((i &amp; 3) * 7 + 0x33)
    cur = (cur + 0x0b) &amp; 0xff</code></pre><p>其中真正使用的是前 <code>16</code> 字节：</p><pre class="language-text lang-text"><code class="language-text lang-text">3c a9 0c 1e 1a 93 68 f8 74 18 bd 95 4c 6c bc d7</code></pre><p>随后程序构造一个 256 字节的 S-Box：</p><pre class="language-python lang-python"><code class="language-python lang-python">for i in range(128):
    sbox[2 * i]     = table_a[i] ^ key[i % key_len]
    sbox[2 * i + 1] = table_b[i] ^ key[(i + 5) % key_len]</code></pre><p>还会从 <code>table C</code> 派生一个 64 字节数组：</p><pre class="language-python lang-python"><code class="language-python lang-python">idx_table[i] = table_c[i] ^ (i + 0x5d) ^ key[(3 * i + 1) % key_len]</code></pre><p>前 38 个值刚好是 <code>0 ~ 37</code> 的一个置换，用来决定每一轮处理 flag 的哪个字符：</p><pre class="language-text lang-text"><code class="language-text lang-text">[2, 27, 34, 17, 8, 26, 21, 7, 35, 13, 16, 12, 24, 14, 32, 3,
 33, 15, 20, 22, 5, 0, 1, 4, 18, 31, 19, 28, 36, 30, 29, 10,
 9, 37, 25, 11, 23, 6]</code></pre><p>程序里有两条校验路径。第一条会尝试用 <code>seed == 0x7a6b5c4d</code> 的路径比较，但不满足后会进入第二条 fallback 路径。真正的 flag 是通过第二条路径还原出来的。</p><hr/><h2 id="4-">4. 逆向求解脚本</h2><p>下面脚本直接解析 <code>panel.dat</code> 并反推 flag：</p><pre class="language-python lang-python"><code class="language-python lang-python">from pathlib import Path
import struct

data = Path(&quot;panel.dat&quot;).read_bytes()

assert data[:4] == b&quot;TPK2&quot;

def rol8(x, r):
    r &amp;= 7
    return ((x &lt;&lt; r) | (x &gt;&gt; (8 - r))) &amp; 0xff

def rol16(x, r):
    r &amp;= 15
    return ((x &lt;&lt; r) | (x &gt;&gt; (16 - r))) &amp; 0xffff

# parse header
enc_header = struct.unpack(&quot;&lt;I&quot;, data[4:8])[0]
header = (enc_header ^ 0x3d6ca55a).to_bytes(4, &quot;little&quot;)

h0 = header[0]
h1 = header[1]
key_len = header[2]
flag_len = header[3]

key_material = data[8:32]
table_a = data[32:160]
table_b = data[160:288]
table_c = data[288:352]
target = data[352:416]
seed = struct.unpack(&quot;&lt;I&quot;, data[416:420])[0]

# derive key
cur = h0
key = []

for i, b in enumerate(key_material):
    key.append(b ^ cur ^ ((i &amp; 3) * 7 + 0x33))
    cur = (cur + 0x0b) &amp; 0xff

key = key[:key_len]

# derive sbox
sbox = []

for i in range(128):
    sbox.append(table_a[i] ^ key[i % key_len])
    sbox.append(table_b[i] ^ key[(i + 5) % key_len])

# derive index table
idx_table = []

for i in range(64):
    v = table_c[i] ^ ((i + 0x5d) &amp; 0xff) ^ key[(3 * i + 1) % key_len]
    idx_table.append(v)

# fallback checker inversion
edi = rol16(int.from_bytes(header[:2], &quot;little&quot;), 8)
r8 = 3
r9 = 1

flag = [None] * flag_len

for i in range(flag_len):
    rem = (edi + i + idx_table[i]) % key_len

    possible_tmp = []

    for tmp in range(256):
        a = key[rem] ^ tmp
        cl = (rol8(edi &amp; 0xff, i) + i) &amp; 0xff
        cl = (cl + sbox[a]) &amp; 0xff

        if cl == target[i]:
            possible_tmp.append(tmp)

    pos = idx_table[i] % flag_len
    possible_char = []

    for tmp in possible_tmp:
        for ch in range(32, 127):
            c = rol8(ch, 4) if (i &amp; 1) == 0 else ch
            calc = ((c ^ key[(pos + i) % key_len]) + rol8(h0, i)) &amp; 0xff

            if calc == tmp:
                possible_char.append(ch)

    assert len(set(possible_char)) == 1
    flag[pos] = possible_char[0]

    # update state with known target byte
    out = target[i]

    edi = (edi * 0x83) &amp; 0xffffffff
    edi ^= idx_table[r8 % flag_len]
    r8 += 7

    edi = (key[r9 % key_len] + ((out ^ edi) &amp; 0xffffffff)) &amp; 0xffffffff
    r9 += 5

flag = bytes(flag).decode()
print(flag)</code></pre><p>运行结果：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">$ python3 solve.py
flag{9d7a228ca5825bc15cd60bee0bb6d585}</code></pre></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/lqb#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/lqb</link><guid isPermaLink="true">https://lkaros.xin/posts/default/lqb</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Sat, 25 Apr 2026 08:15:01 GMT</pubDate></item><item><title><![CDATA[NCTF 2026 Reverse Write Up]]></title><description><![CDATA[<link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c40c8c5a3.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c4385d45f.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c437cc223.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c437d759b.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c4384e413.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43803cb6.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c439a9b9a.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c439ec184.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c4398fae3.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43a28c28.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43a32ab7.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43b2bc5d.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43b106a1.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43bac3e5.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43b7d4ad.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43b88cf4.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43c797c9.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43cabb26.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43d19d08.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43d19fb0.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/23/69e9c43d3ba4f.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/NCTF%202026%20Reverse%20Write%20Up">https://lkaros.xin/posts/default/NCTF%202026%20Reverse%20Write%20Up</a></blockquote><div><h2 id="reverse"><strong><em>Reverse</em></strong></h2><h3 id="hook-my-secret"><strong><em>Hook My Secret</em></strong></h3><p><em>题目一共有3个Stage</em></p><p><strong><em>Stage1:</em></strong></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c40c8c5a3.png" alt="1" height="732" width="1280"/></p><p> 九格图案锁，随便滑动一下，出现错误，提示Pattern incorrect</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c4385d45f.png" alt="2"/></p><p> 在Jadx中找到关键字符串，发现sha-256后的4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3</p><p> 所以可以直接爆破出手势</p><p> 爆破exp:</p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">import hashlib

TARGET = &quot;4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3&quot;

# Android pattern 跳点规则
SKIP = {
    (0, 2): 1, (2, 0): 1,
    (0, 6): 3, (6, 0): 3,
    (2, 8): 5, (8, 2): 5,
    (6, 8): 7, (8, 6): 7,
    (0, 8): 4, (8, 0): 4,
    (2, 6): 4, (6, 2): 4,
    (1, 7): 4, (7, 1): 4,
    (3, 5): 4, (5, 3): 4,
}

def valid_next(path, nxt):
    if nxt in path:
        return False
    if not path:
        return True
    a = path[-1]
    if (a, nxt) in SKIP:
        return SKIP[(a, nxt)] in path
    return True

def dfs(path, max_len=9):
    if 4 &lt;= len(path) &lt;= max_len:
        s = &quot;,&quot;.join(map(str, path))
        if hashlib.sha256(s.encode()).hexdigest() == TARGET:
            return path[:]

    if len(path) == max_len:
        return None

    for nxt in range(9):
        if valid_next(path, nxt):
            path.append(nxt)
            r = dfs(path, max_len)
            if r:
                return r
            path.pop()
    return None

def main():
    for start in range(9):
        ans = dfs([start])
        if ans:
            print(&quot;[+] Found pattern:&quot;, ans)
            print(&quot;[+] String form:&quot;, &quot;,&quot;.join(map(str, ans)))
            print(&quot;[+] 1-based view:&quot;, [x + 1 for x in ans])
            return
    print(&quot;[-] Not found&quot;)

if __name__ == &quot;__main__&quot;:
    main()</code></pre><p><img src="https://free.picui.cn/free/2026/04/23/69e9c437cc223.png" alt="img"/></p><p> 也就是</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c437d759b.png" alt="img"/></p><p> 成功来到</p><p><strong><em>Stage2<em>*</em></em></strong>:<em>*</em></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c4384e413.png" alt="img"/></p><p> 看到Native Check 直接把so扔进IDA 得到一段很长的代码：</p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">int __cdecl Java_com_nctf_hookmysecret_nativebridge_NativeBridge_encryptStage2(int a1, int a2, int a3)
{
  int v3; // eax
  int v4; // edi
  const char *s; // esi
  size_t v6; // eax
  size_t n; // ecx
  char *v8; // edi
  int v9; // ebp
  size_t v10; // edi
  char *v11; // eax
  size_t v12; // esi
  int v13; // ecx
  int v14; // edi
  char *v15; // esi
  int v16; // ebp
  int v18; // esi
  size_t v19; // eax
  bool v20; // sf
  size_t v21; // esi
  _BYTE *v22; // ebp
  char v23; // cl
  size_t v24; // edi
  size_t v25; // eax
  char *v26; // eax
  char v27; // cl
  int v28; // esi
  unsigned int v29; // ecx
  signed int v30; // ebp
  int v31; // edx
  unsigned int v32; // edi
  void *v33; // ebp
  int v34; // eax
  unsigned int v35; // edx
  char *v36; // ecx
  void *v37; // eax
  _BYTE *v38; // esi
  char *v39; // eax
  unsigned int v40; // ebx
  unsigned int v41; // eax
  int v42; // eax
  int v43; // edx
  __m128i v44; // xmm0
  int v45; // ebx
  int v46; // ebx
  char v47; // al
  _BYTE *v48; // edx
  char v49; // al
  char *v50; // edi
  unsigned int v51; // esi
  char *v52; // ebp
  char *v53; // ecx
  unsigned int v54; // eax
  int v55; // edx
  int v56; // ebx
  __m128i v57; // xmm2
  unsigned int v58; // edx
  int v59; // esi
  const char *src; // [esp+4h] [ebp-88h]
  char *v62; // [esp+18h] [ebp-74h]
  void *v63; // [esp+18h] [ebp-74h]
  char *v64; // [esp+1Ch] [ebp-70h]
  bool v65; // [esp+1Ch] [ebp-70h]
  char v66; // [esp+1Ch] [ebp-70h]
  int v67; // [esp+1Ch] [ebp-70h]
  unsigned int v68; // [esp+20h] [ebp-6Ch]
  unsigned int v69; // [esp+24h] [ebp-68h]
  int v70; // [esp+28h] [ebp-64h]
  char v71; // [esp+28h] [ebp-64h]
  int v72; // [esp+28h] [ebp-64h]
  unsigned int v73; // [esp+2Ch] [ebp-60h]
  size_t v74; // [esp+34h] [ebp-58h]
  char v75; // [esp+38h] [ebp-54h]
  int v76; // [esp+3Ch] [ebp-50h]
  unsigned int v77; // [esp+40h] [ebp-4Ch]
  _BYTE *v78; // [esp+44h] [ebp-48h]
  _DWORD v79[4]; // [esp+48h] [ebp-44h] BYREF
  int dest; // [esp+58h] [ebp-34h] BYREF
  _BYTE *v81; // [esp+5Ch] [ebp-30h]
  void *v82; // [esp+60h] [ebp-2Ch]
  __int16 v83; // [esp+68h] [ebp-24h] BYREF
  size_t v84; // [esp+6Ch] [ebp-20h]
  void *v85; // [esp+70h] [ebp-1Ch]
  unsigned int v86; // [esp+78h] [ebp-14h]

  v86 = __readgsdword(0x14u);
  v3 = (*(*a1 + 676))(a1, a3, 0);
  v4 = v3;
  s = &amp;unk_C5F3;
  if ( v3 )
    s = v3;
  v6 = strlen(s);
  if ( v6 &gt;= 0xFFFFFFF0 )
    sub_1E270(&amp;dest);
  n = v6;
  v70 = v4;
  if ( v6 &gt; 0xA )
  {
    v9 = v6 | 0xF;
    v10 = v6;
    v11 = operator new((v6 | 0xF) + 1);
    n = v10;
    v8 = v11;
    v82 = v11;
    dest = v9 + 2;
    v81 = n;
  }
  else
  {
    LOBYTE(dest) = 2 * v6;
    v8 = &amp;dest + 1;
  }
  src = s;
  v12 = n;
  memmove(v8, src, n);
  v8[v12] = 0;
  if ( (dest &amp; 1) != 0 )
  {
    v64 = v82;
    v13 = v81;
    if ( !v81 )
      goto LABEL_19;
  }
  else
  {
    v13 = dest &gt;&gt; 1;
    v64 = &amp;dest + 1;
    if ( !v13 )
      goto LABEL_19;
  }
  v14 = 0;
  v15 = &amp;off_476CC - 238561;
  while ( 1 )
  {
    v16 = v13;
    if ( !memchr(v15, v64[v14], 4u) )
      break;
    ++v14;
    v13 = v16;
    if ( v16 == v14 )
      goto LABEL_19;
  }
  if ( v14 == -1 )
  {
LABEL_19:
    v83 = 0;
    v18 = v70;
    if ( (dest &amp; 1) == 0 )
      goto LABEL_21;
    goto LABEL_20;
  }
  while ( v16 )
  {
    if ( !memchr(v15, v64[--v16], 4u) )
      goto LABEL_97;
  }
  v16 = -1;
LABEL_97:
  std::string::basic_string(&amp;v83, &amp;dest, v14, v16 - v14 + 1);
  v18 = v70;
  if ( (dest &amp; 1) != 0 )
LABEL_20:
    operator delete(v82);
LABEL_21:
  if ( v18 )
    (*(*a1 + 680))(a1, a3, v18);
  dest = 0;
  v81 = nullptr;
  v82 = nullptr;
  v65 = (v83 &amp; 1) == 0;
  if ( (v83 &amp; 1) != 0 )
  {
    v19 = v84;
    v20 = (v84 &amp; 0x80000000) != 0;
    if ( v84 )
    {
LABEL_25:
      if ( v20 )
        sub_1E330(&amp;dest);
      v21 = v19;
      v22 = operator new(v19);
      dest = v22;
      v81 = v22;
      v82 = &amp;v22[v21];
      v23 = 81;
      v24 = 0;
      while ( 1 )
      {
        v26 = &amp;v83 + 1;
        if ( !v65 )
          v26 = v85;
        v66 = v26[v24];
        v71 = v23;
        v27 = __ROL1__(v66 ^ v23 ^ (13 * v24 + 66), 3) + v24 + 7 * v23;
        if ( v22 == v82 )
          break;
        *v22++ = v27;
        v81 = v22;
LABEL_68:
        v23 = v71 + v66 + (v24++ ^ v27);
        v65 = (v83 &amp; 1) == 0;
        if ( (v83 &amp; 1) != 0 )
          v25 = v84;
        else
          v25 = v83 &gt;&gt; 1;
        if ( v24 &gt;= v25 )
        {
          v50 = dest;
          goto LABEL_74;
        }
      }
      v75 = v27;
      v28 = dest;
      v62 = v22;
      v29 = &amp;v22[-dest];
      v30 = &amp;v22[-dest + 1];
      if ( v30 &lt; 0 )
        sub_1E330(&amp;dest);
      if ( 2 * v29 &gt; v30 )
        v30 = 2 * v29;
      v73 = v29;
      if ( v29 &gt;= 0x3FFFFFFF )
        v30 = 0x7FFFFFFF;
      v74 = v24;
      if ( v30 )
        v31 = operator new(v30);
      else
        v31 = 0;
      v32 = v31 + v73;
      v33 = (v31 + v30);
      *(v31 + v73) = v75;
      if ( v62 == v28 )
      {
        dest = v31 + v73;
        v81 = (v32 + 1);
        v82 = v33;
        v37 = v62;
        v38 = (v32 + 1);
        v24 = v74;
        goto LABEL_66;
      }
      v69 = v31 + v73;
      v78 = (v32 + 1);
      v34 = ~v28;
      v76 = v31;
      if ( v73 &gt;= 8 )
      {
        v39 = &amp;v62[v34];
        v24 = v74;
        if ( v31 + v73 - 1 - v39 &gt; v31 + v73 - 1 || v62 - 1 - v39 &gt; (v62 - 1) )
        {
          v35 = v31 + v73;
        }
        else
        {
          v35 = v31 + v73;
          if ( (v28 - v76) &gt;= 0x20 )
          {
            v40 = 0;
            v41 = v73;
            if ( v73 &gt;= 0x20 )
            {
              v40 = v73 &amp; 0xFFFFFFE0;
              v42 = v76 + v73 - 16;
              v43 = 0;
              do
              {
                v44 = _mm_loadu_si128(&amp;v62[v43 - 32]);
                *(v42 + v43) = _mm_loadu_si128(&amp;v62[v43 - 16]);
                *(v42 + v43 - 16) = v44;
                v43 -= 32;
              }
              while ( -(v73 &amp; 0xFFFFFFE0) != v43 );
              v34 = ~v28;
              if ( v73 == v40 )
                goto LABEL_65;
              if ( (v73 &amp; 0x18) == 0 )
              {
                v36 = &amp;v62[-v40];
                v35 = v69 - v40;
                goto LABEL_60;
              }
              v41 = v73;
              v35 = v69;
            }
            v35 -= v41 &amp; 0xFFFFFFF8;
            v77 = v41 &amp; 0xFFFFFFF8;
            v36 = &amp;v62[-(v41 &amp; 0xFFFFFFF8)];
            v45 = -v40;
            do
            {
              *(v69 + v45 - 8) = *&amp;v62[v45 - 8];
              v45 -= 8;
            }
            while ( -(v41 &amp; 0xFFFFFFF8) != v45 );
            v34 = ~v28;
            if ( v73 == v77 )
              goto LABEL_65;
            goto LABEL_60;
          }
        }
        v36 = v62;
        v34 = ~v28;
      }
      else
      {
        v35 = v31 + v73;
        v36 = v62;
        v24 = v74;
      }
LABEL_60:
      v68 = &amp;v36[v34];
      v46 = (v36 - v28) &amp; 7;
      if ( ((v36 - v28) &amp; 7) != 0 )
      {
        do
        {
          v47 = *--v36;
          *--v35 = v47;
          --v46;
        }
        while ( v46 );
      }
      if ( v68 &gt;= 7 )
      {
        v48 = (v35 - 1);
        do
        {
          *v48 = *(v36 - 1);
          *(v48 - 1) = *(v36 - 2);
          *(v48 - 2) = *(v36 - 3);
          *(v48 - 3) = *(v36 - 4);
          *(v48 - 4) = *(v36 - 5);
          *(v48 - 5) = *(v36 - 6);
          *(v48 - 6) = *(v36 - 7);
          v49 = *(v36 - 8);
          v36 -= 8;
          *(v48 - 7) = v49;
          v48 -= 8;
        }
        while ( v36 != v28 );
      }
LABEL_65:
      v37 = dest;
      dest = v76;
      v38 = v78;
      v81 = v78;
      v82 = v33;
      if ( !v37 )
      {
LABEL_67:
        v22 = v38;
        v27 = v75;
        goto LABEL_68;
      }
LABEL_66:
      operator delete(v37);
      goto LABEL_67;
    }
  }
  else
  {
    v19 = v83 &gt;&gt; 1;
    v20 = 0;
    if ( v19 )
      goto LABEL_25;
  }
  v50 = nullptr;
  v22 = nullptr;
LABEL_74:
  v51 = v22 - v50;
  v67 = (*(*a1 + 716))(a1, v22 - v50);
  if ( v67 )
  {
    memset(v79, 0, 12);
    if ( v22 == v50 )
    {
      v52 = nullptr;
      v53 = nullptr;
    }
    else
    {
      v72 = v22 - v50;
      v63 = v22;
      if ( v51 &gt;= 0x40000000 )
        sub_1E350(v79);
      v52 = operator new(4 * v51);
      v53 = v52;
      v54 = v50;
      if ( v51 &lt; 0x14 )
        goto LABEL_83;
      if ( v52 &lt; &amp;v50[v51] )
      {
        v53 = v52;
        v54 = v50;
        if ( v50 &lt; &amp;v52[4 * v72] )
          goto LABEL_83;
      }
      v55 = v51 &amp; 0x3FFFFFF8;
      v53 = &amp;v52[4 * (v51 &amp; 0xFFFFFFF8)];
      v54 = &amp;v50[v51 &amp; 0xFFFFFFF8];
      v56 = 0;
      do
      {
        v57 = _mm_unpacklo_epi16(_mm_unpacklo_epi8(_mm_cvtsi32_si128(*&amp;v50[v56 + 4]), 0LL), 0LL);
        *&amp;v52[4 * v56] = _mm_unpacklo_epi16(_mm_unpacklo_epi8(_mm_cvtsi32_si128(*&amp;v50[v56]), 0LL), 0LL);
        *&amp;v52[4 * v56 + 16] = v57;
        v56 += 8;
      }
      while ( v55 != v56 );
      if ( v51 != v55 )
      {
LABEL_83:
        v58 = v63 + ~v54;
        v59 = (v63 - v54) &amp; 7;
        if ( ((v63 - v54) &amp; 7) != 0 )
        {
          do
          {
            *v53 = *v54++;
            v53 += 4;
            --v59;
          }
          while ( v59 );
        }
        if ( v58 &gt;= 7 )
        {
          do
          {
            *v53 = *v54;
            *(v53 + 1) = *(v54 + 1);
            *(v53 + 2) = *(v54 + 2);
            *(v53 + 3) = *(v54 + 3);
            *(v53 + 4) = *(v54 + 4);
            *(v53 + 5) = *(v54 + 5);
            *(v53 + 6) = *(v54 + 6);
            *(v53 + 7) = *(v54 + 7);
            v54 += 8;
            v53 += 32;
          }
          while ( v54 != v63 );
        }
      }
    }
    (*(*a1 + 844))(a1, v67, 0, (v53 - v52) &gt;&gt; 2, v52);
    if ( v52 )
      operator delete(v52);
  }
  if ( v50 )
  {
    v81 = v50;
    operator delete(v50);
  }
  if ( (v83 &amp; 1) != 0 )
    operator delete(v85);
  return v67;
}</code></pre>丢给AI分析，可以得到这样的逻辑<pre class="language-Python lang-Python"><code class="language-Python lang-Python">def encrypt_stage2(s: str):
    s = s.strip()
    seed = 0x51
    out = []

    for i, ch in enumerate(s.encode()):
        x = ((13 * i + 0x42) ^ seed ^ ch) &amp; 0xff
        x = ((x &lt;&lt; 3) &amp; 0xff) | (x &gt;&gt; 5)   # rol 3
        y = (7 * seed + i + x) &amp; 0xff
        out.append(y)
        seed = (ch + seed + (y ^ i)) &amp; 0xff

    return out</code></pre>再找到目标数组[250, 113, 87, 185, 6, 125, 167, 156, 4, 0, 229, 239, 119, 155, 187, 95]
<p> <strong><em>Stage2 exp:</em></strong></p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">TARGET = [250, 113, 87, 185, 6, 125, 167, 156, 4, 0, 229, 239, 119, 155, 187, 95]

def encrypt_stage2(s: str):
    s = s.strip()
    seed = 0x51
    out = []

    for i, ch in enumerate(s.encode()):
        x = ((13 * i + 0x42) ^ seed ^ ch) &amp; 0xff
        x = ((x &lt;&lt; 3) &amp; 0xff) | (x &gt;&gt; 5)   # rol 3
        y = (7 * seed + i + x) &amp; 0xff
        out.append(y)
        seed = (ch + seed + (y ^ i)) &amp; 0xff

    return out

def recover_key(target):
    seed = 0x51
    res = []

    for i, want in enumerate(target):
        found = None
        for ch in range(256):
            x = ((13 * i + 0x42) ^ seed ^ ch) &amp; 0xff
            x = ((x &lt;&lt; 3) &amp; 0xff) | (x &gt;&gt; 5)
            y = (7 * seed + i + x) &amp; 0xff
            if y == want:
                next_seed = (ch + seed + (y ^ i)) &amp; 0xff
                found = (ch, next_seed)
                break

        if found is None:
            raise ValueError(f&quot;char {i} not found&quot;)

        ch, seed = found
        res.append(ch)

    return bytes(res).decode()

def main():
    key = recover_key(TARGET)
    print(&quot;[+] Recovered key:&quot;, key)
    print(&quot;[+] Verify:&quot;, encrypt_stage2(key))
    print(&quot;[+] Match:&quot;, encrypt_stage2(key) == TARGET)

if __name__ == &quot;__main__&quot;:
    main()</code></pre><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43803cb6.png" alt="img"/></p><p> 所以Key为<strong>k7Xm2Pq9Wv4N8bRt</strong></p><p><strong><em>Stage3<em>*</em></em></strong>:<em>*</em></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c439a9b9a.png" alt="img"/></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c439ec184.png" alt="img"/></p><p> 发现stage3使用了AES加密，密文是jSaMnziall55Tdr+IZc7EKUNm/N4uwrZw1QFPw6DuirfYFJZg88j6GKLhWfNljAB</p><p> 密钥是stage2的key所以还差个IV</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c4398fae3.png" alt="img"/></p><p> 通过对其方法进行追踪可以看到:</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43a28c28.png" alt="img"/></p><p> IV已有，解密即可</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43a32ab7.png" alt="img"/></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43b2bc5d.png" alt="img"/></p><h3 id="no-my-bank"><strong><em>No My Bank!</em></strong></h3><p>一开始 <code>GDRE Tools</code> 直接恢复不完整，看起来像是资源和脚本被加密了。实际情况分成两层:</p><ol start="1"><li><code>NoMyBank.exe</code> 内嵌了一个加密的 Godot PCK。</li><li><p>根目录下还有一个额外加密的 <code>libextension.dll</code>，这是 GDExtension，真正的校验逻辑在里面。</p><p>先解第一层：</p></li></ol><p>使用 <code>KeyDot</code> 从 <code>NoMyBank.exe</code> 中提取到 PCK 解密 key:</p><p>D34BFF62613FDD2861F6D5942C5E99A53EF3E90ADBE9091B4686859D5B7DAB22</p><p>再用GDRE进行解密</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43b106a1.png" alt="img"/></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43bac3e5.png" alt="img"/></p><p>核心逻辑:</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">func _on_button_pressed():
    var flag_checker = get_checker_from_loaded_dll_node()
    if flag_checker:
        flag_checker.show_flag_dialog()
        queue_free()</code></pre><p>说明点击金库弹窗按钮后，会去调用 GDExtension 节点 <code>dllchecker</code> 的方法 <code>show_flag_dialog()</code>。</p><p>这说明真正的校验和 flag 展示都在原生扩展里，不在 GDScript 里。</p><p>这时题目打包的目录下还有一个dll没有用上</p><p>运行程序后，扩展会被自动解密并落到临时目录:</p><p>C:\Users\hao\AppData\Local\Temp_libextension.dll</p><p>这是正常的dll</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43b7d4ad.png" alt="img"/></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43b88cf4.png" alt="img"/></p><p>往上找到:</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43c797c9.png" alt="img"/></p><p>检查长度是否为40</p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">_DWORD *__fastcall sub_1800024A0(__int64 a1, __int64 a2, _DWORD *a3)
{
  int i; // [rsp+20h] [rbp-58h]
  int j; // [rsp+24h] [rbp-54h]
  unsigned int v6; // [rsp+28h] [rbp-50h]
  unsigned int v7; // [rsp+2Ch] [rbp-4Ch]
  int m; // [rsp+30h] [rbp-48h]
  int v9; // [rsp+34h] [rbp-44h]
  int k; // [rsp+38h] [rbp-40h]
  _DWORD v11[10]; // [rsp+40h] [rbp-38h] BYREF

  memset(v11, 0, sizeof(v11));
  for ( i = 0; i &lt; 5; ++i )
  {
    v11[2 * i] = sub_1800023A0((8 * i + a1));
    v11[2 * i + 1] = sub_1800023A0((8 * i + 4 + a1));
  }
  for ( j = 0; j &lt; 10; j += 2 )
  {
    v9 = 0;
    v6 = v11[j];
    v7 = v11[j + 1];
    for ( k = 0; k &lt; 32; ++k )
    {
      v9 += 0x114514;
      v6 += (unk_18014F49C + (v7 &gt;&gt; 5)) ^ (v9 + v7) ^ (unk_18014F498 + 16 * v7);
      v7 += (unk_18014F4A4 + (v6 &gt;&gt; 5)) ^ (v9 + v6) ^ (unk_18014F4A0 + 16 * v6);
    }
    v11[j] = v6;
    v11[j + 1] = v7;
  }
  for ( m = 0; m &lt; 10; ++m )
    sub_180002410(v11[m], 4 * m + a2);
  *a3 = 40;
  return a3;
}</code></pre><p><strong>题目采用三层处理：</strong></p><ol start="1"><li><p>明文经自定义 TEA-like 算法加密，参数为：</p><p>delta = 0x114514</p><p>key = [0x12345678, 0x9ABCDEF0, 0x13579BDF, 0x2468ACE0]</p></li><li><p>TEA 输出再经自定义 Base64 编码，字符表为</p><p>ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/</p><p>且 4 个 6-bit 输出顺序为 [b,a,d,c]</p></li><li><p>最后再做逐字节异或 + rol2 混淆，seed 初始值为 0x114514</p><p><strong>exp:</strong></p></li></ol><pre class="language-Python lang-Python"><code class="language-Python lang-Python">CIPH_HEX = &quot;2bf7675e7c98ed6dd18cef57bb33227eb21f345b366c2bafbb5b12d63c0a4527846c47ab2f75783e88892d7acd5cf6fa3673ff6ed34c1c75&quot;

def ror2(x: int) -&gt; int:
    return ((x &gt;&gt; 2) | ((x &lt;&lt; 6) &amp; 0xff)) &amp; 0xff

def dec_stage1(cipher_hex: str) -&gt; bytes:
    data = bytes.fromhex(cipher_hex)
    seed = 0x114514
    out = bytearray()

    for i, c in enumerate(data):
        k = (seed &gt;&gt; ((i * 8) % 24)) &amp; 0xff
        b = ror2(c ^ 0xBA) ^ k
        out.append(b)
        seed = seed * 0x1010193 + 0x12345678

    return bytes(out)

CUSTOM_B64_TABLE = &quot;ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/&quot;

def custom_b64_decode(s: str) -&gt; bytes:
    mp = {ch: i for i, ch in enumerate(CUSTOM_B64_TABLE)}
    out = bytearray()

    for i in range(0, len(s), 4):
        quad = s[i:i+4]
        pad = quad.count(&#x27;=&#x27;)

        q = []
        for ch in quad:
            if ch == &#x27;=&#x27;:
                q.append(None)
            else:
                q.append(mp[ch])

        # 输出顺序是 [b, a, d, c]
        # 还原为标准顺序 [a, b, c, d]
        a = q[1] if q[1] is not None else 0
        b = q[0] if q[0] is not None else 0
        d = q[2] if q[2] is not None else 0
        c = q[3] if q[3] is not None else 0

        n = (a &lt;&lt; 18) | (b &lt;&lt; 12) | (c &lt;&lt; 6) | d

        if pad == 0:
            out.extend([(n &gt;&gt; 16) &amp; 0xff, (n &gt;&gt; 8) &amp; 0xff, n &amp; 0xff])
        elif pad == 1:
            out.extend([(n &gt;&gt; 16) &amp; 0xff, (n &gt;&gt; 8) &amp; 0xff])
        elif pad == 2:
            out.append((n &gt;&gt; 16) &amp; 0xff)
        else:
            raise ValueError(&quot;invalid padding&quot;)

    return bytes(out)

def main():
    s1 = dec_stage1(CIPH_HEX)
    print(&quot;[+] stage1 result:&quot;, s1.decode())

    s2 = custom_b64_decode(s1.decode())
    print(&quot;[+] stage2 bytes hex:&quot;, s2.hex())
    print(&quot;[+] flag:&quot;, s2.decode())

if __name__ == &quot;__main__&quot;:
    main()</code></pre><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43cabb26.png" alt="img"/></p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43d19d08.png" alt="img"/></p><p>最后还有一个小变换</p><p>最终flag：NCTF{Y0u_d3s3rv3_th1s_g1ft_b1bd7c719cfc}</p><h3 id="pay-for-2048"><strong><em>Pay For 2048</em></strong></h3><p>题目是从一个基于 Electron 的 2048 游戏。直接拿asar解包，拿到源文件</p><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43d19fb0.png"/></p><pre class="language-JavaScript lang-JavaScript"><code class="language-JavaScript lang-JavaScript">class GameController {
    constructor({ gameManager, uiManager, licenseService }) {
        this.gameManager = gameManager;
        this.uiManager = uiManager;
        this.licenseService = licenseService;
        this.keyMap = {
            ArrowLeft: 0,
            ArrowDown: 1,
            ArrowRight: 2,
            ArrowUp: 3
        };
        this.state = this.#createInitialState();
    }

    async bootstrap() {
        this.uiManager.setBanner(&quot;Loading WASM core...&quot;);
        await this.licenseService.init();
        this.#bindEvents();
        this.#setPhase(&quot;PLAYING_FREE&quot;, &quot;WASM core ready. Reach 256 to request the license.&quot;);
        this.#syncView();
    }

    restart() {
        this.gameManager.reset();
        this.state = this.#createInitialState();
        this.uiManager.hideLicenseModal();
        this.uiManager.hideResult();
        this.#setPhase(&quot;PLAYING_FREE&quot;, &quot;Fresh board. Reach 256 to trigger the license gate.&quot;);
        this.#syncView();
    }

    async submitLicense() {
        const key = this.uiManager.getLicenseKey();
        const context = this.#collectContext();
        this.state.busy = true;
        this.uiManager.setLicenseBusy(true);
        this.uiManager.setBanner(&quot;Verifying license...&quot;);

        try {
            const result = await this.licenseService.verifyKey(key, context);
            if (!result.ok) {
                this.state.busy = false;
                this.uiManager.setLicenseBusy(false);
                this.uiManager.showLicenseModal(result.message);
                return;
            }

            this.state.activated = true;
            this.state.paused = false;
            this.state.busy = false;
            this.state.licenseKey = key;
            this.state.sessionToken = result.data.sessionToken;
            this.uiManager.setLicenseBusy(false);
            this.uiManager.hideLicenseModal();
            this.#setPhase(&quot;PLAYING_PRO&quot;, &quot;License accepted. 2048 final verification is now unlocked.&quot;);
            this.#syncView();
        } catch (error) {
            this.state.busy = false;
            this.uiManager.setLicenseBusy(false);
            this.uiManager.showLicenseModal(error.message || &quot;License verification failed.&quot;);
        }
    }

    async #unlockFlag() {
        try {
            const result = await this.licenseService.unlockFlag(this.state.licenseKey, this.#collectContext());
            this.state.busy = false;

            if (!result.ok) {
                this.state.paused = false;
                this.#setPhase(&quot;FAIL&quot;, result.message);
                this.#syncView();
                return;
            }

            this.state.gameOver = true;
            this.#setPhase(&quot;SUCCESS&quot;, &quot;Final verification passed.&quot;);
            this.uiManager.showResult({
                title: &quot;Flag Released&quot;,
                message: &quot;WASM validation passed and the encrypted payload was released.&quot;,
                flag: result.data.flag
            });
            this.#syncView();
        } catch (error) {
            this.state.busy = false;
            this.state.paused = false;
            this.#setPhase(&quot;FAIL&quot;, error.message || &quot;Final verification failed.&quot;);
            this.#syncView();
        }
    }

    #bindEvents() {
        window.addEventListener(&quot;keydown&quot;, (event) =&gt; {
            void this.#onKeydown(event);
        });
        this.uiManager.authButton.addEventListener(&quot;click&quot;, () =&gt; {
            void this.submitLicense();
        });
        this.uiManager.licenseInput.addEventListener(&quot;keydown&quot;, (event) =&gt; {
            if (event.key === &quot;Enter&quot;) {
                event.preventDefault();
                void this.submitLicense();
            }
        });
        this.uiManager.restartButton.addEventListener(&quot;click&quot;, () =&gt; this.restart());
        this.uiManager.resultCloseButton.addEventListener(&quot;click&quot;, () =&gt; {
            this.uiManager.hideResult();
            if (!this.state.gameOver) {
                this.state.paused = false;
                this.#syncView();
            }
        });
    }

    async #onKeydown(event) {
        const direction = this.keyMap[event.key];
        if (direction === undefined || this.state.paused || this.state.busy || this.state.gameOver) {
            return;
        }

        const moveResult = this.gameManager.move(direction);
        if (!moveResult.moved) {
            return;
        }

        this.gameManager.addRandomTile();
        this.#syncView();

        if (!this.state.reached256 &amp;&amp; moveResult.maxTile &gt;= 256 &amp;&amp; !this.state.activated) {
            this.state.reached256 = true;
            this.state.paused = true;
            this.#setPhase(&quot;WAIT_LICENSE&quot;, &quot;Reached 256. Enter the key to continue.&quot;);
            this.uiManager.clearLicenseInput();
            this.uiManager.showLicenseModal();
            this.#syncView();
            return;
        }

        if (!this.state.reached2048 &amp;&amp; this.state.activated &amp;&amp; moveResult.maxTile &gt;= 2048) {
            this.state.reached2048 = true;
            this.state.paused = true;
            this.state.busy = true;
            this.#setPhase(&quot;VERIFYING_FINAL&quot;, &quot;2048 reached. Verifying final state inside WASM...&quot;);
            this.#syncView();
            await this.#unlockFlag();
            return;
        }

        if (!this.gameManager.hasMovesAvailable()) {
            this.state.gameOver = true;
            this.state.paused = true;
            this.#setPhase(&quot;FAIL&quot;, &quot;No moves left. Restart to try another run.&quot;);
            this.uiManager.showResult({
                title: &quot;Game Over&quot;,
                message: &quot;棋盘已无可用移动，请重新开始。&quot;
            });
        }
    }

    #collectContext() {
        const snapshot = this.gameManager.getStateSnapshot();
        return {
            score: snapshot.score,
            steps: snapshot.steps,
            maxTile: snapshot.maxTile,
            board: snapshot.board,
            sessionToken: this.state.sessionToken
        };
    }

    #syncView() {
        const snapshot = this.gameManager.getStateSnapshot();
        this.uiManager.render({
            ...snapshot,
            phase: this.state.phase
        });
    }

    #setPhase(phase, message) {
        this.state.phase = phase;
        this.uiManager.setBanner(message);
    }

    #createInitialState() {
        return {
            activated: false,
            paused: false,
            reached256: false,
            reached2048: false,
            gameOver: false,
            busy: false,
            phase: &quot;INIT&quot;,
            licenseKey: &quot;&quot;,
            sessionToken: &quot;&quot;
        };
    }
}

module.exports = {
    GameController
};</code></pre><p>在<code>game-controller.js</code> 里可以看到：合成到 <code>256</code> 时会弹出授权框 , 输入 key 后调用 <code>verifyLicense()</code>, 成功后继续游戏 , 合成到 <code>2048</code> 时调用 <code>unlockFlag()</code>。</p><p>将 <code>wasm_core.wasm</code> 转成 <code>wat</code> 后，可以看到这些关键符号：</p><p> <code>normalize_key</code></p><p> <code>verify_license_impl</code></p><p> <code>unlock_flag_impl</code></p><p> <code>build_session_token</code></p><p>还能直接看到一些关键字符串：</p><p> <code>license format must match NCTF-XXXX-XXXX-XXXX</code></p><p> <code>reach 256 before requesting activation</code></p><p> <code>reach 2048 before requesting the final unlock</code></p><p> <code>arcade::unlock-seed</code></p><p> <code>NCTF{decrypt_failed}</code></p><p>从这些我可以知道:  key 格式固定为 <code>NCTF-XXXX-XXXX-XXXX</code> , 真 flag 不是明文存放，而是经过解密.</p><p>解密会用到 <code>arcade::unlock-seed</code></p><p>我分析 verify_license,<strong><em><code>verify_license_impl</code></em></strong> 的逻辑可以还原为：</p><p> 检查 <code>maxTile &gt;= 256</code></p><p> 校验 key 格式</p><p> 调用 <code>normalize_key</code></p><p> 对规范化后的 12 字符串做哈希</p><p> 判断哈希结果是否等于常量 <code>-57161169</code></p><pre class=""><code class="">normalize_key` 的作用是把：`NCTF-ABCD-EFGH-IJKL`变成：`ABCDEFGHIJKL</code></pre><p>也就是去掉前缀和横杠，只保留后面的 12 个字符。</p><p>WASM 中的哈希大致形式如下：</p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">h = 4951
for i, ch in enumerate(norm):
    c = 17881 if i % 2 == 0 else 40503
    h = (((ch + c) * (i + 11)) ^ rol32(h, 3)) + ((ch &lt;&lt; (i % 5)) ^ CONST)</code></pre><p>由于字符集被限制在大写字母和数字，可以直接把这 12 个字符，交给 Z3 去解。</p><p>先能解到一个碰撞 key：  <code>NCTF-HPOB-XXGC-5PBA</code></p><p>它确实可以通过 <code>verify_license</code>，但 <code>unlock_flag</code> 返回的不是正常 flag.</p><p>分析 unlock_flag , <code>unlock_flag_impl</code> 的关键逻辑如下：</p><p> 检查 <code>maxTile &gt;= 2048</code></p><p> 根据当前上下文重建 <code>sessionToken</code></p><p> 比较传入的 <code>sessionToken</code> 是否一致</p><p> 再次 <code>normalize_key</code></p><p><em>构造 key material：</em></p><p>normalized_key + &quot;|arcade::unlock-seed&quot;</p><p>用这个 key material 循环异或解密一段 42 字节密文</p><p>可以还原出解密公式：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">km = (normalized_key + &quot;|arcade::unlock-seed&quot;).encode()
flag = bytes(
    enc[i] ^ ((29 * i + 17) &amp; 0xff) ^ km[i % len(km)]
    for i in range(42)
)</code></pre><p><em>这里最关键的是：</em></p><p><em>后 20 字节</em> <em><code>|arcade::unlock-seed</code></em> <em>是已知常量</em> <em>,</em> <em>所以 flag 的第</em> <em><code>12..31</code></em> <em>个字符可以直接解出来</em></p><p><em>这一段解出来后得到：</em></p><p><em><code>6-c4f2-4dbb-b270-f5d</code></em></p><p><em>联立约束求真正 key</em></p><p><em>此时我们已经有约束：</em></p><p><em>必须满足</em> <em><code>verify_license</code></em> <em>的哈希校验</em></p><p><em>把这些条件一起丢给 Z3，最终可以得到唯一有效的 normalized key：</em></p><p><em><code>RU57W45M2048</code></em></p><p><em>所以完整 key 为：</em></p><p><em><code>NCTF-RU57-W45M-2048</code></em></p><p><strong>exp:</strong></p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">from z3 import BitVec, BitVecVal, Or, RotateLeft, Solver


TARGET = (-57161169) &amp; 0xFFFFFFFF
ALNUM = [ord(c) for c in &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&quot;]
HEXLOW = [ord(c) for c in &quot;0123456789abcdef&quot;]
ENC = [
    13, 56, 42, 25, 169, 244, 236, 247, 250, 16, 53, 94,
    39, 198, 182, 147, 230, 168, 83, 54, 11, 101, 131, 237,
    196, 183, 95, 61, 99, 89, 39, 148, 134, 255, 231, 15,
    66, 20, 95, 5, 200, 251,
]


def solve_normalized_key() -&gt; str:
    solver = Solver()
    xs = [BitVec(f&quot;x{i}&quot;, 32) for i in range(12)]

    for x in xs:
        solver.add(Or(*[x == v for v in ALNUM]))

    # From the known plaintext prefix/suffix of NCTF{...}
    known = {
        0: ord(&quot;R&quot;),
        1: ord(&quot;U&quot;),
        2: ord(&quot;5&quot;),
        3: ord(&quot;7&quot;),
        4: ord(&quot;W&quot;),
        9: ord(&quot;0&quot;),
    }
    for idx, value in known.items():
        solver.add(xs[idx] == value)

    # verify_license hash logic
    h = BitVecVal(4951, 32)
    for i, x in enumerate(xs):
        c = 40503 if (i &amp; 1) else 17881
        term1 = BitVecVal(c, 32) + x
        term2 = term1 * BitVecVal(i + 11, 32)
        term3 = RotateLeft(h, 3)
        term4 = (x &lt;&lt; (i % 5)) ^ BitVecVal((-1515890086) &amp; 0xFFFFFFFF, 32)
        h = (term2 ^ term3) + term4
    solver.add(h == BitVecVal(TARGET, 32))

    # Force the unknown plaintext positions to stay lowercase hex
    for i in range(5, 12):
        plain = BitVecVal(ENC[i] ^ ((i * 29 + 17) &amp; 0xFF), 32) ^ xs[i]
        solver.add(Or(*[plain == v for v in HEXLOW]))

    for pos in range(32, 41):
        key_idx = pos - 32
        plain = BitVecVal(ENC[pos] ^ ((pos * 29 + 17) &amp; 0xFF), 32) ^ xs[key_idx]
        solver.add(Or(*[plain == v for v in HEXLOW]))

    assert solver.check().r == 1
    model = solver.model()
    return &quot;&quot;.join(chr(model[x].as_long()) for x in xs)


def decrypt_flag(normalized_key: str) -&gt; str:
    km = (normalized_key + &quot;|arcade::unlock-seed&quot;).encode()
    out = []
    for i, value in enumerate(ENC):
        out.append(value ^ ((i * 29 + 17) &amp; 0xFF) ^ km[i % len(km)])
    return bytes(out).decode()


if __name__ == &quot;__main__&quot;:
    normalized = solve_normalized_key()
    full_key = f&quot;NCTF-{normalized[:4]}-{normalized[4:8]}-{normalized[8:12]}&quot;
    flag = decrypt_flag(normalized)

    print(&quot;Normalized Key:&quot;, normalized)
    print(&quot;License Key:&quot;, full_key)
    print(&quot;Flag:&quot;, flag)</code></pre><p><img src="https://free.picui.cn/free/2026/04/23/69e9c43d3ba4f.png" alt="img"/></p><h3 id="vm-encryptor">VM Encryptor</h3><p>题目给了两个文件</p><p> <code>vm-encryptor.exe</code></p><p> <code>code.bin</code></p><ul><li>题目本质是一个<strong>自定义虚拟机（VM）解释执行题</strong></li></ul><p>进一步分析可知，输入校验流程共分为：</p><p> 按 3 字节分组，进行 24-bit 旋转/XOR 变换</p><p> 对结果做 Base64 编码</p><p> 对编码结果逐字节异或 <code>0x63</code></p><p> 与字节码中的固定 56 字节常量比较</p><p>在code.bin处 <code>0x109d</code> 有 密文：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">const = bytes.fromhex(
    &quot;275a0b080a3a090d30314c37023a120e542a4c302c3211270631563712264c37&quot;
    &quot;28325b375500481a0201112716004c372407113401365b27&quot;
)</code></pre><p>在 <code>0x10e3</code> 能看到标准 Base64 表：</p><p>ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/</p><p>VM指令回复</p><p>把 VM 跳表恢复出来后，这题实际用到的指令并不多。最关键的几条如下：</p><table><thead><tr><th>opcode</th><th>语义</th></tr></thead><tbody><tr><td>0x00</td><td>jmp imm32</td></tr><tr><td>0x01</td><td>jz imm32</td></tr><tr><td>0x02</td><td>jnz imm32</td></tr><tr><td>0x03</td><td>push8 imm8</td></tr><tr><td>0x04</td><td>push32 imm32</td></tr><tr><td>0x05</td><td>load8 [addr]</td></tr><tr><td>0x06</td><td>load32 [addr]</td></tr><tr><td>0x07</td><td>pop</td></tr><tr><td>0x08</td><td>store8 [addr] = val</td></tr><tr><td>0x09</td><td>store32 [addr] = val</td></tr><tr><td>0x0a</td><td>add</td></tr><tr><td>0x0b</td><td>sub</td></tr><tr><td>0x0c</td><td>mul</td></tr><tr><td>0x0d</td><td>div</td></tr><tr><td>0x0e</td><td>mod</td></tr><tr><td>0x0f</td><td>and</td></tr><tr><td>0x10</td><td>or</td></tr><tr><td>0x11</td><td>not</td></tr><tr><td>0x12</td><td>xor</td></tr><tr><td>0x13</td><td>shl</td></tr><tr><td>0x14</td><td>shr</td></tr><tr><td>0x15 / 0x16</td><td>eq</td></tr><tr><td>0x17</td><td>ne</td></tr><tr><td>0x18</td><td>lt</td></tr><tr><td>0x19</td><td>gt</td></tr><tr><td>0x1a</td><td>le</td></tr><tr><td>0x1b</td><td>ge</td></tr><tr><td>0x1c</td><td>dup</td></tr><tr><td>0x1d</td><td>swap</td></tr><tr><td>0x1e</td><td>call imm32</td></tr><tr><td>0x1f</td><td>ret</td></tr><tr><td>0x20</td><td>puts(addr)</td></tr><tr><td>0xff</td><td>halt</td></tr></tbody></table><p><strong><em>还原<em>*</em></em></strong>:<em>*</em></p><p>先把最终常量逆掉：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">stage1 = bytes(x ^ 0x63 for x in const)
print(stage1.decode())</code></pre><p>得到：</p><p>D9hkiYjnSR/TaYqm7I/SOQrDeR5TqE/TKQ8T6c+yabrDuc/TGdrWbU8D</p><p>对这串做 Base64 解码：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">import base64
raw = base64.b64decode(stage1)
print(raw.hex())</code></pre><p>得到 42 字节：</p><p>0fd8648988e7491fd3698aa6ec8fd2390ac3791e53a84fd3290f13e9cfb269bac3b9cfd319dad66d4f03</p><p>继续结合 VM 字节码，可以看到它反复出现：</p><p> <code>0xffffff</code></p><p> <code>0x55757d</code></p><p> 多次位移/组合</p><p> 最后查 Base64 表</p><p>把这段逻辑还原后，每 3 字节块的正向变换是：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">x = block24
x = rol24(x, 5)
x ^= 0x55757d
x = rol24(x, 11)
x ^= 0x55757d
x = rol24(x, 20)
x ^= 0x55757d</code></pre><p>然后把这个 24-bit 结果做 Base64 编码。</p><p>因此逆过程就是：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">x = encoded_block
x = ror24(x ^ 0x55757d, 20)
x = ror24(x ^ 0x55757d, 11)
x = ror24(x ^ 0x55757d, 5)</code></pre><p>恢复出原始 3 字节。</p><p>exp:</p><pre class="language-Python lang-Python"><code class="language-Python lang-Python">import base64

const = bytes.fromhex(
    &quot;275a0b080a3a090d30314c37023a120e542a4c302c3211270631563712264c37&quot;
    &quot;28325b375500481a0201112716004c372407113401365b27&quot;
)

def ror24(x, n):
    n %= 24
    return ((x &gt;&gt; n) | (x &lt;&lt; (24 - n))) &amp; 0xffffff

key = 0x55757d

# Step 1: 逆掉最后一层 xor 0x63
stage1 = bytes(b ^ 0x63 for b in const)
print(&quot;[+] stage1 =&quot;, stage1.decode())

# Step 2: Base64 解码
raw = base64.b64decode(stage1)
print(&quot;[+] raw =&quot;, raw.hex())

# Step 3: 逐 3 字节逆 24-bit 变换
flag = bytearray()
for i in range(0, len(raw), 3):
    x = int.from_bytes(raw[i:i+3], &quot;big&quot;)
    x = ror24(x ^ key, 20)
    x = ror24(x ^ key, 11)
    x = ror24(x ^ key, 5)
    flag += x.to_bytes(3, &quot;big&quot;)

print(&quot;[+] flag =&quot;, flag.decode())


[+] stage1 = D9hkiYjnSR/TaYqm7I/SOQrDeR5TqE/TKQ8T6c+yabrDuc/TGdrWbU8D
[+] raw = 0fd8648988e7491fd3698aa6ec8fd2390ac3791e53a84fd3290f13e9cfb269bac3b9cfd319dad66d4f03
[+] flag = NCTF{1578be15-ad09-4859-9193-5d52585eb485}</code></pre><h3 id=""><strong>鸡爪流高手</strong></h3><p>这题表面上看像一个五子棋小游戏，在服务端业务逻辑里：</p><p> <em>服务维护一个玩家排行榜，玩家通过和机器人下五子棋获得或失去分数。</em></p><p> <em>分数结算时，玩家和对手走了两套不同的更新函数。</em></p><p> <em>玩家使用的那套更新函数存在 bug，可以把分数打成负数。</em></p><p> <em>负分写入 SQLite 时又触发了有符号/无符号处理错误，最终把</em> <em><code>-1</code></em> <em>写成了</em> <em><code>4294967295</code>*</em>。*</p><p> <em>一旦自己的分数变成</em> <em><code>4294967295</code>*</em>，就会直接冲到排行榜第一，随后即可调用取 flag 接口。*</p><p>所以这题的本质利用链是：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">错误的 Elo 结算
-&gt; 得到负分
-&gt; 负分被错误地零扩展写库
-&gt; 分数变成 4294967295
-&gt; rank = 1
-&gt; 拿 flag</code></pre><p>我分析协议:</p><p>逆向 <code>protocol_read_packet</code> 和 <code>protocol_write_response</code> 后可以得到通信格式：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">4 bytes  magic   = &quot;GAME&quot;
4 bytes  len     = big-endian，总长度
1 byte   cmd     = 命令号
N bytes  payload = 命令参数</code></pre><p>服务端返回包格式相同。</p><p>命令号</p><p>根据 <code>handler_handle_packet</code> 的分发表，可以恢复出命令映射：</p><p> 取 flag</p><p> 查询当前分数和排名</p><p> 查询附近排行榜</p><p> reset 当前挑战状态</p><p> 随机匹配一个对手并开始对局</p><p> 查看当前棋盘</p><p> 落子，payload 形如 <code>x,y</code></p><p> 在空棋盘上相当于取消对局，在非空棋盘上相当于认输结算</p><p><strong><em>分析<em>*</em></em></strong>数据库与排行榜逻辑<strong><em></em></strong>:<em>*</em></p><p>服务端内置 SQLite，核心表结构非常简单：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">CREATE TABLE IF NOT EXISTS players (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    score INTEGER NOT NULL
);</code></pre><p>reset 之后，玩家初始分数固定为 <code>50</code>。服务里还会维护若干机器人。</p><p><code>db_pick_default_opponent</code> 对应的 SQL 是：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">SELECT id, name, score
FROM players
WHERE name != ? AND score &lt;= 50
ORDER BY RANDOM()
LIMIT 1;</code></pre><p>也就是说，<code>cmd=5</code> 只能随机匹配 <code>score &lt;= 50</code> 的对手。</p><p><strong><em>reset 的隐藏重点<em>*</em></em></strong>:<em>*</em></p><p><code>db_reset_challenge_state</code> 不是简单地清空当前状态，它会把若干固定机器人分数重置为一组常量：</p><p>[50, 40, 30, 20, 20, 10, 2000000]</p><p>这意味着：</p><p> 服务里天然存在一个 <code>2000000</code> 分的榜一机器人</p><p> 只要能把自己分数打成 <code>4294967295</code>，就会直接无敌</p><p><strong><em>分析<em>*</em></em></strong>五子棋逻辑与可用赢法<strong><em></em></strong>:<em>*</em></p><p>棋盘大小为 <code>15 x 15</code>，玩家执黑，AI 执白。</p><p>实战中，第 3 步需要对 <code>10</code> 分机器人稳定拿下一盘。脚本中使用的固定赢法如下：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">(7,7)
(6,7)
(8,7)
(9,7)
(10,7)</code></pre><p>这条线是可行的。一次本地复现时 AI 的应对为：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">7,7   -&gt; AI=6,6
6,7   -&gt; AI=5,5
8,7   -&gt; AI=4,4
9,7   -&gt; AI=5,7
10,7  -&gt; Win</code></pre><p>最后形成横向五连，成功获胜。</p><p><strong><em>开始定位<em>*</em></em></strong>漏洞点<strong><em></em></strong>:<em>*</em></p><p><em>结算流程*</em>:*</p><p><code>settle_game</code> 是整道题最关键的函数。</p><p>我分析它做了两件事：</p><p> 读取玩家当前分数、对手当前分数</p><p> 分别调用两个不同的分数更新函数</p><p>那么伪代码可以概括为：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">player_new = score_apply_buggy_update(player_score, opponent_score, result);
update_db(player_name, player_new);

opponent_new = score_apply_update(opponent_score, player_score, 1.0 - result);
update_db(opponent_name, opponent_new);</code></pre><p>这里最关键的一点是：</p><p> 玩家：<code>score_apply_buggy_update</code></p><p> 对手：<code>score_apply_update</code></p><p>也就是只有玩家这边走了带 bug 的更新逻辑。</p><p>正常版本 <code>score_apply_update</code></p><p>正常版本大致等价于：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">delta = lround(32 * (actual - expected));
new_score = current + delta;
if (new_score &lt; 0) new_score = 0;
return new_score;</code></pre><p>它会把分数下界截断到 <code>0</code>，因此不会出现负数。</p><p>Buggy 版本 <code>score_apply_buggy_update</code></p><p>玩家侧的 buggy 版本在尾部多了一个特殊分支，逻辑近似为：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">delta = lround(32 * (actual - expected));

if (current_score &lt;= 9 &amp;&amp; delta &lt; 0) {
    return current_score;
}

return current_score + delta;</code></pre><p>这段逻辑分析出来两个严重问题：</p><p> 没有像正常版本一样对结果做 <code>max(new_score, 0)</code> 的保护。</p><p> 只在 <code>current_score &lt;= 9 &amp;&amp; delta &lt; 0</code> 时才“保底不掉分”。</p><p>换句话说，只要玩家当前分数 <strong>大于 9</strong>，并且这次输棋扣分幅度足够大，就能直接得到负数。</p><p><strong><em>写库时的整数符号错误<em>*</em></em></strong>:<em>*</em></p><p><code>db_update_player_score</code> 里最终通过 <code>sqlite3_bind_int64</code> 把分数写回数据库。</p><p>问题在于调用时只把新分数放进了 <code>edx</code>，在 x86-64 SysV ABI 下这会发生 <strong>零扩展</strong>：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">edx = -1
rdx = 0x00000000ffffffff</code></pre><p>于是原本应该是 <code>-1</code> 的分数，被当成了：4294967295</p><p>SQLite 里看到的就是一个超大的正整数。</p><p><strong><em>如何构造</em></strong> <strong><em><code>-1</code></em></strong></p><p>题目的核心不是任意改分，而是把当前分数打成 <code>-1</code>。</p><p>远端 reset 后，脚本实测拿到的一组关键对手如下：</p><p> <code>残阳余温(50)</code></p><p> <code>余晖静候(30)</code></p><p> <code>指尖依旧(10)</code></p><p> <code>安然重演(20)</code></p><p> <code>风月空守(20)</code></p><p><strong><em>利用顺序如下<em>*</em></em></strong>:<em>*</em></p><p><strong><em>Step 1. 对 50 分机器人认输<em>*</em></em></strong>:<em>*</em></p><p> 50 -&gt; 40</p><p> 返回类似：</p><p> Resign Delta=-10 Score=40 Rank=1003 OppDelta=10 OppScore=60</p><p><strong><em>Step 2. 对 30 分机器人认输<em>*</em></em></strong>:<em>*</em></p><p> 40 -&gt; 28</p><p> 返回类似：</p><p> Resign Delta=-12 Score=28 Rank=1005 OppDelta=12 OppScore=42</p><p><strong><em>Step 3. 打赢 10 分机器人<em>*</em></em></strong>:<em>*</em></p><p> 使用前面那条固定赢法：</p><p> (7,7) (6,7) (8,7) (9,7) (10,7)</p><p> 结果：</p><p> 28 -&gt; 34</p><p> 同时这个 10 分机器人会掉到：</p><p> 10 -&gt; 4</p><p><strong><em>Step 4. 对一个 20 分机器人认输<em>*</em></em></strong>:<em>*</em></p><p>34 -&gt; 21</p><p><strong><em>Step 5. 对另一个 20 分机器人认输<em>*</em></em></strong>:<em>*</em></p><p>21 -&gt; 11</p><p><strong><em>Step 6. 再去匹配刚才已经掉到 4 分的机器人，并认输<em>*</em></em></strong>:<em>*</em></p><p> 此时玩家分数是 <code>11</code>，对手分数是 <code>4</code>。</p><p> 根据 buggy Elo 计算，这一步会得到：</p><pre class="language-Plain lang-Plain"><code class="language-Plain lang-Plain">delta = -12
11 + (-12) = -1</code></pre>服务端返回：
<p>  Resign Delta=-12 Score=4294967295 Rank=1 OppDelta=12 OppScore=16</p><p> 注意这里响应里已经直接显示了 <code>Score=4294967295</code>，说明零扩展写库已经成功发生。</p><p><strong><em>Step 7. 调用取 flag 接口<em>*</em></em></strong>:<em>*</em></p><p>这时候再发 <code>cmd=1</code> 即可：</p><p>NCTF{258454e8-045f-4d6c-819b-e289aa8c2607}</p></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/NCTF%202026%20Reverse%20Write%20Up#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/NCTF%202026%20Reverse%20Write%20Up</link><guid isPermaLink="true">https://lkaros.xin/posts/default/NCTF%202026%20Reverse%20Write%20Up</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Wed, 22 Apr 2026 06:05:26 GMT</pubDate></item><item><title><![CDATA[Windows双机内核调试环境配置]]></title><description><![CDATA[<link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c98603b3.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c99b1f85.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c98d620f.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c98f1b83.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c98e7b14.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85c99ccf30.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6c8aed8.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6c9561e.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6cce997.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6cd7919.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6d1793f.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6e36dbf.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6e52ae5.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6e67781.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6e83623.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d6edd992.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d70b67b4.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d706c7c1.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d707d4f4.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d70b5768.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d706359a.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d720749f.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d72102e2.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d721be76.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d7253774.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d725194e.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d73a9a01.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d73cf0f6.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d73d478b.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d73d91c3.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d73d4e21.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d755348a.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/22/69e85d75c31b5.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/win">https://lkaros.xin/posts/default/win</a></blockquote><div><h2 id="windows">Windows双机内核调试环境配置</h2><h3 id="">前言</h3><p>配置环境是因为参加了2026腾讯游戏安全技术竞赛，发现往年题目均多为虚拟驱动环境，所以就必须构建一个安全的调试环境，为了避免在物理机上发生一些错误导致被数据被破坏（<del>物理机给我蓝屏咋办</del>）。</p><h3 id="1vmwarewin">1.VMware与Win系统安装</h3><p>VMware安装：略（可以从网上找教程去装，VMware下载链接: <a href="https://pan.baidu.com/s/1OY2pFlNazFOcfWXuiq_nZQ?pwd=72mx）">https://pan.baidu.com/s/1OY2pFlNazFOcfWXuiq_nZQ?pwd=72mx）</a></p><p>Win系统：这里我看了cc老师的视频，里面是使用的是Windows 10 1709 x32版本，由于迅雷x86版本实在是太难下了(<del>等不及</del>)，所以我这里用的是x64版本，97小时吗有点意思</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c98603b3.png" alt="1" height="269" width="1347"/></p><p>Windows 10 x64 1709版本链接：<a href="https://pan.baidu.com/s/1fDswyXxyx7zeojDKDqZMaA?pwd=ukk8">https://pan.baidu.com/s/1fDswyXxyx7zeojDKDqZMaA?pwd=ukk8</a></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c99b1f85.png" alt="2"/></p><p>首先我们要关闭防火墙</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c98d620f.png" alt="3"/></p><p>关闭自动更新</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c98f1b83.png" alt="4"/></p><p>以及这是什么我也不知道hh，反正是个移除</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c98e7b14.png" alt="5"/></p><p>（管理员运行）工具地址：<a href="https://github.com/cnHHHHHcn/Windows-Driver/tree/main/Tool">Windows-Driver/Tool at main · cnHHHHHcn/Windows-Driver</a></p><p>其次关闭虚拟机，给虚拟机添加一个串行端口</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85c99ccf30.png" alt="6"/></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6c8aed8.png" alt="7"/></p><p>添加管道<code>\\.\pipe\com_1</code></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6c9561e.png" alt="8"/></p><p>打开虚拟机，管理员运行cmd输入下面命令</p><pre class="language-cmd lang-cmd"><code class="language-cmd lang-cmd">bcdedit /copy {current} /d &quot;Debug&quot;                         # 创建调试启动项
bcdedit /timeout 10                                       # 设置启动菜单等待时间</code></pre><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6cce997.png" alt="9"/></p><p>重启后你就能发现，多出一个Debug引导项，进入Debug引导项，回车</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6cd7919.png" alt="10"/></p><p>接下来输入三条命令</p><pre class="language-cmd lang-cmd"><code class="language-cmd lang-cmd">bcdedit /dbgsettings serial debugport:1 baudrate:115200  # 配置串口调试参数
bcdedit /bootdebug ON                                     # 启用启动阶段
bcdedit /debug ON                                         # 启用内核调试</code></pre><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6d1793f.png" alt="11"/></p><p>输入msconfig你就能看到这里调试✔</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6e36dbf.png" alt="12"/></p><pre class="language-cmd lang-cmd"><code class="language-cmd lang-cmd">#以下是可选命令，可自己添加
bcdedit /set testsigning on                               # 启用测试签名模式
bcdedit -set loadoptions DDISABLE_INTEGRITY_CHECKS        # 禁用驱动完整性检查
# 验证配置是否正确
bcdedit /enum                                             # 查看所有启动项
bcdedit /dbgsettings                                      # 查看调试设置</code></pre><p>接下来重启你将会卡死在黑屏界面，不用担心这是正常的</p><h3 id="2windbg">2.Windbg安装</h3><p>直接从微软官方下载即可</p><p><a href="https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/">安装 WinDbg - Windows drivers | Microsoft Learn</a></p><p>安装后打开windbg，单击Home 打开Settings，下面的Default symbos path 修改如下 *号之间路径可随意修改，只是用来缓存符号表用的</p><pre class=""><code class="">srv*D:\Symbols*http://msdl.microsoft.com/download/symbols</code></pre><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6e52ae5.png" alt="13"/></p><p>之后打开文件，选择Attach to kernel</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6e67781.png" alt="14"/></p><p>改成我这个样子再点击右下角Ok</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6e83623.png" alt="15"/></p><p>之后就可以了</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d6edd992.png" alt="16"/></p><p>接下来输入g
kd&gt; g
只要它断下来就输g 直到进入系统，期间会下载pdb文件</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d70b67b4.png" alt="17"/></p><p>你也可以单击Windbg 的Break 随便输入点命令例如!idt 会出现dumping IDT即宣告结束</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d706c7c1.png" alt="18"/></p><h3 id="3wdk">3.驱动WDK工具包安装</h3><p>驱动环境必须与SDK WDK一致</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d707d4f4.png" alt="19"/></p><p>这里我的SDK是从Visual Studio 2019下载的10.0.19041.0版本</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d70b5768.png" alt="20"/></p><p>所以WDK也需要选择19041版本</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d706359a.png" alt="21"/></p><p>下载链接：<a href="https://wwaob.lanzouw.com/i1vwz3nszate">https://wwaob.lanzouw.com/i1vwz3nszate</a></p><p>管理员打开安装</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d720749f.png" alt="22"/></p><p>由于我之前装过，所以我这里就演示一遍</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d72102e2.png" alt="23"/></p><p>漫长的等待.............................................出现这个就行了</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d721be76.png" alt="24"/></p><p>会出现，这里一定确保没有vs2019运行 单击install</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d7253774.png" alt="25"/></p><p>稍微的漫长等待...........................</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d725194e.png" alt="26"/></p><p>此时我们的Visual Studio 2019就会出现 驱动项目</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d73a9a01.png" alt="27"/></p><h3 id="4">4.创建例子调试</h3><p>前提需要做出如下配置</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d73cf0f6.png" alt="28"/></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d73d478b.png" alt="29"/></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d73d91c3.png" alt="30"/></p><p>我们需要写一个开启驱动与停止驱动</p><pre class="language-c lang-c"><code class="language-c lang-c">#include &lt;ntifs.h&gt;
//删除驱动
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
    DbgPrint(&quot;DriverUnload\r\n&quot;);
}
//加载驱动
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject , PUNICODE_STRING RegPath) {
    DbgPrint(&quot;DriverEntry\r\n&quot;);
    DriverObject-&gt;DriverUnload = DriverUnload;

    return STATUS_SUCCESS;
}</code></pre><p><img src="https://free.picui.cn/free/2026/04/22/69e85d73d4e21.png" alt="31"/></p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d755348a.png" alt="32"/></p><p>加个断点也是可以的（中文乱码了。。）</p><p><img src="https://free.picui.cn/free/2026/04/22/69e85d75c31b5.png" alt="33"/></p><p>以上便是整个驱动环境，记得打个快照！</p><h3 id="">总结</h3><p>驱动环境的配置在我看来还是比较好配置的，只是需要特定的SDK和WDK版本一致.</p><p>最后我测试的时候</p><p>我是64位系统，<strong>用的32位编译的sys 驱动无法启动，而转换成64位就能启动了</strong>，奇怪改日再研究一下</p></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/win#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/win</link><guid isPermaLink="true">https://lkaros.xin/posts/default/win</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Wed, 22 Apr 2026 05:17:25 GMT</pubDate></item><item><title><![CDATA[Java Script MCP配置]]></title><description><![CDATA[<link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/19/69e4ce0812206.png"/><link rel="preload" as="image" href="https://free.picui.cn/free/2026/04/19/69e4d019079d1.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/posts/default/jsmcp">https://lkaros.xin/posts/default/jsmcp</a></blockquote><div><h2 id="">前言</h2><p>本文将介绍Java Script MCP如何配置，以及正确使用.</p><h2 id="chrome">安装chrome</h2><p>这个随便找一个浏览器即可，这里我贴一个链接 链接: <a href="https://pan.baidu.com/s/1BTAYhdy9WHc1TQInv3TkSA?pwd=huqt">https://pan.baidu.com/s/1BTAYhdy9WHc1TQInv3TkSA?pwd=huqt</a> 提取码: huqt</p><p>打开Chrome远程调试</p><p>Window：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">&quot;C:\Program Files\Google\Chrome\Application\chrome.exe&quot; --remote-debugging-port=9222 --user-data-dir=&quot;C:\tmp\chrome-mcp&quot;</code></pre><p>MacOS：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">&quot;/Applications/Google Chrome.app/Contents/MacOS/Google Chrome&quot; --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-mcp</code></pre><p>启动后访问下面这个地址检查远程调试是否正常开启：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">http://127.0.0.1:9222/json/version</code></pre><p><img src="https://free.picui.cn/free/2026/04/19/69e4ce0812206.png" alt="1.png" height="333" width="1196"/></p><h2 id="mcp">mcp</h2><p>拉取项目，或者直接从github上下载：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">git clone https://github.com/NoOne-hub/JSReverser-MCP.git 
cd JSReverser-MCP</code></pre><p>安装依赖并构建：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">npm install npm run build</code></pre><p>我这里在CC配置：</p><pre class="language-json lang-json"><code class="language-json lang-json">{ &quot;command&quot;: &quot;node&quot;, &quot;args&quot;: [ &quot;D:\\CTF\\JSReverser-MCP-main\\build\\src\\index.js&quot;, &quot;--browserUrl&quot;, &quot;http://127.0.0.1:9222&quot; ] }</code></pre><p>另外还有几个工具： chrome-devtools</p><pre class="language-json lang-json"><code class="language-json lang-json">{ &quot;chrome-devtools&quot;: { &quot;command&quot;: &quot;npx&quot;, &quot;args&quot;: [ &quot;-y&quot;, &quot;chrome-devtools-mcp@latest&quot;, &quot;--browser-url&quot;, &quot;http://127.0.0.1:9222&quot; ] } }</code></pre><p>playwright-mcp</p><pre class="language-json lang-json"><code class="language-json lang-json">{ &quot;playwright&quot;: { &quot;command&quot;: &quot;npx&quot;, &quot;args&quot;: [ &quot;-y&quot;, &quot;@executeautomation/playwright-mcp-server&quot;, &quot;--browser-url&quot;, &quot;http://127.0.0.1:9222&quot; ] } }</code></pre><h2 id="codex">Codex中的配置</h2><pre class="language-json lang-json"><code class="language-json lang-json">[mcp_servers.js-reverse]
type = &quot;stdio&quot;
command = &quot;node&quot;
args = [&#x27;D:\CTF\JSReverser-MCP-main\build\src\index.js&#x27;, &quot;--browserUrl&quot;, &quot;http://127.0.0.1:9222&quot;]

[mcp_servers.chrome-devtools]
type = &quot;stdio&quot;
command = &quot;npx&quot;
args = [&quot;-y&quot;, &quot;chrome-devtools-mcp@latest&quot;, &quot;--browser-url&quot;, &quot;http://127.0.0.1:9222&quot;]

[mcp_servers.playwright-mcp-server]
type = &quot;stdio&quot;
command = &quot;npx&quot;
args = [&quot;-y&quot;, &quot;@executeautomation/playwright-mcp-server&quot;, &quot;--browser-url&quot;, &quot;http://127.0.0.1:9222&quot;]</code></pre><p>ok成功了</p><p><img src="https://free.picui.cn/free/2026/04/19/69e4d019079d1.png" alt="2.png"/></p></div><p style="text-align:right"><a href="https://lkaros.xin/posts/default/jsmcp#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/posts/default/jsmcp</link><guid isPermaLink="true">https://lkaros.xin/posts/default/jsmcp</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Mon, 20 Apr 2026 11:21:01 GMT</pubDate></item><item><title><![CDATA[关于博客]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://lkaros.xin/notes/1">https://lkaros.xin/notes/1</a></blockquote><span>绷不住了，倒腾来倒腾去，图片一直加载不出来 弄了半天终于好了。弄好再也不瞎捣鼓了....</span><p style="text-align:right"><a href="https://lkaros.xin/notes/1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://lkaros.xin/notes/1</link><guid isPermaLink="true">https://lkaros.xin/notes/1</guid><dc:creator><![CDATA[Lkaros]]></dc:creator><pubDate>Mon, 20 Apr 2026 06:49:54 GMT</pubDate></item></channel></rss>