<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Vineet's site</title><id>https://vineetk.net/feed.xml</id><subtitle>Recent Posts</subtitle><updated>2026-05-15T22:16:34Z</updated><link href="https://vineetk.net/feed.xml" rel="self" /><link href="https://vineetk.net" /><entry><title>*nix workflow after nearly a decade (presentation)</title><id>https://vineetk.net/nix-workflow-after-nearly-a-decade-presentation.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2026-04-13T17:30:00Z</updated><link href="https://vineetk.net/nix-workflow-after-nearly-a-decade-presentation.html" rel="alternate" /><content type="html">&lt;blockquote&gt;&lt;p&gt;note: there's some artifacts like not knowing how to do org-mode's
en-dashes in markdown when I converted this for my site.  this was
originally shown in org-mode via
&lt;a href=&quot;https://github.com/rlister/org-present&quot;&gt;org-present.el&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;h1&gt;who am I and why am I here&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;former e-board officer, been using *nix as my daily driver for
nearly a decade&lt;/li&gt;&lt;li&gt;&lt;p&gt;started at 12 as a 4chan &lt;code&gt;/g/&lt;/code&gt; kid -- any cringe can be blamed on that&lt;/p&gt;&lt;ul&gt;&lt;li&gt;I haven't gone on 4chan since like 2020, I have no idea what kind
of cesspool even &lt;code&gt;/g/&lt;/code&gt; has become (back in the day, there were nice
threads on there everywhere except /pol/ based on how the poltards
were described)&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;guiding principles throughout: unix purist, minimalist, anti-bloat&lt;/li&gt;&lt;li&gt;this is roughly how that went&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;how it started: luke smith&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;his early video on why should one use terminal programs was my
gateway drug into everything&lt;/p&gt;&lt;ul&gt;&lt;li&gt;since I used youtube in like 2011, this was the sole good thing
that ever came out of the algorithm thing that people talk about
for me specifically&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;from there: different distros (he started with parabola on his
librebooted x200), tiling WMs (i3, dwm), suckless and minimalism,
LaTeX&lt;/li&gt;&lt;li&gt;&lt;p&gt;parabola's wiki in 2017 still had the beginner's guide arch had
removed -- effectively the same distro anyway&lt;/p&gt;&lt;ul&gt;&lt;li&gt;parabola is just a more libre and gnu version of arch&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;this was 2017-2018, before he quit being a linguistics professor in
georgia, moved to a cabin in florida, and started complaining about
les youths&lt;/li&gt;&lt;li&gt;didn't follow other linux channels until much later -- pretty much
just david wilson's system crafters for emacs and guix (more on this
at the end)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/yt_2017_lukesmith_terminal_programs.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/yt_2017_lukesmith_danklatexvim_setup.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;distro hopping&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;started on parabola -&amp;gt; arch shortly after for wifi drivers (was
interesting finding out about non-free firmware as soon I start
using linux)&lt;/li&gt;&lt;li&gt;set up gentoo through the handbook to actually learn how linux works
underneath&lt;/li&gt;&lt;li&gt;&lt;p&gt;did a full LFS+BLFS. twice.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;if I didn't get into Guix, I would've done it again after getting
frustrated with regular distros for the nth time.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;side-tangent: I already disliked that it made it seem like
creating a distro was more complicated than it really can be
because of how many dependencies stuff like gcc and glibc need,
but now it's even adding systemd on top of that? I don't recommend
anymore. too many points of failure where it can go wrong.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;distro hopping 2&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;then: CRUX, Sabotage, KISS, Alpine -- hopping between them over
whatever small thing annoyed me at the time&lt;/li&gt;&lt;li&gt;&lt;p&gt;found CRUX through z3bra on nixers.net discussing differences
between it and gentoo&lt;/p&gt;&lt;ul&gt;&lt;li&gt;felt like home: just enough packages for a minimal base, liked
being a package maintainer for a while&lt;/li&gt;&lt;li&gt;power user burnout got to me every single time after a few months
of usage though where I then quickly reinstall arch&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;sabotage was unique and interesting but only lasted one hop&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;it had a similar idea to nixos and guix where there's a singular
store directory but&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/z3bra_crux_nixers.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;distro hopping 3&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;KISS was what I wished CRUX was, but it didn't have the drunk tux
mascot so it only got a couple hops&lt;/li&gt;&lt;li&gt;sabotage and KISS found through people on IRC and XMPP&lt;/li&gt;&lt;li&gt;learned a lot about sysadmin and writing packages through all of
this&lt;/li&gt;&lt;li&gt;choices eventually converged to: arch (easy), CRUX (fuzzy inside),
gentoo (USE flags deepened the purism path, less package maintenance
burden than CRUX)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/cruxlogo.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;os hopping -- the BSDs&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;found them in the continued pursuit of minimalism and purism&lt;/li&gt;&lt;li&gt;direct descendants of bell labs research unix&lt;/li&gt;&lt;li&gt;freebsd: what I wished linux was -- better documentation,
first-class integrated ZFS&lt;/li&gt;&lt;li&gt;openbsd: high quality documentation in form of man pages (rather
than freebsd's handbook in addition to man pages),
security-above-all-else focus (though didn't impact game emulation
much), tight-knit mature community (unlike most of linux), crux's
packaging was heavily inspired by openbsd's, the mascot, and it's
canadian&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/openbsd_55song.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;os hopping -- tangent with plan9 / 9front&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;grade 12 high school phase&lt;/li&gt;&lt;li&gt;&lt;p&gt;the creators of UNIX at the end of Research UNIX v7 and v8 pretty
much thought &amp;quot;how can we do this better?&amp;quot; and made a separate OS
that is like a reimagining of UNIX&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&amp;quot;everything is a file&amp;quot; taken further into even the networking&lt;/li&gt;&lt;li&gt;e.g. in BSD and its derivatives for networking via sockets are all
based around the C sockets API while plan9 put even that into
abstracted files and made 9p (plan9 protocol; even windows used it
in wsl1 before they switched to virtualization)&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;loved the simplicity and elegance of how its programmed&lt;/p&gt;&lt;ul&gt;&lt;li&gt;it uses a different flavour of C&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;rio/8.5 and acme showed me that mice aren't inherently bad for
computer use when designed /properly/&lt;/p&gt;&lt;ul&gt;&lt;li&gt;i.e. not context menu galore&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;couldn't daily it: i219-v and r8168 ethernet drivers weren't working
correctly even after trying to patch them with my then-meager C
skills like changing IRQ interrupt numbers and stuff&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/9front_using.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/9front_what.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;distro hopping fin&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;OSes I used the longest without hopping: crux, gentoo, and openbsd&lt;/li&gt;&lt;li&gt;left crux and gentoo mainly due to power user burnout from package
maintenance&lt;/li&gt;&lt;li&gt;initially left openbsd for windows virtualization and gaming (though I ended
up doing more FOSS engines and retro emulation under it than
expected)&lt;/li&gt;&lt;li&gt;&lt;p&gt;later left openbsd again and freebsd as well because of the amdkfd
driver being patched out, meaning I can't use ROCm on my GPUs for
GPGPU compute&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;it only leaves ggml's vulkan kernels which is quite different from
doing CUDA/HIP&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/pspodcasting_oses.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;window managers&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;started with i3 -- keyboard-only workflow, most popular on
r/unixporn at the time&lt;/p&gt;&lt;ul&gt;&lt;li&gt;back when every post was either i3-gaps or bspwm, not sway and
hyprland like today&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;switched to bspwm: more minimal, more unix philosophy -- keybind
handling split into a separate program (sxhkd)&lt;/li&gt;&lt;li&gt;used herbstluftwm for a bit -- liked the manual tiling&lt;/li&gt;&lt;li&gt;dwm became my main WM for a long time: minimalism + functionality,
suckless, elegant&lt;/li&gt;&lt;li&gt;&lt;p&gt;brief switch to spectrwm -- visually similar to dwm but easier to
configure&lt;/p&gt;&lt;ul&gt;&lt;li&gt;based on my screenshots that lasted only a few months before
switching back to dwm&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;briefly tried sway to see if wayland was worth it -- switched back
to dwm&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;only really used sway when too lazy to set up xorg on a new system&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;some screenshots from other people&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;reflects what I see as a *nix desktop is&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;window managers -- i3 (from xaetlas on r/unixporn in 2021)&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;I couldn't find the picture I saw from luke smith's website, maybe mandela effect&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2021_xaetlas_i3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;window managers -- bspwm (from gamehelp16 on r/unixporn in 2017)&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2017_gamehelp16_bspwm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;window managers -- herbsluftwm (from nerdypepper/abhinav on r/unixporn in 2017)&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2017_nerdypepper_herbstluftwm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;window managers -- cwm (from z3bra in 2018)&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2018_z3bra_cwm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;and now my rices&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;I didn't keep screenshots archived from before 2020. some good ones were lost. :(&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;2020 -- spectrwm&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2020_mine_spectrwm1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2020_mine_spectrwm2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;2023 -- cwm&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2023_mine_cwm1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;2024 -- dwm&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/2024_mine_dwm1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;2025 -- sway&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/202506_mine_sway.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;2025 -- dwm&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/202507_mine_dwm2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/202507_mine_dwm3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;2026 -- (not mine, joey's) hyprland since it looks cool&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/202604_joey_hyprland.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;editors: before emacs&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;before linux: eclipse for java, notepad++ for everything else, brief
atom stint&lt;/li&gt;&lt;li&gt;&lt;p&gt;switched to vim when I started linux&lt;/p&gt;&lt;ul&gt;&lt;li&gt;terminal programs like it were part of what got me to switch in
the first place&lt;/li&gt;&lt;li&gt;learning your tools actually matters -- this comes up a lot&lt;/li&gt;&lt;li&gt;avoided pre-made configs: they were bloated and changed defaults
in ways that were hard to reason about as a new user&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;brief emacs attempt, but not knowing lisp made it impossible to
properly configure&lt;/p&gt;&lt;ul&gt;&lt;li&gt;also noticeably sluggish on windows on the same hardware that ran
arch and gentoo fine&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;after learning vim enough (motions, basic ex commands) -- wanted
more minimalism&lt;/p&gt;&lt;ul&gt;&lt;li&gt;used vis and nvi for a while&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;plan9 phase: used acme briefly, learned sam through its manual&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;sam taught me ed, ed taught me basic regex&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;regex + vi motions: cannot overstate how much this improved
editing&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;nixos&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;peer pressured into it by functional programming friends in junior
year of college&lt;/li&gt;&lt;li&gt;&lt;p&gt;main selling point: entire system configured through a single file,
dotfiles managed in one language&lt;/p&gt;&lt;ul&gt;&lt;li&gt;if I hadn't used nixos, I likely would not have gotten as deep
into guixsd later&lt;/li&gt;&lt;li&gt;guix itself was originally based on nix&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;frustrating at first because I didn't know nixlang&lt;/p&gt;&lt;ul&gt;&lt;li&gt;same situation as when I first tried emacs in 2020&lt;/li&gt;&lt;li&gt;once I learned more nixlang, nixos felt much nicer to use&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;but: still had to deal with systemd, and its administration felt
very bureaucratic and corporate&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;nixos&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/nixos_rocm_jester.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;the broader nixpkgs community was being overrun by new users blindly
copy-pasting configs without understanding them -- everyone ended up
with the same setup&lt;/p&gt;&lt;ul&gt;&lt;li&gt;felt like it was becoming less for power users&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;the DSL is a special snowflake -- they should have used something
else as a base like haskell or anything they actually took
inspiration from&lt;/li&gt;&lt;li&gt;&lt;p&gt;ROCm support issue had a light-hearted tone that implied it wouldn't
be seriously maintained by the team&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;that thread was marked stale three months ago and has had no
activity for the past eight months -- right after I stopped using
nixos&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;first attempt at guix&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;briefly tried guix after nixos&lt;/li&gt;&lt;li&gt;not knowing guile scheme made it hard to use -- same pattern as
nixos and emacs before it&lt;/li&gt;&lt;li&gt;also trying to fold my existing suckless + vi workflow into it was
difficult at the time&lt;/li&gt;&lt;li&gt;switched back to rotating between arch, CRUX, gentoo, and nixos
despite their respective shortcomings&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;editors: almost emacs&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;the completion package situation had changed significantly since I
last looked at emacs&lt;/li&gt;&lt;li&gt;it previously felt like you /needed/ something like ivy or helm --
packages that fundamentally changed how the editor felt, not
improved it&lt;/li&gt;&lt;li&gt;&lt;p&gt;what changed my mind:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;orderless&lt;/code&gt; -- fuzzy searching without the bloat&lt;/li&gt;&lt;li&gt;&lt;code&gt;vertico&lt;/code&gt; -- the vertical completion menu ivy used to give&lt;/li&gt;&lt;li&gt;&lt;code&gt;consult&lt;/code&gt; -- better autocomplete layered over existing emacs
functions&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;these packages improve what's already there rather than overriding
it&lt;/li&gt;&lt;li&gt;finding them was part of my gateway drug into giving emacs a proper
second chance&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;the lispy gopher show&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;heard about it while briefly on the fediverse through prahou
(author/artist of unix_surrealism from analog_nowhere)&lt;/li&gt;&lt;li&gt;it and system crafters were what actually made me learn lisp and
scheme properly rather than just look things up&lt;/li&gt;&lt;li&gt;had learned some functional programming through OCaml and nix the
prior couple summers -- learning lisp and scheme was much easier
with that background&lt;/li&gt;&lt;li&gt;&lt;p&gt;with vi-like editors I never needed to write scripts in them or
touch their config much&lt;/p&gt;&lt;ul&gt;&lt;li&gt;regex + vi motions + a few bridge packages covered most gaps&lt;/li&gt;&lt;li&gt;with emacs, not knowing lisp meant always looking things up
instead of just writing what I needed&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;the parentheses were daunting before. now they make me feel fuzzy
inside, the same way learning linux did in middle school&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;img src=&quot;/images/workflow_9years/lispy_gopher_show.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;&lt;h1&gt;why emacs now and not before&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;the main reason I resisted emacs was it seeming like a monolithic
kitchen sink&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&amp;quot;an operating system that lacks a good editor&amp;quot; is not an entirely
inaccurate description&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;but the way I was applying the unix philosophy had a cohesion
problem&lt;/p&gt;&lt;ul&gt;&lt;li&gt;mutt looks different from newsboat looks different from lynx --
all configured in different syntaxes&lt;/li&gt;&lt;li&gt;vi keybinds and ncurses in common is about as far as the cohesion
went&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;with emacs: yes it's one big program, yes I'm not using POSIX shell
to glue things together&lt;/p&gt;&lt;ul&gt;&lt;li&gt;lisp is a more powerful language than shell, which depends on
other programs written in other languages to do even simple things&lt;/li&gt;&lt;li&gt;everything goes through text-based buffers -- integration is
genuinely easier&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;pre-made configs like doom emacs or spacemacs were also a turnoff&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;same problem as pre-made bloated vim configs: you end up learning
the pre-made config, not the editor&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;neovim has a similar situation now&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;guix + emacs&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;guix got me to learn guile scheme properly&lt;/li&gt;&lt;li&gt;now write most of my programs in guile scheme instead of defaulting
to C99 (suckless style) or python&lt;/li&gt;&lt;li&gt;guix also finally gave me what nixos pointed toward: system + home
config + dotfiles all managed in one language, without systemd,
without the corporate feel, without the special snowflake DSL
problem&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;current stack&lt;/h1&gt;&lt;ul&gt;&lt;li&gt;editor, scheme via geiser, elisp via eval-* functions: emacs,
obviously&lt;/li&gt;&lt;li&gt;&lt;p&gt;org-mode: document editing, task planning, habit tracking&lt;/p&gt;&lt;ul&gt;&lt;li&gt;worth calling out separately -- it's much more than a markup
language&lt;/li&gt;&lt;li&gt;some people use emacs entirely for org-mode&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;email: notmuch + mbsync + msmtp via notmuch.el directly (same setup
as before, just dropped mutt/aerc)&lt;/li&gt;&lt;li&gt;RSS: elfeed&lt;/li&gt;&lt;li&gt;browser: eww primarily, qutebrowser for js-heavy sites (canvas,
etc.)&lt;/li&gt;&lt;li&gt;&lt;p&gt;document viewing: pdftools + docview instead of mupdf&lt;/p&gt;&lt;ul&gt;&lt;li&gt;mupdf still for large PDFs like textbooks where it's just faster&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;background programs: dtach + its emacs management package instead of
tmux&lt;/li&gt;&lt;li&gt;local LLMs: gptel talking to vllm and llama.cpp running on my
desktop&lt;/li&gt;&lt;li&gt;&lt;p&gt;window manager: EXWM instead of dwm or anything else&lt;/p&gt;&lt;ul&gt;&lt;li&gt;worked fine for games -- elite dangerous and others ran through it
without emacs getting in the way&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;</content></entry><entry><title>*nix workflow after nearly a decade (raw braindump)</title><id>https://vineetk.net/nix-workflow-after-nearly-a-decade-raw-braindump.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2026-04-13T12:00:00Z</updated><link href="https://vineetk.net/nix-workflow-after-nearly-a-decade-raw-braindump.html" rel="alternate" /><content type="html">&lt;p&gt;I was asked a few times from members of my university's cybersecurity
club (I was a former e-board member and it's my main club) over the
past months of how I use a computer since apparently how I use it is
different from how others do it. They also found it fascinating that I
used Linux/*BSD for as long as I did, and I'm probably among very few
others if any at my university that used it for like 9.5 years. It's
organized in what got me into this in the first place, my OS-hopping,
my editor-hopping, what my current workflow is, and my (maybe lack of
meaningful) thought process behind each switch. The TL;DR of that is
I'm like a purist (primarily with the UNIX philosophy) and minimalist
(reducing &amp;quot;bloat&amp;quot; as much as possible) and that guided a lot of my
decisions up till now at the end of college. Any mistakes I made or
any &amp;quot;cringe&amp;quot; I did like being a 4chan &lt;code&gt;/g/&lt;/code&gt; kid can be blamed on me
being 12/13 at the time. The following is the raw braindump before I
condensed it into point form.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;I was introduced to Linux via Luke Smith's early video on why use
terminal programs. Then by watching his other videos, I got introduced
to stuff like different kinds of distros (he used Parabola at the time
and Parabola's wiki in 2017 hadn't removed the beginners guide page
unlike Arch wiki, though in hindsight they're basically the same),
tiling window managers (i3, dwm), suckless movement and minimalism
(was the start of me obsessing over purism even to my detriment
sometimes as in spending too much time that I miss deadlines), using
LaTeX for documents and presentations, etc. This was during his early
days back in 2017 and 2018, before he quit being a linguist professor
in Georgia and moved to a cabin in Florida and now complains about les
youths. I didn't watch other Linux-related channels until more
recently with David Wilson's System Crafters for Emacs and Guix (more
on this at the very end, it's a recent change that fundamentally
changed how I do stuff, this will come up a lot).&lt;/p&gt;&lt;p&gt;This put me on the path of both distro-hopping and
WM-hopping. Reminder that I started this journey when I was 12
(now 21) so I had lots of time on my hands. I started with Parabola
and then switched to Arch shortly after for wifi drivers, then setup
Gentoo through reading its handbook to learn about Linux more, and
then later did a full LFS+BLFS twice, then CRUX, Sabotage, KISS,
Alpine, and then switched between them depending on what little thing
annoyed me at the time. Of these, CRUX really made me feel at &amp;quot;home&amp;quot;
with giving me just enough packages for a minimal base and I liked
being a package maintainer for a short while. I found CRUX through
z3bra on the nixers.net forum talking about the differences between it
and Gentoo. Sabotage (the way it did stuff was unique and interesting
to me at the time but only used for one hop) and KISS (what I wished
CRUX was but because it didn't have the drunk tux mascot I didn't use
it much beyond a couple hops) from people on IRC and XMPP. I learned a
lot about system administration and writing my own packages through
distro-hopping. Eventually, the choices converged to Arch (if I wanted
something easy), CRUX (because it made me feel fuzzy inside), Gentoo
(USE flags put me further on the path of purism, not as much need to
maintain so many packages in my overlay unlike CRUX). I later also
found out about the BSDs in my goal of more minimalism and purism. It
being direct descendants of the venerable Bell Labs's Research
UNIX. FreeBSD was what I wished Linux was and I liked its better
documentation and integrated first-class ZFS support. OpenBSD I loved
for its documentation, focus on security above all else, tight-knit
and knowledgeable mature community (unlike with most of Linux),
package management feeling more similar to CRUX, mascot, and being
Canadian (patriotism I guess). Among the OSes I used the longest
without hopping, CRUX, Gentoo, and OpenBSD are the ones I really
used. I usually switched away from CRUX due to power user burnout from
maintaining packages for adding and making them more minimal, same
from Gentoo but to lesser extent due to many other overlays, and from
OpenBSD for Windows virtualization at the time and gaming (though I
did find out more about FOSS engines and did more retro emulation
under OpenBSD). I also had a phase in high school grade 12 with
plan9/9front. Really loved the simplicity and elegance of it. Its
windowing manager rio/8.5 and acme editor also showed me that mice
aren't an inherently bad thing for computer use when designed
properly. I couldn't go further with dailying it because my i219-v and
r8168 driver wasn't working properly even after I tried patching it
with my then meager C skills. Later in college around junior year, I
was peer pressured into NixOS from my functional programming
friends. I liked the idea behind it and it made systemd somewhat
usable, but I disliked the special snowflake DSL (they should have
used something else as a base like Haskell or anything else they took
inspiration from) and it liked pulling in all sorts of transient
dependencies, the exact opposite of what I wanted in Linux since I
started using it years ago. What really put me off more than transient
deps was poor and inconsistent documentation (like pretty much nothing
about flakes: there was a disconnect in documentation of what the
broader community used and what upstream deemed stable). But, it had
advantages like helping organize my system+home configs and dotfiles
all in one place which was very nice. So I briefly tried Guix, but
once again without knowing its language was hard to use and also
trying to mould my existing suckless+vi non-emacs worklow into it was
hard at the time. So I once again switched back and forth between
Arch, CRUX, Gentoo, and NixOS despite their shortcomings I detailed
earlier: my power user burnout or getting bored. Continuing later for
recent switch to Guix+Emacs.&lt;/p&gt;&lt;p&gt;So now about window manager choice. I started with i3 since that's
what Luke Smith used and I was curious about how a keyboard-only
workflow would look like. It's also what the most popular WM on
r/unixporn was. Back in the day when pretty much every post there was
either a close-to-default i3-gaps setup or bspwm instead of sway and
hyprland now. I then switched to bspwm because it was more minimal and
UNIX philosophy like where it split keybind handling into separate
program sxhkd. I also used herbstluftwm, the manual tiling was
nice. Then comes dwm, my main WM of choice for a long time since it
blended minimalism with functionality and also by suckless so was
elegant. However, I still did end up switching to spectrwm since it
looked similar visually but I found it easier to config. According to
my screenshots though, that didn't last for long. I think I only
stayed on it for a few months before switching back to dwm. I did
briefly try out sway to see if Wayland was really all that great, but
ultimately switched back to dwm again, and only really used it when I
felt too lazy to setup Xorg on a new system and not using my
pre-existing configs. Again, continuing later for my recent switch to
Guix+Emacs.&lt;/p&gt;&lt;p&gt;Missing what editor I used is criminal for this kind of topic. Going
back to before Linux, I used Eclipse for Java and Notepad++ for
regular stuff, and then I think I used Atom for a brief period of time
(VS Code wasn't out yet or something in 2016/17). I didn't do much
programming back then, partly because getting dependencies and
compiling anything is a pain if my experience with compiling aseprite
(sprite editor with cmake buildsystem) was anything to go off of. When
I started using Linux, I also started with Vim. Programs like it were
what got me to switch in the first place. Turns out learning your
tools is important (and that comes up a lot). Even back then I didn't
like using much of the pre-made configs, they were bloated and harder
to reason about as a new user since defaults were changed. Also had a
brief stint with Emacs, but not knowing basic Lisp made it hard to
&amp;quot;know&amp;quot; it and it was bloated (common joke is it's an operating system
that lacks a good editor) and also not optimized on Windows (same
config from Linux) where it was just noticeably more sluggish on muh
gamin' laptop when running Win10 instead of like Arch or Gentoo. After
learning Vim enough like how to move easily and basic ex commands, I
of course wanted more minimalism. Had a nice time with vis and nvi,
used them for a while. In my plan9 phase, I used acme briefly and
learned a lot about sam through its manual, which also taught me how
to use ed the standard text editor, which also taught me (basic) regex
which I can't overstate how much better it made editing files in
addition to vi motions.&lt;/p&gt;&lt;p&gt;Finally, my current workflow and choices. It's now all based around
Lisp and Scheme because they make me feel all fuzzy inside when
learning it, just like I did when learning to use Linux in middle
school and how to program in C. The propaganda that got me into using
this now was when I heard about the Lispy Gopher Show when I was
briefly on the Fediverse through Prahou (the author/artist of
unix_surrealism from analog_nowhere). While I did briefly use Emacs in
the past, I did not learn it properly. As in, not knowing Lisp meant I
needed to look a lot of things up instead of just writing them
myself. Technically with the vi-like editors, I also didn't write
scripts in them nor needed to touch their config due to how minimal
they already were by default and that regex+vi-motions going a long
way and there being packages to bridge the remaining gap for
integrations. But with Emacs, the parentheses I guess made it more
daunting or something. This sort of mirrors what I did when I first
got into Linux (well Arch) as I read a lot through its wiki. But
compared to just regular system administration where you run a bunch
of commands or even making your own distro via LFS, I guess properly
learning an editor and its programming language was too much for me at
the time. For new Emacs users, it was also recommended a lot that they
use a pre-made opinionated config like Doom-Emacs or Spacemacs (neovim
has a similar situation) and that really put me off since that felt
like I would be learning instead how those work instead of the editor
itself. Now that I know some functional programming through OCaml and
to some extent Nix from a couple summers ago, learning Lisp and Scheme
was actually much easier. Compared to using a vi-like editor, using
Emacs seemed nicer for working with these kinds of languages since I
can selectively execute parts of a program, simliar to something like
Jupyter notebook without it being so web- and Python-focused and being
an inefficient use of system resources. The main reason why I did not
want to use Emacs previously was that it seemed like a monolithic
kitchen sink of everything. I think it being described as an operating
system within an operating system isn't too much of an inaccurate
description. However, compared to Linux and Xorg proper where I tried
following minimalism and the UNIX philosophy as much as I could
(i.e. small, minimal, self-contained programs for a specific task),
there was not much cohesion between them. At most, they'll have
vi-keybinds and happen to use ncurses but the layout of everything
looks different and configured with different syntaxes (e.g. mutt the
mail client looks different from newsboat the RSS reader and different
from lynx and links2). Meanwhile with Emacs, yes there are multiple
packages and yes I'm not necessarily using POSIX shell scripts for
connecting things together, but Lisp is a more powerful language than
something like shell that depends on other programs (written in other
languages) to do even simple stuff. With Emacs, I now have everything
integrated into a single program, everything properly goes through
text-based buffers, and as a result is even easier to integrate around
because everything is primarily text. Also previously, some popular
packages that people use also seemed like it may have contributed to
Emacs's bloat in my point of view at that time like /needing/
something like ivy or helm. There's better packages available that do
the same thing now, like orderless (for fuzzy searching) combined with
vertico (for the vertical complete menu that ivy gave), and consult
for better autocomplete for various existing Emacs functions, among
other stuff. Now it's my main editor since like last summerish, and I
configured it to be my editor, Scheme programming via Geiser, Lisp via
just Emacs (since for now I'm mainly using Emacs Lisp and not Common
Lisp+SLIME) and using the eval-* functions a lot (for both Lisp and
Scheme), document editing and task planning and habit tracking through
org-mode (some people use Emacs just for org-mode since it's much more
than just a markup language), email through notmuch+mbsync+msmtp
(stayed pretty much the same except I'm using notmuch.el directly
instead of through like mutt or aerc), RSS through elfeed, browsing
primarily through eww (though I have to use qutebrowser for js-heavy
sites like Canvas), document viewing through pdftools and docview
instead of through mupdf (though for some larger PDFs like textbooks,
mupdf is just faster but not needed for most of my PDFs), backgrounded
programs through dtach and its associated emacs management package
instead of tmux, gptel for interacting with my local LLMs running on
my desktop through vllm, and finally as my X window manager via EXWM
instead of dwm or any other WM I used to use in the past. Works well
when playing games too like Elite Dangerous, Emacs doesn't get in the
way.&lt;/p&gt;&lt;p&gt;I know this was very ramble-y. I just braindumped since there's so
much to cover and a lot of stuff to remember over the past 9 years
that I couldn't think in a more organized way without having all of it
written down. I probably missed some stuff, so any questions?&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;In the actual presentation, I missed mentioning my peripherals (a
Ferris Sweep with Kailh Choc Ambient Nocturnal switches for laptop,
Corne with Zealios Zilent v2 switches for my desktop, and a Ploopy
Adept as my mouse for both) as well since that's also different from
how most people use it, as well as my current project &amp;quot;X380&amp;quot;, a modded
stripped-down X280 (I'll make a post when it's more finalized after
fixing my 3D printer).&lt;/p&gt;</content></entry><entry><title>CrewCTF2024 misc/Sniff Writeup</title><id>https://vineetk.net/crewctf2024-miscsniff-writeup.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2024-08-04T12:00:00Z</updated><link href="https://vineetk.net/crewctf2024-miscsniff-writeup.html" rel="alternate" /><content type="html">&lt;h2&gt;Challenge&lt;/h2&gt;&lt;h3&gt;Description&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;I came across this mysterious device. So I hooked up my logic analyzer
and recorded somebody using it. (&lt;code&gt;capture.sol&lt;/code&gt;)
This challenge has two flags in the &lt;code&gt;flag{}&lt;/code&gt; format&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The first (easier) is the password that was typed on the keyboard.&lt;/li&gt;&lt;li&gt;The second (significantly harder) is what was display on the screen after the password was entered.&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;&lt;h3&gt;Images&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/device.jpg&quot; alt=&quot;Device&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/everything.jpg&quot; alt=&quot;Everything&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/pi1.jpg&quot; alt=&quot;Raspberry Pi first&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/pi2.jpg&quot; alt=&quot;Raspberry Pi second&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic1.jpg&quot; alt=&quot;Logic Analyzer first&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2.jpg&quot; alt=&quot;Logic Analyzer second&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/display1.jpg&quot; alt=&quot;Display first&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/display2.jpg&quot; alt=&quot;Display second&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Instead of sleeping, I made the mistake^Wwise decision of looking at my
Discord notification that said there was a hardware challenge in this CTF. It
just so happened that there it was using an ATmega-powered keyboard and an
e-paper screen, and it almost seemed like a coincidence since I was designing
my own keyboard and wanted to interface with an e-paper screen in the near
future. This seemed like a great learning opportunity so I started working on
the two-part challenge.&lt;/p&gt;&lt;p&gt;There were a few files inside the &lt;code&gt;dist.zip&lt;/code&gt;, with the most
interesting one being capture.sal which was technically a zip but actually
the analyzer file for Salae. Sadly it seemed to need their proprietary
program to open.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2_main.png&quot; alt=&quot;Screenshot of Saleae Logic 2, the program used for viewing the dump.&quot; /&gt;&lt;/p&gt;&lt;p&gt;The first thing I did was figuring out what each channel was connected to
and what it meant. It seemed that the keyboard and display were controlled by
the Raspberry Pi which then seemed to go to the logic analyzer. So I looked
at what each channel was connected to and based on its connected pin on the
Pi, I found its function via pinout.xyz. I ended up with this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Channel 0: P03  I2C SDA
Channel 1: P05  I2C SCL
Channel 2: P11  GPIO 17 (busy)
Channel 3: P13  GPIO 27 (reset)
Channel 4: P15  GPIO 22 (data/command)
Channel 5: P21  MOSI
Channel 6: P23  SPI0 SCLK
Channel 7: P24  SPI0 CE0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/rpi_pinout.png&quot; alt=&quot;Raspberry Pi Pinout&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Part 1&lt;/h2&gt;&lt;p&gt;In Logic 2, I opened the I2C analyzer and outputted the dump in the
terminal tab into a file&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2_i2c_1.png&quot; alt=&quot;I2C analyzer screenshot in Logic 2 in the Terminal view.&quot; /&gt;
&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2_i2c_2.png&quot; alt=&quot;I2C analyzer screenshot in Logic 2 in the Data Table view.&quot; /&gt;
It seemed there was a lot of NUL bytes being sent, probably indicating
that there wasn’t anything during that cycle, and a few seconds later there
were also some other different bytes with NUL and some &lt;code&gt;0x01&lt;/code&gt;
bytes in between, and these seemed to be printable ASCII.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;read to 0x5F ack data: 0x01
read to 0x5F ack data: 0x01
read to 0x5F ack data: 0x66
read to 0x5F ack data: 0x6c
read to 0x5F ack data: 0x61
read to 0x5F ack data: 0x67
read to 0x5F ack data: 0x7b
read to 0x5F ack data: 0x37
read to 0x5F ack data: 0x01
read to 0x5F ack data: 0x31
read to 0x5F ack data: 0x37
read to 0x5F ack data: 0x66
read to 0x5F ack data: 0x37
read to 0x5F ack data: 0x35
read to 0x5F ack data: 0x01
read to 0x5F ack data: 0x33
read to 0x5F ack data: 0x32
read to 0x5F ack data: 0x7d
read to 0x5F ack data: 0x01
read to 0x5F ack data: 0x0d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Filtering out the &lt;code&gt;0x00&lt;/code&gt; and &lt;code&gt;0x01&lt;/code&gt; data bytes and
converting to ASCII results in &lt;code&gt;flag{717f7532}&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Part 2&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2_spi.png&quot; alt=&quot;Logic 2 SPI analyzer output.&quot; /&gt;&lt;/p&gt;&lt;p&gt;I first outputted the SPI dump from the analyzer into a file and kept only
the &lt;code&gt;MOSI&lt;/code&gt; and &lt;code&gt;MISO&lt;/code&gt; columns.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Time [s],Packet ID,MOSI,MISO
4.108880200000000,0,0x12,0x00
5.109988000000000,0,0x01,0x00
5.110044320000000,0,0xF9,0xFF
5.110062800000000,0,0x00,0xFF
5.110081280000000,0,0x00,0xFF
5.110125600000000,0,0x3A,0x00
5.110167440000000,0,0x1B,0xFF
5.110210120000000,0,0x3B,0x00
5.110251760000000,0,0x0B,0xFF&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To actually understand what’s going on, I couldn’t find any proper
documentation initially. There wasn’t even a proper datasheet on DigiKey; the
“datasheet” was just a summary of the product.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/digikey_datasheet.png&quot; alt=&quot;DigiKey “datasheet”&quot; /&gt;&lt;/p&gt;&lt;p&gt;Then I found the &lt;a href=&quot;https://github.com/pimoroni/inky&quot;&gt;Python library
source from Pimoroni&lt;/a&gt; of their epaper screens, which is probably what was
used to make this challenge.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def setup(self):
  &amp;quot;&amp;quot;&amp;quot;Set up Inky GPIO and reset display.&amp;quot;&amp;quot;&amp;quot;
  if not self._gpio_setup:
      if self._gpio is None:
          try:
              import RPi.GPIO as GPIO
              self._gpio = GPIO
          except ImportError:
              raise ImportError('This library requires the RPi.GPIO module\nInstall with: sudo apt install python-rpi.gpio')
      self._gpio.setmode(self._gpio.BCM)
      self._gpio.setwarnings(False)
      self._gpio.setup(self.dc_pin, self._gpio.OUT, initial=self._gpio.LOW, pull_up_down=self._gpio.PUD_OFF)
      self._gpio.setup(self.reset_pin, self._gpio.OUT, initial=self._gpio.HIGH, pull_up_down=self._gpio.PUD_OFF)
      self._gpio.setup(self.busy_pin, self._gpio.IN, pull_up_down=self._gpio.PUD_OFF)

      if self._spi_bus is None:
          import spidev
          self._spi_bus = spidev.SpiDev()

      self._spi_bus.open(0, self.cs_pin)
      self._spi_bus.max_speed_hz = 488000

      self._gpio_setup = True

  self._gpio.output(self.reset_pin, self._gpio.LOW)
  time.sleep(0.5)
  self._gpio.output(self.reset_pin, self._gpio.HIGH)
  time.sleep(0.5)

  self._send_command(0x12)  # Soft Reset
  time.sleep(1.0)
  self._busy_wait()

def _update(self, buf_a, buf_b, busy_wait=True):
  &amp;quot;&amp;quot;&amp;quot;Update display.

  Dispatches display update to correct driver.

  :param buf_a: Black/White pixels
  :param buf_b: Yellow/Red pixels

  &amp;quot;&amp;quot;&amp;quot;
  self.setup()

  self._send_command(ssd1608.DRIVER_CONTROL, [self.rows - 1, (self.rows - 1) &amp;gt;&amp;gt; 8, 0x00])
  # Set dummy line period
  self._send_command(ssd1608.WRITE_DUMMY, [0x1B])
  # Set Line Width
  self._send_command(ssd1608.WRITE_GATELINE, [0x0B])
  # Data entry squence (scan direction leftward and downward)
  self._send_command(ssd1608.DATA_MODE, [0x03])
  # Set ram X start and end position
  xposBuf = [0x00, self.cols // 8 - 1]
  self._send_command(ssd1608.SET_RAMXPOS, xposBuf)
  # Set ram Y start and end position
  yposBuf = [0x00, 0x00, (self.rows - 1) &amp;amp; 0xFF, (self.rows - 1) &amp;gt;&amp;gt; 8]
  self._send_command(ssd1608.SET_RAMYPOS, yposBuf)
  # VCOM Voltage
  self._send_command(ssd1608.WRITE_VCOM, [0x70])
  # Write LUT DATA
  self._send_command(ssd1608.WRITE_LUT, self._luts[self.lut])

  if self.border_colour == self.BLACK:
      self._send_command(ssd1608.WRITE_BORDER, 0b00000000)
      # GS Transition + Waveform 00 + GSA 0 + GSB 0
  elif self.border_colour == self.RED and self.colour == 'red':
      self._send_command(ssd1608.WRITE_BORDER, 0b00000110)
      # GS Transition + Waveform 01 + GSA 1 + GSB 0
  elif self.border_colour == self.YELLOW and self.colour == 'yellow':
      self._send_command(ssd1608.WRITE_BORDER, 0b00001111)
      # GS Transition + Waveform 11 + GSA 1 + GSB 1
  elif self.border_colour == self.WHITE:
      self._send_command(ssd1608.WRITE_BORDER, 0b00000001)
      # GS Transition + Waveform 00 + GSA 0 + GSB 1

  # Set RAM address to 0, 0
  self._send_command(ssd1608.SET_RAMXCOUNT, [0x00])
  self._send_command(ssd1608.SET_RAMYCOUNT, [0x00, 0x00])

  for data in ((ssd1608.WRITE_RAM, buf_a), (ssd1608.WRITE_ALTRAM, buf_b)):
      cmd, buf = data
      self._send_command(cmd, buf)

  self._busy_wait()
  self._send_command(ssd1608.MASTER_ACTIVATE)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It was also communicating over SPI which seemed to indicate that this was
the proper library. Then I looked at the &lt;code&gt;setup()&lt;/code&gt; and
&lt;code&gt;_update()&lt;/code&gt; functions in
&lt;code&gt;library/inky/inky_ssd1608.py&lt;/code&gt;, and the SPI commands that were
sent in the analyzed dump log matched exactly, including each byte of the LUT
table.&lt;/p&gt;&lt;p&gt;All the SPI commands used in the library are used with named constants
that are defined &lt;code&gt;library/inky/ssd1608.py&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;quot;&amp;quot;&amp;quot;Constants for SSD1608 driver IC.&amp;quot;&amp;quot;&amp;quot;
DRIVER_CONTROL = 0x01
GATE_VOLTAGE = 0x03
SOURCE_VOLTAGE = 0x04
DISPLAY_CONTROL = 0x07
NON_OVERLAP = 0x0B
BOOSTER_SOFT_START = 0x0C
GATE_SCAN_START = 0x0F
DEEP_SLEEP = 0x10
DATA_MODE = 0x11
SW_RESET = 0x12
TEMP_WRITE = 0x1A
TEMP_READ = 0x1B
TEMP_CONTROL = 0x1C
TEMP_LOAD = 0x1D
MASTER_ACTIVATE = 0x20
DISP_CTRL1 = 0x21
DISP_CTRL2 = 0x22
WRITE_RAM = 0x24
WRITE_ALTRAM = 0x26
READ_RAM = 0x25
VCOM_SENSE = 0x28
VCOM_DURATION = 0x29
WRITE_VCOM = 0x2C
READ_OTP = 0x2D
WRITE_LUT = 0x32
WRITE_DUMMY = 0x3A
WRITE_GATELINE = 0x3B
WRITE_BORDER = 0x3C
SET_RAMXPOS = 0x44
SET_RAMYPOS = 0x45
SET_RAMXCOUNT = 0x4E
SET_RAMYCOUNT = 0x4F
NOP = 0xFF&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I then noticed that there was a long string of bytes being sent after a
&lt;code&gt;0x24&lt;/code&gt; which in the library indicated that it was the memory
buffer for the black/white channel ending with a &lt;code&gt;0x00&lt;/code&gt;
&lt;code&gt;MISO&lt;/code&gt;, with the yellow/red channel afterward with a
&lt;code&gt;0x26&lt;/code&gt; &lt;code&gt;MOSI&lt;/code&gt; and also ended with &lt;code&gt;0x00&lt;/code&gt;
&lt;code&gt;MISO&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;...
0x19,0xFF
0x01,0xFF
0x00,0xFF
0x3C,0x00
0x01,0xFF
0x4E,0x00
0x00,0xFF
0x4F,0x00
0x00,0xFF
0x00,0xFF
0x24,0x00
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
0xFF,0xFF
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There also seemed to be two different updates at around 5 seconds and 70
seconds.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/logic2_spi_update_times.png&quot; alt=&quot;Logic 2 SPI analyzer with 0x24 searched to show when each screen update started.&quot; /&gt;&lt;/p&gt;&lt;p&gt;However, the number of bytes written was 4250, which wasn’t the 3812.5 or
2756 bytes I was expecting. This wasn’t divisible by 250 nor 122 and so I was
stuck for a long time. Looking through the library source for more than an
hour with my tired self didn’t help much either. As a last ditch attempt, I
tried converting the raw bytes into an image via Pillow, I used the
&lt;code&gt;L&lt;/code&gt; mode (&lt;code&gt;8bpp&lt;/code&gt;) and just got an uninteresting garbled
image.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/failed_flag.png&quot; alt=&quot;Garbled image reflecting my sadness at being unable to get the flag.&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Part 2 Part 2: Electric Boogaloo&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/discord1.png&quot; alt=&quot;Joey asking in the CTF’s Discord about the challenge.&quot; /&gt;
&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/discord2.png&quot; alt=&quot;Me being surprised in the Discord for my stupidity that I blame on being tired.&quot; /&gt;&lt;/p&gt;&lt;p&gt;After waking up and working on the CTF after it ended, my partner asked on
the Discord and found some interesting very helpful information. It turned
out the image was a packed 1bpp image. This meant that each byte in the
memory framebuffer contained 8 pixels (8 bits / 1 bits per pixel = 8
pixels).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# under show()
buf_a = numpy.packbits(numpy.where(region == BLACK, 0, 1)).tolist()
buf_b = numpy.packbits(numpy.where(region == RED, 1, 0)).tolist()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the Python source, this was shown by &lt;code&gt;buf_a&lt;/code&gt; and
&lt;code&gt;buf_b&lt;/code&gt; being packed bits of 1bpp via NumPy. I don’t know much
about NumPy, so this was a skill issue as I initially assumed it was a
complicated way of saving all the black and red pixels into lists. This is a
good reminder that the documentation should be checked for all unfamiliar
functions instead of naively assuming what they seem to do.&lt;/p&gt;&lt;p&gt;Also in addition to the screen being rotated by 90 degrees, the vertical
resolution is actually 136 pixels and not 120 according to the driver.&lt;/p&gt;&lt;p&gt;Knowing all this solved all my problems as 4250 * 8 was indeed divisible
by 250 and the actual vertical resolution 136.&lt;/p&gt;&lt;p&gt;All I had to do was change the Pillow mode when converting the bytes to an
image from &lt;code&gt;L&lt;/code&gt; (8bpp) to &lt;code&gt;1&lt;/code&gt; (1bpp) and the (rotated)
resolution from &lt;code&gt;(250, 16)&lt;/code&gt; to &lt;code&gt;(136, 250)&lt;/code&gt; and got an
actual image.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/converted_eink_image.png&quot; alt=&quot;Extracted image of the display showing the initial message shown in the challenge description.&quot; /&gt;&lt;/p&gt;&lt;p&gt;I used the first updated bytes which was the screen shown in the
challenge’s screenshots. Using the second update’s bytes gave half of the
flag in the black/white channel and the other half in the yellow/red
channel.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/flag1.png&quot; alt=&quot;The flag in the black/white channel.&quot; /&gt;
&lt;img src=&quot;https://vineetk.net/images/crewctf24_sniff/flag2.png&quot; alt=&quot;The flag in the yellow/red channel.&quot; /&gt;&lt;/p&gt;&lt;p&gt;Each character index in both channels seemed to alternate, so the actual
flag was &lt;code&gt;flag{ec9cf2b7}&lt;/code&gt;. After I finished writing this writeup
and seeing the two images side-by-side, they probably could’ve been overlayed
after one’s colours are inverted, and is probably what was meant by
“stitching” the channels together.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;This was my most favourite CTF challenge by far and I learned a lot,
especially about stuff I wanted to learn like how SPI e-paper screens work
and not be lost with I2C. I am personally now curious whether the SPI screens
can be interfaced directly with the MCU instead of going through an
intermediate daughterboard/HAT and how different the protocol for parallel
screens are since they’re much faster and use more pins.&lt;/p&gt;</content></entry><entry><title>Fixing bitmap font fallbacks in the st terminal</title><id>https://vineetk.net/fixing-bitmap-font-fallbacks-in-the-st-terminal.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2023-11-24T12:00:00Z</updated><link href="https://vineetk.net/fixing-bitmap-font-fallbacks-in-the-st-terminal.html" rel="alternate" /><content type="html">&lt;p&gt;tldr, change FC_SCALABLE in x.c from 1 to 0. (comes from the font2 patch)&lt;/p&gt;&lt;p&gt;For some context, I have been using xterm for a long while when I'm on OpenBSD
since it is included by default in Xenocara with Terminus as my default font,
and the main reason why I did not use st again was that my bitmap fallback font
for CJK was not loading. Instead, I get an ugly sans-serif scaled font that
looked very out of place in my otherwise clean and crisp bitmap terminal.&lt;/p&gt;&lt;p&gt;Yes, I did make sure that the font2 patch for st was applied correctly.&lt;/p&gt;&lt;p&gt;The X11 font string for reference is Fixed:
-misc-fixed-medium-r-normal-ja-18-120-100-100-c-180-iso10646-1&lt;/p&gt;&lt;p&gt;It also didn't help that fontconfig was unable to find the font either no
matter how much I looked for it with fc-list and fc-match. The weirder thing is
that when I installed GNU Unifont to my fonts directory, fontconfig was able to
find it and st loaded it (I put a printf in the xloadfonts() function in x.c),
but the same old ugly scaled font was still being shown for CJK. The weirderer
thing was that Unifont was rendering just fine when being used as the main font
instead of in font2.&lt;/p&gt;&lt;p&gt;I thought to myself why this was happening and wasn't able to find out, until I
reread the font loading portion in x.c's xloadsparefonts() function that came
part of the font2 patch.&lt;/p&gt;&lt;p&gt;It had set the FC_SCALABLE boolean to 1 (true). That explained why the fallback
font rendered fine as the main font and not fallback. Setting that boolean to
0 (false) fixed my fallback font not matching issue, and now I have clean and
crisp looking text that I can read more easily.&lt;/p&gt;&lt;p&gt;I already disliked fontconfig, freetype, xft, and friends (don't get me started
on pango and harfbuzz), but this incident made me dislike it further.&lt;/p&gt;</content></entry><entry><title>DEADFACE CTF 2023 Shattered Dreams Writeup</title><id>https://vineetk.net/deadface-ctf-2023-shattered-dreams-writeup.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2023-10-26T12:00:00Z</updated><link href="https://vineetk.net/deadface-ctf-2023-shattered-dreams-writeup.html" rel="alternate" /><content type="html">&lt;blockquote&gt;&lt;p&gt;DEADFACE is on the brink of selling a patient's credit card details from the
Aurora database to a dark web buyer. Investigate Ghost Town for potential leads
on the victim's identity.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;A huge hint was dropped immediately, so I went to Ghost Town to find a thread
titled &amp;quot;We got a potential buyer&amp;quot;.&lt;/p&gt;&lt;p&gt;The flag's format is &lt;code&gt;flag{Firstname Lastname}&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;lilith, the original poster of the thread, said the victim's SHA1 hash we need
to look for is &amp;quot;911d1fc5930fa5025dbc2d3953c94de9e4773584&amp;quot; and showed how she
calculated that, including the (lack of) delimeter.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://vineetk.net/images/deadface23-shattered_dreams-forum.png&quot; alt=&quot;https://ghosttown.deadface.io/t/dark-web-dumps-anyone/101&quot; /&gt;&lt;/p&gt;&lt;p&gt;So, we can easily bruteforce getting this SHA1 hash by repeating what lilith
did.&lt;/p&gt;&lt;p&gt;The first three fields (card number, expiration, CCV) are values from the
billing table and the rest of the fields is all the fields in the patient
table.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `billing` (
  `billing_id` int(11) NOT NULL AUTO_INCREMENT,
  `patient_id` int(11) NOT NULL,
  `credit_type_id` int(11) NOT NULL,
  `card_num` varchar(24) NOT NULL,
  `exp` varchar(8) NOT NULL,
  `ccv` varchar(4) NOT NULL,
  PRIMARY KEY (`billing_id`),
  UNIQUE KEY `card_num` (`card_num`),
  KEY `fk_billing_patient_id` (`patient_id`),
  KEY `fk_billing_credit_type_id` (`credit_type_id`),
  CONSTRAINT `fk_billing_credit_type_id` FOREIGN KEY (`credit_type_id`) REFERENCES `credit_types` (`credit_type_id`) ON DELETE CASCADE,
  CONSTRAINT `fk_billing_patient_id` FOREIGN KEY (`patient_id`) REFERENCES `patients` (`patient_id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=14443 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
...
CREATE TABLE `patients` (
  `patient_id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(32) NOT NULL,
  `last_name` varchar(64) NOT NULL,
  `middle` varchar(8) DEFAULT NULL,
  `sex` varchar(8) NOT NULL,
  `email` varchar(128) NOT NULL,
  `street` varchar(64) NOT NULL,
  `city` varchar(64) NOT NULL,
  `state` varchar(8) NOT NULL,
  `zip` varchar(12) NOT NULL,
  `dob` date NOT NULL,
  PRIMARY KEY (`patient_id`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=18542 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since there are no delimeters, they can just be concatenated with each other
and then piped to sha1. The difficult part I had was properly concatenating
those values because I was not able to read the MySQL dump with sqlite3 nor
mariadb.&lt;/p&gt;&lt;p&gt;I noticed that each of the rows that were inserted into the tables were
delimited by a comma, similar to CSV.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;INSERT INTO `patients` VALUES (8151,'Lorrayne','Covey','E','Female','lcovey0@wunderground.com','40411 Old Shore Street','Houston','TX','77201','1985-02-08'),(8152,'Eddy','Omand','S','Female','eomand1@mysql.com','454 Pine View Alley','Columbus','OH','43226','1959-09-29'),(8153,'Renard','Berre','O','Male','rberre2@friendfeed.com','3496 Merrick Center','Pittsburgh','PA','15235','1978-12-05'),(8154,'Galven','Nardrup','M','Male','gnardrup3@mac.com','0 American Road','Denver','CO','80241','1984-04-09'), ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So I was able to easily convert it into a CSV with the following command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep 'INSERT INTO `patients`' aurora.sql \
    | sed 's/^INSERT[^(]*//' \
    | awk '{gsub(/,\(/, &amp;quot;\n&amp;quot;); gsub(/\)*/, &amp;quot;&amp;quot;); print}'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Part of the output is now:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;...18532,'Becka','Hurlin','T','Female','bhurlin80d@yolasite.com','58 Amoth Way','Ventura','CA','93005','1953-11-14'
18533,'Aluin','Horwell','O','Male','ahorwell80e@cbc.ca','3 Oriole Terrace','Miami','FL','33190','1984-06-24'
18534,'Glennis','Walder','R','Female','gwalder80f@cnet.com','966 Packers Hill','Topeka','KS','66617','1950-01-21'
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One problem I had when I used tr to replace ( with \n was that one of the names
had () in their name for some reason, which messed up the concatenating of the
two files to get the hash. I originally just manually edited it, but the above
with awk is cleaner. The same goes with the 'INSERT INTO ...' part messing things up for the same reason. I didn't save my ~/.ksh_history file with the
commands I ran, so this is a non-ugly version I remade with more awk.&lt;/p&gt;&lt;p&gt;To properly concatenate the data fields, the comma and single quotes should
also be removed, which tr can be used for unlike before.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep 'INSERT INTO `patients`' aurora.sql \
    | sed 's/^INSERT[^(]*//' \
    | awk '{gsub(/,\(/, &amp;quot;\n&amp;quot;); gsub(/\)*/, &amp;quot;&amp;quot;); print}' \
    | tr -d &amp;quot;',&amp;quot; &amp;gt;patients_.txt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Part of the output prior to being written to a file is now:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;18527TannerMasselinAMaletmasselin808@google.es10080 Reindahl CourtBoca RatonFL334871957-08-15
18528MerrelYeudeDMalemyeude809@ca.gov7934 Katie PassSaint PaulMN551881951-04-07
18529JeffVan BaarenMMalejvanbaaren80a@sphinn.com58097 Autumn Leaf DriveNew OrleansLA701421984-01-13&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is written to a file so that it can be easy to concatenate both the billing
and patient data by using the paste command.&lt;/p&gt;&lt;p&gt;The next part is doing the same with the billing data. Unlike with the patient
data, only three fields from the billing table is used instead of all, so cut
or awk can be used with the delimeter set to a comma.&lt;/p&gt;&lt;p&gt;Before that, we need to know what index (base 1) the three fields are at. The
credit card number, expiry date, and ccv are fields 4, 5, and 6.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep 'INSERT INTO `billing`' aurora.sql \
    | sed 's/^INSERT[^(]*//' \
    | awk '{gsub(/,\(/, &amp;quot;\n&amp;quot;); gsub(/\)*/, &amp;quot;&amp;quot;); print}' \
    | cut -d, -f4,5,6 \
    | tr -d &amp;quot;',&amp;quot; &amp;gt;billing_.txt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some of the output looks like&lt;/p&gt;&lt;pre&gt;&lt;code&gt;51087507745678202025-01403
50483770976260922023-07242
50483739134688352023-12501&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both tables with the fields have been parsed and saved to two different files, but simply using cat on both the files would simply print the contents of the second file after the first file has finished printing, but we need each line of both the files to be joined together! That's where the paste command comes in. It combines each line of its input files, which is exactly what is required.&lt;/p&gt;&lt;p&gt;These merged lines are then fed into sha1, and finally grep can be used to look for the hash we need and print the victim's name if it shows up.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;target_hash=&amp;quot;911d1fc5930fa5025dbc2d3953c94de9e4773584&amp;quot;

paste billing_.txt patients_.txt | tr -d '\t' \
    | while read line; do
    echo -n &amp;quot;$line&amp;quot; | sha1 \
        | grep &amp;quot;$target_hash&amp;quot; &amp;amp;&amp;amp; echo &amp;quot;$line&amp;quot; &amp;amp;&amp;amp; break
    done&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The outputted line of the victim who had the same hash is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;50483743238485412026-0498316314BertonLuchettiXMalebluchetti6ar@taobao.com39 Meadow Ridge TerraceClevelandOH441251964-10-29&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, the victim is Berton X. Luchetti, and the flag is &lt;code&gt;flag{Berton Luchetti}&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This challenge would probably have been easier if I was able to use proper SQL
commands, but I couldn't do that and standard UNIX tools saved the day. I did
the exact same process of parsing the tables for all the other SQL challenges
and I found it funny that I solved all of them without needing to run a single
SQL command (partly because they didn't load the file).&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><title>BCTF23 crypto/Electronical (medium) Writeup</title><id>https://vineetk.net/bctf23-cryptoelectronical-medium-writeup.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2023-10-26T12:00:00Z</updated><link href="https://vineetk.net/bctf23-cryptoelectronical-medium-writeup.html" rel="alternate" /><content type="html">&lt;blockquote&gt;&lt;p&gt;I do all my ciphering electronically. https://electronical.chall.pwnoh.io/&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;When going to the linked site, you get told to encrypt any message or view the
site's source code. After submitting a message to encrypt, it returns some hex
string.&lt;/p&gt;&lt;p&gt;The source is:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from Crypto.Cipher import AES
from flask import Flask, request, abort, send_file
import math
import os

app = Flask(__name__)

key = os.urandom(32)
flag = os.environ.get('FLAG', 'bctf{fake_flag_fake_flag_fake_flag_fake_flag}')

cipher = AES.new(key, AES.MODE_ECB)

def encrypt(message: str) -&amp;gt; bytes:
    length = math.ceil(len(message) / 16) * 16
    padded = message.encode().ljust(length, b'\0')
    return cipher.encrypt(padded)

def decrypt(msg: str) -&amp;gt; bytes:
    return cipher.decrypt(msg)

@app.get('/encrypt')
def handle_encrypt():
    param = request.args.get('message')

    if not param:
    	return abort(400, &amp;quot;Bad&amp;quot;)
    if not isinstance(param, str):
    	return abort(400, &amp;quot;Bad&amp;quot;)

    print(encrypt(param + flag))

    return encrypt(param + flag).hex()

@app.get('/source')
def handle_source():
    return send_file(__file__, &amp;quot;text/plain&amp;quot;)

@app.get('/')
def handle_home():
    return &amp;quot;&amp;quot;&amp;quot;
    	&amp;lt;style&amp;gt;
    		form {
    			display: flex;
    			flex-direction: column;
    			max-width: 20em;
    			gap: .5em;
    		}

    		input {
    			padding: .4em;
    		}
    	&amp;lt;/style&amp;gt;
    	&amp;lt;form action=&amp;quot;/encrypt&amp;quot;&amp;gt;
    		&amp;lt;h2&amp;gt;&amp;lt;i&amp;gt;ELECTRONICAL&amp;lt;/i&amp;gt;&amp;lt;/h2&amp;gt;
    		&amp;lt;label for=&amp;quot;message&amp;quot;&amp;gt;Message to encrypt:&amp;lt;/label&amp;gt;
    		&amp;lt;input id=&amp;quot;message&amp;quot; name=&amp;quot;message&amp;quot;&amp;gt;&amp;lt;/label&amp;gt;
    		&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot;&amp;gt;
    		&amp;lt;a href=&amp;quot;/source&amp;quot;&amp;gt;Source code&amp;lt;/a&amp;gt;
    	&amp;lt;/form&amp;gt;
    &amp;quot;&amp;quot;&amp;quot;

if __name__ == &amp;quot;__main__&amp;quot;:
    app.run()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It seems that the flag is appended to the user's message and then encrypted with
AES-ECB. The total message is also padded to be a multiple of 16 bytes.&lt;/p&gt;&lt;p&gt;According to Wikipedia, ECB (electronic codebook) works by dividing a message
into blocks of a certain size (like 16 bytes). The problem however is that ECB
doesn't attempt to make any encrypted block unique like by adding a salt or
nonce, so any blocks of data that are identical would also be identical when
encrypted. Wikipedia also has an interesting example of encrypting an image of
Tux and a mountain (on French Wikipedia) with AES.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;images/bctf23_electronical-tux_aes.png&quot; alt=&quot;Tux AES&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;images/bctf23_electronical-mountain_aes.png&quot; alt=&quot;Mountain AES&quot; /&gt;&lt;/p&gt;&lt;p&gt;Through some more searching online, it seems a way to exploit this is with
something called a Chosen Plaintext Attack. Since the message before the flag is
controlled by us the user (attacker?) and the flag is appended to the end, the
provided message can be made in a way that only one byte of the flag needs to be
bruteforced at a time.&lt;/p&gt;&lt;p&gt;Let's say that this is our message: &lt;code&gt;thischallengesucksFLAG{5om3_!mp0r74nt_$3cr37}&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This string is 45 characters, so the server would pad this with 3 \0 characters
to make it evenly divisible by 16 characters.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;b'thischallengesucksFLAG{5om3_!mp0r74nt_$3cr37}\x00\x00\x00'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We know what &amp;quot;thischallengesucks&amp;quot; is, but FLAG and anything else after is
appended by the server and is what we're trying to find.&lt;/p&gt;&lt;p&gt;&amp;quot;thischallengesucks&amp;quot; is 18 characters, but if we send a 15 character string,
then the first block to be encrypted would be &amp;quot;thischallengesu?&amp;quot;, where ? is the
mystery character.&lt;/p&gt;&lt;p&gt;For readability purposes, I'm going to use repeated &amp;quot;0&amp;quot; characters needed
instead of &amp;quot;thischallengesucks&amp;quot;.&lt;/p&gt;&lt;p&gt;When passing &amp;quot;000000000000000?&amp;quot; to the server, a certain hex string would be
returned (newlines every 32 characters not included in original):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;b57189530dacbb9c5707c1cb0b044a34
5377049685bb9553a73e4408565505dd
0c614f69c4749b10f8cbc9c735fd7314
5a9ae527825603a8eb0dba6a0347a4e5&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replacing the ? with any other character would result in only the first row being
changed, like with &amp;quot;000000000000000A&amp;quot;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;b2457a857e82a1d5ad919a4bdaf9133a
7835c84bc75d836fad8ca5fbcec086ff
937cf83a682fa26162a65f2295b2b119
6b398dd6f75e212b1633c5189bdb5689&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the other three blocks remained the same, the last character in the
message being sent simply needs to bruteforced with every printable character
until it results in the same block from ?. In this case, that character would be
F:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;b57189530dacbb9c5707c1cb0b044a34
5377049685bb9553a73e4408565505dd
0c614f69c4749b10f8cbc9c735fd7314
5a9ae527825603a8eb0dba6a0347a4e5&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;(0000000000000, 14 characters long)
5ec61f1209adfeff202edbba28339f83
4f7cc7a4c0c553380874383e93408678
ca91d95b091956edb162da583b51051b
33b91ab14b8807348fc98bf223b4b3a5

(0000000000000FL, 16 characters long)
5ec61f1209adfeff202edbba28339f83
4f7cc7a4c0c553380874383e93408678
ca91d95b091956edb162da583b51051b
33b91ab14b8807348fc98bf223b4b3a5&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then the 0 left pad would be decreased by one character and the process repeats
until the whole block is done. However, a flag usually won't be just 16
characters long. I had some difficulty trying to bruteforce the 17th character
and above because I was prepending and appending the zeros within a single block
(between 0 and 15 padded 0s), but that was among a few other issues I had that
were the result of the message I was sending being in the format of &amp;quot;pad +
known_flag + brute_single_char + pad&amp;quot; where this only worked for the first
block. This did not work later because those messages would have the pad bytes
in the middle of the message, which did not go well.&lt;/p&gt;&lt;p&gt;In the end, I realized that I can check the target block hexstring by sending
only the padded 0 bytes (or anything else of that length) without other
characters and then append my known bytes of the flag and a single other character
to fill that block to brute force that last character.&lt;/p&gt;&lt;p&gt;A visual representation is this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Block size: 8 characters

7 pad, 0 known
XXXXXXX?
XXXXXXXF

6 pad, 1 known
XXXXXX??
XXXXXXFL

5 pad, 2 known
XXXXX???
XXXXXFLA

...

0 pad, 7 known
FLAG{5o?

8 known
FLAG{5om&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This only decrypts the first block, so how I decrypted each additional block was
by prepending another block of pad characters (blocksize - 1) and repeating the
process.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;7 pad, 7 known
XXXXXXXFLAG{5om?
XXXXXXXFLAG{5om3

6 pad, 8 known
XXXXXXFLAG{5om3?
XXXXXXFLAG{5om3_

...

5 pad, 26 known 
XXXXXFLAG{5om3_!mp0r74nt_$3cr37?
XXXXXFLAG{5om3_!mp0r74nt_$3cr37}

...
0 pad, 31 known
FLAG{5om3_!mp0r74nt_$3cr37}\0\0?
FLAG{5om3_!mp0r74nt_$3cr37}\0\0\0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After some automating help with python, I was able to finally get the flag.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Flag: bctf{1_c4n7_b3l13v3_u_f0und_my_c0d3b00k}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;My python file to solve this was:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from requests import get
from requests.utils import quote

# list of characters that will be bruteforced, these are the printable chars
chars = [chr(i) for i in range(ord(' '), ord('~') + 1)]
# nul character is also checked because that's the pad character
chars += '\0'

def encrypt(msg):
    #url = &amp;quot;https://electronical.chall.pwnoh.io/encrypt?message=&amp;quot;
    url = &amp;quot;http://localhost:5000/encrypt?message=&amp;quot;
    return get(url + quote(msg)).content;

def calc_padding_for_known():
    # divided by 2 because each hex byte is 2 characters long
    cur = len(encrypt(&amp;quot;0&amp;quot;)) // 2

    # 16 chosen because that's the padding chosen in app.py on the server
    for i in range(2, 16):
    	tmp = len(encrypt(&amp;quot;0&amp;quot; * i)) // 2
    	if tmp &amp;gt; cur:
    		return tmp, tmp - cur, i - 1
    # shouldn't come here
    return 0,0,0

totalblocks, bs, pad = calc_padding_for_known()

print(f&amp;quot;Block size of padding: {bs}, {pad}&amp;quot;)

# first block are known to not be the flag (is all 0 being encrypted)
# second block is what's being bruteforced
# third block's last character is unknown and being compared with second block
#msg = &amp;quot;0&amp;quot; * (bs + bs - 1) + &amp;quot;a&amp;quot; * 1 + &amp;quot;0&amp;quot; * (bs - 1)
#cur = encrypt(msg)
#print(cur)
flag = &amp;quot;&amp;quot;
curflag = &amp;quot;&amp;quot;

tbs = bs * 2

for j in range(totalblocks // bs):
    for i in range(1, bs + 1):
    	known = &amp;quot;0&amp;quot; * (bs * (1 + j) - len(flag) - 1)
    	msg = known
    	target = encrypt(msg).decode(&amp;quot;utf-8&amp;quot;)

    	print(f&amp;quot;\nNew target message: {msg}&amp;quot;)
    	print(&amp;quot;New target message return:&amp;quot;)
    	print('\n'.join([target[A:A + tbs] for A in range(0, len(target), tbs)]))

    	target = target[tbs * (0 + j):tbs * (1 + j)]
    	print(f&amp;quot;New target block: {target}&amp;quot;)

    	for c in chars:
    		msg = known + flag + c
    		print(f&amp;quot;Current character: {c}&amp;quot;)
    		print(f&amp;quot;Current message: {msg}&amp;quot;)
    		print(f&amp;quot;Target block: {target}&amp;quot;)
    		cur = encrypt(msg).decode(&amp;quot;utf-8&amp;quot;)
    		print('\n'.join([cur[A:A + tbs] for A in range(0, len(cur), tbs)]))

    		print(f&amp;quot;Current block: {cur[:tbs]}&amp;quot;)

    		if (cur[tbs * (0 + j):tbs * (1 + j)] == target):
    			flag += c
    			print(flag)
    			break

    		print(&amp;quot;\n&amp;quot;)
    curflag += flag
print(flag)&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><title>DEADFACE CTF 2023 Host Busters 3 Writeup</title><id>https://vineetk.net/deadface-ctf-2023-host-busters-3-writeup.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2023-10-26T12:00:00Z</updated><link href="https://vineetk.net/deadface-ctf-2023-host-busters-3-writeup.html" rel="alternate" /><content type="html">&lt;blockquote&gt;&lt;p&gt;Continue characterizing the machine. Is there any way you can
escalate to a user that has permissions the vim user does not have? Find
the flag associated with this user.
Submit the flag as &lt;code&gt;flag{flag_here}&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code&gt;vim@ghost404.deadface.io letmevim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You first login to vim, which has vim open. Then you escape from it like you
did in the OverTheWire Bandit challenges with &lt;code&gt;:set shell=bash&lt;/code&gt; and &lt;code&gt;:shell&lt;/code&gt;. Now you have a proper shell over SSH.&lt;/p&gt;&lt;p&gt;The first thing I looked at after mistaking Host Busters 1's key in the home
directory as 3 was look at what other user home directories there were by
running &lt;code&gt;ls /home&lt;/code&gt;. I saw there were a few users, notably &lt;code&gt;gh0st404&lt;/code&gt; and
&lt;code&gt;spookyboi&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;gh0st404&lt;/code&gt;'s user home directory had his OpenSSH private key as
world-readable and in plain sight not in his &lt;code&gt;.ssh&lt;/code&gt; hidden
directory. It being world-readable would have had OpenSSH scream at you, but
them being stupid was good for us.&lt;/p&gt;&lt;p&gt;So, once you use that SSH private key to login as &lt;code&gt;gh0st404&lt;/code&gt;,
you can check the contents of hostbusters3.txt and you got the flag.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cat hostbusters3.txt&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;&amp;quot;This is why you should have come to the Monday meetings for OverTheWire.&amp;quot;
~Joey, FPUSEC President&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://asciinema.org/a/ZhQQwEVwgaqtGCuaqRf6NWu8N&quot;&gt;Here's&lt;/a&gt; an
asciinema of the entire thing in action.&lt;/p&gt;</content></entry><entry><title>CSAW23 rev/Rebug1 Writeup</title><id>https://vineetk.net/csaw23-revrebug1-writeup.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2023-09-28T12:00:00Z</updated><link href="https://vineetk.net/csaw23-revrebug1-writeup.html" rel="alternate" /><content type="html">&lt;blockquote&gt;&lt;p&gt;Can't seem to print out the flag :( Can you figure how to get the flag
with this binary?&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;An innocent looking binary is given that asks for a string:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;./test.out
Enter the String: rptuainadui
that isn't correct, im sorry!&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is part of the rev category (which I think is for reverse
engineering). You could bruteforce this yes, but I found it easier
to put this into a decompiler like the ones on &lt;a href=&quot;https://dogbolt.org/&quot;&gt;DogBolt (Decompiler
Explorer)&lt;/a&gt; to see what it's doing.&lt;/p&gt;&lt;p&gt;Decompiled main function (via angr):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;int main()
{
    char v0;  // [bp-0x448] 
    unsigned int v1;  // [bp-0x41c]
    char v2;  // [bp-0x418]
    char v3;  // [bp-0x408]
    unsigned long long v4;  // [bp-0x18]
    unsigned int v5;  // [bp-0x10]
    unsigned int v6;  // [bp-0xc]
    unsigned long long v8;  // rax

    printf(&amp;quot;Enter the String: &amp;quot;);
    __isoc99_scanf(&amp;quot;%s&amp;quot;, (unsigned int)&amp;amp;v3);
    for (v6 = 0; (&amp;amp;v3)[v6]; v6 += 1);
    if (v6 == 12)
    {
    puts(&amp;quot;that's correct!&amp;quot;);
    v4 = EVP_MD_CTX_new();
    (unsigned int)v8 = EVP_md5();
    EVP_DigestInit_ex(v4, v8, 0x0, v8);
    EVP_DigestUpdate(v4, &amp;quot;12&amp;quot;, 0x2, &amp;quot;12&amp;quot;);
    v1 = 16;
    EVP_DigestFinal_ex(v4, &amp;amp;v2, &amp;amp;v1, &amp;amp;v2);
    EVP_MD_CTX_free(v4);
    for (v5 = 0; v5 &amp;lt;= 15; v5 += 1)
    {
    	sprintf(&amp;amp;(&amp;amp;v0)[2 * v5], &amp;quot;%02x&amp;quot;, (&amp;amp;v2)[v5]);
    }
    printf(&amp;quot;csawctf{%s}\n&amp;quot;, (unsigned int)&amp;amp;v0);
    return 0;
    }
    printf(&amp;quot;that isn't correct, im sorry!&amp;quot;);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This along with the rest of the decompiled binary can't be simply compiled again
as-is because there are a few issues, like the OpenSSL functions being called
having an extra argument added to the end.&lt;/p&gt;&lt;p&gt;When looking at the functions being called, it seems that the flag is just an
md5 of the number 12. The program also seems to give the flag itself if you give
it the character with the ASCII value of 12 (form feed).&lt;/p&gt;&lt;p&gt;The line that that has the data being checksummed is this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;EVP_DigestUpdate(v4, &amp;quot;12&amp;quot;, 0x2, &amp;quot;12&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I did try piping the form feed character via printf to the binary, but it did
not like that, so it seems that the only way to get the flag is through another
way.&lt;/p&gt;&lt;p&gt;While you could just create a very simplified version of the decompiled source
with OpenSSL's crypto library (which is what I did originally), it's much easier
to just pass the number 12 to a pre-installed md5 command (md5 on OpenBSD,
md5sum on Linux).&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ echo -n 12 | md5
c20ad4d76fe97759aa27a0c99bff6710&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This CTF's flags were in the format of csawctf{somethinghere}, as also seen in
the decompiled source, so the actual flag was this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;csawctf{c20ad4d76fe97759aa27a0c99bff6710}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was my first time doing any decompilation of a program, and I think this
was a good start to learn reverse engineering.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><title>Creating a Temporary File Hoster</title><id>https://vineetk.net/creating-a-temporary-file-hoster.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2022-04-27T12:00:00Z</updated><link href="https://vineetk.net/creating-a-temporary-file-hoster.html" rel="alternate" /><content type="html">&lt;p&gt;For the past couple years, whenever I wanted to upload a file, I would
curl the file to &lt;a href=&quot;https://git.qorg11.net/lainsafe.git/&quot;&gt;lainsafe&lt;/a&gt;, &lt;a href=&quot;https://gt.kalli.st/kallist/uploader&quot;&gt;i/u.kalli.st&lt;/a&gt;, and recently &lt;a href=&quot;https://tildegit.org/tildeverse/ttm.sh&quot;&gt;ttm.sh&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Since I want to selfhost, I thought i can just use either of what those
three used. Earlier today though, I realized I could just copy the
file(s) I want to upload via rsync/scp to a public directory that gets
served by an httpd or gopherd.&lt;/p&gt;&lt;p&gt;From what I understand, the previous file hosters had a program running
that read the file that the user uploads to them, does some renaming,
and writes that to a directory that is served. After some time, that
file is deleted. The first part can be handled via rsync/scp like
mentioned previously. For automatic deletion, I recently saw in find's
man page that it can list that haven't been modified via the -mtime
flag, so that can be used with a cron job.&lt;/p&gt;&lt;p&gt;But while thinking of this idea, I got stumped by how to print back the
url to this file that is uploaded since printing the filename as is
appended to its baseurl, there could be spaces and other invalid
unescaped characters which programs trying to download it may not like.&lt;/p&gt;&lt;p&gt;I thought I could just create a separate program for this. However,
doing this seemed more complicated than just copying the file to the
server. So, with the help of awk and some StackExchanging, I've been
able to do it.&lt;/p&gt;&lt;p&gt;&lt;code&gt;upfile.sh&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;#!/bin/sh
urlencode() {
     awk '
BEGIN { for (i = 1; i &amp;lt; 256; i++) hex[sprintf(&amp;quot;%c&amp;quot;, i)] = sprintf(&amp;quot;%%%02X&amp;quot;, i) }
{
     for (i = 1; i &amp;lt;= length($0); i++) {
             c = substr($0, i, 1)
             printf(&amp;quot;%s&amp;quot;, c ~ /^[-._~0-9a-zA-Z]$/ ? c : hex[c])
     }
     printf &amp;quot;\n&amp;quot;
}
'
}

FILE=&amp;quot;$1&amp;quot;
SERVER=&amp;quot;REPLACEME&amp;quot;
BASEURL=&amp;quot;https://u.$SERVER&amp;quot;

[ -z &amp;quot;$1&amp;quot; ] &amp;amp;&amp;amp; exit 1

scp &amp;quot;$FILE&amp;quot; &amp;quot;$SERVER&amp;quot;:files/ || exit 1
printf &amp;quot;%s/&amp;quot; &amp;quot;$BASEURL&amp;quot;
basename &amp;quot;$FILE&amp;quot; | urlencode&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then to purge these files after they become too old (e.g. 3 days), you
can put something like this in a cron job to run daily (replace file
directory):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0 0 * * * find /path/to/dir/ -mtime +3 -exec rm {} \;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can also put this command in /etc/daily.local or /etc/cron/daily,
or whatever file your root crontab's @daily runs (if there is one).&lt;/p&gt;&lt;p&gt;And that's it! The only difficult part that I experienced was encoding
the name of the file and originally did that in C. However, having a
mixed C and shell program just for file uploading didn't sit right with
me. It seems like whenever you're in doubt, you can rely on awk huh.&lt;/p&gt;</content></entry><entry><title>Kobo Clara HD Custom Linux Distro/RootFS</title><id>https://vineetk.net/kobo-clara-hd-custom-linux-distrorootfs.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2021-07-22T12:00:00Z</updated><link href="https://vineetk.net/kobo-clara-hd-custom-linux-distrorootfs.html" rel="alternate" /><content type="html">&lt;p&gt;These are just some notes I made when creating my own mini-distro after
wanting something more custom than just using buildroot or making the
official firmware more slim. For people other than me, I suggest
looking through (C)LFS or running postmarketOS instead once this
reader's pull request&lt;a href=&quot;https://gitlab.com/postmarketOS/pmaports/-/merge_requests/2334&quot;&gt;1&lt;/a&gt; gets integrated into upstream.&lt;/p&gt;&lt;p&gt;Two things that'll greatly help with this is having serial terminal
access with the four uart pins near the top right in the back of the
reader, near the uSD card slot (I don't connect the 5V pin as my reader
doesn't really turn on anything other than the power LED). I suggest
maybe soldering female pin headers to there to make your life easier
(you can later cut out a hole in the back cover or desolder the headers
once you're done). Other than that, I suggest installing QEMU with ARM
userspace to test programs that you have built or running them on a
separate ARM device like a Raspberry Pi.&lt;/p&gt;&lt;h2&gt;Prelude&lt;/h2&gt;&lt;p&gt;Ever since I learnt that the official firmware for the Clara was just
using a modified Linux kernel with busybox as coreutils and many other
libraries, I just knew that I had to minimize it. I also saw that it
was using glibc for it's libc, which I really dislike as statically
linking C programs against it was a pain in my experience, compared to
something like musl and uclibc. It's also much larger than them and I
don't use any of glibc extensions so it seemed like a waste of space to
me.&lt;/p&gt;&lt;p&gt;Initially when I replaced Nickel with Plato, I was able to shave about
100 MiB after I removed /usr/local (which contains Nickel, Qt and a few
other things), from 189 MiB to 74 MiB, but I still wanted to make it
smaller.&lt;/p&gt;&lt;p&gt;Using buildroot, I was able to get it under 2 MiB (!!) which was a
little less than half the size of an uncompressed armhf Alpine Linux
minirootfs (4.9M for 3.14). With Busybox, it was pretty much working
out of the box, with serial terminal access! But waiting around 15
minutes for the toolchain to build each time I wanted to change
something in the rootfs took way too long, although it could've been
minimized if I used ccache with a fairly large cache size. I still
found that it compiled and installed a lot of things I wouldn't be
using (particularly in /usr) even after disabling almost all of the
third-party packages.&lt;/p&gt;&lt;p&gt;I've uploaded the config file and the resulting rootfs for
buildroot 2021.05. The root password by default is changeme.
EDIT 2022-10-21: gone, build it yourself&lt;/p&gt;&lt;p&gt;Of course the rootfs I got from buildroot nor me making the official
firmware smaller is the point of this article, and the actual point is
making one yourself! (or rather what I did to make my own)&lt;/p&gt;&lt;h2&gt;Cross-toolchain&lt;/h2&gt;&lt;p&gt;For now as of July 22, 2021, I'm using my distro (Void Linux)'s
packaged cross toolchain for armhf musl, but eventually I would be
using my own.&lt;/p&gt;&lt;p&gt;I'm not compiling off of the device itself as it would be somewhat slow
for bigger programs, which is currently primarily the Linux kernel,
U-Boot, and the toolchain itself, considering that the ereader's CPU
(Freescale i.MX 6SLL) is a single core running up to 1 GHz. Including
the development tools and headers would also take up more space on the
device itself, and since the terminal can currently only be accessed
through it's serial/uart pins, I don't think it's ideal.&lt;/p&gt;&lt;p&gt;TODO: include steps to create own toolchain (probably based off of gcc
4.7.3 as that doesn't require c++)&lt;/p&gt;&lt;h2&gt;Building the rootfs&lt;/h2&gt;&lt;p&gt;Assuming you made a new filesystem on your rootfs's partition, it'll
likely be empty with no directories you'd expect to find on a regular
distro. So you'll just have to make them.
cd /path/to/rootfs
mkdir bin dev etc proc sbin&lt;/p&gt;&lt;p&gt;Your binaries would usually go in /bin, the uSD card, ttymxc0, and
other devices would go in /dev, felker init's default program/script to
execute is usually in /etc/rc, /proc is optional but I have it mounted
to see what is currently mounted through /proc/mounts (or mount(1)
without any arguments) as well as to see my disk usage through df(1).
/sbin is there to place the init in as /sbin/init is the default init
path the kernel looks at.&lt;/p&gt;&lt;h2&gt;toybox&lt;/h2&gt;&lt;p&gt;Now on to the main part of the distro, the userspace. I intend to keep
it fairly minimal so I've chosen to use toybox along with a slightly
modified version of felker (musl dev)'s init&lt;a href=&quot;https://ewontfix.com/14&quot;&gt;2&lt;/a&gt;, as well as dash&lt;a href=&quot;https://git.kernel.org/pub/scm/utils/dash/dash.git&quot;&gt;3&lt;/a&gt; as
the main shell since toybox doesn't include one as of 0.8.5 (though
it'll probably be there by 1.0). I'll also be statically linking all
the programs that'll be used so I wouldn't have to worry about shared
libraries not being included/copied over, and also including LTO for
slightly faster binaries. Originally, I tried going with sinit, sbase,
and ubase but I was having trouble getting serial terminal access with
getty to /dev/ttymxc0 (the default serial tty, at least with the
vendor kernel). I didn't have this problem with busybox's and toybox's
getty however. My config for toybox was also about 81K smaller than my
trimmed sbase-box and ubase-box (352K compared to 267K+166K) where I
removed programs that I won't use from ${BIN} in their respective
Makefiles.
EDIT 2022-10-21: also gone&lt;/p&gt;&lt;p&gt;First I suggest exporting some environment variables to set the
toolchain used as well as enabling static linking and LTO.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;export CROSS_COMPILE=&amp;quot;arm-linux-musleabihf-&amp;quot; # change to your cross-tc
export CC=&amp;quot;${CROSS_COMPILE}gcc&amp;quot;
export LDFLAGS=&amp;quot;--static&amp;quot;
export CFLAGS=&amp;quot;-flto -static&amp;quot;
export ARCH=arm # for compiling the linux kernel&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To compile toybox, get the source from
https://landley.net/toybox/downloads/ (or clone the upstream repo).
Then run make menuconfig (optionally with make defconfig before it) and
change it as you see fit. Personally, I disabled most of the programs I
wouldn't use and kept only the ones that'll help with fixing a problem.
Finally, make sure to run make.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;make defconfig
make menuconfig
make&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To move it to your rootfs and set it's symlinks, you could probably run
make install after setting PREFIX to your rootfs's /bin directory, but
I did it manually.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# automatic (didn't test, check README)
make PREFIX=/path/to/rootfs/bin/ install

# (semi?) manual
cp toybox /path/to/rootfs/bin

# add symlinks if doing manual and you want them
cd /path/to/rootfs/bin
for prog in $(qemu-arm ./toybox); do ln -s toybox &amp;quot;$prog&amp;quot;; done&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;dash&lt;/h2&gt;&lt;p&gt;Also as of toybox 0.8.5, a shell still isn't included (probably would
be included by 1.0 according to scripts/install.sh as well as a few
other programs like gzip), so a separate shell would need to be built.
Any can be used but dash would be shown as an example as I was able to
get a static binary without too much trouble.&lt;/p&gt;&lt;p&gt;First obtain the source&lt;a href=&quot;https://git.kernel.org/pub/scm/utils/dash/dash.git&quot;&gt;3&lt;/a&gt; and cd into its
untarred directory. Assuming your CC and CFLAGS are set, you can run
these steps:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;autoreconf -fiv
./configure --host=$CROSS_COMPILE --with-libedit
make
${CROSS_COMPILE}strip src/dash&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As this is going to be used as the main shell, I've decided to just
copy it to /bin/sh in the rootfs directory, though copying it there but
as /bin/dash and /bin/sh being symlinked to dash is also an option.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cp src/dash /path/to/rootfs/bin/sh
# or
cp src/dash /path/to/rootfs/bin
cd /path/to/rootfs/bin
ln -s dash sh&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;felker's init&lt;/h2&gt;&lt;p&gt;The init is just a single file that you can get from felker's site&lt;a href=&quot;https://ewontfix.com/14&quot;&gt;2&lt;/a&gt;
or the gist on github&lt;a href=&quot;https://gist.github.com/rofl0r/6168719/raw/183525e0f0007169a49392b21ceee5b507e3aee8/init.c&quot;&gt;7&lt;/a&gt;. I haven't had a good experience with the
default startup program (/etc/rc) as a shell script with execve() run
on it so I'd change it to execvp() and remove the third (specifies
environment). To compile and install the init, all you need to do is
run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$CC $CFLAGS -o init init.c
cp init /path/to/rootfs/sbin&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of /etc/rc being a shell script, you can also make a C program
that does whatever you think is needed for a proper startup. I'll still
use a shell script though which is linked here.
EDIT 2022-10-21: you get the idea, it's gone.&lt;/p&gt;&lt;h2&gt;/etc/passwd&lt;/h2&gt;&lt;p&gt;Copying the rootfs's contents to your device's/uSD card's root
partition and then turning the device on should now work with a login
prompt shown in the serial terminal. However, you probably wouldn't be
able to login to any user. So you'll have to create a file at
/path/to/rootfs/etc/passwd. For an empty password to root, you can use
this, though I suggest setting a password as soon as you login:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# in rootfs's /etc/passwd
root::0:0:root:/root:/bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the passwd file created/updated, you should now be able to login
to root after the rootfs is copied to your uSD card. Your rootfs so far
should now be around 550-560K, which is much much smaller than the
original firmware's, though it'll likely be much larger to maybe a few
megabytes once a proper reader software is added.&lt;/p&gt;&lt;h2&gt;Custom Linux Kernel&lt;/h2&gt;&lt;p&gt;WARNING: I haven't actually gotten the kernel to load in u-boot yet. It
just hangs in the &amp;quot;Starting kernel ...&amp;quot; step and the init doesn't get
loaded, so I'm assuming the kernel itself isn't either. If anyone out
there has gotten a custom kernel working in the Kobo Clara HD, please
send me an email or message on xmpp.&lt;/p&gt;&lt;p&gt;UPDATE Jul 28, 2021: Gave up on it as I just couldn't get any kernels I
built (both vendor and akemnade's mainline) to boot. But neither did
postmarketOS boot beyond the initial initramfs messages without the log
file being created. So I'll revisit this for later.&lt;/p&gt;&lt;p&gt;EDIT 2022-10-21: I have gotten this working, but have been unable to
get Plato build for musl, so I will have to either continue fighting
with the crab or create my own with fbink, as that still works.
Separate article on this later.&lt;/p&gt;&lt;p&gt;My next big step is compiling my own kernel for the Clara HD. With the
default configuration built for the vendor kernel, it appears to be
about 3M, so my goal is to build a kernel that is smaller than that
while retaining only the functionality that I need. I'm also not going
to include networking support as that is unneeded for my purposes, but
I suggest just keeping it if you're unsure. The wifi driver for the
Kobo Clara HD is available as an out-of-tree driver&lt;a href=&quot;https://github.com/jwrdegoede/rtl8189ES_linux/tree/rtl8189fs&quot;&gt;8&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;You should first obtain the kernel source, with two main options, the
vendor kernel&lt;a href=&quot;https://github.com/kobolabs/Kobo-Reader/blob/master/hw/imx6sll-clara/kernel.tar.bz2&quot;&gt;9&lt;/a&gt; and the mainline kernel (with akemnade's
patches)&lt;a href=&quot;https://github.com/akemnade/linux/tree/kobo/drm-merged-5.12&quot;&gt;10&lt;/a&gt;. For the latter, you need to clone the repo and switch to
the latest kobo/drm-merged branch (kobo/merged-5.13 as of July 25,
2021).&lt;/p&gt;&lt;p&gt;After you've got them and assuming the CROSS_COMPILE and ARCH
environment variables are set, you'd want to configure the kernel.&lt;/p&gt;&lt;p&gt;I had a hard time compiling the vendor kernel with many things
disabled, so I've kept my config somewhat similar to the default
config. The config I used is available here (EDIT: dead).&lt;/p&gt;&lt;pre&gt;&lt;code&gt;make menuconfig
make zImage&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Assuming it compiles properly and arch/arm/boot/zImage exists, all
that's needed to is to write it to your uSD card at the 1M offset.
dd if=/path/to/kernel/zImage of=/path/to/uSDdev bs=512 seek=2048&lt;/p&gt;&lt;h2&gt;Custom U-Boot&lt;/h2&gt;&lt;p&gt;I have not done this yet, nor really plan to, but if you do manage to
compile the Kobo's vendored u-boot source, then all you'd have to do to
install it is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;dd if=u-boot-file of=/dev/mmcblk0 bs=128k count=1 seek=6&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If I remember correctly, this command was included in an older
firmware's startup script/rcS for updating udev, and it should still
work.&lt;/p&gt;&lt;h2&gt;Links&lt;/h2&gt;</content></entry><entry><title>Kobo Clara HD Notes for Plato (and KSM)</title><id>https://vineetk.net/kobo-clara-hd-notes-for-plato-and-ksm.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2021-03-27T12:00:00Z</updated><link href="https://vineetk.net/kobo-clara-hd-notes-for-plato-and-ksm.html" rel="alternate" /><content type="html">&lt;p&gt;These are my notes for getting Plato on the Kobo Clara HD from scratch
as well as some notes for getting KSM to work, but I now boot directly
into Plato instead of through KSM.&lt;/p&gt;&lt;p&gt;Previously, I didn't really like using KOReader because it was kind of
slow and was written in Lua. At the time of using Plato, it seemed nice
but it didn't cover thumbnails for books, which while it is a minor
detail, I find books easier to be recognized with a cover thumbnail in
addition to their title. This was added in release 0.9.10 but as an
optional feature which I didn't somehow see until recently when I
retried it. HOWEVER again, I didn't like using k/fmon as I had to still
use Nickel to get back into KOReader/Plato/whatever alternate reader
when I wanted to go away from using Nickel.&lt;/p&gt;&lt;p&gt;&amp;lt;ignore&amp;gt;
That was when I found out about KSM and how there was a working version
for the Clara HD. KSM is like an alternate bootloader for the Kobo
readers and it apparently doesn't work very well with newer models like
the Clara HD and up, but someone got it to work with those devices.
&lt;a href=&quot;https://www.mobileread.com/forums/showthread.php?s=c34e41df391c61810a6b06f991c29168&amp;amp;t=293804&quot;&gt;KSM 09&lt;/a&gt; is apparently not maintained anymore and I'm not sure if KSM
10 is being developed or not since I'm pretty sure it's closed source.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Development and support for KSM stopped some time ago. Therefore, do
not use it!&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&amp;quot;That sign can't stop me because I can't read!&amp;quot; - Me imitating D.W.
from the PBS Kids cartoon Arthur on Mar. 26, 2021 when I saw that it
can be used on my Kobo&lt;/p&gt;&lt;p&gt;The latest firmware version that KSM sort of supports is v4.25.15875
but it can probably work with a newer version like v4.26+ that would
likely only need a couple changes to /etc/init.d/rcS, if any changes
were needed at all. I'll be using v4.26 for the rest of this
article/guide.
&amp;lt;/ignore&amp;gt;&lt;/p&gt;&lt;p&gt;Recently, after seeing how my Kobo boots into KSM and Nickel through
the rcS file, I realized that I could've instead just booted directly
into Plato, and plato.sh (the script that runs Plato) has a standalone
option that supports just that! The KSM notes are still going to be
here in case someone still wants to use KSM.&lt;/p&gt;&lt;h2&gt;Installing Plato (or probably any other reader like KOReader)&lt;/h2&gt;&lt;p&gt;This part probably applies to any other reader other than Plato like
KOReader but I haven't personally tested them. All you have to do is
&lt;a href=&quot;https://github.com/baskerville/plato/releases/latest&quot;&gt;get the latest release&lt;/a&gt; at Plato's repo and unzip it's contents into
a folder called plato in /path/to/kobo/mount/.adds, the latter folder
of which should have already been created by KSM if you are using that.
If you are using KSM, there should be a new option below &amp;quot;start nickel&amp;quot;
called &amp;quot;start plato&amp;quot; when you have rebooted the device. Read below if
you aren't using KSM.&lt;/p&gt;&lt;h2&gt;Loading Plato on Boot&lt;/h2&gt;&lt;p&gt;Since I don't want to load Nickel only to load into another reader like
the recommended options in Plato's forum thread (kfmon, fmon, and
NickelMenu) suggest, I noticed that I could have booted into Plato
directly. The only requirements for doing this having access to the
rootfs, so either through a telnet/ssh session, or having the sd card's
root/first partition mounted to your computer, or just ftp/rsyncing the
files to your Kobo.&lt;/p&gt;&lt;p&gt;First I suggest making a copy of rcS if you haven't already in case an
update overwrites it. My copy is named custominit.sh. Next you'll want
the Kobo's /etc/inittab to boot with custominit.sh instead of rcS:
/etc/inittab:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#::sysinit:/etc/init.d/rcS
::sysinit:/etc/custominit.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The rest of the lines don't need to change. Then you should open
custominit.sh in your favourite editor to add the lines at the bottom
but before hindenburg is executed:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cd /mnt/onboard/.adds/plato     # or whereever Plato is
PLATO_STANDALONE=1 ./plato.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You would probably also want to remove the lines where Nickel-specific
programs/scripts are running like nickel, hindenburg, pickel, sickel,
etc.&lt;/p&gt;&lt;p&gt;Now on subsequent boots, Plato should automatically have been loaded.
Boot times may also be slightly faster! :D&lt;/p&gt;&lt;h2&gt;Installing KSM 09 (not doing anymore)&lt;/h2&gt;&lt;p&gt;First you would want to &lt;a href=&quot;https://www.mobileread.com/forums/attachment.php?s=902078ac2e6fe8ff7a0947b56cbcade6&amp;amp;attachmentid=166556&amp;amp;d=1538176531&quot;&gt;download the Clara HD version of KSM 09&lt;/a&gt; and
&lt;a href=&quot;https://www.mobileread.com/forums/attachment.php?s=902078ac2e6fe8ff7a0947b56cbcade6&amp;amp;attachmentid=184756&amp;amp;d=1610745905&quot;&gt;the fix for v4.25&lt;/a&gt;. Then, you would want to unzip the KoboRoot.tgz
with separate filenames so they don't replace each other and we would
untar those into the same directory. After that, we would cd into the
directory and tar it's contents into a new KoboRoot.tgz and place it in
/path/to/kobo/mount/.kobo/.&lt;/p&gt;&lt;p&gt;An example of what I did after downloading and unzipping the files are
below:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mkdir koboroot
tar -xvf KoboRoot-main.tgz -C koboroot
tar -xvf KoboRoot-v4.25-darkmodefix.tgz -C koboroot
cd koboroot
tar -czvf ../KoboRoot.tgz .
cd ..
rm -r koboroot&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After your Kobo untars it and you wait a while, you should be presented
with KSM's main screen :D ksm09's main screen running on the kobo clara
hd&lt;/p&gt;&lt;h2&gt;Auto-Boot into Plato instead of Nickel via KSM (not doing anymore)&lt;/h2&gt;&lt;p&gt;First make sure USB support is enabled in KSM and then mount your Kobo
to your computer. Once mounted, go to
/path/to/kobo/mount/.adds/kbmenu_user/confoptions and edit
ksm_ini_options.txt in your favourite editor. You should see many
options that are listed but the one that we're interested in is
ksmAutoselectoption which may have start_nickel and start_koreader
already and what we want to do is add ksmAutoselectoption=start_plato.
After a quick restart to reload the options file, you should be able to
see the new option in KSM's settings under [general] and add item if it
wasn't already added. Now Plato should auto-boot on subsequent
powerons.&lt;/p&gt;</content></entry><entry><title>Kobo Clara HD Notes for Nickel</title><id>https://vineetk.net/kobo-clara-hd-notes-for-nickel.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2021-01-13T12:00:00Z</updated><link href="https://vineetk.net/kobo-clara-hd-notes-for-nickel.html" rel="alternate" /><content type="html">&lt;p&gt;My ereader of choice is the Kobo Clara HD and I particularly like it
because my eyes hurt less when reading for long periods of time
compared to when I read on my phone or when I still had my iPad. It
also had much longer battery life and only need to charge it about once
every two weeks when I read for about 4 hours on average daily.&lt;/p&gt;&lt;p&gt;However, the two notable things I don't like about it is it's included
telemetry, like using Google Analytics by default and keeping a unique
salt&lt;/p&gt;&lt;p&gt;Spyware/Anti-Features:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Google Analytics (a lot of actions, if not everything, is sent to
Google)&lt;/li&gt;&lt;li&gt;&lt;p&gt;Auto-update by default&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;I prefer being able to review what the new update provides and
choose not to apply it&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;I don't like the new redesign in firmware v4.23.15505&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I'm also assuming your Kobo reader and it's SD card's device file would
be would located at &lt;code&gt;/dev/sdf&lt;/code&gt; and be mounted at &lt;code&gt;/mnt/kobo&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;If you're going to not be using Nickel and instead be using something
like &lt;a href=&quot;https://github.com/baskerville/plato&quot;&gt;Plato&lt;/a&gt;, there's a newer version of this article available
&lt;a href=&quot;./kobo-clara-plato.html&quot;&gt;here&lt;/a&gt;, but the notes are for ~KSM~ loading Plato directly and not
though k/fmon because I don't want to load Nickel if I'm already using
a different reader.&lt;/p&gt;&lt;h2&gt;Upgrade/Backup Included SD Card&lt;/h2&gt;&lt;p&gt;While the included 8GB microSD card is decent for storing your ebook
library that may not have a lot of images, that would likely not be
enough if you were aiming to read some comics on your ereader as they
can be pretty big (quite a few of mine are over a gigabyte, with some
over. Luckily, you can replace the microSD card with another one.&lt;/p&gt;&lt;p&gt;Before upgrading, you should backup the SD card to into an image file
so the filesystem would be preserved when putting the contents of the
image on the new SD card. I'm using the command dd but there might be
another program doing the same thing. Even if you're not going to
upgrade, I still suggest to backup the SD card in case something goes
wrong.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;dd if=/dev/sdf of=kobo_sd.img conv=sync&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After this is done, you can plug in your new SD card and reimage
kobo_sd.img onto it. With dd, you can do something like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;dd if=kobo_sd.img of=/dev/sdf conv=sync&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checking it's partition table via lsblk or fdisk -l should show three
partitions. If you replaced the SD card with something bigger, than you
should resize the third partition.&lt;/p&gt;&lt;h2&gt;Bypassing Registration On Setup&lt;/h2&gt;&lt;p&gt;When setting up your Kobo, you will be asked to sign into a Kobo
account. There are other options like logging in via Google, Walmart,
and other stores, but I don't like having to login to a device that
would likely not be connected to the public internet. Fortunately, you
can bypass this by choosing that you cannot connect to a Wi-Fi network
and mount your Kobo to your computer. In, &lt;code&gt;.kobo/KoboReader.sqlite&lt;/code&gt;, you
can run:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;echo &amp;quot;INSERT INTO user(UserID,UserKey) VALUES('1','');&amp;quot; \
     | sqlite3 KoboReader.sqlite&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way you don't have to install their application just to be able to
use your device.&lt;/p&gt;&lt;p&gt;Note: Do not try doing this when you still have your SD card mounted
before you setup your device. The device's screen would likely not
update, at least on an early firmware version like v4.7.10733.&lt;/p&gt;&lt;h2&gt;Blocking Google Analytics and other Telemetry&lt;/h2&gt;&lt;p&gt;Just adding 0.0.0.0 analytics.google.com to &lt;code&gt;/etc/hosts&lt;/code&gt; may be enough to
block most of the telemetry from being sent. However, you can try
intercepting what connections your Kobo is making via mitmproxy set to
transparent mode or using a hosts file that blocks all connections to
Google (but not necessarily to Kobo's servers) like &lt;a href=&quot;https://codeberg.org/baobab/hosts&quot;&gt;Baobab's hosts file&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/baobab/hosts/raw/branch/master/hosts&quot;&gt;(raw)&lt;/a&gt;.
EDIT 2022-10-21: Baobab has deleted his account from Codeberg for quite a
while, so these two links are dead. Instead, I now recommend &lt;a href=&quot;https://github.com/StevenBlack/hosts&quot;&gt;Steven Black's&lt;/a&gt;
instead &lt;a href=&quot;https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts&quot;&gt;(raw)&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;To put the hosts file without root (which will be detailed in another
section), you can make a directory called etc, put the hosts file in
there, and tar it into a file called KoboRoot.tgz.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir etc
wget -O etc/hosts https://codeberg.org/baobab/hosts/raw/branch/master/hosts
tar czvf KoboRoot.tgz etc
cp KoboRoot.tgz /mnt/kobo/.kobo/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When you move a tar file with that name into your Kobo's .kobo folder,
it's contents gets untarred into it's root at &lt;code&gt;/&lt;/code&gt; when the device is
turned on again, which is usually done for their updates but can be
used for custom files like this and gaining root access.&lt;/p&gt;&lt;h2&gt;Gaining Root Access via Telnet&lt;/h2&gt;&lt;p&gt;To gain root access, we first need to get the &lt;code&gt;/etc/inittab&lt;/code&gt; and
&lt;code&gt;/etc/inetd.conf&lt;/code&gt; which you can get from mounting the SD card's first
partition into your computer (the second partition seems to be like a
backup). You should copy those two files into a folder called etc
somewhere (probably not on the SD card).&lt;/p&gt;&lt;p&gt;In the &lt;code&gt;etc/inittab&lt;/code&gt; file, you should add these two lines:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;::sysinit:/etc/custominit.sh
::respawn:/usr/sbin/inetd -f /etc/inetd2.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You would want to rename the &lt;code&gt;etc/inetd.conf&lt;/code&gt; file you copied into
&lt;code&gt;etc/inetd2.conf&lt;/code&gt; (or whatever the custom inetd.conf's filename is) and
when editing that, you should add:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;23 stream tcp nowait root /bin/busybox telnetd -i&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, if there is already a commented line for root telnet in the
inetd2.conf, you should probably still add the above line and ignore
the commented line as that may or may not work (didn't for me).&lt;/p&gt;&lt;p&gt;To actually start inetd, you should add these lines somewhere in
&lt;code&gt;/etc/custominit.sh&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
/usr/sbin/inetd /etc/inetd2.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that, you just have to tar the &lt;code&gt;etc/&lt;/code&gt; folder again and copy it to
your Kobo's onboard/third partition's &lt;code&gt;.kobo&lt;/code&gt; folder.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;tar czvf KoboRoot.tgz etc
cp KoboRoot.tgz /mnt/kobo/.kobo/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you could put your SD card back into your Kobo provided that they
are already unmounted and turn your Kobo back on.&lt;/p&gt;&lt;p&gt;After connecting to the WiFi, simplying telnetting (?) into your Kobo
and logging in as root should give you a root shell. :D&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;telnet $KOBO_IP&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, root has no password so you should change it with passwd.&lt;/p&gt;&lt;h2&gt;Getting SSH and SFTP access via Dropbear&lt;/h2&gt;&lt;p&gt;I'm using Dropbear instead of OpenSSH because it's better suited for
embedded hardware like the Kobo Clara HD. Obviously we can't copy a
binary compiled for amd64 or whatever architecture your compiling
computer is running so we would have to cross-compile for our ereader.&lt;/p&gt;&lt;p&gt;Fortunately, we are not required to cross-compile &lt;code&gt;gcc&lt;/code&gt;/&lt;code&gt;clang&lt;/code&gt; and friends
as we can simply download the linaro arm toolchain which has the
binaries for gcc and others included. You could get the toolchain
&lt;a href=&quot;https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/&quot;&gt;here&lt;/a&gt; and you should get the release that matches your host's&lt;/p&gt;&lt;p&gt;architecture. After untarring the file, you should also set your PATH
variable to the toolchain's &lt;code&gt;bin/&lt;/code&gt; folder so you don't have to manually
set the CC and CXX variables when building Dropbear.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
tar xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
export PATH=$(pwd)/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you could get the source for Dropbear and cross-compile it. The
source can be found on their &lt;a href=&quot;https://matt.ucc.asn.au/dropbear/dropbear.html&quot;&gt;homepage&lt;/a&gt; or &lt;a href=&quot;https://github.com/mkj/dropbear/releases&quot;&gt;github&lt;/a&gt; repo.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;wget https://matt.ucc.asn.au/dropbear/releases/dropbear-2020.81.tar.bz2
tar xvf dropbear-2020.81.tar.bz2
cd dropbear-2020.81
./configure --enable-static --host=arm-linux-gnueabihf
# MULTI=1 combines the binaries like busybox does and is also smaller in size
make MULTI=1 PROGRAMS=&amp;quot;dropbear dropbearkey&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you only need to copy the dropbearmulti binary over to your Kobo.
What I've done is running &lt;code&gt;python3 -m http.server&lt;/code&gt; and downloading the
file onto my Kobo but you could also just copy it onto the microSD
card.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;wget your.computer.ip:8000/dropbearmulti
chmod +x dropbearmulti
mv dropbearmulti /usr/bin
cd /usr/bin
# below are optional but dropbear(key) would be an argument for dropbearmulti
ln -s dropbearmulti dropbear
ln -s dropbearmulti dropbearkey&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you only need to generate the host keys. My client key is ed25519
so I'm not going to generate the others.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir /etc/dropbear
dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key
dropbear -F -r /etc/dropbear/dropbear_ed25519_key&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you could &lt;code&gt;ssh&lt;/code&gt; into your Kobo and login as &lt;code&gt;root&lt;/code&gt;. Remember to change
&lt;code&gt;root&lt;/code&gt;'s password beforehand though if you haven't already! I suggest
copying your public key to your Kobo via &lt;code&gt;ssh-copy-id&lt;/code&gt; so you don't have
to enter root's password all the time and so password-based logins can
be disabled in dropbear.&lt;/p&gt;&lt;p&gt;To start it on boot, you could add the following line to
&lt;code&gt;/etc/inetd2.conf&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;22 stream tcp nowait root /usr/bin/dropbearmulti dropbear -i -r /etc/dropbear/dropbear_ed25519_key&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For some reason, the symlink wasn't resolving for me inetd so I had to
call the multi-binary directly. You could also add the command/args
into &lt;code&gt;/etc/custominit.sh&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;FTP Access&lt;/h2&gt;&lt;p&gt;If you don't or can't use sftp or scp for some reason, there's always ftp :D
There's a ftp daemon included in busybox so all we have to do is enable it
in &lt;code&gt;/etc/inetd2.conf&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;21 stream tcp nowait root /bin/busybox ftpd -w -S /&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This would share the entire filesystem so you may or may not want to
restrict the shared directory to maybe just your ebook directory
(&lt;code&gt;/mnt/onboard&lt;/code&gt;) and move the files out via &lt;code&gt;telnet&lt;/code&gt; or &lt;code&gt;ssh&lt;/code&gt;.
EDIT 2022-10-21: A chroot would also work.&lt;/p&gt;&lt;h2&gt;References and Other Links&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://remy.grunblatt.org/kobo-aura-h2o-electronic-reader-hacking.html&quot;&gt;Rémy's notes on hacking a Kobo Aura H2O&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://yingtongli.me/blog/2018/07/30/kobo-rego.html&quot;&gt;Ying's notes on bypassing registration and setting up telnet, ssh, etc.&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.mobileread.com/forums/showthread.php?t=162713&quot;&gt;MobileRead forum thread on disabling Google Analytics on the Kobo Touch&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://wiki.mobileread.com/wiki/Kobo_Touch_Hacking&quot;&gt;MobileRead wiki on hacking the Kobo Touch&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content></entry><entry><title>VFIO Install Notes</title><id>https://vineetk.net/vfio-install-notes.html</id><author><name>Vineet K</name><email>me@vineetk.net</email></author><updated>2020-10-17T12:00:00Z</updated><link href="https://vineetk.net/vfio-install-notes.html" rel="alternate" /><content type="html">&lt;p&gt;You should first go look at &lt;a href=&quot;https://wiki.archlinux.org/index.php/PCI%20passthrough%20via%20OVMF&quot;&gt;the Arch Wiki on it&lt;/a&gt; or &lt;a href=&quot;https://gitlab.com/YuriAlek/vfio&quot;&gt;Yuri Alek's guide on Single GPU passthrough&lt;/a&gt; or &lt;a href=&quot;https://wiki.installgentoo.com/index.php/PCI_passthrough&quot;&gt;4chan's /g/ wiki on it&lt;/a&gt; as these assume prior knowledge.&lt;/p&gt;&lt;h1&gt;Prerequisites&lt;/h1&gt;&lt;h2&gt;UEFI Options&lt;/h2&gt;&lt;p&gt;Enable VT-d and VT-x (or AMD equivalent)&lt;/p&gt;&lt;h2&gt;Kernel Config&lt;/h2&gt;&lt;p&gt;Enable KVM and VFIO&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;you can set VFIO as builtin but as a module is more flexible
Also add &lt;code&gt;&amp;quot;iommu=pt intel_iommu=on&amp;quot;&lt;/code&gt; to your kernel command line (or in CONFIG_CMDLINE)&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;Current Options&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;...
CONFIG_IOMMU_IOVA=y
CONFIG_IOMMU_API=y
CONFIG_IOMMU_SUPPORT=y
CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y
# use the respective AMD options if using an AMD CPU
CONFIG_INTEL_IOMMU=y
CONFIG_INTEL_IOMMU_SVM=y
CONFIG_INTEL_IOMMU_DEFAULT_ON=y
CONFIG_INTEL_IOMMU_FLOPPY_WA=y

CONFIG_KVM_VFIO=y
CONFIG_VFIO_IOMMU_TYPE1=m
CONFIG_VFIO_VIRQFD=m
CONFIG_VFIO=m
CONFIG_VFIO_PCI=m
CONFIG_VFIO_PCI_VGA=y
CONFIG_VFIO_PCI_MMAP=y
CONFIG_VFIO_PCI_INTX=y
CONFIG_VFIO_PCI_IGD=y
CONFIG_VFIO_MDEV=m
CONFIG_VFIO_MDEV_DEVICE=m
...&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Packages Required&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;app-emulation/qemu (actual program)
sys-firmware/edk2-ovmf (UEFI firmware for Nvidia GPU)
media-sound/scream (audio)
looking-glass-client (compile from source if no package, or make your own)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;app-emulation/libvirt&lt;/code&gt; can be used as well for easier configuration and autostart
but I have had problems with it:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Service not starting properly, workaround is restarting service after it starts (Gentoo)&lt;/li&gt;&lt;li&gt;Networks and domains not autostarting, workaround is starting them manually (CRUX)&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Gentoo USE Flags&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;app-emulation/qemu gtk opengl sdl sdl-image usb # (spice, ssh, vhost-user-fs, virgl, and virtfs are optional I think)
media-libs/libsdl2 X gles opengl # for Looking Glass&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;note to self (2020-10-17): check how minimal you can make qemu to run vfio&lt;/p&gt;&lt;h1&gt;IOMMU&lt;/h1&gt;&lt;p&gt;Run &lt;code&gt;dmesg | grep -E 'DMAR'&lt;/code&gt; and see if &lt;code&gt;DMAR: IOMMU enabled&lt;/code&gt; or something similar is in output&lt;/p&gt;&lt;h1&gt;QEMU Script&lt;/h1&gt;&lt;p&gt;All code blocks in this section go in the qemu script file unless specified otherwise&lt;/p&gt;&lt;h2&gt;Environment Variables&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;IMG=/path/to/windows-image-file
VIRTIO=/path/to/virtio-iso
WINDOWS=/path/to/windows-install-iso
OVMF=/usr/share/edk2-ovmf/OVMF_CODE.fd
RAM=16G
ULIMIT=$(ulimit -l)
ULIMIT_TARGET=$(( $(echo $RAM | tr -d 'G')*1048576+100000 ))

GPU_VIDEO=01:00.0
GPU_AUDIO=01:00.1
VIDEOID=&amp;quot;10de 13c0&amp;quot;
AUDIOID=&amp;quot;10de 0fbb&amp;quot;
VIDEOBUSID=&amp;quot;0000:${GPU_VIDEO}&amp;quot;
AUDIOBUSID=&amp;quot;0000:${GPU_AUDIO}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;VFIO Detaching and Attaching&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;vfio_on() {
    # for nvidia card with proprietary drivers
    rmmod nvidia_drm
    rmmod nvidia_modeset
    rmmod nvidia

    # disable bumblebee service or use bbswitch to detach card if using bumblebee

    modprobe vfio-pci

    echo $VIDEOID &amp;gt; /sys/bus/pci/drivers/vfio-pci/new_id
    echo $VIDEOBUSID &amp;gt; /sys/bus/pci/devices/$VIDEOBUSID/driver/unbind
    echo $VIDEOBUSID &amp;gt; /sys/bus/pci/drivers/vfio-pci/bind
    echo $VIDEOID &amp;gt; /sys/bus/pci/drivers/vfio-pci/remove_id

    echo $AUDIOID &amp;gt; /sys/bus/pci/drivers/vfio-pci/new_id
    echo $AUDIOBUSID &amp;gt; /sys/bus/pci/devices/$AUDIOBUSID/driver/unbind
    echo $AUDIOBUSID &amp;gt; /sys/bus/pci/drivers/vfio-pci/bind
    echo $AUDIOID &amp;gt; /sys/bus/pci/drivers/vfio-pci/remove_id

    # add rest of gpu devices if they are in the same group (I think 4 devices in 1000 or 2000 series nvidia)
}

vfio_off() {
    rmmod vfio_iommu_type1
    rmmod vfio_pci
    rmmod vfio_virqfd
    rmmod vfio

    modprobe nvidia
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Networking&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;net_on() {
    ip tuntap add dev tap0 mode tap group kvm
    ip link set dev tap0 up promisc on
    ip addr add 0.0.0.0 dev tap0

    ip link add br0 type bridge
    ip link set br0 up
    ip link set tap0 master br0
    echo 0 &amp;gt; /sys/class/net/br0/bridge/stp_state
    ip addr add 192.168.123.1/24 dev br0

    sysctl net.ipv4.conf.tap0.proxy_arp=1 &amp;gt; /dev/null
    sysctl net.ipv4.conf.enp0s31f6.proxy_arp=1 &amp;gt; /dev/null
    sysctl net.ipv4.ip_forward=1 &amp;gt; /dev/null

    iptables -t nat -A POSTROUTING -o enp0s31f6 -j MASQUERADE &amp;gt; /dev/null
    iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT &amp;gt; /dev/null
    iptables -A FORWARD -i br0 -o enp0s31f6 -j ACCEPT &amp;gt; /dev/null
}

net_off() {
    sysctl net.ipv4.conf.tap0.proxy_arp=0 &amp;gt; /dev/null
    sysctl net.ipv4.conf.enp0s31f6.proxy_arp=0 &amp;gt; /dev/null
    sysctl net.ipv4.ip_forward=0 &amp;gt; /dev/null

    ip link set dev br0 down
    ip link del br0

    ip link set dev tap0 down
    ip tuntap del mode tap name tap0
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also add this to /etc/conf.d/net if using Gentoo (&lt;a href=&quot;https://wiki.gentoo.org/wiki/QEMU/Options#Network_bridge&quot;&gt;source&lt;/a&gt;)&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;replace &lt;code&gt;enp0s31f6&lt;/code&gt; with the host/master interface&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;...
tuntap_tap0=&amp;quot;tap&amp;quot;
config_tap0=&amp;quot;null&amp;quot;
bridge_br0=&amp;quot;enp0s31f6 tap0&amp;quot;

config_br0=&amp;quot;192.168.123.2 netmask 255.255.255.0&amp;quot;
routes_br0=&amp;quot;default via 192.168.123.1&amp;quot;
bridge_forward_delay_br0=0
bridge_hello_time_br0=10

depend_br0() {
    need net.enp0s31f6
    need net.tap0
}
...&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Hugepages&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;hugepages_on() {
    PAGES=$(( $(echo $RAM | tr -d 'G') * 1048576 / 2048))
    mkdir -p /dev/hugepages
    mount -t hugetlbfs hugetlbfs /dev/hugepages
    echo $PAGES &amp;gt; /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
}

hugepages_off() {
    echo 0 &amp;gt; /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
    umount /dev/hugepages
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;QEMU Command&lt;/h2&gt;&lt;h3&gt;Before installing guest OS (Windows 10 used as example)&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ulimit -l $ULIMIT_TARGET

qemu-system-x86_64 \
    -name 'vfio-vm' \
    -vga qxl \
    -nodefaults -enable-kvm -machine q35 \
    -m $RAM -mem-path /dev/hugepages \
    -cpu host,kvm=off,svm=off,topoext,hv_relaxed,hv_spinlocks=0x1fff,hv_time,hv_vapic,hv_vendor_id=novideobad43,hv_vpindex,hv_synic,hv_stimer,hv_frequencies \
    -smp 8,sockets=1,cores=4,threads=2 \
    -rtc clock=host,base=localtime \
    -boot menu=on -boot d \
    -nic tap,ifname=tap0,script=no,downscript=0,model=virtio-net-pci \
    -drive if=pflash,format=raw,readonly,file=$OVMF \
    -drive file=&amp;quot;$VIRTIO&amp;quot;,id=cd1,media=cdrom \
    -drive file=&amp;quot;$WINDOWS&amp;quot;,id=cd2,media=cdrom \
    -device virtio-scsi-pci,id=scsi0 \
    -device scsi-hd,bus=scsi0.0,drive=rootfs \
    -drive file=&amp;quot;$IMG&amp;quot;,id=rootfs,index=0,format=qcow2,media=disk,if=none

ulimit -l $ULIMIT&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;After installing guest OS&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ulimit -l $ULIMIT_TARGET

qemu-system-x86_64 \
    -name 'vfio-vm' \
    -vga none -nographic \
    -nodefaults -enable-kvm -machine q35 \
    -m $RAM -mem-path /dev/hugepages \
    -cpu host,kvm=off,svm=off,topoext,hv_relaxed,hv_spinlocks=0x1fff,hv_time,hv_vapic,hv_vendor_id=novideobad43,hv_vpindex,hv_synic,hv_stimer,hv_frequencies \
    -smp 8,sockets=1,cores=4,threads=2 \
    -rtc clock=host,base=localtime \
    -boot menu=on -boot c \
    -nic tap,ifname=tap0,script=no,downscript=0,model=virtio-net-pci \
    -device vfio-pci,host=$GPU_VIDEO,multifunction=on,x-vga=on \
    -device vfio-pci,host=$GPU_AUDIO \
    -device ivshmem-plain,memdev=ivshmem,bus=pcie.0 \
    -object memory-backend-file,id=ivshmem,share=on,mem-path=/dev/shm/looking-glass,size=32M \
    -device virtio-keyboard-pci \
    -device virtio-mouse-pci \
    -object input-linux,id=kbd0,evdev=/dev/input/by-id/usb-Corsair_Corsair_K70R_Gaming_Keyboard-if02-event-kbd,grab_all=on,repeat=on \
    -object input-linux,id=mouse0,evdev=/dev/input/by-id/usb-Logitech_Gaming_Mouse_G502_0E5F335C3236-event-mouse \
    -object input-linux,id=mouse1,evdev=/dev/input/by-id/usb-Logitech_Gaming_Mouse_G502_0E5F335C3236-if01-event-kbd,grab_all=on,repeat=on \
    -drive if=pflash,format=raw,readonly,file=$OVMF \
    -drive file=&amp;quot;$VIRTIO&amp;quot;,id=cd1,media=cdrom \
    -device virtio-scsi-pci,id=scsi0 \
    -device scsi-hd,bus=scsi0.0,drive=rootfs \
    -drive file=&amp;quot;$IMG&amp;quot;,id=rootfs,index=0,format=qcow2,media=disk,if=none

ulimit -l $ULIMIT&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;Extra&lt;/h1&gt;&lt;h2&gt;Adding USB Devices&lt;/h2&gt;&lt;p&gt;Get vendor and product id from &lt;code&gt;lsusb&lt;/code&gt; and add them to your QEMU command arguments:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;    -device qemu-xhci,id=xhci0 -device usb-host,bus=xhci0.0,vendorid=0x&amp;lt;yourvendorid&amp;gt;,productid=0x&amp;lt;yourproductid&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example for my USB bluetooth receiver:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ lsusb
...
Bus 001 Device 004: ID 0b05:17cb ASUSTek Computer, Inc. Broadcom BCM20702A0 Bluetooth
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;My vendorid is &lt;code&gt;0x0b05&lt;/code&gt; and productid is &lt;code&gt;0x17cb&lt;/code&gt;, so in QEMU it would be:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;    -device qemu-xhci,id=&amp;lt;usb-bus-id&amp;gt; -device usb-host,bus=&amp;lt;usb-bus-id&amp;gt;.0,vendorid=0x0b05,productid=0x17cb&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Set CPU Affinity&lt;/h2&gt;&lt;p&gt;While libvirt makes this more simple, it appears we need a script/function to do it in bare QEMU
Borrowed from &lt;a href=&quot;https://null-src.com/posts/qemu-optimization/post.php#taskset&quot;&gt;here&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;note: uses bash-isms so that's why I put it in a separate file&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
THREAD_LIST=&amp;quot;0,4,1,5,2,6,3,7&amp;quot;
NAME=&amp;quot;vfio-vm&amp;quot;

sleep 20 &amp;amp;&amp;amp;
HOST_THREAD=0
# for each vCPU thread PID
for PID in $(pstree -pa $(pstree -pa $(pidof qemu-system-x86_64) | grep $NAME | awk -F',' '{print $2}' | awk '{print $1}') | grep CPU |  pstree -pa $(pstree -pa $(pidof qemu-system-x86_64) | grep $NAME | cut -d',' -f2 | cut -d' ' -f1) | grep CPU | sort | awk -F',' '{print $2}')
do
    let HOST_THREAD+=1
    # set each vCPU thread PID to next host CPU thread in THREAD_LIST
    echo &amp;quot;taskset -pc $(echo $THREAD_LIST | cut -d',' -f$HOST_THREAD) $PID&amp;quot; | sh
done&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Additional Disk&lt;/h2&gt;&lt;p&gt;You can add another disk by simply copying the arguments for adding the rootfs and slightly modifying
Example for a qcow2 image:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;    -device virtio-scsi-pci,id=&amp;lt;scsi-id&amp;gt; \
    -device scsi-hd,bus=&amp;lt;scsi-id&amp;gt;.0,drive=&amp;lt;drive-id&amp;gt; \
    -drive file=&amp;lt;location&amp;gt;,id=&amp;lt;drive-id&amp;gt;,index=0,format=qcow2,media=disk,if=none&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;No Drives During Installation&lt;/h2&gt;&lt;p&gt;Make sure virtio driver is loaded:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Click Load Driver&lt;/li&gt;&lt;li&gt;Choose virtio-cd disc &amp;gt; amd64 &amp;gt; w10 and press enter&lt;/li&gt;&lt;li&gt;Load Red Hat Virtio SCSI driver&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Looking Glass Not Starting&lt;/h2&gt;&lt;p&gt;Make sure no virtual display like QXL is loaded too (&lt;code&gt;-nographic -vga none&lt;/code&gt; in QEMU)&lt;/p&gt;&lt;h2&gt;JACK Support&lt;/h2&gt;&lt;p&gt;To use JACK instead of Scream, you can use these QEMU arguments&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;-audiodev jack,id=snd0,in.client-name=default,out.client-name=default,in.start-server=off,out.start-server=off,in.exact-name=on,out.exact-name=on,in.connect-ports=system,out.connect-ports=system,in.frequency=48000,out.frequency=48000,timer-period=2048,out.buffer-length=5120 \
-device ich9-intel-hda \
-device hda-output,audiodev=snd0 \&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You might need to change the timer-period and buffer-length if experiencing crackling.
Also you might have to change the controller (ich9-intel-hda) and codec (hda-output) to something else.&lt;/p&gt;&lt;p&gt;To list controller and codecs, run:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;qemu-system-x86_64 -device help | grep hda&lt;/code&gt;&lt;/pre&gt;</content></entry></feed>