<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Xusheng Li</title>
        <link>https://xusheng.dev/posts/</link>
        <description>Recent content in Posts on Xusheng Li</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>&lt;a href=&#34;https://creativecommons.org/licenses/by-nc/4.0/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CC BY-NC 4.0&lt;/a&gt;</copyright>
        <lastBuildDate>Sat, 19 Sep 2026 00:00:00 +0000</lastBuildDate>
        <atom:link href="https://xusheng.dev/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>LG Updated Its “Adware”</title>
            <link>https://xusheng.dev/posts/reversing/lg_monitor_app_manager_update/readme/</link>
            <pubDate>Sat, 19 Sep 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/lg_monitor_app_manager_update/readme/</guid>
            <description>&lt;p&gt;LG has been under fire for what its devices do after people buy them. &lt;a href=&#34;https://www.youtube.com/watch?v=Q9uefFYe6bM&#34;&gt;Gamers Nexus found&lt;/a&gt; that some LG monitors caused Windows to install an LG app that promoted more software, including McAfee. More recently, its investigation &lt;a href=&#34;https://www.youtube.com/watch?v=6IFVTcM28KA&#34;&gt;216,000,000 Spy TVs: The LG Smart TV Problem&lt;/a&gt; examined LG&amp;rsquo;s Automatic Content Recognition and other privacy concerns. Different products, same question: &lt;strong&gt;who decides what a device does after it enters your home?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I &lt;a href=&#34;https://xusheng.dev/posts/reversing/lg_monitor_app_manager/readme/&#34;&gt;previously reversed LG&amp;rsquo;s Monitor App Installer&lt;/a&gt; and the driver that installed it. Today, Windows Update offered a new version of that SoftwareComponent. Given the recent criticism, I wanted to know what LG had changed.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>LG has been under fire for what its devices do after people buy them. <a href="https://www.youtube.com/watch?v=Q9uefFYe6bM">Gamers Nexus found</a> that some LG monitors caused Windows to install an LG app that promoted more software, including McAfee. More recently, its investigation <a href="https://www.youtube.com/watch?v=6IFVTcM28KA">216,000,000 Spy TVs: The LG Smart TV Problem</a> examined LG&rsquo;s Automatic Content Recognition and other privacy concerns. Different products, same question: <strong>who decides what a device does after it enters your home?</strong></p>
<p>I <a href="/posts/reversing/lg_monitor_app_manager/readme/">previously reversed LG&rsquo;s Monitor App Installer</a> and the driver that installed it. Today, Windows Update offered a new version of that SoftwareComponent. Given the recent criticism, I wanted to know what LG had changed.</p>
<p><img src="../imgs/windows-update.png" alt="LG Electronics SoftwareComponent update"></p>
<h2 id="inside-the-update">Inside the update</h2>
<p>I exported version <code>2.0.2026.810</code> from the Driver Store. It contains three files:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>LGMonitorAppSoftwareComponent.cat       11.2 KB
</span></span><span style="display:flex;"><span>lgmonitorappsoftwarecomponent.inf        1.2 KB
</span></span><span style="display:flex;"><span>LGMonitorInstallManager.exe              2.2 MB
</span></span></code></pre></div><p>Before the update, the old INF used a Store-app link:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[LG_Monitor_Control_Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareType</span><span style="color:#f92672">=</span><span style="color:#e6db74">2</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareID</span><span style="color:#f92672">=</span><span style="color:#e6db74">pfn://LGElectronics.LGMonitorApp_cfnzzhwkr8z5w</span>
</span></span></code></pre></div><p><code>SoftwareType=2</code> tells Windows to install that Store package. This is how the old app arrived without a prompt.</p>
<p>The new INF removes that section and replaces it with this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[LGMonitor_Install.Software]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">AddSoftware</span><span style="color:#f92672">=</span><span style="color:#e6db74">LGMonitorInstallManager,,LG_Monitor_InstallManager_Install</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[LG_Monitor_InstallManager_Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareType</span>      <span style="color:#f92672">=</span> <span style="color:#e6db74">1</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareBinary</span>    <span style="color:#f92672">=</span> <span style="color:#e6db74">%13%\LGMonitorInstallManager.exe</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareArguments</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/driverflow,/campaign:1,/delay:60,&lt;&lt;DeviceInstanceID&gt;&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareVersion</span>   <span style="color:#f92672">=</span> <span style="color:#e6db74">1.1.0.0</span>
</span></span></code></pre></div><p><code>SoftwareType=1</code> instead tells Windows to run a bundled Win32 executable. <code>%13%</code> is the Driver Store. Device Setup Manager event 165 recorded the exact launch:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>&#34;C:\Windows\System32\DriverStore\FileRepository\
</span></span><span style="display:flex;"><span>lgmonitorappsoftwarecomponent.inf_amd64_46db1b4b0dcddd74\
</span></span><span style="display:flex;"><span>LGMonitorInstallManager.exe&#34;
</span></span><span style="display:flex;"><span> /driverflow
</span></span><span style="display:flex;"><span> /campaign:1
</span></span><span style="display:flex;"><span> /delay:60
</span></span><span style="display:flex;"><span> SWD\DRIVERENUM\{3846ad8c-dd27-433d-ab89-453654cd542a}#LGMonitorApp&amp;6&amp;4c65595&amp;0
</span></span></code></pre></div><p>The old driver automatically installed a Store app. The new driver automatically runs an EXE.</p>
<h2 id="what-the-new-program-does">What the new program does</h2>
<p><code>LGMonitorInstallManager.exe</code> is a .NET Framework WinForms application, version <code>2.0.2026.805</code>. It is obfuscated with <strong>Eazfuscator.NET</strong>: names are scrambled, strings encrypted, and anti-disassembly and anti-tamper protections enabled. That is an odd amount of protection for a consent dialog whose purpose is supposedly transparency.</p>
<p>I used <a href="https://github.com/holly-hacker/EazFixer">EazFixer</a> to recover its strings, removed <code>SuppressIldasmAttribute</code> from an analysis copy, and disassembled the IL.</p>
<p>Windows launches <code>/driverflow</code> elevated during device installation. It exits if:</p>
<ol>
<li>an older LG SoftwareComponent package with the previous <code>pfn://</code> auto-install directive is still present;</li>
<li>the user has already given a final answer for the current campaign.</li>
</ol>
<p>Otherwise it:</p>
<ol>
<li>copies itself from the Driver Store to <code>C:\Program Files\LG Electronics\LGMonitorInstallManager</code>;</li>
<li>creates a machine-wide <code>Run</code> entry so the question survives future logins;</li>
<li>creates an Add/Remove Programs entry named <strong>LG Monitor Software Notice</strong>;</li>
<li>creates <code>state.ini</code> under <code>C:\ProgramData\LG Electronics\LGMonitorInstallManager</code>;</li>
<li>starts a second copy in the active user&rsquo;s session with <code>/install /campaign:1 /delay:60</code>;</li>
<li>creates a cleanup task that eventually removes the copied EXE, Run entry, uninstall entry, state, and task.</li>
</ol>
<p>The user-session process waits <strong>60 seconds</strong>, checks again, and displays this:</p>
<p><img src="../imgs/consent-dialog.png" alt="LG Monitor App Installer consent dialog"></p>
<h2 id="when-the-prompt-appears-and-what-install-does">When the prompt appears and what Install does</h2>
<p>The prompt is suppressed if the old LG driver is still present, the Store app is already installed, the user has already answered the current campaign, or another copy is running. This is why it initially did not appear on my PC: the old, outranked driver package was still in the Driver Store.</p>
<p>Clicking <strong>Install</strong> removes the temporary Run entry and starts this hidden child process:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>%LOCALAPPDATA%\Microsoft\WindowsApps\winget.exe install \
</span></span><span style="display:flex;"><span>  --id 9PM9N6F47JB8 \
</span></span><span style="display:flex;"><span>  --source msstore \
</span></span><span style="display:flex;"><span>  --accept-package-agreements \
</span></span><span style="display:flex;"><span>  --accept-source-agreements \
</span></span><span style="display:flex;"><span>  --silent
</span></span></code></pre></div><p>Store product <code>9PM9N6F47JB8</code> resolves to:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Package: LGElectronics.LGMonitorApp
</span></span><span style="display:flex;"><span>PFN:     LGElectronics.LGMonitorApp_cfnzzhwkr8z5w
</span></span><span style="display:flex;"><span>Version: 1.2606.1601.0
</span></span><span style="display:flex;"><span>Arch:    x86
</span></span></code></pre></div><p>That is the <strong>same app the previous driver installed directly</strong>. LG has simply inserted a consent dialog before it.</p>
<h2 id="code-and-consent">Code and consent</h2>
<p>LG seems to misunderstand the consent problem. Its earlier defense emphasized that McAfee was optional. Strictly speaking, that was true: the Monitor App installed itself silently, then asked about the next thing.</p>
<p>The new design moves that question one step earlier:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Old:
</span></span><span style="display:flex;"><span>driver -&gt; automatically install LG Monitor App -&gt; ask about more software
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>New:
</span></span><span style="display:flex;"><span>driver -&gt; automatically execute LG consent app -&gt; ask to install LG Monitor App
</span></span></code></pre></div><p>This is an improvement: the full Store app is no longer installed before consent. But connecting a monitor still lets LG run an elevated program that copies itself into Program Files, creates persistence, and asks a marketing question. The question is more polite; the code that asks it still ran without asking.</p>
<p>A monitor should not need to run a two-megabyte obfuscated .NET program to ask whether I want another program. LG can simply link to its software and let me choose to get it.</p>
]]></content>
        </item>
        
        <item>
            <title>Detecting Insecure Software Updates at Scale with LLM Agents</title>
            <link>https://xusheng.dev/posts/reversing/detecting_insecure_software_updates_at_scale/main/</link>
            <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/detecting_insecure_software_updates_at_scale/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I used a hierarchy of low-cost source-reading agents, stronger review agents, and manual verification to trace how &lt;strong&gt;3,006 open-source projects&lt;/strong&gt; handle software updates.&lt;/li&gt;
&lt;li&gt;I found &lt;strong&gt;624 with an updater&lt;/strong&gt; that could download and then install, load, or execute code: 134 used a pinned signing key, 109 enforced a publisher identity, 332 relied only on HTTPS, and &lt;strong&gt;49 used broken transport security&lt;/strong&gt;. The other 2,382 had no updater or were notification-only.&lt;/li&gt;
&lt;li&gt;In those &lt;strong&gt;49 projects&lt;/strong&gt;, a man-in-the-middle attacker could replace update payloads and achieve remote code execution on users&amp;rsquo; machines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Browse the &lt;a href=&#34;https://xusheng6.github.io/update-security-study/&#34;&gt;dataset&lt;/a&gt; or inspect and correct it in the &lt;a href=&#34;https://github.com/xusheng6/update-security-study&#34;&gt;public repository&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>I used a hierarchy of low-cost source-reading agents, stronger review agents, and manual verification to trace how <strong>3,006 open-source projects</strong> handle software updates.</li>
<li>I found <strong>624 with an updater</strong> that could download and then install, load, or execute code: 134 used a pinned signing key, 109 enforced a publisher identity, 332 relied only on HTTPS, and <strong>49 used broken transport security</strong>. The other 2,382 had no updater or were notification-only.</li>
<li>In those <strong>49 projects</strong>, a man-in-the-middle attacker could replace update payloads and achieve remote code execution on users&rsquo; machines.</li>
</ul>
<p>Browse the <a href="https://xusheng6.github.io/update-security-study/">dataset</a> or inspect and correct it in the <a href="https://github.com/xusheng6/update-security-study">public repository</a>.</p>
<h2 id="why-update-security-matters">Why update security matters</h2>
<p>An updater downloads code and places it inside a trusted application. It may launch an installer, replace an executable, load a plugin, or flash firmware. That makes it a natural supply-chain target.</p>
<p><a href="https://www.cisa.gov/news-events/alerts/2020/12/13/active-exploitation-solarwinds-software">SolarWinds</a> distributed malicious code through trusted Orion updates. In 2023, attackers compromised the <a href="https://cloud.google.com/blog/topics/threat-intelligence/3cx-software-supply-chain-compromise">3CX</a> build environment after first compromising another software package. In 2025, attackers compromised infrastructure used by <a href="https://notepad-plus-plus.org/news/hijacked-incident-info-update/">Notepad++</a> and selectively redirected update requests toward malicious payloads.</p>
<p>These incidents had different root causes, but they point to the same trust boundary: <strong>an application should not execute a downloaded file merely because it came from its usual server</strong>. Properly validated HTTPS blocks ordinary network interception, but it does not help when the server, CDN account, storage bucket, or deployment credentials are compromised. Independent payload signatures can protect against that narrower—but important—class of attack.</p>
<p>This problem is not new. A <a href="https://www.usenix.org/legacy/event/hotsec06/tech/full_papers/bellissimo/bellissimo.pdf">2006 HotSec study</a> examined ten deployed update systems and found several vulnerable to weak man-in-the-middle attacks. Two decades later, <a href="https://www.usenix.org/conference/usenixsecurity26/presentation/wan">UpdSight</a> tested 85 desktop applications and found 22 exploitable update vulnerabilities. Those studies performed deeper hands-on testing of smaller samples. My goal was different: use agent-assisted source review to scale the initial survey to 3,006 open-source projects, then manually review the 49 Tier-D findings.</p>
<h2 id="what-i-scanned">What I scanned</h2>
<p>For each project, I asked:</p>
<ol>
<li>Does it update itself or install executable content?</li>
<li>If so, what authenticates the content before it runs?</li>
</ol>
<p>The agents followed the entire path:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>check version
</span></span><span style="display:flex;"><span>  -&gt; obtain manifest
</span></span><span style="display:flex;"><span>  -&gt; choose payload URL
</span></span><span style="display:flex;"><span>  -&gt; download payload
</span></span><span style="display:flex;"><span>  -&gt; verify or skip verification
</span></span><span style="display:flex;"><span>  -&gt; install / execute / load / flash
</span></span></code></pre></div><p>A <code>verifySignature()</code> function is not evidence by itself. It must check the relevant payload, use a trustworthy key, and stop installation on failure. Likewise, a SHA-256 value downloaded beside the payload from the same server is not independent authentication.</p>
<h2 id="the-four-tiers">The four tiers</h2>
<p>I used tiers because update security depends on the threat model. Tier C validates HTTPS and is not immediately vulnerable to an ordinary network attacker. But a high-security user may reject it because a compromised server or CDN can replace both the package and its hash.</p>
<p>I also recorded the underlying facts—transport settings, signatures, trusted keys or publishers, and source locations—rather than only a verdict. Each tier summarizes the trust model of one update path, not the security of an entire project.</p>
<h3 id="a--application-pinned-signing-key">A — application-pinned signing key</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>package, signature <span style="color:#f92672">=</span> download_update()
</span></span><span style="display:flex;"><span>trusted_key <span style="color:#f92672">=</span> key_embedded_in_application
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> verify(trusted_key, package, signature):
</span></span><span style="display:flex;"><span>    install(package)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    reject()
</span></span></code></pre></div><p>Compromising the website, CDN, DNS path, or repository is insufficient without an accepted signing key. Sparkle’s EdDSA support is one example: the public key is embedded in the application while the private key should remain separate from the update server.</p>
<h3 id="b--expected-operating-system-publisher">B — expected operating-system publisher</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>package <span style="color:#f92672">=</span> download_update()
</span></span><span style="display:flex;"><span>signature <span style="color:#f92672">=</span> os_code_signature(package)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> signature<span style="color:#f92672">.</span>valid <span style="color:#f92672">and</span> signature<span style="color:#f92672">.</span>publisher <span style="color:#f92672">==</span> EXPECTED_PUBLISHER:
</span></span><span style="display:flex;"><span>    install(package)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    reject()
</span></span></code></pre></div><p>The updater enforces the expected Authenticode or platform-signing identity. Merely checking that <em>someone</em> signed the file is not enough.</p>
<h3 id="c--validated-https-only">C — validated HTTPS only</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>manifest <span style="color:#f92672">=</span> https_get(server, verify_certificate<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>package <span style="color:#f92672">=</span> https_get(manifest<span style="color:#f92672">.</span>url, verify_certificate<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> sha256(package) <span style="color:#f92672">==</span> manifest<span style="color:#f92672">.</span>sha256:
</span></span><span style="display:flex;"><span>    install(package)
</span></span></code></pre></div><p>This blocks ordinary man-in-the-middle attacks. It does not survive compromise of the authorized distribution path because the attacker can replace both the package and its server-supplied hash.</p>
<h3 id="d--broken-transport-no-independent-signature">D — broken transport, no independent signature</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>client<span style="color:#f92672">.</span>verify_peer <span style="color:#f92672">=</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>client<span style="color:#f92672">.</span>verify_hostname <span style="color:#f92672">=</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>manifest <span style="color:#f92672">=</span> client<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;https://updates.example/manifest.json&#34;</span>)
</span></span><span style="display:flex;"><span>package <span style="color:#f92672">=</span> client<span style="color:#f92672">.</span>get(manifest<span style="color:#f92672">.</span>url)
</span></span><span style="display:flex;"><span>install(package)
</span></span></code></pre></div><p>The older version is even simpler: download an executable over HTTP and run it. Either design may allow an on-path attacker to substitute code.</p>
<p>Some projects used HTTPS but disabled certificate validation. Static analysis cannot tell us why. CA-bundle deployment problems, old certificate stores, and forgotten debugging workarounds are plausible explanations—not established causes.</p>
<p>I used <strong>N</strong> when no in-app updater was found or the application only opened a download page. N does not mean every external package manager or manual installation route is secure.</p>
<h2 id="results">Results</h2>
<table>
	<thead>
			<tr>
					<th>Label</th>
					<th style="text-align: right">Entries</th>
					<th style="text-align: right">All entries</th>
					<th style="text-align: right">A–D only</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>A</strong> — pinned payload key</td>
					<td style="text-align: right">134</td>
					<td style="text-align: right">4.5%</td>
					<td style="text-align: right">21.5%</td>
			</tr>
			<tr>
					<td><strong>B</strong> — expected publisher</td>
					<td style="text-align: right">109</td>
					<td style="text-align: right">3.6%</td>
					<td style="text-align: right">17.5%</td>
			</tr>
			<tr>
					<td><strong>C</strong> — validated HTTPS only</td>
					<td style="text-align: right">332</td>
					<td style="text-align: right">11.0%</td>
					<td style="text-align: right">53.2%</td>
			</tr>
			<tr>
					<td><strong>D</strong> — broken transport</td>
					<td style="text-align: right">49</td>
					<td style="text-align: right">1.6%</td>
					<td style="text-align: right">7.9%</td>
			</tr>
			<tr>
					<td><strong>N</strong> — none or notification-only</td>
					<td style="text-align: right">2,382</td>
					<td style="text-align: right">79.2%</td>
					<td style="text-align: right">—</td>
			</tr>
			<tr>
					<td><strong>Total</strong></td>
					<td style="text-align: right"><strong>3,006</strong></td>
					<td style="text-align: right"><strong>100%</strong></td>
					<td style="text-align: right">—</td>
			</tr>
	</tbody>
</table>
<p>The dataset contained <strong>624 entries (20.8%)</strong> with such an updater and <strong>2,382 (79.2%)</strong> with no updater or only update notifications. Among the 624 updater entries, Tier C was the largest group.</p>
<p>These are <strong>dataset entries</strong>, not 3,006 verified unique applications. The corpus contains duplicate projects, platform-specific implementations, and separate application, plugin, firmware, and content channels. It was collected for breadth rather than statistical sampling. The percentages describe this dataset—not all open-source software.</p>
<h2 id="the-agent-architecture">The agent architecture</h2>
<p>I performed the core research from <strong>August 3–23, 2026</strong>. Claude Opus 4.8 was the strongest model I used in that Claude Code workflow, but using it to read every repository would quickly exhaust the five-hour and weekly limits. I worked with an Opus 4.8 coordinator, while many Claude Sonnet 5 agents handled the broad source review. Opus 4.8 review agents consolidated the reports and rechecked suspected Tier-D findings.</p>
<p><img src="/posts/reversing/detecting_insecure_software_updates_at_scale/agent-architecture-v3.png" alt="The researcher works with an Opus 4.8 coordinator, Opus reviewers, and Sonnet 5 source-reading agents. Reviewers send Tier A, B, and C results directly to the dataset, while suspected Tier-D findings receive manual review before entering it."></p>
<p>The bulk agents cloned source with shallow history, searched for updater code, and recorded the revision, platform, behavior, transport, verification, trust root, and <code>file:line</code> evidence. They were read-only: no builds, dependency installation, or execution of repository code.</p>
<p>The final expansion from 2,008 to 3,006 entries took about <strong>15 hours</strong> and approximately <strong>8.5 million subagent tokens</strong>. Small, lower-cost contexts handled the repetitive reading; higher-cost reasoning was reserved for consolidation, disagreement, and findings with security impact.</p>
<p>The initial pass produced <strong>73 Tier-D candidates</strong>. Suspected vulnerabilities entered a feedback loop: stronger agents re-read the source and tried to disprove them. That pass retained 51 as D, moved six to C, moved fourteen to N, and refuted two. Later corrections produced the published historical count of 49.</p>
<p>This is why the hierarchy matters. Sonnet provided coverage; the adversarial Opus pass raised the bar before I called something broken. I then read all 49 final Tier-D findings myself before disclosure.</p>
<h2 id="responsible-disclosure">Responsible disclosure</h2>
<p>I tried to contact maintainers before publishing actionable Tier-D findings. The disclosure ledger records:</p>
<ul>
<li><strong>9</strong> GitHub Private Vulnerability Reports</li>
<li><strong>16</strong> coordinated-disclosure emails</li>
<li><strong>25</strong> projects contacted in total</li>
</ul>
<p>I could not find a usable private contact channel for the remaining 24 Tier-D projects. Of the 25 projects I contacted, two responded positively: <a href="https://github.com/FOGProject/fogproject">FOG Project</a> and <a href="https://github.com/SuiteCRM/SuiteCRM">SuiteCRM</a>. Both fixed the reported issues.</p>
<p>Writing 25 fully bespoke reports is difficult to scale. My compromise was to write the important opening myself—what I found, why I was contacting the project, and what I wanted the maintainer to do—then attach AI-assisted technical detail with the relevant code path and suggested remediation. That is not ideal, and some maintainers understandably disliked receiving AI-generated material. But silently publishing the findings, or sending no notification at all, would have been worse. The better lesson is that automation must reduce clerical work without replacing evidence review, respectful communication, or accountability for the report.</p>
<p>Some maintainers also challenged the threat model. Two recurring objections were that an attacker would need a fraudulent certificate for GitHub, or that an on-path attacker was unrealistic. Those objections do not apply when the updater itself disables certificate or hostname validation: the attacker can present an arbitrary certificate and does not need to break public-key cryptography or compromise a certificate authority.</p>
<p>An active on-path position is a real prerequisite for many Tier-D findings, and it should be stated rather than exaggerated. Possible positions include a hostile network, compromised router, malicious proxy, or redirected traffic. Server or CDN compromise is a stronger and different position—the main concern for Tier C—but repeated supply-chain incidents show that distribution infrastructure cannot be assumed infallible.</p>
<h2 id="where-i-want-the-ecosystem-to-move">Where I want the ecosystem to move</h2>
<p>Tier D is the immediate problem: plaintext HTTP, disabled certificate checks, or ignored TLS errors should be fixed.</p>
<p>Tier C is the larger supply-chain concern. TLS may work perfectly while a compromised server returns a malicious manifest and matching payload. The Notepad++ incident shows why distribution infrastructure should not be the only trust root.</p>
<p>The practical direction is straightforward:</p>
<ul>
<li><strong>Tier D → C:</strong> require HTTPS, certificate validation, and hostname validation</li>
<li><strong>Tier C → A/B:</strong> authenticate the payload independently of the download server</li>
<li>Fail closed on missing, invalid, or unexpected signatures</li>
<li>Keep signing keys separate from public distribution infrastructure</li>
<li>Test corrupted, unsigned, wrongly signed, and rolled-back updates</li>
</ul>
<p>Projects do not need to invent this themselves. <a href="https://theupdateframework.io/">The Update Framework</a> provides role separation, threshold signatures, rotation, and defenses against rollback and freeze attacks. <a href="https://sparkle-project.org/">Sparkle</a> supports EdDSA-signed macOS updates with a public key embedded in the application and can also require signed feeds.</p>
<p>The data remains public and correctable:</p>
<ul>
<li><a href="https://xusheng6.github.io/update-security-study/">Search all 3,006 analyses</a></li>
<li><a href="https://github.com/xusheng6/update-security-study">Review the evidence</a></li>
<li><a href="https://github.com/xusheng6/update-security-study/issues/new">Propose a correction</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Bringing Chinese Chess Master III: Jiangzu Back to Life</title>
            <link>https://xusheng.dev/posts/rebuilding-a-dos-xiangqi-engine/</link>
            <pubDate>Tue, 08 Sep 2026 00:00:00 -0400</pubDate>
            <guid>https://xusheng.dev/posts/rebuilding-a-dos-xiangqi-engine/</guid>
            <description>&lt;p&gt;&lt;a href=&#34;https://xusheng.dev/posts/rebuilding-a-dos-xiangqi-engine-zh/&#34;&gt;简体中文版&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Chinese Chess Master III: Jiangzu&lt;/em&gt; (象棋大師 III／將族) was a popular DOS Xiangqi game. Its developer’s &lt;a href=&#34;https://games.sina.com.cn/zhuanqu/guangpu/intro.shtml&#34;&gt;historical company profile&lt;/a&gt; claims 200,000 copies sold. I played a number of games against it when I was yong and won most of them. I’m an &lt;a href=&#34;https://xusheng.dev/posts/grandmaster/&#34;&gt;international Xiangqi grandmaster&lt;/a&gt;, so that wasn’t particularly surprising, but I still found it a worthwhile opponent.&lt;/p&gt;
&lt;p&gt;My father is also a Xiangqi fan and used to play this game when he was younger. I found his disk and played it a little myself. It came out in &lt;strong&gt;1992&lt;/strong&gt;, the same year I was born.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><a href="/posts/rebuilding-a-dos-xiangqi-engine-zh/">简体中文版</a></p>
<p><em>Chinese Chess Master III: Jiangzu</em> (象棋大師 III／將族) was a popular DOS Xiangqi game. Its developer’s <a href="https://games.sina.com.cn/zhuanqu/guangpu/intro.shtml">historical company profile</a> claims 200,000 copies sold. I played a number of games against it when I was yong and won most of them. I’m an <a href="https://xusheng.dev/posts/grandmaster/">international Xiangqi grandmaster</a>, so that wasn’t particularly surprising, but I still found it a worthwhile opponent.</p>
<p>My father is also a Xiangqi fan and used to play this game when he was younger. I found his disk and played it a little myself. It came out in <strong>1992</strong>, the same year I was born.</p>
<figure><img src="/posts/rebuilding-a-dos-xiangqi-engine/jiangzu-title.png"
			alt="The original 將族 title screen, with gold calligraphy and a red seal, running in DOSBox-X"><figcaption>
			<p>The original 將族 title screen. Game screenshots captured in <a href="https://dosbox-x.com/">DOSBox-X</a>; original artwork belongs to its respective owners.</p>
		</figcaption>
</figure>

<p>I liked the interface, especially the opponent-selection screen. It includes characters from the Chinese novel <em>Journey to the West</em> (西遊記), each with a portrait and a short description.</p>
<figure><img src="/posts/rebuilding-a-dos-xiangqi-engine/opponent-selection.png"
			alt="An open-book opponent-selection screen showing Spoon Sa, his portrait, biography, and strength rating"><figcaption>
			<p>The opponent-selection screen, with Spoon Sa selected.</p>
		</figcaption>
</figure>

<p>Eventually I wanted to do more than run it in DOS. Could I preserve the engine and play against it in a modern GUI?</p>
<p><strong>TL;DR:</strong> With Codex’s help, I rewrote the engine in C, added UCI/UCCI support, and checked it against the original on <strong>408,708 test cases</strong>. <a href="https://github.com/xusheng6/cch-cms1">The code is on GitHub</a>.</p>
<h2 id="a-little-background">A little background</h2>
<p>The game was developed by <strong>Yu Hsi-Shun (虞希舜)</strong> and <strong>T-Time Technology (光譜資訊)</strong>. It grew out of Yu’s earlier Xiangqi programming work and became a commercial DOS game with graphics, named opponents, and a challenge-based progression. <a href="https://read.99csw.com/book/579/18112.html">This historical account</a> describes how the 1992 release built on the earlier program.</p>
<p>Its 36 opponents look like different personalities, but underneath they share the same engine. Seven use a fixed two-ply search—roughly a move and a reply, with extra tactical searching. The others get different game clocks, which determine how much thinking time the engine can spend. Bogey gets one minute, Hand eight, and Skeleton 47: whole-game allowances, not time per move.</p>
<p>So the characters mostly change the search budget, not the engine’s understanding of Xiangqi. Some even share identical settings.</p>
<h2 id="rebuilding-the-engine">Rebuilding the engine</h2>
<p>I used Codex to investigate the game and write a portable C version. We used <a href="https://binary.ninja/">Binary Ninja</a> and disassembly to inspect the DOS code, then <a href="https://dosbox-x.com/">DOSBox-X</a> to run the game and capture its initialized memory.</p>
<figure><img src="/posts/rebuilding-a-dos-xiangqi-engine/game-board.png"
			alt="The original Xiangqi board with Spoon Sa’s portrait above the human player’s portrait on the left"><figcaption>
			<p>Playing against Spoon Sa in the original DOS game—the board and portraits I wanted to remember, even though the port itself is just the engine.</p>
		</figcaption>
</figure>

<p>The engine lives in <code>CMS1.EXE</code>, a module loaded by the main game, <code>CCH.EXE</code>. It is a classical engine: it searches moves and replies using alpha-beta search, then evaluates positions with hand-written rules and tables.</p>
<p>Getting a working rewrite was only part of the job. It soon agreed with the original on many positions, but there were still plenty of disagreements. I wanted more than an engine that played reasonably well. I wanted this particular engine’s decisions.</p>
<p>That meant recovering the small details too: exact evaluation constants, move order, repetition handling, and special cases in the search.</p>
<p>The rewrite comes to about <a href="https://github.com/xusheng6/cch-cms1/tree/main/port"><strong>3,400 lines of C code</strong></a>.</p>
<h2 id="building-a-feedback-loop">Building a feedback loop</h2>
<p>Rather than keep asking Codex to “make it more accurate,” I pushed for a repeatable test loop.</p>
<p>We built a harness using <a href="https://www.unicorn-engine.org/">Unicorn</a> to run the original 16-bit engine code without clicking through the DOS interface. Then we could:</p>
<ol>
<li>Generate a large batch of positions from legal move histories.</li>
<li>Run the original and the rewrite at the same search depth, with opening books disabled.</li>
<li>Compare their chosen moves and scores.</li>
<li>Investigate disagreements, fix the rewrite, and repeat with fresh positions.</li>
</ol>
<p>This is how I think about <strong>harness engineering</strong> here: give the coding agent a way to run its work, measure the result, and get concrete feedback. The original program was our reference, so we did not have to trust that a plausible-looking translation was correct.</p>
<p>Some differences were tiny. In one case, both engines chose the same move, but one scored it 157 and the other 156. We traced that to the order in which they searched escape moves during a cannon check. Fixing the ordering resolved the discrepancy.</p>
<p>As the tests got faster, I kept asking for larger batches. When one batch matched, we generated another.</p>
<p><img src="/posts/rebuilding-a-dos-xiangqi-engine/progress.svg" alt="The test corpus grows to 408,708 cases across development checkpoints"></p>
<p>The chart shows the growing test corpus, not an accuracy percentage. The final rewrite matched the original’s move and score on <strong>408,708 cases</strong>, mostly at depths 2–4, with a smaller set of deeper searches. The last three fresh shallow batches had 20,000 cases each and found no differences.</p>
<p>That does not mean the algorithm is a perfect rewrite. Since <a href="https://arxiv.org/abs/2507.07480">program equivalence is undecidable in general</a>, the testing convergence gives me enough confidence to call the rewrite successful.</p>
<h2 id="how-strong-is-it">How strong is it?</h2>
<p>I also wanted a rough strength estimate. Full-strength <a href="https://github.com/official-pikafish/Pikafish">Pikafish</a> would be far too strong, so we used a version with calibrated strength controls: <code>UCI_LimitStrength</code> enables reduced-strength play, and <code>UCI_Elo</code> sets the target rating.</p>
<p>Against Pikafish set to 1400, CCH won 13 games and lost 11. That gives a rough estimate of <strong>1,429 on Pikafish’s engine-rating scale</strong>. It is a small sample with wide uncertainty, not an official human Elo. These matches also preceded the final fidelity fixes.</p>
<p>We tested ElephantEye, another open-source Xiangqi engine. It is much weaker than full-strength Pikafish, but still comfortably stronger than CCH: it beat our experimental larger-cache CCH variant 8–0. The <a href="https://github.com/xusheng6/cch-cms1/blob/main/VALIDATION.md#strength-is-a-different-question">validation notes</a> explain the limitations of these comparisons.</p>
<p>None of that takes away from what I like about the game. I wanted to bring back an opponent my father and I had played, not turn it into a modern champion.</p>
<p>The port now runs natively, speaks UCI and UCCI, and works in my Xiangqi GUI, Pikaboard. The <a href="https://github.com/xusheng6/cch-cms1">repository</a> includes the engine and smoke tests, but not the original game binaries, opening book, or artwork. Recovered numeric tables are included; <a href="https://github.com/xusheng6/cch-cms1/blob/main/NOTICE.md">NOTICE</a> explains their provenance and the license scope.</p>
<p>An engine born in the same year as me gets to keep playing.</p>
]]></content>
        </item>
        
        <item>
            <title>Rebuilding a Lost iOS Game: From Screenshots to Reverse Engineering to AI</title>
            <link>https://xusheng.dev/posts/rebuilding-a-lost-ios-game/</link>
            <pubDate>Sun, 06 Sep 2026 00:00:00 -0400</pubDate>
            <guid>https://xusheng.dev/posts/rebuilding-a-lost-ios-game/</guid>
            <description>&lt;p&gt;My wife used to play an iOS puzzle game called &lt;em&gt;Block Crush Blitz&lt;/em&gt;. When we wanted to install it on a new iPad in march 2026, we found that it was no longer available in the App Store. She liked the game, and it did not look too complicated to recreate. So I decided to give it a try. Happy wife, happy life.&lt;/p&gt;
&lt;p&gt;I started by asking &lt;a href=&#34;https://claude.ai/&#34;&gt;Claude&lt;/a&gt; to rewrite it from screenshots. That got us a working game quite quickly, but some of the pieces were different from the original. To figure out which pieces should actually appear, I eventually had to download the old app, buy an iPhone, jailbreak it, and reverse engineer the executable. Then I got distracted by another idea: how well could an AI play this game?&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>My wife used to play an iOS puzzle game called <em>Block Crush Blitz</em>. When we wanted to install it on a new iPad in march 2026, we found that it was no longer available in the App Store. She liked the game, and it did not look too complicated to recreate. So I decided to give it a try. Happy wife, happy life.</p>
<p>I started by asking <a href="https://claude.ai/">Claude</a> to rewrite it from screenshots. That got us a working game quite quickly, but some of the pieces were different from the original. To figure out which pieces should actually appear, I eventually had to download the old app, buy an iPhone, jailbreak it, and reverse engineer the executable. Then I got distracted by another idea: how well could an AI play this game?</p>
<h2 id="the-first-attempt-rebuild-what-i-could-see">The first attempt: rebuild what I could see</h2>
<p>In March 2026, I gave Claude screenshots of the original game and asked it to recreate it. The rules are simple: drag a piece onto the board, and when a line is full, the blocks on that line disappear. On the hexagonal board, lines can run in three directions.</p>
<p>I asked Claude to reproduce the shapes and colors in the images. I used <a href="https://flutter.dev/">Flutter</a> so I could run it on both the iPad and my Mac, and I called the remake <a href="https://github.com/xusheng6/hexris">Hexris</a>. After a few rounds of fixing dragging and adjusting the piece sizes, it was playable.</p>

    <img src="/posts/rebuilding-a-lost-ios-game/hexris-game.png"  alt="The first Hexris remake running in hex mode"  class="left"  style="display: block; max-height: 600px; max-width: 100%; width: auto; height: auto; margin: 1.5rem auto;"  />


<p>However, after playing it for a while, we noticed that some pieces did not seem to belong. The remake generated shapes that we did not remember seeing in the original, and some were quite difficult to place.</p>
<p>I tried removing the shapes that seemed wrong. This improved the game, but I was still relying on memory. I did not know whether I had removed the right pieces, or whether the remaining ones appeared with the right frequencies.</p>
<p>At that point I wanted to find the original app and look at its code. There had to be a piece generator somewhere in it.</p>
<h2 id="recovering-a-delisted-ipa">Recovering a delisted IPA</h2>
<p>Getting a copy was harder than I expected. The app was no longer listed in either the Chinese or US App Store. I also thought I might have saved a copy on my Mac, but could not find one.</p>
<p>Fortunately, my wife&rsquo;s Apple ID had acquired it in 2016. Although the listing was gone, Apple still had a copy available to that account. An archived store page helped us identify the App Store ID, <code>1052414239</code>, and bundle ID, <code>com.balabafire.1010hex</code>.</p>
<p>I used <a href="https://github.com/yuhao7370/ipatool-py">ipatool-py</a> to download it from Apple. The downloader needed some fixes because Apple&rsquo;s authentication endpoints had changed, but eventually it worked. I got version 1.7, an IPA of about 11 MB.</p>
<p>Of course, an IPA downloaded from the App Store is still encrypted with FairPlay. Both the ARMv7 and ARM64 slices had <code>cryptid 1</code>. I could inspect the resources and some unencrypted data, but the code I needed was in the encrypted region.</p>
<h2 id="why-a-virtual-iphone-was-not-enough">Why a virtual iPhone was not enough</h2>
<p>I did not have a jailbroken device, so I first looked into using <a href="https://www.corellium.com/">Corellium</a>. I hoped I could sign into the account on a virtual iPhone, launch the app, and dump the decrypted code there.</p>
<p>That did not work out. Corellium requires an already-decrypted IPA for this use; its virtual devices cannot run the encrypted App Store copy through the normal Apple services. I had the wrong half of the solution.</p>
<p>I bought an iPhone 8 Plus and jailbroke it with <a href="https://github.com/palera1n/palera1n">palera1n</a>. Getting the phone into DFU mode took many attempts. I watched videos, retried the button sequence, and kept seeing the Apple logo. After replacing the USB cable, I finally got it working. I had spent quite a bit of time blaming my button timing.</p>
<p>I then signed into the account that had acquired the app and installed the encrypted copy using <code>appinst</code>, with <a href="https://github.com/akemin-dayo/AppSync">AppSync Unified</a> on the phone. It launched! This worked because the authorization to use the purchased app was tied to the Apple ID, rather than limited to the device on which it was originally downloaded. I could use that account on the newly bought iPhone to run the encrypted copy, even though the app was no longer listed in the store.</p>
<p>For the dump, I used <a href="https://github.com/londek/ipadecrypt">ipadecrypt</a>, which handled the device-side helper and produced a decrypted IPA for analysis. It used <a href="https://www.openssh.com/">OpenSSH</a> to communicate with the phone, with the USB connection forwarded through <code>iproxy</code> from <a href="https://github.com/libimobiledevice/libusbmuxd">libusbmuxd</a>.</p>
<p>This was a nice discovery on its own. An app disappearing from the store does not necessarily mean its binary is lost. In this case, a previous purchase let me download it, and a compatible physical device let me run it. It still depends on Apple serving the app and on the old app working on the available iOS version. A purchase record alone cannot guarantee either of those things.</p>
<h2 id="reverse-engineering-the-game">Reverse engineering the game</h2>
<p>The game uses <a href="https://github.com/cocos2d/cocos2d-x">Cocos2d-x</a> and native C++. Before decryption, we had already checked the resources for shape definitions. There were UI scenes and sprite atlases, but no gameplay scripts or per-piece images. The game draws pieces by repeating a single hexagonal sprite. The actual shapes were defined in the executable.</p>
<p>With the decrypted ARM64 code open in <a href="https://binary.ninja/">Binary Ninja</a>, we could finally follow the catalog construction and random selection routines. The answer to my original question was quite specific: hex mode has <strong>22 possible pieces</strong>, consisting of a one-cell dot and 21 four-cell shapes. Each entry is selected uniformly, with a probability of <code>1/22</code>, or about 4.545%.</p>
<p>There were also two masks elsewhere in the configuration that looked like pieces, but they were not added to the active catalog. Simply collecting every shape-like constant would have given us the wrong answer. We needed to follow how the generator used them.</p>
<p><img src="/posts/rebuilding-a-lost-ios-game/hexris-piece-catalog.png" alt="The 22 recovered hex pieces"></p>
<p>We also checked the surrounding rules. The board has 61 cells, with nine across the middle. Full lines clear along three axes. A used tray slot is replaced immediately, and the game ends when none of the three available pieces can fit. The level calculation does not change the active hex piece distribution in version 1.7; all 22 entries are already available at level one.</p>
<p>Scoring was another difference from the first remake. Ordinary placement gives no points. When a move clears lines, it awards:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>distinct cells removed + lineCount × (10 + 5 × (lineCount - 1))
</span></span></code></pre></div><p>Intersections count only once among removed cells. The line bonuses are therefore 10, 30, 60, and 100 points for one, two, three, and four simultaneous lines.</p>
<p>The square mode turned out to use a different generator. It selects one of nine shape families using integer weights totaling 20, then selects an orientation uniformly within that family. For example, the dot and 3×3 square each occur 5% of the time, while the 2×2 square occurs 15%. Hex mode is simpler: all 22 active catalog entries are equally likely.</p>
<p>I asked AI to add the recovered rules to Hexris as an optional Classic mode, keeping Undo for convenience. The previous version remains the default, so we can play either one.</p>
<h2 id="then-i-wondered-can-an-ai-play-it">Then I wondered: can an AI play it?</h2>
<p>After getting the rules working, I suddenly wanted to see how well an AI could play. I asked the coding agent to try a few approaches using a simulator of the recovered hex rules.</p>
<p>We compared random moves, a greedy player that evaluates one move at a time, a trained version of that evaluator, and two search methods that look ahead at possible future pieces. Each played the same 20 seeded games, with a limit of 1,000 moves per game:</p>
<table>
	<thead>
			<tr>
					<th>Agent</th>
					<th style="text-align: right">Mean score</th>
					<th style="text-align: right">Median score</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Random</td>
					<td style="text-align: right">49.75</td>
					<td style="text-align: right">18.5</td>
			</tr>
			<tr>
					<td>Greedy</td>
					<td style="text-align: right">1,055.30</td>
					<td style="text-align: right">899.5</td>
			</tr>
			<tr>
					<td>Learned linear</td>
					<td style="text-align: right">1,061.95</td>
					<td style="text-align: right">661.0</td>
			</tr>
			<tr>
					<td>Sampled expectimax</td>
					<td style="text-align: right"><strong>3,721.55</strong></td>
					<td style="text-align: right">2,537.0</td>
			</tr>
			<tr>
					<td>Stochastic rollout</td>
					<td style="text-align: right">3,256.90</td>
					<td style="text-align: right"><strong>2,777.0</strong></td>
			</tr>
	</tbody>
</table>
<p>Looking ahead helped much more than tuning the evaluator. Expectimax had the highest average score, with its best game reaching 10,346. Rollout had a slightly better median, but took more than twice as long.</p>
<p>I also tried a small neural network. Combined with search, it averaged 2,532 points in a separate test, versus 5,074 for the handwritten evaluator on the same seeds. So the neural version did not improve things this time.</p>
<p>Here is one complete expectimax game. It is a representative run rather than the longest one: 2,309 points over 235 moves. The three available pieces are shown below the board, and the gold outline marks the piece that was just played.</p>
<video controls preload="metadata" playsinline style="display: block; width: 100%; height: auto; margin: 1.5rem 0 0.5rem;" poster="/posts/rebuilding-a-lost-ios-game/ai-play-expectimax-poster.png" aria-label="An expectimax agent playing Hexris">
  <source src="/posts/rebuilding-a-lost-ios-game/ai-play-expectimax.mp4" type="video/mp4">
  Your browser does not support embedded video. <a href="/posts/rebuilding-a-lost-ios-game/ai-play-expectimax.mp4">Download the video</a> instead.
</video>

<p>My wife&rsquo;s best was around <strong>6,000 points</strong>. This was not a controlled comparison, but it was fun to see some of the agents&rsquo; games exceed that score.</p>
<p>I originally expected to spend a little time getting Claude to draw a board and some pieces. I certainly did not expect to buy an old iPhone to find out which pieces belonged in it. But now I think it is totally worth the time and effort!</p>
]]></content>
        </item>
        
        <item>
            <title>Windows Age APIs Can Expose Your Date of Birth to Apps</title>
            <link>https://xusheng.dev/posts/reversing/windows_age_api_dob/main/</link>
            <pubDate>Fri, 04 Sep 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/windows_age_api_dob/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Windows&amp;rsquo; new age API is supposed to give an app only your age range&lt;/li&gt;
&lt;li&gt;The current implementation first obtains your date of birth, then calculates the range inside the requesting app&amp;rsquo;s process&lt;/li&gt;
&lt;li&gt;The requesting process can intercept the token or DOB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;../social-preview.png&#34; alt=&#34;UserMgrProxy decoding the birthdate claim and calculating the user’s exact age&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-api-returns-a-rangebut-that-is-not-the-whole-story&#34;&gt;The API returns a range—but that is not the whole story&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.windowslatest.com/2026/09/03/windows-11-will-start-telling-apps-your-age-but-microsoft-says-it-wont-share-your-birthday/&#34;&gt;Windows Latest recently reported&lt;/a&gt; that Windows 11 will allow apps to request an age range without receiving the user&amp;rsquo;s birthday. Microsoft&amp;rsquo;s &lt;a href=&#34;https://learn.microsoft.com/en-us/windows/apps/develop/security/age-signals/&#34;&gt;documentation&lt;/a&gt; describes &lt;code&gt;GetUserAgeRangeAsync&lt;/code&gt;, which returns ranges such as 13–15, 16–17, or 18+.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>Windows&rsquo; new age API is supposed to give an app only your age range</li>
<li>The current implementation first obtains your date of birth, then calculates the range inside the requesting app&rsquo;s process</li>
<li>The requesting process can intercept the token or DOB</li>
</ul>
<p><img src="../social-preview.png" alt="UserMgrProxy decoding the birthdate claim and calculating the user’s exact age"></p>
<h2 id="the-api-returns-a-rangebut-that-is-not-the-whole-story">The API returns a range—but that is not the whole story</h2>
<p><a href="https://www.windowslatest.com/2026/09/03/windows-11-will-start-telling-apps-your-age-but-microsoft-says-it-wont-share-your-birthday/">Windows Latest recently reported</a> that Windows 11 will allow apps to request an age range without receiving the user&rsquo;s birthday. Microsoft&rsquo;s <a href="https://learn.microsoft.com/en-us/windows/apps/develop/security/age-signals/">documentation</a> describes <code>GetUserAgeRangeAsync</code>, which returns ranges such as 13–15, 16–17, or 18+.</p>
<p>On Windows 11 25H2 build 26200.9168, the API is present but currently returns <code>E_NOTIMPL</code> because a <code>DigitalSafetyAPI</code> feature flag is disabled. The implementation is already inside <code>UserMgrProxy.dll</code>, however, so I looked at what will happen after that gate is enabled.</p>
<p>The flow is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>requesting application
</span></span><span style="display:flex;"><span>  -&gt; UserMgrProxy.dll requests an ID token through WAM
</span></span><span style="display:flex;"><span>  -&gt; the token contains the birthdate claim
</span></span><span style="display:flex;"><span>  -&gt; UserMgrProxy.dll extracts the DOB
</span></span><span style="display:flex;"><span>  -&gt; it calculates the exact age
</span></span><span style="display:flex;"><span>  -&gt; it converts that age to a range
</span></span><span style="display:flex;"><span>  -&gt; the public API returns only the range
</span></span></code></pre></div><p>The relevant decompiled code is unambiguous:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (DecodeJwtAndExtractClaim(
</span></span><span style="display:flex;"><span>        token, tokenLength, <span style="color:#e6db74">&#34;birthdate&#34;</span>, <span style="color:#f92672">&amp;</span>birthdate, ...))
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int32_t</span> age <span style="color:#f92672">=</span> CalculateAgeFromDoB(<span style="color:#f92672">&amp;</span>birthdate);
</span></span><span style="display:flex;"><span>    memset(<span style="color:#f92672">&amp;</span>birthdate, <span style="color:#ae81ff">0</span>, <span style="color:#66d9ef">sizeof</span>(birthdate));
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (arge <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">int64_t</span> range <span style="color:#f92672">=</span> MapAgeToRange(age);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// omitted...
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In other words, Windows receives the DOB and derives the range locally.</p>
<h2 id="the-conversion-happens-inside-the-requesting-process">The conversion happens inside the requesting process</h2>
<p>This distinction matters because <code>UserMgrProxy.dll</code> is loaded into the process that calls the API. I confirmed this by invoking the API from PowerShell and observing <code>C:\Windows\System32\UserMgrProxy.dll</code> loaded in that same PowerShell process. The OAuth response, JWT, and decoded <code>birthdate</code> therefore enter the requesting application&rsquo;s address space before being reduced to an age range.</p>
<p><strong>This means the process can hook the JWT decoder or inspect its memory to obtain the user&rsquo;s date of birth.</strong> There are plenty of ways to do this because the process has full control over itself, including the DLLs loaded into it.</p>
<p>Windows does perform capability, account, and caller checks. Those checks can prevent an unauthorized app from using the API, but they cannot hide data from an authorized app after Windows has placed that data inside the app&rsquo;s own process.</p>
<p>I also downloaded and analyzed <code>UserMgrProxy.dll</code> version 10.0.29648.1000 from the latest available Windows preview build. Microsoft changed some internal plumbing, but the core mechanism for obtaining the age range remains the same. While Microsoft can change what it eventually enables or ships, there is currently no sign that it has replaced this design with a broker that returns only an age range.</p>
<p>The application should call a trusted Windows broker or Microsoft service that returns the age range directly. The DOB-to-range conversion should happen before any result crosses into the application&rsquo;s process. It is surprising to see Microsoft make such a basic privacy-design mistake, even if temporarily.</p>
]]></content>
        </item>
        
        <item>
            <title>Bring Your Own Trusted Caller (BYOTC): A New Way to Exploit Vulnerable Windows Drivers (Part 1)</title>
            <link>https://xusheng.dev/posts/byotc/main/</link>
            <pubDate>Thu, 03 Sep 2026 00:00:00 -0400</pubDate>
            <guid>https://xusheng.dev/posts/byotc/main/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://xusheng.dev/posts/byotc/social-preview.jpg&#34; alt=&#34;A malicious process injecting code into a signed trusted client, which passes caller verification and reaches a privileged kernel driver&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bring Your Own Vulnerable Driver (BYOVD) is a familiar Windows attack technique: an attacker loads a legitimate, signed driver and abuses a flaw in it to gain kernel-level capabilities. Microsoft maintains a &lt;a href=&#34;https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules&#34;&gt;vulnerable-driver blocklist&lt;/a&gt; to make this harder, while the community-maintained &lt;a href=&#34;https://www.loldrivers.io/&#34;&gt;LOLDrivers&lt;/a&gt; project catalogs known vulnerable and malicious Windows drivers.&lt;/p&gt;
&lt;p&gt;But sometimes the dangerous capability is intentional. A security or system-inspection driver may legitimately need to terminate processes, open privileged handles, or resist interference. Removing that functionality would break the product, so the driver must instead decide &lt;em&gt;who is allowed to request it&lt;/em&gt;. Vendors therefore add caller authentication:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="/posts/byotc/social-preview.jpg" alt="A malicious process injecting code into a signed trusted client, which passes caller verification and reaches a privileged kernel driver"></p>
<p>Bring Your Own Vulnerable Driver (BYOVD) is a familiar Windows attack technique: an attacker loads a legitimate, signed driver and abuses a flaw in it to gain kernel-level capabilities. Microsoft maintains a <a href="https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules">vulnerable-driver blocklist</a> to make this harder, while the community-maintained <a href="https://www.loldrivers.io/">LOLDrivers</a> project catalogs known vulnerable and malicious Windows drivers.</p>
<p>But sometimes the dangerous capability is intentional. A security or system-inspection driver may legitimately need to terminate processes, open privileged handles, or resist interference. Removing that functionality would break the product, so the driver must instead decide <em>who is allowed to request it</em>. Vendors therefore add caller authentication:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>untrusted process ──X──&gt; privileged driver operation
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>trusted client  ───────&gt; privileged driver operation
</span></span></code></pre></div><p>This creates a second security boundary. The question is no longer only whether an untrusted executable can send a privileged IOCTL, but whether an attacker can take control of the trusted caller:</p>
<ul>
<li>Can code be injected into the trusted process?</li>
<li>Can an attacker launch the trusted image while retaining a powerful creator handle?</li>
<li>Can trust be inherited across processes, or remain valid after the process has been modified?</li>
</ul>
<p>I call this pattern <strong>Bring Your Own Trusted Caller (BYOTC)</strong>: instead of attacking the kernel interface directly, obtain code execution inside—or otherwise abuse—the user-mode program the driver already trusts, then make it invoke the privileged interface. The authorization check succeeds, but its security meaning has been lost. BYOTC is therefore a confused-deputy problem at the user/kernel boundary.</p>
<p>This post develops the idea through two case studies from my research: <a href="https://www.malwarebytes.com/">Malwarebytes</a>&rsquo; <code>mbamchameleon.sys</code> and <a href="https://www.systeminformer.com/">System Informer</a>&rsquo;s <code>SystemInformer.sys</code>. Their trust models differ substantially, but both illustrate the same lesson:</p>
<blockquote>
<p>Establishing—and continuously preserving—the integrity of a process at runtime is hard.</p>
</blockquote>
<h2 id="case-study-1-malwarebytes-chameleon">Case study 1: Malwarebytes Chameleon</h2>
<p>In June 2025, I reported a BYOTC issue in Malwarebytes&rsquo; <code>mbamchameleon.sys</code> (SHA-256: d86b9b20788b6bff70a1a4c4111b2ea33b9ec705cc6b8fe869362fc3899820a3). The driver exposed an IOCTL, <code>0x222024</code>, that ultimately invoked <code>ZwTerminateProcess</code>. This was not a stray memory-safety bug: process termination was a feature of the driver.</p>
<p>The driver did not allow every process to use that operation. A caller first had to register as trusted through IOCTL <code>0x222008</code>. My reverse engineering showed that this path checked whether the calling process&rsquo;s image was signed by Malwarebytes.</p>
<p>The problem was that a valid signature describes the file that started the process; it does not prove that the process is still executing only Malwarebytes-controlled code.</p>
<p>For the proof of concept, I launched the legitimate, signed <code>MBAM.exe</code>, injected a DLL into it, registered that process as trusted, and then asked the driver to terminate Microsoft Defender&rsquo;s <code>MsMpEng.exe</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>attacker (administrator)
</span></span><span style="display:flex;"><span>        │
</span></span><span style="display:flex;"><span>        ├── inject DLL ──&gt; signed MBAM.exe
</span></span><span style="display:flex;"><span>        │                        │
</span></span><span style="display:flex;"><span>        │                        ├── IOCTL 0x222008: register trusted client
</span></span><span style="display:flex;"><span>        │                        └── IOCTL 0x222024: terminate MsMpEng.exe
</span></span><span style="display:flex;"><span>        │
</span></span><span style="display:flex;"><span>        └────────────────&gt; mbamchameleon.sys ──&gt; ZwTerminateProcess
</span></span></code></pre></div><video controls preload="metadata" playsinline style="display: block; width: 100%; height: auto; margin: 1.5rem 0 0.5rem;" poster="/posts/byotc/malwarebytes-poc-poster.jpg" aria-label="Malwarebytes Chameleon proof of concept">
  <source src="/posts/byotc/malwarebytes-poc.mp4" type="video/mp4">
  Your browser does not support embedded video. <a href="/posts/byotc/malwarebytes-poc.mp4">Download the video</a> instead.
</video>

<p><em>The proof of concept loads <code>mbamchameleon.sys</code>, injects into the signed Malwarebytes client, and uses the trusted process to terminate Microsoft Defender.</em></p>
<p>This case is the simplest form of BYOTC:</p>
<ol>
<li>Bring the signed driver.</li>
<li>Bring its signed user-mode client.</li>
<li>Gain execution inside the client.</li>
<li>Let the client pass the driver&rsquo;s signer check.</li>
<li>Invoke the driver&rsquo;s intended privileged feature for an unintended purpose.</li>
</ol>
<h3 id="malwarebytes-disclosure-timeline">Malwarebytes disclosure timeline</h3>
<ul>
<li><strong>June 15, 2025:</strong> I submit my Chameleon report to Malwarebytes.</li>
<li><strong>July 2, 2025:</strong> Malwarebytes says my report duplicates an earlier report made by a different researcher.</li>
<li><strong>September 5, 2025:</strong> My report is closed as a duplicate.</li>
<li><strong>October 31, 2025:</strong> Malwarebytes says a fix is in progress and that I will be invited to retest after release.</li>
<li><strong>March 4, 2026:</strong> 180 days have elapsed since the report was closed.</li>
<li><strong>September 3, 2026:</strong> I publicly disclose my findings in this blog post. As of publication, no fix is available.</li>
</ul>
<p>I do not blame Malwarebytes for not shipping a fix after a year. As we will see in the next case study, even systematic
validation of the client process can still leave a gap.</p>
<h2 id="case-study-2-system-informer-and-the-evil-parent">Case study 2: System Informer and the evil parent</h2>
<p>System Informer was the more interesting target because KSystemInformer already performed unusually thorough client validation. It assigned each client one of five cumulative process-integrity levels. The <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/kphlib/include/kphapi.h#L20-L76">state definitions</a> and <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/cid_tracking.c#L2270-L2354"><code>KphGetProcessState</code></a> show exactly what each level added:</p>
<table>
	<thead>
			<tr>
					<th>Level</th>
					<th>Checks required</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>MINIMUM</code></td>
					<td>The process has a real backing file and section; the file is not writable in a way that permits replacement; the mapped image has no user-writable references; and the file is not participating in a transaction.</td>
			</tr>
			<tr>
					<td><code>LOW</code></td>
					<td>Everything in <code>MINIMUM</code>, plus a verified image. <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/verify.c#L405-L614"><code>KphVerifyFileObject</code></a> hashes the image with SHA-512 and verifies its adjacent <code>.sig</code> file using an <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/verify.c#L41-L75">RSA public key embedded in the driver</a>.</td>
			</tr>
			<tr>
					<td><code>MEDIUM</code></td>
					<td>Everything in <code>LOW</code>, plus active driver-enforced process protection. Object Manager callbacks strip dangerous rights from handles opened to the protected process and its threads.</td>
			</tr>
			<tr>
					<td><code>HIGH</code></td>
					<td>Everything in <code>MEDIUM</code>, plus no untrusted image loads and no active debugger.</td>
			</tr>
			<tr>
					<td><code>MAXIMUM</code></td>
					<td>Everything in <code>HIGH</code>, plus proof that the process was securely created by a process that is <code>HIGH</code>.</td>
			</tr>
	</tbody>
</table>
<p>System Informer uses its own message system for user-kernel communication, and each message declares the minimum integrity level required to execute it. On every request, <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/comms.c#L825-L874"><code>KphpCommsMessageNotifyCallback</code></a> recomputes the caller&rsquo;s state and rejects the message if the process does not meet that requirement. For example, <code>KphMsgTerminateProcess</code> requires <code>MAXIMUM</code>, so only a client at that level can ask the driver to terminate a process.</p>
<p>This reduced my research question to two parts: <strong>How does <code>SystemInformer.exe</code> obtain <code>MAXIMUM</code>, and can an attacker get code execution inside that instance?</strong> The answer to the first part lies in how System Informer starts.</p>
<h3 id="how-the-truest-client-reached-maximum">How The Truest Client reached MAXIMUM</h3>
<p>Before following the startup sequence, it helps to see the complete set of checks required for a <code>MAXIMUM</code> process:</p>
<table>
	<thead>
			<tr>
					<th>Protection</th>
					<th>What the driver checks or enforces</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Real backing image</td>
					<td>The process has a real image file and section rather than an anonymous or substituted mapping.</td>
			</tr>
			<tr>
					<td>File cannot be replaced</td>
					<td>The image file is not writable in a way that would allow its contents to be changed after verification.</td>
			</tr>
			<tr>
					<td>No writable image references</td>
					<td>The mapped image section has no user-writable references.</td>
			</tr>
			<tr>
					<td>No file transaction</td>
					<td>The image file is not participating in a transaction that could present different contents at different times.</td>
			</tr>
			<tr>
					<td>Cryptographic image verification</td>
					<td><a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/verify.c#L405-L614"><code>KphVerifyFileObject</code></a> hashes the executable with SHA-512 and verifies its adjacent <code>.sig</code> file using an <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/verify.c#L41-L75">RSA public key embedded in the driver</a>.</td>
			</tr>
			<tr>
					<td>Protected process and thread handles</td>
					<td><a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/protection.c#L623-L829"><code>KphApplyObProtections</code></a> uses Object Manager callbacks to strip dangerous rights from handles opened by outside processes.</td>
			</tr>
			<tr>
					<td>No untrusted image loads</td>
					<td>The <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/protection.c#L1083-L1139">image-load path</a> tries to remove untrusted mappings and records any that remain.</td>
			</tr>
			<tr>
					<td>Not being debugged</td>
					<td>The driver checks whether the process is being debugged.</td>
			</tr>
			<tr>
					<td>Secure creation history</td>
					<td>The driver tracks whether the process was created through an approved parent-child chain.</td>
			</tr>
	</tbody>
</table>
<p>System Informer used three separate processes rather than gradually raising the trust level of one process. The sequence began when the user launched <code>SystemInformer.exe</code> with administrator rights. This first instance brought up the driver: <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/phlib/kph.c#L27-L140"><code>KphConnect</code></a> first tried to connect to an existing driver and, if none was available, created the kernel-driver service before starting it and connecting again.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1. The user launches SystemInformer.exe as administrator.
</span></span><span style="display:flex;"><span>   This first instance loads SystemInformer.sys and connects to it.
</span></span><span style="display:flex;"><span>   Because the driver started after this process was created, it can
</span></span><span style="display:flex;"><span>   verify the image but cannot establish the full creation-time chain. → LOW
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>2. Because it is below HIGH, it starts SystemInformer.exe -kh.
</span></span><span style="display:flex;"><span>   The driver observes this child from creation, verifies it,
</span></span><span style="display:flex;"><span>   protects it against dangerous outside handles, and confirms
</span></span><span style="display:flex;"><span>   that it is not being debugged and has loaded no untrusted images. → HIGH
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>3. Because the child is HIGH but not MAXIMUM, it starts
</span></span><span style="display:flex;"><span>   SystemInformer.exe -kh -kx. Under the old rule, creation by a
</span></span><span style="display:flex;"><span>   HIGH parent set SecurelyCreated; the child repeats all checks.      → MAXIMUM
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>4. The MAXIMUM instance can invoke the most privileged driver APIs.
</span></span></code></pre></div><p>The user-mode restart decisions are in <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/SystemInformer/ksisup.c#L1544-L1563"><code>KsiConnectDriver</code></a>, while the old <code>HIGH parent → SecurelyCreated child</code> rule is in <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/informer_process.c#L89-L163"><code>KphpPerformProcessTracking</code></a>.</p>
<h3 id="how-the-evil-parent-attack-broke-the-chain">How the evil-parent attack broke the chain</h3>
<p>The gap was a narrow exception in an otherwise strong design. Windows process creation requires the creator to retain powerful handles while initializing its child. System Informer&rsquo;s object callbacks normally stripped dangerous rights from handles to protected clients, but <a href="https://github.com/winsiderss/systeminformer/blob/553d309edb07934bda9387cff29a82ca1714acbb/KSystemInformer/protection.c#L581-L621"><code>KphpShouldPermitCreatorProcess</code></a> permitted the actual creator extra rights, including <code>PROCESS_VM_OPERATION</code> and <code>PROCESS_VM_WRITE</code>. Duplication was blocked, but the creator could use its own handle directly.</p>
<p>An elevated <code>malicious.exe</code> could therefore create the legitimate <code>-kh</code> instance and modify it through the creator handle after it became <code>HIGH</code>. That compromised process could create the <code>-kx</code> instance, which the old rule promoted to <code>MAXIMUM</code>, and modify that child through the next creator handle. All image and runtime checks could pass even though control flowed from the evil parent into the trusted chain.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>malicious.exe
</span></span><span style="display:flex;"><span>    └─ creates and modifies ─&gt; verified HIGH instance
</span></span><span style="display:flex;"><span>                                  └─ creates and modifies ─&gt; verified MAXIMUM instance
</span></span><span style="display:flex;"><span>                                                                  └─&gt; privileged driver APIs
</span></span></code></pre></div><p>Simply removing the creator exception was not practical: process initialization legitimately needed those rights and failed without them. The weakness was the combination of necessary Windows creation semantics with a trust rule that allowed a child to become more trusted than the process that still controlled its initialization.</p>
<h2 id="how-system-informer-fixed-it">How System Informer fixed it</h2>
<p>The System Informer developers responded quickly, confirmed the gap, and designed a fix. The fix has two complementary parts.</p>
<p>First, the driver no longer treats a <code>HIGH</code> System Informer process as securely created merely because another <code>HIGH</code> process created it. The new process can advance into the maximum-trust chain only if its creator is already <code>MAXIMUM</code>, or if the creator is a protected process signed at Windows TCB/System level. This change is visible in <a href="https://github.com/winsiderss/systeminformer/commit/0f189f43a4b72c1766bab7e3293f7a85ae23b25e"><code>KphpPerformProcessTracking</code></a>, where the driver checks <code>PsGetProcessProtection</code> and accepts only <code>PsProtectedSignerWinTcb</code> or <code>PsProtectedSignerWinSystem</code> as the root of a new secure-creation chain.</p>
<p>Second, System Informer changed how it restarts itself. Rather than letting the weaker GUI process directly create the stronger instance, it creates a temporary service and asks the service path to perform the creation. The Service Control Manager runs <code>services.exe</code> as a WinTcb-protected process, providing an operating-system-backed root of trust. The user-mode side of this design is shown in the <a href="https://github.com/winsiderss/systeminformer/commit/fd70a88fa809a01fe2197e53d0516bb599c9fb42"><code>kph restart self through tcb</code></a> commit.</p>
<p>The resulting chain is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>System Informer (&lt; MAXIMUM)
</span></span><span style="display:flex;"><span>        │ create and start temporary service
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>services.exe (WinTcb PPL)
</span></span><span style="display:flex;"><span>        │ create
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>System Informer (HIGH)
</span></span><span style="display:flex;"><span>        │ create
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>System Informer (MAXIMUM)
</span></span></code></pre></div><p>Because the untrusted initiator does not receive the privileged creator handle to the trusted child, and handle duplication across the verified-process boundary is already prohibited, the evil-parent path is broken. A malicious ordinary administrator cannot manufacture the WinTcb-protected root that the driver now requires.</p>
<p>Two nearby hardening changes also made relevant state checks “sticky”: once a violation such as debugging or an untrusted image load is observed, temporarily removing that condition cannot restore the process&rsquo;s earlier trust. That addresses the broader TOCTOU concern: trust should generally be monotonic in the direction of revocation.</p>
<h3 id="system-informer-disclosure-timeline">System Informer disclosure timeline</h3>
<ul>
<li><strong>April 20, 2025:</strong> I privately report the evil-parent design gap.</li>
<li><strong>April 20–21, 2025:</strong> System Informer&rsquo;s maintainers author the TCB-rooted secure-creation and service-restart changes during our technical discussion.</li>
<li><strong>February 1, 2026:</strong> The rebased equivalents of those changes enter the current public repository history.</li>
<li><strong>August 29, 2026:</strong> The maintainer confirms that the updated signed driver has shipped to the Release Channel and closes the disclosure loop.</li>
</ul>
<h2 id="a-lesson-from-game-anti-cheat">A lesson from game anti-cheat</h2>
<p>Game anti-cheat provides a useful comparison. Kernel anti-cheat drivers also try to determine whether a user-mode process remains trustworthy while an adversary actively attempts to modify or deceive it. <a href="https://www.riotgames.com/en/news/a-message-about-vanguard-from-our-security-privacy-teams">Riot Games&rsquo; Vanguard architecture overview</a> describes its driver as validating memory and system state and checking that the client has not been tampered with.</p>
<p>Anti-cheat and general-purpose utilities have different compatibility constraints, but the structural problem is the same. The never-ending contest between anti-cheat developers and cheat authors shows that runtime integrity is not a single check that can be perfected. A live process can be influenced through its memory, threads, loaded modules, handles, creation history, and even its view of the operating system. BYOTC brings this lesson to driver review: a recognized process is not a static identity, but a security-sensitive object whose integrity must be established and maintained over time.</p>
<h2 id="how-should-byotc-be-fixed">How should BYOTC be fixed?</h2>
<p>The strongest pattern I have found so far is to anchor trust in <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">Protected Process Light (PPL)</a>. Ideally, the trusted client itself runs as PPL. A less direct design, like System Informer&rsquo;s fix, roots the client&rsquo;s creation chain in a PPL process and combines that origin with extensive runtime protections. In both designs, Windows—not the vendor&rsquo;s driver alone—protects the root of trust from an ordinary administrator.</p>
<p>That is much harder to deploy than it sounds. Windows has a supported route for protected antimalware services, but it is not a general-purpose “make this process PPL” switch. It requires an ELAM driver and strict certification and code-signing arrangements. A protected antimalware service can also launch a protected child, but this remains a specialized service model. It is a poor fit for an on-demand system utility such as System Informer, which instead used the already-protected <code>services.exe</code> as the root of its launch sequence.</p>
<p>That solution is impressive, but it is also complicated and product-specific. I would not expect every affected software to reproduce it correctly, especially products that currently rely on a simple caller check. For software that cannot run its client as PPL, a broadly reusable answer is still missing. In that sense, I increasingly see BYOTC as a platform problem for Microsoft: Windows needs a supported way for a driver to grant sensitive capabilities to a user-mode principal whose runtime integrity the operating system can preserve.</p>
<p>Microsoft is already working on an adjacent problem. The <a href="https://blogs.windows.com/windowsexperience/2025/11/18/preparing-for-whats-next-windows-security-and-resiliency-innovations-help-organizations-mitigate-risks-recover-faster-and-prepare-for-the-era-of-ai/">Windows Endpoint Security Platform</a> is intended to move more antivirus enforcement from third-party kernel drivers into user mode. Reducing kernel code is valuable: a bug in user mode is less likely to crash or corrupt the operating system. But moving an operation out of a vendor driver does not, by itself, answer the BYOTC question. If a privileged service still authenticates a client process that an attacker can modify, the attacker may inject into that trusted client and make the same privileged request from there.</p>
<p>macOS offers a useful contrast. Its security model binds entitlements to a process&rsquo;s code signature, while the Hardened Runtime enables library validation by default. Apple documents that <a href="https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.cs.disable-library-validation">library validation rejects external code</a> unless it is signed by Apple or by the same team as the main executable. This is not identical to PPL, and it does not make injection impossible, but it makes loading unsigned code into a trusted client substantially harder. Windows does not currently give ordinary third-party applications an equally simple equivalent for this use case.</p>
<p>For antivirus and EDR developers, BYOTC should be treated as an emerging attack surface. Defensively blocking known-abusable drivers can remove obvious building blocks. If your own driver exposes legitimate but dangerous operations, test more than whether the caller&rsquo;s executable is signed: determine whether an attacker can take control of the trusted client or exploit a trust transition. If you are working on this problem, developing an affected product, or believe you have a more general solution, please feel free to <a href="/contact/">Contact me</a> &ndash; I would be happy to chat.</p>
<p>BYOTC also deserves a large-scale study across the Windows driver ecosystem. Related research on vulnerable and abused drivers includes:</p>
<ul>
<li><a href="https://eclypsium.com/blog/screwed-drivers-signed-sealed-delivered/"><em>Screwed Drivers—Signed, Sealed, Delivered</em></a></li>
<li><a href="https://research.checkpoint.com/2024/breaking-boundaries-investigating-vulnerable-drivers-and-mitigating-risks/"><em>Breaking Boundaries: Investigating Vulnerable Drivers and Mitigating Risks</em></a></li>
<li><a href="https://blog.talosintelligence.com/exploring-vulnerable-windows-drivers/"><em>Exploring vulnerable Windows drivers</em></a></li>
<li><a href="https://doi.org/10.1145/3564625.3564631"><em>POPKORN: Popping Windows Kernel Drivers At Scale</em></a></li>
<li><a href="https://link.springer.com/article/10.1186/s42400-025-00434-w"><em>Finding vulnerable drivers with hooking and reading result verification</em></a></li>
<li><a href="https://www.ndss-symposium.org/ndss-paper/unveiling-byovd-threats-malwares-use-and-abuse-of-kernel-drivers/"><em>Unveiling BYOVD Threats: Malware&rsquo;s Use and Abuse of Kernel Drivers</em></a></li>
<li><a href="https://arxiv.org/abs/2606.07732"><em>The Windows IOCTL Census: A Corpus-Scale, Multi-Architecture Database of the Driver Control-Code Surface</em></a></li>
<li><a href="https://github.com/zeze-zeze/ioctlance"><em>IOCTLance: Enhanced Vulnerability Hunting in WDM Drivers with Symbolic Execution and Taint Analysis</em></a></li>
<li><a href="https://threatunpacked.com/2026/02/04/building-a-scalable-windows-driver-vulnerability-analyzer-part-2/"><em>Building a Scalable Windows Driver Vulnerability Analyzer</em></a></li>
<li><a href="https://ydinkin.substack.com/p/200-kernel-bugs-in-30-days"><em>100+ Kernel Bugs in 30 Days</em></a></li>
</ul>
<p>A similar corpus-scale study of trusted user-mode clients would help determine how widespread BYOTC is across the Windows driver ecosystem: how many drivers authorize privileged operations this way, and how many of those trust boundaries can be bypassed.</p>
<h2 id="conclusion">Conclusion</h2>
<p>BYOTC targets the trusted user-mode client of a privileged driver. Malwarebytes showed the direct version—injecting into a signed client—while System Informer showed how a process-creation trust chain could be hijacked. Both cases demonstrate how hard it is to preserve a process&rsquo;s integrity at runtime.</p>
<p>I have also found a third BYOTC case study, which remains under embargo. If coordination proceeds as expected, I will publish its technical analysis and disclosure story later in September 2026.</p>
]]></content>
        </item>
        
        <item>
            <title>How Zoom Detects Google Meet Without Microphone Access</title>
            <link>https://xusheng.dev/posts/reversing/zoom_my_notes_detector/main/</link>
            <pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/zoom_my_notes_detector/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Zoom can notice that Google Meet, Teams, Slack, and other applications may be in a meeting and offer to start My Notes&lt;/li&gt;
&lt;li&gt;On macOS, Zoom monitors Control Center&amp;rsquo;s &lt;code&gt;sensor-indicators&lt;/code&gt; log stream&lt;/li&gt;
&lt;li&gt;The messages contain records such as &lt;code&gt;[mic] Google Chrome (com.google.Chrome)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Zoom extracts an application identity and detects when microphone use starts or stops&lt;/li&gt;
&lt;li&gt;On Windows, it obtains equivalent application-usage information from the per-user Capability Access Manager microphone records in the registry&lt;/li&gt;
&lt;li&gt;Reading this metadata requires neither administrator/root privileges nor Zoom microphone permission&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://xusheng.dev/posts/reversing/zoom_my_notes_detector/social-preview-v3.jpg&#34; alt=&#34;Zoom suggesting note-taking during a Google Meet call&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>Zoom can notice that Google Meet, Teams, Slack, and other applications may be in a meeting and offer to start My Notes</li>
<li>On macOS, Zoom monitors Control Center&rsquo;s <code>sensor-indicators</code> log stream</li>
<li>The messages contain records such as <code>[mic] Google Chrome (com.google.Chrome)</code></li>
<li>Zoom extracts an application identity and detects when microphone use starts or stops</li>
<li>On Windows, it obtains equivalent application-usage information from the per-user Capability Access Manager microphone records in the registry</li>
<li>Reading this metadata requires neither administrator/root privileges nor Zoom microphone permission</li>
</ul>
<p><img src="/posts/reversing/zoom_my_notes_detector/social-preview-v3.jpg" alt="Zoom suggesting note-taking during a Google Meet call"></p>
<h2 id="why-does-zoom-know-that-chrome-is-using-the-microphone">Why does Zoom know that Chrome is using the microphone?</h2>
<p>Zoom&rsquo;s My Notes feature can take notes during meetings hosted in applications
such as Google Meet, Microsoft Teams, and Slack. Before the user starts it, Zoom
can display a <strong>Take Note</strong> prompt when it thinks a third-party meeting is in
progress.</p>
<p>That raised an interesting question: how does Zoom recognize the other
application before it has started taking notes?</p>
<p>The answer differs by platform. On macOS, Zoom parses human-readable Unified
Logging output. On Windows, it enumerates microphone-usage records in the
registry.</p>
<h2 id="the-macos-detector">The macOS detector</h2>
<p>I statically analyzed the arm64 build of Zoom Workplace 7.1.5 (84650).</p>
<p>The implementation is in a class named <code>PrivacyEventMonitor</code> inside
<code>viper.framework</code>. Its relevant methods are:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>-[PrivacyEventMonitor startMonitoring]
</span></span><span style="display:flex;"><span>-[PrivacyEventMonitor handleLogOutput:]
</span></span><span style="display:flex;"><span>-[PrivacyEventMonitor parseSystemLogLine:]
</span></span><span style="display:flex;"><span>-[PrivacyEventMonitor parseAttributionsStringForMicrophone:]
</span></span><span style="display:flex;"><span>-[PrivacyEventMonitor notifyMacAppStateChange:previousStates:]
</span></span></code></pre></div><p><code>startMonitoring</code> launches <code>/usr/bin/log</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>/usr/bin/log stream <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --type log <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --level default <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --predicate <span style="color:#e6db74">&#39;subsystem == &#34;com.apple.controlcenter&#34; AND category == &#34;sensor-indicators&#34;&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --style compact
</span></span></code></pre></div><p>macOS Control Center maintains the orange and green privacy indicators for
microphone and camera use. The same subsystem writes the currently responsible
applications to Unified Logging. A microphone record for Chrome looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Sorted active attributions from SystemStatus update: [mic] Google Chrome (com.google.Chrome)
</span></span></code></pre></div><p>macOS has already supplied the display name and bundle ID. Zoom keeps entries
marked <code>[mic]</code>, parses those two fields, and compares each new attribution list
with the previous one. An application appearing or disappearing becomes a
microphone-start or microphone-stop event; no audio analysis is involved.</p>
<h2 id="from-chrome-to-google-meet">From Chrome to Google Meet</h2>
<p>The UI layer contains a product category named <code>chrome</code>, labels it <strong>Google
Meet</strong>, and applies notification and per-application blacklist settings. The
overall flow is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Chrome opens the microphone
</span></span><span style="display:flex;"><span>  -&gt; macOS updates Control Center&#39;s sensor attribution
</span></span><span style="display:flex;"><span>  -&gt; Control Center emits a sensor-indicators log record
</span></span><span style="display:flex;"><span>  -&gt; Zoom extracts “Google Chrome” and “com.google.Chrome”
</span></span><span style="display:flex;"><span>  -&gt; Zoom detects an application-state transition
</span></span><span style="display:flex;"><span>  -&gt; product mapping and notification policy
</span></span><span style="display:flex;"><span>  -&gt; optional “Take Note” prompt
</span></span></code></pre></div><p>This is more accurately called <strong>microphone-use detection</strong> than meeting
detection. Chrome could be accessing the microphone for a reason unrelated to
Google Meet. Zoom&rsquo;s higher layers decide whether the signal should produce a
prompt.</p>
<h2 id="the-windows-detector">The Windows detector</h2>
<p>I also statically analyzed Zoom Workplace for Windows x64 7.1.8.46825. Its
detector is in <code>viper_async_device.dll</code> and reads this per-user registry key:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\
</span></span><span style="display:flex;"><span>CapabilityAccessManager\ConsentStore\microphone
</span></span></code></pre></div><p>Zoom opens the key with <code>KEY_READ</code>, enumerates packaged application entries and
the <code>NonPackaged</code> subtree used by classic desktop applications, and queries
each entry&rsquo;s 64-bit <code>LastUsedTimeStop</code> value. A zero value represents an access
interval that has not ended, so Zoom treats that application as currently using
the microphone.</p>
<p>The Windows flow is therefore:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Chrome opens the microphone
</span></span><span style="display:flex;"><span>  -&gt; Windows updates ConsentStore\microphone\NonPackaged
</span></span><span style="display:flex;"><span>  -&gt; LastUsedTimeStop remains zero while access is active
</span></span><span style="display:flex;"><span>  -&gt; Zoom enumerates the registry records
</span></span><span style="display:flex;"><span>  -&gt; Zoom identifies Chrome&#39;s executable
</span></span><span style="display:flex;"><span>  -&gt; product mapping and notification policy
</span></span><span style="display:flex;"><span>  -&gt; optional “Take Note” prompt
</span></span></code></pre></div><p>This part uses ordinary registry APIs. I found no ETW consumer, audio-session
observer, or PCM processing in the application-attribution routine. Static
analysis does not establish its polling interval, the feature flags controlling
it, or whether detection metadata is included in telemetry.</p>
<h2 id="does-this-require-permission">Does this require permission?</h2>
<p>I tested Zoom&rsquo;s exact <code>/usr/bin/log stream</code> predicate as an ordinary user,
without <code>sudo</code>, and macOS accepted it. Reading the records did not require
Microphone, Screen Recording, System Audio Recording, or Accessibility
permission.</p>
<p>On Windows, the key is under <code>HKEY_CURRENT_USER</code> and Zoom requests only
<code>KEY_READ</code>, so it likewise requires neither elevation nor microphone access.</p>
<p>Neither mechanism should be treated as a stable application API. Zoom depends
on undocumented macOS log formatting and Windows registry implementation
details that either operating-system vendor could change.</p>
<p>It is a clever use of privacy bookkeeping that both operating systems already
expose—and a reminder that privacy indicators can themselves become a source of
cross-application activity information.</p>
]]></content>
        </item>
        
        <item>
            <title>Microsoft Teams Can Detect Recording Apps and Notify Other Participants</title>
            <link>https://xusheng.dev/posts/reversing/teams_external_recording_detector/main/</link>
            <pubDate>Mon, 31 Aug 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/teams_external_recording_detector/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Teams has a feature that can detect third-party recording and AI note-taking applications during a meeting&lt;/li&gt;
&lt;li&gt;On Windows and macOS, it enumerates running processes every 30 seconds and compares their names with a classification table&lt;/li&gt;
&lt;li&gt;The local user sees the detected application name&lt;/li&gt;
&lt;li&gt;Eligible meeting participants are told which participant may be recording, but not which application was found&lt;/li&gt;
&lt;li&gt;Separate Microsoft telemetry contains the matched app name, type, and risk level&lt;/li&gt;
&lt;li&gt;Microsoft can update the table remotely through its Experimentation and Configuration Service (ECS)&lt;/li&gt;
&lt;li&gt;It only runs in meetings where the host organization&amp;rsquo;s policy enables it; the default is off&lt;/li&gt;
&lt;li&gt;This detector is separate from Teams&amp;rsquo; &amp;ldquo;Prevent screen capture&amp;rdquo; feature&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;../social-preview.jpg&#34; alt=&#34;A Teams-style meeting window warning that an external app on Alice’s device may be recording or listening to the meeting&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>Teams has a feature that can detect third-party recording and AI note-taking applications during a meeting</li>
<li>On Windows and macOS, it enumerates running processes every 30 seconds and compares their names with a classification table</li>
<li>The local user sees the detected application name</li>
<li>Eligible meeting participants are told which participant may be recording, but not which application was found</li>
<li>Separate Microsoft telemetry contains the matched app name, type, and risk level</li>
<li>Microsoft can update the table remotely through its Experimentation and Configuration Service (ECS)</li>
<li>It only runs in meetings where the host organization&rsquo;s policy enables it; the default is off</li>
<li>This detector is separate from Teams&rsquo; &ldquo;Prevent screen capture&rdquo; feature</li>
</ul>
<p><img src="../social-preview.jpg" alt="A Teams-style meeting window warning that an external app on Alice’s device may be recording or listening to the meeting"></p>
<h2 id="does-teams-know-i-opened-obs">Does Teams know I opened OBS?</h2>
<p>Microsoft <a href="https://techcommunity.microsoft.com/blog/microsoftteamsblog/what%E2%80%99s-new-in-microsoft-teams--september-2025/4457965">introduced Prevent Screen Capture in September 2025</a>. That part was fairly conventional. Teams uses Windows window-content protection, including <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity"><code>SetWindowDisplayAffinity</code></a>, and propagates the protection to meeting pop-out windows. It is a useful speed bump, but it is easy for a sufficiently uncooperative client to bypass.</p>
<p>While looking through the client, however, I found something more interesting: <code>RecordingDetectorModule</code>.</p>
<p>The current Teams client contains a process classification table with entries for OBS, ShareX, Snipping Tool, Camtasia, Bandicam, Loom, Snagit, XSplit, AI note-takers, audio tools, browsers, and a surprisingly broad supporting cast.</p>
<p>For example, the built-in Windows rules include:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;obs64&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;displayName&#34;</span>: <span style="color:#e6db74">&#34;OBS Studio&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;appType&#34;</span>: <span style="color:#e6db74">&#34;ScreenRecorder&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;risk&#34;</span>: <span style="color:#e6db74">&#34;High&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;expected&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The complete built-in Windows list contains 167 process names:</p>
<h3 id="full-built-in-windows-list-167-entries">Full built-in Windows list (167 entries)</h3>
<p><strong>High (33):</strong> <code>Airgram</code>, <code>Avoma</code>, <code>Chorus</code>, <code>Fathom</code>, <code>Fireflies</code>, <code>Gong</code>, <code>Grain</code>, <code>Read AI</code>, <code>Tactiq</code>, <code>CamtasiaStudio</code>, <code>handy</code>, <code>Krisp</code>, <code>LogiCapture</code>, <code>ManyCam</code>, <code>natspeak</code>, <code>obs32</code>, <code>obs64</code>, <code>Otter</code>, <code>ShareX</code>, <code>voiceai</code>, <code>willow voice</code>, <code>Camtasia</code>, <code>Loom</code>, <code>obs</code>, <code>streamlabs</code>, <code>XSplit</code>, <code>XSplit.Core</code>, <code>Zoom</code>, <code>Zoom.exe</code>, <code>murmure</code>, <code>OpenWhispr</code>, <code>Typeless</code>, <code>Wispr Flow</code>.</p>
<p><strong>Medium (24):</strong> <code>Action</code>, <code>Audacity</code>, <code>bdcam</code>, <code>CamRecorder</code>, <code>copilot</code>, <code>copilot.exe.old</code>, <code>Debut</code>, <code>FlashBack</code>, <code>GoldWave</code>, <code>M365Copilot</code>, <code>mscopilot</code>, <code>NVIDIA Share</code>, <code>Resolve</code>, <code>ScreenPal</code>, <code>ScreenToGif</code>, <code>Snagit32</code>, <code>SnagitEditor</code>, <code>SnippingTool</code>, <code>SoundRecorder</code>, <code>WavePad</code>, <code>Ableton Live 12 Suite</code>, <code>Adobe Premiere Pro</code>, <code>Aximmetry.Composer</code>, <code>MOTIV Mix</code>.</p>
<p><strong>Low (76):</strong> <code>ffmpeg</code>, <code>ffplay</code>, <code>ffprobe</code>, <code>nvcontainer</code>, <code>VoiceMeeter</code>, <code>VoiceMeeter8</code>, <code>chrome</code>, <code>Code</code>, <code>firefox</code>, <code>GoXLR App</code>, <code>msedgewebview2</code>, <code>msrdc</code>, <code>mstsc</code>, <code>NVIDIA Broadcast</code>, <code>proximity</code>, <code>python</code>, <code>qemu-system-x86_64</code>, <code>rundll32</code>, <code>SoTGame</code>, <code>SteelSeriesSonar</code>, <code>Unity</code>, <code>voicemeeter_x64</code>, <code>brave</code>, <code>msedge</code>, <code>iCUE</code>, <code>Elgato.WaveLink</code>, <code>FxSound</code>, <code>LogiTune</code>, <code>SilencePlayer</code>, <code>SoundKeeper64</code>, <code>comet</code>, <code>opera</code>, <code>PrismaAccessBrowser</code>, <code>thorium</code>, <code>vivaldi</code>, <code>waterfox</code>, <code>Figma</code>, <code>thunderbird</code>, <code>Signal</code>, <code>Telegram</code>, <code>Viber</code>, <code>WeChat</code>, <code>Weixin</code>, <code>WhatsApp.Root</code>, <code>XboxPcApp</code>, <code>Zoiper5</code>, <code>AzureKinectServer</code>, <code>github</code>, <code>scrcpy</code>, <code>conhost</code>, <code>powershell</code>, <code>WindowsTerminal</code>, <code>RuneLite</code>, <code>UnrealEditor</code>, <code>UnrealGame-Win64-Shipping</code>, <code>GoTV</code>, <code>Photos</code>, <code>cloudmusic</code>, <code>QQMusic</code>, <code>Spotify</code>, <code>vlc</code>, <code>Microsoft.Media.Player</code>, <code>wmplayer</code>, <code>dcvagent</code>, <code>rdcx</code>, <code>RDCMan</code>, <code>vmware-vmx</code>, <code>Windows365</code>, <code>VoiceAssistant</code>, <code>jfw</code>, <code>fsSynth32</code>, <code>nvda</code>, <code>wfica32</code>, <code>vmconnect</code>, <code>horizon-protocol</code>, <code>LiftComponent</code>.</p>
<p><strong>None / expected (34):</strong> <code>POWERPNT</code>, <code>TextInputHost</code>, <code>audiodg</code>, <code>CiscoCollabHost</code>, <code>Cortana</code>, <code>Discord</code>, <code>g2mcomm</code>, <code>GoToMeeting</code>, <code>HdxRtcEngine</code>, <code>Microsoft Teams</code>, <code>ms-teams</code>, <code>ms-teams_modulehost</code>, <code>MsMmrHost</code>, <code>MsTeamsVdi</code>, <code>RuntimeBroker</code>, <code>SearchHost</code>, <code>ShellExperienceHost</code>, <code>Skype</code>, <code>SkypeApp</code>, <code>Slack</code>, <code>svchost</code>, <code>System</code>, <code>SystemSettings</code>, <code>Teams</code>, <code>webexmta</code>, <code>EXCEL</code>, <code>OUTLOOK</code>, <code>Todo</code>, <code>WINWORD</code>, <code>LiveCaptions</code>, <code>Narrator</code>, <code>osk</code>, <code>VoiceAccess</code>, <code>RtkAudUService64</code>.</p>
<h2 id="what-happens-after-a-match">What happens after a match?</h2>
<p>When the native detector reports a match, the meeting JavaScript applies the configured app-type and risk-level filters, then stores the remaining app names, types, and risk levels locally.</p>
<p>The defaults in the JavaScript are:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">displayRiskLevels</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">externalRecordingDisplayRiskLevels</span> <span style="color:#f92672">??</span> [<span style="color:#e6db74">&#34;Medium&#34;</span>, <span style="color:#e6db74">&#34;High&#34;</span>, <span style="color:#e6db74">&#34;Critical&#34;</span>];
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">publishRiskLevels</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">externalRecordingMetaDataPublishRiskLevels</span> <span style="color:#f92672">??</span> [<span style="color:#e6db74">&#34;High&#34;</span>, <span style="color:#e6db74">&#34;Critical&#34;</span>];
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th>Risk</th>
					<th style="text-align: right">Local warning</th>
					<th style="text-align: right">Other participants warned</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>None</code></td>
					<td style="text-align: right">No</td>
					<td style="text-align: right">No</td>
			</tr>
			<tr>
					<td><code>Low</code></td>
					<td style="text-align: right">No</td>
					<td style="text-align: right">No</td>
			</tr>
			<tr>
					<td><code>Medium</code></td>
					<td style="text-align: right">Yes</td>
					<td style="text-align: right">No</td>
			</tr>
			<tr>
					<td><code>High</code></td>
					<td style="text-align: right">Yes</td>
					<td style="text-align: right">Yes</td>
			</tr>
			<tr>
					<td><code>Critical</code></td>
					<td style="text-align: right">Yes</td>
					<td style="text-align: right">Yes</td>
			</tr>
	</tbody>
</table>
<p>OBS is classified as <code>High</code>, so it qualifies for both.</p>
<p>The user running the matched application gets a dismissible warning. It is quite direct:</p>
<blockquote>
<p>You are capturing meeting with OBS Studio<br>
An app on your device has been detected to be listening or recording this meeting. Others in the meeting have been notified about this.</p>
</blockquote>
<p>Teams then invokes this GraphQL mutation when metadata publication is enabled:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-graphql" data-lang="graphql"><span style="display:flex;"><span><span style="color:#66d9ef">mutation</span> ($teamsCallId: <span style="color:#a6e22e">ID</span>!, $isRecording: <span style="color:#a6e22e">Boolean</span>!, $recordingCount: <span style="color:#a6e22e">Int</span>!) {
</span></span><span style="display:flex;"><span>  publishLocalExternalRecordingState(
</span></span><span style="display:flex;"><span>    teamsCallId: $teamsCallId
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">isRecording</span>: $isRecording
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">recordingCount</span>: $recordingCount
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The mutation includes neither the process name, application type, nor risk level. Other clients receive participant state containing <code>externalRecording.isRecording</code> and use it to render an indicator:</p>
<blockquote>
<p>This meeting is being captured<br>
An external app on Alice&rsquo;s device may be recording or listening to this meeting.</p>
</blockquote>
<p>Who can see it is separately feature-gated. Depending on <code>externalRecordingVisibility</code>, it can be available to everyone or limited to organizers and co-organizers.</p>
<h2 id="telemetry-and-remote-configuration">Telemetry and remote configuration</h2>
<p>There is a second outbound path. The client emits an <code>external_recording_detected_apps</code> telemetry event containing detector matches shaped as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;appName&#34;</span>: <span style="color:#e6db74">&#34;OBS Studio&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;appType&#34;</span>: <span style="color:#e6db74">&#34;ScreenRecorder&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;riskLevel&#34;</span>: <span style="color:#e6db74">&#34;High&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Microsoft&rsquo;s telemetry therefore receives richer information about the matched application.</p>
<p>Microsoft can also change the process list without shipping a Teams update. The hard-coded JSON is the built-in default, but Teams fetches configuration from Microsoft&rsquo;s Experimentation and Configuration Service (ECS), including:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>recordingDetectorClassificationsWin
</span></span><span style="display:flex;"><span>recordingDetectorClassificationsMac
</span></span><span style="display:flex;"><span>recordingDetectorPollIntervalMs
</span></span><span style="display:flex;"><span>recordingDetectorStopTimeoutMs
</span></span><span style="display:flex;"><span>recordingDetectorUnknownRiskLevel
</span></span><span style="display:flex;"><span>recordingDetectorUnclassifiedRiskLevel
</span></span></code></pre></div><p>The response is cached locally in <code>ecs_settings.dat64</code>. If Microsoft supplies a valid classification list, the client uses it; if parsing fails, native logging says it uses the built-in table.</p>
<h2 id="how-detection-runs">How detection runs</h2>
<p>An organization administrator can enable the feature with the <a href="https://learn.microsoft.com/en-us/powershell/module/microsoftteams/set-csteamsmeetingpolicy#-enableexternalrecordingdetection"><code>EnableExternalRecordingDetection</code></a> meeting policy, which defaults to <code>false</code>. Microsoft says it detects third-party screen recorders, audio recorders, and AI note-takers and notifies participants, but does not document the implementation details discussed here. It only runs during an eligible active meeting and stops when the call ends. This policy is independent of Prevent Screen Capture.</p>
<p>On Windows, Teams takes an initial process snapshot with <code>CreateToolhelp32Snapshot</code>, walks it with <code>Process32FirstW</code> and <code>Process32NextW</code>, and uses <code>ProcessIdToSessionId</code> and <code>QueryFullProcessImageNameW</code> to identify processes in the applicable session. It repeats this every 30 seconds by default and compares the names with its configured list. This establishes that a listed process is running, not that it has started recording.</p>
<h2 id="macos-does-the-same-thing">macOS does the same thing</h2>
<p>I also checked out how it works on the macOS version. The detector lives in the main <code>MSTeams</code> Mach-O, which helpfully contains its original source path:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>native_modules/mods/recordingdetector/mac/mac_detection_module.cpp
</span></span></code></pre></div><p>It imports process-enumeration facilities including <code>sysctl</code>, <code>getuid</code>, <code>proc_pidinfo</code>, and <code>proc_pidpath</code>, performs periodic process snapshots, and applies <code>recordingDetectorClassificationsMac</code>. It does not need macOS Screen Recording permission because it is reading process information, not the screen.</p>
<p>The built-in Mac list includes:</p>
<table>
	<thead>
			<tr>
					<th>Process</th>
					<th>Display name</th>
					<th>Type</th>
					<th>Risk</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>obs</code></td>
					<td>OBS Studio</td>
					<td>ScreenRecorder</td>
					<td>High</td>
			</tr>
			<tr>
					<td><code>QuickTime Player</code></td>
					<td>QuickTime Player</td>
					<td>ScreenRecorder</td>
					<td>High</td>
			</tr>
			<tr>
					<td><code>ManyCam</code></td>
					<td>ManyCam</td>
					<td>ScreenRecorder</td>
					<td>High</td>
			</tr>
			<tr>
					<td><code>VoiceMemos</code></td>
					<td>Apple Voice Memos</td>
					<td>AudioRecorder</td>
					<td>Medium</td>
			</tr>
			<tr>
					<td><code>Granola Helper</code></td>
					<td>Granola</td>
					<td>AIAssistant</td>
					<td>High</td>
			</tr>
			<tr>
					<td><code>Wispr Flow Helper</code></td>
					<td>Wispr Flow</td>
					<td>AIAssistant</td>
					<td>High</td>
			</tr>
			<tr>
					<td><code>zoom.us</code></td>
					<td>Zoom</td>
					<td>Communication</td>
					<td>High</td>
			</tr>
	</tbody>
</table>
<p>System processes such as <code>coreaudiod</code>, <code>WindowServer</code>, and ReplayKit&rsquo;s <code>replayd</code> are expected and assigned <code>None</code> risk.</p>
<p>Thinking about secretly recording a Teams meeting? Think twice—the other participants might find out.</p>
]]></content>
        </item>
        
        <item>
            <title>How Windows UCPD Grew from a 29 KB Registry Filter into a 196 KB Policy Engine</title>
            <link>https://xusheng.dev/posts/reversing/ucpd_evolution/main/</link>
            <pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/ucpd_evolution/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I collected 38 &lt;code&gt;UCPD.sys&lt;/code&gt; binaries representing 24 versions&lt;/li&gt;
&lt;li&gt;UCPD first shipped in August 2023, seven months before its public discovery&lt;/li&gt;
&lt;li&gt;It grew from a 29 KB browser-association filter into a 196 KB registry and process policy engine&lt;/li&gt;
&lt;li&gt;Later versions added ACL, rename, injection, UI Automation, backtrace, and dynamic-rule defenses&lt;/li&gt;
&lt;li&gt;Versions 4.4–4.7 added cross-device resume, gaming, &lt;code&gt;FeatureV3&lt;/code&gt;, and an expanded deny list&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;going-backward&#34;&gt;Going backward&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&#34;https://binary.ninja/2026/08/04/ucpd-dynamic-rules.html&#34;&gt;recent article&lt;/a&gt; examined how the Dynamic Rules (DR) work in the UCPD driver, and earlier research by &lt;a href=&#34;https://kolbi.cz/blog/2024/04/03/userchoice-protection-driver-ucpd-sys/&#34;&gt;Christoph Kolbicz&lt;/a&gt;, &lt;a href=&#34;https://hitco.at/blog/windows-userchoice-protection-driver-ucpd/&#34;&gt;Gunnar Haslinger&lt;/a&gt;, and &lt;a href=&#34;https://binary.ninja/2025/03/25/default-browser-upcd.html&#34;&gt;me&lt;/a&gt; captured individual stages of its development. The remaining piece was to track how the driver evolved over time.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>I collected 38 <code>UCPD.sys</code> binaries representing 24 versions</li>
<li>UCPD first shipped in August 2023, seven months before its public discovery</li>
<li>It grew from a 29 KB browser-association filter into a 196 KB registry and process policy engine</li>
<li>Later versions added ACL, rename, injection, UI Automation, backtrace, and dynamic-rule defenses</li>
<li>Versions 4.4–4.7 added cross-device resume, gaming, <code>FeatureV3</code>, and an expanded deny list</li>
</ul>
<h2 id="going-backward">Going backward</h2>
<p>My <a href="https://binary.ninja/2026/08/04/ucpd-dynamic-rules.html">recent article</a> examined how the Dynamic Rules (DR) work in the UCPD driver, and earlier research by <a href="https://kolbi.cz/blog/2024/04/03/userchoice-protection-driver-ucpd-sys/">Christoph Kolbicz</a>, <a href="https://hitco.at/blog/windows-userchoice-protection-driver-ucpd/">Gunnar Haslinger</a>, and <a href="https://binary.ninja/2025/03/25/default-browser-upcd.html">me</a> captured individual stages of its development. The remaining piece was to track how the driver evolved over time.</p>
<p>So I used Winbindex to locate and retrieve 38 reconstructable x64 <code>UCPD.sys</code> files from Microsoft&rsquo;s public symbol server. Microsoft had removed the corresponding PDBs for most versions, so the comparison relied on the binaries rather than symbol information. The files cover 24 versions, and I compared adjacent versions.</p>
<p>Most of this work was performed by Codex using Binary Ninja&rsquo;s MCP integration. I was quite surprised by its ability to diff binaries without specialized tools such as BinDiff and BinExport.</p>
<h2 id="ucpd-was-already-in-windows-in-august-2023">UCPD was already in Windows in August 2023</h2>
<p>The oldest indexed binary is <code>UCPD.sys</code> version <code>1.0.1.383513</code>. Winbindex associates it with Windows 11 22H2 preview update <a href="https://support.microsoft.com/help/5029351">KB5029351</a>, released on August 22, 2023.</p>
<p>That is about seven months before the reports that led to UCPD&rsquo;s public discovery.</p>
<p>The distinction is important. Early 2024 appears to be when Microsoft activated or broadly rolled out the protection, not when it wrote the driver. Microsoft had already been developing and servicing UCPD in public Windows packages for months.</p>
<p>The first version was not an empty shell, either. It was a 29,184-byte driver with 58 unwind-described functions. Its strings and code identify the essential design:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice
</span></span><span style="display:flex;"><span>Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>RemoveUserChoice
</span></span><span style="display:flex;"><span>RenameUserChoice
</span></span><span style="display:flex;"><span>OriginalFilename
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>*\dllhost.exe
</span></span><span style="display:flex;"><span>*\reg.exe
</span></span><span style="display:flex;"><span>*\rundll32.exe
</span></span><span style="display:flex;"><span>*\svchost.exe
</span></span></code></pre></div><p>In other words, version 1.0 already knew which registry keys represented the default browser, distinguished operations against them, and had a list of Microsoft utilities that should not be trusted merely because Microsoft signed them.</p>
<p>The architecture that would define UCPD was present from the beginning:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>registry operation
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      v
</span></span><span style="display:flex;"><span>is this a protected key?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      v
</span></span><span style="display:flex;"><span>is the requesting executable trusted?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- not Microsoft-signed -&gt; deny
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      `-- Microsoft-signed
</span></span><span style="display:flex;"><span>              |
</span></span><span style="display:flex;"><span>              `-- utility deny list -&gt; deny
</span></span></code></pre></div><p>Everything that followed made the protected surface wider and made the definition of &ldquo;trusted&rdquo; much harder to fool.</p>
<h2 id="the-complete-growth-curve">The complete growth curve</h2>
<p>The driver grew almost sevenfold over three years:</p>
<table>
	<thead>
			<tr>
					<th>Version</th>
					<th>Approximate period</th>
					<th style="text-align: right">Size</th>
					<th style="text-align: right">Functions</th>
					<th>Important visible addition</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>1.0.1</td>
					<td>Aug 2023</td>
					<td style="text-align: right">29 KB</td>
					<td style="text-align: right">58</td>
					<td>HTTP/HTTPS UserChoice protection</td>
			</tr>
			<tr>
					<td>2.0.0</td>
					<td>Dec 2023</td>
					<td style="text-align: right">34 KB</td>
					<td style="text-align: right">65</td>
					<td>PDF, DeviceRegion, FeatureV2</td>
			</tr>
			<tr>
					<td>2.1.0</td>
					<td>2024</td>
					<td style="text-align: right">45 KB</td>
					<td style="text-align: right">74</td>
					<td>Feeds, NandI, taskbar</td>
			</tr>
			<tr>
					<td>2.2.0</td>
					<td>Jun 2024</td>
					<td style="text-align: right">41 KB</td>
					<td style="text-align: right">74</td>
					<td><code>cmd.exe</code> deny entry</td>
			</tr>
			<tr>
					<td>3.0.0</td>
					<td>Jul 2024</td>
					<td style="text-align: right">86 KB</td>
					<td style="text-align: right">143</td>
					<td>Process/publisher defenses, Search, Taskband</td>
			</tr>
			<tr>
					<td>3.1–3.2</td>
					<td>late 2024</td>
					<td style="text-align: right">86–96 KB</td>
					<td style="text-align: right">146–156</td>
					<td>Copilot experiments, more denied utilities</td>
			</tr>
			<tr>
					<td>4.0.x</td>
					<td>early 2025</td>
					<td style="text-align: right">102–112 KB</td>
					<td style="text-align: right">181</td>
					<td>Backtrace-era process/module architecture</td>
			</tr>
			<tr>
					<td>4.1.x</td>
					<td>Mar–Apr 2025</td>
					<td style="text-align: right">115–119 KB</td>
					<td style="text-align: right">193</td>
					<td>Unknown/OpenWith, Office, ACL operations</td>
			</tr>
			<tr>
					<td>4.2.x</td>
					<td>Apr 2025</td>
					<td style="text-align: right">135 KB</td>
					<td style="text-align: right">248</td>
					<td>Dynamic rules and self-protection</td>
			</tr>
			<tr>
					<td>4.3.0</td>
					<td>Jun 2025</td>
					<td style="text-align: right">157 KB</td>
					<td style="text-align: right">295</td>
					<td>StackTrace and renamed-file parsing</td>
			</tr>
			<tr>
					<td>4.4.x</td>
					<td>late 2025</td>
					<td style="text-align: right">164–174 KB</td>
					<td style="text-align: right">312–316</td>
					<td>CrossDeviceResume</td>
			</tr>
			<tr>
					<td>4.5.0</td>
					<td>Oct 2025</td>
					<td style="text-align: right">168 KB</td>
					<td style="text-align: right">333</td>
					<td>GamingConfiguration</td>
			</tr>
			<tr>
					<td>4.6.0</td>
					<td>Feb 2026</td>
					<td style="text-align: right">176 KB</td>
					<td style="text-align: right">341</td>
					<td>FeatureV3 and SHA-512</td>
			</tr>
			<tr>
					<td>4.7.x</td>
					<td>2026</td>
					<td style="text-align: right">197 KB</td>
					<td style="text-align: right">354–355</td>
					<td>Complete 14-entry deny list</td>
			</tr>
	</tbody>
</table>
<p>Function counts come from the x64 exception directory. They are not a perfect source-level function count, but they are consistent enough to show the scale of each transition.</p>
<p>Windows servicing branches do not form a perfectly linear history. The same logical version can appear as multiple binaries for 22H2, 24H2, 25H2, or 26H1, and release dates can cross. I therefore treated file version and policy contents as the primary ordering and used KB dates as supporting provenance.</p>
<h2 id="version-2-no-longer-just-the-default-browser">Version 2: no longer just the default browser</h2>
<p>Version 2.0 added the first major expansion:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice
</span></span><span style="display:flex;"><span>SOFTWARE\Microsoft\Windows\CurrentVersion\Control Panel\DeviceRegion
</span></span><span style="display:flex;"><span>FeatureV2
</span></span></code></pre></div><p>It also introduced <code>UserChoiceLatest</code> and <code>UserChoicePrevious</code> forms for HTTP, HTTPS, and PDF. <code>UserChoiceLatest</code> would not become operationally important until much later, when Windows enabled its new association-hash rotation design. Christoph eventually documented that migration in <a href="https://kolbi.cz/blog/2025/04/20/userchoicelatest-microsofts-new-protection-for-file-type-associations/">UserChoiceLatest – Microsoft&rsquo;s new protection for file type associations</a>.</p>
<p>The code had been waiting in UCPD since 2023.</p>
<p>The deny list also changed. Version 2 removed <code>svchost.exe</code> and added <code>powershell.exe</code> and <code>regedit.exe</code>. Version 2.1 added <code>wscript.exe</code> and <code>cscript.exe</code>; version 2.2 added <code>cmd.exe</code>.</p>
<p>Version 2.1 was also where UCPD visibly escaped its name. It added registry rules for Windows UI features:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>SOFTWARE\Microsoft\Windows\CurrentVersion\Feeds
</span></span><span style="display:flex;"><span>SOFTWARE\Microsoft\Windows\CurrentVersion\NandI
</span></span><span style="display:flex;"><span>Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ShellFeedsTaskbarViewMode
</span></span><span style="display:flex;"><span>IsFeedsAvailable
</span></span><span style="display:flex;"><span>TaskbarDa
</span></span></code></pre></div><p>At that point, &ldquo;User Choice Protection Driver&rdquo; was already becoming a misleading description. It was protecting choices, but they were no longer limited to file associations.</p>
<h2 id="version-3-defending-trusted-processes-from-other-processes">Version 3: defending trusted processes from other processes</h2>
<p>The jump from 2.2 to 3.0 is the first enormous one: 41 KB became 86 KB and 74 functions became 143.</p>
<p>This is where the problem changed from &ldquo;who made this registry call?&rdquo; to &ldquo;can I trust what is running inside the process that made it?&rdquo;</p>
<p>Version 3 added process and publisher vocabulary for Kingsoft, Qihoo/360, Edge, Explorer, and Opera. It classified selected processes and used object callbacks to prevent particular vendors from injecting into trusted Microsoft processes or driving the Settings UI through desktop journal playback.</p>
<p>The driver also expanded its registry surface to Search and Taskband, while the deny list gained <code>InfDefaultInstall.exe</code> and <code>pwsh.exe</code>.</p>
<p>Version 3.1 is an interesting reminder that this history is not monotonic. One 3.1 build contains the Copilot <code>BrandedKeyChoiceType</code> rule; another removes its path and value strings. Version 4.0 restores them. That looks like branch divergence or A/B feature experimentation rather than a feature being implemented once and permanently enabled.</p>
<p>The manager confirms the feature-driven design. I recovered the original UCPDMgr input from my old Binary Ninja database, then downloaded its matching PDB from Microsoft&rsquo;s symbol server. Its feature traits include:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>UCPD
</span></span><span style="display:flex;"><span>UCPDV2
</span></span><span style="display:flex;"><span>UCPD_PRONG1
</span></span><span style="display:flex;"><span>UCPD_PRONG2
</span></span><span style="display:flex;"><span>UCPD_TASKBAR
</span></span><span style="display:flex;"><span>UCPD_WSB
</span></span><span style="display:flex;"><span>UCPD_ANTIUIA
</span></span><span style="display:flex;"><span>UCPD_ANTIINJECTION
</span></span><span style="display:flex;"><span>UCPD_NEWDENYLIST
</span></span><span style="display:flex;"><span>UCPD_RENAME_ATTACK
</span></span></code></pre></div><p>UCPDMgr did not invent the policies. It translated Windows Feature Staging decisions into the <code>FeatureV2</code> mask consumed by the driver.</p>
<h2 id="version-41-a-general-registry-integrity-engine">Version 4.1: a general registry integrity engine</h2>
<p>Version 4.0 broadened the publisher and regional policies, but 4.1 is the more important conceptual transition. It added:</p>
<ul>
<li>unknown-file/OpenWith handler repair</li>
<li><code>.htm</code> and <code>.html</code> associations</li>
<li>Word, Excel, and PowerPoint associations</li>
<li><code>UserChoiceLatest\ProgId</code> rules</li>
<li>registry security and ACL operations</li>
<li>explicit delete and rename handling for the new families</li>
</ul>
<p>The Unknown handler is especially illustrative. Windows expects these keys to launch the real <code>OpenWith.exe</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>SOFTWARE\Classes\Unknown\shell
</span></span><span style="display:flex;"><span>SOFTWARE\Classes\Unknown\shell\open\command
</span></span><span style="display:flex;"><span>SOFTWARE\Classes\Unknown\shell\openas\command
</span></span></code></pre></div><p>UCPD contains the expected command, DelegateExecute CLSID, and dedicated <code>UnknownModify</code>, <code>UnknownDelKey</code>, and <code>UnknownRenKey</code> paths. This is not about choosing a browser. It is registry integrity protection against replacing a trusted shell action with an attacker-controlled command.</p>
<p>Version 4.1 also removed the remaining <code>UserChoicePrevious</code> strings. Windows kept <code>UserChoiceLatest</code>; the earlier fallback design apparently did not survive.</p>
<h2 id="version-42-a-signed-policy-channel-appears">Version 4.2: a signed policy channel appears</h2>
<p>Version 4.2 grew from 119 KB to 135 KB and added the machinery that led to my dynamic-rules article:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\UCPD\DR
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Base64Decode
</span></span><span style="display:flex;"><span>ParsePEFormat
</span></span><span style="display:flex;"><span>CalculatePEHashInMem
</span></span><span style="display:flex;"><span>CertificateVerify
</span></span><span style="display:flex;"><span>DecryptData
</span></span><span style="display:flex;"><span>DispatcherConfig
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>AntiInjection
</span></span><span style="display:flex;"><span>AllowListV1
</span></span><span style="display:flex;"><span>DenyListV1
</span></span></code></pre></div><p>The registry value is a Base64-encoded, data-only PE. UCPD parses it, verifies its Microsoft signature, decrypts the <code>.rdata</code>, and dispatches nested TLV records into independent policy structures.</p>
<p>This solved an operational problem for Microsoft. Before this mechanism, changing UCPD policy required a new driver and usually a reboot. A signed registry policy can change deny/allow, injection, UI Automation, or stack-trace rules independently.</p>
<p>The same version also contains explicit paths for protecting the UCPD service and <code>DR</code> key themselves. A remotely updatable kernel policy is only useful if another administrator-level process cannot replace its input.</p>
<h2 id="version-43-trust-the-original-file-and-the-entire-stack">Version 4.3: trust the original file and the entire stack</h2>
<p>Version 4.3 added two names that summarize the next defenses:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ParseOriginalFileName
</span></span><span style="display:flex;"><span>StackTrace
</span></span></code></pre></div><p>The original UCPD trust rule had a wonderfully simple weakness: copy a Microsoft binary under another name. Its signature remained valid, but it no longer matched the utility deny list. VMware even used this approach for its default-association tooling.</p>
<p>The OriginalFilename check closes that gap by reading the PE version resource and comparing the program&rsquo;s real name rather than trusting its current path.</p>
<p>The stack check addresses the other obvious route. Even if the registry write originates in <code>SystemSettings.exe</code>, UCPD can walk the stack, identify the loaded modules behind the call, and reject the operation when a blocked publisher appears in the chain.</p>
<p>At this point, the trust decision looked more like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>registry operation
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- protected path/value/operation?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- Microsoft-signed caller?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- denied utility by path or OriginalFilename?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- blocked process/publisher classification?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      +-- suspicious module publisher in stack?
</span></span><span style="display:flex;"><span>      |
</span></span><span style="display:flex;"><span>      `-- allow or deny, then emit telemetry
</span></span></code></pre></div><h2 id="version-44-crossdeviceresume">Version 4.4: CrossDeviceResume</h2>
<p>Version 4.4 introduced a previously undocumented rule family named <code>CrossDeviceResume</code>. It protects five path/value pairs:</p>
<table>
	<thead>
			<tr>
					<th>Path suffix</th>
					<th>Value</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>CrossDeviceResume\ResumeIntent</code></td>
					<td><code>AppIconPath</code></td>
			</tr>
			<tr>
					<td>same</td>
					<td><code>AppDisplayName</code></td>
			</tr>
			<tr>
					<td>same</td>
					<td><code>IsAppInstalled</code></td>
			</tr>
			<tr>
					<td><code>CrossDeviceResume\Notification</code></td>
					<td><code>Status</code></td>
			</tr>
			<tr>
					<td><code>CrossDeviceResume\Configuration</code></td>
					<td><code>OverlayIconPath</code></td>
			</tr>
	</tbody>
</table>
<p>In the later driver, this family is controlled by <code>FeatureV2</code> bit <code>0x08000000</code> and has a dedicated <code>CrossDeviceResume</code> ETW label.</p>
<p>The names strongly suggest protection against spoofing a cross-device resume notification: which app it claims to represent, its icon, whether it is installed, and its notification state. I have not found public documentation for this internal registry schema, so that interpretation is an inference rather than a confirmed product contract.</p>
<p>Version 4.4 also adds <code>regini.exe</code> to the hardcoded deny list and makes the dynamic loader&rsquo;s <code>CertificateVerify</code> stage explicit in its event vocabulary.</p>
<h2 id="version-45-gamingconfiguration">Version 4.5: GamingConfiguration</h2>
<p>Version 4.5 added another complete policy family:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Software\Microsoft\Windows\CurrentVersion\GamingConfiguration
</span></span><span style="display:flex;"><span>    DefaultGamingApp
</span></span><span style="display:flex;"><span>    DefaultToGamingExperience
</span></span></code></pre></div><p>This is controlled by <code>FeatureV2</code> bit <code>0x40000000</code>. Unlike a coincidental registry string, it has dedicated paths for every important operation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>SetGameConfig
</span></span><span style="display:flex;"><span>DelGameConfig
</span></span><span style="display:flex;"><span>RenameGameConfig
</span></span><span style="display:flex;"><span>DeleteGameConfigValue
</span></span><span style="display:flex;"><span>GameConfig
</span></span></code></pre></div><p>Microsoft&rsquo;s current <a href="https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-gaming-configuration">Gaming Configuration documentation</a> describes an OEM-configurable full-screen gaming experience, including the gaming-home app launched at startup. The public setting names differ, but <code>DefaultGamingApp</code> and <code>DefaultToGamingExperience</code> line up closely with that design.</p>
<p>The security motivation is sensible: a third-party program should not silently install itself as a shell-like gaming launcher or force the device to start in that experience.</p>
<h2 id="version-46-featurev3-and-the-broken-cipher">Version 4.6: FeatureV3 and the broken cipher</h2>
<p>Version 4.6 adds three particularly revealing strings:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>FeatureV3
</span></span><span style="display:flex;"><span>SHA512
</span></span><span style="display:flex;"><span>Unknown publisher
</span></span></code></pre></div><p><code>FeatureV2</code> is a 32-bit <code>REG_DWORD</code>, and by this point Microsoft had assigned nearly every bit. <code>FeatureV3</code> is a separate 64-bit <code>REG_QWORD</code>. The 4.7 manager&rsquo;s public PDB identifies its writer exactly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#a6e22e">SetFeatureV3Status</span>(<span style="color:#66d9ef">__int64</span> mask)
</span></span></code></pre></div><p>It currently maps seven Windows feature IDs into bits 0 through 6. The feature traits have only numeric names, so the PDB tells us their IDs but not their product descriptions. The driver consumes those bits in its rule database and in several distinct enforcement paths.</p>
<p>This also dates the dynamic-rule crypto bug more precisely.</p>
<p>The captured DR policy was encrypted using the old MD5-derived CS64 algorithm. Version 4.6&rsquo;s consumer uses SHA-512 to derive its state. The policy consequently decrypts to garbage. The loader existed in 4.2, while the incompatible SHA-512 change appears in 4.6.</p>
<p>That does not prove the entire mechanism can never work. It proves that the captured producer and this consumer disagree. Microsoft could correct the policy producer without changing UCPD again.</p>
<h2 id="version-47-the-dynamic-deny-list-becomes-the-baseline">Version 4.7: the dynamic deny list becomes the baseline</h2>
<p>The DR sample contained 14 denied Microsoft utilities:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>dllhost.exe          reg.exe             rundll32.exe
</span></span><span style="display:flex;"><span>powershell.exe       regedit.exe         wscript.exe
</span></span><span style="display:flex;"><span>cscript.exe          cmd.exe             InfDefaultInstall.exe
</span></span><span style="display:flex;"><span>pwsh.exe             wmiprvse.exe        regini.exe
</span></span><span style="display:flex;"><span>bssafe.exe           mshta.exe
</span></span></code></pre></div><p>In 4.6, the last two existed only in the captured dynamic policy. Version 4.7 adds <code>bssafe.exe</code> and <code>mshta.exe</code> to the driver&rsquo;s hardcoded strings, so the static and dynamic lists finally become identical.</p>
<p>Version 4.7 also adds generic matching vocabulary:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>*\UserChoice
</span></span><span style="display:flex;"><span>*\ProgId
</span></span><span style="display:flex;"><span>*\UserChoiceLatest
</span></span><span style="display:flex;"><span>\ProgId
</span></span></code></pre></div><p>The explicit paths are still present, but these patterns suggest Microsoft was consolidating parts of the association matching logic rather than continuing to special-case every suffix.</p>
<h2 id="what-changed-conceptually">What changed, conceptually?</h2>
<p>Looking across all versions, UCPD evolved through four different jobs:</p>
<ol>
<li><strong>Association filter</strong> — protect HTTP and HTTPS UserChoice from simple registry writes.</li>
<li><strong>Shell-setting filter</strong> — protect PDF, DeviceRegion, Feeds, taskbar, Search, Copilot, and Office associations.</li>
<li><strong>Process integrity system</strong> — track processes, publishers, loaded modules, injection rights, UI Automation, original filenames, and call stacks.</li>
<li><strong>Updatable policy engine</strong> — accept Microsoft-signed rule blobs and protect its own configuration.</li>
</ol>
<p>The name stayed the same while the product changed underneath it.</p>
<p>The pattern of additions also looks much less arbitrary when placed on a timeline. Many changes are direct answers to a bypass:</p>
<table>
	<thead>
			<tr>
					<th>Bypass or concern</th>
					<th>UCPD response</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Use a Microsoft utility to write the key</td>
					<td>Utility deny list</td>
			</tr>
			<tr>
					<td>Use another utility not yet listed</td>
					<td>Expanded and dynamic deny lists</td>
			</tr>
			<tr>
					<td>Copy a signed utility under another name</td>
					<td>OriginalFilename verification</td>
			</tr>
			<tr>
					<td>Inject into Edge or Explorer</td>
					<td>Process/thread handle-right filtering</td>
			</tr>
			<tr>
					<td>Automate the Settings UI</td>
					<td>Desktop/UIA restrictions</td>
			</tr>
			<tr>
					<td>Inject a module into SystemSettings</td>
					<td>Stack/module publisher inspection</td>
			</tr>
			<tr>
					<td>Delete instead of setting a protected value</td>
					<td>Delete-value enforcement</td>
			</tr>
			<tr>
					<td>Reset the key ACL</td>
					<td>SetSecurityKey enforcement</td>
			</tr>
			<tr>
					<td>Rename a protected key</td>
					<td>Rename enforcement</td>
			</tr>
			<tr>
					<td>Disable features through UCPD&rsquo;s own registry</td>
					<td>Self-protection</td>
			</tr>
			<tr>
					<td>Require policy changes without a reboot</td>
					<td>Signed dynamic-rule PE</td>
			</tr>
	</tbody>
</table>
<p>It is an arms race preserved in version history.</p>
<h2 id="how-i-built-the-corpus">How I built the corpus</h2>
<p>I queried Winbindex for every indexed x64 <code>UCPD.sys</code>, then downloaded the available PE reconstruction from Microsoft&rsquo;s symbol server. For each entry I recorded:</p>
<ul>
<li>indexed original SHA-256</li>
<li>downloaded-file SHA-256</li>
<li>file version and size</li>
<li>Windows branch, KB, build, and release metadata</li>
<li>PE sections and imports</li>
<li>exception-directory function count</li>
<li>ASCII and UTF-16 strings</li>
<li>CodeView/PDB identity</li>
<li>additions and removals relative to the adjacent version</li>
</ul>
<p>Microsoft no longer serves the corresponding UCPD PDBs for most of this corpus, so this comparison did not depend on symbols. I grouped equivalent branch builds, ordered the remaining versions, and compared their strings, imports, and PE metadata. For important additions, I followed the string references into the machine code to confirm that they created real rules rather than being unused text.</p>
<p>There is an important provenance detail here. The Microsoft symbol server sometimes reconstructs the executable without its original Authenticode certificate overlay. Consequently, the downloaded file can differ from the original indexed SHA-256 and may report <code>NotSigned</code> even though it came from Microsoft&rsquo;s symbol infrastructure.</p>
<p>I kept the two hashes separate and did not treat every reconstruction as independently signature-valid. Several original binaries recovered from my older <code>.bndb</code> files still retain their exact signatures, as does the current locally installed pair.</p>
<p>I also gave Codex access to the Binary Ninja databases from my earlier UCPD research, covering snapshots from March 2024 through June 2026. Symbols for <code>UCPD.sys</code> were missing for most versions, but Microsoft still served matching PDBs for the corresponding <code>UCPDMgr.exe</code> files. Those manager symbols exposed feature traits and helper names that offered another view into the changes, while the driver comparison itself remained symbol-free.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Following one binary over time tells a different story from analyzing one version deeply.</p>
<p>UCPD began as a small, narrowly focused registry filter at least as early as August 2023. Each generation responded to another way software could influence a supposedly trusted registry operation. By 2026 it had become a region- and feature-dependent kernel policy engine that watches registry operations, process creation, loaded images, object handles, UI Automation, publishers, original filenames, and stacks—and can consume signed policy from the registry.</p>
<p>The most interesting additions are not even about the default browser anymore. Cross-device resume prompts and the default gaming experience are now part of the same protection framework.</p>
<p>I suspect <code>FeatureV3</code> is a good indication of what happens next. <code>FeatureV2</code> ran out of room, but Microsoft is clearly not finished adding rules.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://binary.ninja/2026/08/04/ucpd-dynamic-rules.html">The Binary Hiding in Your Registry: Cracking Windows UCPD&rsquo;s Dynamic Rules</a></li>
<li><a href="https://binary.ninja/2025/03/25/default-browser-upcd.html">Inside Windows&rsquo; Default Browser Protection</a></li>
<li><a href="https://kolbi.cz/blog/2025/07/15/ucpd-sys-userchoice-protection-driver-part-2/">UCPD.sys – UserChoice Protection Driver Part 2</a></li>
<li><a href="https://kolbi.cz/blog/2024/04/03/userchoice-protection-driver-ucpd-sys/">UserChoice Protection Driver – UCPD.sys</a></li>
<li><a href="https://kolbi.cz/blog/2025/04/20/userchoicelatest-microsofts-new-protection-for-file-type-associations/">UserChoiceLatest – Microsoft&rsquo;s new protection for file type associations</a></li>
<li><a href="https://kolbi.cz/blog/2026/07/23/kb5121767-enables-additional-ucpd-sys-features-on-windows-11-enterprise/">KB5121767 Enables Additional UCPD.sys Features on Windows 11 Enterprise</a></li>
<li><a href="https://hitco.at/blog/windows-userchoice-protection-driver-ucpd/">Windows UserChoice Protection Driver UCPD</a></li>
<li><a href="https://github.com/xusheng6/ucpd_analysis">UCPD analysis databases and tooling</a></li>
<li><a href="https://winbindex.m417z.com/">Winbindex</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Microsoft Paint and Photos Embed Server-Issued GUIDs as Invisible Watermarks in Locally-Generated Images</title>
            <link>https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/</link>
            <pubDate>Fri, 21 Aug 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/mspaint_invisible_watermark/main/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Paint supports both local and cloud image generation&lt;/li&gt;
&lt;li&gt;Paint and Photos also ship local AI models&lt;/li&gt;
&lt;li&gt;The two apps send the prompt to a remote server for moderation&lt;/li&gt;
&lt;li&gt;The server returns a GUID along with the moderated prompt&lt;/li&gt;
&lt;li&gt;The GUID is embedded into the locally generated image as an invisible watermark&lt;/li&gt;
&lt;li&gt;A separate visible-watermark setting does not control this invisible watermark&lt;/li&gt;
&lt;li&gt;On Copilot+ PCs, image generation is local but prompt moderation remains remote&lt;/li&gt;
&lt;li&gt;Microsoft discloses that Paint adds C2PA metadata to AI-generated images&lt;/li&gt;
&lt;li&gt;AI-generated image saves limited to C2PA-preserving formats: PNG, JPEG, GIF, and &lt;code&gt;.paint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;../social-preview.jpg&#34; alt=&#34;Paint sends the user prompt to Microsoft’s moderation server, receives a moderated prompt and watermark GUID, generates the image locally, and embeds the GUID into the final image pixels&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li>Microsoft Paint supports both local and cloud image generation</li>
<li>Paint and Photos also ship local AI models</li>
<li>The two apps send the prompt to a remote server for moderation</li>
<li>The server returns a GUID along with the moderated prompt</li>
<li>The GUID is embedded into the locally generated image as an invisible watermark</li>
<li>A separate visible-watermark setting does not control this invisible watermark</li>
<li>On Copilot+ PCs, image generation is local but prompt moderation remains remote</li>
<li>Microsoft discloses that Paint adds C2PA metadata to AI-generated images</li>
<li>AI-generated image saves limited to C2PA-preserving formats: PNG, JPEG, GIF, and <code>.paint</code></li>
</ul>
<p><img src="../social-preview.jpg" alt="Paint sends the user prompt to Microsoft’s moderation server, receives a moderated prompt and watermark GUID, generates the image locally, and embeds the GUID into the final image pixels"></p>
<h2 id="a-curious-look-at-microsoft-paint">A curious look at Microsoft Paint</h2>
<p>This research started with my curiosity about Paint. I recently had some success looking into less-explored Windows features like <a href="https://binary.ninja/2026/08/04/ucpd-dynamic-rules.html">UCPD</a>, <a href="https://xusheng.dev/posts/reversing/whesvc/main/">WHESCVC</a>, and I have long known that Microsoft
added a bunch of <a href="https://support.microsoft.com/en-us/windows/ai/ai-apps/use-copilot-pc-features-in-paint">AI features</a> into the Paint app.
I do not know if anyone actually uses Paint + AI to generate images, but I wanted to see how exactly the image generation works.</p>
<p>Before I started, I expected that it simply called a remote API to do the image generation. However, after I set up <a href="https://dev-docs.binary.ninja/guide/mcp.html">Binary Ninja MCP</a> with Codex and started the analysis, I soon realized that Microsoft actually shipped local models in Windows as part of Copilot.</p>
<p>The Paint App is sitting in the following path (yes, they are all <a href="https://learn.microsoft.com/en-us/windows-app/overview">Windows Apps</a> now):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>C:\Program Files\WindowsApps\Microsoft.Paint_11.2605.71.0_x64__8wekyb3d8bbwe\PaintApp\
</span></span></code></pre></div><p>And there are four apparent model files with the <code>.onnxe</code> extension:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>seg.onnxe          23.1 MB
</span></span><span style="display:flex;"><span>inseg_enc.onnxe    28.0 MB
</span></span><span style="display:flex;"><span>inseg_dec.onnxe    16.5 MB
</span></span><span style="display:flex;"><span>mager.onnxe       302.4 MB
</span></span></code></pre></div><p>The format of <code>seg.onnxe</code> was <a href="https://itstechbased.com/new-windows-11-build-25947-new-23h2-news-new-paint-and-microsoft-store-and-fixes-canary/">previously known</a>, i.e., when it is XORed with the string <code>Microsoft_2023</code>, it becomes a normal ONNX file. However, the format of the other three <code>.onnxe</code> files initially looked different.</p>
<p>It turned out that Microsoft had not changed the algorithm, only the key. <code>segapi.dll</code> contains a small key registry:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ps_enc_key.1.0.80-main -&gt; &#34;Microsoft_2023&#34;
</span></span><span style="display:flex;"><span>ps_enc_key.1.0.81-main -&gt; a 4,096-byte alphanumeric string
</span></span></code></pre></div><p>After decryption, <code>onnx.checker.check_model()</code> works on all of them:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th>Graph</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>seg.onnx</code></td>
					<td>1,094 nodes, input <code>input_image</code>, output <code>output</code></td>
			</tr>
			<tr>
					<td><code>inseg_enc.onnx</code></td>
					<td>1,014 nodes, output <code>image_embeddings</code></td>
			</tr>
			<tr>
					<td><code>inseg_dec.onnx</code></td>
					<td>1,133 nodes, inputs for embeddings, points and masks; output <code>masks</code></td>
			</tr>
			<tr>
					<td><code>mager.onnx</code></td>
					<td>15,284 nodes, image/mask inputs; output <code>output</code></td>
			</tr>
	</tbody>
</table>
<h2 id="a-visible-watermark">A visible watermark</h2>
<p>While walking through these files, I found a <code>Watermarker.dll</code>:</p>
<p><img src="../watermarker-dll-properties.png" alt="The properties of Watermarker.dll included with Microsoft Paint"></p>
<p>This is not super surprising to me, because while I interacted with the Paint app, I already discovered that it has a setting to embed a <a href="https://support.microsoft.com/en-us/topic/include-a-watermark-when-content-from-microsoft-365-is-ai-generated-b00a656e-ae61-4692-8086-67d004421030">visible watermark</a> to the image that it produces:</p>
<p><img src="../visible-watermark-setting.png" alt="Paint offers Never, Always, and Ask every time choices for its visible AI watermark"></p>
<p>The visible watermark is just a small Copilot logo at the bottom right of the image, which is totally normal.</p>
<p>Then, out of nowhere, I decided to ask AI to analyze the DLL and see if it could also be embedding an <em>invisible</em> watermark. This is part of my intuition as a reverse engineer, because the file is 1.67 MB in size, which is unusually large for such trivial functionality (arguably, the visible watermark does not even require a separate DLL). Apparently, the recent Claude Code <a href="https://www.anthropic.com/news/claude-text-watermark">text-watermark announcement</a> also played a role in prompting me to think about this possibility.</p>
<h2 id="an-invisible-watermark">An <em>invisible</em> watermark</h2>
<p>To begin with, the visible watermark is added by <code>AddPerceptibleWatermark</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>CPBDoc::Save(...)
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  `-- perceptible-watermark save helper(bitmap, WatermarkSetting)
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        +-- WatermarkSetting::Never
</span></span><span style="display:flex;"><span>        |     `-- return the original bitmap
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        +-- WatermarkSetting::AskEveryTime
</span></span><span style="display:flex;"><span>        |     `-- show the Yes / No confirmation popup
</span></span><span style="display:flex;"><span>        |           +-- No: return the original bitmap
</span></span><span style="display:flex;"><span>        |           `-- Yes: continue
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        `-- Always or confirmed Yes
</span></span><span style="display:flex;"><span>              +-- Paint::AI::GetPerceptibleWatermarkSvg()
</span></span><span style="display:flex;"><span>              `-- Paint::AI::AddPerceptibleWatermark(bitmap, SVG stream)
</span></span><span style="display:flex;"><span>                    `-- composite the visible Copilot logo
</span></span></code></pre></div><p>Then there is also a different <code>WmkWriteWatermark</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span>Watermarker.dll<span style="color:#f92672">!</span><span style="color:#a6e22e">WmkWriteWatermark</span>(
</span></span><span style="display:flex;"><span>    output_pixels,
</span></span><span style="display:flex;"><span>    payload,
</span></span><span style="display:flex;"><span>    payload_length,
</span></span><span style="display:flex;"><span>    width,
</span></span><span style="display:flex;"><span>    height,
</span></span><span style="display:flex;"><span>    stride,
</span></span><span style="display:flex;"><span>    input_pixels,
</span></span><span style="display:flex;"><span>    pixel_format);
</span></span></code></pre></div><p>Tracing the call tree, we can see <code>WmkWriteWatermark</code> is called after a local Stable Diffusion image generation. And if <code>WmkWriteWatermark</code> fails, Paint converts the entire generation into an error rather than returning the image without it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>CocreatorViewModel::GenerateImageAsync(...)
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  `-- Paint::AI::StableDiffusionHelpers::GenerateAsync(..., watermarkId, ...)
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        `-- Microsoft.ImageCreation.ImageGenerator
</span></span><span style="display:flex;"><span>              |
</span></span><span style="display:flex;"><span>              `-- NPU-generated image result
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    +-- output safety/moderation checks
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    +-- Paint::AI::AddWatermark(bitmap, watermarkId)
</span></span><span style="display:flex;"><span>                    |     |
</span></span><span style="display:flex;"><span>                    |     `-- Watermarker.dll!WmkWriteWatermark(...)
</span></span><span style="display:flex;"><span>                    |           |
</span></span><span style="display:flex;"><span>                    |           +-- success: return the watermarked bitmap
</span></span><span style="display:flex;"><span>                    |           `-- failure: turn generation into an error
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    `-- construct successful StableDiffusionResult
</span></span></code></pre></div><p>Then it is natural to ask what the incoming <code>payload</code> actually is. It quickly becomes apparent that it must be 16 bytes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (payload_length <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">6</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (payload_length <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">5</span>;
</span></span></code></pre></div><p>It is funny to me that the code is using two different error codes when the payload is too short or too long. The function then ignores the length parameter and uses a hard-coded loop bound when it copies the payload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">size_t</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">16</span>; i<span style="color:#f92672">++</span>)
</span></span><span style="display:flex;"><span>    message.<span style="color:#a6e22e">push_back</span>(payload[i]);
</span></span></code></pre></div><p>We do not yet know what the 16-byte payload is, but as we will see later, it is a GUID! <code>WmkWriteWatermark</code> does not embed the GUID directly. Its wrapper constructs the following 18-byte (144-bit) message:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0x4c || GUID[0..15] || (sum of the 16 GUID bytes modulo 256)
</span></span></code></pre></div><p>The core encoder rounds the usable image dimensions down to multiples of eight and keeps 144 counters, one for each bit. It requires every bit to be placed at least three times.</p>
<p>The encoder itself can be summarized as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>WmkWriteWatermark(output, guid, 16, width, height, stride, input, format)
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  +-- validate pointers, format, stride, and payload length
</span></span><span style="display:flex;"><span>  +-- require width &gt;= 192 and height &gt;= 192
</span></span><span style="display:flex;"><span>  +-- construct payload
</span></span><span style="display:flex;"><span>  |     `-- 0x4c || GUID || byte-sum checksum
</span></span><span style="display:flex;"><span>  +-- expand 18 bytes into 144 individual bits
</span></span><span style="display:flex;"><span>  +-- round usable dimensions down to 8-pixel boundaries
</span></span><span style="display:flex;"><span>  +-- scan/select suitable image blocks
</span></span><span style="display:flex;"><span>  +-- quantize selected block/matrix values according to each bit
</span></span><span style="display:flex;"><span>  +-- require at least three successful placements per bit
</span></span><span style="display:flex;"><span>  |     |
</span></span><span style="display:flex;"><span>  |     `-- insufficient capacity -&gt; return -8
</span></span><span style="display:flex;"><span>  `-- reconstruct RGB pixels into the output buffer
</span></span></code></pre></div><p>The embedding loop performs small quantized changes over selected image blocks. It contains 3-by-5 matrix operations and a matrix-decomposition routine, and it uses constants including <code>24.0</code>, <code>0.25</code>, <code>0.5</code>, and <code>0.2</code>. This looks like a content-adaptive block-domain, SVD-style watermark.</p>
<p>I am not an expert in image watermarking, but one thing should be clear &ndash; this is an invisible watermark! AI even wrote some code to call this function directly and tested it with a synthetic 512-by-512 BGRA image &ndash; 193,376 of the 262,144 pixels changed after adding the watermark.</p>
<p>That led to the next question. Where does the input of the watermark come from?</p>
<h2 id="a-guid-from-remote-prompt-moderation">a GUID from remote prompt moderation</h2>
<p>At the <code>WmkWriteWatermark</code> boundary, the payload is only a pointer and a length. Knowing that it must be 16 bytes was a clue, but many things can be 16 bytes. I therefore started walking backward through its callers. The immediate wrapper in <code>PaintAIManager.dll</code> has this symbolized signature:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span>Paint<span style="color:#f92672">::</span>AI<span style="color:#f92672">::</span>AddWatermark(
</span></span><span style="display:flex;"><span>    Gdiplus<span style="color:#f92672">::</span>Bitmap<span style="color:#f92672">&amp;</span> image,
</span></span><span style="display:flex;"><span>    winrt<span style="color:#f92672">::</span>guid <span style="color:#66d9ef">const</span><span style="color:#f92672">&amp;</span> watermarkId);
</span></span></code></pre></div><p><code>winrt::guid</code>, yikes! Now we know that the 16-byte watermark payload is indeed a GUID.</p>
<p>Further tracking the source, we find that the GUID actually comes from a network request. Before Paint runs the local image model, <code>AIServices.dll</code> sends the prompt and style to:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>https://apsaiservices-a0fqcjc6bzbhgdcd.b02.azurefd.net/
</span></span><span style="display:flex;"><span>v1/paint-cocreator/moderate-prompt
</span></span></code></pre></div><p>The request is JSON and contains at least these fields:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;prompt&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;style&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;lastPromptGenerationId&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The response parser expects:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;revisedPrompt&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;promptGenerationId&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;watermarkId&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;containsHumanReference&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Static analysis is nice, but at this point I wanted to see a real response from
the server. I reused Paint&rsquo;s own authenticated session and sent the following
prompt through the moderation endpoint:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>a cobalt blue circle above a tiny orange square
</span></span></code></pre></div><p>The server returned HTTP 200:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;revisedPrompt&#34;</span>: <span style="color:#e6db74">&#34;a cobalt blue circle above a tiny orange square&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;promptGenerationId&#34;</span>: <span style="color:#e6db74">&#34;74d9e06b-adea-43ce-85fe-186a26e2e34a&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;watermarkId&#34;</span>: <span style="color:#e6db74">&#34;83424621-03cb-40e3-9808-a9fae837156d&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;containsHumanReference&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I also tried the prompt <code>a portrait of a smiling person wearing a blue hat</code>.
This time the response contained a different pair of
GUIDs and <code>containsHumanReference</code> was <code>true</code>. The field is therefore a
server-side classification of whether the prompt refers to a human. Paint
parses and stores it alongside the IDs, although I found no evidence that it
controls the watermarking step itself.</p>
<p><code>ParseModerateResponse</code> parses both ID strings as GUIDs and rejects zero values with <code>InvalidPromptGenerationId</code> or <code>InvalidWatermarkId</code>. The server&rsquo;s <code>watermarkId</code> is what becomes part of the generated image:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>PaintUI.dll
</span></span><span style="display:flex;"><span>  `-- IPromptModerationService
</span></span><span style="display:flex;"><span>        `-- PaintAIManager.dll
</span></span><span style="display:flex;"><span>              `-- AIServices.dll!ModerateAsync(...)
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    +-- build JSON
</span></span><span style="display:flex;"><span>                    |     +-- prompt
</span></span><span style="display:flex;"><span>                    |     +-- style
</span></span><span style="display:flex;"><span>                    |     `-- lastPromptGenerationId
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    +-- HTTPS POST /v1/paint-cocreator/moderate-prompt
</span></span><span style="display:flex;"><span>                    |
</span></span><span style="display:flex;"><span>                    `-- AIServices.dll!ParseModerateResponse(response)
</span></span><span style="display:flex;"><span>                          +-- revisedPrompt
</span></span><span style="display:flex;"><span>                          +-- promptGenerationId -&gt; parse as GUID
</span></span><span style="display:flex;"><span>                          +-- watermarkId        -&gt; parse as GUID
</span></span><span style="display:flex;"><span>                          `-- containsHumanReference
</span></span><span style="display:flex;"><span>                                |
</span></span><span style="display:flex;"><span>                                `-- PaintUI stores WatermarkId
</span></span><span style="display:flex;"><span>                                      `-- StableDiffusionHelpers::GenerateAsync(..., watermarkId, ...)
</span></span><span style="display:flex;"><span>                                            `-- local Stable Diffusion result
</span></span><span style="display:flex;"><span>                                                  `-- Paint::AI::AddWatermark(bitmap, winrt::guid const&amp;)
</span></span><span style="display:flex;"><span>                                                        `-- WmkWriteWatermark(..., guid, 16, ...)
</span></span><span style="display:flex;"><span>                                                              `-- modified RGB pixels
</span></span></code></pre></div><p>In other words, &ldquo;generated locally&rdquo; does not mean that the complete operation is local. Microsoft receives and moderates the prompt, then issues the unique GUID that Paint embeds into the locally generated image. Paint also sends the previous <code>promptGenerationId</code> as <code>lastPromptGenerationId</code> with its next moderation request, allowing successive requests to be linked explicitly.</p>
<h2 id="the-same-watermark-guid-in-c2pa-metadata">The same watermark GUID in C2PA metadata</h2>
<p>There is another piece to this story. Paint does more than alter the pixels. It also attaches <a href="https://c2pa.org/">C2PA Content Credentials</a> to the saved file. The code responsible for this lives in <code>ProvenanceHelper.dll</code>, backed by <code>provenancesdk.dll</code>.</p>
<p>For the local Stable Diffusion path, the flow looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>local Stable Diffusion result
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  +-- Paint::AI::AddWatermark(bitmap, watermarkId)
</span></span><span style="display:flex;"><span>  |     `-- Watermarker.dll!WmkWriteWatermark(..., watermarkId, 16, ...)
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  `-- AIServices.dll!SignIngredientOnlineAsync(..., promptGenerationId, image, ...)
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        +-- POST /v1/paint-cocreator/image-sign
</span></span><span style="display:flex;"><span>        |     +-- imageMetadata
</span></span><span style="display:flex;"><span>        |     |     +-- PromptGenerationId
</span></span><span style="display:flex;"><span>        |     |     +-- GenerationSeed
</span></span><span style="display:flex;"><span>        |     |     +-- CreativityLevel
</span></span><span style="display:flex;"><span>        |     |     +-- AIFVersion
</span></span><span style="display:flex;"><span>        |     |     `-- moderation scores
</span></span><span style="display:flex;"><span>        |     `-- imageToSign.jpg
</span></span><span style="display:flex;"><span>        |
</span></span><span style="display:flex;"><span>        `-- ParseProvenanceResponse(...)
</span></span><span style="display:flex;"><span>              `-- server-supplied C2PA manifest
</span></span><span style="display:flex;"><span>                    `-- ProvenanceHelper::InsertManifestIngredient(...)
</span></span><span style="display:flex;"><span>                          `-- AuthoringFinalizeOutputToBufferAsync(...)
</span></span><span style="display:flex;"><span>                                `-- final image with C2PA metadata
</span></span></code></pre></div><p>Notice that the signing request sends <code>PromptGenerationId</code>, while the image already contains the separately returned <code>watermarkId</code>. The server assigned both values during moderation, so it can associate the signing request with the watermark already present in the submitted pixels.</p>
<p>I then saved a real image directly from Paint&rsquo;s Image Creator and inspected its PNG chunks. Immediately after <code>IHDR</code> was an 18,979-byte <code>caBX</code> chunk containing a signed C2PA manifest. The interesting part was this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;c2pa.soft-binding&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;alg&#34;</span>: <span style="color:#e6db74">&#34;com.microsoft.invismark.1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;blocks&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;scope&#34;</span>: <span style="color:#e6db74">&#34;the entire image&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;83424621-03cb-40e3-9808-a9fae837156d&#34;</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;c2pa.actions.v2&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;actions&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;action&#34;</span>: <span style="color:#e6db74">&#34;c2pa.watermarked&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Content watermarked by Microsoft Responsible AI&#34;</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Decoded into something more readable, the manifest says:</p>
<ul>
<li>Generator: <code>Microsoft Responsible AI Provenance</code></li>
<li>AI system: <code>Azure OpenAI ImageGen</code></li>
<li>Action: <code>c2pa.watermarked</code></li>
<li>Algorithm: <code>com.microsoft.invismark.1</code></li>
<li>Watermark value: <code>83424621-03cb-40e3-9808-a9fae837156d</code></li>
<li>Description: <code>Content watermarked by Microsoft Responsible AI</code></li>
</ul>
<p>The server&rsquo;s <code>watermarkId</code>, the identifier embedded into the pixels, and the C2PA <code>c2pa.soft-binding.value</code> are the same per-generation value.</p>
<p>That relationship is important. C2PA calls this a <em>soft binding</em>: a value derived from, or embedded into, the content so that the content can still be matched with its provenance record after the file-level manifest has been removed. For a watermark soft binding, the <code>value</code> is the watermark&rsquo;s content identifier. Microsoft cryptographically signed this assertion.</p>
<h2 id="why-does-paint-watermark-locally">Why does Paint watermark locally?</h2>
<p>At this point, the existence of <code>Watermarker.dll</code> started to make more sense. Paint actually has two rather different generation paths.</p>
<p>The Image Creator feature I tested above uses <code>Azure OpenAI ImageGen</code>. Generation, watermarking, and provenance packaging can all happen in Microsoft&rsquo;s cloud, and Paint can simply receive a finished image that already contains both the invisible watermark and C2PA manifest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Image Creator
</span></span><span style="display:flex;"><span>  `-- Microsoft cloud
</span></span><span style="display:flex;"><span>        +-- content filtering
</span></span><span style="display:flex;"><span>        +-- Azure OpenAI ImageGen
</span></span><span style="display:flex;"><span>        +-- invisible watermark
</span></span><span style="display:flex;"><span>        +-- C2PA manifest
</span></span><span style="display:flex;"><span>        `-- completed image returned to Paint
</span></span></code></pre></div><p>Cocreator is different. On a supported Copilot+ PC, Microsoft <a href="https://support.microsoft.com/en-us/windows/ai/ai-apps/use-copilot-pc-features-in-paint">says that the NPU generates the image locally</a>, while Azure online services still perform the safety checks. The feature therefore requires both a Microsoft account and an internet connection even though the actual Stable Diffusion inference runs on the device:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Cocreator on a Copilot+ PC
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  +-- prompt -&gt; Microsoft moderation service
</span></span><span style="display:flex;"><span>  |                 +-- revisedPrompt
</span></span><span style="display:flex;"><span>  |                 +-- promptGenerationId
</span></span><span style="display:flex;"><span>  |                 `-- watermarkId
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  +-- revisedPrompt + sketch -&gt; local NPU generation
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  +-- Watermarker.dll -&gt; embed watermarkId locally
</span></span><span style="display:flex;"><span>  |
</span></span><span style="display:flex;"><span>  `-- online provenance signing -&gt; final C2PA manifest
</span></span></code></pre></div><p>This is probably the reason Paint needs a local watermark implementation at all. A cloud generator can watermark its output before returning it. A local generator cannot rely on that, so Paint has to alter the locally generated pixels itself. It also explains why Paint treats a failure from <code>WmkWriteWatermark</code> as a failure of the entire generation instead of quietly returning an unmarked image.</p>
<p>There is another surprisingly visible sign that Microsoft designed the save path around provenance. When I save a generated result directly from the Image Creator pane, Paint offers exactly one format: PNG.</p>
<p><img src="../can-only-save-png.png" alt="Paint only offers PNG when saving an AI-generated result directly"></p>
<p>After an AI result is applied to the Paint canvas, the available formats are still restricted to PNG, JPEG, GIF, and Paint&rsquo;s own <code>.paint</code> format. BMP—the classic Paint format—is conspicuously absent.</p>
<p>This lines up with the formats supported by C2PA. PNG stores its manifest in a <code>caBX</code> chunk, JPEG uses one or more <code>APP11</code> marker segments, and GIF has its own C2PA application-extension representation. The <code>.paint</code> format is controlled by Microsoft and can preserve whatever provenance state Paint requires. By contrast, the <a href="https://spec.c2pa.org/specifications/specifications/2.4/specs/ContentCredentials.html">C2PA specification explicitly calls out BMP</a> as a classic format that cannot embed arbitrary manifest data without using an external manifest. If Paint allowed the image to be exported directly as BMP, the file-level C2PA manifest would therefore disappear.</p>
<p>The split also raises an interesting security question about the cloud path. If the underlying remote image-generation endpoint can be made to return the generated image before watermarking and provenance packaging—or has an internal option that suppresses those stages—it might be possible to obtain a cloud-generated image with neither signal attached.</p>
<p>How to classify such a path would depend entirely on Microsoft&rsquo;s design goal. It could be intended behavior if the underlying service is allowed to return raw generations and Paint is merely responsible for applying the provenance layers. It could be a product bug if Microsoft overlooked the possibility of someone calling the API directly and bypassing Paint&rsquo;s watermarking step. Or it could be a security vulnerability if Microsoft treats watermarking as a mandatory abuse-prevention or provenance control and the endpoint can be made to bypass it. Without knowing the intended trust boundary, all three possibilities remain open.</p>
<h2 id="photos-app-does-the-same-thing">Photos app does the same thing</h2>
<p>While I was trying to locate the <code>Watermarker.dll</code> on disk, I happened to notice that Microsoft Photos contains a DLL with the same name:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>C:\Program Files\WindowsApps\
</span></span><span style="display:flex;"><span>  Microsoft.Windows.Photos_2026.11060.2004.0_x64__8wekyb3d8bbwe\Watermarker.dll
</span></span></code></pre></div><p>There are also local Stable Diffusion operations behind Photos&rsquo; Image Creator and Restyle Image features. Both lead to the same watermark wrapper:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Photos Image Creator
</span></span><span style="display:flex;"><span>  `-- PerformSDTextToImageAndWatermarkAsync(..., promptGenerationId, ...)
</span></span><span style="display:flex;"><span>        +-- run the local text-to-image model
</span></span><span style="display:flex;"><span>        `-- ApplyWatermark(image, promptGenerationId)
</span></span><span style="display:flex;"><span>              +-- parse promptGenerationId as a GUID
</span></span><span style="display:flex;"><span>              +-- ConvertGUIDtoContiguousByteArray()
</span></span><span style="display:flex;"><span>              +-- convert RGBA to ARGB
</span></span><span style="display:flex;"><span>              +-- Watermarker.dll!WmkWriteWatermark(..., guid, 16, ...)
</span></span><span style="display:flex;"><span>              `-- convert ARGB back to RGBA
</span></span></code></pre></div><p>Restyle Image takes the parallel path:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Photos Restyle Image
</span></span><span style="display:flex;"><span>  `-- PerformSDSketchToImageAndWatermarkAsync(..., promptGenerationId, ...)
</span></span><span style="display:flex;"><span>        `-- ApplyWatermark(image, promptGenerationId)
</span></span><span style="display:flex;"><span>              `-- Watermarker.dll!WmkWriteWatermark(..., guid, 16, ...)
</span></span></code></pre></div><p>A subtle difference between Photos and Paint is failure behavior. If the watermark encoder returns an error, its code logs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ApplyWatermark encountered error: ... - watermark will not be applied.
</span></span></code></pre></div><p>It then appears to continue returning the generated image. Paint instead treats a watermarking failure as a generation failure and the image is not returned to the user.</p>
<h2 id="what-microsoft-discloses">What Microsoft discloses</h2>
<p>After doing this analysis, I found that Microsoft does disclose some adjacent parts of the system on its <a href="https://support.microsoft.com/en-us/windows/ai/ai-apps/use-image-creator-in-paint-to-generate-ai-art">Image Creator support page</a>. On content filtering, it says:</p>
<blockquote>
<p>&ldquo;we apply content filtering to prevent the generation of images&rdquo;</p>
</blockquote>
<p>The same page says that generated images:</p>
<blockquote>
<p>&ldquo;will contain C2PA manifest helping users identify that it is an AI generated image.&rdquo;</p>
</blockquote>
<p>It also explains that Image Creator uses Azure online services and says Microsoft collects user and device identifiers together with prompts for abuse prevention and monitoring. That is a meaningful disclosure of remote filtering and C2PA metadata.</p>
<p>What the page does not explain is that the C2PA manifest contains a GUID identifying the invisible pixel watermark, or that Paint&rsquo;s local generation path receives its watermark GUID from remote prompt moderation. Calling the feature “Content Credentials” is accurate, but it does not make this prompt-associated identifier obvious to a Windows user.</p>
<h2 id="conclusion">Conclusion</h2>
<p>To the best of my knowledge, this is the first research to document and analyze the invisible-watermarking behavior of Paint and Photos. Visible watermarks on AI-generated images are not new—Microsoft documents them for <a href="https://support.microsoft.com/en-us/topic/include-a-watermark-when-content-from-microsoft-365-is-ai-generated-b00a656e-ae61-4692-8086-67d004421030">Microsoft 365</a> and <a href="https://www.microsoft.com/en-us/bing/features/bing-image-creator/">Bing Image Creator</a>—nor are invisible pixel watermarks such as <a href="https://deepmind.google/models/synthid/">Google&rsquo;s SynthID</a> and <a href="https://cdn-dynmedia-1.microsoft.com/is/content/microsoftcorp/microsoft/final/en-us/microsoft-brand/documents/August-2024-Microsoft-Bing-Systemic-Risk-Assessment-Report-EU-Digital-Services-Act.pdf">Bing&rsquo;s hidden watermark</a>.</p>
<p>Microsoft does disclose that Paint uses remote content filtering and adds C2PA Content Credentials. The new evidence shows that this metadata is not merely an unrelated file-level AI label: its signed <code>c2pa.soft-binding</code> assertion names Microsoft InvisMark and records the identifier carried by the invisible pixel watermark. The file-level manifest and pixel-level watermark are two layers of the same provenance system.</p>
<p>The local and cloud paths also explain the unusual division of labor. Cloud Image Creator can return an already watermarked and signed image, while Cocreator must embed the server-issued identifier after local NPU inference. In both cases, “local” does not mean offline: the prompt still goes to Microsoft for moderation, and the completed local result goes through online provenance signing.</p>
<p>This might be related to <a href="https://digital-strategy.ec.europa.eu/en/policies/code-practice-ai-generated-content">Article 50 of the EU AI Act</a>, whose transparency rules took effect on August 2, 2026 and require AI-generated content to carry a detectable, machine-readable mark—but not a prompt-specific GUID. Microsoft discloses the existence of C2PA metadata, but I could not find a disclosure explaining the server-issued watermark GUID, its association with prompt moderation, or its presence in the pixels. Those details carry obvious privacy and right-to-know implications.</p>
<p>It also appears possible to modify Paint or Photos to bypass both prompt moderation and watermarking. But that does not provide a new capability: anyone can already run Stable Diffusion directly without either mechanism.</p>
]]></content>
        </item>
        
        <item>
            <title>Windows&#39; Performance Optimizer Is More Capable Than It Needs to Be</title>
            <link>https://xusheng.dev/posts/reversing/whesvc/main/</link>
            <pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/whesvc/main/</guid>
            <description>&lt;p&gt;Earlier this month, &lt;a href=&#34;https://x.com/x1lly/status/2083229864862056532&#34;&gt;a post on X&lt;/a&gt; went viral by claiming Microsoft had quietly slipped a spy into Windows 11:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MICROSOFT ADDED A NEW BACKGROUND SERVICE TO YOUR PC IN WINDOWS 11. It is called Windows Health and Optimized Experiences. It starts automatically every time you boot. It monitors your CPU, thermals, and battery then sends data to Microsoft every 15 minutes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough people believed it that Microsoft&amp;rsquo;s Scott Hanselman &lt;a href=&#34;https://x.com/shanselman/status/2083959057845727702&#34;&gt;replied personally&lt;/a&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Earlier this month, <a href="https://x.com/x1lly/status/2083229864862056532">a post on X</a> went viral by claiming Microsoft had quietly slipped a spy into Windows 11:</p>
<blockquote>
<p>MICROSOFT ADDED A NEW BACKGROUND SERVICE TO YOUR PC IN WINDOWS 11. It is called Windows Health and Optimized Experiences. It starts automatically every time you boot. It monitors your CPU, thermals, and battery then sends data to Microsoft every 15 minutes.</p>
</blockquote>
<p>Enough people believed it that Microsoft&rsquo;s Scott Hanselman <a href="https://x.com/shanselman/status/2083959057845727702">replied personally</a>:</p>
<blockquote>
<p>This was added in 2025 and is for Performance-diagnostic capture: When Windows detects slow or sluggish behavior, it can record targeted performance traces locally under <code>%SystemRoot%\Temp\DiagOutputDir\Whesvc</code> that can be filed with Feedback Hub. It&rsquo;s not laptop specific. SAYING THINGS IN ALL CAPS FOR DRAMA DOESN&rsquo;T MAKE THEM DRAMATIC</p>
</blockquote>
<p>He is right, and so are the fact-checks that followed on <a href="https://www.neowin.net/reports/what-is-the-viral-whesvc-service-and-should-you-disable-it/">Neowin</a>, <a href="https://www.pcworld.com/article/3206170/microsoft-denies-viral-rumor-of-windows-11-spying-on-you-every-15-minutes.html">PCWorld</a>, and <a href="https://www.windowslatest.com/2026/08/04/microsoft-denies-windows-11-is-spying-on-desktop-pcs-reveals-what-the-service-actually-does/">Windows Latest</a>. The service is not new, it is not recording your screen, and the traces stay on your disk unless you file them yourself.</p>
<p>But it appears to me that nobody has really looked at the binary, and that is quite unsatisfying for a reverse engineer.</p>
<p>So I opened it (well, mostly with <a href="https://dev-docs.binary.ninja/guide/mcp.html">Binary Ninja MCP</a>). It is a perfectly legitimate diagnostics service, and everything it ships does exactly what the name suggests. What surprised me is how it is built: <code>whesvc</code> runs a <strong>Lua interpreter as SYSTEM</strong>, with 84 compiled Lua scripts riding along, and the machinery underneath is considerably more powerful than the job seems to need.</p>
<p>Everything below is on Windows 11 Pro 26200, with service binaries <code>10.0.26100.8972</code>. The tools I wrote are in the <a href="https://github.com/xusheng6/whesvc-analysis">companion repo</a> if you wish to follow along on your own machine.</p>
<h2 id="where-do-i-start">Where Do I Start?</h2>
<p>The service short name is <code>whesvc</code>, and at first glance it is entirely unremarkable:</p>
<pre tabindex="0"><code>Name        : whesvc
DisplayName : Windows Health and Optimized Experiences
Description : Monitors the device for a better user experience
PathName    : C:\WINDOWS\system32\svchost.exe -k whesvc -p
StartName   : LocalSystem
StartMode   : Auto (delayed)
</code></pre><p>It is a <code>svchost</code> service, so the interesting part is the <code>ServiceDll</code>, which points at <code>C:\Windows\System32\whesvc.dll</code>. That is only 229 KB, and public symbols are available for it, which makes the job much easier.</p>
<p>The entry point is <code>HealthAndOptimizerService::StartWHEService</code>, and it is short enough to read in one screen:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span>HealthAndOptimizerService<span style="color:#f92672">::</span><span style="color:#a6e22e">StartWHEService</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> <span style="color:#a6e22e">RegGetValue</span>(...<span style="color:#960050;background-color:#1e0010">\</span>whesvc, <span style="color:#e6db74">&#34;WaitForDebugger&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> <span style="color:#a6e22e">SetProcessMitigationPolicy</span>(ProcessRedirectionTrustPolicy, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> <span style="color:#a6e22e">InitializeAssetLoader</span>()   <span style="color:#960050;background-color:#1e0010">→</span> <span style="color:#a6e22e">LoadLibraryW</span>(<span style="color:#f92672">%</span>systemroot<span style="color:#f92672">%</span><span style="color:#960050;background-color:#1e0010">\</span>system32<span style="color:#960050;background-color:#1e0010">\</span>whesvc_assets.dll)
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> WinDiag<span style="color:#f92672">::</span><span style="color:#a6e22e">Initialize</span>()     <span style="color:#960050;background-color:#1e0010">→</span> GetSystemDirectoryW <span style="color:#f92672">+</span> <span style="color:#960050;background-color:#1e0010">\</span>windiag.dll
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> OrchestratorSingleton<span style="color:#f92672">::</span><span style="color:#a6e22e">Initialize</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">├─</span> OrchestratorSingleton<span style="color:#f92672">::</span><span style="color:#a6e22e">RegisterOneSettingsConfigChangeWNF</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">└─</span> <span style="color:#a6e22e">StartWindiagModuleWithUri</span>(<span style="color:#e6db74">&#34;builtin://scenario/init&#34;</span>)
</span></span></code></pre></div><p>Three things caught my eye here. It loads two other DLLs I had never heard of, <code>whesvc_assets.dll</code> and <code>windiag.dll</code>. It registers for OneSettings configuration changes, so Microsoft can adjust its behavior remotely. And the last thing it does is start a <em>module</em> named by a URI, <code>builtin://scenario/init</code>.</p>
<p>That last one is unusual. Services do not normally have modules, and they certainly do not address them by URI. Something else is running the show here, and I will come back to what that module turns out to be.</p>
<p>In fact, <code>whesvc.dll</code> barely does any work at all. <code>WinDiag::Initialize</code> loads <code>windiag.dll</code> and resolves exactly three exports from it &ndash; <code>WinDiagnosticsRequest</code>, <code>WinDiagnosticsResume</code>, and <code>WinDiagnosticsFree</code>. That is the entire interface. So whatever this service really is, it lives in <code>windiag.dll</code>.</p>
<h2 id="a-lua-interpreter-in-system32">A Lua Interpreter in system32?</h2>
<p><code>windiag.dll</code> is 946 KB, and its strings answer the question immediately:</p>
<pre tabindex="0"><code>$LuaVersion: Lua 5.4.7  Copyright (C) 1994-2024 Lua.org, PUC-Rio $
</code></pre><p>I had not seen this before &ndash; Windows ships a <strong>Lua interpreter</strong> in <code>system32</code>, and a service runs it as SYSTEM.</p>
<p>To be fair, Lua is a sensible choice for this kind of job. It is tiny, it is fast, and embedding it is easy. That is exactly why games and embedded devices use it. It is still a little surprising to see Windows take on a new language dependency inside a system service.</p>
<p>Now, what about the other new DLL? <code>whesvc_assets.dll</code> turns out to be even stranger, because it contains no code at all. Its section table is just <code>.rdata</code> and <code>.rsrc</code>, there is no <code>.text</code>, and consequently there is no PDB for it either &ndash; there was nothing to compile. Everything is in the resources:</p>
<pre tabindex="0"><code>== TYPE LUALIBM ==   61 resources   (libraries)
== TYPE LUAMOD ==    23 resources   (scenarios)
== TYPE PROFILE ==    5 resources   (WPRP trace profile XML)
</code></pre><p><code>LUAMOD</code> and <code>LUALIBM</code> are the Lua. So there are 84 scripts in here, and their names read like a table of contents: <code>SCENARIO/HANG_TRACE</code>, <code>SCENARIO/SLOW_APP_LAUNCH</code>, <code>SCENARIO/NOISY_FAN</code>, <code>CORE/REG</code>, <code>CORE/SECURITY</code>, <code>MISC/ARTIFACT_MANAGER</code>.</p>
<h2 id="what-could-the-scripts-do">What Could the Scripts Do?</h2>
<p>When you find a scripting engine inside a program, the bridge between the script and the native world is the half worth looking at first. It sets the ceiling: a script can never do more than the bridge allows.</p>
<p>Here the bridge is a global table called <code>wdg</code>, and every <code>CORE/*</code> library is a thin wrapper that re-exports part of it. So I wrote a small pass over the bytecode that tracks which registers hold that table and collects every field read from it. That gives 79 native functions.</p>
<p>Sixty of them are grouped into twelve libraries:</p>
<table>
	<thead>
			<tr>
					<th>Library</th>
					<th>What it exposes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>core/reg</code></td>
					<td>Full registry read <strong>and write</strong>, all hives: <code>create_key</code>, <code>delete_key</code>, <code>delete_value</code>, <code>set_*</code>, <code>get_*</code>, <code>enum_*</code></td>
			</tr>
			<tr>
					<td><code>core/file</code></td>
					<td><code>read_data</code>, <code>write_data</code>, <code>copy</code>, <code>move</code>, <code>remove</code>, <code>mkdir</code>, <code>rmdir</code>, <code>enum</code>, and <code>grep_init</code>/<code>grep_next</code> for searching file contents</td>
			</tr>
			<tr>
					<td><code>core/security</code></td>
					<td><code>create_process</code>, <code>impersonate_process</code>, <code>revert_impersonation</code>, <code>token_info</code>, <code>code_integrity</code>, <code>process_protection</code></td>
			</tr>
			<tr>
					<td><code>core/etw</code></td>
					<td>Start and stop WPR traces, realtime sessions, provider and keyword control</td>
			</tr>
			<tr>
					<td><code>core/wmi</code></td>
					<td><code>wmi_query</code> <strong>and <code>wmi_method</code></strong> &ndash; <code>Win32_Process</code>, <code>Win32_Service</code>, <code>Win32_NTLogEvent</code>, <code>MSFT_MpComputerStatus</code>, <code>MSFT_MpPreference</code></td>
			</tr>
			<tr>
					<td><code>core/wnf</code></td>
					<td>Create, update, query, and delete WNF state names</td>
			</tr>
			<tr>
					<td><code>core/native</code></td>
					<td><code>invoke</code> &ndash; a general FFI, with typed buffers and DLL/file/registry/kernel/SCM handles</td>
			</tr>
			<tr>
					<td><code>core/power</code></td>
					<td>Battery, power schemes, thermal and fan sensors, EMI, processor info</td>
			</tr>
			<tr>
					<td><code>core/net</code></td>
					<td><code>http_req</code></td>
			</tr>
			<tr>
					<td><code>core/sym</code></td>
					<td>Symbol download, <code>sym_decode_stack</code></td>
			</tr>
			<tr>
					<td><code>core/ai</code></td>
					<td>A local language model &ndash; <code>Summarize</code>, <code>Rewrite</code>, <code>TextToTable</code></td>
			</tr>
			<tr>
					<td><code>core/utc</code>, <code>core/oset</code>, <code>core/cabinet</code></td>
					<td>DiagTrack scenario state, OneSettings config, <code>.cab</code> creation</td>
			</tr>
	</tbody>
</table>
<p>The remaining nineteen do not belong to any library. <code>core/global.lua</code> injects them directly into the environment of every script, so they are available without importing anything: <code>getpid</code>, <code>getcmd</code>, <code>getcwd</code>, <code>process_info</code>, <code>system_info</code>, <code>system_times</code>, <code>sessionid</code>, <code>thread_name</code>, <code>create_guid</code>, <code>sleep</code>, <code>event_write</code>, and a handful of timing primitives.</p>
<p>Registry write, arbitrary file I/O, process creation, WMI <em>method</em> invocation, and a general-purpose FFI. That is roughly the capability set of a systems administration language. It is quite a lot for a program whose job is to notice that your laptop is running hot.</p>
<p>The file access surprised me the most, because it has no restriction whatsoever. This is the whole of <code>write_data</code>, at line 9 of <code>core/file.lua</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">write_data</span>(path, data)
</span></span><span style="display:flex;"><span>    assert(io.open(path, <span style="color:#e6db74">&#34;wb&#34;</span>)):write(data)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span></code></pre></div><p>No prefix check, no allowlist, no canonicalization. And before you assume <code>io</code> here is some hardened Microsoft replacement, it is not &ndash; <code>windiag.pdb</code> exports <code>luaopen_io</code>, <code>luaopen_os</code>, and <code>luaopen_package</code> unmodified, so this is the stock PUC-Rio <code>liolib</code> calling <code>fopen</code>.</p>
<p>This is the part the viral post got backwards, so let me be clear. <strong>None of the shipped scripts do anything questionable with this.</strong> They read sensors and write JSON into their own directories, which is exactly what a diagnostics service should do. The engine underneath would simply let them do much more, and what keeps them in their lane is that Microsoft wrote them that way.</p>
<p>It is worth seeing what the sharper capabilities are actually used for, because the answer is mundane in every case.</p>
<p><strong>File I/O.</strong> <code>scenario/system_summary</code> writes its result to a temporary file, and <code>misc/artifact_manager</code> later publishes it into <code>%ProgramData%\Whesvc\</code> and enforces the retention limits:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">local</span> path <span style="color:#f92672">=</span> env.expand(<span style="color:#e6db74">&#34;%TEMP%</span><span style="color:#ae81ff">\\</span><span style="color:#e6db74">&#34;</span>) <span style="color:#f92672">..</span> name <span style="color:#f92672">..</span> <span style="color:#e6db74">&#34;_summary.json&#34;</span>
</span></span><span style="display:flex;"><span>file.write_data(path, json.stringify(summary))
</span></span></code></pre></div><p><strong>Registry.</strong> <code>scenario/perftrack_monitor</code> keeps its counters under whesvc&rsquo;s own key so they survive a restart. On my machine one of those entries looks like this:</p>
<pre tabindex="0"><code>HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\whesvc\scenarios\29783662:1
    id                a92c2390-587f-4ee7-af5b-d1e05402e98a
    number            29783662
    on_ac             1
    total_count       5
    bucket_ms         2
    last_update_time  134309573839635035
</code></pre><p>The only write that leaves whesvc&rsquo;s own keys is <code>misc/driver_info</code>, which can set <code>VerifyDrivers</code>, <code>VerifyDriverLevel</code>, <code>VerifyMode</code>, and <code>VerifierOptions</code> under <code>Session Manager\Memory Management</code>. That is Driver Verifier configuration, used by <code>memory_monitor</code> for pool tracking, and it is gated behind <code>WINDIAG_DRIVER_TELEMETRY_ENABLED</code>. It is easily the most invasive thing any of these scripts can do to a machine.</p>
<p><strong>Process creation.</strong> Exactly one script spawns a process through <code>security.create_process</code>, and it runs an in-box Windows tool:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">local</span> opts <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    cmd_line   <span style="color:#f92672">=</span> sysdir <span style="color:#f92672">..</span> <span style="color:#e6db74">&#34;/powercfg.exe /sleepstudy /output </span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">..</span> outfile <span style="color:#f92672">..</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>,
</span></span><span style="display:flex;"><span>    redirected <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    console    <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">local</span> proc <span style="color:#f92672">=</span> security.create_process(opts)
</span></span></code></pre></div><p>That is <code>misc/sleep_study</code> asking <code>powercfg</code> for a sleep study report and then reading its output. Interestingly, there is a second process-spawning path that does not use this binding at all: <code>core/etw</code> shells out to <code>wpr.exe -merge</code> through <code>io_popen</code>. Two mechanisms for the same job, in the same codebase.</p>
<h2 id="getting-the-scripts-out">Getting the Scripts Out</h2>
<p>Now I can go read them. Except the resources are not plain Lua, and this is probably why nobody had looked inside: running <code>strings</code> on <code>whesvc_assets.dll</code> shows nothing resembling a script.</p>
<p>Each resource starts with a small header:</p>
<pre tabindex="0"><code>0x00  u32  magic 0xC0E5510A
0x04  u16  header size (0x18)
0x06  u16  tag
0x08  u64  uncompressed size
0x10  u64  uncompressed size (again)
0x18  u32  compressed size
0x1C  ...  &#39;CK&#39; + raw DEFLATE
</code></pre><p>That <code>CK</code> is the giveaway: MSZIP, which is deflate with a two-byte signature in front, so the unpacker is four lines of Python. No encryption, and no hash or MAC anywhere in the header either, which will matter later.</p>
<p>Underneath the compression is Lua 5.4 bytecode. And here is the part I did not expect:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Modules carrying debug info</td>
					<td>105 / 105</td>
			</tr>
			<tr>
					<td>Named local variables</td>
					<td>6,069</td>
			</tr>
			<tr>
					<td>Named upvalues</td>
					<td>2,149</td>
			</tr>
			<tr>
					<td>Line-number entries</td>
					<td>47,688</td>
			</tr>
			<tr>
					<td>Distinct source paths</td>
					<td>86 (<code>@lualib\core\net.lua</code>, <code>@luamod\scenario\ecp.lua</code>, &hellip;)</td>
			</tr>
	</tbody>
</table>
<p><strong>Nothing was stripped.</strong> The header sentinels are the stock values (<code>LUAC_INT = 0x5678</code>, <code>LUAC_NUM = 370.5</code>), and the opcode table is unmodified upstream ordering. I wrote a disassembler against the plain Lua 5.4 opcode list and it decoded every module correctly on the first attempt.</p>
<p>Why keep the debug info? I suspect it is deliberate. The engine formats its errors as <code>file(line)!address</code>, and the WIL telemetry logs failures with call context. If you strip the symbols, you blind yourself to your own field failures. There are even <code>lua_debugger_enabled</code> and <code>vscode_debugger_enabled</code> hooks sitting in the sandbox script.</p>
<h2 id="so-what-is-keeping-this-in-check">So What Is Keeping This In Check?</h2>
<p>Two things are supposed to, and both are worth a look.</p>
<p>The first is a sandbox. It lives in a <code>LUASB</code> resource plus <code>core/global.lua</code>, and before any scenario script runs it deletes a list of globals from the environment: <code>debug</code>, <code>require</code>, <code>os</code>, <code>package</code>, <code>loadfile</code>, <code>dofile</code>, <code>load</code>, <code>getmetatable</code>, <code>setmetatable</code>, and <code>collectgarbage</code>.</p>
<p>That accomplishes one thing, and it is the important one. With <code>load</code>, <code>loadfile</code>, <code>dofile</code>, and <code>require</code> all gone, a scenario script <strong>cannot load code that did not ship in the signed DLL</strong>. There is a path that loads Lua from the filesystem through a <code>WINDIAG_LUALIB_PATH</code> environment variable, but it is gated on the host process being <code>windiag.exe</code>, a separate developer tool, so that door is closed too.</p>
<p>Beyond that it does not do much. <code>io</code> was never on the block list, and <code>io.popen</code> is deliberately re-exported as <code>io_popen</code> &ndash; <code>core/etw.lua</code> uses it to shell out to <code>wpr.exe -merge</code> &ndash; so removing <code>os</code> does not remove the ability to run commands. The core libraries also grab what they need before the strip happens, and <code>core/global</code> keeps an upvalue named, with admirable honesty, <code>sandbox_stripped_refs</code>. It is namespace hygiene rather than confinement.</p>
<p>The second thing is the signature on <code>whesvc_assets.dll</code>, since that is the DLL holding the code that actually runs. Except there is no check. Neither DLL imports <code>WinVerifyTrust</code>, the process never sets <code>MicrosoftSignedOnly</code>, and there is no hash or MAC in the container header either. The file is validly signed, and nothing looks at that signature when it is loaded.</p>
<p>But that does not really matter, because of who is allowed to write the file:</p>
<pre tabindex="0"><code>C:\Windows\System32\whesvc_assets.dll
    NT SERVICE\TrustedInstaller:(F)
    BUILTIN\Administrators:(RX)
    NT AUTHORITY\SYSTEM:(RX)
    BUILTIN\Users:(RX)
</code></pre><p>TrustedInstaller has full control, and everybody else &ndash; including SYSTEM and Administrators &ndash; gets read and execute only. Anyone who can replace this file is already an administrator, and administrator to SYSTEM is not a boundary Windows defends anyway. <strong>So this is fine.</strong></p>
<p>So what actually keeps this engine in bounds is not the sandbox and not the signature. It is a file permission, plus the set of scripts Microsoft chose to ship. That is enough in practice, but it is worth knowing that is what the safety rests on.</p>
<h2 id="so-what-do-the-scripts-actually-do">So What Do the Scripts Actually Do?</h2>
<p>Now I can finally answer the question I left hanging earlier: what is <code>builtin://scenario/init</code>?</p>
<p>The <code>builtin://</code> scheme is handled inside <code>windiag.dll</code>, and it simply names a resource. <code>scenario/init</code> becomes the <code>LUAMOD</code> resource called <code>SCENARIO/INIT</code>, which is 536 bytes on disk and unpacks to 777 bytes of bytecode. The debug info calls it <code>@luamod\scenario\init.lua</code>, and it is 48 instructions in total, so I can show the whole thing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span>disable_global_variables()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">local</span> opts <span style="color:#f92672">=</span> module_options{
</span></span><span style="display:flex;"><span>    disabled_modules <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">local</span> workflow  <span style="color:#f92672">=</span> reflib(<span style="color:#e6db74">&#34;misc/module_workflow&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">local</span> telemetry <span style="color:#f92672">=</span> reflib(<span style="color:#e6db74">&#34;misc/utc_telemetry&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>telemetry.event_write(<span style="color:#e6db74">&#34;FeatureStates&#34;</span>, feature_states())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>workflow.start_feature_modules({
</span></span><span style="display:flex;"><span>    { module_uri <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/housekeeper&#34;</span> },
</span></span><span style="display:flex;"><span>    { module_uri <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/rtsmon&#34;</span> },
</span></span><span style="display:flex;"><span>    { module_uri <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/devicehot&#34;</span> },
</span></span><span style="display:flex;"><span>    { feature_name    <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;ECP&#34;</span>,
</span></span><span style="display:flex;"><span>      module_uri      <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/ecp&#34;</span>,
</span></span><span style="display:flex;"><span>      on_battery_only <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span> },
</span></span><span style="display:flex;"><span>    { feature_name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;MemoryLeakDetection&#34;</span>,
</span></span><span style="display:flex;"><span>      module_uri   <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/memory_handle_leak&#34;</span> },
</span></span><span style="display:flex;"><span>    { module_uri <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;builtin://scenario/noisy_fan&#34;</span> },
</span></span><span style="display:flex;"><span>}, <span style="color:#e6db74">&#34;init&#34;</span>, opts.disabled_modules)
</span></span></code></pre></div><p>That is the entire entry point. It locks down the environment, pulls in two helper libraries, emits one telemetry event recording which feature flags are on, and hands six modules to the workflow starter. No logic of its own beyond that.</p>
<p>From there the structure is easy to follow. Of the 23 scenario modules, 21 start by default. Those six above are the first wave, and one of them, <code>rtsmon</code>, opens a realtime ETW session and starts fourteen more. Thirteen run unconditionally, six sit behind Windows feature flags that Microsoft controls server-side, and one needs a battery to be present.</p>
<p>Roughly, they fall into four groups.</p>
<p><strong>Responsiveness.</strong> <code>hang_trace</code> waits on <code>WerHangTraceSignal</code> and captures a trace when an application stops responding. <code>input_delays</code> and <code>output_delays</code> watch input latency and display glitches. <code>slow_app_launch</code> fires when an app takes too long to start. <code>svc_start_stop</code> flags services stuck in a pending state. And <code>hotkey_trace</code> is the interesting one, because it captures a full trace when <em>you</em> press a hotkey. That is the manual reproduction path, for when you can trigger the problem yourself.</p>
<p><strong>Power and thermals.</strong> <code>ecp</code> decides between <code>LowerPower</code>, <code>NoChange</code>, and <code>HigherPower</code>, and then applies the result. <code>excessive_power_drain</code> looks for sustained drain and badly behaved background apps. <code>fast_battery_drain_improvement</code> is a small state machine that engages QoS separation and CPU frequency throttling when drain stays high. <code>sleep_offenders</code> works out what kept the machine awake, and shells out to <code>powercfg /sleepstudy</code> to do it.</p>
<p><strong>Memory.</strong> <code>memory_monitor</code> takes heap snapshots. <code>memory_handle_leak</code> does real leak detection: it tracks per-process memory and handle counts across intervals, and uses consecutive-increase counts together with a linear regression slope to tell a leak apart from normal growth. That is more statistics than I expected to find in a shipped diagnostic script.</p>
<p><strong>Aggregation.</strong> <code>system_summary</code> is where the famous 15 minutes comes from. It reads <code>WINDIAG_SYSTEM_SUMMARY_FLUSH_SEC</code>, which defaults to <code>900</code>. The artifacts on my own machine agree:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;start_time&#34;</span>:<span style="color:#e6db74">&#34;2026-08-11 21:02:16.727114&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;end_time&#34;</span>:<span style="color:#e6db74">&#34;2026-08-11 21:17:56.930144&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;elapsed_time&#34;</span>:<span style="color:#e6db74">&#34;00:15:40.203030&#34;</span>, <span style="color:#960050;background-color:#1e0010">...</span>}
</span></span></code></pre></div><p>So the 15 minutes is a real number. The question is what happens when the timer fires, and the answer is that it writes a JSON file into <code>C:\ProgramData\Whesvc\</code> and emits one small ETW telemetry event. Here is an entire crash record, from my machine:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;app_name&#34;</span>:<span style="color:#e6db74">&#34;binaryninja.exe&#34;</span>,<span style="color:#f92672">&#34;mod_name&#34;</span>:<span style="color:#e6db74">&#34;TTDReplay.dll&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;exception_code&#34;</span>:<span style="color:#e6db74">&#34;0xC0000005&#34;</span>,<span style="color:#f92672">&#34;exception_offset&#34;</span>:<span style="color:#e6db74">&#34;0x994EA&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;app_version&#34;</span>:<span style="color:#e6db74">&#34;5.4.10219.0&#34;</span>,<span style="color:#f92672">&#34;count&#34;</span>:<span style="color:#ae81ff">1</span>}
</span></span></code></pre></div><p>That is the payload everybody was worried about. It is a crash summary, and in this case it is my own debugger crashing on me during development.</p>
<p>The network side is similarly undramatic. Across all 84 scripts the only URL is <code>symweb.azurefd.net</code>, which is Microsoft&rsquo;s public symbol server, and it is gated behind a <code>WINDIAG_SYM_CLOUD_TOKEN</code> environment variable that is not set on a retail machine. No scenario module makes an HTTP request at all. <code>windiag.dll</code> imports three WinINet functions and zero socket functions. The running service holds no TCP or UDP endpoints.</p>
<p>Two controls deserve more credit than the discussion gave Microsoft. Heavy trace capture checks <code>AllowTelemetry</code> and requires it to be 3, meaning full or optional diagnostic data, and returns false when the value cannot be read at all &ndash; so it fails closed. And auto-escalation, the one mechanism that can hand data to the telemetry pipeline without you filing feedback, is off by default in all ten automatic scenarios. It is on in exactly one: <code>hotkey_trace</code>, the one you trigger yourself.</p>
<h2 id="how-does-it-know-the-fan-is-loud">How Does It Know the Fan Is Loud?</h2>
<p><code>SCENARIO/NOISY_FAN</code> was actually the first module I opened, because I could not immediately think of a non-alarming way to detect a noisy fan. I have <a href="/posts/reversing/reverse_engineering_and_fixing_a_fan/main/">taken a fan apart on this blog before</a>, so I felt some obligation to check this one.</p>
<p>Happily, it is not listening to anything. I went through the full import tables of both binaries, and there are no audio APIs anywhere.</p>
<p>What it does instead is read RPM. The platform&rsquo;s thermal stack exposes a fan descriptor that sorts RPM into four zones, each with a maximum RPM and an accumulated time spent in that zone:</p>
<pre tabindex="0"><code>FanNoiseZoneLow  FanNoiseZoneMedium  FanNoiseZoneMediumHigh  FanNoiseZoneHigh
</code></pre><p>The module polls on an interval, and then does exactly one comparison:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> time_per_impact[FanNoiseZoneHigh].seconds_in_zone <span style="color:#f92672">&gt;</span> high_impact_sec_threshold <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    device_health.signal_problem{ problem_area <span style="color:#f92672">=</span> Power, source <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;NoisyFan&#34;</span> }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span></code></pre></div><p>So &ldquo;noisy&rdquo; means the fan has spent more than N accumulated seconds above an RPM threshold that the OEM declared. It is a proxy for noise rather than a measurement of it, which is a sensible way to do this without a microphone. And if your machine has no fan telemetry at all, the module exits immediately with <code>Fan sensors not supported on device</code>, which is what happens on most desktops.</p>
<h2 id="there-was-already-a-cve">There Was Already a CVE</h2>
<p>While writing this up, I found that this service has already produced a real vulnerability: <a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-59241">CVE-2025-59241</a>, a local elevation of privilege with CVSS 7.8, fixed in builds <code>26100.6899</code> and <code>26200.6899</code> back in October 2025. Microsoft classified it as improper link resolution before file access. (<a href="https://www.tenable.com/cve/CVE-2025-59241">Tenable</a> has a more readable summary than the MSRC page.)</p>
<p>Which rather punctures my earlier line about nobody having opened this binary. Somebody clearly had, and they walked away with a CVE while I walked away with a blog post. I will console myself with the belief that I am at least the first to write the thing up in this much detail.</p>
<p>Microsoft published one sentence about it, and I could not find a pre-patch binary on my machine to diff against, so what follows is the shape of the problem rather than a confirmed account of the exact reported case.</p>
<p>The ingredients are still visible. The service creates <code>C:\ProgramData\Whesvc\&lt;artifact_type&gt;\</code> if it does not exist and writes a file inside it, as SYSTEM, with no path validation. The type names are hardcoded and predictable, and <code>system_summary</code> guarantees a write every fifteen minutes. Meanwhile, look at who can write into that tree:</p>
<pre tabindex="0"><code>icacls C:\ProgramData\Whesvc
    BUILTIN\Users:(I)(CI)(WD,AD,WEA,WA)      &lt;- create files and directories
    CREATOR OWNER:(I)(OI)(CI)(IO)(F)
</code></pre><p>Every entry is marked <code>(I)</code>, meaning inherited &ndash; the service never sets its own DACL, it just accepts whatever <code>C:\ProgramData</code> hands down. Once an unprivileged user can create a directory a SYSTEM process will later write into, the classic move is to make it a junction pointing somewhere else. The retention logic, which deletes old artifacts, offers the same trick in reverse.</p>
<p>The fix is visible in the current binaries, and I like it. Remember that <code>SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy, 1)</code> at the very top of <code>StartWHEService</code>? That tells the kernel to refuse to follow junctions and symlinks created by less-trusted principals. It kills the entire class of bug rather than patching one path, and it is set declaratively in the svchost group configuration as well. There is a second and narrower guard in <code>core/file.lua</code>, where the recursive directory walker refuses to descend into reparse points:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> file.is_directory(entry) <span style="color:#f92672">and</span> <span style="color:#f92672">not</span> file.is_reparse_point(entry) <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    table.insert(q, entry.path)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span></code></pre></div><p>This is the only call to <code>is_reparse_point</code> in all 84 modules, which tells you plainly what it was added for.</p>
<p>The artifact directory still inherits its permissive DACL rather than setting its own, so the defense rests on that one kernel mitigation. It is a good mitigation and it does the job, but an explicit DACL would have made it two layers instead of one.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>The viral claim was wrong on essentially every count. The service is not new, it does not upload your traces, and it is not recording anything. The 15 minutes is a real number attached to a thoroughly boring event, and Hanselman&rsquo;s description matches the code right down to the directory path he quoted.</p>
<p>What is worth talking about is the machinery rather than the behavior. The scripts read sensors and write JSON files, while the engine underneath would let them write any file, set any registry key, spawn processes, and call arbitrary native functions through an FFI, all as SYSTEM. None of that is being misused &ndash; I went looking for something dodgy and did not find it. It is simply more capability than the task calls for, and CVE-2025-59241 is a fair illustration of what that can cost. Windows Health and Optimized Experiences is not spyware; it is a legitimate diagnostics service built on a general-purpose scripting runtime, and that is a different thing to threat model than a program which reads a fan sensor.</p>
<hr>
<p>The tooling I wrote for this &ndash; the PE resource extractor, the MSZIP container unpacker, and the Lua 5.4 disassembler &ndash; is on <a href="https://github.com/xusheng6/whesvc-analysis">GitHub</a>, together with instructions for unpacking the scripts from your own copy of Windows.</p>
]]></content>
        </item>
        
        <item>
            <title>How Sogou Browser Rewrites Search Affiliate Codes</title>
            <link>https://xusheng.dev/posts/reversing/sogou_affiliate_rewrite/main/</link>
            <pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/sogou_affiliate_rewrite/main/</guid>
            <description>&lt;p&gt;I recently poked at &lt;a href=&#34;https://ie.sogou.com/&#34;&gt;Sogou Browser&lt;/a&gt; (version &lt;code&gt;13.9.6121.400&lt;/code&gt;) out of curiosity and found that it rewrites hundreds of affiliate codes in Baidu or Sogou search URLs. Specifically, there is a list of search affiliate codes hardcoded in the binary, and when one is seen, it is replaced. The search engine only ever sees the substituted code — the original never reaches it.&lt;/p&gt;
&lt;h2 id=&#34;affiliate-codes-briefly&#34;&gt;Affiliate codes, briefly&lt;/h2&gt;
&lt;p&gt;If you have looked closely at a Baidu search URL you have seen something like this:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I recently poked at <a href="https://ie.sogou.com/">Sogou Browser</a> (version <code>13.9.6121.400</code>) out of curiosity and found that it rewrites hundreds of affiliate codes in Baidu or Sogou search URLs. Specifically, there is a list of search affiliate codes hardcoded in the binary, and when one is seen, it is replaced. The search engine only ever sees the substituted code — the original never reaches it.</p>
<h2 id="affiliate-codes-briefly">Affiliate codes, briefly</h2>
<p>If you have looked closely at a Baidu search URL you have seen something like this:</p>
<pre tabindex="0"><code>https://www.baidu.com/s?wd=something&amp;tn=47018152_13_dg&amp;ie=utf-8
</code></pre><p>That <code>tn=</code> is a Baidu Union account identifier — it tells Baidu which partner the traffic came through. This is old and well known; Firefox historically shipped <code>tn=suvion_dg</code>, Maxthon used <code>tn=myie2dg</code>. Software that ships a Baidu search box generally has its own code.</p>
<p>Sogou has an equivalent. Sogou search URLs carry a <code>p=</code> parameter shaped like <code>sogou-site-0e57098d0318a954</code> — a four-letter channel type and sixteen hex digits.</p>
<h2 id="replacing-the-codes">Replacing the codes</h2>
<p>When a Sogou Browser user navigates to a Baidu search carrying an affiliate code, this is what the browser actually requests:</p>
<pre tabindex="0"><code>in   https://www.baidu.com/s?wd=test&amp;tn=47018152_13_dg&amp;ie=utf-8
out  https://www.baidu.com/s?wd=test&amp;tn=98010089_dg&amp;ie=utf-8&amp;ch=19
</code></pre><p>And the same on Sogou, with that engine&rsquo;s own <code>p=</code> scheme:</p>
<pre tabindex="0"><code>in   https://www.sogou.com/web?query=x&amp;p=sogou-site-0e57098d0318a954&amp;ie=utf8
out  https://www.sogou.com/web?query=x&amp;p=sogou-addr-cc9657884708170e&amp;ie=utf8
</code></pre><p>The code is swapped and the rest of the URL is left alone. On the Baidu side a <code>ch=19</code> parameter is set as well, overwriting any existing <code>ch</code> value rather than duplicating it.</p>
<p>Two conditions have to hold for it to fire at all. The URL must <em>begin</em> with <code>www.baidu.com</code> or <code>sogou.com</code> — a Sogou code carried by some other site, or a Baidu URL nested in a redirect wrapper, is ignored. And the code has to appear on a list built into the browser; anything else is left untouched.</p>
<h2 id="the-list-of-codes">The list of codes</h2>
<p>The lists live in a plugin called <code>QBSafe.dll</code>, loaded at browser startup, which receives every navigation URL.</p>
<p>For Sogou URLs there are three tables of strings in <code>.data</code>, holding 128, 219 and 246 entries — 593 in total, every one of the form <code>sogou-&lt;four letters&gt;-&lt;sixteen hex&gt;</code>, and every one distinct. Each table has a single replacement, and the tables are tried in order with the first match winning:</p>
<table>
	<thead>
			<tr>
					<th>entries</th>
					<th>replaced with</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>128</td>
					<td><code>sogou-clse-f507783927f2ec27</code></td>
			</tr>
			<tr>
					<td>219</td>
					<td><code>sogou-clse-60a70bb05b08d6cd</code></td>
			</tr>
			<tr>
					<td>246</td>
					<td><code>sogou-addr-cc9657884708170e</code></td>
			</tr>
	</tbody>
</table>
<p>For Baidu URLs there are eleven codes built directly on the stack, all mapping to one replacement:</p>
<pre tabindex="0"><code>tn=93912046_hao_pg   tn=68082196_oem_dg   tn=18029102_oem_dg
tn=39042058_2_oem_dg tn=02049043_35_pg    tn=47018152_13_dg
tn=02049043_32_pg    tn=47018152_8_dg     tn=02049043_50_pg
tn=93139410_hao_pg   tn=02049043_30_pg
                  ↓
            tn=98010089_dg     (plus ch=19)
</code></pre><h2 id="mitm-verification">MITM verification</h2>
<p>To confirm my finding, I set up mitmproxy in front of the browser and observed the behavior:</p>
<table>
	<thead>
			<tr>
					<th>navigated to</th>
					<th>what left the machine</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>baidu <code>tn=02049043_35_pg</code></td>
					<td><code>tn=98010089_dg …&amp;ch=19</code></td>
			</tr>
			<tr>
					<td>baidu <code>tn=00000000_xx_yy</code></td>
					<td>unchanged</td>
			</tr>
			<tr>
					<td>sogou <code>p=sogou-site-0e57098d0318a954</code></td>
					<td><code>p=sogou-addr-cc9657884708170e</code></td>
			</tr>
			<tr>
					<td>sogou <code>p=sogou-site-ffffffffffffffff</code></td>
					<td>unchanged</td>
			</tr>
			<tr>
					<td><code>example.com/?p=sogou-site-0e57098d0318a954&amp;x=1</code></td>
					<td>unchanged</td>
			</tr>
	</tbody>
</table>
<p>You might be curious why Sogou is doing this. There are many possible reasons, and I will leave that to interested readers.</p>
]]></content>
        </item>
        
        <item>
            <title>Freeing Dev Hunter from Excel 2000</title>
            <link>https://xusheng.dev/posts/reversing/dev_hunter/readme/</link>
            <pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/dev_hunter/readme/</guid>
            <description>&lt;p&gt;Dev Hunter is a small game hidden inside Microsoft Excel 2000. It was one of the few pastimes I had during the dull computer classes I sat through in high school, and, like all adults who grow nostalgic for the things they had when they were young, I eventually went looking for it again.&lt;/p&gt;
&lt;p&gt;This post is the whole story of chasing it down: first getting the game to run on its own outside of Excel, and then — years later — freeing it completely, into a standalone program that carries nothing of Excel with it at all.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dev Hunter is a small game hidden inside Microsoft Excel 2000. It was one of the few pastimes I had during the dull computer classes I sat through in high school, and, like all adults who grow nostalgic for the things they had when they were young, I eventually went looking for it again.</p>
<p>This post is the whole story of chasing it down: first getting the game to run on its own outside of Excel, and then — years later — freeing it completely, into a standalone program that carries nothing of Excel with it at all.</p>
<p><img src="../imgs/gameplay.gif" alt="Dev Hunter running as a standalone program"></p>
<h2 id="what-dev-hunter-is">What Dev Hunter is</h2>
<p>To trigger the Easter egg in Excel 2000, you navigate to the cell <code>WC2000</code> and press the little Office logo. Up pops a 2D driving game: you steer a car down a highway and shoot the cars ahead of you. It is called <em>Dev Hunter</em> because the road is painted with the names of the Excel developers — you are, the game informs you, hunting them down. Between the names it scrolls taunts at you like <em>&ldquo;Pivot! Pivot! Pivot!&rdquo;</em> and <em>&ldquo;You will respect the rectangles.&rdquo;</em></p>
<h2 id="getting-it-out-of-excel">Getting it out of Excel</h2>
<p>The first step, back in 2019, was just to get it running at all. Excel 2000 does not install on a modern machine, so I started inside a Windows XP virtual machine, and after enough fiddling, Dev Hunter popped up. I played it for a few minutes and found it… not that interesting anymore. Maybe it was only ever interesting because I had nothing else to do. But by then I had become a reverse engineer, and a more interesting question had already taken hold: how is this thing actually implemented?</p>
<p>To get to the game, I exported the spreadsheet to a web page, and that page references the CLSID <code>0002E510-0000-0000-C000-000000000046</code>, which points at <code>MSOWC.DLL</code> — the Office Web Components control. I loaded that DLL into Binary Ninja, searched for the string &ldquo;Dev Hunter,&rdquo; and landed straight in the function that holds the game logic. Its parent runs the familiar <code>PeekMessage</code> / <code>TranslateMessage</code> / <code>DispatchMessage</code> loop; its grandparent is the part that decides whether to trigger the egg at all, by checking that you are sitting on row <code>0x7cf</code> (1999) and column <code>0x2bd</code> (WC):</p>
<p><img src="../imgs/wc2000-check.png" alt="The check that fires the Easter egg: column 0x2bd (WC) and row 0x7cf (1999)"></p>
<p>I could have stopped there, but I wanted to run the game on its own, without the entire bulk of Excel behind it. The message-loop function turned out to be conveniently self-contained. It is <code>thiscall</code>, with <code>ecx</code> pointing at a large buffer that is really a C++ object — and the game does not depend on anything being in that buffer beforehand. So I could simply allocate <code>0x100000</code> bytes, zero them, and hand that over as <code>ecx</code>, along with three stack arguments: a handle to the DLL, a zero, and a magic <code>0x7fa87860</code> that turns out to control the car colors. The whole loader is only a few dozen lines of assembly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nasm" data-lang="nasm"><span style="display:flex;"><span><span style="color:#75715e">; PAGE_READWRITE</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0x4</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">; MEM_COMMIT</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0x1000</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">; size</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0x100000</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">call</span> [VA(VirtualAlloc)]
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mov</span>  edx, eax
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">; zero the allocated page</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mov</span>  edi, eax
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">xor</span>  eax, eax
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mov</span>  ecx, <span style="color:#ae81ff">0x40000</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">stosd</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mov</span>  ecx, edx          <span style="color:#75715e">; hand the buffer over as `this`</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> VA(Dll)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">call</span> [VA(LoadLibraryA)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0x7fa87860</span>        <span style="color:#75715e">; magic constant — the player car&#39;s color</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">push</span> eax
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">add</span>  eax, <span style="color:#ae81ff">0x10c946</span>     <span style="color:#75715e">; func2 = DLL base + 0x10c946</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">call</span> eax
</span></span></code></pre></div><p>Compiled to a tiny <code>loader.exe</code> — and, with a couple of patches plus the wonderful <a href="https://github.com/narzoul/DDrawCompat">DDrawCompat</a> shim to make its ancient DirectDraw calls behave on modern Windows, the game ran outside of Excel. Here it is running standalone, straight from the loader:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/B2jlbsmL2fQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>I wrote that adventure up as an <a href="https://github.com/xusheng6/excel2000-devhunter-standalone/blob/main/docs/pagedout6-devhunter.pdf">article in <em>Paged Out!</em> #6</a>, published in March 2025. One detail I only chased down while writing it: the developer names are stored XOR-encrypted with the byte <code>0x52</code>, and, of all things, in CBC mode — you can read the <a href="https://pastebin.com/81T9VzuB">decrypted roll of names</a> that scrolls down the road. The loader itself lives at <a href="https://github.com/jeffli678/excel2000-devhunter-loader">github.com/jeffli678/excel2000-devhunter-loader</a>.</p>
<h2 id="freeing-it-completely">Freeing it completely</h2>
<p>The loader was a good trick, but it still needs <code>MSOWC.DLL</code>. And that DLL, with its ancient DirectDraw, only gets harder to run with every passing year. What I really wanted was to rewrite the game completely: to reimplement it as a small standalone program with no external dependencies.</p>
<p>This time I did not write it myself. I had an AI agent do the entire job: reverse engineer <code>MSOWC.DLL</code> and translate the game into a standalone C++ program, while I steered.</p>
<p>Working from Binary Ninja&rsquo;s decompilation, the AI rebuilt the game piece by piece — the recovered 256-color palette, the decrypted developer text, the floating-point RNG, the perspective projection, and, crucially, the DLL&rsquo;s real <code>render</code> and rasterizer routines, all translated into C. It built, and it ran. And it looked badly wrong:</p>
<p><img src="../imgs/before.png" alt="The first standalone build, before the debugger"></p>
<p>The names spill off the tarmac, the road is at the wrong scale, and the cars are reduced to blocks. The integer and pointer code had come across faithfully, but the geometry runs entirely on the x87 FPU stack, and it is not decompiled perfectly. Each little error was invisible in the listing, and no amount of re-reading it was going to fix them.</p>
<p>At this point I stepped in — I told it to run the original under Binary Ninja&rsquo;s debugger and compare against ground truth.</p>
<p>The AI scripted the headless debugger to launch the real game, break right before the DirectDraw <code>Unlock</code> — the moment the finished frame exists in memory but is not yet on screen — and read out the live 8-bit framebuffer, using the pixel pointer at <code>*(this+0x34)</code> and the row pitch at <code>*(this+0x20)</code>, together with the entire <code>~0x9a96c</code>-byte game object. Then it diffed its own rendering against those real pixels, frame by frame, and checked the physics by comparing the game object before and after a single tick.</p>
<p>That changed everything. Every vague &ldquo;it looks off&rdquo; became a specific, usually one-line fix: a projection edge the decompiler had turned into a running total (which sheared the road); a floating-point value dropped off the FPU stack, so the off-road crash check was reading the odometer instead of the car&rsquo;s speed; a texture-row mask that is really <code>and al, 0xe0</code> — a low-byte-only mask that keeps 32 rows — rather than the full-width <code>&amp; 0xe0</code> the decompiler implied, which had crushed the letters. One by one, the render converged on the original:</p>
<p><img src="../imgs/after.png" alt="The debugger-verified port, byte-identical to the original"></p>
<p>Getting to a clean comparison took two more tricks. The game&rsquo;s one-time setup (terrain, textures, and a 3D letter atlas built with GDI) is deterministic, seeded from zero, so rather than re-derive that gnarly initialization the port simply loads a snapshot of its exact output and relocates the pointers inside it. And because the game derives its frame timing from the wall clock, headless runs were not even reproducible until I had it wire in a fixed virtual clock — only then could a SHA-256 of the frame dumps prove that the two are truly the same.</p>
<p>The source, build instructions, and a playable Windows release are on GitHub: <a href="https://github.com/xusheng6/excel2000-devhunter-standalone">github.com/xusheng6/excel2000-devhunter-standalone</a>.</p>
<h2 id="what-i-take-away-from-it">What I take away from it</h2>
<p>The lesson I take away is about the limits of static reverse engineering. Even with a capable AI agent driving it, reading the disassembly got the port perhaps 90% of the way, and then stalled — the remaining errors simply were not visible in the code it was reading. Debugging is the right way to close the gap.</p>
<p>And at the end of it, Dev Hunter is out. A little game that spent twenty-five years locked inside an office suite that barely runs anymore is now a few hundred kilobytes you can clone, compile, and play in a window. I doubt the developers who hid their names in that road ever expected someone to come along, decades later, and rewrite the whole thing.</p>
]]></content>
        </item>
        
        <item>
            <title>What is LG Monitor App Manager actually doing?</title>
            <link>https://xusheng.dev/posts/reversing/lg_monitor_app_manager/readme/</link>
            <pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/lg_monitor_app_manager/readme/</guid>
            <description>&lt;p&gt;I recently bought an LG 34-inch UltraWide monitor. The screen itself works great — no complaints there. But a little while after setting it up, I started to notice this &lt;code&gt;LGMonitorAppManager&lt;/code&gt; thing on my machine:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/1.png&#34; alt=&#34;LG Monitor App Installer&#34;&gt;&lt;/p&gt;
&lt;p&gt;It showed up on its own — I never went to LG&amp;rsquo;s website and downloaded anything. It just appeared one day, offering to install &amp;ldquo;Dual Controller&amp;rdquo; and a few other LG utilities. I did not think about it too much at the time; I clicked the little &lt;code&gt;x&lt;/code&gt; and moved on with my life.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I recently bought an LG 34-inch UltraWide monitor. The screen itself works great — no complaints there. But a little while after setting it up, I started to notice this <code>LGMonitorAppManager</code> thing on my machine:</p>
<p><img src="../imgs/1.png" alt="LG Monitor App Installer"></p>
<p>It showed up on its own — I never went to LG&rsquo;s website and downloaded anything. It just appeared one day, offering to install &ldquo;Dual Controller&rdquo; and a few other LG utilities. I did not think about it too much at the time; I clicked the little <code>x</code> and moved on with my life.</p>
<p>Then, more recently, I came across a few X posts reporting the exact same thing, for example <a href="https://x.com/wexosu/status/2078183359335874940">this one</a>, and even a <a href="https://www.youtube.com/watch?v=Q9uefFYe6bM">Gamers Nexus video</a> digging into how LG ships this software through Windows Update. The consensus was some flavor of &ldquo;LG is installing bloatware/adware on your PC without asking.&rdquo; That matches my experience, and it is a fair complaint.</p>
<p>But none of the coverage I saw actually reverse engineered the binary to see what it <em>does</em>. That is the part I care about, so I pulled the files apart. This post is about that: which packer it uses, how to unpack it, how to recover the strings, and what the program actually does once you can read it.</p>
<h2 id="the-files">The files</h2>
<p>The program lives in a folder called <code>LGMonitorAppManager</code>. The interesting pieces are:</p>
<pre tabindex="0"><code>LGMonitorAppManager.exe            (.NET, WPF)        &lt;- the brain
Executables/EDIDMgr.dll            (native)           &lt;- reads monitor EDID
Executables/DPIHandler.dll         (native)
Executables/LGErrorHandler.dll     (native)
Executables/TracerLib.dll          (native, MFC)      &lt;- logging
Executables/LGMonitorAppSilentInstaller.exe  (native) &lt;- the elevated installer
Executables/LGMonitorAutoStartupApp.exe      (native) &lt;- persistence
Ionic.Zip.dll  Newtonsoft.Json.dll                    &lt;- third party
</code></pre><p>The first thing I checked was the Authenticode signatures. Everything native is validly signed by <code>LG Electronics Inc.</code>, so this is genuinely LG&rsquo;s software and not some third party impersonating them. The one exception is interesting: the main <code>LGMonitorAppManager.exe</code> — the .NET assembly that actually orchestrates everything — is <strong>not signed</strong>. Keep that in mind for later.</p>
<p>The native DLLs turn out to be the boring part. Their imports contain no networking APIs at all — no WinHTTP, no WinINet, no sockets. <code>EDIDMgr.dll</code> reads the connected monitor&rsquo;s EDID (so LG can tell which of its monitors you have plugged in), <code>DPIHandler</code> does DPI stuff, <code>LGMonitorAutoStartupApp.exe</code> drops a shortcut into the Startup folder, and <code>LGMonitorAppSilentInstaller.exe</code> runs InstallShield setups silently. All the actual logic — the network calls, the decision making, the installs — lives in the unsigned .NET brain. So that is where I spent my time.</p>
<h2 id="unpacking-the-net-assembly">Unpacking the .NET assembly</h2>
<p>Decompiling <code>LGMonitorAppManager.exe</code> immediately shows it is obfuscated. Every class is named with unprintable Unicode characters, and every string is gone, replaced by calls like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>Registry.LocalMachine.OpenSubKey(Class8.smethod_0(<span style="color:#ae81ff">252488964</span>));
</span></span></code></pre></div><p><code>Class8.smethod_0(int)</code> is a string decryptor — you hand it an integer id and it returns the real string. This pattern (confusable Unicode identifiers + an int-keyed string decryptor) is the signature of <strong>Eazfuscator.NET</strong>, a commercial .NET obfuscator.</p>
<p>Step one is <a href="https://github.com/de4dot/de4dot">de4dot</a>, which cleans up the control flow and renames the garbage symbols into something readable (<code>Class8</code>, <code>smethod_0</code>, etc.). But de4dot could not decrypt the strings — it reports the obfuscator as &ldquo;Unknown,&rdquo; because this is a 2025 build of Eazfuscator and de4dot&rsquo;s engine is from 2015.</p>
<p>I tried pointing de4dot at the decryptor manually:</p>
<pre tabindex="0"><code>de4dot input.exe --strtyp delegate --strtok 0x06000106
</code></pre><p>This <em>did</em> run — every <code>smethod_0(...)</code> call got replaced — but every single string came out as the literal placeholder <code>X0X</code>. That was the clue for what is going on. Reading the decompiled decryptor explains it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#66d9ef">static</span> Class8()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>    StackTrace stackTrace = <span style="color:#66d9ef">new</span> StackTrace(<span style="color:#ae81ff">2</span>, fNeedFileInfo: <span style="color:#66d9ef">false</span>);
</span></span><span style="display:flex;"><span>    StackFrame frame = stackTrace.GetFrame(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    MethodBase methodBase = frame?.GetMethod();
</span></span><span style="display:flex;"><span>    Type type = methodBase?.DeclaringType;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (type == <span style="color:#66d9ef">typeof</span>(RuntimeMethodHandle)) { <span style="color:#75715e">/* seed A */</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (type == <span style="color:#66d9ef">null</span>)                   { <span style="color:#75715e">/* seed B */</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>                                     { <span style="color:#75715e">/* seed C */</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// the seed becomes the key used to decrypt every string</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The static constructor walks the <strong>call stack</strong> and derives the decryption key from <em>who called it</em>. When the caller is a normal method inside the assembly, it takes one branch; when the caller is <code>RuntimeMethodHandle</code> — i.e. you invoked it through reflection, exactly what de4dot does — it takes a different branch and derives the wrong key. It is an anti-tamper trick: try to call the decryptor out of context and you get garbage.</p>
<p>The clean way around this is <a href="https://github.com/holly-hacker/EazFixer">EazFixer</a>, which is built specifically for modern Eazfuscator. It uses Harmony to patch that stack-based check at runtime, then lets the assembly decrypt its own strings in the correct context:</p>
<pre tabindex="0"><code>EazFixer.exe --file LGMonitorAppManager.exe --out fixed.exe
...
StringFixer: Success
</code></pre><p>Decompiling <code>fixed.exe</code> finally gives fully readable code, strings and all. Now we can actually see what the thing does.</p>
<h2 id="what-it-does">What it does</h2>
<p>The core is a class called <code>UIManager</code>. On startup it:</p>
<ol>
<li>Reads the connected monitors&rsquo; EDID (via the native <code>EDIDMgr.dll</code>) to get each monitor&rsquo;s vendor/product id, and keeps only the LG ones.</li>
<li>Checks that the machine is online.</li>
<li>Fetches a JSON catalog from LG&rsquo;s server:</li>
</ol>
<pre tabindex="0"><code>https://lmu.lge.com/ExternalService/LGMonitorAppInstaller/MonitorAppInfo.json
https://lmu.lge.com/ExternalService/LGMonitorAppInstaller/McAfee_MonitorAppInfo.json
</code></pre><p>The <code>MonitorAppInfo.json</code> is the app catalog. It is an array of monitor groups; each group lists the product ids it applies to and the apps offered for those monitors. Here is one entry (trimmed):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;pid_list&#34;</span>: [<span style="color:#e6db74">&#34;23603&#34;</span>, <span style="color:#e6db74">&#34;23604&#34;</span>, <span style="color:#e6db74">&#34;23640&#34;</span>, <span style="color:#e6db74">&#34;23641&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;vid_list&#34;</span>: [<span style="color:#e6db74">&#34;7789&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;supported_app_list&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;app_type&#34;</span>: <span style="color:#e6db74">&#34;Recommended&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;app_name&#34;</span>: <span style="color:#e6db74">&#34;OnScreen Control&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;setup_url&#34;</span>: <span style="color:#e6db74">&#34;https://lmu.lge.com/ExternalService/LGMonitorAppInstaller/Win/OnScreen Control/Setup/Win_OSC_9.16.zip&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;iss_url&#34;</span>:   <span style="color:#e6db74">&#34;https://lmu.lge.com/ExternalService/LGMonitorAppInstaller/Win/OnScreen Control/Setup/OnScreen Control.iss&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;app_info_list&#34;</span>: [
</span></span><span style="display:flex;"><span>        { <span style="color:#f92672">&#34;lang_name&#34;</span>: <span style="color:#e6db74">&#34;English&#34;</span>, <span style="color:#f92672">&#34;app_description&#34;</span>: <span style="color:#e6db74">&#34;OnScreen Control is an application to ...&#34;</span>, <span style="color:#f92672">&#34;eula_url&#34;</span>: <span style="color:#e6db74">&#34;...&#34;</span> }
</span></span><span style="display:flex;"><span>      ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Each app entry points at a <code>setup_url</code> (a zip) and an <code>iss_url</code> (an InstallShield silent-install response file). When you accept an app, <code>UIManager</code> downloads the zip, unzips it with <code>Ionic.Zip</code>, and hands the extracted <code>setup.exe</code> plus the <code>.iss</code> file off to the elevated <code>LGMonitorAppSilentInstaller.exe</code>, which runs the installer silently.</p>
<p>The catalog it serves me right now offers these LG applications, all hosted on <code>lmu.lge.com</code>:</p>
<table>
	<thead>
			<tr>
					<th>App</th>
					<th>Type</th>
					<th>Payload</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>OnScreen Control</td>
					<td>Recommended</td>
					<td><code>Win_OSC_9.16.zip</code> (also 9.47)</td>
			</tr>
			<tr>
					<td>LG Calibration Studio</td>
					<td>Optional</td>
					<td><code>Win_LCS_6.9.7.zip</code> (also 7.5.0)</td>
			</tr>
			<tr>
					<td>Dual Controller</td>
					<td>Optional</td>
					<td><code>Win_DC_2.81.zip</code> (also 2.89)</td>
			</tr>
			<tr>
					<td>LG Switch</td>
					<td>Recommended</td>
					<td><code>Win_LGSwitch_3.16.zip</code> (also 7.60)</td>
			</tr>
			<tr>
					<td>LG Channels</td>
					<td>—</td>
					<td>a <em>shortcut</em> to <code>www.lgchannels.com</code>, not an installed app</td>
			</tr>
	</tbody>
</table>
<p>&ldquo;LG Channels&rdquo; is a nice tell about the nature of this thing. It is not a program at all — installing it just drops a desktop shortcut that opens <code>www.lgchannels.com/?Y2FsbGVyPU1vbml0b3ImdGFyZ2V0PWhvbWU=</code> in your browser. That base64 decodes to <code>caller=Monitor&amp;target=home</code>, i.e. an attribution tag so LG can count how many of these ad clicks came from the monitor installer. The shortcut&rsquo;s target is your default browser, resolved by reading <code>HKCU\...\UrlAssociations\http\UserChoice</code>.</p>
<h2 id="the-mcafee-part">The McAfee part</h2>
<p>There is a second catalog, <code>McAfee_MonitorAppInfo.json</code>, and a bunch of McAfee-specific machinery in the client. It enumerates your installed antivirus via WMI:</p>
<pre tabindex="0"><code>\\.\root\SecurityCenter2  -&gt;  SELECT displayName FROM AntivirusProduct
</code></pre><p>and if it decides McAfee should be pushed, it downloads and installs it like any other app, then fires a &ldquo;conversion ping&rdquo; to a tracking URL from the server config. That ping is the giveaway: this is an affiliate arrangement, where LG is set to earn a commission on every McAfee installation that comes through the monitor installer. It is the same referral economics that gets a McAfee trial bundled with so many other products — the AV enumeration decides whether you are a viable &ldquo;conversion,&rdquo; and the ping reports back when one lands.</p>
<p>The one funny detail: the McAfee bundle is a password-protected zip, and the password is hardcoded in the (unsigned) client:</p>
<pre tabindex="0"><code>McAfeeLGdasusodkug!@#
</code></pre><p><code>McAfee</code> + <code>LG</code> + <code>dasusodkug</code> + <code>!@#</code>. I wondered whether <code>dasusodkug</code> was Korean typed on an English keyboard (LG being a Korean company), but mapping it through the 2-beolsik layout gives <code>ㅇㅁㄴㅕㄴㅐㅇㅏㅕㅎ</code>, which starts with three consonants and no vowel — not real Korean. It is just filler to make the password look strong. In any case, a hardcoded password in a shipped binary is not a security control.</p>
<p>I wanted to grab the McAfee installer to look at it, but <strong>I was unable to obtain it</strong>. The live <code>McAfee_MonitorAppInfo.json</code> currently contains zero McAfee entries, and the actual installer is nowhere I could reach: the catalog URL is static with no parameters, so there is no query that surfaces the payload, and guessing the path just returns a tiny <code>text/html</code> soft-404. There are a couple of plausible reasons. It could be gated server-side — by region, or by which antivirus you already have — so a generic request never gets it. But it is also entirely possible that LG simply pulled the McAfee installer after the widespread backlash over exactly this bundling, and the code path is now dormant. Either way, the McAfee capability is plainly still present in the client, even though I could not pull the actual payload.</p>
<h2 id="no-signature-check">No signature check</h2>
<p>Here is the part I want to point out, gently. I looked for any integrity verification in the download-and-install path — Authenticode/<code>WinVerifyTrust</code>, a hash or checksum compared against the catalog, certificate pinning, anything. There is none. The download is simply:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>webClient.DownloadFile(<span style="color:#66d9ef">new</span> Uri(setup_url), destination);
</span></span></code></pre></div><p>where <code>setup_url</code> comes straight from the server&rsquo;s JSON. The zip is unzipped and the <code>setup.exe</code> inside is executed with elevation. Nothing between &ldquo;bytes off the network&rdquo; and &ldquo;run as admin&rdquo; checks that the bytes are what LG intended.</p>
<p>To be fair, I downloaded all eight LG installers and checked them, and every one is validly Authenticode-signed by <code>LG Electronics Inc.</code> (the newer builds even use an EV certificate). So under normal conditions you are running authentic LG code. But that is a property of what LG&rsquo;s server happens to be serving today, not something the client enforces — if the server returned an unsigned or swapped <code>setup.exe</code>, the client would run it just the same. There is also no scheme check, so the client would follow an <code>http://</code> URL if the catalog contained one; today it does not, and TLS validation is at least not disabled.</p>
<p>The net of it: the whole trust model rests on HTTPS and on LG keeping <code>lmu.lge.com</code> pristine. Whoever controls that endpoint — an origin/CDN compromise on LG&rsquo;s side, or an attacker who can MITM with a certificate the system trusts — gets silent, elevated, zero-interaction code execution on every machine running this, because the software auto-starts and will happily install whatever the catalog points at. Under the right conditions that is a textbook supply chain attack. It is not something anyone can trivially exploit from the outside, but for software that shipped itself to your PC uninvited and runs at boot, &ldquo;we only pinky-promise the server is fine&rdquo; is a thin guarantee.</p>
<h2 id="how-does-this-even-get-on-your-machine">How does this even get on your machine?</h2>
<p>The part that bothered most people, including me, is that nobody installed this. You buy a monitor, plug it in, and some time later it is just <em>there</em>. The common explanation online is &ldquo;Windows Update,&rdquo; and that turns out to be exactly right — but the mechanism is more specific and more interesting than &ldquo;Windows Update installs random programs,&rdquo; so I dug the trail out of my own machine.</p>
<p>It rides in on a driver, using Microsoft&rsquo;s <strong>Hardware Support App (HSA)</strong> feature. Here is the chain, all verifiable locally with <code>pnputil</code> and the registry:</p>
<ol>
<li>You connect the monitor. Windows sees its PnP hardware id — on my machine <code>DISPLAY\GSM77E8</code>, &ldquo;Generic Monitor (LG ULTRAWIDE)&rdquo;.</li>
<li>Windows Update matches that id and automatically pulls down two LG driver packages, <strong>both WHQL-signed by &ldquo;Microsoft Windows Hardware Compatibility Publisher&rdquo;</strong> (i.e. they went through Microsoft&rsquo;s hardware certification and are distributed through Windows Update itself):
<ul>
<li>a monitor <strong>extension</strong> INF (<code>monitor.inf</code>), which among other things advertises a child device literally named &ldquo;LG Monitor Support Application&rdquo;;</li>
<li>a <strong>SoftwareComponent</strong> INF (<code>lgmonitorappsoftwarecomponent.inf</code>), which matches that child and runs an <code>AddSoftware</code> directive.</li>
</ul>
</li>
<li>That directive is the whole trick:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[LG_Monitor_Control_Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareType</span><span style="color:#f92672">=</span><span style="color:#e6db74">2</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SoftwareID</span><span style="color:#f92672">=</span><span style="color:#e6db74">pfn://LGElectronics.LGMonitorApp_cfnzzhwkr8z5w</span>
</span></span></code></pre></div><p><code>SoftwareType=2</code> with a <code>pfn://</code> id means &ldquo;go install this <strong>Microsoft Store</strong> app by its package family name.&rdquo; So Windows fetches <code>LGElectronics.LGMonitorApp</code> from the Store and installs it. On my machine it landed at <code>C:\Program Files\WindowsApps\LGElectronics.LGMonitorApp_1.2606.1601.0_x86__cfnzzhwkr8z5w</code>, <code>SignatureKind = Store</code>, version <code>1.2606.1601.0</code> — the exact binary I had been reverse engineering. The INF does not even hide what it is; one of its strings is <code>Hsa.ComponentDesc = &quot;LG Monitor Support Application&quot;</code>, and <em>HSA</em> is Microsoft&rsquo;s own acronym for Hardware Support App.</p>
<p>So, does Windows Update install third-party programs? Not arbitrary <code>.exe</code> files, no — but through this sanctioned path, effectively yes. A WHQL-signed driver is allowed to pair itself with a companion app via <code>AddSoftware</code>. There are two flavors: <code>SoftwareType=1</code> is a Win32 executable bundled <em>inside</em> the driver package and run once, and <code>SoftwareType=2</code> is a Microsoft Store (MSIX) app referenced by package family name. LG uses type 2, so what you get is a packaged, Store-signed app that a Windows-Update-delivered driver quietly requested on your behalf.</p>
<p>Here is the nuance that ties this back to the rest of the post. The Microsoft-blessed, signed, sandboxed part of this whole story <em>ends</em> at &ldquo;a Store app is installed.&rdquo; Everything that app then does — phoning <code>lmu.lge.com</code>, and downloading and silently installing classic Win32 installers with no signature check — happens entirely outside the Windows Update and Store trust model. Windows Update delivered a signed driver, the driver requested a signed Store app, and that Store app is the thing that goes off and side-loads everything else. The trust chain quietly falls off a cliff at the last step.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So what is LG Monitor App Manager? Honestly, it is fairly ordinary adware. It detects your LG monitor, phones home for a list of LG apps and promotions, nudges you to install them, quietly bundles a McAfee referral, and plants a tracked shortcut to an ad-supported streaming service. Nothing here is malware, nothing exfiltrates your files, and it is all signed by LG. As adware goes, it is tame.</p>
<p>What still surprises me is the <em>delivery</em>. I did not install this. I did not visit lg.com. I plugged in a monitor, and Windows Update — by way of a signed driver and the Hardware Support App mechanism above — installed a Store app that then set itself to run at every boot and started offering to install more software. The HSA part is a legitimate Windows feature and works as designed; what unsettles me is what LG chose to put at the end of it. Shipping an auto-updating, self-starting software distribution channel to people simply because they bought your monitor — and wiring up its final stage with no signature checks on the payloads — is a lot of trust to ask for a brightness-control utility. If you have an LG monitor and would rather not have this, uninstalling the &ldquo;LG Monitor&rdquo; software, deleting the shortcut from your Startup folder, and disabling the corresponding StartupTask will get rid of it, at least until the next time you reconnect the monitor.</p>
]]></content>
        </item>
        
        <item>
            <title>Solving a VM Challenge Using BinaryNinja</title>
            <link>https://xusheng.dev/posts/reversing/kata_vm/readme/</link>
            <pubDate>Sun, 18 Apr 2021 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/kata_vm/readme/</guid>
            <description>&lt;p&gt;Recently, my friend &lt;a href=&#34;https://crackmes.one/user/Towel&#34;&gt;Towel&lt;/a&gt; created a &lt;a href=&#34;https://crackmes.one/crackme/605443e333c5d42c3d016f59&#34;&gt;VM challenge&lt;/a&gt;. I have not done any VM crackmes in the last year and decided to try this one. Towel says the challenge should be easy and serves mostly as an introduction for VM crackmes. More importantly, this one has no anti-debugging or static obfuscation, to allow the solver to concentrate on the VM itself.&lt;/p&gt;
&lt;h2 id=&#34;preliminary&#34;&gt;Preliminary&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;main()&lt;/code&gt; function is actually quite simple:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Just one function call and a branch, which decides if the player succeeds. The &lt;code&gt;core&lt;/code&gt; function looks like what a VM should be:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Recently, my friend <a href="https://crackmes.one/user/Towel">Towel</a> created a <a href="https://crackmes.one/crackme/605443e333c5d42c3d016f59">VM challenge</a>. I have not done any VM crackmes in the last year and decided to try this one. Towel says the challenge should be easy and serves mostly as an introduction for VM crackmes. More importantly, this one has no anti-debugging or static obfuscation, to allow the solver to concentrate on the VM itself.</p>
<h2 id="preliminary">Preliminary</h2>
<p>The <code>main()</code> function is actually quite simple:</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>Just one function call and a branch, which decides if the player succeeds. The <code>core</code> function looks like what a VM should be:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>It has a dispatcher at the top-center of the graph, and various VM instruction handlers beneath it. We see most of the instructions are not complex (supposedly), except for the longer four ones at the left-hand side. Looking at the first basic block in the function quickly reveals some important information about the VM:</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>The code first sets up a huge buffer on the stack and then copies 0x3b1a bytes of data from address 0x3008 into it. Upon further inspection, I decided this is the actual VM code. Then it clears 32-byte spaces at the top of the stack. I did not know what it is at the moment, since it could be RAM or registers for the VM. Anyways, let us proceed to the first VM instruction and its handlers.</p>
<h2 id="analyzing-the-first-vm-instruction">Analyzing the First VM Instruction</h2>
<p><img src="../imgs/4.png" alt=""></p>
<p>The start of the common handler reads one byte from the current VMIP (VM IP) and goes to different handlers based on its value. It serves as an opcode (operation code) for the instruction. The opcode of the first VM instruction is 0xf6, and we can find its handler according to the value:</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>Looking at the handler, it reads another byte right after the opcode byte and checks whether the lowest two bits of it is 0x0. If so, it goes into the left branch in the image. We call this byte <code>sub-opcode</code> byte because it determines which variant of the opcode to execute. Then it has two <code>shr</code> instructions followed by two <code>and</code> and <code>mov</code>. It is not immediately clear to me what it is doing at first sight.</p>
<p>Upon closer inspection, I find if we treat the data at the top of the stack (address <code>rsp</code>) as an array of dwords, then the two <code>mov</code> essentially moves a dword from index <code>rcx</code> into index <code>rax</code>. And tracing back, we find the two values come from the sub-opcode byte. The source index comes from s 7-8 of it, and the destination index comes from bits 5-6. Remember the lowest two bits of the sub-opcode byte are used to decide the opcode variant.</p>
<p>Now it is pretty clear that this is moving values between VM registers. (Well, it could also be VM memory, but this does not make a big difference.) This instruction can be disassembled something like <code>mov r1, r3</code>, for example. Of course, the actual indices of the registers need to be parsed from the sub-opcode byte.</p>
<p>Alright, what about the other sub-opcode? I.e., when the lowest two bits of the byte is 0x2. It reads a dword after the sub-opcode, and moves that into a register decided by the bits 5-6 of the sub-opcode. Here, since both instructions are encoding registers using two bits, I deduced that this VM has four registers. However, this conclusion is later challenged when I reverse the last VM Opcode.</p>
<p>Nice, we analyzed one VM instruction. Now we simply need to repeat the process for every VM instruction and will eventually complete it. However, the VM code is quite lengthy (0x3b1a bytes long), manually analyzing it is hopeless. We need some way to automate the process.</p>
<h2 id="writing-an-architecture-plugin-in-binaryninja">Writing an Architecture Plugin in BinaryNinja</h2>
<p>This time I decided to write an architecture plugin in BinaryNinja to disassemble the VM. I have been using BinaryNinja for several years, but I have not written any architecture plugins before. Previously, I wrote my recursive descent <a href="https://github.com/jeffli678/VM_Disassembler">disassembler</a> for VMs. It requires the user to write a disassembler function for the custom architecture, and it will drive the disassembler, i.e., fetch the binary code, ask for disassembly, and then print it.</p>
<p>However, writing an architecture plugin in BinaryNinja is a more powerful solution. Firstly, it benefits from a great GUI that I am already familiar with. I get a decent graph view, register highlighting, cross-references, etc, for free. Secondly, if we lift it to BinaryNinja IL, we can almost forget that we are dealing with an alien architecture &ndash; just read the BLIL and analyze it from there.</p>
<p>Andrew has done a <a href="https://binary.ninja/2020/01/08/guide-to-architecture-plugins-part1.html">tutorial</a> on writing new architecture plugins, which I followed as I set up the initial code.</p>
<h2 id="basics-of-architecture-plugin">Basics of Architecture Plugin</h2>
<p>Writing a new architecture plugin turns out to be quite simple: one simply sub-classes <code>Architecture</code>, and then <code>register()</code> it. After that, in the GUI, we can create a function in the new architecture. Since Andrew&rsquo;s blogpost can already serve as a walk-through, so I will not cover every detail here. Instead, I will just describe some of my feelings and thoughts about the process.</p>
<p>Most of the work needs to be done are implementing two functions of the new architecture: <code>get_instruction_info()</code> and <code>get_instruction_text()</code>. <code>get_instruction_info()</code> returns the length of the instruction, branches (if any) after the instruction, etc. It will help BinaryNinja disassemble the function and draw the graph of basic blocks. <code>get_instruction_text()</code> returns to tokens of the instruction, which will be displayed as the disassembly. There is another function <code>get_instruction_low_level_il()</code>, which allows the instruction in the architecture to be lifted to LLIL. Then BinaryNinja will then lift it to MLIL and HLIL, do lots more analysis, and produce a decompiler output eventually. In this VM example, since the disassembly is already quite straightforward to read, I did not lift it to LLIL.</p>
<p>Let us go back to the first VM instruction and see how we handle it in the architecture plugin.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_instruction_info</span>(self, data, addr):
</span></span><span style="display:flex;"><span>    result <span style="color:#f92672">=</span> InstructionInfo()
</span></span><span style="display:flex;"><span>    opcode <span style="color:#f92672">=</span> data[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    byte1 <span style="color:#f92672">=</span> data[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> opcode <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xf6</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> byte1 <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            result<span style="color:#f92672">.</span>length <span style="color:#f92672">=</span> <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> byte1 <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>:
</span></span><span style="display:flex;"><span>            result<span style="color:#f92672">.</span>length <span style="color:#f92672">=</span> <span style="color:#ae81ff">6</span>
</span></span></code></pre></div><p>Right, since this one does not affect the control-flow, we simply need to return the instruction length of it. This VM happens to use the <code>rdx</code> register to store the VMIP, so tracking its value change can tell us the instruction length. One interesting part of this VM is NOT all bytes in an instruction is necessarily used for encoding. Sometimes, it wastes several bytes for nothing. I guess the purpose of this is to confuse the reverser. For disassembly, it is slightly more complex:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_instruction_text</span>(self, data, addr):
</span></span><span style="display:flex;"><span>    instrLen <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    tokens <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    opcode <span style="color:#f92672">=</span> data[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    byte1 <span style="color:#f92672">=</span> data[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> opcode <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xf6</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> byte1 <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            instrLen <span style="color:#f92672">=</span> <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>InstructionToken, <span style="color:#e6db74">&#39;mov&#39;</span>))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>TextToken, <span style="color:#e6db74">&#39; &#39;</span>))
</span></span><span style="display:flex;"><span>            reg0 <span style="color:#f92672">=</span> (byte1 <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">4</span>) <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>            reg1 <span style="color:#f92672">=</span> (byte1 <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">6</span>) <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>RegisterToken, <span style="color:#e6db74">&#39;r</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> reg0))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>OperandSeparatorToken, <span style="color:#e6db74">&#39;,&#39;</span>))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>TextToken, <span style="color:#e6db74">&#39; &#39;</span>))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>RegisterToken, <span style="color:#e6db74">&#39;r</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> reg1))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> byte1 <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>:
</span></span><span style="display:flex;"><span>            instrLen <span style="color:#f92672">=</span> <span style="color:#ae81ff">6</span>
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>InstructionToken, <span style="color:#e6db74">&#39;mov&#39;</span>))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>TextToken, <span style="color:#e6db74">&#39; &#39;</span>))
</span></span><span style="display:flex;"><span>            reg0 <span style="color:#f92672">=</span> (byte1 <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">4</span>) <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>RegisterToken, <span style="color:#e6db74">&#39;r</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> reg0))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>OperandSeparatorToken, <span style="color:#e6db74">&#39;,&#39;</span>))
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>TextToken, <span style="color:#e6db74">&#39; &#39;</span>))
</span></span><span style="display:flex;"><span>            int0 <span style="color:#f92672">=</span> int<span style="color:#f92672">.</span>from_bytes(data[<span style="color:#ae81ff">2</span>:<span style="color:#ae81ff">6</span>], byteorder<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;little&#39;</span>)
</span></span><span style="display:flex;"><span>            tokens<span style="color:#f92672">.</span>append(InstructionTextToken(InstructionTextTokenType<span style="color:#f92672">.</span>IntegerToken, <span style="color:#e6db74">&#39;0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> int0, int0))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> tokens, instrLen
</span></span></code></pre></div><p>The disassembly is made up of several tokens. The reason to use different types of tokens is to inform the UI of its purpose, which can then provide better support. For example, when we use <code>RegisterToken</code> in the disassembly, and we select one of them, all register tokens with the same text will be all highlighted. This allows us to track the data flow faster.</p>
<p>Also, even one does not wish to spend time on creating these tokens, he can simply use a <code>TextToken</code> to hold all the token texts. That means my disassembler should retire because it is completely superseded by using the BinaryNinja architecture plugin.</p>
<p>From here we repeat the process and disassemble all instructions one by one. In BinaryNinja, we can easily see what is the bytecode of the next unhandled instruction, which is quite convenient to speed up the development loop.</p>
<h2 id="analyzing-the-algorithm">Analyzing the Algorithm</h2>
<p>I did not wait until I finish every instruction to start analyzing the algorithm. The first three VM instruction already gives me something meaningful:</p>
<p><img src="../imgs/6.png" alt=""></p>
<p>We see that <code>r0</code> is 0x20 after the subtraction, and then it writes it to the terminal. The ASCII 0x20 corresponds to the space char. So these three instructions print space to the terminal. This process is repeated lots of times:</p>
<p><img src="../imgs/7.png" alt=""></p>
<p>What is it doing? Well, it just prints the text we see when we execute the binary:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>$ ./KataVM_L1 
</span></span><span style="display:flex;"><span>    .-------------------------.
</span></span><span style="display:flex;"><span>    | Towel<span style="color:#e6db74">&#39;s KataVM: Level 1 |
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;</span>-------------------------<span style="color:#960050;background-color:#1e0010">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&gt;&gt; 
</span></span></code></pre></div><p>Well, yeah, it prints a banner and then asks for the input. So we do not bother analyzing these instructions, because we know their effect. Let us see what happens after the prints:</p>
<p><img src="../imgs/8.png" alt=""></p>
<p>It first sets <code>r2</code> to 4, and reads 4 bytes of input into both <code>r0</code> and <code>r1</code>. Note in this VM, the bytes to read are decided by the third parameter of the <code>read()</code> call, in this case being 4. Next, it copies the input in <code>r1</code> into <code>r2</code> and <code>r3</code>, and does some shift, add, and xor on it. This looks like the <code>TEA</code> algorithm, isn&rsquo;t it?</p>
<h2 id="a-complex-vm-instruction">A COMPLEX VM Instruction</h2>
<p>The next VM code I encounter is the hardest one in this VM. It has four sub-opcode, and all of them look similar.</p>
<p><img src="../imgs/9.png" alt=""></p>
<p>Even if I probably only need to analyze one of them, it is still quite complex. I first read its disassembly, but I quickly get lost. However, I do see some patterns within it. Let us look at the left-most sub-opcode, and the red block in the middle is always executed. And the code on top of it and beneath it also looks similar to each other. So it means maybe I only need to understand one of the two, drastically lowering the workload.</p>
<p>Also, at the bottom of the handler, we see this interesting block:</p>
<p><img src="../imgs/10.png" alt=""></p>
<p>It is using the SSE instruction <code>pshufd</code> to reorganize the four dwords at address <code>rsp</code> and <code>rsp+0x10</code>. At first sight, this is more confusing than revealing. I thought this VM has four registers, which means four dwords. They are 16 bytes in total and span from <code>rsp</code> to <code>rsp+0x10</code>. Then what the heck is this code doing when it transforms the four dwords starting at <code>rsp+0x10</code>, is it just trying to confuse me?</p>
<p>Well, let&rsquo;s first figure out what it does to the four registers sorted at <code>rsp</code>. We know <code>pshufd</code> transforms the second operand according to the third operand (which is an 8-bit immediate), and stores the result in the first operand. And what does the third parameter, <code>0x1b</code> corresponds to? It is actually <code>0b00011011</code> in binary, or <code>00 01 10 11</code> when put into groups. Wow, so the <code>pshufd</code> will swap the first dword with the last one, and the second one with the third one. Nice, this makes sense!</p>
<p>Now we still need to read the bulky part of the handler.</p>
<p><img src="../imgs/12.png" alt=""></p>
<p>At the beginning of it, it first reads a dword after the sub-opcode byte. Then it does some length-based comparison based on that value. I did not make sense out of it initially, but when I zoom out, I discovered a new pattern:</p>
<p><img src="../imgs/11.png" alt=""></p>
<p>There are two loops in it. And the smaller one looks like this:</p>
<p><img src="../imgs/13.png" alt=""></p>
<p>It is just copying dwords from one pointer to another pointer until the two-pointer converges. Well, it seems to be reversing all of the dwords between two pointers. Looking at the larger one, it appears to be doing the same thing, just using SSE instructions to handle more data at the same time, to run it faster:</p>
<p><img src="../imgs/14.png" alt=""></p>
<p>Suddenly, the length comparison, as well as the two loops that does the same thing start to make sense to me! It is trying to reverse the dwords, but it will first use the SSE version when the buffer is large, then use regular instructions to wrap up. It is similar to the handling in SSE version of string functions, e,g., <code>strcpy</code>.</p>
<p>Nice progress! The next thing I discover is that no arithmetic computation is performed on the data, they are just moved from one place to another. Given all this information, I fired up gdb to see what it is doing.</p>
<p>The VM instruction I followed reads out a parameter from the dword after sub-opcode. The parameter has a value 0x5. The instruction breaks up the 8 dwords start at <code>rsp</code> into two groups, with the first group being the first 6 dwords, while the other being the rest 2 dwords. It then reverses the position of the dwords WITHIN the two groups, respectively. If you use an integer to represent the location of the dwords, it works like this:</p>
<pre tabindex="0"><code>Start 
0 1 2 3 4 5 6 7

Group 1
0 1 2 3 4 5 ==&gt; 5 4 3 2 1 0

Group 2
6 7 ==&gt; 7 6

End 
5 4 3 2 1 0 7 6
</code></pre><p>So far, no magic, right? Then it reverses the orders of the first four dwords, and the last four dwords, respectively:</p>
<pre tabindex="0"><code>Start 
5 4 3 2 1 0 7 6

Group 1
5 4 3 2 ==&gt; 2 3 4 5

Group 2
1 0 7 6 ==&gt; 6 7 0 1

End 
2 3 4 5 6 7 0 1
</code></pre><p>If we put the start and end together, we can see it clearly:</p>
<pre tabindex="0"><code>Start 
0 1 2 3 4 5 6 7

End 
2 3 4 5 6 7 0 1
</code></pre><p>Equivalently, the dwords are rotate shifted right 6 times! Wow, how smart a way to do it! I did not work out the mathematics behind this transformation, though I would expect it to be simpler than reversing this function.</p>
<p>Now that the VM turns out to have eight registers, though only the first four can be accessed using <code>r0</code>-<code>r3</code>. To access the other four ones, they have to be first shifted into the position of the first four, and then operated upon. Interesting, this is the first time I have seen something like this!</p>
<p>For the rest handlers similar to the one we have dechiphered, one does shift left in the same way. And the other two reads the number of cells to shift from a register. Great, we now fully reverse-engineered and understood the VM!</p>
<h2 id="is-it-tea">Is it TEA?</h2>
<p><img src="../imgs/15.png" alt=""></p>
<p>Looking at the code and comparing it with a reference implementation (shown below, excerpted from Wikipedia), I believe it must be TEA. The registers are being rotated but if we track it precisely, we see those are just obfuscations. Everything matches except the delta is 0xe09ffbb1, not 0x9E3779B9. But that does not make a big difference to the algorithm. Also, the VM code is 32 rounds of unrolled loops. The entire program reads 16 bytes of input, and every 8 bytes are processed in the same way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">encrypt</span> (<span style="color:#66d9ef">uint32_t</span> v[<span style="color:#ae81ff">2</span>], <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint32_t</span> k[<span style="color:#ae81ff">4</span>]) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> v0<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">0</span>], v1<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">1</span>], sum<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, i;           <span style="color:#75715e">/* set up */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> delta<span style="color:#f92672">=</span><span style="color:#ae81ff">0x9E3779B9</span>;                     <span style="color:#75715e">/* a key schedule constant */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> k0<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">0</span>], k1<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">1</span>], k2<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">2</span>], k3<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">3</span>];   <span style="color:#75715e">/* cache key */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (i<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; i<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">32</span>; i<span style="color:#f92672">++</span>) {                         <span style="color:#75715e">/* basic cycle start */</span>
</span></span><span style="display:flex;"><span>        sum <span style="color:#f92672">+=</span> delta;
</span></span><span style="display:flex;"><span>        v0 <span style="color:#f92672">+=</span> ((v1<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k0) <span style="color:#f92672">^</span> (v1 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v1<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k1);
</span></span><span style="display:flex;"><span>        v1 <span style="color:#f92672">+=</span> ((v0<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k2) <span style="color:#f92672">^</span> (v0 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v0<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k3);
</span></span><span style="display:flex;"><span>    }                                              <span style="color:#75715e">/* end cycle */</span>
</span></span><span style="display:flex;"><span>    v[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">=</span>v0; v[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">=</span>v1;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And the key appears to be:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">uint32_t</span> key[<span style="color:#ae81ff">4</span>] <span style="color:#f92672">=</span> {<span style="color:#ae81ff">0x80b86e21</span>, <span style="color:#ae81ff">0xa268295d</span>, <span style="color:#ae81ff">0xf171f22d</span>, <span style="color:#ae81ff">0x28a13c94</span>};
</span></span></code></pre></div><p>After 32 rounds of encryption, the encrypted input is checked against two constants:</p>
<p><img src="../imgs/16.png" alt=""></p>
<p>The <code>fail = 0x1</code> means if the <code>cmp</code> returns not equal, the check fails.</p>
<p>Then I copied the reference decrypting code (from Wikipedia), changed the delta (and sum as well):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">decrypt</span> (<span style="color:#66d9ef">uint32_t</span> v[<span style="color:#ae81ff">2</span>], <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint32_t</span> k[<span style="color:#ae81ff">4</span>]) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> v0<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">0</span>], v1<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">1</span>], sum<span style="color:#f92672">=</span><span style="color:#ae81ff">0x9E3779B9</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">32</span>, i;  <span style="color:#75715e">/* set up; sum is 32*delta */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> delta<span style="color:#f92672">=</span><span style="color:#ae81ff">0x9E3779B9</span>;                     <span style="color:#75715e">/* a key schedule constant */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> k0<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">0</span>], k1<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">1</span>], k2<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">2</span>], k3<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">3</span>];   <span style="color:#75715e">/* cache key */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (i<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; i<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">32</span>; i<span style="color:#f92672">++</span>) {                         <span style="color:#75715e">/* basic cycle start */</span>
</span></span><span style="display:flex;"><span>        v1 <span style="color:#f92672">-=</span> ((v0<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k2) <span style="color:#f92672">^</span> (v0 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v0<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k3);
</span></span><span style="display:flex;"><span>        v0 <span style="color:#f92672">-=</span> ((v1<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k0) <span style="color:#f92672">^</span> (v1 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v1<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k1);
</span></span><span style="display:flex;"><span>        sum <span style="color:#f92672">-=</span> delta;
</span></span><span style="display:flex;"><span>    }                                              <span style="color:#75715e">/* end cycle */</span>
</span></span><span style="display:flex;"><span>    v[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">=</span>v0; v[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">=</span>v1;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then I happily ran it, and believe I solved it. However, the decrypted one seems to contain unprintable char. And when I feed them in as input, the check also fails. Emmm, where did I make any mistake?</p>
<h2 id="checking-each-round-one-by-one">Checking Each Round One-By-One</h2>
<p>I used the text <code>12345678</code> as input and compared the encryption result from TEA and the VM. The first several rounds all match, and I am tired of comparing them manually. One difficulty here is there is no way to set a breakpoint on the VMIP, we can only set up breakpoints on the VM handlers. So if we wish to compare the result after n rounds, it is not easy to set a breakpoint.</p>
<p>However, I made an observation that always ends up a <code>swap registers</code> VM instruction, and each round contains exactly four <code>swap registers</code> instructions. That makes the handler of that instruction a great place to set up a breakpoint. Set up a breakpoint at the end of the handler, when it hits, first use <code>c</code> to continue four times, and then dump the 8 dwords starting at <code>rsp</code> using <code>d/8dx $rsp</code>. I wrote a script to automate this process and dump the result, then compare it with the output from TEA.</p>
<p>I discovered the result from the 22nd round of encryption is different in two outputs. I browsed the code to do the 22nd round of encryption, and almost immediately saw the issue:</p>
<p><img src="../imgs/17.png" alt=""></p>
<p>The number of shifts are different! They are 2 and 3 instead of 4 and 5. I adjusted my decryption code a little bit:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">decrypt</span> (<span style="color:#66d9ef">uint32_t</span> v[<span style="color:#ae81ff">2</span>], <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint32_t</span> k[<span style="color:#ae81ff">4</span>]) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> v0<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">0</span>], v1<span style="color:#f92672">=</span>v[<span style="color:#ae81ff">1</span>], sum<span style="color:#f92672">=</span><span style="color:#ae81ff">0xe09ffbb1</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">32</span>, i;  <span style="color:#75715e">/* set up; sum is 32*delta */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> delta<span style="color:#f92672">=</span><span style="color:#ae81ff">0xe09ffbb1</span>;                     <span style="color:#75715e">/* a key schedule constant */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> k0<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">0</span>], k1<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">1</span>], k2<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">2</span>], k3<span style="color:#f92672">=</span>k[<span style="color:#ae81ff">3</span>];   <span style="color:#75715e">/* cache key */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (i<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; i<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">32</span>; i<span style="color:#f92672">++</span>) {                         <span style="color:#75715e">/* basic cycle start */</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (i <span style="color:#f92672">==</span> <span style="color:#ae81ff">9</span>)
</span></span><span style="display:flex;"><span>            v1 <span style="color:#f92672">-=</span> ((v0<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">2</span>) <span style="color:#f92672">+</span> k2) <span style="color:#f92672">^</span> (v0 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v0<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">3</span>) <span style="color:#f92672">+</span> k3);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>            v1 <span style="color:#f92672">-=</span> ((v0<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k2) <span style="color:#f92672">^</span> (v0 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v0<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k3);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        v0 <span style="color:#f92672">-=</span> ((v1<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">4</span>) <span style="color:#f92672">+</span> k0) <span style="color:#f92672">^</span> (v1 <span style="color:#f92672">+</span> sum) <span style="color:#f92672">^</span> ((v1<span style="color:#f92672">&gt;&gt;</span><span style="color:#ae81ff">5</span>) <span style="color:#f92672">+</span> k1);
</span></span><span style="display:flex;"><span>        sum <span style="color:#f92672">-=</span> delta;
</span></span><span style="display:flex;"><span>    }                                              <span style="color:#75715e">/* end cycle */</span>
</span></span><span style="display:flex;"><span>    v[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">=</span>v0; v[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">=</span>v1;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note, the decryption works in reverse, so the 22nd round in forwarding means the 9th round in reverse. Now it works! I get the output <code>xNVa2_N07_t3aAlg</code>, and it is correct:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>$ ./KataVM_L1 
</span></span><span style="display:flex;"><span>    .-------------------------.
</span></span><span style="display:flex;"><span>    | Towel<span style="color:#e6db74">&#39;s KataVM: Level 1 |
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;</span>-------------------------<span style="color:#960050;background-color:#1e0010">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&gt;&gt; xNVa2_N07_t3aAlg
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>+<span style="color:#f92672">]</span> Correct!
</span></span></code></pre></div>]]></content>
        </item>
        
        <item>
            <title>International Grand Master</title>
            <link>https://xusheng.dev/posts/grandmaster/</link>
            <pubDate>Sat, 13 Feb 2021 19:44:00 +0800</pubDate>
            <guid>https://xusheng.dev/posts/grandmaster/</guid>
            <description>&lt;p&gt;I was awarded the title of &lt;code&gt;International Grand Master&lt;/code&gt; for Xiangqi in late 2020. I am very excited about this title!&lt;/p&gt;
&lt;p&gt;I earned this title since I am the &lt;a href=&#34;http://www.naxf.org/wp/wp-content/uploads/2018/11/ranking.txt&#34;&gt;Champnion&lt;/a&gt; of the 8th North American Cup. Game records can be viewed &lt;a href=&#34;http://www.naxf.org/wp/8th/&#34;&gt;online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The certificate is shown here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../grandmaster.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I was awarded the title of <code>International Grand Master</code> for Xiangqi in late 2020. I am very excited about this title!</p>
<p>I earned this title since I am the <a href="http://www.naxf.org/wp/wp-content/uploads/2018/11/ranking.txt">Champnion</a> of the 8th North American Cup. Game records can be viewed <a href="http://www.naxf.org/wp/8th/">online</a>.</p>
<p>The certificate is shown here:</p>
<p><img src="../grandmaster.jpg" alt=""></p>
]]></content>
        </item>
        
        <item>
            <title>Photo Works</title>
            <link>https://xusheng.dev/posts/photos/</link>
            <pubDate>Sat, 13 Feb 2021 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/photos/</guid>
            <description>&lt;p&gt;I used to be a keen photographer. I treat photography as a way of expressing my views toward the world. My photos capture both the beauty and absurdity of this world.&lt;/p&gt;
&lt;p&gt;However, I have not taken very few photos in recent years. One excuse is I become busy and get obsessed with reverse engineering. But a more convincing reason is I am less interested in this world. The situation might change in the future, though.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I used to be a keen photographer. I treat photography as a way of expressing my views toward the world. My photos capture both the beauty and absurdity of this world.</p>
<p>However, I have not taken very few photos in recent years. One excuse is I become busy and get obsessed with reverse engineering. But a more convincing reason is I am less interested in this world. The situation might change in the future, though.</p>
<p> 
 </p>
<script src="/js/jquery.min.js" type="text/javascript"></script>
          

<link  href="/css/nanogallery2.min.css" rel="stylesheet" type="text/css">
<script  type="text/javascript" src="/js/jquery.nanogallery2.min.js"></script>

<div class="gallery" data-nanogallery2 = '{
        "thumbnailHeight":  200,
        "thumbnailWidth":   200,
        "viewerGallery": "none"
    }'>
    
    

    
    
            <a href = "/imgs/004f190e727b65fa32d167c6c734c9bf.jpg"   data-ngThumb = "/imgs/004f190e727b65fa32d167c6c734c9bf-thumb.jpg" > </a>
    
    
            <a href = "/imgs/05884d06678c9eff168b21508268e213.jpg"   data-ngThumb = "/imgs/05884d06678c9eff168b21508268e213-thumb.jpg" > </a>
    
    
            <a href = "/imgs/0745305a7c04d3fc243285b0d1655005.jpg"   data-ngThumb = "/imgs/0745305a7c04d3fc243285b0d1655005-thumb.jpg" > </a>
    
    
            <a href = "/imgs/0a625fcb8226fbf5dd6c37af0d85f114.jpg"   data-ngThumb = "/imgs/0a625fcb8226fbf5dd6c37af0d85f114-thumb.jpg" > </a>
    
    
            <a href = "/imgs/0bb4a2b78a9c84c6f29019441003e208.jpg"   data-ngThumb = "/imgs/0bb4a2b78a9c84c6f29019441003e208-thumb.jpg" > </a>
    
    
            <a href = "/imgs/0e1b84bef3fa9301dc93bd53f4b748a0.jpg"   data-ngThumb = "/imgs/0e1b84bef3fa9301dc93bd53f4b748a0-thumb.jpg" > </a>
    
    
            <a href = "/imgs/0e61c28b60a7c65710600e95dd67a2f4.jpg"   data-ngThumb = "/imgs/0e61c28b60a7c65710600e95dd67a2f4-thumb.jpg" > </a>
    
    
            <a href = "/imgs/113d4ec18937492ab7a8a0e16f4e5bfc.jpg"   data-ngThumb = "/imgs/113d4ec18937492ab7a8a0e16f4e5bfc-thumb.jpg" > </a>
    
    
            <a href = "/imgs/11ee2eaf317ecc4732cf6aafad9a28a8.jpg"   data-ngThumb = "/imgs/11ee2eaf317ecc4732cf6aafad9a28a8-thumb.jpg" > </a>
    
    
            <a href = "/imgs/137cec05df0b9192311e238a26a3d49d.jpg"   data-ngThumb = "/imgs/137cec05df0b9192311e238a26a3d49d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/156c8b08a88084fcabba32ce237d69eb.jpg"   data-ngThumb = "/imgs/156c8b08a88084fcabba32ce237d69eb-thumb.jpg" > </a>
    
    
            <a href = "/imgs/162d3af6f06ebf07da5069153441f90d.jpg"   data-ngThumb = "/imgs/162d3af6f06ebf07da5069153441f90d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/16bb9d3d1a6e1479e11445dc74269d8a.jpg"   data-ngThumb = "/imgs/16bb9d3d1a6e1479e11445dc74269d8a-thumb.jpg" > </a>
    
    
            <a href = "/imgs/173ad9043d93663c3641daddb44c9a45.jpg"   data-ngThumb = "/imgs/173ad9043d93663c3641daddb44c9a45-thumb.jpg" > </a>
    
    
            <a href = "/imgs/19720d22175f58e203c823be8b574787.jpg"   data-ngThumb = "/imgs/19720d22175f58e203c823be8b574787-thumb.jpg" > </a>
    
    
            <a href = "/imgs/1ce72d2c2d51a30a72afb5cbd3c6018d.jpg"   data-ngThumb = "/imgs/1ce72d2c2d51a30a72afb5cbd3c6018d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/1d326aa9fb3c88be7d3c37e63b1b1e10.jpeg"   data-ngThumb = "/imgs/1d326aa9fb3c88be7d3c37e63b1b1e10-thumb.jpeg" > </a>
    
    
            <a href = "/imgs/1e8568f8a9d179f3c3a4cea4fac8fca2.jpg"   data-ngThumb = "/imgs/1e8568f8a9d179f3c3a4cea4fac8fca2-thumb.jpg" > </a>
    
    
            <a href = "/imgs/1fd232b640ffee8289f45b351a0df30e.jpg"   data-ngThumb = "/imgs/1fd232b640ffee8289f45b351a0df30e-thumb.jpg" > </a>
    
    
            <a href = "/imgs/22557894e3435af7553eafdbc7946141.jpg"   data-ngThumb = "/imgs/22557894e3435af7553eafdbc7946141-thumb.jpg" > </a>
    
    
            <a href = "/imgs/226efa60b6bffb8d0d95110c34cc56d4.jpg"   data-ngThumb = "/imgs/226efa60b6bffb8d0d95110c34cc56d4-thumb.jpg" > </a>
    
    
            <a href = "/imgs/2341279d8f7fa97fd01f9d4d50bf7863.jpg"   data-ngThumb = "/imgs/2341279d8f7fa97fd01f9d4d50bf7863-thumb.jpg" > </a>
    
    
            <a href = "/imgs/23974b7e1f5e801c35108c94ab331fbe.jpg"   data-ngThumb = "/imgs/23974b7e1f5e801c35108c94ab331fbe-thumb.jpg" > </a>
    
    
            <a href = "/imgs/252507309ce9c8efd7364df88a060e31.jpg"   data-ngThumb = "/imgs/252507309ce9c8efd7364df88a060e31-thumb.jpg" > </a>
    
    
            <a href = "/imgs/29c4b8964dead63e920ba075f71f7925.jpg"   data-ngThumb = "/imgs/29c4b8964dead63e920ba075f71f7925-thumb.jpg" > </a>
    
    
            <a href = "/imgs/305645815db88ba6c497bada9d4e699b.jpg"   data-ngThumb = "/imgs/305645815db88ba6c497bada9d4e699b-thumb.jpg" > </a>
    
    
            <a href = "/imgs/30b76003ce51943f717cb97ad870bb7f.jpg"   data-ngThumb = "/imgs/30b76003ce51943f717cb97ad870bb7f-thumb.jpg" > </a>
    
    
            <a href = "/imgs/322c71c6d84a30a4e1e19b9a2b173e95.jpg"   data-ngThumb = "/imgs/322c71c6d84a30a4e1e19b9a2b173e95-thumb.jpg" > </a>
    
    
            <a href = "/imgs/3358f4755a7573477c762aecd4b3f9e2.jpg"   data-ngThumb = "/imgs/3358f4755a7573477c762aecd4b3f9e2-thumb.jpg" > </a>
    
    
            <a href = "/imgs/342792c796ed138917a7ae42c588f6fb.jpg"   data-ngThumb = "/imgs/342792c796ed138917a7ae42c588f6fb-thumb.jpg" > </a>
    
    
            <a href = "/imgs/3551be61211c8b74ef8b9e95f2f53d89.jpg"   data-ngThumb = "/imgs/3551be61211c8b74ef8b9e95f2f53d89-thumb.jpg" > </a>
    
    
            <a href = "/imgs/378fd57d16d3fcc8ae19579273bc155e.jpg"   data-ngThumb = "/imgs/378fd57d16d3fcc8ae19579273bc155e-thumb.jpg" > </a>
    
    
            <a href = "/imgs/44411058e0b3674d6f73484e4f355a7d.jpg"   data-ngThumb = "/imgs/44411058e0b3674d6f73484e4f355a7d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/486b44f761ec1a466d23fa921289ab0b.jpg"   data-ngThumb = "/imgs/486b44f761ec1a466d23fa921289ab0b-thumb.jpg" > </a>
    
    
            <a href = "/imgs/494d3664d79adba55911ba4e3d14a94d.jpg"   data-ngThumb = "/imgs/494d3664d79adba55911ba4e3d14a94d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/4967ccf689b409475d549c4d4838b4d9.jpg"   data-ngThumb = "/imgs/4967ccf689b409475d549c4d4838b4d9-thumb.jpg" > </a>
    
    
            <a href = "/imgs/4d467d56cc38dfa592a196b955911cf6.jpg"   data-ngThumb = "/imgs/4d467d56cc38dfa592a196b955911cf6-thumb.jpg" > </a>
    
    
            <a href = "/imgs/4e18f152b9977228eafec7844261db59.jpg"   data-ngThumb = "/imgs/4e18f152b9977228eafec7844261db59-thumb.jpg" > </a>
    
    
            <a href = "/imgs/51628ef602f5bf8f527f173dbb735c2c.jpg"   data-ngThumb = "/imgs/51628ef602f5bf8f527f173dbb735c2c-thumb.jpg" > </a>
    
    
            <a href = "/imgs/537495440438b1ec4cc1cbf9fd8d7ab8.jpg"   data-ngThumb = "/imgs/537495440438b1ec4cc1cbf9fd8d7ab8-thumb.jpg" > </a>
    
    
            <a href = "/imgs/55112c5ff3870cb7d982fa4e91767ef0.jpg"   data-ngThumb = "/imgs/55112c5ff3870cb7d982fa4e91767ef0-thumb.jpg" > </a>
    
    
            <a href = "/imgs/582af99f441d22dd930e02281ad6ab77.jpg"   data-ngThumb = "/imgs/582af99f441d22dd930e02281ad6ab77-thumb.jpg" > </a>
    
    
            <a href = "/imgs/58b83238f76466031c28cc9972639ddf.jpg"   data-ngThumb = "/imgs/58b83238f76466031c28cc9972639ddf-thumb.jpg" > </a>
    
    
            <a href = "/imgs/58cb7f8ed0279e7865823fa00d93fd9b.jpg"   data-ngThumb = "/imgs/58cb7f8ed0279e7865823fa00d93fd9b-thumb.jpg" > </a>
    
    
            <a href = "/imgs/5f173c7dc31ab37cc5c2958373632448.jpg"   data-ngThumb = "/imgs/5f173c7dc31ab37cc5c2958373632448-thumb.jpg" > </a>
    
    
            <a href = "/imgs/600b0807a92a962413b44f1131f91524.jpg"   data-ngThumb = "/imgs/600b0807a92a962413b44f1131f91524-thumb.jpg" > </a>
    
    
            <a href = "/imgs/6269d0f1472dec784af8fd1bb71ff343.jpg"   data-ngThumb = "/imgs/6269d0f1472dec784af8fd1bb71ff343-thumb.jpg" > </a>
    
    
            <a href = "/imgs/63260dee308c0f2efc3a8f189939a4e9.jpg"   data-ngThumb = "/imgs/63260dee308c0f2efc3a8f189939a4e9-thumb.jpg" > </a>
    
    
            <a href = "/imgs/6326926e66d74eb35f4185eba130ee2f.jpg"   data-ngThumb = "/imgs/6326926e66d74eb35f4185eba130ee2f-thumb.jpg" > </a>
    
    
            <a href = "/imgs/6981cfe0908a49042670a92159f4e4c8.jpg"   data-ngThumb = "/imgs/6981cfe0908a49042670a92159f4e4c8-thumb.jpg" > </a>
    
    
            <a href = "/imgs/6c293f75ed5621dd7158390af4ab0308.jpg"   data-ngThumb = "/imgs/6c293f75ed5621dd7158390af4ab0308-thumb.jpg" > </a>
    
    
            <a href = "/imgs/781248d9c76ebfd11192f4126f77f7ad.jpg"   data-ngThumb = "/imgs/781248d9c76ebfd11192f4126f77f7ad-thumb.jpg" > </a>
    
    
            <a href = "/imgs/78543bd5dcbc3bd3b894d4dbc767107d.jpg"   data-ngThumb = "/imgs/78543bd5dcbc3bd3b894d4dbc767107d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/7a0a79d09af32f179dd2090504b1685b.jpg"   data-ngThumb = "/imgs/7a0a79d09af32f179dd2090504b1685b-thumb.jpg" > </a>
    
    
            <a href = "/imgs/844c61b039186244636c2bb8a360648d.jpg"   data-ngThumb = "/imgs/844c61b039186244636c2bb8a360648d-thumb.jpg" > </a>
    
    
            <a href = "/imgs/862f58a33a1c5ba5b0eaa4c2820c0443.jpg"   data-ngThumb = "/imgs/862f58a33a1c5ba5b0eaa4c2820c0443-thumb.jpg" > </a>
    
    
            <a href = "/imgs/8bdaef02fe750887871edd92fc9df920.jpg"   data-ngThumb = "/imgs/8bdaef02fe750887871edd92fc9df920-thumb.jpg" > </a>
    
    
            <a href = "/imgs/8e66070e06ae2d39acc98c9782ceaf2f.jpg"   data-ngThumb = "/imgs/8e66070e06ae2d39acc98c9782ceaf2f-thumb.jpg" > </a>
    
    
            <a href = "/imgs/8ead3fb5cb301e137ed0e4ed84477ba0.jpg"   data-ngThumb = "/imgs/8ead3fb5cb301e137ed0e4ed84477ba0-thumb.jpg" > </a>
    
    
            <a href = "/imgs/8f094f4044eccbc7bee922aea7e784cb.jpg"   data-ngThumb = "/imgs/8f094f4044eccbc7bee922aea7e784cb-thumb.jpg" > </a>
    
    
            <a href = "/imgs/94dac48dc5dfdf3577c72926c25d4cb8.jpg"   data-ngThumb = "/imgs/94dac48dc5dfdf3577c72926c25d4cb8-thumb.jpg" > </a>
    
    
            <a href = "/imgs/96b03e7259efd6e08ff4fa33c53726e1.jpg"   data-ngThumb = "/imgs/96b03e7259efd6e08ff4fa33c53726e1-thumb.jpg" > </a>
    
    
            <a href = "/imgs/99431c7ad060f3f742e3c8e3a4c9a57c.jpg"   data-ngThumb = "/imgs/99431c7ad060f3f742e3c8e3a4c9a57c-thumb.jpg" > </a>
    
    
            <a href = "/imgs/9c4de1e239e1f8ff52f248ea453615ec.jpg"   data-ngThumb = "/imgs/9c4de1e239e1f8ff52f248ea453615ec-thumb.jpg" > </a>
    
    
            <a href = "/imgs/9d958f076c1e0105a77feaff8243cb42.jpg"   data-ngThumb = "/imgs/9d958f076c1e0105a77feaff8243cb42-thumb.jpg" > </a>
    
    
            <a href = "/imgs/9e1be91c65f86a423a4af082c0e6f147.jpg"   data-ngThumb = "/imgs/9e1be91c65f86a423a4af082c0e6f147-thumb.jpg" > </a>
    
    
            <a href = "/imgs/a0515f6b2e4f92ad21de85a5bb5256fc.jpg"   data-ngThumb = "/imgs/a0515f6b2e4f92ad21de85a5bb5256fc-thumb.jpg" > </a>
    
    
            <a href = "/imgs/a60d035a8afb277ea4338f0471a97c73.jpg"   data-ngThumb = "/imgs/a60d035a8afb277ea4338f0471a97c73-thumb.jpg" > </a>
    
    
            <a href = "/imgs/a6674551dc36ceb5db4d5cf82eb9a424.jpg"   data-ngThumb = "/imgs/a6674551dc36ceb5db4d5cf82eb9a424-thumb.jpg" > </a>
    
    
            <a href = "/imgs/ae9b3a22da6010190da7dc464b9c0ebb.jpg"   data-ngThumb = "/imgs/ae9b3a22da6010190da7dc464b9c0ebb-thumb.jpg" > </a>
    
    
            <a href = "/imgs/af654365e80d7c0bf947f591760fea35.jpg"   data-ngThumb = "/imgs/af654365e80d7c0bf947f591760fea35-thumb.jpg" > </a>
    
    
            <a href = "/imgs/b491542e29f926376b5941e6f04168a2.jpg"   data-ngThumb = "/imgs/b491542e29f926376b5941e6f04168a2-thumb.jpg" > </a>
    
    
            <a href = "/imgs/b6e30c15471be55af8471cc9f3ca3469.jpg"   data-ngThumb = "/imgs/b6e30c15471be55af8471cc9f3ca3469-thumb.jpg" > </a>
    
    
            <a href = "/imgs/bb1b25f49c7136463314f53fd98d88e5.jpg"   data-ngThumb = "/imgs/bb1b25f49c7136463314f53fd98d88e5-thumb.jpg" > </a>
    
    
            <a href = "/imgs/be5071aeee10ca261ce365a810033fa0.jpg"   data-ngThumb = "/imgs/be5071aeee10ca261ce365a810033fa0-thumb.jpg" > </a>
    
    
            <a href = "/imgs/c007e537aad6f547044db33bf0273375.jpg"   data-ngThumb = "/imgs/c007e537aad6f547044db33bf0273375-thumb.jpg" > </a>
    
    
            <a href = "/imgs/c0af59dbabc22047c84b4bbaa7a9e38c.jpg"   data-ngThumb = "/imgs/c0af59dbabc22047c84b4bbaa7a9e38c-thumb.jpg" > </a>
    
    
            <a href = "/imgs/c1873d39ad4e7ee0618e512a1f662b4f.jpg"   data-ngThumb = "/imgs/c1873d39ad4e7ee0618e512a1f662b4f-thumb.jpg" > </a>
    
    
            <a href = "/imgs/c6a60acc6c8475d2058c2ee6fe844cb7.jpg"   data-ngThumb = "/imgs/c6a60acc6c8475d2058c2ee6fe844cb7-thumb.jpg" > </a>
    
    
            <a href = "/imgs/c9c193288e05c56cc03f802195aef371.jpg"   data-ngThumb = "/imgs/c9c193288e05c56cc03f802195aef371-thumb.jpg" > </a>
    
    
            <a href = "/imgs/cdabf43f581d0035d5c8c784fd9a7e13.jpg"   data-ngThumb = "/imgs/cdabf43f581d0035d5c8c784fd9a7e13-thumb.jpg" > </a>
    
    
            <a href = "/imgs/ce7344d6720e90a3ad44c20fd078a84a.jpg"   data-ngThumb = "/imgs/ce7344d6720e90a3ad44c20fd078a84a-thumb.jpg" > </a>
    
    
            <a href = "/imgs/cfcf89686fac38ff9198f73ee4adc1c6.jpg"   data-ngThumb = "/imgs/cfcf89686fac38ff9198f73ee4adc1c6-thumb.jpg" > </a>
    
    
            <a href = "/imgs/d2de284a84ec92013daa8e041b723dbe.jpg"   data-ngThumb = "/imgs/d2de284a84ec92013daa8e041b723dbe-thumb.jpg" > </a>
    
    
            <a href = "/imgs/d7d72aaa63f07d9db61739c97931aca1.jpg"   data-ngThumb = "/imgs/d7d72aaa63f07d9db61739c97931aca1-thumb.jpg" > </a>
    
    
            <a href = "/imgs/db080f96b30819d634cd6afb383824ea.jpg"   data-ngThumb = "/imgs/db080f96b30819d634cd6afb383824ea-thumb.jpg" > </a>
    
    
            <a href = "/imgs/dc4915ecbe46536299fd1a8f0e17dcc2.jpg"   data-ngThumb = "/imgs/dc4915ecbe46536299fd1a8f0e17dcc2-thumb.jpg" > </a>
    
    
            <a href = "/imgs/dcae8b31fd55ab794e75550f6943f3c2.jpg"   data-ngThumb = "/imgs/dcae8b31fd55ab794e75550f6943f3c2-thumb.jpg" > </a>
    
    
            <a href = "/imgs/def85aca8280978859a45f22634d4982.jpg"   data-ngThumb = "/imgs/def85aca8280978859a45f22634d4982-thumb.jpg" > </a>
    
    
            <a href = "/imgs/e2ac05824f696c4f45a59d8a1376f86b.jpg"   data-ngThumb = "/imgs/e2ac05824f696c4f45a59d8a1376f86b-thumb.jpg" > </a>
    
    
            <a href = "/imgs/e36651423cc28960aeabb399380f29b9.jpg"   data-ngThumb = "/imgs/e36651423cc28960aeabb399380f29b9-thumb.jpg" > </a>
    
    
            <a href = "/imgs/e3963ca8975f09130cb63f998513f367.jpg"   data-ngThumb = "/imgs/e3963ca8975f09130cb63f998513f367-thumb.jpg" > </a>
    
    
            <a href = "/imgs/e515bca960ed9d210851d681037a9070.jpg"   data-ngThumb = "/imgs/e515bca960ed9d210851d681037a9070-thumb.jpg" > </a>
    
    
            <a href = "/imgs/e583966c78970ecd1f26c670e480b48a.jpg"   data-ngThumb = "/imgs/e583966c78970ecd1f26c670e480b48a-thumb.jpg" > </a>
    
    
            <a href = "/imgs/ec43f512122d4ef87efb5915a5a27869.jpg"   data-ngThumb = "/imgs/ec43f512122d4ef87efb5915a5a27869-thumb.jpg" > </a>
    
    
            <a href = "/imgs/eed3ea86102d356edcb17f72fcdada11.jpg"   data-ngThumb = "/imgs/eed3ea86102d356edcb17f72fcdada11-thumb.jpg" > </a>
    
    
            <a href = "/imgs/f090139e6db6a3abb21e7b6a3ae1da8e.jpg"   data-ngThumb = "/imgs/f090139e6db6a3abb21e7b6a3ae1da8e-thumb.jpg" > </a>
    
    
            <a href = "/imgs/f9ea974e0cd073c2d2a577654f6f7268.jpg"   data-ngThumb = "/imgs/f9ea974e0cd073c2d2a577654f6f7268-thumb.jpg" > </a>
    
    
            <a href = "/imgs/fa41f1b87480b37d878dbdd5a4ff2c5f.jpg"   data-ngThumb = "/imgs/fa41f1b87480b37d878dbdd5a4ff2c5f-thumb.jpg" > </a>
    
    
            <a href = "/imgs/fd61030e47405f5e43cb20b0b9a1fb9b.jpg"   data-ngThumb = "/imgs/fd61030e47405f5e43cb20b0b9a1fb9b-thumb.jpg" > </a>
    
</div>
]]></content>
        </item>
        
        <item>
            <title>My New Blog is Ready!</title>
            <link>https://xusheng.dev/posts/my_new_blog/</link>
            <pubDate>Thu, 11 Feb 2021 18:46:24 +0800</pubDate>
            <guid>https://xusheng.dev/posts/my_new_blog/</guid>
            <description>&lt;p&gt;I spent some time with &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; and now my blog is hosted on GitHub. Feel free to visit it at &lt;a href=&#34;https://xusheng.dev&#34;&gt;xusheng.dev&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I will write about reversing, coding, Xiangqi, and other stuff.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I spent some time with <a href="https://gohugo.io/">Hugo</a> and now my blog is hosted on GitHub. Feel free to visit it at <a href="https://xusheng.dev">xusheng.dev</a>!</p>
<p>I will write about reversing, coding, Xiangqi, and other stuff.</p>
]]></content>
        </item>
        
        <item>
            <title>How to Avoid Writing a Bad Crackme</title>
            <link>https://xusheng.dev/posts/reversing/how_to_not_write_a_bad_crackme/how_to_not_write_a_bad_crackme/</link>
            <pubDate>Tue, 29 Dec 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/how_to_not_write_a_bad_crackme/how_to_not_write_a_bad_crackme/</guid>
            <description>&lt;p&gt;Recently, I was promoted to a reviewer on crackmes.one (along with @zed). I am so honored with this and I appreciate the recognition and trust from @stan (creator of crackmes.one) and the entire community. The task for a reviewer is interesting, that I read submitted solutions and verify new crackmes. This allows me to grasp the latest trend on the website.&lt;/p&gt;
&lt;p&gt;I did not tally the statistics, but there is a fairly good amount of new submissions (of both crackmes and solutions) every week. And most of them are nice! For me, crackmes.one is a place for reversers to exchange knowledge and joyfulness. So I am very glad that we see a steady flow of input. Now that we have three reviewers, and I hope an increase in the reviewing speed will shorten the feedback loop for contributors, which, in turn, will lead to more contributions from the community.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Recently, I was promoted to a reviewer on crackmes.one (along with @zed). I am so honored with this and I appreciate the recognition and trust from @stan (creator of crackmes.one) and the entire community. The task for a reviewer is interesting, that I read submitted solutions and verify new crackmes. This allows me to grasp the latest trend on the website.</p>
<p>I did not tally the statistics, but there is a fairly good amount of new submissions (of both crackmes and solutions) every week. And most of them are nice! For me, crackmes.one is a place for reversers to exchange knowledge and joyfulness. So I am very glad that we see a steady flow of input. Now that we have three reviewers, and I hope an increase in the reviewing speed will shorten the feedback loop for contributors, which, in turn, will lead to more contributions from the community.</p>
<p>Nevertheless, some of the submissions did not meet our standards and got rejected. The reasons vary, but many of them are using existing obfuscator/protector. Among them, many have a dull verification algorithm and the sole challenge is to get past the obfuscator. We welcome the use of protectors/obfuscators, but we do not like the use of existing ones, especially commercial ones, e.g., VMP, WinLicense. These protectors are definitely breakable (trust me), but it is too hard for a crackme and it will take very long to solve. For folks who can do it, they would probably invest the time in some more important/interesting things, rather than spending a long time on it to break the protector, and only to find the actual algorithm is just on XOR.</p>
<p>Meanwhile, using existing tools deviates from the spirit of crackmes.one. As I wrote above, I believe this is a place for us reversers to <code>&quot;exchange knowledge and joyfulness&quot;</code>. We not only practice and improve our reversing skills but also share and obtain knowledge. However, using an existing tool does not help the author learn anything, beyond how to execute the tool, which is relatively simple. Conversely, if the author digs deep into an existing (open-source) tool, understands how it works, makes certain changes to defeat existing tools, s/he would learn more.</p>
<p>Below, I will list some of the things that we should better avoid when writing a crackme. Note, these rules are not absolute and I will write a longer version of explanation following it.</p>
<h2 id="donts">Don&rsquo;ts</h2>
<ol>
<li>Do not upload crackmes that are not written by you.</li>
<li>Do not upload malware or unwanted software of any kind, e.g., trojan, ransomware, adware, etc.</li>
<li>Do not use a commercial packer, protector, or obfuscator.</li>
<li>Do not upload a crackme that you cannot solve.</li>
<li>The crackme should not fail to execute. Please, no missing library dependencies or internal errors!</li>
<li>The crackme should not make network connections to any host other than localhost (127.0.0.1).</li>
<li>The crackme me should make it clear how it accepts/expects input (if any). And it should also clearly tell the player whether the input is correct.</li>
<li>The crackme must be solvable without guessing or a non-trivial amount of brute-forcing.</li>
<li>The crackme must be solvable in a reasonable time &ndash; when solved optimally.</li>
<li>The crackme should not rely on any hardware unique identifier as part of the algorithm.</li>
<li>The crackme should not stack unrelated levels of protection together.</li>
</ol>
<h2 id="justifications">Justifications</h2>
<p>A reader might notice some of the items above are too restrictive. So I will now explain the reason to set them and some of the exceptions for it. Also, if you are in doubt about a specific crackme or crackme idea, please contact one of the reviewers on Discord.</p>
<ol>
<li>The crackme should be the uploader&rsquo;s original work. Do not upload crackmes that have potential copyright issues. Do not upload crackmes you see on the Internet or in CTFs, unless you get permission to do so.</li>
<li>An exception is that one might make a pseudo-ransomware/malware that is a reverse engineering challenge. If that is the case, be sure to limit the damage to a very small and specific range (e.g., a <code>flag.txt</code> in the current dir), and state it clearly before the actual payload runs.</li>
<li>Using commercial packers, protectors, or obfuscators does not help challenge authors to learn and improve. And it could also take too long to solve. Also, avoid using any of these tools that already exist. Making your own or improving existing tools are very welcome!</li>
<li>Related to #3 and #8, do not make a crackme that even the author cannot solve.</li>
<li>This is a disappointing situation. Try to be compatible with more systems you target. Though we know that compatibility with all systems is impossible. At least test it on another computer and see if it works!</li>
<li>We discourage the use of network connections. Network traffic makes it harder to determine whether the program has any malicious behavior. If you need to have a network connection, only do that with the localhost. If you do not wish the player to temper with the &ldquo;remote&rdquo; server, still bundle the server and run it on the localhost, but tell the player not to reverse it.</li>
<li>If the crackme accepts inputs, e.g., user name and passwords, do not obscure the way it reads it. Also, be honest and tell the player if s/he solves it. Do not accept fake flags. Do not hide the flag somewhere that cannot be triggered by code execution. Note, this rule does not state that a crackme has to do password validation. We do have crackmes that ask the player to defeat the anti-debugging or decrypt a file that gets encrypted. These are good and not affected by this rule. In other words, if you have a novel challenge style, explain it to the player so they do not get lost.</li>
<li>Do not put the flag/secret in a function that is never gonna be executed. Do not make crackmes that the player has to guess something important to proceed.</li>
<li>Most crackmes can be solved instantly, or in a few seconds. I think a max 1-minute time limit is a reasonable recommended maximum.</li>
<li>Do not blindly add layers of protection, unless they form a cohesive unity. If protections are duplicated in large numbers, there should be a way to automatically tackle it.</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>Solving Two OCaml Crackmes Without Knowing Much about OCaml</title>
            <link>https://xusheng.dev/posts/reversing/ocaml_crackmes/readme/</link>
            <pubDate>Sun, 13 Dec 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/ocaml_crackmes/readme/</guid>
            <description>&lt;p&gt;Earlier this year, my friend &lt;a href=&#34;https://crackmes.one/user/Towel&#34;&gt;Towel&lt;/a&gt; uploaded two OCaml crackmes to crackmes.one. One of them is &lt;a href=&#34;https://crackmes.one/crackme/5f600af333c5d4357b3b01d6&#34;&gt;&lt;code&gt;Baby OCaml&lt;/code&gt;&lt;/a&gt;, and the other one is called &lt;a href=&#34;https://crackmes.one/crackme/5f600b9933c5d4357b3b01d7&#34;&gt;&lt;code&gt;Teenager OCaml&lt;/code&gt;&lt;/a&gt;. Well, interesting names!&lt;/p&gt;
&lt;p&gt;This is not the first time Towel came up with OCaml crackmes. &lt;a href=&#34;https://crackmes.one/crackme/5ec1b82133c5d449d91ae539&#34;&gt;&lt;code&gt;Qt Scanner&lt;/code&gt;&lt;/a&gt;, rated as level 5, is a hard challenge. I attempted that, but have not succeeded yet. So, when I first saw these two new OCaml challenges, I am not very eager to try them, despite they are rated as level 1 and 3. Nevertheless, we cannot hide from challenges forever, so I decided to try it last week. And the outcome is good, I managed to solve them without digging deep into the OCaml runtime.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Earlier this year, my friend <a href="https://crackmes.one/user/Towel">Towel</a> uploaded two OCaml crackmes to crackmes.one. One of them is <a href="https://crackmes.one/crackme/5f600af333c5d4357b3b01d6"><code>Baby OCaml</code></a>, and the other one is called <a href="https://crackmes.one/crackme/5f600b9933c5d4357b3b01d7"><code>Teenager OCaml</code></a>. Well, interesting names!</p>
<p>This is not the first time Towel came up with OCaml crackmes. <a href="https://crackmes.one/crackme/5ec1b82133c5d449d91ae539"><code>Qt Scanner</code></a>, rated as level 5, is a hard challenge. I attempted that, but have not succeeded yet. So, when I first saw these two new OCaml challenges, I am not very eager to try them, despite they are rated as level 1 and 3. Nevertheless, we cannot hide from challenges forever, so I decided to try it last week. And the outcome is good, I managed to solve them without digging deep into the OCaml runtime.</p>
<h2 id="baby-ocaml">Baby OCaml</h2>
<p>OCaml is an interpretive language, but it can be compiled to native code. This is in contrast to Python/PyInstaller, where the script is just packaged into the generated binary and we can restore the original source of it. The OCaml compiler generates native code based on the source code, and the source is not present within the generated binary. Worse still, when we deal with new programming languages, e.g., OCaml, Go, Rust, we are likely to encounter some novel things we do not expect. For example, Rust has a very different way of passing parameters and return values of a function. We need to first get familiar with it, then start reversing the actual code logic.</p>
<p>The Baby binary is 2.0 MB in size, which is HUGE for a crackme. The OCaml runtime will occupy lots of space in it, so we need to find the code that we are interested in. Opening the binary in BinaryNinja reveals that it is a statically linked binary:</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>OK, so even libc functions are not easy to find. But the entry point looks so familiar to me that I can still recognize the <code>call 0x470980</code> at 0x401c48 is <code>libc_start_main</code>, and <code>sub_401770</code> at 0x401c41 is the <code>main</code> function. However, the <code>main</code> function is mostly initializing the OCaml runtime, and I cannot find the actual entry point to the code.</p>
<p>Then I decided to run the binary and see if I can get any clue from it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>$ ./baby 
</span></span><span style="display:flex;"><span>-<span style="color:#f92672">=</span> Montrehack <span style="color:#f92672">=</span>-
</span></span><span style="display:flex;"><span>   Baby OCaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>!<span style="color:#f92672">]</span> Nope, try again.
</span></span></code></pre></div><p>Ok, it does not ask for input, so the input should probably be supplied as a command line argument. I tried to find the strings it prints but failed. Well, the strings must be encrypted or otherwise obfuscated. Now I cannot quickly find the logic that checks the input, so again this is a dead end.</p>
<p>I tried to reverse the binary for a half-day but cannot make a breakthrough. The call stack is deep and lots of function pointers are used. I was lost and put the binary aside for a while until one day Towel poked me to try his challenges. I told him that I cannot even solve the baby one, thanks to the string obfuscation. We chatted about the challenges a little bit, and I decided to give it a try again.</p>
<p>This time, I have to admit, that I am super lucky. I browsed the string list and spotted something unusual in the first few:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>Looks readable, right? I navigated to the location and the code seems to be comparing strings:</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>I am pretty sure the code is checking whether the ASCII string at <code>rax</code> is <code>Getting_Warmed_Up</code>. Note, the last char, <code>'p'</code>, with an ASCII value 0x70, is checked against 0x600000000000070. Well, due to little-endian, this will be effectively checking the lowest byte in the qword, but I have no idea what the 0x60 means. So OCaml runtime does have some weird things that are quite unusual.</p>
<p>Anyways, I solved the challenge:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>$ ./baby Getting_Warmed_Up
</span></span><span style="display:flex;"><span>-<span style="color:#f92672">=</span> Montrehack <span style="color:#f92672">=</span>-
</span></span><span style="display:flex;"><span>   Baby OCaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>+<span style="color:#f92672">]</span> Success!
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>FLAG-c34bc2bd73fdb06799061a8e76f62664
</span></span></code></pre></div><h2 id="tennager-ocaml">Tennager OCaml</h2>
<p>Although I did not solve the last challenge decently, I cannot wait to start working on the Teenager one. This binary is 1.9 MB in size. So, yeah, the size is mostly static libraries + OCaml runtime, and the size of the actual logic is almost negligible within it.</p>
<p>This time it does not use string obfuscation so I can easily locate the place where the binary asks for input:</p>
<p><img src="../imgs/4.png" alt=""></p>
<p>The control flow seems quite obvious, in the first node it asks for input, there there are two checks, and we must get to the lower left node to pass the check. I was pretty relieved when I saw this since there aren&rsquo;t many functions in this graph. However, it turns out I am naive and too optimistic about it.</p>
<p>The first thing that I cannot understand is&hellip;. the first check.</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>At 0x403168, <code>rbx</code> must be 0x2b, from which we can deduce that <code>rbx</code> must be 0x15 at 0x403163. And tracing back, it becomes weird. From debugging I noticed at 0x40314b, <code>rax</code> actually holds the ASCII string of the input. What could be located at <code>rax-0x8</code>? Well, I am not sure, but it is highly likely to be something related to the string&rsquo;s length. However, reading the code I cannot make any sense of it. I tried inputs with different lengths and the value does not change according to the input length.</p>
<p>Furthermore, at 0x40315b there is a <code>movzx rdi, byte [rax+rbx]</code>. We know <code>rax</code> is the string, if this is one of the input char, then this check is very strange. The length will be checked against one particular char, and the result must be 0x15.</p>
<p>Luckily, I debugged the code more and find after code at 0x403160, <code>rbx</code> always holds the length of the input. So this one is checking whether the string length is 0x15. The OCaml is yet unsolved, but I managed to get some information out of it.</p>
<p>Now, there are only three functions ahead, but I cannot trace the execution easily. The code uses lots of function pointers and I quickly get lost. A patient reverser would study the OCaml compiler to figure out how the code is generated, but I still have one thing to try: hardware breakpoint on the input string.</p>
<p>The plan is simple, we now know the string is held in <code>rax</code> at 0x40314b, then we can set a hardware breakpoint on it and see who accesses it. If everything goes well, we can find the code that reads the input, which is very likely to be also the checking logic code.</p>
<p>I set a breakpoint at 0x40317f, and supplied the input string &ldquo;111111111111111111111&rdquo; (which is just &lsquo;1&rsquo; * 0x15). It hits! Not bad, at least we are correct on the length check. The pwndbg shows <code>rax</code> does point to the input string:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>RAX  0x7ffff7ff9b90 ◂— <span style="color:#e6db74">&#39;111111111111111111111&#39;</span>
</span></span></code></pre></div><p>Then I set a hardware read breakpoint:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>pwndbg&gt; rwatch *0x7ffff7ff9b95
</span></span><span style="display:flex;"><span>Hardware read watchpoint 3: *0x7ffff7ff9b95
</span></span></code></pre></div><p>Note, the string starts at 0x7ffff7ff9b95, but I set a breakpoint at 0x7ffff7ff9b95, which is the 6th char of the input. This is a personal habit since there could potentially be more places that access the first char than we are interested in. On the other hand, the code that reads a char in the middle is more likely to be interesting and worth checking out.</p>
<p>The hardware breakpoint is hit at 0x402c07, and the instruction above it is reading the 6th char of the input:</p>
<p><img src="../imgs/6.png" alt=""></p>
<p>This function (sub_4024f0) looks like:</p>
<p><img src="../imgs/8.png" alt=""></p>
<p>So it is very likely that the function is checking very char one by one. This function has no xref to it at all, so I probably will not be able to find it easily, if I do not use hardware breakpoint. Inspecting the stack gives me the actual caller, 0x402410, and I have to say it is not easy to find the actual callee without debugging. The good news is if I were to reverse OCaml in the future, I know where to look at and with the help of debugging, I can hopefully find the callee and sort out the execution flow.</p>
<p><img src="../imgs/9.png" alt=""></p>
<p>I notice if the check passes, the return value is set to 0xa7. Remember the check at 0x4031e6? 0xd9f is quite a strange value, but it could be related to the 0xa7 here.</p>
<p><img src="../imgs/7.png" alt=""></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> hex(<span style="color:#ae81ff">0xd9f</span><span style="color:#f92672">/</span><span style="color:#ae81ff">0x15</span>)
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;0xa6&#39;</span>
</span></span></code></pre></div><p>So, there is some code, which I have not discovered, that minus 1 from the return value and then sum everything up. Now we know the check for each char, and it should not be hard to dump the constraints and solve it with z3.</p>
<p>I am lazy and do not wish to manually transcript the constraints and z3 syntax. However, angr does not easily work with it, thanks to the OCaml runtime, which angr does not understand. So I need to combine the power of BinaryNinja API to simplify the binary and enable angr to work with it.</p>
<h2 id="solving-with-binaryninja-and-angr">Solving with BinaryNinja and Angr</h2>
<p><img src="../imgs/10.png" alt=""></p>
<p>If we look at the basic block at 0x402d4c, there are two inputs to it: 1) the ASCII string in <code>rax</code>, 2) the value of <code>rbx</code> set at 0x402d24. We also need to extract the char index from the instruction at 0x402d4c (0x3 for in this screenshot). To get the initial value of <code>rbx</code>, we do not need to search for the instruction at 0x402d24. Instead, we can use the possible value set of <code>rbx</code> to get it. To enable angr, we also need to get the target address of the true/false branch of the conditional at 0x402d64.</p>
<h3 id="getting-truefalse-branch-address">Getting True/False Branch Address</h3>
<p>To get the good/bad branch, we first get the <code>outgoing_edges</code> of a basic block and check the <code>edge.type</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>bbl <span style="color:#f92672">=</span> bv<span style="color:#f92672">.</span>get_basic_blocks_at(addr)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>edges <span style="color:#f92672">=</span> bbl<span style="color:#f92672">.</span>outgoing_edges
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> edge <span style="color:#f92672">in</span> edges:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> edge<span style="color:#f92672">.</span>type <span style="color:#f92672">==</span> BranchType<span style="color:#f92672">.</span>TrueBranch:
</span></span><span style="display:flex;"><span>        good_addr <span style="color:#f92672">=</span> edge<span style="color:#f92672">.</span>target<span style="color:#f92672">.</span>start
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> edge<span style="color:#f92672">.</span>type <span style="color:#f92672">==</span> BranchType<span style="color:#f92672">.</span>FalseBranch:
</span></span><span style="display:flex;"><span>        bad_addr <span style="color:#f92672">=</span> edge<span style="color:#f92672">.</span>target<span style="color:#f92672">.</span>start
</span></span></code></pre></div><h3 id="parsing-llil-and-getting-char-index">Parsing LLIL and Getting Char Index</h3>
<p>For each constraint, we need to know the index of the char being checked. For example, for instruction <code>movzx rax, byte [rax+0x3]</code>, we need to get 0x3 from it. This requires us to walk the LLIL instruction and find its value.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">find_llil_basic_block</span>(llil_basic_blocks, addr):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> llil_bbl <span style="color:#f92672">in</span> llil_basic_blocks:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> llil_bbl[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>address <span style="color:#f92672">==</span> addr:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> llil_bbl
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>func <span style="color:#f92672">=</span> bv<span style="color:#f92672">.</span>get_functions_containing(addr)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>llil_basic_blocks <span style="color:#f92672">=</span> list(func<span style="color:#f92672">.</span>llil_basic_blocks)
</span></span><span style="display:flex;"><span>llil_bbl <span style="color:#f92672">=</span> find_llil_basic_block(llil_basic_blocks, addr)
</span></span><span style="display:flex;"><span>src <span style="color:#f92672">=</span> llil_bbl[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>operands[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>operands[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>operands[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>char_idx <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> src<span style="color:#f92672">.</span>operation <span style="color:#f92672">==</span> LowLevelILOperation<span style="color:#f92672">.</span>LLIL_ADD:
</span></span><span style="display:flex;"><span>    char_idx <span style="color:#f92672">=</span> src<span style="color:#f92672">.</span>operands[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>value<span style="color:#f92672">.</span>value
</span></span></code></pre></div><p>Note, the above code might not be very reader-friendly, e.g., <code>src = llil_bbl[0].operands[1].operands[0].operands[0]</code>. This is because LLIL is essentially a tree, and we are travelling down it.</p>
<h3 id="getting-the-possible-value-of-rbx">Getting the Possible Value of rbx</h3>
<p>To get the possible value of rbx when the execution enters the basic block, we need to use the <code>get_possible_reg_values</code> API.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>rbx_value <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>value_set <span style="color:#f92672">=</span> llil_bbl[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>get_possible_reg_values(<span style="color:#e6db74">&#39;rbx&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> value_set<span style="color:#f92672">.</span>type <span style="color:#f92672">==</span> RegisterValueType<span style="color:#f92672">.</span>ConstantValue:
</span></span><span style="display:flex;"><span>    rbx_value <span style="color:#f92672">=</span> value_set<span style="color:#f92672">.</span>value
</span></span><span style="display:flex;"><span>    rbx_value <span style="color:#f92672">&amp;=</span> <span style="color:#ae81ff">0xffffffffffffffff</span>
</span></span></code></pre></div><p>Note, not all of the check uses rbx. For them, the <code>value_set.type</code> will be <code>UnderterminedValue</code>, and rbx_value will remain 0x0. This has no side effect on solving.</p>
<h3 id="angr-time">Angr Time</h3>
<p>The last step is to solve it with angr:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">angr_solve</span>(addr, good_addr, bad_addr, char_idx, rbx_value):
</span></span><span style="display:flex;"><span>    proj <span style="color:#f92672">=</span> angr<span style="color:#f92672">.</span>Project(<span style="color:#e6db74">&#39;./teenager&#39;</span>)
</span></span><span style="display:flex;"><span>    state <span style="color:#f92672">=</span> proj<span style="color:#f92672">.</span>factory<span style="color:#f92672">.</span>entry_state(addr <span style="color:#f92672">=</span> addr)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># suppose the input string (ASCII) is stored at 0xaa000000</span>
</span></span><span style="display:flex;"><span>    input_addr <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xaa000000</span>
</span></span><span style="display:flex;"><span>    state<span style="color:#f92672">.</span>regs<span style="color:#f92672">.</span>rax <span style="color:#f92672">=</span> input_addr
</span></span><span style="display:flex;"><span>    state<span style="color:#f92672">.</span>regs<span style="color:#f92672">.</span>rbx <span style="color:#f92672">=</span> rbx_value
</span></span><span style="display:flex;"><span>    flag <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>solver<span style="color:#f92672">.</span>BVS(<span style="color:#e6db74">&#39;flag&#39;</span>, <span style="color:#ae81ff">8</span>)
</span></span><span style="display:flex;"><span>    state<span style="color:#f92672">.</span>memory<span style="color:#f92672">.</span>store(input_addr <span style="color:#f92672">+</span> char_idx, flag)
</span></span><span style="display:flex;"><span>    simgr <span style="color:#f92672">=</span> proj<span style="color:#f92672">.</span>factory<span style="color:#f92672">.</span>simgr(state)
</span></span><span style="display:flex;"><span>    simgr<span style="color:#f92672">.</span>explore(find <span style="color:#f92672">=</span> good_addr, avoid <span style="color:#f92672">=</span> [bad_addr])
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> simgr<span style="color:#f92672">.</span>found:
</span></span><span style="display:flex;"><span>        solution_state <span style="color:#f92672">=</span> simgr<span style="color:#f92672">.</span>found[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        char_solution <span style="color:#f92672">=</span> solution_state<span style="color:#f92672">.</span>solver<span style="color:#f92672">.</span>eval(flag, cast_to <span style="color:#f92672">=</span> bytes)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>, char_solution
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">False</span>, <span style="color:#66d9ef">None</span>
</span></span></code></pre></div><p>Note, the above script is not super robust, since we really expect the solving to succeed.</p>
<p>We still need to manually collect the 0x15 address of the basic blocks. Although it is possible to automatically collect them, I feel the time to make it work will be longer than just select and copy 0x15 addresses.</p>
<p>The script returns <code>0CamL_Ints_Ar3_W4rped</code>, and feeding it to the challenge gives me:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Bash" data-lang="Bash"><span style="display:flex;"><span>$ ./teenager 
</span></span><span style="display:flex;"><span>-<span style="color:#f92672">=</span> Montrehack <span style="color:#f92672">=</span>-
</span></span><span style="display:flex;"><span>   Teenager
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Enter Password: 0CamL_Ints_Ar3_W4rped
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>+<span style="color:#f92672">]</span> Success!
</span></span><span style="display:flex;"><span>FLAG-221fddd2bbf810be10d156b060b0eda5
</span></span></code></pre></div><p>This reminds me of the description of the challenge:</p>
<pre tabindex="0"><code>A slightly harder OCaml challenge to get practice with OCaml integer representations.
</code></pre><p>So, it seems that I solved without knowing anything about OCaml integer representation.</p>
]]></content>
        </item>
        
        <item>
            <title>Deciphering a Windows Anti-debugging Challenge</title>
            <link>https://xusheng.dev/posts/reversing/reverseme3/readme/</link>
            <pubDate>Sun, 29 Nov 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/reverseme3/readme/</guid>
            <description>&lt;p&gt;It has been a long while since I last wrote about anything. We try to post something every week, but it has been, at least for me, super busy recently. So sorry for the gap. The good news is I am going to post several writeups recently.&lt;/p&gt;
&lt;p&gt;This time I am writing about the challenge &lt;code&gt;ReverseMe3&lt;/code&gt; from &lt;code&gt;jochen_&lt;/code&gt;. The challenge can be found on &lt;a href=&#34;https://crackmes.one/crackme/5fb1642933c5d424269a1850&#34;&gt;crackmes.one&lt;/a&gt;. The password to unzip is &amp;ldquo;crackmes.one&amp;rdquo;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>It has been a long while since I last wrote about anything. We try to post something every week, but it has been, at least for me, super busy recently. So sorry for the gap. The good news is I am going to post several writeups recently.</p>
<p>This time I am writing about the challenge <code>ReverseMe3</code> from <code>jochen_</code>. The challenge can be found on <a href="https://crackmes.one/crackme/5fb1642933c5d424269a1850">crackmes.one</a>. The password to unzip is &ldquo;crackmes.one&rdquo;.</p>
<p>The description given on the challenge page says the program will show a message box when it not running under a debugger. And the goal is to make it also show the message box when it is running under a debugger. Basically, to circumvent the anti-debugging techniques.</p>
<p>Interestingly, the author mentions the program only runs properly on <code>3 latest builds of Windows 10. 1909 2004 and 20H2</code>. And if we were to conquer it inside a VM, we have to bypass one more check. So maybe the program uses some new feature that is introduced in the latest versions? Or it might be relying on low-level/un-documented features that only work on these versions. I do not have a VM that has the proper Windows build version, so I decide to solve it statically.</p>
<p>As always, I will not only explain how to solve it correctly &ndash; I will try to mention many of my thought processes as well as some detours that I have took. I believe this is more interesting to read than a flawless straight-sail.</p>
<h2 id="first-impression">First Impression</h2>
<p>The challenge binary <code>ReverseMe3.EXE</code> is only 2.6 KB, relatively small. Loading it into BinaryNinja quickly reveals something unusual:</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>It first sets <code>eax</code> to 0x40, <code>bswap</code> it, and then calls <code>cpuid</code>. the <code>bswap</code> will make eax <code>0x40000000</code>, and according to my memory that does not return anything useful for <code>cpuid</code>. However, at <code>0x40101d</code>, the return value in <code>ecx</code> is moved into <code>eax</code>, which is then used to decrypt the code starting from <code>0x401030</code>. The code to be decrypted is 0x258 bytes long. Code at <code>00401030</code> immediately follows the decryption loop, and of course, it cannot be properly disassembled since it is still encrypted.</p>
<p>From what I see now the challenge is probably hand-written. I like hand-written challenges since it can be denser in terms of tricks and traps, which is the fun of reverse engineering.</p>
<p>Note the decryption only uses one byte from the return value of <code>cpuid</code>, so it is possible to try all 256 possibilities, disassemble the decrypted code and see which one can be disassembled properly. The same technique is used in one of the crackmes in the book &ldquo;Reversing: Secrets of Reverse Engineering&rdquo;.</p>
<h2 id="cpuid">CPUID</h2>
<p>But we do not have to do the hard work, at least for now. We can see what <code>cpuid</code> is returning and decrypt the code with the correct return value. However, as stated in the challenge description, this challenge does not run properly in a VM. And we know that <code>cpuid</code> sometimes returns different values inside/outside the VM. So if we actually run the program, we probably get a wrong return value.</p>
<p>Anyways, let us have a look at of the documentation about <code>cpuid</code>. In fact, <code>eax</code> register determines the type of information that will be returned by the <code>cpuid</code>. For example, if <code>eax</code> is zero upon the execution, <code>cpuid</code> will return the maximum valid input value for basic CPUID information. Besides, a value that is larger than 0x80000000 will request extended CPUID information. However, these do not help us since the input is 0x40000000.</p>
<p>In the doc, anything between 0x40000000 - 0x4FFFFFFF is descibed as:</p>
<pre tabindex="0"><code>Invalid. No existing or future CPU will return processor identification or feature information if the initial EAX value is in the range 40000000H to 4FFFFFFFH.
</code></pre><p>I was tempted to think the return value will be undefined, or even a certain kind of fault will be triggered. However, we see clearly from the code that not only the <code>cpuid</code> should execute properly, its return value should be stable.</p>
<p>Upon closer inspection of the documentation, I find that:</p>
<pre tabindex="0"><code>If a value is entered for CPUID.EAX is higher than the maximum input value for basic or extended function for that processor then the data for the highest basic information leaf is returned.
</code></pre><p>I am not sure 0x40000000 is greater than 0x80000000 &ndash; maybe they do a signed comparison. Anyways, we know what is going to happen: <code>the data for the highest basic information leaf is returned.</code></p>
<p>Now that we still need to find the concrete return value. I do have a Windows VM, but that gives me 0x56 in <code>cl</code>, which after the decryption, gives me garbage rather than valid code. I am not planning to install an actual Windows machine to solve this challenge, so what should I do now?</p>
<p>I soon realize, for things like <code>cpuid</code>, it does not matter what OS I run. I am currently on Linux, but the value returned should be the same. I launched <a href="https://github.com/yrp604/rappel"><code>rappel</code></a>, an assembly REPL tool.</p>
<pre tabindex="0"><code>&gt; mov eax, 0x40000000
&gt; cpuid
rax=00000000000008fc rbx=00000000000012c0 rcx=0000000000000064
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=0000000000400003 rsp=0000000000000033 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
[cf=0, zf=0, of=0, sf=0, pf=0, af=0, df=0]
cs=002b  ss=0000  ds=0000  es=0000  fs=0000  gs=0000            efl=00000202
</code></pre><p>The return value of <code>ecx</code> is 0x64. Is this reliable? Will it always return the same value on different machines? I tried to find the maximum index of the basic information by calling <code>cpuid</code> when eax is set to 0:</p>
<pre tabindex="0"><code>&gt; mov eax, 0
&gt; cpuid
rax=0000000000000016 rbx=00000000756e6547 rcx=000000006c65746e
rdx=0000000049656e69 rsi=0000000000000000 rdi=0000000000000000
rip=0000000000400003 rsp=0000000000000033 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
[cf=0, zf=0, of=0, sf=0, pf=0, af=0, df=0]
cs=002b  ss=0000  ds=0000  es=0000  fs=0000  gs=0000            efl=00000202
</code></pre><p>We can see the return value of eax 9s 0x16, which means the maximum basic information is 0x16. Then I set <code>eax</code> to 0x16 and call <code>cpuid</code> again:</p>
<pre tabindex="0"><code>&gt; mov eax, 0x16
&gt; cpuid
rax=00000000000008fc rbx=00000000000012c0 rcx=0000000000000064
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=0000000000400003 rsp=0000000000000033 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
[cf=0, zf=0, of=0, sf=0, pf=0, af=0, df=0]
cs=002b  ss=0000  ds=0000  es=0000  fs=0000  gs=0000            efl=00000202
</code></pre><p>Not bad, the same output is returned. So I am pretty confident calling <code>cpuid</code> with eax set to 0x40000000 has the same effect as calling it with 0x16. Looking for the value 0x16 in the table in Intel docs tells me that it returns <code>Processor Frequency Information Leaf</code>. For register <code>ecx</code>, it says &ldquo;Bits 15 - 00: Bus (Reference) Frequency (in MHz).&rdquo;</p>
<p>I am not sure whether this information is all the same on different machines since such bus frequency could vary in different cases. Please feel free to get in touch with me if you have any ideas about this! Anyways, if I do install a Windows machine and execute the program, I should get the same output. I proceeded with the value and used it to decrypt the 0x258 bytes of encrypted code, starting at 0x401030. BinaryNinja makes it super easy to transform the code, and the output looks valid:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>Before we proceed to analyze the decrypted code, I would like to see what is returned on a VM. I used a similar assembly REPL tool, <a href="https://github.com/zerosum0x0/WinREPL">WinREPL</a>, to checkout the result on Windows:</p>
<pre tabindex="0"><code>&gt;&gt;&gt; mov eax, 0x40000000
&gt;&gt;&gt; cpuid
assembled (2 bytes): 0f a2
rax: 0000000040000006 rbx: 00000000786f4256 rcx: 00000000786f4256 rdx: 00000000786f4256
r8 : 0000000000000000 r9 : 0000000000000000 r10: 0000000000000000 r11: 0000000000000000
r12: 0000000000000000 r13: 0000000000000000 r14: 0000000000000000 r15: 0000000000000000
rsi: 0000000000000000 rdi: 0000000000000000
rip: 0000023ca56f000e rsp: 000000478b2fef00 rbp: 0000000000000000
flags: 00000200  CF: 0  PF: 0  AF: 0  ZF: 0  SF: 0  DF: 0  OF: 0
</code></pre><p>When we run <code>cpuid</code> with <code>eax</code> set to 0x40000000, the return value in ecx is actually the string &ldquo;VBox&rdquo;. I am not entirely sure which basic information it is trying to return, so I set <code>eax</code> to 0 to find out:</p>
<pre tabindex="0"><code>&gt;&gt;&gt; mov eax, 0
&gt;&gt;&gt; cpuid
assembled (2 bytes): 0f a2
rax: 0000000000000016 rbx: 00000000756e6547 rcx: 000000006c65746e rdx: 0000000049656e69
r8 : 0000000000000000 r9 : 0000000000000000 r10: 0000000000000000 r11: 0000000000000000
r12: 0000000000000000 r13: 0000000000000000 r14: 0000000000000000 r15: 0000000000000000
rsi: 0000000000000000 rdi: 0000000000000000
rip: 0000023ca56f0007 rsp: 000000478b2fef00 rbp: 0000000000000000
flags: 00000200  CF: 0  PF: 0  AF: 0  ZF: 0  SF: 0  DF: 0  OF: 0
</code></pre><p>Not bad, it returns the same value when running on a real machine, 0x16. However, when I run <code>cpuid</code> with <code>eax</code> set to 0x16, I get the surprising output:</p>
<pre tabindex="0"><code>&gt;&gt;&gt; mov eax, 0x16
&gt;&gt;&gt; cpuid
assembled (2 bytes): 0f a2
rax: 0000000000000000 rbx: 0000000000000000 rcx: 0000000000000000 rdx: 0000000000000000
r8 : 0000000000000000 r9 : 0000000000000000 r10: 0000000000000000 r11: 0000000000000000
r12: 0000000000000000 r13: 0000000000000000 r14: 0000000000000000 r15: 0000000000000000
rsi: 0000000000000000 rdi: 0000000000000000
rip: 0000023ca56f0015 rsp: 000000478b2fef00 rbp: 0000000000000000
flags: 00000200  CF: 0  PF: 0  AF: 0  ZF: 0  SF: 0  DF: 0  OF: 0
</code></pre><p>Then return values are all zero, and they are different from those returned when we set <code>eax</code> to 0x40000000. To sum up, the output is wrong in two senses: 1). when we set <code>eax</code> to 0x16, it does not return a valid CPUID information; 2). when we set <code>eax</code> to 0x40000000, it does not give the same output as if we were running <code>cpuid</code> with <code>eax</code> set to the maximum index of basic information, in this case, 0x16. So these two are subtle differences between a real machine and a VM, which can be used as VM detection as well.</p>
<h2 id="native-syscall-on-windows">Native Syscall on Windows</h2>
<p>Now it is time to analyze the decrypted code. Looking at the image above, we notice it first calls <code>NTCreateThread</code> to create a thread, whose entry is at 0x4010ce. And it then makes two system calls using the <code>syscall</code> instruction. Note the thread entry 0x4010ce is right below the code that makes syscalls, and the two syscalls do not seem to transfer the control to any other places, so it is very likely they are not doing something special.</p>
<p>But we still need to figure out what the two syscalls are doing. I did not see any challenges using <code>syscall</code> on Windows, mostly because the system call index is opaque on Windows, and they can be different across different system versions. Oh, this could be the reason why the author says it only works on specific versions of Windows &ndash; it relies on the specific index of certain system calls.</p>
<p>There are many ways to dump the system call on a Windows system. However, since my Windows VM is having a wrong build version, the system call index I can get is also probably NOT the same as the author expects. So I searched online for a bit and found something already organized the information into a nice searchable table at <a href="https://j00ru.vexillium.org/syscalls/nt/64/">https://j00ru.vexillium.org/syscalls/nt/64/</a>.</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>So the first syscall is made with eax = 4, and I found it is <code>NtWaitForSingleObject</code>. And the object it waits for happens to be the newly created thread. The next system call is 0x2c, which is <code>NtTerminateProcess</code>. So the remaining code just waits for the thread to finish, and terminate the process.</p>
<p>We also see that the index for these two system calls is different on different major Windows versions, e.g., Win7 v.s. Win10, but they remain the same within different Win10 versions. So they do not account for the special requirement for the three Win10 builds.</p>
<p>So now we shift the focus of the analysis to the thread routine, as shown below:</p>
<p><img src="../imgs/4.png" alt=""></p>
<p>It starts by making a syscall 0xd, which translates to <code>NtSetInformationThread</code>, with <code>r9</code> set to 0x11, which means <code>ThreadHideFromDebugger</code>. This is a common anti-debug technique that tried to hide the thread from the debugger. The thread will continue to execute, but the debugger will no longer be notified by any debug events related to the thread.</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>Next, it retrieves <code>PEB</code> at <code>gs:0x60</code>, at checks whether the field at 0x118 is equal to 0xa. Inspecting the structure with windbg shows (some output omitted):</p>
<pre tabindex="0"><code>lkd&gt; dt _PEB
nt!_PEB
...
   +0x118 OSMajorVersion   : Uint4B
   +0x11c OSMinorVersion   : Uint4B
   +0x120 OSBuildNumber    : Uint2B
...
</code></pre><p>So it is checking whether the OSMajorVersion is 10. Yeah, so it is checking whether this is a Win10.</p>
<p><img src="../imgs/6.png" alt=""></p>
<p>Moving downward, we see that it is checking OSBuildNumber for a specific value, 0x47bb. I have no idea what it is when I first see it (though there are ways to figure it out). If the version matches, we see it is setting the dword at 0x401149 to be 0xa1. At first, I do not understand it, so I skipped it and moved to analyze the code at 0x401125.</p>
<p>The next system call it makes is 0xa5. Searching it on the previous webpage gives me an interesting result (the image is cropped to show only the important part):</p>
<p><img src="../imgs/7.png" alt=""></p>
<p>We can see on the latest two versions of Win10, i.e., version 2004 and 20H2, system call 0xa5 means <code>NtCreateDebugObject</code>. However, on version 1909, 0xa5 means a different system call and <code>NtCreateDebugObject</code> is 0xa1. Hmmm, 0xa1, does it look familiar? We have just seen it at address 0x40111e, right? And it is writing to <code>data_401149</code>, in the middle of an instruction. I suddenly understand what it is doing: it is patching the instruction at 0x401148 to <code>&quot;mov eax, 0xa1&quot;</code>, when the current system has OSBuildNumber 0x47bb.</p>
<p>Now I am pretty sure 0x47bb means Win10 version 1909. And the code uses a small patching trick to make sure that always calls <code>NtCreateDebugObject</code>, even if the system call number varies. Besides, this also explains why the program only works on a very limited number of Win10 builds: the system call number of other Win10 builds are different as well, and there is no code to take care of that.</p>
<p>A careful reader should have noticed that the code should also work on Win10 version 1903, which happens to have the system call number 0xa1 for <code>NtCreateDebugObject</code>. The only change needed is to also change the instruction to <code>&quot;mov eax, 0xa1&quot;</code> when a Win10 1903 is encountered.</p>
<p>Now, let us get back to the code itself. It is calling <code>NtCreateDebugObject</code>, which is likely to be a common anti-debugging technique. Although I know this technique, I cannot remember the details of it. So let me research the code as if it is something new to me.</p>
<p>The first thing to find out is the definition of the system call, which helps us understand the meaning of its parameters. MS does not document it, but we can still find some clue by searching withing the ReactOS source code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span>NTSTATUS
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">NtCreateDebugObject</span>(
</span></span><span style="display:flex;"><span>    _Out_ PHANDLE DebugHandle,
</span></span><span style="display:flex;"><span>    _In_ ACCESS_MASK DesiredAccess,
</span></span><span style="display:flex;"><span>    _In_ POBJECT_ATTRIBUTES ObjectAttributes,
</span></span><span style="display:flex;"><span>    _In_ ULONG Flags
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Alternatively, we can always search it with Duckduckgo and this time we will be lucky since <code>Process Hacker</code> also uses it: <a href="https://processhacker.sourceforge.io/doc/ntdbg_8h.html#aaf201d37b7597c3997ba3380de6253dd">https://processhacker.sourceforge.io/doc/ntdbg_8h.html#aaf201d37b7597c3997ba3380de6253dd</a>.</p>
<p>Among the four parameters, the interesting one is the first one, <code>DebugHandle</code>. Note the Windows x64 calling convention passes the first four parameters in the order of <code>rcx, rdx, r8, r9</code>. So we know <code>data_4015ac</code> is the returned <code>DebugHandle</code>. Then the code checks the return value and the system call <code>NtCreateDebugObject</code> must have succeeded.</p>
<p><img src="../imgs/8.png" alt=""></p>
<h2 id="anti-debugging-techniques">Anti-debugging Techniques</h2>
<p>The next basic block is longer, which starts with two system calls and also includes a function call. Note the last two instructions, i.e.,</p>
<pre tabindex="0"><code>push 0x401288
ret
</code></pre><p>will transfer the control flow to 0x401288. The bytes at 0x401288 do not look like valid code yet, so there might be a second round of decryption. Scanning upwards I see at address 0x4011dc, 0x401288 is moved into <code>rcx</code>, along with a strange string <code>&quot;1c4TLKe6Px8M2fN7iAlC&quot;</code>. sub_401226 is very likely a decryption function! Let us have a look at it first:</p>
<p><img src="../imgs/9.png" alt=""></p>
<p>It is a rather small and simple function: it uses a string as the key to xor decrypt the given data. The actual xor happens at 0x401256. Its four parameters are:</p>
<pre tabindex="0"><code>rcx: data to decrypt
rdx: data length
r8:  xor key
r9:  key length
</code></pre><p>Comparing these with the call-site at 0x4011f8, we can know that the data (code) at 0x401288 is 0x143 bytes long, and the xor key is <code>&quot;1c4TLKe6Px8M2fN7iAlC&quot;</code>, which is 0x14 bytes long.</p>
<p>However, if we proceed to decrypt the code, we will get the wrong result. The author sets up another trap here. The xor key string is written to before the decryption. This can be seen in a more obvious way if we define it as a string:</p>
<p><img src="../imgs/10.png" alt="">
<img src="../imgs/11.png" alt=""></p>
<p><code>xor_key[4]</code> and <code>xor_key[0x13]</code> are both changed before the decryption. And the new value is derived from <code>data_4014b8</code> and <code>data_4015bc</code>, which we need to figure out now.</p>
<p><code>data_4015bc</code> is easier to figure out, since we get cross reference to it, right in the code above:</p>
<p><img src="../imgs/12.png" alt=""></p>
<p>It is calling <code>NtQueryInformationThread</code> with the second parameter being <code>ThreadHideFromDebugger</code> to query whether <code>ThreadHideFromDebugger</code> is set. Remember the code at 0x4010ef, which uses <code>NtSetInformationThread</code> to set <code>ThreadHideFromDebugger</code> to true (0x1)? Here, the code is checking its value. It is trying to verify we do not change the value. Since a common way to circumvent the <code>ThreadHideFromDebugger</code> is to skip the <code>NtQueryInformationThread</code> call, in which case the thread will not hide from the debugger, but the query result will return false (0x0). Further, the program does not check whether the return value is true or false, rather it uses it as part of the decryption key. Had the program been running outside of a debugger, <code>data_4015bc</code> should have value 0x1 at 0x4011b6. If the value is altered, the program will appear to be running fine, but the decrypted code will contain errors.</p>
<p>This is an aha moment in reversing, and it is one of the reasons I love reversing. Although I do not know the author of the challenge when I reached this point, I enjoy the obstacle he set up here. It even feels that we had a short but pleasant virtual conversation about this challenge.</p>
<p>Also, this reminds me of checking out the implementation of <a href="https://github.com/x64dbg/ScyllaHide">ScyllaHide</a>, the popular anti-anti-debug plugin for x64db, to see if it handles this correctly. Which I will cover at the end of the write-up.</p>
<p>What about <code>data_4014b8</code>? There is no xref to it. But it is pretty close to another data variable <code>data_4014a8</code>,</p>
<p><img src="../imgs/13.png" alt=""></p>
<p><code>data_4014a8</code> is referenced in the remaining system all that we have not analyzed yet:</p>
<p><img src="../imgs/14.png" alt=""></p>
<p>If <code>data_4014a8</code> is a non-trivial structure, then <code>data_4014b8</code> is very likely a field inside of it. And its value will be determined by the <code>NtQueryObject</code> call.</p>
<p><code>NtQueryObject</code> happens to be <a href="https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryobject">documented</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span>__kernel_entry NTSYSCALLAPI NTSTATUS <span style="color:#a6e22e">NtQueryObject</span>(
</span></span><span style="display:flex;"><span>  HANDLE                   Handle,
</span></span><span style="display:flex;"><span>  OBJECT_INFORMATION_CLASS ObjectInformationClass,
</span></span><span style="display:flex;"><span>  PVOID                    ObjectInformation,
</span></span><span style="display:flex;"><span>  ULONG                    ObjectInformationLength,
</span></span><span style="display:flex;"><span>  PULONG                   ReturnLength
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p><code>rdx</code> is <code>ObjectInformationClass</code> and its value is 0x2, <code>r8</code> holds <code>ata_401478</code> and it is the <code>ObjectInformation</code> that we are interested in.</p>
<p>Searching the MSVC headers or ReactOS source gives the following definition for <code>OBJECT_INFORMATION_CLASS</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">enum</span> _OBJECT_INFORMATION_CLASS {
</span></span><span style="display:flex;"><span>    ObjectBasicInformation,
</span></span><span style="display:flex;"><span>    ObjectNameInformation,
</span></span><span style="display:flex;"><span>    ObjectTypeInformation,
</span></span><span style="display:flex;"><span>    ObjectAllTypesInformation,
</span></span><span style="display:flex;"><span>    ObjectHandleInformation
</span></span><span style="display:flex;"><span>} OBJECT_INFO_CLASS;
</span></span></code></pre></div><p>And 0x2 for <code>ObjectInformationClass</code> means <code>ObjectTypeInformation</code>.</p>
<p>The reverse-engineered definition of <code>OBJECT_TYPE_INFORMATION</code> can be found at <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ob/obquery/type.htm">geoffchappell&rsquo;s site</a>:</p>
<p><img src="../imgs/15.png" alt=""></p>
<p>(The screenshot is cut short to save space)</p>
<p>We can do the calculation and find <code>data_4014a8</code> is at offset 0x10 into the structure at <code>data_4014a8</code>, and the field is <code>ULONG TotalNumberOfObjects</code>. What is the <code>correct</code> value of <code>TotalNumberOfObjects</code>, if the program is not being debugged? Details can be found <a href="https://ntquery.wordpress.com/2014/03/30/anti-debug-ntqueryobject/">here</a> and it should be 0x1. Now the code looks like this:</p>
<p><img src="../imgs/16.png" alt=""></p>
<p>And we know both <code>object_type.TotalNumberOfObjects</code> and <code>thread_hide_from_debugger</code> is 0x1, so we can deduce <code>xor_key[4]</code> will be changed to 0x42 (&lsquo;B&rsquo;), and <code>xor_key[0x13]</code> will be changed to 0x51 (&lsquo;Q&rsquo;). And the <code>xor_key</code> becomes <code>&quot;1c4TBKe6Px8M2fN7iAlQ&quot;</code>. Decrypting 0x143 bytes starting from 0x401288 gives me the following code:</p>
<p><img src="../imgs/17.png" alt=""></p>
<h2 id="resolving-dll-imports-with-function-name-hash">Resolving DLL Imports with Function Name Hash</h2>
<p>The call at 0x40129c and 0x4012b7 looks way too familiar to me. It is finding the address of a particular function inside a DLL. The difference between the ordinary GetProcAddr is that it uses a hash of the function name, rather than the function name itself for the lookup. There is a <code>uint32_t hash(char* function_name)</code> that returns a dword as the hash for the function name. And the code walks the export table of the given DLL, calculate the hash of every export function, and see if there is a match. In this example, the two hashes are 0xa216a185 and 0x9a9c4525.</p>
<p>How do we know which function it tries to find? Well, for this simple example, we can guess from the code that the first one is a <code>LoadLibraryA</code>, and the second one is a <code>MessageBoxA</code> (from the challenge description). However, if we need to deobfuscate a lot of functions, guessing is not a good idea.</p>
<p>There are mostly two ways to deal with it. The first one is to run the code, ask itself to find the function, and we just write it down. This is helpful when the number of functions is small, and the code can run properly. If we are analyzing a large binary or a memory dump, then it is not feasible.</p>
<p>The second way is to reverse engineer the hash function, enumerate the export table of the DLL, and try to find a match by ourselves.</p>
<p><img src="../imgs/18.png" alt=""></p>
<p>The function needs to process the PE format so it is not trivial. But there is a shortcut to deal with it. We can see the loop at 0x401372 is operating on a string in <code>rsi</code>, and the result is put into <code>rdi</code>. At 0x401381, the calculated hash is compared with the <code>expected_hash</code>, which is an argument of the function. If they match, the code proceeds to find the address of the function; if they do not match, the code proceeds to the next loop.</p>
<p>So we only need to reverse engineer the loop at 0x401372. A Python equivalent is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">rol</span>(val, n):
</span></span><span style="display:flex;"><span>    bin_str <span style="color:#f92672">=</span> bin(val)[<span style="color:#ae81ff">2</span>:]
</span></span><span style="display:flex;"><span>    bin_str <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;0&#39;</span> <span style="color:#f92672">*</span> (<span style="color:#ae81ff">32</span> <span style="color:#f92672">-</span> len(bin_str)) <span style="color:#f92672">+</span> bin_str
</span></span><span style="display:flex;"><span>    bin_str <span style="color:#f92672">=</span> bin_str[n : ] <span style="color:#f92672">+</span> bin_str[ : n]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> int(bin_str, <span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">calc_hash</span>(name):
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> c <span style="color:#f92672">in</span> name:
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">+=</span> ord(c)
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">&amp;=</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">=</span> rol(val, ord(c) <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">31</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> val
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># prints 0xa216a185</span>
</span></span><span style="display:flex;"><span>print(calc_hash(<span style="color:#e6db74">&#39;LoadLibraryA&#39;</span>))
</span></span><span style="display:flex;"><span><span style="color:#75715e"># prints 0x9a9c4525</span>
</span></span><span style="display:flex;"><span>print(calc_hash(<span style="color:#e6db74">&#39;MessageBoxA&#39;</span>))
</span></span></code></pre></div><p>There is one thing worth noting about at 0x401377, the code is <code>&quot;rol edi, cl&quot;</code> where the <code>cl</code> is the next input char. <code>cl</code> can be (and likely is) larger than 31 since it is an ASCII char. However, the <code>edi</code> is only 32 bits wide, what would happen? Well, I studied the behavior of this case earlier in another <a href="https://github.com/jeffli678/writeups/blob/master/shl_undefined_behavior/writeup.md">writeup</a>, and the conclusion is:</p>
<pre tabindex="0"><code>The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used).
</code></pre><p>In other words, only the lowest 5 bits of the input char will be involved in the <code>rol</code> operation. And that is also the reason that I wrote <code>val = rol(val, ord(c) &amp; 31)</code> in the Python code.</p>
<p>Another interesting thing is the way the author passes the DLL handle. The second handle, i.e. the handle of <code>user32.dll</code>, is returned by <code>LoadLibraryA</code> so there is nothing special about it. But for the first handle, we can find <code>LoadLibraryA</code> from it, which indicates the module must be <code>kernel32.dll</code>. How does the author get it? We can see it is passed in by <code>r15</code> at 0x401288. Tracing back to the previous function we see <code>mov r15, qword [rel 0x4015c4]</code>, and cross-reference of <code>data_4015c4</code> brings me back to the very beginning of <code>_start</code>:</p>
<p><img src="../imgs/19.png" alt=""></p>
<p>I noticed this piece of code when I started the journey, but I do not immediately understand it then. But now it is another aha moment! Note this is a hand-written program, so its entry point is where the execution starts. This is different from a compiled program where the <code>main()</code> function is called by the C runtime.</p>
<p>Who calls the entry point? Well, it is somebody inside of the <code>kernel32.dll</code>. Then the author clears the <code>ax</code>, and subtract 0x10000 from <code>rax</code> to get the base of <code>kernel32.dll</code>. This is related to memory alignment, the behavior of ASLR, as well as the offset of the callee into the base of the DLL. I am not sure it always works, but it looks fine to me. In the end, the base of <code>kernel32.dll</code> is saved into <code>data_4015c4</code>.</p>
<p>Alright, we have deciphered all the secrets of this challenge!</p>
<p>This is a small binary so we can reverse all of the bytes in it. I notice there are some bytes under the <code>GetProcAddr</code> function, which looks like this:</p>
<p><img src="../imgs/20.png" alt=""></p>
<p>The <code>4883</code> in the beginning tells me it is probably code, and defining a function there gives me:</p>
<p><img src="../imgs/21.png" alt=""></p>
<p>Ha, another call to <code>MessageBoxA</code>. And this one is calling the one in the IAT table, whereas the real one is dynamically resolved.</p>
<h2 id="ntcreatethreadex-and-thread_create_flags_hide_from_debugger">NtCreateThreadEx and THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER</h2>
<p>The original challenge asked us to make the program run properly and also pop up a MessageBox even when it runs under a debugger. Since we have analyzed all its tricks, I think it would not be hard at all to do it. To do this, I downloaded the Win10 20H2 ISO and installed a VM.</p>
<p>The first patch needed is to hardcode the return value (0x64) of <code>cpuid</code>. I patched the following four instructions:</p>
<pre tabindex="0"><code>mov eax, 0x40
bswap eax
cpuid
mov eax, ecx
</code></pre><p>to</p>
<pre tabindex="0"><code>mov eax, 0x64
</code></pre><p>and copies the patched binary to VM. Now if I double-click it, it does run and the MessageBox pops up:</p>
<p><img src="../imgs/22.png" alt=""></p>
<p>Not bad, it works!</p>
<p>The next step is to remove the anti-debugging checks. The simplest way is to remove the calls to <code>NtSetInformationThread</code>, <code>NtQueryInformationThread</code>, <code>NtCreateDebugObject</code> and <code>NtQueryObject</code>. And hardcode the expected value, or directly change the <code>xor_key</code>. However, before I make those patches, I found the breakpoint at 0x4010ce is never triggered. The debuggee simple exits.</p>
<p>The first mistake I made is using software breakpoint on self-modifying code. We know that software breakpoint works by patching the byte to 0xcc. The debugger internally keeps a list of addresses of software breakpoints and the original byte value so it will still display the old byte value if we display it. However, things get tricky when the code self-modifies. The 0xcc byte will be overwritten by the new byte value, and it is no longer a breakpoint. Now if the execution reaches the address where we put a breakpoint, it will not fire since the actual byte value is NOT 0xcc. If in a good case, another breakpoint hit (or any other debug event happens) and the debugger gets a chance to inspect the list of breakpoints and see one of them is changed from 0xcc to a new different value, then it can reason:</p>
<pre tabindex="0"><code>Ok, this byte was 0x12, and I changed it to 0xcc. Now it is 0xab.
What happened? It must be the program modifies the byte.
I simply need to restore it to 0xcc, and update the &#34;original value&#34; of this byte to 0xab.
</code></pre><p>If this is the case, the debugger can handle self-modifying code properly. Unfortunately, the case I encounter is the bad case where the modified breakpoint is the one that is expected to fire. So there is no way it works. And the only way is to use a hardware breakpoint (or trace it with single-step).</p>
<p>Nevertheless, even if I used hardware breakpoint on the thread entry it still does not hit. Later, I found the secret is with <code>NtCreateThreadEx</code>. I do not know any anti-debugging tricks with it, so I naively think I can set a breakpoint on the thread entry point and wait there. However, this <a href="https://ntquery.wordpress.com/2014/03/29/anti-debug-ntcreatethreadex/">blog post</a> explains an anti-debugging with <code>NtCreateThreadEx</code>: setting the <code>CreateFlags</code> to <code>THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER</code> (0x4) will also hide the debuggee from the debugger, which has the same effect as calling <code>NtSetInformationThread</code> with <code>ThreadHideFromDebugger</code>. So no wonder the breakpoint on the thread routine will not hit at all! The thread is hidden from the debugger.</p>
<p>This is easy to fix once I understand the trick. Since the code is decrypted before it runs, if we were to patch it we need to make sure we patch the encrypted bytes so the decrypted code is what we want. This is possible, especially when the decryption is just xor. However, I am a little bit lazy and I decide to bypass it in a debugger, i.e., change <code>rbx</code> back to 0, right after the code set it to 0x4 at 0x401034. Now the breakpoint on the thread entry point hits.</p>
<p>Then I directly set the <code>rip</code> to 0x4011b6:</p>
<p><img src="../imgs/16.png" alt=""></p>
<p>And manually set the value of <code>object_type.TotalNumberOfObjects</code> and <code>thread_hide_from_debugger</code> to 1. Then press F9 to run the program, and the MessageBox pops up:</p>
<p><img src="../imgs/23.png" alt=""></p>
<p>Nice, I have COMPLETELY analyzed the program and explained lots of details in it. I hope you like this write-up! And feel free to get in touch with me if you have any questions or suggestions.</p>
]]></content>
        </item>
        
        <item>
            <title>Dealing with Manipulated ELF Binary and Manually Resolving Import Functions</title>
            <link>https://xusheng.dev/posts/reversing/elf_format/readme/</link>
            <pubDate>Sun, 30 Aug 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/elf_format/readme/</guid>
            <description>&lt;p&gt;Unfortunately, this writeup is delayed for almost a week because I am super busy recently. Please take my apologies and I will try my best to keep the weekly challenge going, forever!&lt;/p&gt;
&lt;p&gt;The challenge can be downloaded at &lt;a href=&#34;https://crackmes.one/crackme/5e727daa33c5d4439bb2decd&#34;&gt;https://crackmes.one/crackme/5e727daa33c5d4439bb2decd&lt;/a&gt;. It is created by user BinaryNewbie, who is NOT a newbie for binary reversing.&lt;/p&gt;
&lt;p&gt;We will discuss an important topic in this writeup: how to mutate binary executable to obstruct reverse engineering tools. Specifically, these techniques aim to fool the binary parsers so that they fail to parse the binary or the parsing result is missing important information. In the worst case, the binary crashes the analysis tool. For convenience, we will call these techniques &lt;code&gt;Executable Format Manipulation (EFP)&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Unfortunately, this writeup is delayed for almost a week because I am super busy recently. Please take my apologies and I will try my best to keep the weekly challenge going, forever!</p>
<p>The challenge can be downloaded at <a href="https://crackmes.one/crackme/5e727daa33c5d4439bb2decd">https://crackmes.one/crackme/5e727daa33c5d4439bb2decd</a>. It is created by user BinaryNewbie, who is NOT a newbie for binary reversing.</p>
<p>We will discuss an important topic in this writeup: how to mutate binary executable to obstruct reverse engineering tools. Specifically, these techniques aim to fool the binary parsers so that they fail to parse the binary or the parsing result is missing important information. In the worst case, the binary crashes the analysis tool. For convenience, we will call these techniques <code>Executable Format Manipulation (EFP)</code>.</p>
<p>These scenarios are especially discouraging for reversers since they cannot even get started easily. That said, these techniques are not resilient to determined reversers since they can study the file format and figure out what has caused the issue, and fix the binary to allow a better analysis, or improve the binary parser if it is open-source.</p>
<p>EFP often leverages the ambiguity in the executable format specification as well as the gap between the code that enforces the format and the specification. Executable formats are complex to implement, and for several reasons, the executable parser/loader in the operating system can differ from the docs slightly. It is also hard for an analysis tool to precisely replicate the behavior of the OS parser, so this enables the EFP to create binaries that can be executed properly but is hard to analyze properly.</p>
<h2 id="stage-one">Stage One</h2>
<p>This crackme comes with a relatively simple first stage. It is carried out inside the main() function at 0xa80. Since it is not related to what I want to discuss, I will skip the reversing part and only give the algorithm. It first retrieves the currently logged-in user name, and then calculate a key as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;jeff&#39;</span>
</span></span><span style="display:flex;"><span>val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i, c <span style="color:#f92672">in</span> enumerate(name):
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">+=</span> ord(c)
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">^=</span> i
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">^=</span> <span style="color:#ae81ff">0xf</span>
</span></span></code></pre></div><p>My user name is &ldquo;jeff&rdquo; so the corresponding value is 385. Now I can get past the first stage and proceed to the real challenge.</p>
<h2 id="stage-two">Stage Two</h2>
<p>After we pass the first check, we can see that it loads another program from 0x202020. It starts with <code>&quot;\x7fELF&quot;</code>, which is the sign of another ELF. By analyzing the loader code we also know its size is 0x2008. We save this file to the disk and it can be executed properly. We will now work on this file from now. The file is included in the repo and its sha1 sum is <code>1d31fac493665f8baa23baac8e1aa5385dd1ace2</code>.</p>
<p>BinaryNinja and Ghidra fail to parse it as a valid ELF and can only load it as raw binary. Cutter can load the binary and identify some code (probably thanks to linear sweep), but it cannot find the entry point and cannot find any important functions.</p>
<p>This immediately tells me that there is something unusual with this file, we need to examine it, especially the header, closely.</p>
<p>There are two tools that I often use to inspect a file format. The first one is <a href="https://kaitai.io/">Kaitai Struct</a>, which is a <code>declarative binary format parsing language</code>. It is specifically designed to ease the development of binary parsers and it can show the parsed binary in a tree view. For many popular formats, including ELF, the community already contributed parsers. It has an <a href="https://ide.kaitai.io/">online IDE</a> so we do not have to install it. There is a <a href="https://github.com/Vector35/kaitai">BinaryNinja plugin</a> for it, as well.</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>Unfortunately, this file also triggers an error in the Kaitai parser so we cannot easily view it in the tree view. This is a big drawback since we lose a strong tool. But we still have other options.</p>
<p><a href="https://www.sweetscape.com/010editor/">010Editor</a> is another option for binary format inspection. It is primarily a hex editor, but it comes with <code>template</code> that describes various file formats and can show the parse result in a table view. Templates can be downloaded from <a href="https://www.sweetscape.com/010editor/repository/templates/">here</a> or installed from within the program.</p>
<p>010Editor also fails to parse it without issue. However, it can show partial parse results so we can at least see what is normal and what could be wrong. Besides, if no tool can parse it at all, we can still manually inspect it and find and defeat any tricks in it. The 010Editor parsing result looks like this:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>The ELF magic looks fine. Immediately after that, we find something unusual, the ELF class byte (offset 0x4) is set to none, whereas it should set to 1 for 32-bit programs and 2 for 64-bit programs. Cutter already showed us some code snippets in it and it is x64 code. So we set it to 0x2. At byte 0x5, the endianness is also not set, which is probably little-endian since it runs properly on my little-endian Linux system. Next, the EI_VERSION at byte 0x6 is set to 0x1 in most ordinary binaries.</p>
<p>After we fix these three fields, BinaryNinja is now able to load it as ELF and find the entry point:</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>Not bad, but we are missing symbols. I have reversed lots of binaries on Linux so <code>data_201fe0</code> is actually <code>__libc_start_main</code> and <code>data_a50</code> is the main() function. After I define the function, it looks like this:</p>
<p><img src="../imgs/4.png" alt=""></p>
<p>The complexity of the CFG is OK. But since we are missing all imported functions, we have no clue of what this program is doing. If i double click <code>sub_980</code>, it is essentially a jmp to a PLT function.</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>Which means it is an imported function. Had this binary not been tampered with, we should already know what it is.</p>
<p>Since we have fixed the binary, 010Editor can parse it without any errors. We see that all of the section headers are removed, and the number of segments is also less than usual.</p>
<p><img src="../imgs/6.png" alt=""></p>
<p>The readers might have heard of that section headers are not important for ELF file executions. Linux only uses information in segments to load the binary and kick off the execution. However, since sections typically exist (generated by compilers) and contain more information than the segments, many reverse engineering tools use information from sections to parse the binary. Now that the section headers are wiped out, they cannot function properly.</p>
<p>There is a <code>e_shoff</code> field in the ELF header that points to the start of the section headers. Section headers, when they exist, are organized into an array and placed at the end of the binary. Of course, they do not have to be there &ndash; as long as the <code>e_shoff</code> actually points to the start of it. In several obfuscated ELF binaries that I reversed, the <code>e_shoff</code> is wiped out but the section headers are left intact at the end of the file. For that case, it is easy to repair since we simply need to count how many sections do we have and find out the start of the array accordingly.</p>
<p>However, for this particular binary, the <code>e_shoff</code> and the actual section headers are both wiped out. It is very hard to recover all the section headers since there are lots of information on it.</p>
<p>Note, however, I am not saying the sections are wiped out. No, impossible. Only the section headers are gone. Sections still contain data that is crucial for the execution. And it is highly likely that they are not touched. Though we do not know the boundaries of each of them, so we cannot recover them.</p>
<p>But here is a problem: if we cannot resolve the imports, how could the loader resolve them? The programs execute properly. So there must be a way!</p>
<p>We still have segments and the loader relies on segments to resolve imports. Do segments store the information at another place so it still works? Well, sections and segments overlap. We do not know where sections are since we do not have the section headers, but the segments contain offsets to useful information and can help the loader. Nevertheless, the information in segments is organized in a different format so existing tools do not parse them well. We can either reconstruct a part of the section headers so the analysis tool can parse the imports correctly, or we replace the loader by ourselves &ndash; parse the imports directly. This is especially doable for Linux since everything is open-source.</p>
<p>It is noteworthy that there is a third approach &ndash; debug the program and see where those stub function jumps into, so that we can resolve the imports semi-manually. I have used this previously for a PE challenge where the imported APIs are obfuscated. This is a small crackme and we do not have lots of them, so the workload is fine. However, when I try to run it in gdb, gdb fails to parse the binary too. It cannot find <code>__libc_start_main</code> so we cannot properly debug it. I would say gdb could do a better job but enhancing gdb right now is not an option for me.</p>
<p>So I turned to another approach &ndash; resolve the imports via a script.</p>
<h2 id="non-lazy-symbols">Non-lazy symbols</h2>
<p>We will start with non-lazy symbols first. Non-lazy symbols are resolved by the loader at loading time. The first thing we need is the DYNAMIC segment. The segments are described in an array of <code>Elf64_ProgramHeader</code> in the ELF header. For this particular binary, it is the No.4 one inside the array.</p>
<pre tabindex="0"><code>00000120      [0x4] = 
00000120      {
00000120          enum p_type type = PT_DYNAMIC
00000124          enum p_flags flags = PF_R
00000128          uint64_t offset = 0x0
00000130          uint64_t virtual_address = 0x201d58
00000138          uint64_t physical_address = 0x0
00000140          uint64_t file_size = 0x0
00000148          uint64_t memory_size = 0x0
00000150          uint64_t align = 0x0
00000158      }
</code></pre><p>We see that the DYNAMIC segment starts at offset <code>0x201d58</code>. It contains an array of structure <code>Elf64_Dyn</code>, whose definition is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">struct</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  Elf64_Sxword  d_tag;          <span style="color:#75715e">/* Dynamic entry type */</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">union</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      Elf64_Xword d_val;        <span style="color:#75715e">/* Integer value */</span>
</span></span><span style="display:flex;"><span>      Elf64_Addr d_ptr;         <span style="color:#75715e">/* Address value */</span>
</span></span><span style="display:flex;"><span>    } d_un;
</span></span><span style="display:flex;"><span>} Elf64_Dyn;
</span></span></code></pre></div><p>Among the two fields, <code>d_tag</code> specifies the type of it. And depending on the value of <code>d_tag</code>, <code>d_un</code> could be either an address or an integer value. Some legal values for <code>d_tag</code> are:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">/* Legal values for d_tag (dynamic entry type).  */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_NULL     0       </span><span style="color:#75715e">/* Marks end of dynamic section */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_NEEDED   1       </span><span style="color:#75715e">/* Name of needed library */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PLTRELSZ 2       </span><span style="color:#75715e">/* Size in bytes of PLT relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PLTGOT   3       </span><span style="color:#75715e">/* Processor defined value */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_HASH     4       </span><span style="color:#75715e">/* Address of symbol hash table */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_STRTAB   5       </span><span style="color:#75715e">/* Address of string table */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_SYMTAB   6       </span><span style="color:#75715e">/* Address of symbol table */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RELA     7       </span><span style="color:#75715e">/* Address of Rela relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RELASZ   8       </span><span style="color:#75715e">/* Total size of Rela relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RELAENT  9       </span><span style="color:#75715e">/* Size of one Rela reloc */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_STRSZ    10      </span><span style="color:#75715e">/* Size of string table */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_SYMENT   11      </span><span style="color:#75715e">/* Size of one symbol table entry */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_INIT     12      </span><span style="color:#75715e">/* Address of init function */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_FINI     13      </span><span style="color:#75715e">/* Address of termination function */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_SONAME   14      </span><span style="color:#75715e">/* Name of shared object */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RPATH    15      </span><span style="color:#75715e">/* Library search path (deprecated) */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_SYMBOLIC 16      </span><span style="color:#75715e">/* Start symbol search here */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_REL      17      </span><span style="color:#75715e">/* Address of Rel relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RELSZ    18      </span><span style="color:#75715e">/* Total size of Rel relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RELENT   19      </span><span style="color:#75715e">/* Size of one Rel reloc */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PLTREL   20      </span><span style="color:#75715e">/* Type of reloc in PLT */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_DEBUG    21      </span><span style="color:#75715e">/* For debugging; unspecified */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_TEXTREL  22      </span><span style="color:#75715e">/* Reloc might modify .text */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_JMPREL   23      </span><span style="color:#75715e">/* Address of PLT relocs */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_BIND_NOW 24      </span><span style="color:#75715e">/* Process relocations of object */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_INIT_ARRAY   25      </span><span style="color:#75715e">/* Array with addresses of init fct */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_FINI_ARRAY   26      </span><span style="color:#75715e">/* Array with addresses of fini fct */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_INIT_ARRAYSZ 27      </span><span style="color:#75715e">/* Size in bytes of DT_INIT_ARRAY */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_FINI_ARRAYSZ 28      </span><span style="color:#75715e">/* Size in bytes of DT_FINI_ARRAY */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_RUNPATH  29      </span><span style="color:#75715e">/* Library search path */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_FLAGS    30      </span><span style="color:#75715e">/* Flags for the object being loaded */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_ENCODING 32      </span><span style="color:#75715e">/* Start of encoded range */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PREINIT_ARRAY 32     </span><span style="color:#75715e">/* Array with addresses of preinit fct*/</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PREINIT_ARRAYSZ 33       </span><span style="color:#75715e">/* size in bytes of DT_PREINIT_ARRAY */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_SYMTAB_SHNDX 34      </span><span style="color:#75715e">/* Address of SYMTAB_SHNDX section */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_NUM      35      </span><span style="color:#75715e">/* Number used */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_LOOS     0x6000000d  </span><span style="color:#75715e">/* Start of OS-specific */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_HIOS     0x6ffff000  </span><span style="color:#75715e">/* End of OS-specific */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_LOPROC   0x70000000  </span><span style="color:#75715e">/* Start of processor-specific */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_HIPROC   0x7fffffff  </span><span style="color:#75715e">/* End of processor-specific */</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define DT_PROCNUM  DT_MIPS_NUM </span><span style="color:#75715e">/* Most used by any processor */</span><span style="color:#75715e">
</span></span></span></code></pre></div><p>There are many important information in this array. For example, there are six fields related to INIT and FINI, i.e., <code>DT_INIT</code>, <code>DT_FINI</code>, <code>DT_INIT_ARRAY</code>, <code>DT_INIT_ARRAYSZ</code>, <code>DT_FINI_ARRAY</code>, <code>DT_FINI_ARRAYSZ</code>. However, those relevant to the resolving of import functions are listed below:</p>
<pre tabindex="0"><code>00201d58  struct Elf32_Dyn _.dynamic[26] =  
00201d58  {
00201dd8      [0x8] = 
00201dd8      {
00201dd8          enum Elf64_Sxword d_tag = DT_STRTAB
00201de0          uint64_t d_ptr = 0x4e8
00201de8      }
00201de8      [0x9] = 
00201de8      {
00201de8          enum Elf64_Sxword d_tag = DT_SYMTAB
00201df0          uint64_t d_ptr = 0x2c0
00201df8      }
00201df8      [0xa] = 
00201df8      {
00201df8          enum Elf64_Sxword d_tag = DT_STRSZ
00201e00          uint64_t d_ptr = 0x113
00201e08      }
00201e08      [0xb] = 
00201e08      {
00201e08          enum Elf64_Sxword d_tag = DT_SYMENT
00201e10          uint64_t d_ptr = 0x18
00201e18      }
00201e28      [0xd] = 
00201e28      {
00201e28          enum Elf64_Sxword d_tag = DT_PLTGOT
00201e30          uint64_t d_ptr = 0x201f48
00201e38      }
00201e38      [0xe] = 
00201e38      {
00201e38          enum Elf64_Sxword d_tag = DT_PLTRELSZ
00201e40          uint64_t d_ptr = 0x168
00201e48      }
00201e58      [0x10] = 
00201e58      {
00201e58          enum Elf64_Sxword d_tag = DT_JMPREL
00201e60          uint64_t d_ptr = 0x788
00201e68      }
00201e68      [0x11] = 
00201e68      {
00201e68          enum Elf64_Sxword d_tag = DT_RELA
00201e70          uint64_t d_ptr = 0x680
00201e78      }
00201e78      [0x12] = 
00201e78      {
00201e78          enum Elf64_Sxword d_tag = DT_RELASZ
00201e80          uint64_t d_ptr = 0x108
00201e88      }
00201ef8  }
</code></pre><p>We can see that <code>DT_STRTAB</code> has value 0x4e8, which means the string table starts at offset 0x468. Also, the <code>DT_STRSZ</code> is 0x113, so the size of the string table is 0x113. This can be verified in the following image:</p>
<p><img src="../imgs/7.png" alt=""></p>
<p>We can see many familiar names in it, e.g., <code>srand</code>, <code>time</code>, and <code>malloc</code>. These are the names of imported functions. Now what we need to figure out is how these are linked with symbols so that we can know which symbol corresponds to which name.</p>
<p>The next thing we should look at is the symbol table. <code>DT_SYMTAB</code> is 0x2c0, which means the symbol table starts at 0x2c0. <code>DT_SYMENT</code> is 0x18, which suggests there are 0x18 = 24 symbol entries. Interestingly, there are 23 entries, which I do not fully understand. If anyone has any idea on this, feel free to help me out.</p>
<p>Each symbol entry is an <code>Elf64_Sym</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">struct</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  Elf64_Word    st_name;        <span style="color:#75715e">/* Symbol name (string tbl index) */</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> st_info;        <span style="color:#75715e">/* Symbol type and binding */</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> st_other;       <span style="color:#75715e">/* Symbol visibility */</span>
</span></span><span style="display:flex;"><span>  Elf64_Section st_shndx;       <span style="color:#75715e">/* Section index */</span>
</span></span><span style="display:flex;"><span>  Elf64_Addr    st_value;       <span style="color:#75715e">/* Symbol value */</span>
</span></span><span style="display:flex;"><span>  Elf64_Xword   st_size;        <span style="color:#75715e">/* Symbol size */</span>
</span></span><span style="display:flex;"><span>} Elf64_Sym;
</span></span></code></pre></div><p>Due to space limitation, I will only show the first three symbol entries here:</p>
<pre tabindex="0"><code>000002c0  struct Elf64_Sym data_2c0[23] = 
000002c0  {
000002c0      [0x0] = 
000002c0      {
000002c0          uint32_t st_name = 0x0
000002c4          uint8_t st_info = 0x0
000002c5          uint8_t st_other = 0x0
000002c6          uint16_t st_shndx = 0x0
000002c8          uint64_t st_value = 0x0
000002d0          uint64_t st_size = 0x0
000002d8      }
000002d8      [0x1] = 
000002d8      {
000002d8          uint32_t st_name = 0x9d
000002dc          uint8_t st_info = 0x12
000002dd          uint8_t st_other = 0x0
000002de          uint16_t st_shndx = 0x0
000002e0          uint64_t st_value = 0x0
000002e8          uint64_t st_size = 0x0
000002f0      }
000002f0      [0x2] = 
000002f0      {
000002f0          uint32_t st_name = 0xce
000002f4          uint8_t st_info = 0x20
000002f5          uint8_t st_other = 0x0
000002f6          uint16_t st_shndx = 0x0
000002f8          uint64_t st_value = 0x0
00000300          uint64_t st_size = 0x0
00000308      }
00000308      [0x3] = 
00000308      {
00000308          uint32_t st_name = 0x54
0000030c          uint8_t st_info = 0x12
0000030d          uint8_t st_other = 0x0
0000030e          uint16_t st_shndx = 0x0
00000310          uint64_t st_value = 0x0
00000318          uint64_t st_size = 0x0
00000320      }
</code></pre><p>The first entry is always all 0. So the first entry has index 0x1. Field <code>st_name</code> does not store the string itself. Instead, it stores the offset into the string table that we discussed earlier. Each name is null-terminated. Given the value 0x9d, we can find out that is <code>free</code>. <code>st_info</code> contains the symbol type that we will not cover in detail here. Now that we connect symbols with its name, we still have no idea which PLT entry corresponds to which symbol. And that is the last part of the mystery.</p>
<p>There are two relocation tables. The first one is <code>DT_RELA</code> and it starts at 0x680 and has a size 0x108. The second one is <code>DT_JMPREL</code>. It starts at 0x788 and it has a size 0x168. Both of them are arrays of <code>Elf64_Rela</code> which is defined as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">struct</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  Elf64_Addr    r_offset;       <span style="color:#75715e">/* Address */</span>
</span></span><span style="display:flex;"><span>  Elf64_Xword   r_info;         <span style="color:#75715e">/* Relocation type and symbol index */</span>
</span></span><span style="display:flex;"><span>  Elf64_Sxword  r_addend;       <span style="color:#75715e">/* Addend */</span>
</span></span><span style="display:flex;"><span>} Elf64_Rela;
</span></span></code></pre></div><p>The first 4 entries are listed here:</p>
<pre tabindex="0"><code>00000788  struct Elf64_Rela _.rela.plt[15] = 
00000788  {
00000788      [0x0] = 
00000788      {
00000788          uint64_t r_offset = 0x201f60
00000790          uint64_t r_info = 0x100000007
00000798          int64_t r_addend = 0x0
000007a0      }
000007a0      [0x1] = 
000007a0      {
000007a0          uint64_t r_offset = 0x201f68
000007a8          uint64_t r_info = 0x300000007
000007b0          int64_t r_addend = 0x0
000007b8      }
000007b8      [0x2] = 
000007b8      {
000007b8          uint64_t r_offset = 0x201f70
000007c0          uint64_t r_info = 0x400000007
000007c8          int64_t r_addend = 0x0
000007d0      }
000007d0      [0x3] = 
000007d0      {
000007d0          uint64_t r_offset = 0x201f78
000007d8          uint64_t r_info = 0x500000007
000007e0          int64_t r_addend = 0x0
000007e8      }
000007e8      [0x4] = 
000007e8      {
000007e8          uint64_t r_offset = 0x201f80
000007f0          uint64_t r_info = 0x700000007
000007f8          int64_t r_addend = 0x0
00000800      }
</code></pre><p>Taking the first entry as an example, the <code>r_offset</code> is 0x201f60. Where is this address? Going back to the DYNAMIC segment, we see that <code>DT_PLTGOT</code> is 0x201f48. This means the PLT starts at 0x201f48.</p>
<p><img src="../imgs/8.png" alt=""></p>
<p>We know the first three entries of PLT have special uses so the first item the compiler tends to use is the fourth one. Now the last problem is which function does the first entry corresponds to.</p>
<p>The secret is in the <code>r_info</code>, which has a value of 0x100000007. There are three macros related to it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#define ELF64_R_SYM(i)          ((i) &gt;&gt; 32)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define ELF64_R_TYPE(i)         ((i) &amp; 0xffffffff)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define ELF64_R_INFO(sym,type)      ((((Elf64_Xword) (sym)) &lt;&lt; 32) + (type))
</span></span></span></code></pre></div><p>The low dword is 7, which specifies the type of relocation entry. and the high dword is 1, which means that the current relocation entry corresponds to the no.1 entry in the symbol table. Remember the No.0 entry is always 0 and we already mentioned that the no.1 entry is <code>free()</code>, so we can rename it to <code>free</code> right now.</p>
<p>This binary is small so I manually resolved all the names like this. But if the binary is large and has many import functions, it is also not hard to write a Python script to resolve them automatically. It looks like this after I finish them.</p>
<p><img src="../imgs/9.png" alt=""></p>
<p>And we now know what is happening in <code>main()</code>.</p>
<p><img src="../imgs/10.png" alt=""></p>
<h2 id="solve-it-finally">Solve it, finally!</h2>
<p>Not that we have passed the most difficult part of the challenge. We still have to reverse the algorithm and solve the challenge itself. The first thing I notice is there is a <code>ptrace</code> based anti-debug:</p>
<p><img src="../imgs/11.png" alt=""></p>
<p>Since there is no integrity check on the binary, I simply patched to <code>Never Branch</code> so it never gets to the failure path.</p>
<p>The next challenge is <code>gdb</code> cannot properly debug it. It cannot even find and set a breakpoint at <code>__libc_start_main</code>. Even if we use the actual address of _start, which is the entry point of the binary, it still malfunctions. I do not have even time to debug why gdb fails, but I managed to find a workaround. I patched an instruction (near the _start) to 0xcc (int3), so it will break and gdb manages to catch it (thx!), we can start debugging from there.</p>
<p>The underlying algorithm is not complex so we will skip it here.</p>
<h2 id="lazy-symbols">Lazy Symbols</h2>
<p>Now that we have explained how non-lazy symbols are resolved. Many materials explain how lazy symbols are resolved. Note the way that the two types of symbols are resolved differently. Lazy symbols are not resolved in a slightly different way. We will recap how they differ and discuss what are some consequences.</p>
<p><img src="../imgs/12.png" alt=""></p>
<p>So the call to <code>time()</code> in the <code>main()</code> function actually first calls into a stub function. The qword at <code>time_GOT</code> is 0x986, and the code at 0x986 is:</p>
<p><img src="../imgs/13.png" alt=""></p>
<p>Now we see the <code>push 0x6</code> and the <code>jmp</code> which is covered in lots of writeups about PLT/GOT. 0x6 is also the index into the relocation table that helps the loader resolve the import. We all know that lazy-symbols are not resolved at load-time. When the program calls it for the first time, the index (0x6) is pushed onto the stack before the code gets to <code>_dl_runtime_resolve()</code>. Note this <code>push n</code> is ONLY used for lazy symbols. For non-lazy ones, the loader uses the information in <code>Elf64_Rela-&gt;r_info</code> to resolve the imports. By the time the import function is called for the first time, the value at <code>time_GOT</code> is already the actual address of time() in <code>libc.so</code> and NOT 0x986. So the code in the above screenshot is never executed. You can patch it to <code>int 3</code> to test it. However, for lazy symbols, where the <code>push n</code> plays a role, if we do the same patch, the program will crash during loading.</p>
<p>To sum up, non-lazy symbols directly use the index in <code>Elf64_Rela-&gt;r_info</code> and lazy-symbols use index in the <code>push n</code> to the first index into the relocation table, and then use <code>Elf64_Rela-&gt;r_info</code> to continue the resolution.</p>
<p>Thus, to obfuscate the binary, we can change the value of <code>push n</code> and make it point to a different symbol. Since it seems current tools all use <code>Elf64_Rela-&gt;r_info</code> to retrieve the index value into the symbol table. So there is an opportunity to create a binary that executes as expected, but cannot be properly analyzed by reverse engineering tools. I will update this writeup when I have a concrete example.</p>
<p>For reverse engineering tools, the best way to defeat this obfuscation is to distinguish lazy/non-lazy symbols and parse the <code>push n</code> for lazy symbols and obtain the index accordingly. But this creates several issues since they typically do not only support x86/x64, they have to also support other architectures like ARM. There are going to be some nuances that inflate the code logic so it is not simple to do.</p>
<h2 id="references">References</h2>
<p>There are many references that I used during writing. Unfortunately, I did not mark it when I referenced them. Instead, I assembled a list and put it here:</p>
<ul>
<li><code>elf.h</code> in Linux source code</li>
<li>Book <code>&quot;Learning Linux Binary Analysis&quot;</code></li>
<li><a href="https://ypl.coffee/dl-resolve/">Understanding _dl_runtime_resolve</a></li>
<li><a href="http://michalmalik.github.io/elf-dynamic-segment-struggles">ELF: dynamic struggles</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Making and solving a Reversing Challenge Based-on x86 ISA Encoding</title>
            <link>https://xusheng.dev/posts/reversing/x86/readme/</link>
            <pubDate>Sun, 02 Aug 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/x86/readme/</guid>
            <description>&lt;p&gt;This time the writeup is a little bit different &amp;ndash; I am the maker of this challenge so the narrative is from a different perspective. I will first cover how I made it, and then show two possible ways to solve it.&lt;/p&gt;
&lt;h2 id=&#34;the-plan&#34;&gt;The Plan&lt;/h2&gt;
&lt;p&gt;I have always been hoping to make some reversing challenges based-on the encoding of the x86 instruction set. It does not have to be super hard, maybe just explore some interesting aspects of the x86, which goes lower than the disassembly. Recently, thanks to my intern task that lifts x86 instructions, as well as reading this &lt;a href=&#34;https://www.msreverseengineering.com/blog/2015/6/9/x86-trivia-for-nerds&#34;&gt;blog post&lt;/a&gt;, I decided to do it rather than set it for the future (indefinitely).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>This time the writeup is a little bit different &ndash; I am the maker of this challenge so the narrative is from a different perspective. I will first cover how I made it, and then show two possible ways to solve it.</p>
<h2 id="the-plan">The Plan</h2>
<p>I have always been hoping to make some reversing challenges based-on the encoding of the x86 instruction set. It does not have to be super hard, maybe just explore some interesting aspects of the x86, which goes lower than the disassembly. Recently, thanks to my intern task that lifts x86 instructions, as well as reading this <a href="https://www.msreverseengineering.com/blog/2015/6/9/x86-trivia-for-nerds">blog post</a>, I decided to do it rather than set it for the future (indefinitely).</p>
<p>There are several ways to do it, and I think it is not a bad idea to mutate the executable code according to the user input. It is interesting because, for most reversing challenges, the solver is not expected to change (patch) the code. However, we can take the user input and explicitly use it, in certain ways, to modify the code.</p>
<p>So how do we do it? Executing the user input directly is probably not a good idea. Since code is typically non-printable, so the solution is going to be ugly. More importantly, when we grant the player with arbitrary code execution, it is hard to enforce that they solve it in our intended way.</p>
<p>So it is best to modify existing code according to the user input. The first thing that came to my mind is we can do some arithmetics with it. We can have an equation like:</p>
<pre tabindex="0"><code>start_value ± a1 ± a2 ± .. ± an == result
</code></pre><p>Where the user has to figure out the correct plus or minus sign to make this equation correct. The <code>start_value</code>, <code>result</code>, as well as the <code>ai</code> (1 &lt;= i &lt;= n), are all randomly generated. I made them 32-bit integers.</p>
<h2 id="implementing-and-automating">Implementing and Automating</h2>
<p>There are a couple of things to make the idea concrete.</p>
<p>Firstly, how do we accept the user inputs? We can directly take plus or minus signs as string literals but I wish to make it slightly twisted here: the program will take a 32-bit integer and use each of its bits as the indicator of plus/minus.</p>
<p>The next thing is about the x86 instruction encoding. I decided to use the register <code>eax</code> to hold the accumulated value and eventually compare it with the target value. We know that x86 instruction encodes the opcode in a straight-forward way, so it is quite easy to switch between an add instruction and sub instruction.</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>If you look at the highlighted line, you will notice that <code>ADD EAX, imm32</code> is encoded as <code>05 id</code>, where the <code>05</code> stands for the opcode, and the <code>id</code> means a 32-bit immediate follows it. So if we have bytes <code>0512345678</code>, it will decode to <code>ADD EAX, 0x78563412</code> (note the endianess). Similarly, <code>SUB EAX, imm32</code> is encoded as <code>2D id</code>. So the real difference between an <code>ADD EAX, imm32</code> and a <code>SUB EAX, imm32</code> is the opcode, i.e., the first byte of the instruction.</p>
<p>So the code modifying is easy: we just need to check every bit of the user input and overwrite the opcode byte of the correct one (05 or 2D). Each instruction is 5 bytes and the latter four bytes encode the immediate value in the equation.</p>
<p>This challenge can be made manually, but I prefer to be able to generate it automatically. That brings several benefits, e.g., the ease of debugging during development. The source code of the challenge is provided in the <a href="../source.zip">source folder</a>, and you can have a look at it.</p>
<p>The code that does not change is written in C, whereas a Python script will generate random constant values for the changes and write it to a .h header file. the header file is included in the C source file so it can compile end-to-end. I also make a Makefile so I can easily build debug and release version of it. The Python generator looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">import</span> random
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>rounds <span style="color:#f92672">=</span> <span style="color:#ae81ff">32</span>
</span></span><span style="display:flex;"><span>MAXINT <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>output <span style="color:#f92672">=</span> open(<span style="color:#e6db74">&#39;code.h&#39;</span>, <span style="color:#e6db74">&#39;w&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>val <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, MAXINT)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># mov eax, val</span>
</span></span><span style="display:flex;"><span>output<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#39;{0xb8, 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">},</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> val)
</span></span><span style="display:flex;"><span>ans <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(rounds):
</span></span><span style="display:flex;"><span>    op <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    round_val <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, MAXINT)
</span></span><span style="display:flex;"><span>    ans <span style="color:#f92672">|=</span> (op <span style="color:#f92672">&lt;&lt;</span> i)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> op <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">-=</span> round_val
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">+=</span> round_val
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">&amp;=</span> MAXINT
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    junk_opcode <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0xff</span>)
</span></span><span style="display:flex;"><span>    output<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#39;{0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">, 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">},</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> (junk_opcode, round_val))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># cmp eax, val</span>
</span></span><span style="display:flex;"><span>output<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#39;{0x3d, 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">},&#39;</span> <span style="color:#f92672">%</span> val)
</span></span><span style="display:flex;"><span>output<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#39;the answer is: </span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> ans)
</span></span><span style="display:flex;"><span>os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#39;make&#39;</span>)
</span></span></code></pre></div><p>The C source file defines a struct to describe the two particular instructions we are using:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#pragma pack(1) 
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">struct</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">char</span> opCode;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint32_t</span> operand;
</span></span><span style="display:flex;"><span>}instr;
</span></span></code></pre></div><p>The main.c is the core part of the challenge:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#define N 32
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>instr code[]  <span style="color:#a6e22e">__attribute__</span> ((<span style="color:#a6e22e">section</span> (<span style="color:#e6db74">&#34;.x86&#34;</span>))) <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#include</span> <span style="color:#75715e">&#34;code.h&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span>    {<span style="color:#ae81ff">0x0f</span>, <span style="color:#ae81ff">0x9090d094</span>},         
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 00201043  0f94d0             sete    al  {0x1}
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 00201046  90                 nop     
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 00201047  90                 nop     
</span></span></span><span style="display:flex;"><span>    {<span style="color:#ae81ff">0xc3</span>, <span style="color:#ae81ff">0</span>}
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 00201048  c3                 retn     {__return_addr}
</span></span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// read the input
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> input <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> unused <span style="color:#f92672">=</span> <span style="color:#a6e22e">scanf</span>(<span style="color:#e6db74">&#34;%d&#34;</span>, <span style="color:#f92672">&amp;</span>input);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// modify the code according to the user input
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span>(<span style="color:#66d9ef">int</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> N; i <span style="color:#f92672">++</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">bool</span> bit <span style="color:#f92672">=</span> input <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        input <span style="color:#f92672">&gt;&gt;=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (bit)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// add eax, imm32
</span></span></span><span style="display:flex;"><span>            code[i <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>].opCode <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x05</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// sub eax, imm32
</span></span></span><span style="display:flex;"><span>            code[i <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>].opCode <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x2d</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// set page to executable
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">void</span> <span style="color:#f92672">*</span>page <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>     (<span style="color:#66d9ef">void</span> <span style="color:#f92672">*</span>) ((<span style="color:#66d9ef">unsigned</span> <span style="color:#66d9ef">long</span>) (<span style="color:#f92672">&amp;</span>code) <span style="color:#f92672">&amp;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">~</span>(<span style="color:#a6e22e">getpagesize</span>() <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>));
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mprotect</span>(page, <span style="color:#a6e22e">getpagesize</span>(), PROT_READ <span style="color:#f92672">|</span> PROT_WRITE <span style="color:#f92672">|</span> PROT_EXEC);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// call the code and check result
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">bool</span> (<span style="color:#f92672">*</span>func_ptr)() <span style="color:#f92672">=</span> (<span style="color:#66d9ef">void</span><span style="color:#f92672">*</span>)<span style="color:#f92672">&amp;</span>code;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">func_ptr</span>())
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;Well done!</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;Try again!</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="solving-it-with-z3">Solving it with Z3</h2>
<p>Now it is time to solve it. A dull brute-force solves it, though it could take a while to complete. The most straightforward idea is to use Z3. We create 32 booleans and transcribe the calculations into Z3 syntax. Of course, we need to extract those constant values, but it should be relatively easy. Then I get:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">from</span> z3 <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># extracted from the challenge binary</span>
</span></span><span style="display:flex;"><span>init_val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x3df2f794</span>
</span></span><span style="display:flex;"><span>target_val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x7a612770</span>
</span></span><span style="display:flex;"><span>constants <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x52ae22f2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xbf409bcc</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x46417dc1</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x25f7d9a1</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xef83a7ce</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2dd63e8e</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x584a1ec5</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x8e58e1df</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xf2705f70</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2e94ef1e</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x3ca9e080</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xa617b5df</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x29ae9c3d</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x7461ed52</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x7125faac</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x65dfffd6</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x97f1f41c</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x6f4e0648</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xd803e5d0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xf358f0eb</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xbc3b30c7</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x585685f8</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2a9cc47c</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x7f03d175</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xc1d942ae</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x174c7d4f</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xb7d004f0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0xbec8b077</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x8ce8eaa2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x2510e330</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x4aed0eee</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x4043cd91</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># solver script</span>
</span></span><span style="display:flex;"><span>n <span style="color:#f92672">=</span> <span style="color:#ae81ff">32</span>
</span></span><span style="display:flex;"><span>inputs <span style="color:#f92672">=</span> [Bool(<span style="color:#e6db74">&#39;bit_</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> i) <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>val <span style="color:#f92672">=</span> BitVecVal(init_val, <span style="color:#ae81ff">32</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n):
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">=</span> If(inputs[i], val <span style="color:#f92672">+</span> constants[i], val <span style="color:#f92672">-</span> constants[i])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>s <span style="color:#f92672">=</span> Solver()
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(val <span style="color:#f92672">==</span> BitVecVal(target_val, <span style="color:#ae81ff">32</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> s<span style="color:#f92672">.</span>check() <span style="color:#f92672">==</span> sat:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;solved&#39;</span>)
</span></span><span style="display:flex;"><span>    m <span style="color:#f92672">=</span> s<span style="color:#f92672">.</span>model()
</span></span><span style="display:flex;"><span>    solution <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n):
</span></span><span style="display:flex;"><span>        bit <span style="color:#f92672">=</span> m<span style="color:#f92672">.</span>evaluate(inputs[i])
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> bit:
</span></span><span style="display:flex;"><span>            solution <span style="color:#f92672">|=</span> (<span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> i)
</span></span><span style="display:flex;"><span>    print(solution)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;failed&#39;</span>)
</span></span></code></pre></div><p>It works but it is a little bit slow. It took 5 minutes to solve it, IIRC. The solution I get is:</p>
<pre tabindex="0"><code>$ python z3_solve.py 
solved
2371132652
</code></pre><p>And it works:</p>
<pre tabindex="0"><code>$ ./x86
2371132652
Well done!
</code></pre><p>Interestingly, the solution found by Z3 is different from the seed I used to generate the challenge, which is <code>1804139300</code>. But this is not surprising since there could exist other solutions than the original one. And I did not do anything to enforce the uniqueness of the challenge.</p>
<h2 id="solving-it-with-divide-and-conquer">Solving it with Divide-and-Conquer</h2>
<p>Z3 is good enough. However, there is another way to solve it. We can use divide-and-conquer to accelerate the brute-force. We can try the first 16 bits, which make up (2 ^ 16 = 65536) possibilities. We take note of the values we get. After that, we do the same thing for the latter 16 bits and do the same. Now we compare the two sets and compare if there are any matches. This allows us to find solutions in a faster way. Also, this can help us find ALL the solutions to this challenge.</p>
<p>I am too lazy to do it by myself. I will leave it for interested readers!</p>
]]></content>
        </item>
        
        <item>
            <title>Solving a Recursive Crackme by Automating GDB</title>
            <link>https://xusheng.dev/posts/reversing/automating-gdb/readme/</link>
            <pubDate>Mon, 27 Jul 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/automating-gdb/readme/</guid>
            <description>&lt;p&gt;The last week&amp;rsquo;s challenge is called &lt;a href=&#34;https://0x00sec.org/t/reverseme-recursion/21802&#34;&gt;&lt;code&gt;Recursion&lt;/code&gt;&lt;/a&gt;. From the name we already expect to do some automation &amp;ndash; manually solving stuff recursively is not a wise idea.&lt;/p&gt;
&lt;h2 id=&#34;first-impression&#34;&gt;First Impression&lt;/h2&gt;
&lt;p&gt;The forum probably does not allow users to post binary files, so challenges are all posted as base64 encoded. There are too many ways to restore the binary, but Binary Ninja saves you from remembering the command: Just copy the encoded text, create a new empty binary, and then click &amp;ldquo;Paste From&amp;rdquo; -&amp;gt; &amp;ldquo;Base64&amp;rdquo;. Then you are done!&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>The last week&rsquo;s challenge is called <a href="https://0x00sec.org/t/reverseme-recursion/21802"><code>Recursion</code></a>. From the name we already expect to do some automation &ndash; manually solving stuff recursively is not a wise idea.</p>
<h2 id="first-impression">First Impression</h2>
<p>The forum probably does not allow users to post binary files, so challenges are all posted as base64 encoded. There are too many ways to restore the binary, but Binary Ninja saves you from remembering the command: Just copy the encoded text, create a new empty binary, and then click &ldquo;Paste From&rdquo; -&gt; &ldquo;Base64&rdquo;. Then you are done!</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>We get a 14.5 kB ELF file. There is some mild obfuscation in the start of the <code>main</code>, which does not pose a serious challenge. In the middle of the <code>main</code> we see the program is reading input and checking length:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>The first thing I notice is that the input must be exactly 0x50 chars, which is quite unusual. Not it reads at most 0x50 chars and checks if the chars read are at least 0x50 chars, which means it must be 0x50 chars.</p>
<p>Besides, after the length check, we see it calls <code>mmap</code>. For reversing challenges, once we see a <code>mmap</code> in it, probably there is a self-modifying code.</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>Moving downward we see that the program copies a 0xae4-byte buffer into the newly allocated buffer, and then calls it. A strange thing here is the user input is moved into register <code>r12</code>. Typically, no compilers will use register <code>r12</code> to pass function argument, so this code might be hand-crafted.</p>
<p>After the <code>call rdx</code>, the program tells if the flag is correct based on the return value. Now the next step is obvious, we need to define a function on that code buffer and see what it has.</p>
<h2 id="decryption-routine">Decryption routine</h2>
<p><img src="../imgs/4.png" alt=""></p>
<p>The function looks like this. The loop decrypts another buffer at <code>data_20ab</code>, whose size is 0xa59. The decryption is just xor with 0x9f. Note the code_size variable sits right after this function, and right before the next data buffer to be decrypted. Meanwhile, the loop calculates a checksum of the next data buffer, and compare it with the dword at register <code>r12</code>. What is it? It is the user input! So the user input must match the checksum value.</p>
<p>If the checksum matches, the program continues to execute the second newly decrypted buffer. Here, we can use Binary Ninja&rsquo;s transformation to transform the data in place, after which we define a function at the start of it.</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>The newly defined function looks like this:</p>
<p><img src="../imgs/6.png" alt=""></p>
<p>It looks almost the same as the previous one, except for some small mutations. The xor key is different and it is 0xb6 this time. The buffer size is 0x9ce this time, which is smaller than the previous one. And that indicates we are probably recursively decrypting this buffer and each time we only decrypt the first part of it, which forms a function.</p>
<p>I tried to repeat the process a few times and it just repeats. RECURSION. That is probably a good reason for the name.</p>
<p>The first way to solve this is to solve it statically. We only need to get the xor key and the buffer size, to decrypt the buffer and calculate the checksum. However, due to the mutation, it is not that easy to get it correct. It is, though, definitely possible, but not optimal. So I come up with a dynamic approach.</p>
<h2 id="using-hardware-breakpoints-and-automating-gdb">Using Hardware Breakpoints and Automating GDB</h2>
<p>I did not rewrite the checksum algorithm by myself, despite it is super simple. Even if it is super complex and I cannot reverse/rewrite it, I can still solve this challenge. Why?</p>
<p>Because we can wait at the line where the dword from the user input is compared with the correct checksum. Particularly, it is the <code>cmp esi, edi</code> line. the register <code>esi</code> holds our input, which, during debugging, is trash. Register <code>edi</code> holds the correct checksum. If we set a breakpoint here and examine the value of <code>edi</code>, we directly get the correct checksum.</p>
<p>However, this approach cannot easily scale to the entire challenge. The problem here is we do not know where to set the next breakpoint before we decrypt the code. However, manually decrypting the code is arduous and error-prone, so we would better automate the solution.</p>
<p>Note the address of the user input buffer is moved into r12 and never changed. If one checksum matches, the program executes <code>add r12, 0x4</code> to move to the next dword. So we can use a hardware breakpoint to catch the program when it reads the buffer <code>r12</code>, and read the value of <code>edi</code>. Then we remove the current hardware breakpoint, set a new one on the next address, and wait for the program to break again.</p>
<p>Automating GDB is easier said than done. I have known it is possible for a long time, though I have never done it before. After duckduckgo-ing a little bit, I found there are two ways to do it. The first one is to implement a GDB command in Python; the second way is to use pygdbmi to interact with GDB&rsquo;s machine interface.</p>
<p>Both methods allow us to execute gdb commands as if we directly use GDB, and get the output from GDB afterward. However, I found the pygdbmi approach is much harder to use for the current purpose. First of all, it runs GDB headlessly. So if there is an error in the script, it is hard to find it. Conversely, if we take the first approach, since we register ourselves as a new command (<code>solve</code> in particular) after we run the stuff we are still in GDB. We can see the commands we executed and see the outputs from GDB, which allows painless debugging. Also, despite the name machine interface, it does not automatically parse the string output from GDB. For example, if we examine the value of <code>rdi</code> by executing</p>
<p><code>p/x $rdi</code></p>
<p>The GDB returns something like:</p>
<p><code>$1 = 0x555555557e90</code></p>
<p>I would expect the pygdbmi to parse the value for me. However, it does nothing for this and directly returns the string output. We get the very same thing in the first approach. So obviously it is the better way to do it.</p>
<p>Note that I am not saying gdbmi is not good. It is used by various projects, e.g., gdbgui, which is a browser-based GDB frontend. If you have not tried it, I strongly recommend you to experiment with it. It is just using gdbmi will require more development work and it is not suitable for reversing challenge, where we care more about getting things rolling faster.</p>
<p>Ok, so much for the comparison. It is time to get to the code. The code is not fancy &ndash; it just requires some effort to write it correctly.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">import</span> gdb
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> struct
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_reg_value</span>(response):
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>split()[<span style="color:#ae81ff">2</span>]
</span></span><span style="display:flex;"><span>    value <span style="color:#f92672">=</span> int(response, <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> value
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Solve</span>(gdb<span style="color:#f92672">.</span>Command):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__init__</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># This registers our class as &#34;solve&#34;</span>
</span></span><span style="display:flex;"><span>        super(Solve, self)<span style="color:#f92672">.</span><span style="color:#a6e22e">__init__</span>(<span style="color:#e6db74">&#34;solve&#34;</span>, gdb<span style="color:#f92672">.</span>COMMAND_DATA)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">invoke</span>(self, arg, from_tty):
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># When we call &#34;solve&#34; from gdb, this is the method</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># that will be called.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        dummy_input <span style="color:#f92672">=</span> open(<span style="color:#e6db74">&#39;input.txt&#39;</span>, <span style="color:#e6db74">&#39;wb&#39;</span>)
</span></span><span style="display:flex;"><span>        dummy_input<span style="color:#f92672">.</span>write(<span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;1&#39;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">0x50</span>)
</span></span><span style="display:flex;"><span>        dummy_input<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        solution <span style="color:#f92672">=</span> bytes()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        inferiors <span style="color:#f92672">=</span> gdb<span style="color:#f92672">.</span>inferiors()
</span></span><span style="display:flex;"><span>        inferior <span style="color:#f92672">=</span> inferiors[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;del&#39;</span>)
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;file crackme.elf&#39;</span>)
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;set breakpoint pending on&#39;</span>)
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;b __libc_start_main&#39;</span>)
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;r &lt; input.txt&#39;</span>)
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;p/x $rdi&#39;</span>, to_string <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>        main_addr <span style="color:#f92672">=</span> get_reg_value(response)
</span></span><span style="display:flex;"><span>        main_addr_raw <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x1229</span>
</span></span><span style="display:flex;"><span>        print(main_addr)
</span></span><span style="display:flex;"><span>        base <span style="color:#f92672">=</span> main_addr <span style="color:#f92672">-</span> main_addr_raw
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;b *</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> (base <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x1399</span>))
</span></span><span style="display:flex;"><span>        gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;c&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;p/x $rax&#39;</span>, to_string <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>        input_addr <span style="color:#f92672">=</span> get_reg_value(response)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;input_addr&#39;</span>, hex(input_addr))
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>                gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;del&#39;</span>)
</span></span><span style="display:flex;"><span>                gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;rwatch *</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> (input_addr <span style="color:#f92672">+</span> i <span style="color:#f92672">*</span> <span style="color:#ae81ff">4</span>))
</span></span><span style="display:flex;"><span>                gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;c&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                response <span style="color:#f92672">=</span> gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;p/x $edi&#39;</span>, to_string <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>                checksum <span style="color:#f92672">=</span> get_reg_value(response)
</span></span><span style="display:flex;"><span>                print(<span style="color:#e6db74">&#39;checksum&#39;</span>, hex(checksum))
</span></span><span style="display:flex;"><span>                solution <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;I&#39;</span>, checksum)
</span></span><span style="display:flex;"><span>                
</span></span><span style="display:flex;"><span>                gdb<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#39;set $rsi = </span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> checksum)
</span></span><span style="display:flex;"><span>                i <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">except</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;=&#39;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;the flag is:&#39;</span>)
</span></span><span style="display:flex;"><span>        print(solution)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;len:&#39;</span>, len(solution))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        output <span style="color:#f92672">=</span> open(<span style="color:#e6db74">&#39;solution.txt&#39;</span>, <span style="color:#e6db74">&#39;wb&#39;</span>)
</span></span><span style="display:flex;"><span>        output<span style="color:#f92672">.</span>write(solution)
</span></span><span style="display:flex;"><span>        output<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This registers our class to the gdb runtime at &#34;source&#34; time.</span>
</span></span><span style="display:flex;"><span>Solve()
</span></span></code></pre></div><p>To use it,</p>
<ol>
<li>run gdb</li>
<li>inside gdb, run <code>source gdb_solve.py</code></li>
<li>inside gdb, run <code>solve</code></li>
<li>after it runs, it should print the solution and also write it to solution.txt</li>
<li>verify it by <code>cat solution.txt | ./crackme.elf</code></li>
</ol>
<p>Which is quite simple, isn&rsquo;t it? Maybe the command <code>rwatch</code> is new, which sets hardware read/write breakpoints.</p>
<p>The correct flag contains non-printable chars, which is not surprising, as it is unlikely the checksum of the code happens to be a printable string. Unless the maker put some effort to make it that way.</p>
<p>In the above script, there is one thing to point out. We run</p>
<p><code>set breakpoint pending on</code></p>
<p>before</p>
<p><code>b __libc_start_main</code>.</p>
<p>This is because, if we do not do it, the attempt to set a breakpoint on <code>__libc_start_main</code> will produce an error. And it only happens before we run the binary. So in other words, if we first run the binary in GDB for at least once, and then directly set a breakpoint on <code>__libc_start_main</code>, it will succeed. That is because GDB has seen that function once, and it knows to wait for it. However, since we are automating GDB, every time it bootstraps cleanly and it does not know there exists a <code>__libc_start_main</code>, hence the error. I am not familiar enough with the GDB source code so I cannot speculate why it happens, but probably there are some reasons behind it. Anyway, <code>set breakpoint pending on</code> is the correct way to deal with it.</p>
<p>The author also released his/her own <a href="https://0x00sec.org/t/reverseme-recursion/21802/4?u=xusheng">writeup</a>, which needs to be decrypted by the correct flag. I suggest you to have a look at it, espcially the mutation part.</p>
]]></content>
        </item>
        
        <item>
            <title>Solving an Obfuscated Crackme with BinaryNinja and Triton</title>
            <link>https://xusheng.dev/posts/reversing/obfuscation/readme/</link>
            <pubDate>Thu, 02 Jul 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/obfuscation/readme/</guid>
            <description>&lt;p&gt;The last week&amp;rsquo;s &lt;a href=&#34;https://yurichev.com/blog/58/&#34;&gt;challenge&lt;/a&gt; was created by &lt;a href=&#34;https://yurichev.com/&#34;&gt;Dennis Yurichev&lt;/a&gt;. It is also hosted on &lt;a href=&#34;https://crackmes.one/crackme/5ab77f6233c5d40ad448c9e4&#34;&gt;crackmes.one&lt;/a&gt;. The challenge is compiled by a modified &lt;a href=&#34;https://bellard.org/tcc/&#34;&gt;Tiny C Compiler (TCC)&lt;/a&gt; which obfuscates the generated code during compilation. We will cover the major techniques to deobfuscate the binary, followed by a quick analysis of the algorithm itself.&lt;/p&gt;
&lt;h2 id=&#34;first-impression&#34;&gt;First Impression&lt;/h2&gt;
&lt;p&gt;The target (&lt;a href=&#34;../keygenme4.zip&#34;&gt;keygenme4.exe&lt;/a&gt;) is a PE. The entry point looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are several things which we can notice easily:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>The last week&rsquo;s <a href="https://yurichev.com/blog/58/">challenge</a> was created by <a href="https://yurichev.com/">Dennis Yurichev</a>. It is also hosted on <a href="https://crackmes.one/crackme/5ab77f6233c5d40ad448c9e4">crackmes.one</a>. The challenge is compiled by a modified <a href="https://bellard.org/tcc/">Tiny C Compiler (TCC)</a> which obfuscates the generated code during compilation. We will cover the major techniques to deobfuscate the binary, followed by a quick analysis of the algorithm itself.</p>
<h2 id="first-impression">First Impression</h2>
<p>The target (<a href="../keygenme4.zip">keygenme4.exe</a>) is a PE. The entry point looks like this:</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>There are several things which we can notice easily:</p>
<ol>
<li>The basic block is quite long.</li>
<li>It has excessive amounts of continuous arithmetic operations.</li>
</ol>
<ol>
<li>is quite common for obfuscated code. Several obfuscation techniques inflate the code and make it hard to read. 2. is unique to this obfuscator. If we look at the following instructions closely, we notice it is first loading a constant into <code>eax</code>, does a series of arithmetic operations on it, and saving it to a variable.</li>
</ol>
<pre tabindex="0"><code>0041af0a  mov     eax, dword [data_41c1b4]
0041af10  shr     eax, 0x0
0041af13  shr     eax, 0x1
0041af16  xor     eax, 0xa8f3a9ca
0041af1c  shl     eax, 0x7
0041af1f  sub     eax, 0x5a041880
0041af25  mov     dword [ebp-0x14 {var_18}], eax
</code></pre><p>After we check the dword value at data_41c1b4, we can emulate the above code snippet and find out the final value of <code>eax</code>. It turns out to be 0. So the code is equivalent to:</p>
<pre tabindex="0"><code>0041af25  mov     dword [ebp-0x14 {var_18}], 0x0
</code></pre><p><code>data_41c1b4</code> is a global variable. When we click on it, we can see all its cross-references. After browsing the list we find that the value is read a lot of time, but it is never written to.</p>
<p><img src="../imgs/9.png" alt=""></p>
<p>This means the value never changes. And sequence of instructions like the above one can be simplified to just one constant load.</p>
<p>We will call this <code>convoluted constant load</code> later on. Not bad, we break one of the obfuscation! Well, not yet. We just understand how it works and solves it manually. But we need to solve them automatically. <code>Automation</code> is an important topic in deobfuscation. Often the obfuscation is not hard to understand, but solving them can be much harder. We will discuss how to tackle it later.</p>
<h2 id="hunting-for-other-obfuscation-techniques">Hunting for Other Obfuscation Techniques</h2>
<p>As we explore the binary, we can find the following obfuscation techniques.</p>
<h3 id="obfuscated-calls">Obfuscated Calls</h3>
<p><img src="../imgs/2.png" alt=""></p>
<p>0x41c0fa is the string to be printed by <code>printf</code>, which is nothing special. The next four instructions do the interesting stuff:</p>
<pre tabindex="0"><code>00416db8  call    $+5 {var_c}  {data_416dbd}
00416dbd  pop     eax {var_c}
00416dbe  add     eax, 0xa  {sub_416dc7}
00416dc1  push    eax {var_c}  {sub_416dc7}
</code></pre><p>After the <code>call $+5</code>, the <code>eip</code> becomes 0x416dbd. Meanwhile, the return address is pushed onto the top of the stack. Note the return address is also 0x416dbd. The following <code>pop, add, push</code> sequence would change the return address to <code>sub_416dc7</code> (which is also calculated by BinaryNinja).</p>
<p>Now it executes <code>jmp printf</code>. Note this is a <code>jmp</code> so it does not push a return address onto the stack. <code>sub_416dc7</code> is still on the top of the stack. The string to be printed is right below it. In other words, this creates a fake call stack and it manipulates the return address so the code will continue execution from a different place (rather than the code below the printf).</p>
<p>printf has no magic and it just prints the string. When the execution returns from the printf, the return address sub_416dc7 is popped from the stack and executed. In other words, the above code is equivalent to:</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>This is not hard to deal with since the pattern is quite obvious. We will cover how to solve it later.</p>
<h3 id="opaque-predicate">Opaque Predicate</h3>
<p><img src="../imgs/6.png" alt=""></p>
<p>Another abnormal thing we notice is the code has an excessive amount of branches. If we look at the code closely, we can see something like this:</p>
<pre tabindex="0"><code>00401248  sub     edx, edx  {0x883d6589}  {0x883d6589}
0040124a  jne     0x40120a  {0x0}
</code></pre><p>Thanks to the <code>sub edx, edx</code>, the zero flag is always set thus the jne never jumps. This is more obvious if we switch to the Low-Level IL (LLIL):</p>
<p><img src="../imgs/7.png" alt=""></p>
<p>In other words, the branch is fake and the execution always gets to 0x40124c. We call this an <code>opaque predicate</code>.</p>
<p>Opaque predicate is a well-known obfuscation technique that slows down reversers. When we reverse a piece of code, we often first get a grasp of the behavior of the code by looking at its layout (branches, loops, etc). Even beginners know to look for the critical branch that decides whether the code will print a &ldquo;congratulation&rdquo; or &ldquo;sorry&rdquo;.</p>
<p>Opaque predicate can be removed statically &ndash; since they are opaque and one branch is always taken. However, in real-world, we can use some mathematical fact that is hard to be solved by a program. For example, for any integer <code>x</code>, this is always true:</p>
<pre tabindex="0"><code>x * (x + 1) * (x + 2) == 0 (mod 6)
</code></pre><p>If we know the set of opaque predicates the obfuscator use, then we can do a pattern matching. Otherwise, we might need to use z3 to prove it. The good news is, in this particular binary, the opaque predicates are quite easy to deal with.</p>
<h3 id="junk-code-insertion">Junk Code Insertion</h3>
<p>Inserting junk code is another popular obfuscation technique. Check out the following code:</p>
<p><img src="../imgs/5.png" alt=""></p>
<p>It is pretty messy &ndash; which is a sign of useless code. And we see the register <code>edx</code> is overwritten before it is used. By &ldquo;used&rdquo; I mean it is not written to the memory or used to calculate other values.</p>
<p>Junk code insertion is very easy to implement, and not always easy to solve. There is no silver bullet for it, though optimization is the general method to deal with it. Yes, optimization not only helps compiler generate faster code, it also helps remove unnecessary code. For the interested readers, this <a href="https://github.com/JonathanSalwan/Tigress_protection">script</a> uses LLVM to remove the Trigress VM.</p>
<p>However, in this writeup, we will take a different approach. We will leverage some property of the code generated by TCC and solve it by backward taint analysis.</p>
<h2 id="writing-a-deobfuscator">Writing a deobfuscator</h2>
<p>Before I discuss the details of the deobfuscator, I would like to first address the availability of the obfuscator source code. We all know TCC is open-source. And Dennis provided the <a href="http://conus.info/stuff/tcc-obfuscate/tcc-0.9.25-diff">patch file</a>. So we can look at how the obfuscated binary is generated. This is of course a good thing since we can learn from the obfuscator. And I do recommend that everyone read it since it can show a relatively simple and lightweight way of implementing an obfuscating compiler.</p>
<p>However, this also introduces a problem: in the real-world, we typically do not have the source code for the obfuscator. So we should avoid using too many details from it. Though I still use some &ndash; but I try to limit them as few as possible.</p>
<p>Writing a deobfuscator is harder than understanding the obfuscation. I was once hesitant to start tackling this since I know it is going to take a few days. And now when I look at the code I have written, I still remember the hardship that I encountered. But I have succeeded! This is something that I am proud of.</p>
<p>We probably cannot cover every detail of the deobfuscator. It is only 500 LoC but discussing every bit (as well as the reasoning behind it) is beyond the scope of this writeup. But I will cover the major highlights.</p>
<h3 id="planning">Planning</h3>
<p>Writing a good deobfuscator needs some planning before actual coding. The first thing we need to consider is tooling. Binary analysis requires lots of tools to proceed. How do we get the disassembly? What is the processing pipeline?</p>
<p>In this writeup, I use BinaryNinja as the primary tool. It has a powerful Python API so it is quite easy to access the disassembled functions, basic blocks, assembly lines, etc. Later I also used Triton for backward taint analysis, which allows me to remove junk code quite effectively.</p>
<p>The goal is simple: produce deobfuscated code. Note, however, one hurdle here is we do not have the ground truth for the obfuscated binary. As a result, we need to write the code, see its result in action, and make modifications and adjustments accordingly. When I deal with the opaque predicate, once my assumption was too wide and the deobfuscated code is empty. Then I have to go back and examine every branch the tool patched out and see which one is wrong (and should not be wiped out). One thing I did is to write a simple test C program, produce both the original binary and an obfuscated binary, and then test my tool on it. This allows me to fix several bugs faster.</p>
<p>Automation is the result we want. However, we also need to make some compromise on this. Ideally, our program should take the binary as input and produce a deobfuscated one. However, I found there are too many corner cases so I decided that my tool will process one basic block a time. This allows me to verify if the result is correct. Later on, I enhanced it to process a function a time  &ndash; just iterate over the basic blocks in a function and process them one by one. Even this sometimes causes problem and I have to go back to process basic blocks one by one. The good part is my script is pretty robust that almost never needs any manual fixes.</p>
<h3 id="the-skeleton-of-a-binaryninja-plugin">The Skeleton of a BinaryNinja Plugin</h3>
<p>BinaryNinja allows a plugin to register a callback on an address.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>PluginCommand<span style="color:#f92672">.</span>register_for_address(<span style="color:#e6db74">&#34;Deobfuscate&#34;</span>,
</span></span><span style="display:flex;"><span>                                   <span style="color:#e6db74">&#34;Remove tcc&#34;</span>,
</span></span><span style="display:flex;"><span>                                   bootstrap)
</span></span></code></pre></div><p><code>bootstrap</code> is the function that gets called every time we click the <code>Deobfuscate</code> context menu. It is just a wrapper around the <code>simplify_bbl_handler</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">simplify_bbl_handler</span>(bv, addr):
</span></span><span style="display:flex;"><span>    bbl <span style="color:#f92672">=</span> bv<span style="color:#f92672">.</span>get_basic_blocks_at(addr)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    instrs_to_include <span style="color:#f92672">=</span> simplify_bbl(bv, bbl)
</span></span><span style="display:flex;"><span>    bv<span style="color:#f92672">.</span>begin_undo_actions()
</span></span><span style="display:flex;"><span>    nop_excluded_instrs(bv, bbl, instrs_to_include)
</span></span><span style="display:flex;"><span>    solve_load_bbl(bv, bbl)
</span></span><span style="display:flex;"><span>    bv<span style="color:#f92672">.</span>commit_undo_actions()
</span></span></code></pre></div><p>We get the basic block at the current address by <code>bv.get_basic_blocks_at()</code>. Then we start the real deobfuscating. Note we also add undo actions which is quite handy during development &ndash; since we need to frequently change the code and see its result.</p>
<h3 id="solving-convoluted-constant-load">Solving Convoluted Constant Load</h3>
<p>The convoluted constant load is the first obfuscation technique we discussed. And it is used a lot across the binary. It is not hard to solve since its operations are simple. The problem is we need to locate it in the binary &ndash; where it starts and where it ends. This is trivial for a human reverser, but it is not easy for a program.</p>
<p>The implementation is in the function <code>solve_load_bbl()</code> in the <a href="solution/de-obfuscate/de-obfuscate.py">script</a>. The code is long so I cannot show it here. It does some pattern matching. It looks for consecutive arithmetic operations (<code>'add|sub|shl|shr|xor'</code>) on registers <code>['eax', 'ebx', 'ecx', 'edx']</code>. This might not be the <code>best</code> solution, but it works. We have to make some compromise since it is very hard to write the best code for a deobfuscator, which deals with messy things.</p>
<p>After discovering the pattern, it emulates the operations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">solve_load_ops</span>(bv, ops):
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> opcode, operand <span style="color:#f92672">in</span> ops:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;mov&#39;</span>:
</span></span><span style="display:flex;"><span>            addr <span style="color:#f92672">=</span> int(operand, <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>            val_bytes <span style="color:#f92672">=</span> bv<span style="color:#f92672">.</span>read(addr, <span style="color:#ae81ff">4</span>)
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> struct<span style="color:#f92672">.</span>unpack(<span style="color:#e6db74">&#39;&lt;I&#39;</span>, val_bytes)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>            print(hex(val))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;add&#39;</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> (val <span style="color:#f92672">+</span> int(operand, <span style="color:#ae81ff">16</span>)) <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;sub&#39;</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> (val <span style="color:#f92672">-</span> int(operand, <span style="color:#ae81ff">16</span>)) <span style="color:#f92672">%</span> (<span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">32</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;xor&#39;</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> val <span style="color:#f92672">^</span> int(operand, <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;shl&#39;</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> val <span style="color:#f92672">&lt;&lt;</span> int(operand, <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">&amp;=</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> opcode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;shr&#39;</span>:
</span></span><span style="display:flex;"><span>            val <span style="color:#f92672">=</span> val <span style="color:#f92672">&gt;&gt;</span> int(operand, <span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#39;unknown operation: </span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> opcode)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> val
</span></span></code></pre></div><p>After we calculate the final load value, we need to patch the code, which is super convenient in BinaryNinja.</p>
<h3 id="solving-obfuscated-calls">Solving Obfuscated Calls</h3>
<p>The obfuscated call needs to be restored. Note the obfuscated code has <code>jmp printf</code> in it, but we need to change it to <code>call printf</code>. And then add a <code>jmp</code> to the next function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">solve_push_jmp</span>(bv, func):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> bbl <span style="color:#f92672">in</span> func<span style="color:#f92672">.</span>basic_blocks:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> bbl<span style="color:#f92672">.</span>instruction_count <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">5</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        disassembly_text <span style="color:#f92672">=</span> bbl<span style="color:#f92672">.</span>get_disassembly_text()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> str(disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">5</span>])<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;call    $+5&#39;</span>) <span style="color:#f92672">and</span> \
</span></span><span style="display:flex;"><span>            str(disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">4</span>])<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;pop     eax&#39;</span>) <span style="color:#f92672">and</span> \
</span></span><span style="display:flex;"><span>            str(disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">3</span>])<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;add     eax, 0xa&#39;</span>) <span style="color:#f92672">and</span> \
</span></span><span style="display:flex;"><span>            str(disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>])<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;push    eax&#39;</span>) <span style="color:#f92672">and</span> \
</span></span><span style="display:flex;"><span>            str(disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>])<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;jmp&#39;</span>):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            patch_addr <span style="color:#f92672">=</span> disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">5</span>]<span style="color:#f92672">.</span>address
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#39;push_jump at: 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> patch_addr)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            jmp_addr <span style="color:#f92672">=</span> disassembly_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>address
</span></span><span style="display:flex;"><span>            callee_offset_bytes <span style="color:#f92672">=</span> bv<span style="color:#f92672">.</span>read(jmp_addr <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">4</span>)
</span></span><span style="display:flex;"><span>            caller_offset <span style="color:#f92672">=</span> struct<span style="color:#f92672">.</span>unpack(<span style="color:#e6db74">&#39;&lt;i&#39;</span>, callee_offset_bytes)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>            callee_addr <span style="color:#f92672">=</span> jmp_addr <span style="color:#f92672">+</span> caller_offset <span style="color:#f92672">+</span> <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            dis <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;call 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> callee_addr
</span></span><span style="display:flex;"><span>            inst_bytes <span style="color:#f92672">=</span> arch<span style="color:#f92672">.</span>assemble(dis, patch_addr)
</span></span><span style="display:flex;"><span>            bv<span style="color:#f92672">.</span>write(patch_addr, inst_bytes)
</span></span><span style="display:flex;"><span>            
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># this sequence is 15 byte long</span>
</span></span><span style="display:flex;"><span>            return_addr <span style="color:#f92672">=</span> patch_addr <span style="color:#f92672">+</span> <span style="color:#ae81ff">15</span>
</span></span><span style="display:flex;"><span>            jmp_addr <span style="color:#f92672">=</span> patch_addr <span style="color:#f92672">+</span> len(inst_bytes)
</span></span><span style="display:flex;"><span>            dis_jmp <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;jmp 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> return_addr
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            inst_bytes <span style="color:#f92672">=</span> arch<span style="color:#f92672">.</span>assemble(dis_jmp, jmp_addr)
</span></span><span style="display:flex;"><span>            bv<span style="color:#f92672">.</span>write(jmp_addr, inst_bytes)
</span></span></code></pre></div><p>We need to do some math to calculate the <code>callee_addr</code>, <code>return_addr</code>, and <code>jmp_addr</code>. Once we finish this the control flow is much better since we now know which functions get called.</p>
<h3 id="solving-opaque-predicate">Solving Opaque Predicate</h3>
<p>BinaryNinja already has a <a href="https://binary.ninja/2017/10/01/automated-opaque-predicate-removal.html">plugin</a> for patching opaque predicates. It relies on the lifted LLIL to see if the flags used in the conditional jump can be deduced from the preceding code, and patch it if so.</p>
<p>However, it does not suit my need because it does not take care of the convoluted constant load. Have a look at the following code:</p>
<pre tabindex="0"><code>00401172  mov     eax, dword [data_41c000]
00401178  mov     ecx, dword [data_41c1ac]
0040117e  xor     ecx, 0x32744b9b
00401184  shl     ecx, 0x2
00401187  shl     ecx, 0x4
0040118a  sub     ecx, 0x63fa1799
00401190  shr     ecx, 0x2
00401193  xor     ecx, 0x6a18d496
00401199  sub     ecx, 0x756243af
0040119f  cmp     eax, ecx
004011a1  jge     0x401516
</code></pre><p>The <code>eax</code> is loaded directly from the memory, and <code>ecx</code> is also a constant. So the result of the comparison is deterministic. However, LLIL does not squash these arithmetic operations so it does not know this is an opaque predicate.</p>
<p>So I wrote my opaque predicate patcher. Here I read the source code of the obfuscator and found out that it only inject two types of opaque predicates: the first one is the <code>sub-and-jump</code> and the second is <code>xor-and-jump</code>. Both are quite easy to find. Then I just search for sub/xor instruction, whose two operands are the same, and it is followed by a conditional jump. If found, I patch it accordingly.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">is_opaque_predicate</span>(instr):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    tokens <span style="color:#f92672">=</span> instr<span style="color:#f92672">.</span>tokens
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> tokens[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>text <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;xor&#39;</span> <span style="color:#f92672">and</span> tokens[<span style="color:#ae81ff">2</span>]<span style="color:#f92672">.</span>text <span style="color:#f92672">==</span> tokens[<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>text:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> tokens[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>text <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;sub&#39;</span> <span style="color:#f92672">and</span> tokens[<span style="color:#ae81ff">2</span>]<span style="color:#f92672">.</span>text <span style="color:#f92672">==</span> tokens[<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>text:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>   
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">solve_opaque_predicate</span>(bv, func):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> bbl <span style="color:#f92672">in</span> func<span style="color:#f92672">.</span>basic_blocks:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># jne to self</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> bbl<span style="color:#f92672">.</span>instruction_count <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>            instr <span style="color:#f92672">=</span> bbl<span style="color:#f92672">.</span>get_disassembly_text()[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> instr<span style="color:#f92672">.</span>tokens[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>text<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#39;jne&#39;</span>):
</span></span><span style="display:flex;"><span>                bv<span style="color:#f92672">.</span>never_branch(instr<span style="color:#f92672">.</span>address)  
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        instrs <span style="color:#f92672">=</span> bbl<span style="color:#f92672">.</span>get_disassembly_text()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>            instr1, instr2 <span style="color:#f92672">=</span> instrs[<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span> :]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">except</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#39;error at: 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> bbl<span style="color:#f92672">.</span>start)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> is_opaque_predicate(instr1):
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> should_patch_to_always_branch(instr2):
</span></span><span style="display:flex;"><span>                log_info(<span style="color:#e6db74">&#39;always branch at: 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> instr2<span style="color:#f92672">.</span>address)
</span></span><span style="display:flex;"><span>                bv<span style="color:#f92672">.</span>always_branch(instr2<span style="color:#f92672">.</span>address)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">elif</span> should_patch_to_never_branch(instr2):
</span></span><span style="display:flex;"><span>                log_info(<span style="color:#e6db74">&#39;never branch at: 0x</span><span style="color:#e6db74">%x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> instr2<span style="color:#f92672">.</span>address)
</span></span><span style="display:flex;"><span>                bv<span style="color:#f92672">.</span>never_branch(instr2<span style="color:#f92672">.</span>address)
</span></span></code></pre></div><p>A careful reader should already find that I made a mistake here. On the one hand, after I read the source code I find there are only two types of opaque predicates; on the other hand, I think the conditional shown above (<code>0040119f  cmp     eax, ecx; 004011a1  jge     0x401516</code>) is also opaque. The problem is this is a <code>cmp</code> followed by <code>jge</code>, which is not opaque at all!</p>
<p>The problem is the <code>00401172  mov     eax, dword [data_41c000]</code>. This is not a convoluted constant load; it is just a regular variable load! The value of <code>dword [data_41c000]</code> could change and this is a meaningful branch. After I realized this, I went back to change the code that solves the constant load: it has to have two operations at least.</p>
<h3 id="solving-junk-code">Solving Junk Code</h3>
<p>Junk code removal is the hardest part of this binary. It is not the most prominent technique used in it, but it does require a significant amount of work to solve. Junk code is easy to recognize for human reverser: it often contains a lot of meaningless/weird combinations of instructions. But this does not help a program to recognize them.</p>
<p>As I mentioned, simplification is probably the <code>right</code> way to do it. But it also requires a huge amount of work. I looked at the generated assembly and found one property of TCC can help us solve it more easily. This involves compiler theories but I will keep it as simple as possible.</p>
<p>TCC is <code>tiny</code> so it does not do a lot of optimization on the generated code. The <a href="https://lists.gnu.org/archive/html/tinycc-devel/2006-09/msg00028.html">email list</a> writes:</p>
<pre tabindex="0"><code>TinyCC compiles every statement on its own. After every line of code,
values in registers are written back to the stack. And even if the next
line uses the variables that can still be found in registers, they are
read again from the stack.
</code></pre><p>This means, for TCC emitted code, if a value is calculated (and held in register) but not written back to the memory, then it is to be discarded. Though we do not necessarily see the register holding the value being over-written. This allows us to remove junk code on a basic block level rather than a function level, which is a lot easier to implement.</p>
<p>Note, however, the above statement is not true for other compilers, e.g., gcc. Gcc generates code that uses a register to hold the loop counter <code>i</code>. The value is never written back to the memory inside the loop. For code like that, it is harder to deduce if a value is used later or not.</p>
<p>The idea of the simplification is to pick all the useful instructions from a basic block. We start from instructions that write to the memory. And we do backward taint analysis on all of them to get a set of instructions that affect the final value written to the memory. We do this repeatedly until no new instructions are added. Then we get all the useful instructions. Finally, we remove all the useless instructions (junk code).</p>
<p>The code is in <code>simplify_bbl()</code>. It is quite long so I do not post it here. <a href="https://triton.quarkslab.com/">Triton</a> does the taint analysis. The recursion part is the most difficult to write.</p>
<p>This method will be voided if the obfuscator writes the junk value into the memory. However, it is also harder to implement since the obfuscator authors need to find a safe place to write to.</p>
<p>One question puzzles me for a long time until I read the source of the obfuscator. How does the junk code inserter make sure that it does not accidentally destroy some useful register value? For example, when it writes to <code>edx</code>, is it sure that <code>edx</code> does not hold any value that is used later? The answer is, TCC uses value stack (vstack) to keep track of values. And the obfuscator avoids writing to any registers in the vstack. The relevant code is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">is_reg_in_vstack</span> (<span style="color:#66d9ef">int</span> r)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    SValue <span style="color:#f92672">*</span>p;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span>(p<span style="color:#f92672">=</span>vstack; p<span style="color:#f92672">&lt;=</span>vtop; p<span style="color:#f92672">++</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (r<span style="color:#f92672">==</span>((p<span style="color:#f92672">-&gt;</span>r)<span style="color:#f92672">&amp;</span><span style="color:#ae81ff">0xFF</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><h3 id="nop-ing-out-useless-instructions">Nop-ing Out Useless Instructions</h3>
<p>We almost succeed! We have handled all the obfuscation techniques. The last step is to remove those junk code (as well as some residue of other obfuscations). We can simply <code>nop</code> them, but then it creates looooong nop slides which make the code hard to read. Moving the remaining code to make it compact is non-trivial. It affects relocation, inline data, etc. Later, I learned (thx Jordan!) that switching to LLIL automatically removes these nops, as shown below:</p>
<p><img src="../imgs/10.png" alt=""></p>
<p>However, when I approach the problem by myself, I found this is seldom discussed in deobfuscation literature so I came up with my own solution.</p>
<p>We still first patch the junk code with <code>nop</code>. Since a <code>jmp</code> is only 5 bytes long, if there is a nop-slide that is longer than 5 bytes, we create a <code>jmp</code> that directly jumps to the end of the nop slide. The logic is implemented in <code>nop_excluded_instrs()</code>. The result does not look perfect, but it is already much more readable:</p>
<p><img src="../imgs/8.png" alt=""></p>
<p>It first checks if argv[1] is 0. If so, it prints an error message. If not, it calls <code>strlen</code> on it and jumps to another function, which I named <code>check</code>.</p>
<h2 id="analyzing-the-algorithm">Analyzing the algorithm</h2>
<p>Finally, we get a clean binary that can be analyzed. The algorithm itself is not trivial and it requires some patience. It is probably a level 3 crackme if no obfuscation is applied. Though we will only cover the most important pieces of it.</p>
<p>Interestingly, the deobfuscated binary still has other obfuscations. The most prominent one is the buffer is processed in 16 functions. Each function takes care of 8 bytes of the buffer. the functions are functionally identical but it is not organized into a loop. The first function will call the second, the second will call the third, etc.</p>
<p>The key must be 128 bytes long. The buffer is first decrypted using a CBC mode XOR. The key is 16 DWORDs calculated dynamically. We do not care about how the key is calculated; we just need to dump it once the programs calculated it.</p>
<p>The decrypted buffer contains the serial number, user name, enabled feature sets, and expiration date. At the end of it, there is a checksum value. The checksum is calculated at sub_415353. One has to be familiar with the calculation of crc32 to understand this function. This is calculating a crc32 of the input buffer using magic value 0xedb88320 (see address 0x415747).</p>
<p>Finally, we arrived at the keygen script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">import</span> struct 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">crc32</span>(s, init_val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>, final_xor <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    poly <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xedb88320</span>
</span></span><span style="display:flex;"><span>    crc <span style="color:#f92672">=</span> init_val
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> c <span style="color:#f92672">in</span> s:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> type(c) <span style="color:#f92672">==</span> str:
</span></span><span style="display:flex;"><span>            asc <span style="color:#f92672">=</span> ord(c)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            asc <span style="color:#f92672">=</span> c
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        asc <span style="color:#f92672">^=</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>        crc <span style="color:#f92672">^=</span> asc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">8</span>):
</span></span><span style="display:flex;"><span>            eax <span style="color:#f92672">=</span> crc <span style="color:#f92672">&amp;</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            var_c_1 <span style="color:#f92672">=</span> (<span style="color:#f92672">-</span>eax) <span style="color:#f92672">%</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            var_8 <span style="color:#f92672">=</span> crc <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            var_c_1 <span style="color:#f92672">&amp;=</span> poly
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            crc <span style="color:#f92672">=</span> var_8 <span style="color:#f92672">^</span> var_c_1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        crc <span style="color:#f92672">^=</span> <span style="color:#ae81ff">0xffffffff</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    crc <span style="color:#f92672">^=</span> final_xor   
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> crc  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">transform_back</span>(buffer):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    rngs_vals <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x10D88067</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xBC16D3D5</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xE7039A64</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x39EC8A6D</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xFF09B4BF</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xF828DB76</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x8BE40C8E</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xF7AA583E</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x60858E23</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xE487F5A3</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x39A57B89</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0xB006573E</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x79609807</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x620AD108</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x5CD86398</span>, 
</span></span><span style="display:flex;"><span>        <span style="color:#ae81ff">0x6CA94B51</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    var_0x8c <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    ints <span style="color:#f92672">=</span> struct<span style="color:#f92672">.</span>unpack(<span style="color:#e6db74">&#39;&lt;&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;I&#39;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">16</span>, buffer)
</span></span><span style="display:flex;"><span>    restored_ints <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">16</span>):
</span></span><span style="display:flex;"><span>        restored_int <span style="color:#f92672">=</span> ints[i] <span style="color:#f92672">^</span> rngs_vals[i] <span style="color:#f92672">^</span> var_0x8c
</span></span><span style="display:flex;"><span>        restored_ints<span style="color:#f92672">.</span>append(restored_int)
</span></span><span style="display:flex;"><span>        var_0x8c <span style="color:#f92672">=</span> restored_int
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> restored_ints
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;jeff&#39;</span>
</span></span><span style="display:flex;"><span>    sn <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x12348765</span>
</span></span><span style="display:flex;"><span>    feature <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x123</span>
</span></span><span style="display:flex;"><span>    expire_year <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x2981</span>
</span></span><span style="display:flex;"><span>    expire_month <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x34</span>
</span></span><span style="display:flex;"><span>    expire_date <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x12</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">=</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\x00</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">*</span> (<span style="color:#ae81ff">32</span> <span style="color:#f92672">-</span> len(name))
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;I&#39;</span>, sn)
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;I&#39;</span>, feature)
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;H&#39;</span>, expire_year)
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;B&#39;</span>, expire_month)
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;B&#39;</span>, expire_date)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\x00</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">16</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    crc <span style="color:#f92672">=</span> crc32(buffer)
</span></span><span style="display:flex;"><span>    buffer <span style="color:#f92672">+=</span> struct<span style="color:#f92672">.</span>pack(<span style="color:#e6db74">&#39;&lt;I&#39;</span>, crc)
</span></span><span style="display:flex;"><span>    print(buffer<span style="color:#f92672">.</span>encode(<span style="color:#e6db74">&#39;hex&#39;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    restored_int <span style="color:#f92672">=</span> transform_back(buffer)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    key <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> val <span style="color:#f92672">in</span> restored_int:
</span></span><span style="display:flex;"><span>        key <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;</span><span style="color:#e6db74">%08x</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> val
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;key: &#39;</span>)
</span></span><span style="display:flex;"><span>    print(key)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    main()
</span></span></code></pre></div><p>An example run looks like this:</p>
<pre tabindex="0"><code>&gt;keygenme4.exe 76bee50dcaa836d82dabacbc144726d1eb4e926e13664918988245966f281da81d9914eef91ee06ed28fb2666289e5581be97d5f79e3ac57253bcfcf254ff9d4
Yonkie&#39;s keygenme#4 &lt;dennis@conus.info&gt;
licensed name=jeff
sn=12348765
featureset=0123
expiration=12342981
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Examining the difference between C program and Assembly -- An Example of &lt;&lt; and shl</title>
            <link>https://xusheng.dev/posts/reversing/shl_undefined_behavior/writeup/</link>
            <pubDate>Sat, 20 Jun 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/shl_undefined_behavior/writeup/</guid>
            <description>&lt;h2 id=&#34;encountering-a-weird-issue&#34;&gt;Encountering a Weird Issue&lt;/h2&gt;
&lt;p&gt;Recently, I needed to write one function that returns a bitmask according to the number of bits. Basically, if the input is 8, it should return 0xff. The input &lt;code&gt;n&lt;/code&gt; is in the range of 0-64 (both side include).&lt;/p&gt;
&lt;p&gt;The first idea is to use left shift and then minus 1:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getBitMask&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; ret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1UL&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; n) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ret;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This works well when n is in the range of 0-63. However, when n is 64, the code returns 0 instead of 0xffffffffffffffff.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="encountering-a-weird-issue">Encountering a Weird Issue</h2>
<p>Recently, I needed to write one function that returns a bitmask according to the number of bits. Basically, if the input is 8, it should return 0xff. The input <code>n</code> is in the range of 0-64 (both side include).</p>
<p>The first idea is to use left shift and then minus 1:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">uint64_t</span> <span style="color:#a6e22e">getBitMask</span>(<span style="color:#66d9ef">size_t</span> n)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint64_t</span> ret <span style="color:#f92672">=</span> (<span style="color:#ae81ff">1UL</span> <span style="color:#f92672">&lt;&lt;</span> n) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> ret;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This works well when n is in the range of 0-63. However, when n is 64, the code returns 0 instead of 0xffffffffffffffff.</p>
<p>I isolated the problem and created the following minimal PoC:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;stdio.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;inttypes.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> n <span style="color:#f92672">=</span> <span style="color:#ae81ff">64</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint64_t</span> ret <span style="color:#f92672">=</span> (<span style="color:#ae81ff">1UL</span> <span style="color:#f92672">&lt;&lt;</span> n) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;0x%lx</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, ret);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And the command to compile and run it:</p>
<pre tabindex="0"><code> $ gcc -o test test.c
 $ ./test
0x0
</code></pre><p>This result is counter-intuitive since when n is 64, the only bit in 1 should be shifted out and it becomes <code>0 - 1</code>, which should give me 0xffffffffffffffff.</p>
<p>I have no idea why it behaves like this so I decided to load the compiled binary into BinaryNinja to see what is happening.</p>
<h2 id="assembly-never-lies">Assembly Never Lies</h2>
<p><img src="../imgs/1.png" alt=""></p>
<p>It looks correct to me. Completely confused, I launched gdb and see what is happening. It quickly turns out that after the <code>shl rdx, cl</code> at 0x663, rdx remains 0x1 rather than becoming 0. And 1 - 1 is 0 &ndash; that is why 0x0 is printed.</p>
<p>Some vague impression of the shl instruction struck me. <code>cl</code> is 64 now, which is also the size of the register being shifted. Does it affect the execution? I navigated to the Intel reference manual and start reading the page that documents the shl instruction. I found this:</p>
<pre tabindex="0"><code>The count operand can be an immediate value or the CL register. The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used).
</code></pre><p>We are in 64-bit mode here. The documentation states that the bits beyond the lowest 6 are discarded. Now we have 64 (0b1000000) in cl, whose lowest 6 bits are zeros. No wonder rdx remains 1 after the shl &ndash; we are effectively shifting 0 bit.</p>
<p>Ok, things are sorted out now. But I decided to test how gcc handles this when optimizations are on. Because when we turn on optimization (e.g., -O2), it is very likely the value of <code>ret</code> is calculated by the compiler rather than in runtime. Does gcc also enforce the width limit on the shift count?</p>
<pre tabindex="0"><code> $ gcc -O2 -o test_O2 test.c
 $ ./test_O2 
0xffffffffffffffff
</code></pre><p>Wow, the output is different from the previous one! And the disassembly looks like this:</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>The value 0xffffffffffffffff is directly printed. It same gcc -O2 behaves in the same way as I expected &ndash; it ignores the limit on the shift count.</p>
<p>Well, we now get one source code that gives different result when compiled with <code>-O0</code> and <code>-O2</code>. Is this a gcc bug?</p>
<p>Nope, it is not. C standard actually defines the behavior as undefined:</p>
<pre tabindex="0"><code>-- An expression is shifted by a negative number or by an amount greater than or equal to the width of the promoted expression (6.5.7).
</code></pre><p>Since this behavior is undefined, the difference between the <code>-O0</code> and <code>-O2</code> is not a bug.</p>
<p>Back to the function I need to write, although there might be a way to implement the functionality without a branch, it probably exploits certain implementation of a particular compiler. Which is unreliable and bad for cross-platform and cross-compiler compatibility. I decided to put a <code>if</code> for the case <code>n == 64</code>.</p>
<h2 id="epilog">Epilog</h2>
<p>Differences between the C source code and the compiled x86 binary is an well-known issue. This paper comes to my mind first: <a href="https://research.cs.wisc.edu/wpis/papers/wysinwyx.final.pdf">WYSINWYX:What You See Is Not What You eXecute</a>.</p>
<p>C is quite low level so it has a close relation with the underlying hardware. C standard defines certain behavior as undefined to save the effort of C compiler authors. If the <code>&lt;&lt;</code> operator is defined when the shift count is larger than or equal to the register width, there will be more branches in the compiler code to take care of many edge cases.</p>
<p>Reading the assembly is probably the best method to resolve similar issues. In fact, during the development I once missed the <code>UL</code> after the constant <code>1</code>. And the code stops working after the n is larger than 32.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">int</span> n <span style="color:#f92672">=</span> <span style="color:#ae81ff">48</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">uint64_t</span> ret <span style="color:#f92672">=</span> (<span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> n) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;0x%lx</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, ret);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>When the above code is compiled with <code>-O0</code>, it prints <code>0xffff</code>. Why? Because 1 is considered a 32-bit integer and gcc decides to use <code>edx</code> (instead of <code>rdx</code>) to hold it.</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>Since 48 = 0b110000, and only the lowest 5 bits are involved in the calculation, we are effectively left shifting 16 bits. That is why we get 0xffff as the output.</p>
<p>Last but not least, what would we get if we compile the above code with <code>-O2</code>? The result is surprising to me at first sight, followed by an aha moment.</p>
]]></content>
        </item>
        
        <item>
            <title>Solving an ARM challenge with z3</title>
            <link>https://xusheng.dev/posts/reversing/armageddon/solve/</link>
            <pubDate>Thu, 18 Jun 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/armageddon/solve/</guid>
            <description>&lt;h2 id=&#34;first-impression&#34;&gt;First Impression&lt;/h2&gt;
&lt;p&gt;The last week&amp;rsquo;s challenge is hosted at &lt;a href=&#34;https://crackmes.one/crackme/5edb0b8533c5d449d91ae73b&#34;&gt;https://crackmes.one/crackme/5edb0b8533c5d449d91ae73b&lt;/a&gt;. It is authored by Towel and it is a real challenge in UMDCTF 2019.&lt;/p&gt;
&lt;p&gt;Loading it into BinaryNinja reveals that it is an ARM binary. Not very surprised as its name is &lt;code&gt;armageddon&lt;/code&gt;. ARM is no longer special for me as I gradually become familiar with the ISA. After all, it is simpler than the x86 and those frequently used instructions are easy to understand and remember.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="first-impression">First Impression</h2>
<p>The last week&rsquo;s challenge is hosted at <a href="https://crackmes.one/crackme/5edb0b8533c5d449d91ae73b">https://crackmes.one/crackme/5edb0b8533c5d449d91ae73b</a>. It is authored by Towel and it is a real challenge in UMDCTF 2019.</p>
<p>Loading it into BinaryNinja reveals that it is an ARM binary. Not very surprised as its name is <code>armageddon</code>. ARM is no longer special for me as I gradually become familiar with the ISA. After all, it is simpler than the x86 and those frequently used instructions are easy to understand and remember.</p>
<p>BinaryNinja has no problem recognizing the <code>__libc_start_main</code> and I can get to <code>main</code> easily. The first thing I find is that <code>main</code> is a <strong>long</strong> function.</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>Well, a long function is not necessarily hard to analyze. It probably leverages certain obfuscation and/or its code is pretty repetitive. I started browsing the code from the beginning.</p>
<p><img src="../imgs/1.png" alt=""></p>
<p>It first prints a welcoming message and then asks the user to type the input. After that, it calls <code>scanf</code> with <code>&quot;%41s&quot;</code> which reads at most 41 chars from the terminal. Not bad, we now know it accepts a string as the input and we know the maximum length of it.</p>
<p>We also notice that the basic blocks are split into quite short ones. This is probably an obfuscation technique. Nevetheless BinaryNinja kind of automatically accounts for it so we are not bothered by it. If a disassembler does not correctly inline the blocks after the jump (<code>b</code>), it could be harder to analyze.</p>
<p>After reading the input, the code becomes repetitive: each time, a function is called with the user input as its only parameter. The pattern is repeated until near the bottom of the function, where a loop is found. The loop could be decrypting the flag based on the correct user input. And the checks on the input is obviously inside these called functions.</p>
<p>I followed the first check function and it looks like this:</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>Near the bottom of the function we see the comparison and if the comparison is not equal, an error message is printed. After analyzing the algorithm, I find the constraint is:</p>
<pre tabindex="0"><code>passwd[1] * passwd[0x27] * passwd[0x15] + passwd[0x11] + passwd[0x13] * passwd[0x1e] == 0xdb11e
</code></pre><p>I browsed several other check functions and they all look similar. Now it becomes obvious: There are a series of constraints and the correct input must satisfy all of them.</p>
<h2 id="round-one-failure-of-angr">Round One: Failure of angr</h2>
<p>This challenge is very suitable for tools like <code>angr</code> or <code>z3</code>. In fact angr also uses z3 as its constraint solving backend. However, angr can automatically extract constraints from the binary, which could save a lot of time for reversers. So I decided to first give angr a try.</p>
<p>The code is not hard to write &ndash; especially they all look similar for different binaries.</p>
<p>Code for <a href="../angr-solve.py">angr-solve.py</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">import</span> angr
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> claripy
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>proj <span style="color:#f92672">=</span> angr<span style="color:#f92672">.</span>Project(<span style="color:#e6db74">&#39;./armageddon&#39;</span>)
</span></span><span style="display:flex;"><span>print(hex(proj<span style="color:#f92672">.</span>entry))
</span></span><span style="display:flex;"><span>start_address <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x14a88</span>
</span></span><span style="display:flex;"><span>state <span style="color:#f92672">=</span> proj<span style="color:#f92672">.</span>factory<span style="color:#f92672">.</span>entry_state(addr <span style="color:#f92672">=</span> start_address)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>input_addr <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xaa000000</span>
</span></span><span style="display:flex;"><span>r11 <span style="color:#f92672">=</span> input_addr <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x34</span>
</span></span><span style="display:flex;"><span>state<span style="color:#f92672">.</span>regs<span style="color:#f92672">.</span>r11 <span style="color:#f92672">=</span> r11
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>n <span style="color:#f92672">=</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span>flag <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>solver<span style="color:#f92672">.</span>BVS(<span style="color:#e6db74">&#39;flag&#39;</span>, n <span style="color:#f92672">*</span> <span style="color:#ae81ff">8</span>)
</span></span><span style="display:flex;"><span>state<span style="color:#f92672">.</span>memory<span style="color:#f92672">.</span>store(input_addr, flag)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>simgr <span style="color:#f92672">=</span> proj<span style="color:#f92672">.</span>factory<span style="color:#f92672">.</span>simgr(state)
</span></span><span style="display:flex;"><span>good <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x1504c</span>
</span></span><span style="display:flex;"><span>simgr<span style="color:#f92672">.</span>explore(find <span style="color:#f92672">=</span> good,
</span></span><span style="display:flex;"><span>        avoid <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x10674</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x107c8</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x109ac</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x10b6c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x10cf0</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x10ea4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11010</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11190</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11308</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x114a4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x116a8</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x1185c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x119c8</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11b84</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11d38</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x11f10</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x120c4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x122e4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x124c8</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x1264c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x12800</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x12948</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x12b1c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x12d30</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x12e9c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13070</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13248</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x133e0</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x135f0</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x137d4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13970</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13b50</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13cbc</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x13e6c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x14014</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x141c8</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x1434c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x144c4</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x14648</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x1485c</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">0x149a0</span>
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>        ) 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> simgr<span style="color:#f92672">.</span>found:
</span></span><span style="display:flex;"><span>    solution_state <span style="color:#f92672">=</span> simgr<span style="color:#f92672">.</span>found[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    input1 <span style="color:#f92672">=</span> solution_state<span style="color:#f92672">.</span>solver<span style="color:#f92672">.</span>eval(flag, cast_to <span style="color:#f92672">=</span> bytes)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;flag: &#39;</span>, input1)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;Cound not find flag&#39;</span>)
</span></span></code></pre></div><p>We tell angr where is the input, and specify a good address to reach, as well as an (optional) list of addresses to avoid. Those addresses to be avoided are those printing error messages.</p>
<p>This, in theory, should work. However, after running for several minutes angr tells me there is no solution. This is a little bit surprising as I assume as long as there is a solution, angr either returns it or keeps running. There could be multiple reasons for it, e.g., a bug in angr, or the constraints are not properly lifted, etc. We could output the constraints that angr is solving and troubleshot what went wrong. But please allow me to save it as future work.</p>
<h2 id="round-two-conquering-it-with-z3">Round Two: Conquering it with z3</h2>
<p>The next option is to convert the constraints into Python syntax and solve it with z3. The transcribing is arduous work and prone to error. It is better done in an automated or semi-automated way.</p>
<p>I opened the challenge binary in Ghidra and found that the decompilation is generally good:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">FUN_000104fc</span>(<span style="color:#66d9ef">int</span> param_1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> ((uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>) <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>      (uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x27</span>) <span style="color:#f92672">*</span> (uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x15</span>) <span style="color:#f92672">+</span>
</span></span><span style="display:flex;"><span>      (uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x11</span>) <span style="color:#f92672">+</span>
</span></span><span style="display:flex;"><span>      (uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x13</span>) <span style="color:#f92672">*</span> (uint)<span style="color:#f92672">*</span>(byte <span style="color:#f92672">*</span>)(param_1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x1e</span>) <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0xdb11e</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">puts</span>(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">[!] Code did not validate! :(</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>);
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">/* WARNING: Subroutine does not return */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">exit</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> param_1;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then I copy-and-pasted all the constraints into a temp script and converted it into Python syntax. Note this work is still quite repetitive, so I decided to convert the code with a regular expression.</p>
<p>I did it in VS Code. I searched for</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>\(uint\)\<span style="color:#f92672">*</span>\(byte \<span style="color:#f92672">*</span>\)\(param_1 \<span style="color:#f92672">+</span> ((<span style="color:#ae81ff">0</span>x)<span style="color:#960050;background-color:#1e0010">?</span>[<span style="color:#ae81ff">0</span><span style="color:#f92672">-</span><span style="color:#ae81ff">9</span>a<span style="color:#f92672">-</span>f]<span style="color:#f92672">+</span>)\)
</span></span></code></pre></div><p>and replaced them with</p>
<pre tabindex="0"><code>passwd[$1]
</code></pre><p>Basically, this will convert <code>(uint)*(byte *)(param_1 + 1)</code> to <code>passwd[1]</code>. There are still manual works needed, like removing the <code>if</code>, etc. But those are not hard to do.</p>
<p>Eventually, the solving script looks like this <a href="../z3_solve.py">(z3_solve.py)</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">from</span> z3 <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>n <span style="color:#f92672">=</span> <span style="color:#ae81ff">41</span>
</span></span><span style="display:flex;"><span>passwd <span style="color:#f92672">=</span> [BitVec(<span style="color:#e6db74">&#39;s_</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> i, <span style="color:#ae81ff">32</span>)  <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>s <span style="color:#f92672">=</span> Solver()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n):
</span></span><span style="display:flex;"><span>    s<span style="color:#f92672">.</span>add(passwd[i] <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0x21</span>)
</span></span><span style="display:flex;"><span>    s<span style="color:#f92672">.</span>add(passwd[i] <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">127</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x27</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x13</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x1e</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xdb11e</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0x25</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x13</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xc</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0xc0c</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x1f</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x21</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xd</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x14</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xebd1d</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">7</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x24</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xf</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x1d</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x18e5</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x1b</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xf</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x2e3b</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0xf</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x25</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">8</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">5</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">6</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x19a5</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1d</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x14</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1a</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xc4</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">7</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x20</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1f</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x45ca</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0x1d</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x18</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x24</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x25</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xac3fb</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x10</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xc</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x28</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xf</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xd0</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x0</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xb</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xc</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">7</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x26</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x172e48</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0x1a</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xd</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">3</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">8</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">5</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x10b8</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">3</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x24</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x14</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x160</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x1a</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x12</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1b</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x19</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x8a2</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xe</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">5</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x21</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x1bd8</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">5</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x26</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x19</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2ca6988</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xc</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x24</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2430</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">2</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">7</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">9</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x12</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x27</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2de</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">5</span>] <span style="color:#f92672">*</span> (passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">6</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x14</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x17</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x11d5</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xb</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xd</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2aba</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xb</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xd</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2aba</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0x1b</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x12</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xf</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x20</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">9</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2668</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xe</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x1d</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1400</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">9</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">9</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">10</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xd</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x24</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x14</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x19ac</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0xc</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x22</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">4</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x14</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x17</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x16</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0xafa0c</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">4</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">5</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">10</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1b</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xb4</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0xf</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x1c</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x25</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x18</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x12</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x0</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0xd06e8</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">4</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x19</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x15</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x18</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x14</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1f8</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">0x19</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">10</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xf</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1c</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x21</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x3e</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">6</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x19</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">2</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x19</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x12</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x1c</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x1eb9</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">*</span> (passwd[<span style="color:#ae81ff">5</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x16</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xc</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x121b93</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">3</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xe</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x26</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xd</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x80</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">0x1e</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x15</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x11</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x17</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">5</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x21</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1afd</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">7</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xe</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x21</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xdf</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">3</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">2</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">10</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">10</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x626e2</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(((passwd[<span style="color:#ae81ff">0x25</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">7</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x13</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xc</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x12f</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">1</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">8</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x14</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x20</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xf</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x167a</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0x11</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">4</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x1d</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x12</span>] <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x11ca</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((passwd[<span style="color:#ae81ff">0xd</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x16</span>] <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">10</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x32e9</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">0xd</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0xb</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x1d</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x13</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xec9</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add((((passwd[<span style="color:#ae81ff">0x19</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x26</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0xf</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0xb</span>]) <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x20</span>]) <span style="color:#f92672">-</span> passwd[<span style="color:#ae81ff">0x15</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">0x22</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x2a</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>add(passwd[<span style="color:#ae81ff">6</span>] <span style="color:#f92672">*</span> passwd[<span style="color:#ae81ff">9</span>] <span style="color:#f92672">+</span> passwd[<span style="color:#ae81ff">0x23</span>] <span style="color:#f92672">==</span> <span style="color:#ae81ff">0xedd</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> s<span style="color:#f92672">.</span>check() <span style="color:#f92672">==</span> sat:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;solved!&#39;</span>)
</span></span><span style="display:flex;"><span>    m <span style="color:#f92672">=</span> s<span style="color:#f92672">.</span>model()
</span></span><span style="display:flex;"><span>    flag <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n):
</span></span><span style="display:flex;"><span>        c <span style="color:#f92672">=</span> m[passwd[i]]<span style="color:#f92672">.</span>as_long()
</span></span><span style="display:flex;"><span>        flag <span style="color:#f92672">+=</span> chr(c)
</span></span><span style="display:flex;"><span>    print(flag)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;failed&#39;</span>)
</span></span></code></pre></div><p>One thing to mention here is although the individual chars of passwd are only 8 bits wide, we declare them to be 32-bit wide. Otherwise, it could cause a problem to the <code>==</code> at the end of the line. Obviously, we have to add the constraint <code>passwd[i] &gt;= 0x21</code> and <code>passwd[i] &lt;= 127</code>, to actually enforce they are printable ASCII chars.</p>
<p>Running this immediately returns the flag:</p>
<pre tabindex="0"><code>UMDCTF-{ARM_1s_s0_SATisfying_7y8fdlsjebn}
</code></pre><h2 id="epilog">Epilog</h2>
<p>Despite z3 returns a result and it looks quite convincing, there are still some code below the last constraint. Typically, in CTF, this means the correct input that passes the constraints is NOT the actual flag; rather the input is used to decrypt the flag to be submitted. However, the above code is already in a good flag format. This confuses me so I decide to run the binary to see what happens. There is an excellent tool for this situation: the process level emulator &ndash; <a href="https://www.qiling.io/"><code>Qiling</code></a>.</p>
<p>Qiling is an emulator based on Unicorn. It is simpler than Qemu since it only emulates the process that we are interested in. So there is no need to set up a bulky OS to run it. The code is extremely simple <a href="../qiling_emulate.py">(qiling_emulate.py)</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">from</span> qiling <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    ql <span style="color:#f92672">=</span> Qiling([<span style="color:#e6db74">&#34;./armageddon&#34;</span>], <span style="color:#e6db74">&#34;QILING_INSTALL_PATH/examples/rootfs/arm_linux&#34;</span>)
</span></span><span style="display:flex;"><span>    ql<span style="color:#f92672">.</span>run()
</span></span></code></pre></div><p>Since major system calls are implemented by Qiling, the program executes properly. Below is an excerpt of the output:</p>
<pre tabindex="0"><code>write(1,27008,16) = 0
[+] write() CONTENT: bytearray(b&#39;[+] Enter Code: &#39;)
[+] Enter Code: UMDCTF-{ARM_1s_s0_SATisfying_7y8fdlsjebn}
read(0, 0x29010, 0x2000) = 42
write(1,27008,1) = 0
[+] write() CONTENT: bytearray(b&#39;\n&#39;)

write(1,27008,33) = 0
[+] write() CONTENT: bytearray(b&#39;[+] Code validated successfully!\n&#39;)
[+] Code validated successfully!
write(1,27008,1) = 0
[+] write() CONTENT: bytearray(b&#39;\n&#39;)

[!] 0xf7ca9be8: syscall number = 0x8c(140) not implemented
exit_group(0)
</code></pre><p>So after we supply the correct code, it simply prints <code>Code validated successfully!\n</code>.</p>
<p>LoL! I forget that it does not tell us the code is correct yet. Well, not bad, since playing with Qiling is quite painless.</p>
]]></content>
        </item>
        
        <item>
            <title>Debugging and Solving an Android Challenge</title>
            <link>https://xusheng.dev/posts/reversing/quarkslab_android_crackme/main/</link>
            <pubDate>Sat, 30 May 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/quarkslab_android_crackme/main/</guid>
            <description>&lt;p&gt;Our first challenge is an Android challenge that features native library reverse engineering and debugging. Since the algorithm itself is not very complex, in this writeup I will cover the major steps to set up an Android debugging environment. I will also share some of my thoughts as we progress.&lt;/p&gt;
&lt;h2 id=&#34;first-impression&#34;&gt;First Impression&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://blog.quarkslab.com/android-challenge.html&#34;&gt;challenge&lt;/a&gt; is created by Quarkslab. The &lt;strong&gt;crackme-telegram.apk&lt;/strong&gt; is ~25MB in size which is larger than a typical crackme. One of the challenges in real-world reverse engineering is the huge size of the binary. There are too many possible places to hide the crucial code so even finding it is non-trivial in the first place.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Our first challenge is an Android challenge that features native library reverse engineering and debugging. Since the algorithm itself is not very complex, in this writeup I will cover the major steps to set up an Android debugging environment. I will also share some of my thoughts as we progress.</p>
<h2 id="first-impression">First Impression</h2>
<p>The <a href="https://blog.quarkslab.com/android-challenge.html">challenge</a> is created by Quarkslab. The <strong>crackme-telegram.apk</strong> is ~25MB in size which is larger than a typical crackme. One of the challenges in real-world reverse engineering is the huge size of the binary. There are too many possible places to hide the crucial code so even finding it is non-trivial in the first place.</p>
<p>Unzipping the apk gives us a folder that has <strong>classes.dex</strong> and sub-folder <strong>lib</strong> in it. The code for an Android app can be either in the .dex file or the native libraries. the .dex file is typically produced from Java whereas the native libraries are mostly complied from C/C++. And they require different reverse engineering skills. Nevertheless, here I want to share a heuristic: <strong>if an android crackme has native libraries, almost certainly the important code sits in these  libs.</strong> Well, this is not 100% reliable and the situation could change since I publish this, but it works very well for now.</p>
<h2 id="creating-an-avd-and-running-the-app">Creating an AVD and Running the App</h2>
<p>I do not have an Android phone or tablet so I need to run it in an emulator. There are many available Android emulators. In this writeup, I will use the official <a href="https://developer.android.com/studio/">Android Studio</a>. This crackme comes with both Arm and x86 versions of the native library, so we can run it in an x86 AVD (Android Virtual Device). Otherwise, I would have to use the Arm CPU AVD, which also works but runs slow on my Intel CPU.</p>
<p>Creating an AVD should be quite straightforward following this <a href="https://developer.android.com/studio/run/managing-avds">document</a>. I got one with API 28 and x86 CPU. Once we launch the app, it prompts us to register with a phone number:</p>
<p><img src="../imgs/wrong.png" alt=""></p>
<p>If we randomly input a phone number, we will be greeted by an error message. So this is already the main crackme. We need to find a special phone number (along with the country code) that is accepted by the app.</p>
<h2 id="finding-the-code">Finding the Code</h2>
<p>As always, we need to first locate the code which does the verification. One clue is the error message itself: <strong>Wrong number! Try again</strong>. This string can be found in the libtmessages.29.so. However, there are no Xrefs to it. Now there are several possibilities: 1. the string will be used but the code is obfuscated so my disassembler does not find a reference to it; 2. the string is not used and the code is somewhere else. I continued to search in class.dex, libtmessages.28.so, and also used <a href="https://ibotpeaches.github.io/Apktool/">Apktool</a> to unpack the resources.arsc. Nothing else can be found.</p>
<p>I do not want to create the illusion that I <strong>systematically</strong> find the verification function. Actually, I took some detous here. I reversed the class.dex and libtmessages.28.so for a while without success before I tried the libtmessages.29.so. This is indeed quite common in reverse engineering. Going back to the libtmessages.29.so, I had a look at the JNI_OnLoad() which has some related stuff but does not have the verification function. I checked the functions before and after the JNI_OnLoad() to see if there are any interesting functions. The logic is compilers tend to arrange the functions close to each other in the source code also adjacent to the generated binary. So there is a chance the important function is near the JNI_OnLoad().</p>
<p>I spotted the <strong>data_6871</strong> that sits right after the function. It starts with 0x5b81, which looks like code for me.</p>
<p><img src="../imgs/data.png" alt=""></p>
<p>Then I defined a function here and it is real code. It seems innocent at first look, but I quickly noticed that it is preparing a constant string on the stack:</p>
<p><img src="../imgs/analyze_me.png" alt=""></p>
<pre tabindex="0"><code>Are you trying to analyze me?
</code></pre><p>It looks like a message related to anti-debugging &ndash; we might see this while debugging the app. Remember we are not yet sure whether this is related to the verification, so it is worthy to debug it now and see if this function is called.</p>
<h2 id="setting-up-debugging">Setting up Debugging</h2>
<p>Simply put, debugging an android app is a remote debugging scene. We run the <strong>gdbserver</strong> on the phone (either an emulator or a real one) and attach it to the target process. And then we launch <strong>gdb</strong> on our computer and connect to the remote target. After that, there is no difference between debugging locally and remotely.</p>
<p>An android app may run inside a Dalvik VM. However, the VM is just a regular process and can be debugged like any other processes. Furthermore, the native libraries are directed loaded into the process memory space so we can also debug that.</p>
<p>We first need to download the <a href="https://developer.android.com/ndk/">Android NDK</a> since we need the prebuilt gdbserver in it. The NDK is large and we do not need other things in it (for debugging purpose). However, it is better than randomly searching on the Internet for it &ndash; it may not work properly inside the AVD.</p>
<p>The gdbserver can be found in the <code>android-ndk-r21b/prebuilt/android-x86/gdbserver</code>. Note I have a x86 AVD so I need the x86 version of it. First I push it to the device:</p>
<pre tabindex="0"><code>$ adb push ./android-ndk-r21b/prebuilt/android-x86/gdbserver /system/bin/
</code></pre><p>After that, I launch the app on the device. Then On my computer, I spawn an adb shell by running:</p>
<pre tabindex="0"><code>$ adb shell
</code></pre><p>The app is called <strong>telegram</strong> so I run the following command to find the PID of the target process by running:</p>
<pre tabindex="0"><code># ps -A | grep telegram                                                                   
u0_a80        4165  7934 1562976 153292 ep_poll      e9897b59 S org.telegram.messenger
</code></pre><p>Note: I use <code>$</code> for any command to be executed in the host shell and <code>#</code> for anything inside the adb shell.</p>
<p>The PID of our target is 4165. The command to attach gdbserver to the process is:</p>
<pre tabindex="0"><code># gdbserver --attach host:port PID
</code></pre><p>In my case, I use:</p>
<pre tabindex="0"><code># gdbserver --attach localhost:12345 4165
</code></pre><p>Now the gdbserver will attach to the process with PID 4165 and listen on port 12345 for remote connection. Meanwhile, the app will hang.</p>
<p>We need to set up a port forwarding before connecting to it. This is because the gdbserver is listening on the port 12345 of the device, not our host computer.</p>
<pre tabindex="0"><code>$ adb forward tcp:12345 tcp:12345
</code></pre><p>This will forward the port 12345 on the host to the port 12345 on the device.</p>
<p>Now launch gdb on the computer and attach to it:</p>
<pre tabindex="0"><code>pwndbg&gt; target remote localhost:12345
</code></pre><p>If everything works fine gdb should be printing a lot of information about the remote target. This might take a while and eventually, it should stop and ask for your input. The prompt starts with <code>pwndbg&gt;</code> because I installed the pwngdb enhancement, which makes gdb more usable.</p>
<p>The next thing to figure out is the base address of the loaded libtmessages.29.so.</p>
<pre tabindex="0"><code>pwndbg&gt; info sharedlibrary
From        To          Syms Read   Shared Object Library
// many lines omitted
0xc9b69000  0xc9b6eaf7  Yes (*)     target:/data/app/org.telegram.messenger-o_d807FF7eGAXMhf5s3qqQ==/oat/x86/base.odex
0xc9406570  0xc9406830  Yes (*)     target:/data/app/org.telegram.messenger-o_d807FF7eGAXMhf5s3qqQ==/lib/x86/libtmessages.29.so
0xc8896400  0xc8f70f71  Yes (*)     target:/data/app/org.telegram.messenger-o_d807FF7eGAXMhf5s3qqQ==/lib/x86/libtmessages.28.so
0xc7d329b0  0xc7d36ea5  Yes (*)     target:/vendor/lib/hw/gralloc.ranchu.so
(*): Shared library is missing debugging information.
</code></pre><p>We can see the address of libtmessages.29.so is 0xc9406570. Interestingly, the address reported by <strong>info sharedblibrary</strong> is the address of the <strong>.text</strong> section, which is not very convenient for rebasing. But it is fine since we can calculate it manually.</p>
<p>In BinaryNinja we can see the start of the .text is at 0x5570, while the start of the function is at 0x6871. We now the offset of the remains the same, so the actual address to set the breakpoint is:</p>
<pre tabindex="0"><code>&gt;&gt;&gt; hex(0xc9406570 + (0x6871 - 0x5570))
&#39;0xc9407871&#39;
</code></pre><p>. Then we rebase it in BinaryNinja and we now the address of that.</p>
<pre tabindex="0"><code>pwndbg&gt; b *0xc9407871
Breakpoint 1 at 0xc9407871
pwndbg&gt; c
Continuing.
</code></pre><p>Now, give a random phone number and hit enter on the phone. And the breakpoint hits! We find the verification function!</p>
<h2 id="solving-the-country-code">Solving the Country Code</h2>
<p>The function is medium-sized and we need to have a big picture of it before plunging into lines of assemblies. Near the bottom of the function, we see the string &ldquo;Wrong number&rdquo; being created in a buffer:</p>
<p><img src="../imgs/wrong_number.png" alt=""></p>
<p>So we need to avoid this basic block. Scrolling up a little bit and we find two checks must be satisfied:</p>
<p><img src="../imgs/key.png" alt=""></p>
<p>These are testing if the lowest bit is set. However, if we go further upward we can find that both <strong>check_1</strong> and <strong>check_2</strong> are booleans and they represent whether a check is satisfied. For check_1, we have the following block:</p>
<p><img src="../imgs/country_code.png" alt=""></p>
<p>We see a string input is passed into function <strong>std::__ndk1::stoul</strong> and converted to an integer using base 10. Then <strong>7 * int + 9</strong> is calculated and the result is fed into function <strong>__umoddi3</strong>. I have seen <strong>__umoddi3</strong> before so I quickly figure out the divisor is 0x25. In fact, <strong>__umoddi3</strong> calculates 64-bit unsigned modulus. This is a 32-bit binary so it has to use two registers to hold 64bit values. The <strong>edx</strong> pushed onto the stack is the higher 32 bits of the dividend; the <strong>eax</strong> is the lower 32bits. If I have not seen it, I can also figure it out by debugging the code and observe the input and output for it. The modulus is returned as <strong>edx:eax</strong> too.</p>
<p>We want variable <strong>check_1</strong> to be 1, so we must set it at 0x6a60. To ensure the <strong>ZF</strong> is set when it gets to 0x6a60, the eax must be 0x17 and the edx must be 0. This means the modulus must be equal to 0x17.</p>
<p>A quick debugging veries the input string is the country code we input. So the constaint here is:</p>
<pre tabindex="0"><code>(7 * country_code + 9) % 0x25 == 0x17
</code></pre><p>A simple script to print the accepted coutry_code is as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> coutry_code <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">999</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>  (<span style="color:#ae81ff">7</span> <span style="color:#f92672">*</span> coutry_code <span style="color:#f92672">+</span> <span style="color:#ae81ff">9</span>) <span style="color:#f92672">%</span> <span style="color:#ae81ff">0x25</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0x17</span>:
</span></span><span style="display:flex;"><span>        print(coutry_code)
</span></span></code></pre></div><p>We know there are many values that satisfy the above equation, but only one among them is a valid country code. It is +39, which is the code for Italy.</p>
<h2 id="solving-the-phone-number">Solving the Phone Number</h2>
<p>Below the country code check, we can find the check for the phone number. At 0x6c6f it calls into another medium-sized function, which is probably the check function. It looks like this:</p>
<p><img src="../imgs/phone_func.png" alt=""></p>
<p>It is not immediately obvious what this function does. Though from the first few basic blocks we can observe the <strong>std::string</strong> being used and the valid length is probably 0x16. Remember the correct phone number is not necessarily a phone number at <strong>al</strong> and it does not have to have a length that looks like a phone number (e.g., 10 digits for the U.S.).</p>
<p>To approach a function like this, there are two methods. The first way is to check how is the return value calculated and back-slice it and do taint-analysis in the brain. From the previous analysis we now this function should return 1 in <strong>eax</strong>. We can go back from the last instruction that touches <strong>eax</strong> and see what is the way to set it to 1.</p>
<p>Besides, we see there is a loop in the lower-right side of the mini graph. Loops can give us a lot of information about what is happening. My way to reverse a loop is to identify the iteration variable (similar to <strong>i</strong> in C code), and see what is the initial value, final value, and stride. Or more generally speaking, what is the exit condition and what is the update rule. This lets us know how many times this loop is going to be executed.</p>
<p>Then we should get into the loop body and analyze it. This lets us know what is done in one iteration. These two combined tell us what the loop is doing as a whole.</p>
<p>It is hard to include every step I took to reverse this loop, but let me describe the major steps.
First thing first, there are many ways to exit this loop, but the exit at 0x7325 is the only place where the return value of this function can be 1. Above it, we see <strong>cmp ecx, esi</strong>, which is probably comparing the iterator with the final value. But which one is the iterator?</p>
<p><img src="../imgs/loop.png" alt=""></p>
<p>In many cases, we can figure out by looking at the code, but for this one, I am not so sure. Never mind, we can debug it. I set a breakpoint at 0x7323 and send an input with length 0x16 (if the length is wrong, the execution never enters the loop). In the first iteration <strong>ecx</strong> is 0 and <strong>esi</strong> is 0x16; in the second iteration <strong>ecx</strong> is 2 and <strong>esi</strong> is 0x16.</p>
<p>So, it looks like ecx is <strong>i</strong> and esi is the final value 0x16. Going up a little bit and at 0x7306 we see the i is incremented by 2 each time. So this loop probably processes two bytes of the input one time.</p>
<p>Now it is time to analyze one iteration. We want the code at <strong>0x732d</strong> to set al, then <strong>edi</strong> must not be 0. At <strong>0x7300</strong> there is a <strong>and</strong>, so ecx must not be 0. <strong>ecx</strong> is updated at 0x72d8, where we have a <strong>cmp</strong> before it. So to have 1 as the return value for the function, this <strong>cmp</strong> must be equal. Then we move further upward to see what is <strong>al</strong> and <strong>byte [esp+0x36]</strong>.</p>
<p><img src="../imgs/loop_body.png" alt=""></p>
<p>It turns out the al is the result of another <strong>std::__ndk1::stoul</strong>. The base is still 10 and the input is the two chars (for every iteration) from the input string. The other operand is a little bit complex. During debugging, I find that at 0x7283, the <strong>eax</strong> points to a string</p>
<pre tabindex="0"><code>org.telegram.messenger
</code></pre><p>It is the name of the app. I did not bother backtrace how it gets to here but this is an interesting finding: it is probably used in the algorithm. At 0x7295, it takes the ith char of the above string. At 0x729d, the char (ASCII value) is xor-ed with a variable we do not understand yet.</p>
<p>Then we see a <strong>division by multiplication</strong>. This is an optimization technique used by compilers to speed up divisions. Division instructions (e.g., <strong>idiv</strong>) are super slow to execute so the compilers calculate it differently. Even though we ended up with more instructions, the code executes faster. For more details on this topic, please refer to: <a href="https://stackoverflow.com/questions/30790184/perform-integer-division-using-multiplication">ref 1</a> or <a href="https://gmplib.org/~tege/divcnst-pldi94.pdf">ref 2</a>.</p>
<p>It is not hard to recognize the divisor from the assembly after we know how it works. Furthermore, if the division is used to calculate a modulus, it is easier to recognize. For example, if the code calculates <strong>eax % n</strong>, it will do the following two things:</p>
<pre tabindex="0"><code>quotient = eax / n
modulus = eax - quotient * n
</code></pre><p>The &ldquo;divide by n&rdquo; part might not be immediately obvious, but the &ldquo;multiply by n&rdquo; part is super easy.</p>
<pre tabindex="0"><code>000072a3  movsx   ecx, byte [esp+0x36 {var_3a_1}]
000072a8  mov     eax, ecx
000072aa  mov     edx, 0x51eb851f
000072af  imul    edx
000072b1  mov     eax, edx
000072b3  shr     eax, 0x1f
000072b6  shr     edx, 0x5
000072b9  add     edx, eax
000072bb  imul    eax, edx, 0x64
000072be  sub     ecx, eax
000072c0  mov     byte [esp+0x36 {xored_val % 0x64}], cl
</code></pre><p>At 0x72bb we see a <strong>imul    eax, edx, 0x64</strong> followed by a <strong>sub     ecx, eax</strong>. Obviously, this is calculating the modules and divisor is 0x64. So this whole thing is calculating <strong>ecx % 0x64</strong>.</p>
<p>For many other divisor values, the multiplication will be further optimized. Like if the divisor is 9, it will become something like <strong>mov edx, eax; shl edx, 3; add edx, eax</strong>. But the &ldquo;shift left and add&rdquo; trick is still more obvious than the division.</p>
<p>Now the only missing piece is the mysterious variable referenced at address 0x7299. Notice it is initialized to 0 before entering the loop and update at 0x72c9 according to the result of the transformation in each iteration. In fact, this is similar to the <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation">Cipher block chaining (CBC)</a> in block ciphers, where an initilization vector is provided and updated on every block.</p>
<p>We can now reconstruct the algorithm as the following pseudo-code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">check</span>(input_string):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> len(input_string) <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0x16</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    s <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;org.telegram.messenger&#39;</span>
</span></span><span style="display:flex;"><span>    IV <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    check_ok <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i, two_char <span style="color:#f92672">in</span> yield_two_char_every_time(input_string):
</span></span><span style="display:flex;"><span>        val <span style="color:#f92672">=</span> int(two_char, <span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>        IV <span style="color:#f92672">=</span> (IV <span style="color:#f92672">^</span> i <span style="color:#f92672">^</span> asc(s[i])) <span style="color:#f92672">%</span> <span style="color:#ae81ff">0x64</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> val <span style="color:#f92672">!=</span> IV:
</span></span><span style="display:flex;"><span>            check_ok <span style="color:#f92672">=</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> check_ok
</span></span></code></pre></div><p>Which can be solved by the following script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>s <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;org.telegram.messenger&#39;</span>
</span></span><span style="display:flex;"><span>val <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>flag <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> i <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0x16</span>:
</span></span><span style="display:flex;"><span>    c <span style="color:#f92672">=</span> s[i]
</span></span><span style="display:flex;"><span>    asc <span style="color:#f92672">=</span> val <span style="color:#f92672">^</span> i <span style="color:#f92672">^</span> ord(c)
</span></span><span style="display:flex;"><span>    asc <span style="color:#f92672">%=</span> <span style="color:#ae81ff">0x64</span>
</span></span><span style="display:flex;"><span>    val <span style="color:#f92672">=</span> asc
</span></span><span style="display:flex;"><span>    flag <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> asc
</span></span><span style="display:flex;"><span>    i <span style="color:#f92672">+=</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(flag)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the flag is:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1110222419205493626651</span>
</span></span></code></pre></div>]]></content>
        </item>
        
        <item>
            <title>Solving a Reversing Challenge with Mitmproxy and OCR</title>
            <link>https://xusheng.dev/posts/reversing/client_houseplant_ctf_2020/solve/</link>
            <pubDate>Mon, 27 Apr 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/client_houseplant_ctf_2020/solve/</guid>
            <description>&lt;p&gt;Over the weekend I had some fun with the &lt;a href=&#34;https://houseplant.riceteacatpanda.wtf/home&#34;&gt;Houseplant CTF&lt;/a&gt;. Among the reversing challenges, the &lt;a href=&#34;https://houseplant.riceteacatpanda.wtf/challenge?id=8&#34;&gt;&lt;strong&gt;RTCP Trivia&lt;/strong&gt;&lt;/a&gt; is particularly interesting and I would like to share my unconventional way of solving it.&lt;/p&gt;
&lt;h2 id=&#34;first-impression&#34;&gt;First Impression&lt;/h2&gt;
&lt;p&gt;We get a &lt;a href=&#34;../client.zip&#34;&gt;&lt;strong&gt;client.apk&lt;/strong&gt;&lt;/a&gt; after downloading the challenge. I have no Android phones so I ran it in an emulator. It has no ARM native library so it runs well in x86 emulators.&lt;/p&gt;
&lt;p&gt;After asking for a user name, the app presents a multiple-choice problem with four options (shown below). The problem itself is not difficult. However, there is a ten-second countdown and we must answer it before the time elapses. The challenge description says that we need to correctly answer 1000 such problems. So manual solving is probably not a wise idea.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Over the weekend I had some fun with the <a href="https://houseplant.riceteacatpanda.wtf/home">Houseplant CTF</a>. Among the reversing challenges, the <a href="https://houseplant.riceteacatpanda.wtf/challenge?id=8"><strong>RTCP Trivia</strong></a> is particularly interesting and I would like to share my unconventional way of solving it.</p>
<h2 id="first-impression">First Impression</h2>
<p>We get a <a href="../client.zip"><strong>client.apk</strong></a> after downloading the challenge. I have no Android phones so I ran it in an emulator. It has no ARM native library so it runs well in x86 emulators.</p>
<p>After asking for a user name, the app presents a multiple-choice problem with four options (shown below). The problem itself is not difficult. However, there is a ten-second countdown and we must answer it before the time elapses. The challenge description says that we need to correctly answer 1000 such problems. So manual solving is probably not a wise idea.</p>
<p><img src="../imgs/1.png" alt=""></p>
<h2 id="inspecting-the-traffic">Inspecting the Traffic</h2>
<p>After I unzipped the apk and inspected the files inside of it, I found the challenges are not stored inside the apk. I confirmed this by cutting the network to the emulator &ndash; it no longer shows new challenges or tells you the answer is wrong.</p>
<p>I inspected the resources of this app and found the real flag is not there (a fake flag can be found in the strings). So it probably comes from the server after we solve 1000 problems.</p>
<p>I then launched Wireshark to have a look at the traffic. The app uses websocket to communicate with the server. The problem is sent from the server and the choice is submitted to the server. So the logic is not local. But I quickly notice something strange:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-JSON" data-lang="JSON"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;method&#34;</span>: <span style="color:#e6db74">&#34;question&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;30a3956f-cd60-4c51-bc01-dbbf1b09f9b0&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;questionText&#34;</span>: <span style="color:#e6db74">&#34;S62ZtWoNqto0jxuZalalAmv4s/n2GmaTai5Z7/bVsk6W48CbtUvYcOyVRi7qcPeP&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;options&#34;</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;bNMO3oWCI/s5OHBEiXfgkg==&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;qpDFxRVJXyczm52QbPTa8A==&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;8UQQMs42vvLpLIq0wNEIaw==&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;cLYF4H6LVlIi3YPF3R4MUg==&#34;</span>
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;correctAnswer&#34;</span>: <span style="color:#e6db74">&#34;mboZgfosD3S1ZUf330zmxaeq+bR2vzKkCV2AKOB8vlA=&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;requestIdentifier&#34;</span>: <span style="color:#e6db74">&#34;f814ce11519a16be435ac73bc0e89238&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Despite most data are encrypted, we see that the <strong>correctAnswer</strong> is also sent to the client. This means if we can decrypt it, we get the correct answer. And we know the app can decrypt the questionText and options, since it needs to show them to us. It is highly likely that the answer is encrypted in the same way and we can also decrypt it.</p>
<h2 id="reversing-the-algorithm-no">Reversing the Algorithm? No!</h2>
<p>A routine way to solve this is: 1). reverse the app to find out the encryption algorithm; 2). rewrite a client to communicate with the server. I did not take this approach since: 1). although it is easy to find out the encryption algorithm is AES and the iv is indeed requestIdentifier, it is not immediately clear how is the key generated. 2). I mistakenbly think the traffic sent from the client to the server is encrypted using a custom crypto (which later turns out to be just compression). These two obstacles are not prohibiting me from solving it, but I think it will take longer than I expected, so I decide to try a novel method.</p>
<p>After reading how the app displays the question text, I found that if I swap the keyword &ldquo;questionText&rdquo; with &ldquo;requestIdentifier&rdquo; in the json, the correct answer will be displayed on the screen!</p>
<p>Since the traffic is plaintext websocket, it is quite easy to implement it. I first tried Burp but it does not support match-and-replace in websocket. Then I used <a href="https://mitmproxy.org/">mitmproxy</a>. Mitmproxy allows us to script in Python, so we can easily modify the traffic.</p>
<p>I copy-and-pasted one example from the official repo and made some changes. The following code will change <code>'correctAnswer'</code> to <code>'questionText'</code> and change <code>'questionText'</code> to <code>'replaced'</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">from</span> mitmproxy <span style="color:#f92672">import</span> ctx
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">websocket_message</span>(flow):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    message <span style="color:#f92672">=</span> flow<span style="color:#f92672">.</span>messages[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> message<span style="color:#f92672">.</span>from_client:
</span></span><span style="display:flex;"><span>        ctx<span style="color:#f92672">.</span>log<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Client sent a message: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span><span style="color:#f92672">.</span>format(message<span style="color:#f92672">.</span>content))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        ctx<span style="color:#f92672">.</span>log<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Server sent a message: </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span><span style="color:#f92672">.</span>format(message<span style="color:#f92672">.</span>content))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#39;correctAnswer&#39;</span> <span style="color:#f92672">in</span> message<span style="color:#f92672">.</span>content:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        message<span style="color:#f92672">.</span>content <span style="color:#f92672">=</span> message<span style="color:#f92672">.</span>content<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;questionText&#39;</span>, <span style="color:#e6db74">&#39;replaced&#39;</span>)
</span></span><span style="display:flex;"><span>        message<span style="color:#f92672">.</span>content <span style="color:#f92672">=</span> message<span style="color:#f92672">.</span>content<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;correctAnswer&#39;</span>, <span style="color:#e6db74">&#39;questionText&#39;</span>)
</span></span></code></pre></div><p>Mitmproxy scripts are not meant to run on its own. Instread, we should run tools from mitmrpoxy and specify it with the <code>-s</code> option:</p>
<pre tabindex="0"><code>mitmdump -s ./mitm-solve.py
</code></pre><p>And it works! Now instead of the question text, the app shows the index of the correct answer to us.</p>
<p><img src="../imgs/2.png" alt=""></p>
<p>I tried to solve it by hand. But even if I have the correct answer, I still cannot stop clicking the wrong button. I do not want to solve it as an action game, so I start to seek viable ways to automate the solving.</p>
<p>The good thing is, mitmproxy allows us to inject packets. And thanks to the nature of websocket, this will not disrupt the communicaition between the client and the server. So the last problem is how to get the correct answer. Reversing the crypto algorithm is always an option, but I decide not to do it for this time.</p>
<h2 id="solving-a-reversing-challenge-with-ocr">Solving a Reversing Challenge with OCR</h2>
<p>It quickly pops up my mind that I can use OCR to recognize the correct answer. Does it work? I have not really done it before. Nevertheless the workflow is really simple: 1). capture a screenshot and crop it to the desired region. 2). use some OCR tool to recognize it.</p>
<p>I use <strong>pyautogui</strong> to capture a screenshot of my laptop screen. I already measured the bounding box of the answer digit with gimp. Then I just crop it accordingly. It feels like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>image <span style="color:#f92672">=</span> pyautogui<span style="color:#f92672">.</span>screenshot()
</span></span><span style="display:flex;"><span>image <span style="color:#f92672">=</span> image<span style="color:#f92672">.</span>crop((<span style="color:#ae81ff">1540</span>, <span style="color:#ae81ff">430</span>, <span style="color:#ae81ff">1560</span>, <span style="color:#ae81ff">465</span>))
</span></span></code></pre></div><p>After that I used a well-known open-source OCR engine <a href="https://github.com/tesseract-ocr/tesseract">tesseract</a> to recognize the digit on it. I have not used it before but it is quite reliable (at least for our super easy case).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span>txt <span style="color:#f92672">=</span> pytesseract<span style="color:#f92672">.</span>image_to_string(image, 
</span></span><span style="display:flex;"><span>    config <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;--psm 10 --oem 3 -c tessedit_char_whitelist=0123&#39;</span>)
</span></span></code></pre></div><p>The config option is found on the Stackoverflow and I do not really understand it. But it works!</p>
<p><img src="../imgs/4.png" alt="digits"></p>
<p>Now that it comes to the last step: injecting the solution. Note we need to first do the keyword swap, let the traffic reach the client app, wait for the answer to be displayed on the screen, and then read it and inject it. In my script, I waited 0.5 seconds to start the recognition.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">solve_and_inject</span>(flow):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">global</span> i 
</span></span><span style="display:flex;"><span>    time<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">0.5</span>)
</span></span><span style="display:flex;"><span>    ans <span style="color:#f92672">=</span> recognize_char()
</span></span><span style="display:flex;"><span>    sol <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#39;method&#39;</span> : <span style="color:#e6db74">&#39;answer&#39;</span>, <span style="color:#e6db74">&#39;answer&#39;</span> : ans}
</span></span><span style="display:flex;"><span>    print(sol)
</span></span><span style="display:flex;"><span>    sol_str <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>dumps(sol)
</span></span><span style="display:flex;"><span>    flow<span style="color:#f92672">.</span>inject_message(flow<span style="color:#f92672">.</span>server_conn, sol_str)
</span></span><span style="display:flex;"><span>    i <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#39;solved: </span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#39;</span> <span style="color:#f92672">%</span> i)
</span></span></code></pre></div><p>Alright, it now works! Wait for some 20 minutes and we get the flag: rtcp{qu1z_4pps_4re_c00l_aeecfa13}.</p>
<p><img src="../imgs/3.png" alt=""></p>
<p>I actually recorded a <a href="https://youtu.be/Acp8PDbsvQk">video</a> to demonstrate the solving.</p>
<p><a href="https://youtu.be/Acp8PDbsvQk" title="Video"><img src="../imgs/video.png" alt="Video"></a></p>
]]></content>
        </item>
        
        <item>
            <title>Reverse Engineering and Repairing a Fan</title>
            <link>https://xusheng.dev/posts/reversing/reverse_engineering_and_fixing_a_fan/main/</link>
            <pubDate>Sun, 26 Apr 2020 00:00:00 +0000</pubDate>
            <guid>https://xusheng.dev/posts/reversing/reverse_engineering_and_fixing_a_fan/main/</guid>
            <description>&lt;p&gt;Last summer, I broke a fan and managed to repair it. Although the repairing process is not so exciting, I recently find it can serve as a good example to explain a reverser&amp;rsquo;s mindset. Like how I approached the problem and solved it. I hope to share some of my understanding about reverse engineering in this writeup.&lt;/p&gt;
&lt;h2 id=&#34;a-broken-fan&#34;&gt;A Broken Fan&lt;/h2&gt;
&lt;p&gt;I have a fan &amp;ndash; an eight-year-old fan &amp;ndash; that is NOT smart or IoT. It is just a simple fan. One day it fell from the table to the ground and stopped working. &lt;em&gt;RIP&lt;/em&gt;. It accompanied me for several summers and I love it. I decided to take it apart and see what is indeed broken, before saying farewell to my friend.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Last summer, I broke a fan and managed to repair it. Although the repairing process is not so exciting, I recently find it can serve as a good example to explain a reverser&rsquo;s mindset. Like how I approached the problem and solved it. I hope to share some of my understanding about reverse engineering in this writeup.</p>
<h2 id="a-broken-fan">A Broken Fan</h2>
<p>I have a fan &ndash; an eight-year-old fan &ndash; that is NOT smart or IoT. It is just a simple fan. One day it fell from the table to the ground and stopped working. <em>RIP</em>. It accompanied me for several summers and I love it. I decided to take it apart and see what is indeed broken, before saying farewell to my friend.</p>
<p>I know little about electronics, but a fan should not be too complex. It looks like this after I opened it:</p>
<p><img src="../imgs/img-1.jpg" alt=""></p>
<p>We can see the fan blades, the power cord, the timer, and the ON/OFF switch in it. It looks all in good shape despite the impact. How should I start reversing it?</p>
<p>I quickly notice there is a small metal cylinder that is moving freely in the fan closure. Normally we do not have such small moving parts in a fan (it will clash with the blade easily). It is probably broken apart from the fan due to the impact. It is a reasonable guess. But how could I prove it or refute it?</p>
<p><img src="../imgs/img-2.jpg" alt=""></p>
<p>I decided to see other parts of the fan. The logic is, if the cylinder breaks apart from somewhere, there should be a trace of it. I then spotted a previously unnoticed part. It is a plastic box and there is a sharp irregular edge on it, which is a sign of a broken part. I have no idea what the box is and it does not look critical to the fan&rsquo;s functionality, since I already identified the timer and the switch, etc.</p>
<h2 id="making-the-fan-alive-again">Making the Fan Alive Again!</h2>
<p>Upon closer inspection of the plastic box, I see two wires going into it and the wires are connected to small metal blades. One weird thing is the blades are <strong>NOT</strong> connected. And they are likely to remain unconnected during the operation of the fan. What is this?</p>
<p>Now it becomes interesting: I have examined most parts of the fan, and found stuff that worth investigating. I need to connect the dots. Some creativity, as well as luck, are needed here. I stared at the plastic box and the cylinder for a while, and I suddenly <strong>have a hypothesis</strong>. If I put this metal cylinder inside the plastic box, the blades will be connected. Could it be the reason that the fan stopped working?</p>
<p><img src="../imgs/img-3.jpg" alt=""></p>
<p>I did a quick test: I put the cylinder inside it and turned the switch on. Wow, it works! The fan is alive again!</p>
<h2 id="why-is-there-a-plastic-box-and-a-metal-cylinder">Why is there a Plastic Box and a Metal Cylinder?</h2>
<p>Not all of the mysteries are solved yet. I am still puzzled by this plastic box and the metal cylinder. What is the purpose of having them? What did they look like before the fall?</p>
<p>Now it comes to the fundamental part of reverse engineering: <strong>understanding how the system works</strong>. The fan works but it is quite weird: this plastic box can be removed and we just connect the two wires directly. There must be a reason to have it.</p>
<p>There are two ways to reason about it in such a situation. The first way is to think of what could go wrong if we do not have it. Like why we need to check whether the divisor is zero before we divide. However, as mentioned above, nothing seems wrong without this box. This method does not work here. It must be serving <strong>certain purpose yet unknown</strong> to us. This is quite a typical scenario in reverse engineering.</p>
<p>The other way is to imagine different inputs to the system (the fan), and predict the possible status or outcomes of the system. Then we deduce the purpose of it. This is harder to do because we need to generate lots of inputs and examine many possible status or outcomes. And it is not guaranteed to succeed! It could be purposed for a situation we could never think of, so we would never know why it is here.</p>
<p>Let us start with it. The cylinder currently connects the two metal blades. What would stop it from doing so? Not too hard, right? If it leaves the current position and goes up, the blades are disconnected. However, due to gravity, it will not move up by itself. Can we come up with a case where the gravity does not moderate this cylinder? Well, if this fan is used in the space station then the cylinder can move freely. But it is not the case here. It is a consumer product. What could be another case where the effect of gravity is gone or altered?</p>
<p><strong>WHEN IT FALLS!</strong></p>
<p>When the fan falls, the gravity will no longer drag the cylinder toward the position that connects the two blades. The result is, the cylinder moves, leaving the two blads disconnected, and the fan stops working. Now we have a reasonable explanation for the plastic part and the metal cylinder: it is a <strong>fall-protection mechanism</strong>!</p>
<h2 id="connecting-the-dots">Connecting the Dots</h2>
<p>Note the cylinder can not only move vertically, but it can also move horizontally. It can leave the plastic box and never (easily) get back. In fact, this is probably the cause of the fan&rsquo;s failure. We still miss something.</p>
<p>I did not guess it, though some readers could already guessed it. I examined the fan again and found another plastic piece in it. It looks like a lid for the plastic box. If there is a lid, then the cylinder will not leave the plasitc box. And in case of the fan falls, once it is erected again, the cylinder will go back to its original position and connect the blades again.</p>
<p>All the dots are eventually connected. The metal cylinder was confined in a plastic box. It serves as a fall protection mechanism. However, the fan fell from a high desk and the impact was so strong that the cylinder broke the plastic box apart. (We can see the lid was somehow connected to the box before it broke.) It is unable to go back to its original position again and the fan stopped working.</p>
<p>I have to admit this is quite simple yet effective. If I were to implement such functionality, what comes to my mind first is gyroscopes and a program, which is both complex and expensive. Through reverse enginnering,  I learned the same thing can be achieved like this.</p>
<p>It eventyally comes to the last step in reverse engineering. We need to repair the fan. For this particular one, it is not hard to repair. We first put the cylinder inside the box, then put the lid on top of the box, and then use some tape to secure it. It looks like this after it is repaired:</p>
<p><img src="../imgs/img-4.jpg" alt=""></p>
<h2 id="relating-to-reverse-engineering">Relating to Reverse Engineering</h2>
<p>I admit this example of reverse engineering the fall-protection mechanism and repairing the fan is trivial. However, it does show some important steps in reverse engineering. Let me explain.</p>
<p>In the first step, I opened the fan to see its internal. This is analogous to analyzing a binary statically. I did some preliminary analysis on the fan, like identifying the core components. In reverse engineering, we do this too. Typically we would have a quick look at the binary to get some information about it. Like what platform it runs on and what API functions it calls.</p>
<p>Then I spotted a metal cylinder that moves freely. This is called (by me) a <strong>pivot</strong>. A binary program can be huge and we cannot blindly reverse it entirely. We need to focus on something. It could be a string, an API function, or a constant value (in crypto function).</p>
<p>From the cylinder, I investigated the fan and came up with a possible <strong>hypothesis</strong> for it. I tested it by putting the cylinder back and turn the fan on. Then the hypothesis is confirmed. This loop is quite common in real-world reverse engineering. For example, there is a function that we are not sure about. We could study it and get several possible guesses for it. Then we confirm or refute them. What I did is most close to <strong>debugging</strong>, where I launch the fan and see if it works. I am lucky since my first hypothesis is correct. In reversing this loop could repeat several times before one understands a complex function.</p>
<p>Now it comes to the hard part. I did not immediately understand why there are such a plastic box and a cylinder. This is also common in reverse engineering. We encounter lots of things that we cannot properly understand or guess their meaning. The approach I took can be understood as a <strong>symbolic execution</strong> of the fan. I tried to reason about what could happen to the fan in a different scenario. While doing this, <strong>constriant solving</strong> is quite helpful as it gave me several cases of why the cylinder could move. Symbolic execution and constraint solving are intermediate topics in reverse engineering. They could look like <em>magic</em> in many cases.</p>
<p>After I get a comprehensive understanding of the fan, I need to repair it. In reverse engineering, most likely we do not need to repair anything (well, in certain cases we need to fix a bug in the binary, but that is rare). We need to re-implement it, either as code or documentation.</p>
<p>The above can be summarized in the following chart:</p>
<table>
	<thead>
			<tr>
					<th>Repairing a fan</th>
					<th>Reverse Enginnering</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>take the fan apart</td>
					<td>static analysis</td>
			</tr>
			<tr>
					<td>spot the cylinder</td>
					<td>find a pivot</td>
			</tr>
			<tr>
					<td>guess the cylinder can connect the circuit</td>
					<td>have a hypothesis</td>
			</tr>
			<tr>
					<td>put the cylinder back and turn the fan on</td>
					<td>test the hypothesis (debugging)</td>
			</tr>
			<tr>
					<td>reason about the plastic box&rsquo;s functionality</td>
					<td>symbolic execution &amp; constraint solving</td>
			</tr>
			<tr>
					<td>understand it is fall protection mechanism</td>
					<td>understand the functionality of code</td>
			</tr>
			<tr>
					<td>repair the fan</td>
					<td>reimplement as code or documentation</td>
			</tr>
	</tbody>
</table>
<p>Of course, this analogy is not meant to be complete or always accurate. For example, debugging is only one of the ways to test the hypothesis. And we do not explicitly use symbolic execution and constraint solving every time we reverse. An interesting fact is, when we reason about a piece of code, we probably symbolically executed it many times <strong>in our mind</strong> without using any external tools like Triton or angr.</p>
]]></content>
        </item>
        
        <item>
            <title>排局-20</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-20/</link>
            <pubDate>Sun, 06 May 2018 16:19:08 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-20/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-24.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车八退一 士４进５&lt;/li&gt;
&lt;li&gt;车八平六 士５进４&lt;/li&gt;
&lt;li&gt;炮八进九 马５退４&lt;/li&gt;
&lt;li&gt;炮八退三 马４进３&lt;/li&gt;
&lt;li&gt;马九退七 将５平４&lt;/li&gt;
&lt;li&gt;兵四平五 炮７平５&lt;/li&gt;
&lt;li&gt;炮八进三 象３进５&lt;/li&gt;
&lt;li&gt;兵五进一 将４进１&lt;/li&gt;
&lt;li&gt;炮八退一&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-24.png" alt=""></p>
<ol>
<li>车八退一 士４进５</li>
<li>车八平六 士５进４</li>
<li>炮八进九 马５退４</li>
<li>炮八退三 马４进３</li>
<li>马九退七 将５平４</li>
<li>兵四平五 炮７平５</li>
<li>炮八进三 象３进５</li>
<li>兵五进一 将４进１</li>
<li>炮八退一</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-19</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-19/</link>
            <pubDate>Sun, 06 May 2018 16:10:59 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-19/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-1-3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮六平八 士４进５&lt;/li&gt;
&lt;li&gt;马四进五! 士５进４&lt;/li&gt;
&lt;li&gt;马五进六!&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-1-3.png" alt=""></p>
<ol>
<li>炮六平八 士４进５</li>
<li>马四进五! 士５进４</li>
<li>马五进六!</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-18</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-18/</link>
            <pubDate>Sun, 06 May 2018 16:06:56 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-18/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-21.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;兵六平五 炮７平５&lt;/li&gt;
&lt;li&gt;兵三平四 马５退６&lt;/li&gt;
&lt;li&gt;马一进二 车７退７&lt;/li&gt;
&lt;li&gt;炮七进一 士５退４&lt;/li&gt;
&lt;li&gt;马四进六 士４退５&lt;/li&gt;
&lt;li&gt;炮七退七&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由此红方转为进攻黑方底士，此亦本局取名《峰回路转》之意。&lt;/p&gt;
&lt;ol start=&#34;6&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        车７平８&lt;/li&gt;
&lt;li&gt;炮七平六 车８进６&lt;/li&gt;
&lt;li&gt;炮六平二 马８退７&lt;/li&gt;
&lt;li&gt;炮二进一 马７进９&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如马7退6，炮二平四，困毙红胜&lt;/p&gt;
&lt;ol start=&#34;10&#34;&gt;
&lt;li&gt;炮二平六 马９进７&lt;/li&gt;
&lt;li&gt;帅四退一 马７退８&lt;/li&gt;
&lt;li&gt;帅四进一 马８退７&lt;/li&gt;
&lt;li&gt;帅四退一 马７进５&lt;/li&gt;
&lt;li&gt;帅四进一 马５退４&lt;/li&gt;
&lt;li&gt;炮六进一 马４退３&lt;/li&gt;
&lt;li&gt;炮六进三 马３退２&lt;/li&gt;
&lt;li&gt;炮六进二 红胜&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-21.png" alt=""></p>
<ol>
<li>兵六平五 炮７平５</li>
<li>兵三平四 马５退６</li>
<li>马一进二 车７退７</li>
<li>炮七进一 士５退４</li>
<li>马四进六 士４退５</li>
<li>炮七退七</li>
</ol>
<p>由此红方转为进攻黑方底士，此亦本局取名《峰回路转》之意。</p>
<ol start="6">
<li>&hellip;&hellip;        车７平８</li>
<li>炮七平六 车８进６</li>
<li>炮六平二 马８退７</li>
<li>炮二进一 马７进９</li>
</ol>
<p>如马7退6，炮二平四，困毙红胜</p>
<ol start="10">
<li>炮二平六 马９进７</li>
<li>帅四退一 马７退８</li>
<li>帅四进一 马８退７</li>
<li>帅四退一 马７进５</li>
<li>帅四进一 马５退４</li>
<li>炮六进一 马４退３</li>
<li>炮六进三 马３退２</li>
<li>炮六进二 红胜</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-17</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-17/</link>
            <pubDate>Sun, 06 May 2018 15:31:13 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-17/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-20.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;帅六退一 士５进６&lt;/li&gt;
&lt;li&gt;兵五平四 将４退１&lt;/li&gt;
&lt;li&gt;炮五平七 将４进１&lt;/li&gt;
&lt;li&gt;炮七平四 将４平５&lt;/li&gt;
&lt;li&gt;炮四退一 将５平４&lt;/li&gt;
&lt;li&gt;炮四平五 士６退５&lt;/li&gt;
&lt;li&gt;兵四平五 士５退６&lt;/li&gt;
&lt;li&gt;炮五进一 士６进５&lt;/li&gt;
&lt;li&gt;炮五平六&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-20.png" alt=""></p>
<ol>
<li>帅六退一 士５进６</li>
<li>兵五平四 将４退１</li>
<li>炮五平七 将４进１</li>
<li>炮七平四 将４平５</li>
<li>炮四退一 将５平４</li>
<li>炮四平五 士６退５</li>
<li>兵四平五 士５退６</li>
<li>炮五进一 士６进５</li>
<li>炮五平六</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-16</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-16/</link>
            <pubDate>Sun, 06 May 2018 15:30:08 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-16/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-18.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;兵九平八 将４进１&lt;/li&gt;
&lt;li&gt;兵八平七 将４退１&lt;/li&gt;
&lt;li&gt;帅五退一 象３进１&lt;/li&gt;
&lt;li&gt;马七进八 象１进３&lt;/li&gt;
&lt;li&gt;兵七进一 将４退１&lt;/li&gt;
&lt;li&gt;马八进七 象３退５&lt;/li&gt;
&lt;li&gt;兵七平六 将４平５&lt;/li&gt;
&lt;li&gt;马七退五 车１退１&lt;/li&gt;
&lt;li&gt;仕六退五 车１平４&lt;/li&gt;
&lt;li&gt;马五进四 将５平６&lt;/li&gt;
&lt;li&gt;兵六平五 车４退２&lt;/li&gt;
&lt;li&gt;马四进二&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-19.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-18.png" alt=""></p>
<ol>
<li>兵九平八 将４进１</li>
<li>兵八平七 将４退１</li>
<li>帅五退一 象３进１</li>
<li>马七进八 象１进３</li>
<li>兵七进一 将４退１</li>
<li>马八进七 象３退５</li>
<li>兵七平六 将４平５</li>
<li>马七退五 车１退１</li>
<li>仕六退五 车１平４</li>
<li>马五进四 将５平６</li>
<li>兵六平五 车４退２</li>
<li>马四进二</li>
</ol>
<p><img src="../imgs/2018/05/capture-19.png" alt=""></p>
]]></content>
        </item>
        
        <item>
            <title>排局-15</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-15/</link>
            <pubDate>Sun, 06 May 2018 15:27:58 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-15/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-16.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-17.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-16.png" alt=""></p>
<p><img src="../imgs/2018/05/capture-17.png" alt=""></p>
]]></content>
        </item>
        
        <item>
            <title>排局-14</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-14/</link>
            <pubDate>Sun, 06 May 2018 15:25:19 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-14/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-14-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车四平六 将４平５&lt;/li&gt;
&lt;li&gt;车六平五 将５平６&lt;/li&gt;
&lt;li&gt;相五进三 车７平６&lt;/li&gt;
&lt;li&gt;车五平二 将６退１&lt;/li&gt;
&lt;li&gt;帅五平六 车６平９&lt;/li&gt;
&lt;li&gt;车二进五 将６退１&lt;/li&gt;
&lt;li&gt;车二退八 车９平７&lt;/li&gt;
&lt;li&gt;车二进三 车７平９&lt;/li&gt;
&lt;li&gt;帅六进一 车９进１&lt;/li&gt;
&lt;li&gt;帅六进一 车９平２&lt;/li&gt;
&lt;li&gt;帅六平五 车２退２&lt;/li&gt;
&lt;li&gt;仕五进六 车２退６&lt;/li&gt;
&lt;li&gt;车二进六 将６进１&lt;/li&gt;
&lt;li&gt;车二退一 将６退１&lt;/li&gt;
&lt;li&gt;车二平八&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-14-1.png" alt=""></p>
<ol>
<li>车四平六 将４平５</li>
<li>车六平五 将５平６</li>
<li>相五进三 车７平６</li>
<li>车五平二 将６退１</li>
<li>帅五平六 车６平９</li>
<li>车二进五 将６退１</li>
<li>车二退八 车９平７</li>
<li>车二进三 车７平９</li>
<li>帅六进一 车９进１</li>
<li>帅六进一 车９平２</li>
<li>帅六平五 车２退２</li>
<li>仕五进六 车２退６</li>
<li>车二进六 将６进１</li>
<li>车二退一 将６退１</li>
<li>车二平八</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-13</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-13/</link>
            <pubDate>Sun, 06 May 2018 15:23:59 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-13/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-13-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮九平六 士５进４&lt;/li&gt;
&lt;li&gt;仕六退五 车４平５&lt;/li&gt;
&lt;li&gt;炮六平八 卒６平７&lt;/li&gt;
&lt;li&gt;炮八退八 卒７平６&lt;/li&gt;
&lt;li&gt;帅五平六 士４退５&lt;/li&gt;
&lt;li&gt;炮八进四 卒６平７&lt;/li&gt;
&lt;li&gt;炮八平五 卒７平６&lt;/li&gt;
&lt;li&gt;炮五平一 卒６平７&lt;/li&gt;
&lt;li&gt;炮一退四 卒７平６&lt;/li&gt;
&lt;li&gt;相九进七 将６进１&lt;/li&gt;
&lt;li&gt;相七退五 将６退１&lt;/li&gt;
&lt;li&gt;炮一进四 卒６平７&lt;/li&gt;
&lt;li&gt;炮一平八 卒７平６&lt;/li&gt;
&lt;li&gt;炮八退四 将６进１&lt;/li&gt;
&lt;li&gt;仕五退四 车５平６&lt;/li&gt;
&lt;li&gt;炮八平四&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-13-1.png" alt=""></p>
<ol>
<li>炮九平六 士５进４</li>
<li>仕六退五 车４平５</li>
<li>炮六平八 卒６平７</li>
<li>炮八退八 卒７平６</li>
<li>帅五平六 士４退５</li>
<li>炮八进四 卒６平７</li>
<li>炮八平五 卒７平６</li>
<li>炮五平一 卒６平７</li>
<li>炮一退四 卒７平６</li>
<li>相九进七 将６进１</li>
<li>相七退五 将６退１</li>
<li>炮一进四 卒６平７</li>
<li>炮一平八 卒７平６</li>
<li>炮八退四 将６进１</li>
<li>仕五退四 车５平６</li>
<li>炮八平四</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-12</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-12/</link>
            <pubDate>Sun, 06 May 2018 15:21:12 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-12/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-12-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;马七退五 将６退１&lt;/li&gt;
&lt;li&gt;马五进三 将６进１&lt;/li&gt;
&lt;li&gt;马三退一 士５退４&lt;/li&gt;
&lt;li&gt;前马退二 将６退１&lt;/li&gt;
&lt;li&gt;马一进二 将６平５&lt;/li&gt;
&lt;li&gt;帅五平六 象５进７&lt;/li&gt;
&lt;li&gt;后马退四 象７退９&lt;/li&gt;
&lt;li&gt;马四退二 将５进１&lt;/li&gt;
&lt;li&gt;后马进三 将５平６&lt;/li&gt;
&lt;li&gt;马三进二 将６平５&lt;/li&gt;
&lt;li&gt;后马进四 象９进７&lt;/li&gt;
&lt;li&gt;马四退三 将５退１&lt;/li&gt;
&lt;li&gt;马二退一 象３进５&lt;/li&gt;
&lt;li&gt;马一进三 将５平６&lt;/li&gt;
&lt;li&gt;前马进二 将６平５&lt;/li&gt;
&lt;li&gt;马二退四 象５退３&lt;/li&gt;
&lt;li&gt;马四退五 象７退９&lt;/li&gt;
&lt;li&gt;马五进七&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-12-1.png" alt=""></p>
<ol>
<li>马七退五 将６退１</li>
<li>马五进三 将６进１</li>
<li>马三退一 士５退４</li>
<li>前马退二 将６退１</li>
<li>马一进二 将６平５</li>
<li>帅五平六 象５进７</li>
<li>后马退四 象７退９</li>
<li>马四退二 将５进１</li>
<li>后马进三 将５平６</li>
<li>马三进二 将６平５</li>
<li>后马进四 象９进７</li>
<li>马四退三 将５退１</li>
<li>马二退一 象３进５</li>
<li>马一进三 将５平６</li>
<li>前马进二 将６平５</li>
<li>马二退四 象５退３</li>
<li>马四退五 象７退９</li>
<li>马五进七</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-11</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-11/</link>
            <pubDate>Sun, 06 May 2018 15:19:51 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-11/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-11-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;马六退七 将５退１&lt;/li&gt;
&lt;li&gt;马七进八 象１进３&lt;/li&gt;
&lt;li&gt;相九进七 象３退１&lt;/li&gt;
&lt;li&gt;帅四退一 象１进３&lt;/li&gt;
&lt;li&gt;相七退五 象３退１&lt;/li&gt;
&lt;li&gt;帅四平五 象１进３&lt;/li&gt;
&lt;li&gt;帅五平六 象３退１&lt;/li&gt;
&lt;li&gt;帅六退一 象１进３&lt;/li&gt;
&lt;li&gt;炮九退一 将５进１&lt;/li&gt;
&lt;li&gt;马八退七 将５平６&lt;/li&gt;
&lt;li&gt;马七进六 将６平５&lt;/li&gt;
&lt;li&gt;炮九退七 将５退１&lt;/li&gt;
&lt;li&gt;炮九平五 象３退５&lt;/li&gt;
&lt;li&gt;相五进七 象５进７&lt;/li&gt;
&lt;li&gt;马六退五 象３进５&lt;/li&gt;
&lt;li&gt;马五进三 将５平６&lt;/li&gt;
&lt;li&gt;炮五进八&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-11-1.png" alt=""></p>
<ol>
<li>马六退七 将５退１</li>
<li>马七进八 象１进３</li>
<li>相九进七 象３退１</li>
<li>帅四退一 象１进３</li>
<li>相七退五 象３退１</li>
<li>帅四平五 象１进３</li>
<li>帅五平六 象３退１</li>
<li>帅六退一 象１进３</li>
<li>炮九退一 将５进１</li>
<li>马八退七 将５平６</li>
<li>马七进六 将６平５</li>
<li>炮九退七 将５退１</li>
<li>炮九平五 象３退５</li>
<li>相五进七 象５进７</li>
<li>马六退五 象３进５</li>
<li>马五进三 将５平６</li>
<li>炮五进八</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-10</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-10/</link>
            <pubDate>Sun, 06 May 2018 15:09:32 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-10/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-3-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-3-2.png" alt=""></p>
<ol>
<li>车七平四 将６平５</li>
<li>车四平二 马３退２</li>
</ol>
<p>起手红方不吃马，而是照将然后平二，威胁抽车。黑车位置尴尬，只有回马防守。</p>
<ol start="3">
<li>帅五平四 将５退１</li>
<li>车二进三 将５进１</li>
</ol>
<p>红方出帅，意图控制肋道。黑方车马士四子均动弹不得。如果黑方将5平6，则红方顺势车二平四，将6平5，士四退五，以下胜法相似。</p>
<ol start="5">
<li>仕四退五 卒４平5</li>
</ol>
<p>红方落士露将，并且捉吃黑卒。黑如不逃卒，改走将5进1，则红方车二退二，再吃掉黑卒，获胜更容易。</p>
<ol start="6">
<li>车二平四 将５进１</li>
<li>车四退二 将５退１</li>
</ol>
<p>红方控住黑方五子，黑方只有上下老将。红方一车之力虽然不能直接将杀，但暗中扬相，上帅，夺取中路。</p>
<ol start="8">
<li>相五进七 将５退１</li>
<li>车四进二 将５进１</li>
<li>帅四进一 将５进１</li>
</ol>
<p>红帅升到楼顶，此时黑将亦在顶楼，时机成熟，红方果断退车叫杀。</p>
<ol start="11">
<li>车四退四 士４退５</li>
<li>车四平五 将５平４</li>
<li>帅四平五 车４平１</li>
<li>车五平六 白脸将杀，红胜！</li>
</ol>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p><img src="../imgs/2018/05/capture-4-2.png" alt=""></p>
<ol>
<li>车三平五 将５平６</li>
<li>车五平四 将６平５</li>
<li>车四退一 车４平５</li>
<li>仕四退五 卒６平５</li>
<li>仕五退六 卒５平４</li>
<li>帅五退一 卒４平３</li>
<li>帅五退一 卒３平４</li>
<li>帅五平四 车５平６</li>
<li>车四进四 马５进７</li>
<li>相三退五</li>
</ol>
<p><img src="../imgs/2018/05/capture-5-1.png" alt=""></p>
<ol>
<li>车九平五 将５平６</li>
<li>车五平四 将６平５</li>
<li>车四退一 车４平５</li>
<li>仕四进五 象７进９</li>
<li>仕五退六 象９进７</li>
<li>仕四退五 象７退９</li>
<li>仕五进六 象９进７</li>
<li>帅五退一 象７退９</li>
<li>帅五退一 象９退７</li>
<li>帅五平四 车５平６</li>
<li>车四进二 车５平６</li>
<li>车四进二</li>
</ol>
<p><img src="../imgs/2018/05/capture-6-1.png" alt=""></p>
<ol>
<li>帅六平五 炮５退１</li>
<li>车二进一 炮５退１</li>
<li>车二进一 炮５退１</li>
<li>车二进一 炮５退１</li>
<li>车二进一 炮５退１</li>
<li>车二进一 炮５进１</li>
<li>车二平五 将５平６</li>
<li>车五平四 将６平５</li>
<li>帅五平四 士４退５</li>
<li>车四平五 将５平４</li>
<li>车五平六 将４平５</li>
<li>车六进二</li>
</ol>
<p><img src="../imgs/2018/05/capture-7-1.png" alt=""></p>
<ol>
<li>车九退一 将４进１</li>
<li>车九退四 卒４进１</li>
<li>帅六退一 卒４进１</li>
<li>帅六平五 将４退１</li>
<li>车九进四 将４进１</li>
<li>车九退六 卒４进１</li>
<li>帅五进一 将４退１</li>
<li>车九进六 将４进１</li>
<li>车九退五 将４退１</li>
<li>车九平六 将４平５</li>
<li>帅五平六 车６退１</li>
<li>车六进五 将５退１</li>
<li>车六进一 将５进１</li>
<li>车六平四</li>
</ol>
<p><img src="../imgs/2018/05/capture-9-1.png" alt=""></p>
<p><img src="../imgs/2018/05/capture-10-1.png" alt=""></p>
<ol>
<li>车九平六 将４平５</li>
<li>车六平五 将５平４</li>
<li>帅四平五 将４退１</li>
<li>车五平七 车９退１</li>
<li>车七进二 将４进１</li>
<li>车七退五 炮５退１</li>
<li>车七平六 将４平５</li>
<li>车六进一 炮５退１</li>
<li>车六进一 炮５退１</li>
<li>车六进一 炮５进４</li>
<li>车六平五 将５平４</li>
<li>帅五进一 车９平５</li>
<li>车五进三</li>
</ol>]]></content>
        </item>
        
        <item>
            <title>排局-09</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-09/</link>
            <pubDate>Sun, 06 May 2018 15:05:02 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-09/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-1-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-2-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车五退一 炮５进６&lt;/li&gt;
&lt;li&gt;兵五平四 卒９平８&lt;/li&gt;
&lt;li&gt;相三退一 象７退５&lt;/li&gt;
&lt;li&gt;后兵进一 炮５退４&lt;/li&gt;
&lt;li&gt;后兵进一 卒６平５&lt;/li&gt;
&lt;li&gt;帅五平四&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第一步只有车五退一可以获胜&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-1-2.png" alt=""></p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p><img src="../imgs/2018/05/capture-2-2.png" alt=""></p>
<ol>
<li>车五退一 炮５进６</li>
<li>兵五平四 卒９平８</li>
<li>相三退一 象７退５</li>
<li>后兵进一 炮５退４</li>
<li>后兵进一 卒６平５</li>
<li>帅五平四</li>
</ol>
<p>第一步只有车五退一可以获胜</p>
]]></content>
        </item>
        
        <item>
            <title>排局-08</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-08/</link>
            <pubDate>Sun, 06 May 2018 15:01:11 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-06-endgame-08/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-14.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?2b6/9/3k5/9/6b2/6B2/9/3A5/4AK3/p4CB1p%20w&#34;&gt;2b6/9/3k5/9/6b2/6B2/9/3A5/4AK3/p4CB1p w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图形势，红方当然可以连续打掉两个黑卒。但这样黑方得以调整阵型，红方无法取胜。&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;正确的走法是借叫杀之机，调整士相，将黑方双象赶到两边，进而获胜。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮四平六 将４平５&lt;/li&gt;
&lt;li&gt;炮六平五 将５平４&lt;/li&gt;
&lt;li&gt;仕五退四 将４平５&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如改走将4退1，则帅四平五，红必得象，胜定。&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;相三退五 将５平４&lt;/li&gt;
&lt;li&gt;相五进七 将４平５&lt;/li&gt;
&lt;li&gt;仕四进五 将５平４&lt;/li&gt;
&lt;li&gt;炮五平七&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;驱黑象到边线。&lt;/p&gt;
&lt;ol start=&#34;7&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        象３进１&lt;/li&gt;
&lt;li&gt;炮七平六 将４平５&lt;/li&gt;
&lt;li&gt;炮六平五 将５平４&lt;/li&gt;
&lt;li&gt;仕五进四 将４平５&lt;/li&gt;
&lt;li&gt;相七退五 将５平６&lt;/li&gt;
&lt;li&gt;帅四平五 将６平５&lt;/li&gt;
&lt;li&gt;帅五平六&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红帅移行换位。&lt;/p&gt;
&lt;ol start=&#34;13&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        将５平４&lt;/li&gt;
&lt;li&gt;相五进三 将４平５&lt;/li&gt;
&lt;li&gt;仕六退五 将５平６&lt;/li&gt;
&lt;li&gt;仕五退六 将６平５&lt;/li&gt;
&lt;li&gt;相三进五 将５平６&lt;/li&gt;
&lt;li&gt;相五进七 将６平５&lt;/li&gt;
&lt;li&gt;仕六进五 将５平６&lt;/li&gt;
&lt;li&gt;炮五平三 象７退９&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;再驱另一象到边线。&lt;/p&gt;
&lt;ol start=&#34;21&#34;&gt;
&lt;li&gt;帅六进一 将６退１&lt;/li&gt;
&lt;li&gt;帅六平五 红胜。&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-14.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?2b6/9/3k5/9/6b2/6B2/9/3A5/4AK3/p4CB1p%20w">2b6/9/3k5/9/6b2/6B2/9/3A5/4AK3/p4CB1p w</a></p>
<p>如图形势，红方当然可以连续打掉两个黑卒。但这样黑方得以调整阵型，红方无法取胜。</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>正确的走法是借叫杀之机，调整士相，将黑方双象赶到两边，进而获胜。</p>
<ol>
<li>炮四平六 将４平５</li>
<li>炮六平五 将５平４</li>
<li>仕五退四 将４平５</li>
</ol>
<p>如改走将4退1，则帅四平五，红必得象，胜定。</p>
<ol start="4">
<li>相三退五 将５平４</li>
<li>相五进七 将４平５</li>
<li>仕四进五 将５平４</li>
<li>炮五平七</li>
</ol>
<p>驱黑象到边线。</p>
<ol start="7">
<li>&hellip;&hellip;        象３进１</li>
<li>炮七平六 将４平５</li>
<li>炮六平五 将５平４</li>
<li>仕五进四 将４平５</li>
<li>相七退五 将５平６</li>
<li>帅四平五 将６平５</li>
<li>帅五平六</li>
</ol>
<p>红帅移行换位。</p>
<ol start="13">
<li>&hellip;&hellip;        将５平４</li>
<li>相五进三 将４平５</li>
<li>仕六退五 将５平６</li>
<li>仕五退六 将６平５</li>
<li>相三进五 将５平６</li>
<li>相五进七 将６平５</li>
<li>仕六进五 将５平６</li>
<li>炮五平三 象７退９</li>
</ol>
<p>再驱另一象到边线。</p>
<ol start="21">
<li>帅六进一 将６退１</li>
<li>帅六平五 红胜。</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-07</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-05-endgame-07/</link>
            <pubDate>Sat, 05 May 2018 23:18:22 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-05-endgame-07/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-4-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?4ka3/9/5a3/6CC1/9/9/9/3K5/9/8c%20w&#34;&gt;4ka3/9/5a3/6CC1/9/9/9/3K5/9/8c w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图形势，红方仅有双炮做攻，似乎难以进取。&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;第一步的进攻方向很是重要，如果沉底叫将，则与胜利失之交臂。&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-4-1.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?4ka3/9/5a3/6CC1/9/9/9/3K5/9/8c%20w">4ka3/9/5a3/6CC1/9/9/9/3K5/9/8c w</a></p>
<p>如图形势，红方仅有双炮做攻，似乎难以进取。</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>第一步的进攻方向很是重要，如果沉底叫将，则与胜利失之交臂。</p>
]]></content>
        </item>
        
        <item>
            <title>排局-06</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-05-endgame-06/</link>
            <pubDate>Sat, 05 May 2018 22:21:07 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-05-endgame-06/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-13.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?3r1r3/5k3/3a1a3/4C4/9/9/9/C8/5K3/3A5%20w%22&#34;&gt;3r1r3/5k3/3a1a3/4C4/9/9/9/C8/5K3/3A5 w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图形式，红方双炮巧妙腾挪，迫使黑方子力自相堵塞，最终一举获胜。&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;此局的进攻思路比较直接，运炮叫将，利用黑士自相阻塞，重炮或者闷宫而胜。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮九平四 将６平５&lt;/li&gt;
&lt;li&gt;炮五退六！&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方退炮引而不发，伏有士六进五， 将5平4，炮四平六，士4退5，炮五平六重炮杀。黑方只有先将5平4才能解杀，红方则士六进五追杀：&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        将５平４&lt;/li&gt;
&lt;li&gt;仕六进五 士４退５&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方退士也是仅有的解着。如图形式，直观的攻法是架炮做杀，但均难以奏效。试演两变如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-1-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;a.&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;炮四平六 将４进１&lt;/li&gt;
&lt;li&gt;帅四进一 车４平３&lt;/li&gt;
&lt;li&gt;帅四平五 车３进９&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方无杀，黑方胜势&lt;/p&gt;
&lt;p&gt;b.&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;炮五平六 将４进１&lt;/li&gt;
&lt;li&gt;仕五进六 将４平５&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方无杀，黑方胜势&lt;/p&gt;
&lt;p&gt;正确的走法是先士五进六做准备。细看之下，其实这是叫杀，演变如下：&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;仕五进六 车４平３？&lt;/li&gt;
&lt;li&gt;炮五平六 士５进４&lt;/li&gt;
&lt;li&gt;仕六退五 将４平５&lt;/li&gt;
&lt;li&gt;炮六平五 将５平４&lt;/li&gt;
&lt;li&gt;炮四平六 士４退５&lt;/li&gt;
&lt;li&gt;炮五平六 重炮胜&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此路攻法虽是连杀，但需要来回运炮，粗看之下不易发现，可以算作此局的核心。&lt;/p&gt;
&lt;p&gt;仕五进六扬士之后，黑方无暇挪车。最顽强的防守是先将4进1，当然红方辗转腾挪，攻势依然紧凑：&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;仕五进六 将４进１&lt;/li&gt;
&lt;li&gt;帅四平五！ 将４退１&lt;/li&gt;
&lt;li&gt;炮五平六 士５进４&lt;/li&gt;
&lt;li&gt;帅五平四！&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-2-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;以上着法，红方先进帅做杀，逼黑方下将，而后平炮打将，待黑方扬士时候再出帅，次序井然。注意此时红方仍然威胁士六退五然后连杀。&lt;/p&gt;
&lt;ol start=&#34;7&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        将４平５&lt;/li&gt;
&lt;li&gt;仕六退五 将５退１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-3-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;红方出帅，黑方顺势占中解杀。然而红方一手士六退五继续做杀，此时四六两路均已被控制，黑方只有坐将求生，但仍难免被重炮：&lt;/p&gt;
&lt;ol start=&#34;9&#34;&gt;
&lt;li&gt;炮六平五 士６退５&lt;/li&gt;
&lt;li&gt;仕五退六 士５进６&lt;/li&gt;
&lt;li&gt;炮四平五&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从开始的局面看，谁又能想到最后黑将会在原位被擒住呢？&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-13.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?3r1r3/5k3/3a1a3/4C4/9/9/9/C8/5K3/3A5%20w%22">3r1r3/5k3/3a1a3/4C4/9/9/9/C8/5K3/3A5 w</a></p>
<p>如图形式，红方双炮巧妙腾挪，迫使黑方子力自相堵塞，最终一举获胜。</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>此局的进攻思路比较直接，运炮叫将，利用黑士自相阻塞，重炮或者闷宫而胜。</p>
<ol>
<li>炮九平四 将６平５</li>
<li>炮五退六！</li>
</ol>
<p>红方退炮引而不发，伏有士六进五， 将5平4，炮四平六，士4退5，炮五平六重炮杀。黑方只有先将5平4才能解杀，红方则士六进五追杀：</p>
<ol start="2">
<li>&hellip;&hellip;        将５平４</li>
<li>仕六进五 士４退５</li>
</ol>
<p>黑方退士也是仅有的解着。如图形式，直观的攻法是架炮做杀，但均难以奏效。试演两变如下：</p>
<p><img src="../imgs/2018/05/capture-1-1.png" alt=""></p>
<p>a.</p>
<ol start="4">
<li>炮四平六 将４进１</li>
<li>帅四进一 车４平３</li>
<li>帅四平五 车３进９</li>
</ol>
<p>红方无杀，黑方胜势</p>
<p>b.</p>
<ol start="4">
<li>炮五平六 将４进１</li>
<li>仕五进六 将４平５</li>
</ol>
<p>红方无杀，黑方胜势</p>
<p>正确的走法是先士五进六做准备。细看之下，其实这是叫杀，演变如下：</p>
<ol start="4">
<li>仕五进六 车４平３？</li>
<li>炮五平六 士５进４</li>
<li>仕六退五 将４平５</li>
<li>炮六平五 将５平４</li>
<li>炮四平六 士４退５</li>
<li>炮五平六 重炮胜</li>
</ol>
<p>此路攻法虽是连杀，但需要来回运炮，粗看之下不易发现，可以算作此局的核心。</p>
<p>仕五进六扬士之后，黑方无暇挪车。最顽强的防守是先将4进1，当然红方辗转腾挪，攻势依然紧凑：</p>
<ol start="4">
<li>仕五进六 将４进１</li>
<li>帅四平五！ 将４退１</li>
<li>炮五平六 士５进４</li>
<li>帅五平四！</li>
</ol>
<p><img src="../imgs/2018/05/capture-2-1.png" alt=""></p>
<p>以上着法，红方先进帅做杀，逼黑方下将，而后平炮打将，待黑方扬士时候再出帅，次序井然。注意此时红方仍然威胁士六退五然后连杀。</p>
<ol start="7">
<li>&hellip;&hellip;        将４平５</li>
<li>仕六退五 将５退１</li>
</ol>
<p><img src="../imgs/2018/05/capture-3-1.png" alt=""></p>
<p>红方出帅，黑方顺势占中解杀。然而红方一手士六退五继续做杀，此时四六两路均已被控制，黑方只有坐将求生，但仍难免被重炮：</p>
<ol start="9">
<li>炮六平五 士６退５</li>
<li>仕五退六 士５进６</li>
<li>炮四平五</li>
</ol>
<p>从开始的局面看，谁又能想到最后黑将会在原位被擒住呢？</p>
]]></content>
        </item>
        
        <item>
            <title>排局-05</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-04-endgame-05/</link>
            <pubDate>Fri, 04 May 2018 17:13:21 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-04-endgame-05/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-4.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?6R2/3r5/3kr4/9/9/9/9/5C3/4A4/3A1K3%20w&#34;&gt;6R2/3r5/3kr4/9/9/9/9/5C3/4A4/3A1K3 w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图，红方似乎可以车三退三叫杀得车．可红方的取胜之路真的如此简单嘛？&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;显然不是这样简单&amp;ndash;不过黑方的应对也可谓奇思妙想:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车三退三 车５退１!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一着花心车龟缩防守，红方也暂时无计可施．此时平车打将会自找麻烦，因为黑方平将躲避后会形成兑车，红方反而不好处理．不过借打将之机调整炮位，进而扬士叫杀似乎是可行之策．&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;车三进一 车５进１&lt;/li&gt;
&lt;li&gt;炮四进五 车５退１&lt;/li&gt;
&lt;li&gt;炮四退六 车５进１&lt;/li&gt;
&lt;li&gt;车三退一 车５退１&lt;/li&gt;
&lt;li&gt;仕五进六&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-5.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此时红方伏有炮四平六，将４平５，车三平五的杀着．黑方不能车５平６牵炮，因为红方车三进一绝杀．其他解杀手段均会丢４路车．看似红方已经取得了胜利，但这真的是黑方最顽强的抵抗嘛？非也！惯性思维导致黑方在第５回合还是退花心车，但此时红炮的位置已经挡住了自己的老帅，所以黑方可以车５平８反杀！&lt;/p&gt;
&lt;p&gt;５．　　　　车５平８！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-6.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此时红方无暇抽车，只有借打将之机先占中路，然后进帅做杀：&lt;/p&gt;
&lt;ol start=&#34;6&#34;&gt;
&lt;li&gt;车三平六 将４平５&lt;/li&gt;
&lt;li&gt;车六平五 将５平４&lt;/li&gt;
&lt;li&gt;帅四平五&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此时黑方有两种防守方法，均难逃一败：&lt;/p&gt;
&lt;p&gt;a.&lt;/p&gt;
&lt;ol start=&#34;8&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        车４平８&lt;/li&gt;
&lt;li&gt;仕五退四 前车进１&lt;/li&gt;
&lt;li&gt;车五进一 将４退１&lt;/li&gt;
&lt;li&gt;炮四平六 前车进５&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-9.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此路变化黑方双车并线，与红方邀兑．无奈红方借先手打将，进而运炮卡位，胜势已成．前车进５是比较顽强的防守．如果后车进１？则车五进一！将４退１，车五退四，红胜定．注意这里车五进一的过们是必须的，如果直接车五退三，黑方有前车进２！红方车五平六，黑有后车平４，反而节外生枝．&lt;/p&gt;
&lt;ol start=&#34;12&#34;&gt;
&lt;li&gt;仕六进五 后车进５&lt;/li&gt;
&lt;li&gt;车五退一 后车退４&lt;/li&gt;
&lt;li&gt;车五退三&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上几步红方顿挫井然，如下图形势，黑方如果动前车，则红方士五进六得车胜．黑方如果动将或者移动后车，如将４退１，红方仍有士五进六，弃炮绝杀！以下黑方前车平４，车五平六，车８平４，车六进四绝杀红胜．&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-10.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此路变化，黑方竖向连车难以抵挡红方进攻，如果改为横向会怎样呢？&lt;/p&gt;
&lt;p&gt;b.&lt;/p&gt;
&lt;ol start=&#34;8&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        车４平７&lt;/li&gt;
&lt;li&gt;仕五退四 将４退１&lt;/li&gt;
&lt;li&gt;炮四平六 车７进１&lt;/li&gt;
&lt;li&gt;车五平六 车７平４&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-12.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;此路变化黑车会被红方栓住，但以炮换车红方只能得到和局，所以红方并不会轻易交换．&lt;/p&gt;
&lt;ol start=&#34;12&#34;&gt;
&lt;li&gt;仕六进五 车８平５&lt;/li&gt;
&lt;li&gt;炮六退一　红胜定&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方士六进五，暗伏车六进一，将４进１，士五进六绝杀．黑方不能简单车８进１弃车解杀，否则仍是车六进一，将４进１，士五进六，红得车亦胜．&lt;/p&gt;
&lt;p&gt;从最初的车三退三看似一步即胜，到后来的占中，架炮，红方的取胜之路可谓曲折．&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-4.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?6R2/3r5/3kr4/9/9/9/9/5C3/4A4/3A1K3%20w">6R2/3r5/3kr4/9/9/9/9/5C3/4A4/3A1K3 w</a></p>
<p>如图，红方似乎可以车三退三叫杀得车．可红方的取胜之路真的如此简单嘛？</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>显然不是这样简单&ndash;不过黑方的应对也可谓奇思妙想:</p>
<ol>
<li>车三退三 车５退１!</li>
</ol>
<p>一着花心车龟缩防守，红方也暂时无计可施．此时平车打将会自找麻烦，因为黑方平将躲避后会形成兑车，红方反而不好处理．不过借打将之机调整炮位，进而扬士叫杀似乎是可行之策．</p>
<ol start="2">
<li>车三进一 车５进１</li>
<li>炮四进五 车５退１</li>
<li>炮四退六 车５进１</li>
<li>车三退一 车５退１</li>
<li>仕五进六</li>
</ol>
<p><img src="../imgs/2018/05/capture-5.png" alt=""></p>
<p>此时红方伏有炮四平六，将４平５，车三平五的杀着．黑方不能车５平６牵炮，因为红方车三进一绝杀．其他解杀手段均会丢４路车．看似红方已经取得了胜利，但这真的是黑方最顽强的抵抗嘛？非也！惯性思维导致黑方在第５回合还是退花心车，但此时红炮的位置已经挡住了自己的老帅，所以黑方可以车５平８反杀！</p>
<p>５．　　　　车５平８！</p>
<p><img src="../imgs/2018/05/capture-6.png" alt=""></p>
<p>此时红方无暇抽车，只有借打将之机先占中路，然后进帅做杀：</p>
<ol start="6">
<li>车三平六 将４平５</li>
<li>车六平五 将５平４</li>
<li>帅四平五</li>
</ol>
<p><img src="../imgs/2018/05/capture-8.png" alt=""></p>
<p>此时黑方有两种防守方法，均难逃一败：</p>
<p>a.</p>
<ol start="8">
<li>&hellip;&hellip;        车４平８</li>
<li>仕五退四 前车进１</li>
<li>车五进一 将４退１</li>
<li>炮四平六 前车进５</li>
</ol>
<p><img src="../imgs/2018/05/capture-9.png" alt=""></p>
<p>此路变化黑方双车并线，与红方邀兑．无奈红方借先手打将，进而运炮卡位，胜势已成．前车进５是比较顽强的防守．如果后车进１？则车五进一！将４退１，车五退四，红胜定．注意这里车五进一的过们是必须的，如果直接车五退三，黑方有前车进２！红方车五平六，黑有后车平４，反而节外生枝．</p>
<ol start="12">
<li>仕六进五 后车进５</li>
<li>车五退一 后车退４</li>
<li>车五退三</li>
</ol>
<p>以上几步红方顿挫井然，如下图形势，黑方如果动前车，则红方士五进六得车胜．黑方如果动将或者移动后车，如将４退１，红方仍有士五进六，弃炮绝杀！以下黑方前车平４，车五平六，车８平４，车六进四绝杀红胜．</p>
<p><img src="../imgs/2018/05/capture-10.png" alt=""></p>
<p>此路变化，黑方竖向连车难以抵挡红方进攻，如果改为横向会怎样呢？</p>
<p>b.</p>
<ol start="8">
<li>&hellip;&hellip;        车４平７</li>
<li>仕五退四 将４退１</li>
<li>炮四平六 车７进１</li>
<li>车五平六 车７平４</li>
</ol>
<p><img src="../imgs/2018/05/capture-12.png" alt=""></p>
<p>此路变化黑车会被红方栓住，但以炮换车红方只能得到和局，所以红方并不会轻易交换．</p>
<ol start="12">
<li>仕六进五 车８平５</li>
<li>炮六退一　红胜定</li>
</ol>
<p>红方士六进五，暗伏车六进一，将４进１，士五进六绝杀．黑方不能简单车８进１弃车解杀，否则仍是车六进一，将４进１，士五进六，红得车亦胜．</p>
<p>从最初的车三退三看似一步即胜，到后来的占中，架炮，红方的取胜之路可谓曲折．</p>
]]></content>
        </item>
        
        <item>
            <title>排局-04</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-05-04-endgame-04/</link>
            <pubDate>Fri, 04 May 2018 16:00:41 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-05-04-endgame-04/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-04252018.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?1R1ara3/9/4k4/9/2b6/6B2/9/5K3/7p1/9%20w&#34;&gt;1R1ara3/9/4k4/9/2b6/6B2/9/5K3/7p1/9 w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图形势，红方进攻子力仅有一车，如何才能利用黑车的位置取得胜利？&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;最直观的思路并不能奏效：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车八退三 士６进５&lt;/li&gt;
&lt;li&gt;车八平五 将５平４&lt;/li&gt;
&lt;li&gt;帅四平五 将４退１&lt;/li&gt;
&lt;li&gt;车五平六 士５进４&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虽然黑方篡位车位置尴尬，但这样直接叫吃还是太过急躁．同样，如果直接车八退八，确实可以吃掉黑卒，但红方并不能取得胜利．&lt;/p&gt;
&lt;p&gt;正确的走法是先将黑将打到二楼，然后退车捉卒：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车八退二 将５退１&lt;/li&gt;
&lt;li&gt;车八退六&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时黑方虽然没有丢车之虞，但也无法疏通子力．当然不能回中象，否则红方车八进七绝杀．将５平４躲避会被红方车八平六先手带将吃掉卒，上将则会送车．所以黑方只有躲卒．这里有一个小陷阱，就是黑方卒８平７送吃：&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        卒８平７&lt;/li&gt;
&lt;li&gt;车八平三 象３退５&lt;/li&gt;
&lt;li&gt;车三平八 将５平４&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方无法取胜，和棋&lt;/p&gt;
&lt;p&gt;红方急于吃卒，但车被自己的高相挡住，黑方侥幸某得和局．细看之下，死卒不急吃，红方没有必要立刻杀卒，可以先落边相给车通头．&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;相三退一&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;接下来，黑卒走投无路，红方可以将其吃掉并且保持车通头，已成胜势．具体的胜方可以参考后文着法．&lt;/p&gt;
&lt;p&gt;黑方较为顽强的抵抗是卒８进１沉底：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车八退二 将５退１&lt;/li&gt;
&lt;li&gt;车八退六 卒８进１&lt;/li&gt;
&lt;li&gt;车八退一 卒８平７！&lt;/li&gt;
&lt;li&gt;相三退一 卒７平６！&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方先将卒沉底，然后平移进入红方九宫，如入无人之境．红方为了保持车通头，竟然不能简单将其杀掉．如下图形势，红方如帅四退一捉卒，黑方可以卒６平５占中！红方车八平五杀卒叫将则象３退５，进而某得和局．&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;那红方怎样才能取得胜利呢？这里红方需要切换一下进攻思路，不再用车捉卒，而是采用排局中常见的捉弄底卒的方式来将其擒获．正着初看之下有点不可思议，红方不仅不吃卒，还主动抬车；并且抬车的方式必须是车八进三或进四，连看起来更有威胁的车八进六都不行：&lt;/p&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;车八进三 卒６平５&lt;/li&gt;
&lt;li&gt;相一进三 卒５平４&lt;/li&gt;
&lt;li&gt;相三退五 卒４平５&lt;/li&gt;
&lt;li&gt;帅四退一 卒５平４&lt;/li&gt;
&lt;li&gt;帅四退一&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/05/capture-3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;以上着法对于排局爱好者并不陌生，首先运相回中路让黑卒无法离开九宫，然后下帅挤掉其仅有的活动空间．此时黑方只有动将，而对红方来说，取得胜利已经不再困难：&lt;/p&gt;
&lt;ol start=&#34;9&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        将５平４&lt;/li&gt;
&lt;li&gt;车八平六 将４平５&lt;/li&gt;
&lt;li&gt;车六进三 卒４平５&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方叫将后进车点穴，黑方无子可动．其实黑方的高象也是有意为之，如果黑方是底象，没有这一手塞象眼，红方还是无法取得胜利．此时只有方弃卒一搏，不过仍难免一败：&lt;/p&gt;
&lt;ol start=&#34;12&#34;&gt;
&lt;li&gt;帅四平五 将５平６&lt;/li&gt;
&lt;li&gt;车六平四 将６平５&lt;/li&gt;
&lt;li&gt;帅五平四 将５平４&lt;/li&gt;
&lt;li&gt;车四平六 将４平５&lt;/li&gt;
&lt;li&gt;相五进七　红胜定&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果黑方先出将，则红方先车八平六叫将然后车六进三点穴．黑卒还是会被困死．细心的读者可能已经发现前面为什么只能车八进三或进四，不能直接进六．否则，由于不需要进车点穴，差了一步棋．换句话说，最后单双步不对，并不能困死黑卒．&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/05/capture-04252018.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?1R1ara3/9/4k4/9/2b6/6B2/9/5K3/7p1/9%20w">1R1ara3/9/4k4/9/2b6/6B2/9/5K3/7p1/9 w</a></p>
<p>如图形势，红方进攻子力仅有一车，如何才能利用黑车的位置取得胜利？</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>最直观的思路并不能奏效：</p>
<ol>
<li>车八退三 士６进５</li>
<li>车八平五 将５平４</li>
<li>帅四平五 将４退１</li>
<li>车五平六 士５进４</li>
</ol>
<p>虽然黑方篡位车位置尴尬，但这样直接叫吃还是太过急躁．同样，如果直接车八退八，确实可以吃掉黑卒，但红方并不能取得胜利．</p>
<p>正确的走法是先将黑将打到二楼，然后退车捉卒：</p>
<ol>
<li>车八退二 将５退１</li>
<li>车八退六</li>
</ol>
<p>此时黑方虽然没有丢车之虞，但也无法疏通子力．当然不能回中象，否则红方车八进七绝杀．将５平４躲避会被红方车八平六先手带将吃掉卒，上将则会送车．所以黑方只有躲卒．这里有一个小陷阱，就是黑方卒８平７送吃：</p>
<ol start="2">
<li>&hellip;&hellip;        卒８平７</li>
<li>车八平三 象３退５</li>
<li>车三平八 将５平４</li>
</ol>
<p>红方无法取胜，和棋</p>
<p>红方急于吃卒，但车被自己的高相挡住，黑方侥幸某得和局．细看之下，死卒不急吃，红方没有必要立刻杀卒，可以先落边相给车通头．</p>
<ol start="3">
<li>相三退一</li>
</ol>
<p><img src="../imgs/2018/05/capture.png" alt=""></p>
<p>接下来，黑卒走投无路，红方可以将其吃掉并且保持车通头，已成胜势．具体的胜方可以参考后文着法．</p>
<p>黑方较为顽强的抵抗是卒８进１沉底：</p>
<ol>
<li>车八退二 将５退１</li>
<li>车八退六 卒８进１</li>
<li>车八退一 卒８平７！</li>
<li>相三退一 卒７平６！</li>
</ol>
<p>黑方先将卒沉底，然后平移进入红方九宫，如入无人之境．红方为了保持车通头，竟然不能简单将其杀掉．如下图形势，红方如帅四退一捉卒，黑方可以卒６平５占中！红方车八平五杀卒叫将则象３退５，进而某得和局．</p>
<p><img src="../imgs/2018/05/capture-2.png" alt=""></p>
<p>那红方怎样才能取得胜利呢？这里红方需要切换一下进攻思路，不再用车捉卒，而是采用排局中常见的捉弄底卒的方式来将其擒获．正着初看之下有点不可思议，红方不仅不吃卒，还主动抬车；并且抬车的方式必须是车八进三或进四，连看起来更有威胁的车八进六都不行：</p>
<ol start="5">
<li>车八进三 卒６平５</li>
<li>相一进三 卒５平４</li>
<li>相三退五 卒４平５</li>
<li>帅四退一 卒５平４</li>
<li>帅四退一</li>
</ol>
<p><img src="../imgs/2018/05/capture-3.png" alt=""></p>
<p>以上着法对于排局爱好者并不陌生，首先运相回中路让黑卒无法离开九宫，然后下帅挤掉其仅有的活动空间．此时黑方只有动将，而对红方来说，取得胜利已经不再困难：</p>
<ol start="9">
<li>&hellip;&hellip;        将５平４</li>
<li>车八平六 将４平５</li>
<li>车六进三 卒４平５</li>
</ol>
<p>红方叫将后进车点穴，黑方无子可动．其实黑方的高象也是有意为之，如果黑方是底象，没有这一手塞象眼，红方还是无法取得胜利．此时只有方弃卒一搏，不过仍难免一败：</p>
<ol start="12">
<li>帅四平五 将５平６</li>
<li>车六平四 将６平５</li>
<li>帅五平四 将５平４</li>
<li>车四平六 将４平５</li>
<li>相五进七　红胜定</li>
</ol>
<p>如果黑方先出将，则红方先车八平六叫将然后车六进三点穴．黑卒还是会被困死．细心的读者可能已经发现前面为什么只能车八进三或进四，不能直接进六．否则，由于不需要进车点穴，差了一步棋．换句话说，最后单双步不对，并不能困死黑卒．</p>
<p>本局的精妙之处在于借捉卒之机另其自陷虎口，转而运相退帅将其困毙，颇有声动击西之妙．现在看来，最开始的那句＂红方进攻子力仅有一车＂也需商榷，因为在排局中，士相甚至老帅都可以用来进攻．</p>
]]></content>
        </item>
        
        <item>
            <title>排局-03</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-01-18-endgame-03/</link>
            <pubDate>Thu, 18 Jan 2018 20:47:50 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-01-18-endgame-03/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/5.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?2rr5/9/3a1k3/3P5/2R6/2B6/9/9/3K5/9%20w&#34;&gt;2rr5/9/3a1k3/3P5/2R6/2B6/9/9/3K5/9 w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图，黑方将位不安，且双车略背，红方如何凭借先行之利取胜？&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;按照惯例，首先分析一路不成熟的攻势，打将之后占中做杀：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车七平四 将６平５&lt;/li&gt;
&lt;li&gt;车四平五 将５平６&lt;/li&gt;
&lt;li&gt;帅六平五 车４平５&lt;/li&gt;
&lt;li&gt;兵六平五 将６退１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方平兵继续叫杀，黑方当然不能直接车5进3砍兵，否则红方顺势车五进一吃掉之后即是绝杀。黑如车3进4弃车解杀，则红方兵五平四之后吃掉底车，仍是胜势。黑方下将正着，红如兵五进一，黑方便可以车5进2某得和局。&lt;/p&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;车五平四 将６平５&lt;/li&gt;
&lt;li&gt;兵五进一 将５平４&lt;/li&gt;
&lt;li&gt;车四进三 将４退１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红劣黑胜&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/6.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;黑方下将是一步容易被忽略的着法。最初算到这里的时候我以为黑方只能落士，红兵五进一之后由于黑车低头，形成巧胜。不料黑方有此着躲避，从而反败为胜。
所以红方正确的攻法是打两将之后平兵叫杀：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车七平四 将６平５&lt;/li&gt;
&lt;li&gt;车四平五 将５平６&lt;/li&gt;
&lt;li&gt;兵六平五 将６退１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方当然不能士4退5打将，否则红方帅六平五占中之后，黑方速败。&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;兵五进一 将６退１&lt;/li&gt;
&lt;li&gt;车五平四 将６平５&lt;/li&gt;
&lt;li&gt;车四平二 将５平６&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时红方走不到兵五平四做杀，因为黑方可以士4退5带将抽掉。又不能帅六平五，否则黑方车3进5弃车杀相可以谋和。红方攻势暂时受阻，怎样才能打开局面呢？&lt;/p&gt;
&lt;ol start=&#34;7&#34;&gt;
&lt;li&gt;车二进四 将６进１&lt;/li&gt;
&lt;li&gt;车二退五 将６退１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方借叫杀之机保住自己的高相，使得黑方杀相谋和的计划落空。但此时平帅黑方还是可以车3进4通头，下步即可以抢占中路，红方似乎还是难有进取。&lt;/p&gt;
&lt;ol start=&#34;9&#34;&gt;
&lt;li&gt;帅六平五 车３进４&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如图形势，红方有两路较为直观的攻法，但都难以奏效：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/9.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;a.&lt;/p&gt;
&lt;ol start=&#34;10&#34;&gt;
&lt;li&gt;兵五平四 车４平５！&lt;/li&gt;
&lt;li&gt;相七退五 车３进４&lt;/li&gt;
&lt;li&gt;帅五退一 车５进７ 黑胜&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方车4平5妙手，粉碎了红方的攻势，一举反败为胜&lt;/p&gt;
&lt;p&gt;b.&lt;/p&gt;
&lt;ol start=&#34;10&#34;&gt;
&lt;li&gt;车二平四 将６平５&lt;/li&gt;
&lt;li&gt;帅五平四 士４退５&lt;/li&gt;
&lt;li&gt;车四进四 车３平６!&lt;/li&gt;
&lt;li&gt;车四退三 车４进８&lt;/li&gt;
&lt;li&gt;帅四进一 车４平５&lt;/li&gt;
&lt;li&gt;相七退五 车５进１!&lt;/li&gt;
&lt;li&gt;帅四退一 车５退２&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方弃车照将的手法颇有排局味道，且即使红方落相盖车，黑方还是有沉底车的冷手，最终谋得和局。&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/01/5.png" alt=""></p>
<p><a href="https://www.chessdb.cn/query/?2rr5/9/3a1k3/3P5/2R6/2B6/9/9/3K5/9%20w">2rr5/9/3a1k3/3P5/2R6/2B6/9/9/3K5/9 w</a></p>
<p>如图，黑方将位不安，且双车略背，红方如何凭借先行之利取胜？</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>按照惯例，首先分析一路不成熟的攻势，打将之后占中做杀：</p>
<ol>
<li>车七平四 将６平５</li>
<li>车四平五 将５平６</li>
<li>帅六平五 车４平５</li>
<li>兵六平五 将６退１</li>
</ol>
<p>红方平兵继续叫杀，黑方当然不能直接车5进3砍兵，否则红方顺势车五进一吃掉之后即是绝杀。黑如车3进4弃车解杀，则红方兵五平四之后吃掉底车，仍是胜势。黑方下将正着，红如兵五进一，黑方便可以车5进2某得和局。</p>
<ol start="5">
<li>车五平四 将６平５</li>
<li>兵五进一 将５平４</li>
<li>车四进三 将４退１</li>
</ol>
<p>红劣黑胜</p>
<p><img src="../imgs/2018/01/6.png" alt=""></p>
<p>黑方下将是一步容易被忽略的着法。最初算到这里的时候我以为黑方只能落士，红兵五进一之后由于黑车低头，形成巧胜。不料黑方有此着躲避，从而反败为胜。
所以红方正确的攻法是打两将之后平兵叫杀：</p>
<ol>
<li>车七平四 将６平５</li>
<li>车四平五 将５平６</li>
<li>兵六平五 将６退１</li>
</ol>
<p>黑方当然不能士4退5打将，否则红方帅六平五占中之后，黑方速败。</p>
<ol start="4">
<li>兵五进一 将６退１</li>
<li>车五平四 将６平５</li>
<li>车四平二 将５平６</li>
</ol>
<p>此时红方走不到兵五平四做杀，因为黑方可以士4退5带将抽掉。又不能帅六平五，否则黑方车3进5弃车杀相可以谋和。红方攻势暂时受阻，怎样才能打开局面呢？</p>
<ol start="7">
<li>车二进四 将６进１</li>
<li>车二退五 将６退１</li>
</ol>
<p>红方借叫杀之机保住自己的高相，使得黑方杀相谋和的计划落空。但此时平帅黑方还是可以车3进4通头，下步即可以抢占中路，红方似乎还是难有进取。</p>
<ol start="9">
<li>帅六平五 车３进４</li>
</ol>
<p>如图形势，红方有两路较为直观的攻法，但都难以奏效：</p>
<p><img src="../imgs/2018/01/9.png" alt=""></p>
<p>a.</p>
<ol start="10">
<li>兵五平四 车４平５！</li>
<li>相七退五 车３进４</li>
<li>帅五退一 车５进７ 黑胜</li>
</ol>
<p>黑方车4平5妙手，粉碎了红方的攻势，一举反败为胜</p>
<p>b.</p>
<ol start="10">
<li>车二平四 将６平５</li>
<li>帅五平四 士４退５</li>
<li>车四进四 车３平６!</li>
<li>车四退三 车４进８</li>
<li>帅四进一 车４平５</li>
<li>相七退五 车５进１!</li>
<li>帅四退一 车５退２</li>
</ol>
<p>黑方弃车照将的手法颇有排局味道，且即使红方落相盖车，黑方还是有沉底车的冷手，最终谋得和局。</p>
<p>红方的正确走法并不起眼：</p>
<ol start="10">
<li>相七退五</li>
</ol>
<p><img src="../imgs/2018/01/7.png" alt=""></p>
<p>红方一手轻巧的落相，同时威胁兵五平四绝杀和车二进五抽车，黑方立感不妙。第一感是车4平3，似乎既能解杀，又给车生根。其实不然，红用传统攻法获胜：</p>
<ol start="10">
<li>&hellip;&hellip;        车４平３</li>
<li>车二平四 将６平５</li>
<li>帅五平四 士４退５</li>
<li>车四进四 红胜定</li>
</ol>
<p>与前面弃车解杀的b变相比，此时红相已在中路，黑方抢不到中路，必败无疑。</p>
<p>于是黑方只有丢车保帅，车3平6弃车，而后负隅顽抗：</p>
<ol start="10">
<li>&hellip;&hellip;        车３平６</li>
<li>车二进五 将６进１</li>
<li>车二平六 士４退５</li>
</ol>
<p>黑落士后，红如心急误走车六退一，则黑方车6平5巧和！</p>
<ol start="13">
<li>车六平二 车６进４</li>
<li>帅五退一 士５进６</li>
</ol>
<p><img src="../imgs/2018/01/8.png" alt=""></p>
<p>困兽犹斗，此时红方已是必胜局面，但若是不得门道，恐怕难有突破。</p>
<ol start="15">
<li>车二退九 车６退１</li>
<li>帅五进一 车６退４</li>
<li>车二进一 将６退１</li>
<li>车二平四 车６进５</li>
<li>帅五平四 红胜定</li>
</ol>
<p>回看全局，红方打将顿挫保相而后进帅落相，可谓手筋，值得玩味。</p>
]]></content>
        </item>
        
        <item>
            <title>排局-02</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-01-11-endgame-02/</link>
            <pubDate>Thu, 11 Jan 2018 23:08:29 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-01-11-endgame-02/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/3.png&#34; alt=&#34;rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4 w&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4%20w&#34;&gt;rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4 w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图，双方子力不多，且黑方双车通头，红方怎样才能借先行之利取胜？&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;初看之下，红方虽有空头炮，但黑方双车相连，抽将也得不到便宜。且边线双车对头，所以红方第一步究竟要不要打将就是值得思索的问题。如果打将，黑方必定士4进5，等红车再次打将离开中路，黑方可以有针对性的支士落士，红方难以进取。但若是不打将，则只有平车躲避，此时黑方可以进车打将，似乎很快可以化解红方尚不成熟的攻势。&lt;/p&gt;
&lt;p&gt;当然，红方还是可以先车九平三叫杀（伏士六进五，士4进5，车三进八杀）。此时黑方并不能打将抽车，因为车2进7，帅四退一，车2进1? 士六进五，反将红胜。简单上将并不能解杀：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车九平三 将５进１&lt;/li&gt;
&lt;li&gt;车三平五 将５平４&lt;/li&gt;
&lt;li&gt;车五平六 将４平５&lt;/li&gt;
&lt;li&gt;仕六进五 红胜&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以将5进1的走法是错误的。那黑方怎样才能化解红方攻势呢？看来只有先车2进7打将，红方必定帅四退一躲避。此时再将5进1，红方没法平车打将，是不是可以守住呢？非也，此时红方可以从竖线进攻：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车九平三 车２进７&lt;/li&gt;
&lt;li&gt;帅四退一 将５进１&lt;/li&gt;
&lt;li&gt;车三进七 将５进１&lt;/li&gt;
&lt;li&gt;仕六进五 将５平４&lt;/li&gt;
&lt;li&gt;车三退二 红胜定&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可惜，红方的胜利是建立在黑方的失误之上，当然正解也绝非寻常着法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;车九平三 车２进７&lt;/li&gt;
&lt;li&gt;帅四退一 车１进９&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;黑方一手车1进9（亦可车2进2），明为送，实为捉，红方不能炮五平九，否则车2进1抽车黑速胜。因炮被牵制，士六进五也不成，所以红方只能车三进八借打将之机先吃一车。&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;车三进八 将５进１&lt;/li&gt;
&lt;li&gt;炮五平九&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时红方不仅净多一炮，且底车先捉黑士，怎么看黑方也是败势难逃。但黑方自有妙计：&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;        车２进１&lt;/li&gt;
&lt;li&gt;帅四进一 车２进１&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;黑方车2进1打将，迫使红帅定位。红方如果帅四退一下帅，黑方则车2进1捉双，随后车2平4吃士，然后车4平5占中成单车守和车炮之势。红方不肯，只好帅上三楼。黑方随即再度进车捉双。为什么黑方不直接车2进2捉双呢？这里按下不表，稍后揭晓。&lt;/p&gt;
&lt;ol start=&#34;6&#34;&gt;
&lt;li&gt;炮九进九 车２平４&lt;/li&gt;
&lt;li&gt;车三平五 将５平４&lt;/li&gt;
&lt;/ol&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/01/3.png" alt="rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4 w"></p>
<p><a href="https://www.chessdb.cn/query/?rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4%20w">rr1ak4/9/9/9/9/9/9/5K3/R8/3AC4 w</a></p>
<p>如图，双方子力不多，且黑方双车通头，红方怎样才能借先行之利取胜？</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>初看之下，红方虽有空头炮，但黑方双车相连，抽将也得不到便宜。且边线双车对头，所以红方第一步究竟要不要打将就是值得思索的问题。如果打将，黑方必定士4进5，等红车再次打将离开中路，黑方可以有针对性的支士落士，红方难以进取。但若是不打将，则只有平车躲避，此时黑方可以进车打将，似乎很快可以化解红方尚不成熟的攻势。</p>
<p>当然，红方还是可以先车九平三叫杀（伏士六进五，士4进5，车三进八杀）。此时黑方并不能打将抽车，因为车2进7，帅四退一，车2进1? 士六进五，反将红胜。简单上将并不能解杀：</p>
<ol>
<li>车九平三 将５进１</li>
<li>车三平五 将５平４</li>
<li>车五平六 将４平５</li>
<li>仕六进五 红胜</li>
</ol>
<p>所以将5进1的走法是错误的。那黑方怎样才能化解红方攻势呢？看来只有先车2进7打将，红方必定帅四退一躲避。此时再将5进1，红方没法平车打将，是不是可以守住呢？非也，此时红方可以从竖线进攻：</p>
<ol>
<li>车九平三 车２进７</li>
<li>帅四退一 将５进１</li>
<li>车三进七 将５进１</li>
<li>仕六进五 将５平４</li>
<li>车三退二 红胜定</li>
</ol>
<p>可惜，红方的胜利是建立在黑方的失误之上，当然正解也绝非寻常着法：</p>
<ol>
<li>车九平三 车２进７</li>
<li>帅四退一 车１进９</li>
</ol>
<p><img src="../imgs/2018/01/3.png" alt=""></p>
<p>黑方一手车1进9（亦可车2进2），明为送，实为捉，红方不能炮五平九，否则车2进1抽车黑速胜。因炮被牵制，士六进五也不成，所以红方只能车三进八借打将之机先吃一车。</p>
<ol start="3">
<li>车三进八 将５进１</li>
<li>炮五平九</li>
</ol>
<p>此时红方不仅净多一炮，且底车先捉黑士，怎么看黑方也是败势难逃。但黑方自有妙计：</p>
<ol start="4">
<li>&hellip;&hellip;        车２进１</li>
<li>帅四进一 车２进１</li>
</ol>
<p>黑方车2进1打将，迫使红帅定位。红方如果帅四退一下帅，黑方则车2进1捉双，随后车2平4吃士，然后车4平5占中成单车守和车炮之势。红方不肯，只好帅上三楼。黑方随即再度进车捉双。为什么黑方不直接车2进2捉双呢？这里按下不表，稍后揭晓。</p>
<ol start="6">
<li>炮九进九 车２平４</li>
<li>车三平五 将５平４</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>排局-01</title>
            <link>https://xusheng.dev/posts/xiangqi/2018-01-11-endgame-01/</link>
            <pubDate>Thu, 11 Jan 2018 23:04:07 +0800</pubDate>
            <guid>https://xusheng.dev/posts/xiangqi/2018-01-11-endgame-01/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/1.png&#34; alt=&#34;9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc w&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chessdb.cn/query/?9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc%20w&#34;&gt;9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如图局面，是我不久前从残局库中发现的一个挺有意思的局面，红先胜。&lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt; 
 &lt;/p&gt;
&lt;p&gt;分析：&lt;/p&gt;
&lt;p&gt;从初始盘面来看，双方各有一炮，红方有士相，黑方仅光将且在三楼，估计是要在困炮的同时调整阵型，最后白脸将杀。&lt;/p&gt;
&lt;p&gt;最直接的思路是打两将之后扬相，然后帅占中路做杀。但黑方有一手叫闷可解：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮二平四 将６平５&lt;/li&gt;
&lt;li&gt;炮四平五 将５平６&lt;/li&gt;
&lt;li&gt;相五进三 将６平５&lt;/li&gt;
&lt;li&gt;仕六进五 将５平６&lt;/li&gt;
&lt;li&gt;帅六进一 炮９平８&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此路虽不通，但红方另有一路攻法，就是先上帅，然后再运炮做攻。由于红炮占位，黑方走不到炮9平8，形势十分不妙。将如6平5红方则顺势炮二平五打将。炮9退1也难挽颓势：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;帅六进一 炮９退１&lt;/li&gt;
&lt;li&gt;炮二平四 炮９平６&lt;/li&gt;
&lt;li&gt;帅六退一 将６平５&lt;/li&gt;
&lt;li&gt;炮四平五 将５平６&lt;/li&gt;
&lt;li&gt;帅六平五 炮６进１&lt;/li&gt;
&lt;li&gt;相五进七 将６退１&lt;/li&gt;
&lt;li&gt;帅五进一 将６退１&lt;/li&gt;
&lt;li&gt;仕六进五 炮６退１&lt;/li&gt;
&lt;li&gt;炮五平四 将６进１&lt;/li&gt;
&lt;li&gt;仕五进六 将６进１&lt;/li&gt;
&lt;li&gt;帅五退一 捉死炮胜&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然，这则排局并非如此简单。黑方炮9退1不是应对帅六进一的最顽强走法。黑方有一手将6退1可以某得和局。下将后，红方当然不能急于炮二平四打将，否则黑方可以从容炮9平8再炮8退7防守。于是红方只有先飞相，但无论红相往哪边去，黑方都有妙手化解。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../imgs/2018/01/2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;如图形势，如红飞三七高相，则黑方炮击底士，而后绕回防守，红方无计可施：&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;相五进七 炮９平４&lt;/li&gt;
&lt;li&gt;炮二平四 将６平５&lt;/li&gt;
&lt;li&gt;炮四平五 炮４平２&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如红落相，黑方当然不能打士（否则炮被困死）。正确走法是将6平5占中：&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;相五退七 将６平５&lt;/li&gt;
&lt;li&gt;仕六进五 炮９平３&lt;/li&gt;
&lt;li&gt;炮二平五 将５平６&lt;/li&gt;
&lt;li&gt;帅六平五 炮３退７&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方自己落下来的底相被黑方打掉然后借机回防，也是十分有趣。&lt;/p&gt;
&lt;p&gt;那正确的攻法是什么呢？还是先从运炮入手：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;炮二平四 将６平５&lt;/li&gt;
&lt;li&gt;炮四平五 将５平６&lt;/li&gt;
&lt;li&gt;相五进三 将６平５&lt;/li&gt;
&lt;li&gt;仕六进五 将５平６&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;红方直接上帅并不能奏效，但这里如果46路的子力调换一下，红帅在4路，士和黑将在6路，则黑方无法借助炮9平8防守。但红方怎样才能调整阵型，达到上述目标呢？且看后续着法：&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="../imgs/2018/01/1.png" alt="9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc w"></p>
<p><a href="https://www.chessdb.cn/query/?9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc%20w">9/9/5k3/9/9/9/9/4BA2B/3K5/3A3Cc w</a></p>
<p>如图局面，是我不久前从残局库中发现的一个挺有意思的局面，红先胜。</p>
<p> 
 </p>
<p> 
 </p>
<p> 
 </p>
<p>分析：</p>
<p>从初始盘面来看，双方各有一炮，红方有士相，黑方仅光将且在三楼，估计是要在困炮的同时调整阵型，最后白脸将杀。</p>
<p>最直接的思路是打两将之后扬相，然后帅占中路做杀。但黑方有一手叫闷可解：</p>
<ol>
<li>炮二平四 将６平５</li>
<li>炮四平五 将５平６</li>
<li>相五进三 将６平５</li>
<li>仕六进五 将５平６</li>
<li>帅六进一 炮９平８</li>
</ol>
<p>此路虽不通，但红方另有一路攻法，就是先上帅，然后再运炮做攻。由于红炮占位，黑方走不到炮9平8，形势十分不妙。将如6平5红方则顺势炮二平五打将。炮9退1也难挽颓势：</p>
<ol>
<li>帅六进一 炮９退１</li>
<li>炮二平四 炮９平６</li>
<li>帅六退一 将６平５</li>
<li>炮四平五 将５平６</li>
<li>帅六平五 炮６进１</li>
<li>相五进七 将６退１</li>
<li>帅五进一 将６退１</li>
<li>仕六进五 炮６退１</li>
<li>炮五平四 将６进１</li>
<li>仕五进六 将６进１</li>
<li>帅五退一 捉死炮胜</li>
</ol>
<p>当然，这则排局并非如此简单。黑方炮9退1不是应对帅六进一的最顽强走法。黑方有一手将6退1可以某得和局。下将后，红方当然不能急于炮二平四打将，否则黑方可以从容炮9平8再炮8退7防守。于是红方只有先飞相，但无论红相往哪边去，黑方都有妙手化解。</p>
<p><img src="../imgs/2018/01/2.png" alt=""></p>
<p>如图形势，如红飞三七高相，则黑方炮击底士，而后绕回防守，红方无计可施：</p>
<ol start="2">
<li>相五进七 炮９平４</li>
<li>炮二平四 将６平５</li>
<li>炮四平五 炮４平２</li>
</ol>
<p>如红落相，黑方当然不能打士（否则炮被困死）。正确走法是将6平5占中：</p>
<ol start="2">
<li>相五退七 将６平５</li>
<li>仕六进五 炮９平３</li>
<li>炮二平五 将５平６</li>
<li>帅六平五 炮３退７</li>
</ol>
<p>红方自己落下来的底相被黑方打掉然后借机回防，也是十分有趣。</p>
<p>那正确的攻法是什么呢？还是先从运炮入手：</p>
<ol>
<li>炮二平四 将６平５</li>
<li>炮四平五 将５平６</li>
<li>相五进三 将６平５</li>
<li>仕六进五 将５平６</li>
</ol>
<p>红方直接上帅并不能奏效，但这里如果46路的子力调换一下，红帅在4路，士和黑将在6路，则黑方无法借助炮9平8防守。但红方怎样才能调整阵型，达到上述目标呢？且看后续着法：</p>
<ol start="5">
<li>仕五进六 将６平５</li>
<li>相三退五 将５平６</li>
<li>帅六平五 将６平５</li>
<li>帅五平四 将５平６</li>
<li>相五进三 将６平５</li>
</ol>
<p>红方借连续做杀之机，将底士高扬，并将中相落回，后将帅运至4路。黑方将6平5，是因红威胁进帅绝杀，所以只得放弃4路。红方继续贯彻思路：</p>
<ol start="10">
<li>仕四退五 将５平４</li>
<li>帅四进一 炮９平８</li>
<li>帅四平五 红胜定</li>
</ol>
<p>本局独特之处在于，红方的攻法不仅是隔步杀，而且心中需要有一个明确的目标。离开这个目标，红方的着法都没有意义。一位好友指出，这局有些像数学题。我觉得在这局棋里，更多的是推理和排除，而不是传统意义上的计算。说来也巧，我们都是数学专业出身，所以对这类题目情有独钟。后续我还会发布几则类似味道的局面，欢迎大家批评指正。</p>
]]></content>
        </item>
        
    </channel>
</rss>
