<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>GDB on Rebel Zhang 的博客</title>
        <link>https://rebel1725.codeberg.page/blog/zh/tags/gdb/</link>
        <description>Recent content in GDB on Rebel Zhang 的博客</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-CN</language>
        <managingEditor>rebel1725@tilde.club (Rebel Zhang)</managingEditor>
        <webMaster>rebel1725@tilde.club (Rebel Zhang)</webMaster>
        <lastBuildDate>Wed, 27 May 2026 23:56:34 +0800</lastBuildDate><atom:link href="https://rebel1725.codeberg.page/blog/zh/tags/gdb/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>使用 GDB 和 Iaito/Radare2 的逆向工程示例</title>
            <link>https://rebel1725.codeberg.page/blog/zh/post/a-reverse-engineering-example-with-gdb-and-iaito-radare2/</link>
            <pubDate>Wed, 27 May 2026 23:56:34 +0800</pubDate><author>rebel1725@tilde.club (Rebel Zhang)</author>
            <guid>https://rebel1725.codeberg.page/blog/zh/post/a-reverse-engineering-example-with-gdb-and-iaito-radare2/</guid>
            <description>&lt;p&gt;过去几周里，我一直在学习逆向工程，并且&lt;a class=&#34;link&#34; href=&#34;../a-brief-introduction-into-reverse-engineering/&#34; &gt;写了一篇博客文章&lt;/a&gt;来分享我的第一次体验。最近，我又深入了一些，正在用静态分析和动态分析相结合的方式解决一个 CTF 题目，而我觉得这正是进一步解释逆向工程的一个很好的例子。&lt;/p&gt;&#xA;&lt;p&gt;我之前原本打算写一篇关于使用 GDB 进行动态分析的文章。不过，我发现这个题目同样非常适合用来解释 GDB。为了解出这个题目，我还会使用另一个工具，&lt;strong&gt;iaito&lt;/strong&gt;，它是 radare2 的图形前端。&lt;/p&gt;&#xA;&lt;h2 id=&#34;题目&#34;&gt;题目&#xA;&lt;/h2&gt;&lt;p&gt;cIMG 可以说是一种图像格式，用来在终端中表示由不同颜色字符组成的图像。它由以下部分构成：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;魔数，也就是 &lt;code&gt;cIMG&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;版本号；&lt;/li&gt;&#xA;&lt;li&gt;宽度和高度；&lt;/li&gt;&#xA;&lt;li&gt;数据，每个像素都包含 R、G、B 和 ASCII 字符。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这个题目要求你向一个可执行程序提供一个 cIMG 文件，只有当 cIMG 满足某些条件时，它才会给出 flag。&lt;/p&gt;&#xA;&lt;h2 id=&#34;源代码&#34;&gt;源代码&#xA;&lt;/h2&gt;&lt;p&gt;在这里获取题目的&lt;a class=&#34;link&#34; href=&#34;https://raw.githubusercontent.com/pwncollege/intro-to-cybersecurity-dojo/refs/heads/main/reverse-engineering/cimg-framebuffer-mini-c/_0/cimg.c&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;source&lt;/a&gt;。这份源代码看起来使用的是 BSD 2-Clause 许可证。&lt;/p&gt;&#xA;&lt;p&gt;使用 GCC 编译它：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ gcc -O3 challenge.c -o challenge&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;打开 Python 并创建一个示例 cIMG 文件：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ python3&#xA;Python 3.13.5 (Jun 25 2025, 18:55:22) [GCC 14.2.0] on linux&#xA;Type &amp;#34;help&amp;#34;, &amp;#34;copyright&amp;#34;, &amp;#34;credits&amp;#34; or &amp;#34;license&amp;#34; for more information.&#xA;&amp;gt;&amp;gt;&amp;gt; with open(&amp;#34;example.cimg&amp;#34;, &amp;#34;wb&amp;#34;) as file:&#xA;...     file.write(b&amp;#34;cIMG\x02\0\x0a\x0a&amp;#34;)&#xA;...     file.write(b&amp;#34;\x50&amp;#34;*(10*10*4))&#xA;...     &#xA;8&#xA;400&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用题目的二进制程序打开这个示例 cIMG 文件：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;user@learnaarch64asm:~$ ./challenge ./example.cimg &#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个文件会输出内容，但不会给出 flag。&lt;/p&gt;&#xA;&lt;h2 id=&#34;静态分析&#34;&gt;静态分析&#xA;&lt;/h2&gt;&lt;p&gt;我们需要构造一个能 &lt;em&gt;给出 flag&lt;/em&gt; 的 cIMG 文件。先做静态分析。&lt;/p&gt;&#xA;&lt;p&gt;在 iaito 中打开文件。执行 &lt;code&gt;aaa&lt;/code&gt; 分析，并打开 &lt;code&gt;main&lt;/code&gt; 函数的图形视图。你会得到：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_001_417726312315916099.png&#34;&#xA;&#x9;width=&#34;3532&#34;&#xA;&#x9;height=&#34;5056&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;main 函数的图形&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;69&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;167px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我们可以看到在 &lt;code&gt;0x00000cac&lt;/code&gt; 处读取文件头，在 &lt;code&gt;0x00000cc4&lt;/code&gt; 处检查魔数，在 &lt;code&gt;0x00000cd0&lt;/code&gt; 处检查版本号，在 &lt;code&gt;0x00000ce0&lt;/code&gt; 处读取宽度和高度。然后它使用 &lt;code&gt;malloc&lt;/code&gt; 为图像数据分配一块内存，并把数据读入其中。&lt;/p&gt;&#xA;&lt;p&gt;魔数是 &lt;code&gt;cIMG&lt;/code&gt;，换成十六进制就是 &lt;code&gt;63 49 4d 47&lt;/code&gt;；版本号是一个 32 位小端整数；高度和宽度都是 8 位整数。&lt;/p&gt;&#xA;&lt;h2 id=&#34;flag-条件&#34;&gt;flag 条件&#xA;&lt;/h2&gt;&lt;p&gt;我们直接看函数结尾。显然，&lt;code&gt;sym.win&lt;/code&gt; 是会给出 flag 的函数。它在 &lt;code&gt;0x00000e8c&lt;/code&gt; 被调用，而这一步是在 &lt;code&gt;0x00000e58&lt;/code&gt; 处的一次检查之后执行的。这项检查要求 &lt;code&gt;w19&lt;/code&gt; 不能为零。&lt;/p&gt;&#xA;&lt;h2 id=&#34;篡改控制流&#34;&gt;篡改控制流&#xA;&lt;/h2&gt;&lt;p&gt;该启动 GDB 了。&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ gdb --args ./challenge ./example.cimg &#xA;GNU gdb (Debian 16.3-1) 16.3&#xA;Copyright (C) 2024 Free Software Foundation, Inc.&#xA;License GPLv3+ or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;&#xA;This is free software: you are free to change and redistribute it.&#xA;There is NO WARRANTY, to the extent permitted by law.&#xA;Type &amp;#34;show copying&amp;#34; and &amp;#34;show warranty&amp;#34; for details.&#xA;This GDB was configured as &amp;#34;aarch64-linux-gnu&amp;#34;.&#xA;Type &amp;#34;show configuration&amp;#34; for configuration details.&#xA;For bug reporting instructions, please see:&#xA;&amp;lt;https://www.gnu.org/software/gdb/bugs/&amp;gt;.&#xA;Find the GDB manual and other documentation resources online at:&#xA;    &amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.&#xA;&#xA;For help, type &amp;#34;help&amp;#34;.&#xA;Type &amp;#34;apropos word&amp;#34; to search for commands related to &amp;#34;word&amp;#34;...&#xA;Reading symbols from ./challenge...&#xA;(No debugging symbols found in ./challenge)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 &lt;code&gt;main&lt;/code&gt; 函数处打断点并启动程序：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *main&#xA;Breakpoint 1 at 0xc44&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db enabled]&#xA;Using host libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1&amp;#34;.&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们注意到 GDB 的数据地址有一个 &lt;code&gt;0xaaaaaaaa0000&lt;/code&gt; 的偏移。&lt;/p&gt;&#xA;&lt;p&gt;在 flag 条件检查之前打断点，然后继续执行：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0x0000aaaaaaaa0e58&#xA;Breakpoint 2 at 0xaaaaaaaa0e58&#xA;(gdb) continue&#xA;Continuing.&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&#xA;Breakpoint 2, 0x0000aaaaaaaa0e58 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改寄存器 &lt;code&gt;w19&lt;/code&gt; 的内容并继续执行：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) set $w19 = 1&#xA;(gdb) continue&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后这个可执行程序就会给出 flag。至此任务完成，祝你有美好的一天 :-p&lt;/p&gt;&#xA;&lt;p&gt;开玩笑的。如果我们真的这样结束这个题目，逆向工程的乐趣就没了。让我们继续深入。&lt;/p&gt;&#xA;&lt;h2 id=&#34;数据检查&#34;&gt;数据检查&#xA;&lt;/h2&gt;&lt;p&gt;回到 iaito。点击图中的 &lt;code&gt;w19&lt;/code&gt;。这会在图里的所有指令中高亮该寄存器。向前回溯图形，寻找线索。&lt;/p&gt;&#xA;&lt;p&gt;我们可以在图中看到一些对 &lt;code&gt;sym.imp.memcmp&lt;/code&gt; 的调用。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;memcmp&lt;/code&gt; 是 C 标准库中的一个函数，声明在 &lt;code&gt;&amp;lt;string.h&amp;gt;&lt;/code&gt; 中，它会逐字节比较两块内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;int memcmp(const void *ptr1, const void *ptr2, size_t n);&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ptr1&lt;/code&gt; 和 &lt;code&gt;ptr2&lt;/code&gt; 分别指向那两块内存，而 &lt;code&gt;n&lt;/code&gt; 指定要比较的字节数。如果两块内存完全相同，该函数返回 &lt;code&gt;0&lt;/code&gt;；否则返回一个非零值。&lt;/p&gt;&#xA;&lt;p&gt;这里有四次 &lt;code&gt;memcmp&lt;/code&gt; 调用。每次调用后面都跟着 &lt;code&gt;cmp w0, 0&lt;/code&gt;。除了第一次调用之外，&lt;code&gt;cmp&lt;/code&gt; 后面还有 &lt;code&gt;cset w0, eq&lt;/code&gt; 和 &lt;code&gt;and w19, w19, w0&lt;/code&gt;。我们可以得出结论：如果 &lt;code&gt;memcmp&lt;/code&gt; 发现差异，&lt;code&gt;w19&lt;/code&gt; 就会被清零，因为那时 &lt;code&gt;w0&lt;/code&gt; 会变成零。因此我们猜测，需要让四对内存块完全相同。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;x0&lt;/code&gt; 和 &lt;code&gt;x1&lt;/code&gt; 是要比较的两个地址。&lt;code&gt;x2&lt;/code&gt; 包含大小；&lt;code&gt;mov x2, 0x18&lt;/code&gt; 告诉我们大小是 24 字节。&lt;/p&gt;&#xA;&lt;p&gt;再次打开 GDB，并在第一次 &lt;code&gt;memcmp&lt;/code&gt; 之前检查内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0x0000aaaaaaaa0e78&#xA;Breakpoint 1 at 0xaaaaaaaa0e78&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0e78 in main ()&#xA;(gdb) info registers x0 x1&#xA;x0             0xaaaaaaac12a0      187649984565920&#xA;x1             0xaaaaaaac00c0      187649984561344&#xA;(gdb) x/24xb $x0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;(gdb) x/24xb $x1&#xA;0xaaaaaaac00c0 &amp;lt;desired_output&amp;gt;:        0x1b    0x5b    0x33    0x38    0x3b   0x32     0x3b    0x32&#xA;0xaaaaaaac00c8 &amp;lt;desired_output+8&amp;gt;:      0x33    0x31    0x3b    0x30    0x31   0x37     0x3b    0x31&#xA;0xaaaaaaac00d0 &amp;lt;desired_output+16&amp;gt;:     0x33    0x30    0x6d    0x63    0x1b   0x5b     0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随机字节。我看不出它们是什么意思。&lt;/p&gt;&#xA;&lt;p&gt;回到 iaito。第一块内存是从 &lt;code&gt;x22&lt;/code&gt; 载入的，而 &lt;code&gt;x22&lt;/code&gt; 又是从地址 &lt;code&gt;var_48h&lt;/code&gt; 载入的。&lt;code&gt;afv&lt;/code&gt; 告诉我们它距离 &lt;code&gt;sp&lt;/code&gt; 是 &lt;code&gt;0x48&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[0x00000e70]&amp;gt; afv&#xA;arg signed int argc @ x0&#xA;arg char ** s @ x1&#xA;var int64_t var_50h @ sp+0x0&#xA;var int64_t var_50h_2 @ sp+0x8&#xA;var int64_t var_10h @ sp+0x10&#xA;var int64_t var_10h_2 @ sp+0x18&#xA;var int64_t var_20h @ sp+0x20&#xA;var int64_t var_20h_2 @ sp+0x28&#xA;var void * buf @ sp+0x38&#xA;var int64_t var_3ch @ sp+0x3c&#xA;var int64_t var_3eh @ sp+0x3e&#xA;var int64_t var_3fh @ sp+0x3f&#xA;var int64_t var_40h @ sp+0x40&#xA;var int64_t var_48h @ sp+0x48&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;重新启动 GDB，并在 &lt;code&gt;main&lt;/code&gt; 被调用后跳过函数序言：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *main&#xA;Breakpoint 1 at 0xaaaaaaaa0c44&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()&#xA;(gdb) si&#xA;0x0000aaaaaaaa0c48 in main ()&#xA;(gdb) si&#xA;0x0000aaaaaaaa0c4c in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;查看栈指针的值并设置一个观察点：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers sp&#xA;sp             0xfffffffff290      0xfffffffff290&#xA;(gdb) watch *(long long *)0xfffffffff2d8&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;继续执行：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-(gdb)&#34; data-lang=&#34;(gdb)&#34;&gt;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&#xA;Old value = 281474840286680&#xA;New value = 0&#xA;0x0000aaaaaaaa0c5c in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 iaito 中定位这个地址。我们可以看到，在 &lt;code&gt;0x00000c58&lt;/code&gt;（也就是 &lt;code&gt;0x00000c5c&lt;/code&gt; 前一条指令）处，&lt;code&gt;var_48h&lt;/code&gt; 通过一个 &lt;code&gt;str&lt;/code&gt; 被清零了。&lt;/p&gt;&#xA;&lt;p&gt;继续往下，看看这个变量是在什么地方被设置的：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&#xA;Old value = 0&#xA;New value = 187649984565920&#xA;0x0000aaaaaaaa1358 in initialize_framebuffer ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们看到一个我们还没探索过的新函数：&lt;code&gt;initialize_framebuffer&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;在 iaito 中打开这个函数：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_002_13426986448128896865.png&#34;&#xA;&#x9;width=&#34;1775&#34;&#xA;&#x9;height=&#34;1384&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;initialize\_framebuffer 函数的图形&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;128&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;307px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我们可以看到，&lt;code&gt;0x00001354&lt;/code&gt; 处的一条指令把 &lt;code&gt;malloc&lt;/code&gt; 分配出来的内存地址写入了变量。不过它被表示为 &lt;code&gt;[x22, 0x10]&lt;/code&gt;。我们来检查一下 &lt;code&gt;x22&lt;/code&gt; 的内容：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers $x22&#xA;x22            0xfffffffff2c8      281474976707272&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;0xfffffffff2c8 + 0x10 = 0xfffffffff2d8&lt;/code&gt;。这正好就是 &lt;code&gt;var_48h&lt;/code&gt; 的地址。&lt;/p&gt;&#xA;&lt;p&gt;此外，传给 &lt;code&gt;malloc&lt;/code&gt; 的大小也很有意思。它是通过把偏移 6 和 7 处的值相乘，再把结果乘以 &lt;code&gt;w1&lt;/code&gt; 得到的，而 &lt;code&gt;w1&lt;/code&gt; 是 &lt;code&gt;0x18&lt;/code&gt;（24），最后再加上 &lt;code&gt;x0&lt;/code&gt;，即 1。&lt;/p&gt;&#xA;&lt;p&gt;我们也来查看这些偏移处的值：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/1xb 0xfffffffff2ce&#xA;0xfffffffff2ce: 0x0a&#xA;(gdb) x/1xb 0xfffffffff2cf&#xA;0xfffffffff2cf: 0x0a&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;两个都是 10。它们看起来就是我们之前指定的图像宽度和高度。让我们通过设置硬件观察点来验证一下。由于 AArch64 需要对齐，我们把硬件观察点设在 &lt;code&gt;0xfffffffff2cc&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) watch *(unsigned int *)0xfffffffff2cc&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;跳过 GNU C Library 的触发：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-Hardware&#34; data-lang=&#34;Hardware&#34;&gt;&#xA;Old value = 0&#xA;New value = 65535&#xA;0x0000fffff7fccd08 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 65535&#xA;New value = 0&#xA;0x0000fffff7fd5668 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1&amp;#34;]&#xA;&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 0&#xA;New value = 65535&#xA;0x0000fffff7fcc3a4 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里我们看到内存在 &lt;code&gt;0x00000c54&lt;/code&gt; 处被清零了：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 65535&#xA;New value = 0&#xA;0x0000aaaaaaaa0c58 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们看到一个 &lt;code&gt;stp xzr, xzr, [buf]&lt;/code&gt;。从上面的 &lt;code&gt;afv&lt;/code&gt; 我们知道 &lt;code&gt;buf&lt;/code&gt; 距离栈指针是 &lt;code&gt;0x38&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;在 &lt;code&gt;0x00000cb8&lt;/code&gt; 处，我们可以看到一个从 &lt;code&gt;buf&lt;/code&gt; 读取的 &lt;code&gt;ldr&lt;/code&gt;，然后是魔数检查。&lt;/p&gt;&#xA;&lt;p&gt;在 &lt;code&gt;0x00000c9c&lt;/code&gt; 处，我们可以看到一些有意思的内容：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0x00000c9c      add      x21,    sp,     0x38&#xA;0x00000ca0      mov      x2,     8                          ; size_t nbyte&#xA;0x00000ca4      mov      x1,     x21                        ; void *buf&#xA;0x00000ca8      mov      w0,     0&#xA;0x00000cac      bl       sym.imp.read                       ; ssize_t read(int fildes, void *buf, size_t nbyte)&#xA;; ssize_t read(0, 0x0000000000000000, 0x00000000)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此我们已经确认，这些偏移处的两个值就是宽度和高度。&lt;/p&gt;&#xA;&lt;p&gt;回到 iaito 和 GDB 中的 &lt;code&gt;initialize_framebuffer&lt;/code&gt; 函数。设置一个观察点并检查内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1354&#xA;Breakpoint 1 at 0xaaaaaaaa1354&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1354 in initialize_framebuffer ()&#xA;(gdb) info registers x0&#xA;x0             0xaaaaaaac12a0      187649984565920&#xA;(gdb) watch *(char *)0xaaaaaaac12a0&#xA;Hardware watchpoint 2: *(char *)0xaaaaaaac12a0&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(char *)0xaaaaaaac12a0&#xA;&#xA;Old value = 0 &amp;#39;\000&amp;#39;&#xA;New value = 27 &amp;#39;\033&amp;#39;&#xA;0x0000aaaaaaaa13bc in initialize_framebuffer ()&#xA;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x32&#xA;0xaaaaaaac12a8: 0x35    0x35    0x3b    0x32    0x35    0x35    0x3b    0x32&#xA;0xaaaaaaac12b0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把它和上面的数据对比一下：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb $x0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;0xaaaaaaac12a7&lt;/code&gt; 是第一个不同的字节。我们可以对它设置观察点：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) watch *(char *)0xaaaaaaac12a7&#xA;Hardware watchpoint 3: *(char *)0xaaaaaaac12a7&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 3: *(char *)0xaaaaaaac12a7&#xA;&#xA;Old value = 50 &amp;#39;2&amp;#39;&#xA;New value = 48 &amp;#39;0&amp;#39;&#xA;0x0000aaaaaaaa128c in display ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;出现了一个新的函数需要探索。在 iaito 中打开它。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_003_2825852626928846418.png&#34;&#xA;&#x9;width=&#34;1129&#34;&#xA;&#x9;height=&#34;2426&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;initialize\_framebuffer 函数的图形&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;46&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;111px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我们可以看到，数据变化发生在 &lt;code&gt;0x00001288&lt;/code&gt;，是一条 &lt;code&gt;stp&lt;/code&gt; 指令。&lt;code&gt;0x0000128c&lt;/code&gt; 处的 &lt;code&gt;str&lt;/code&gt; 指令也很有意思；我们来看看这里的内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x35    0x35    0x6d    0x20    0x1b    0x5b    0x30    0x6d&#xA;(gdb) si&#xA;0x0000aaaaaaaa1290 in display ()&#xA;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从 &lt;code&gt;0xaaaaaaac12b0&lt;/code&gt; 开始的 8 字节块已经被 &lt;code&gt;str&lt;/code&gt; 指令改写了。寄存器说明了一切：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers x2&#xA;x2             0xaaaaaaac12a0      187649984565920&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们不必太在意 &lt;code&gt;x2&lt;/code&gt;。重点关注 &lt;code&gt;x4&lt;/code&gt;、&lt;code&gt;x5&lt;/code&gt; 和 &lt;code&gt;x1&lt;/code&gt;。在 &lt;code&gt;0x00001268&lt;/code&gt; 和 &lt;code&gt;0x00001270&lt;/code&gt; 处，我们看到一个非常重要的寄存器 &lt;code&gt;x25&lt;/code&gt;。它保存着一块内存的地址，而这块内存里装着将要写入 &lt;code&gt;x2&lt;/code&gt; 指向内存的数据。检查这块内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb $x25&#xA;0xfffffffff270: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xfffffffff278: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xfffffffff280: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 &lt;code&gt;0x00001238&lt;/code&gt; 处，&lt;code&gt;x25&lt;/code&gt; 中的地址被复制到 &lt;code&gt;x0&lt;/code&gt;。然后，后面的几个寄存器被填入相应的数据。接着调用了 &lt;code&gt;snprintf&lt;/code&gt;。&lt;code&gt;snprintf&lt;/code&gt; 会把格式化输出写入字符数组：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;int snprintf(char *str, size_t size, const char *format, ...);&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;它会把格式化字符串写到 &lt;code&gt;x25&lt;/code&gt; 所指向的内存中。重新启动 GDB，并检查 &lt;code&gt;x2&lt;/code&gt; 指向的内存：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1258&#xA;Breakpoint 1 at 0xaaaaaaaa1258&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1258 in display ()&#xA;(gdb) x/s $x2&#xA;0xaaaaaaaa1510: &amp;#34;\033[38;2;%03d;%03d;%03dm%c\033[0m&amp;#34;&#xA;(gdb) x/29xb $x2&#xA;0xaaaaaaaa1510: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x25&#xA;0xaaaaaaaa1518: 0x30    0x33    0x64    0x3b    0x25    0x30    0x33    0x64&#xA;0xaaaaaaaa1520: 0x3b    0x25    0x30    0x33    0x64    0x6d    0x25    0x63&#xA;0xaaaaaaaa1528: 0x1b    0x5b    0x30    0x6d    0x00&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这就是格式字符串。如果你对 &lt;code&gt;x/s&lt;/code&gt; 返回的字符串不太放心，可以用 Python 逐字节打印这个字符串：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(b&amp;#34;\x1b\x5b\x33\x38\x3b\x32\x3b\x25\x30\x33\x64\x3b\x25\x30\x33\x64\x3\&#xA;b\x25\x30\x33\x64\x6d\x25\x63\x1b\x5b\x30\x6d\x00&amp;#34;)&#xA;b&amp;#39;\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00&amp;#39;&#xA;&amp;gt;&amp;gt;&amp;gt; blob = b&amp;#39;\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00&amp;#39;&#xA;&amp;gt;&amp;gt;&amp;gt; for i1 in range(len(blob)):&#xA;...     blob[i1:i1+1]&#xA;...     &#xA;b&amp;#39;\x1b&amp;#39;&#xA;b&amp;#39;[&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;8&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;2&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;m&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;c&amp;#39;&#xA;b&amp;#39;\x1b&amp;#39;&#xA;b&amp;#39;[&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;m&amp;#39;&#xA;b&amp;#39;\x00&amp;#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个格式字符串有四个占位符。前三个是 &lt;code&gt;%03d&lt;/code&gt;，它会把整数格式化为至少三位的十进制字符串（例如 7 会变成 &lt;code&gt;007&lt;/code&gt;，15 会变成 &lt;code&gt;015&lt;/code&gt;）。最后一个是 &lt;code&gt;%c&lt;/code&gt;，它只匹配一个字符。&lt;/p&gt;&#xA;&lt;p&gt;现在我们有了这个字节模式：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    [N1]&#xA;[N1]    [N1]    0x3b    [N2]    [N2]    [N2]    0x3b    [N3]&#xA;[N3]    [N3]    0x6d    [CH]    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在我们可以检查这些占位符究竟填入了什么：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers x3 x4 x5 x6&#xA;x3             0x50                80&#xA;x4             0x50                80&#xA;x5             0x50                80&#xA;x6             0x50                80&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;它们全都是 &lt;code&gt;0x50&lt;/code&gt;，看起来就是我们在示例 cIMG 中使用的数据。现在我们重建这个 cIMG，让这一点更清楚：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; with open(&amp;#34;example.cimg&amp;#34;, &amp;#34;wb&amp;#34;) as file:&#xA;...     file.write(b&amp;#34;cIMG\x02\0\x0a\x0a\x12\x34\x56\x78&amp;#34;)&#xA;...     file.write(b&amp;#34;\x50&amp;#34;*(10*10*4-4))&#xA;...     &#xA;12&#xA;396&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1258&#xA;Breakpoint 1 at 0xaaaaaaaa1258&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1258 in display ()&#xA;(gdb) info registers x3 x4 x5 x6&#xA;x3             0x12                18&#xA;x4             0x34                52&#xA;x5             0x56                86&#xA;x6             0x78                120&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此，上面十六进制模式中的 N1、N2 和 N3 分别对应 R、G 和 B，而 CH 就是我们要填入的字符。&lt;/p&gt;&#xA;&lt;p&gt;现在我们可以回到 GDB，在每个 &lt;code&gt;memcmp&lt;/code&gt; 之前设置断点，运行 &lt;code&gt;x/24xb $x1&lt;/code&gt; 来找出期望的 framebuffer，然后利用这些 framebuffer 还原出期望的 cIMG 数据。接着用十六进制编辑器把数据填进去。&lt;/p&gt;&#xA;&lt;h2 id=&#34;大小检查&#34;&gt;大小检查&#xA;&lt;/h2&gt;&lt;p&gt;然而，在填入期望数据之后，可执行程序仍然没有给出 flag。用 GDB 检查内存之后，我们可以知道这些 &lt;code&gt;memcmp&lt;/code&gt; 的返回值都如预期那样是零。因此，应该还有别的条件没有满足。&lt;/p&gt;&#xA;&lt;p&gt;回到 iaito，从第一个 &lt;code&gt;memcmp&lt;/code&gt; 往回滚动。我们可以看到在 &lt;code&gt;0x00000d60&lt;/code&gt; 处有一个 &lt;code&gt;cmp&lt;/code&gt;，它把 &lt;code&gt;var_40h&lt;/code&gt; 与 4 进行比较。在 &lt;code&gt;initialize_framebuffer&lt;/code&gt; 中的 &lt;code&gt;0x00001348&lt;/code&gt; 处，我们可以找到一个 &lt;code&gt;str&lt;/code&gt;，它保存了宽度与高度的乘积。&lt;/p&gt;&#xA;&lt;p&gt;随后 &lt;code&gt;0x00000d60&lt;/code&gt; 处的 &lt;code&gt;cmp&lt;/code&gt; 决定 &lt;code&gt;w0&lt;/code&gt; 的值。如果 &lt;code&gt;var_40h&lt;/code&gt; 等于 4，那么 &lt;code&gt;w0&lt;/code&gt; 将变成 1，否则变成 0。&lt;/p&gt;&#xA;&lt;p&gt;在 &lt;code&gt;0x00000d78&lt;/code&gt; 处，&lt;code&gt;w0&lt;/code&gt; 与 0 比较，这会影响 &lt;code&gt;0x00000d84&lt;/code&gt; 处 &lt;code&gt;ccmp&lt;/code&gt; 指令的行为。&lt;code&gt;[x22, 0x13]&lt;/code&gt; 的内容被载入到 &lt;code&gt;w0&lt;/code&gt;，而 &lt;code&gt;[x1, 0x13]&lt;/code&gt; 被载入到 &lt;code&gt;w2&lt;/code&gt;。根据 &lt;code&gt;memcmp&lt;/code&gt;，我们很容易知道 &lt;code&gt;x22&lt;/code&gt; 是实际 framebuffer 的地址，而 &lt;code&gt;x1&lt;/code&gt; 是期望 framebuffer 的地址。既然我们已经让数据与期望值一致，并且偏移 19 处是一个 ASCII 字符，那么 &lt;code&gt;w0&lt;/code&gt; 和 &lt;code&gt;w2&lt;/code&gt; 应该相同，而且都不应为零。&lt;/p&gt;&#xA;&lt;p&gt;因此我们可以得出结论：如果 &lt;code&gt;var_40h&lt;/code&gt; 等于 4，那么 &lt;code&gt;w2&lt;/code&gt; 会与 &lt;code&gt;w0&lt;/code&gt; 比较，这会把 &lt;code&gt;w19&lt;/code&gt; 设为 1。否则，&lt;code&gt;w2&lt;/code&gt; 会与 0 比较，从而把 &lt;code&gt;w19&lt;/code&gt; 置零。&lt;/p&gt;&#xA;&lt;p&gt;在第一次 &lt;code&gt;memcmp&lt;/code&gt; 之后，因为 &lt;code&gt;0x00000e7c&lt;/code&gt; 处 &lt;code&gt;w0&lt;/code&gt; 等于 0，所以 &lt;code&gt;w19&lt;/code&gt; 会与 0 比较。然后 &lt;code&gt;cset&lt;/code&gt; 会在不相等时把 &lt;code&gt;w19&lt;/code&gt; 设为 1，否则设为 0。&lt;/p&gt;&#xA;&lt;p&gt;现在用十六进制编辑器修改 cIMG，把宽度和高度改成乘积为 4 的值。例如，我们可以设为 &lt;code&gt;0202&lt;/code&gt;。这样可执行程序就应该会给出 flag。&lt;/p&gt;&#xA;&lt;h2 id=&#34;结论&#34;&gt;结论&#xA;&lt;/h2&gt;&lt;p&gt;上面的过程包括：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;使用 radare2 和 iaito 进行静态分析；&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;使用 GDB 进行动态分析，其中包括：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;篡改控制流；&lt;/li&gt;&#xA;&lt;li&gt;检查内存；&lt;/li&gt;&#xA;&lt;li&gt;检查整数值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这就是逆向工程中非常典型的一套流程。&lt;/p&gt;&#xA;&lt;p&gt;顺带一提，逆向这个可执行程序可能还有别的方法。不过无论采用哪种方法，通常都会同时涉及静态分析和动态分析。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
