<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://thorsell.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://thorsell.io/" rel="alternate" type="text/html" /><updated>2026-05-08T20:23:20+02:00</updated><id>https://thorsell.io/feed.xml</id><title type="html">Erik Thorsell</title><subtitle>Webpage, portfolio, blog, and everything else. Run by Erik Thorsell, DevOps Transformation Leader trying to automate the world.</subtitle><author><name>Erik Thorsell</name></author><entry><title type="html">State of the Homelab 2026 (Network edition)</title><link href="https://thorsell.io/2026/01/08/state-of-the-home-lab-network.html" rel="alternate" type="text/html" title="State of the Homelab 2026 (Network edition)" /><published>2026-01-08T00:00:00+01:00</published><updated>2026-01-08T00:00:00+01:00</updated><id>https://thorsell.io/2026/01/08/state-of-the-home-lab-network</id><content type="html" xml:base="https://thorsell.io/2026/01/08/state-of-the-home-lab-network.html"><![CDATA[<p><em>This is a follow-up post to my <a href="/2026/01/06/state-of-the-home-lab.html">State of the Homelab 2026 (Servers Edition)</a>-post, which talks about my storage / compute philosophy, how my server game has
evolved over the years as well as what hardware I use as of January 2026.</em></p>

<h1 id="background">Background</h1>

<p>As with <em>home computers utilised for “server stuff”</em>, I have a history when it comes to networking which goes a little
bit further than that of the average Internet user.</p>

<p>My very first router, after moving to my own place, was an <a href="https://www.asus.com/se/supportonly/rt-n66u%20(ver.b1)/helpdesk_bios/">Asus
RT-N66U</a>. I think I struck gold with this
purchase, because unknowingly I bought a router that put me on open source and modding early on in my life. I only ran
the router as purchased for a couple of months after which I flashed it with
<a href="https://advancedtomato.com/downloads/router/rt-n66u">Tomato</a><sup id="fnref:tomato" role="doc-noteref"><a href="#fn:tomato" class="footnote" rel="footnote">1</a></sup>. The main issue with this router was that
it was too slow. The apartment I lived in had a <code class="language-plaintext highlighter-rouge">1 000/1 000 Mbps</code> Internet connection, but the poor router would not
push more than a couple of hundred Mbps. I don’t remember, but I doubt I got more than 250 Mbps over WiFi.</p>

<p>An upgrade was in order!</p>

<p>I already touched upon this upgrade in <a href="/2026/01/06/state-of-the-home-lab.html">my previous post</a>:</p>

<blockquote>
  <p>A couple of years later I built my first “home server” which mostly just acted as a router. I did however host a ZNC
Bouncer and an OpenVPN Server.</p>
</blockquote>

<p>More specifically, I built a Debian-based router on top of an <a href="https://www.intel.com/content/www/us/en/products/sku/76531/intel-celeron-processor-j1750-1m-cache-2-41-ghz/specifications.html">Intel Celeron
J1750</a>
(IIRC). I don’t remember the specifications but I do remember that I powered it using a PicoPSU. Initially, I think I
used the Asus router as an access point for WiFi, but I did install a network card with WiFi after a little while.</p>

<h2 id="pfsense">pfSense</h2>

<p>In 2018 I had gotten tired of the Debian box.</p>

<p>After a lot of research I decided to build my own <a href="https://www.pfsense.org/">pfSense</a> router. After additional
research I decided that I did not want to build my own pfSense router and instead ended up buying a ready-made one. I
did however not buy a Netgate router, but one based on a <a href="https://www.pcengines.ch/apu2e4.htm">PC Engine APU2E4</a>. I
paired it with a <a href="https://techspecs.ui.com/unifi/wifi/uap-ac-lite">Unifi AP AC Lite</a> and that combo served me for more
than five years with excellence.</p>

<p>I did not do much with respect to advanced network configuration, initially, but I did try both hosting VPN servers
<em>and</em> routing all egress from my network via VPN just to see if it would work.<sup id="fnref:netflix" role="doc-noteref"><a href="#fn:netflix" class="footnote" rel="footnote">2</a></sup> All in all, pfSense has
worked very well for me. I know that there have been controversies concerning Netgate (and I have considered moving to
both OPNSense and UniFi, more than once) but since I use pfSense professionally I have not been able to motivate myself
to make the migration.</p>

<h2 id="levelling-up">Levelling up</h2>

<p>After adopting pfSense and UniFi I have been focusing less on the hardware/software aspects of my network and more on
its configuration. In particular, this concerns separating devices that connect to my network into tiers and ensuring
proper isolation of lower-tier (less secure/trusted) devices from higher tier ones. I achieve this predominantly using
VLANs, mDNS and regular firewall rules.</p>

<p>I have also abandoned OpenVPN. I had a very quick encounter with WireGuard, but I was never really able to get it to
work the way I wanted it to. Instead, I have migrated all of my VPN use cases to Tailscale. (You can read more about
this migration in <a href="/2025/06/30/tailscale.html">this post</a>.)</p>

<h1 id="current-setup">Current Setup</h1>

<p>As of January 2026, I’m still running pfSense on my router together with pretty much only UniFi networking equipment.
The image below outlines its configuration <em>(click it!)</em>.</p>

<p><a href="https://thorsell.io/assets/images/network-2026-01-08.png"><img src="https://thorsell.io/assets/images/network-2026-01-08.png" alt="Diagram of my network configuration" /></a></p>

<p>The gist of my networking philosophy is to have one VLAN per use case. I also tend to number them such that a lower
number indicates that the devices on that VLAN are more trustworthy. I must admit that this not really hold true for the
setup above, because I trust 30-60 equally little :P Let’s say the rust is: <code class="language-plaintext highlighter-rouge">10 | 20 | &lt;the rest&gt;</code>.</p>

<p>Each VLAN has its own <code class="language-plaintext highlighter-rouge">/24</code> IPv4 range and I have split them such that the first 100 addresses are statically assigned,
leaving the remaining addresses to be used for dynamic assignment via DHCP.</p>

<p>In addition to the above, I have two guiding lights that I try to keep in mind whenever I design a network:</p>

<h3 id="1-difficult-to-shoot-yourself-in-the-foot">(1) Difficult to shoot yourself in the foot</h3>

<p>Above all, it should be difficult to do something that is outright bad!</p>

<p>There are two ways to connect to my network: (1) by plugging in a cable to a switch, and (2) by connecting to an SSID.
I have RJ45 in pretty much every room in the house, but only the ones that are in active use are connected to my main
switch. Additionally, only the ports on the switch that are in active use are enabled. This might sound like a pain to
work with, but it forces me to be deliberate with every new device I plug in.</p>

<blockquote>
  <p>If it’s getting a cable, it needs a port, and the port needs to be enabled and configured accordingly.</p>
</blockquote>

<p>Since I have distinct SSIDs for each WiFi network, the mental model is similar for wireless devices. Instead of
connecting all devices to the same WiFi network and configure firewall rules for groups or specific devices, I can be
sure that a new surveillance camera (connected to the surveillance WiFi) will absolutely not send any frames to
anywhere.</p>

<h3 id="2-observability">(2) Observability</h3>

<p>As mentioned earlier, each VLAN has its own IPv4 DHCP range and they are all coupled such that the third octet is the
same as the VLAN ID. This makes log inspection significantly easier, because as soon as I see <code class="language-plaintext highlighter-rouge">10.1.30.xxx</code> I know that
I’m looking at a camera.</p>

<h1 id="a-remark-concerning-hardware--software">A remark, concerning hardware / software</h1>

<p>A prerequisite for being able to manage a network like mine without going mad is to have a hardware and software stack
that makes it easy to work with. As I wrote in the <a href="#background">background section</a>, I have been happily running
pfSense + UniFi for more than five years and it has been working very well for me. There are a couple of things I
dislike (like UniFi not allowing meshing if you have more than 4 SSIDs<sup id="fnref:ssid" role="doc-noteref"><a href="#fn:ssid" class="footnote" rel="footnote">3</a></sup>) but overall I think it’s an excellent
prosumer combo.</p>

<p>All networking equipment in my house lives on VLAN 1 and I use DHCP the same way on this VLAN as all the other ones.</p>

<p>If I were to start from scratch today, I would either go all in on UniFi or I would look into OPNSense. For my router
that is. I still believe UniFi is the hands down best choice when it comes to switches. Having had a Zyxel managed
switch (I used it together with the PC Engines router, for several years) I really, really, really, appreciate UniFi’s
management tool. The Zyxel was dreadful.</p>

<h1 id="future-improvements">Future improvements</h1>

<p>I have mentioned it several times, but one last time: I would like to refactor my network such that I have fewer
VLANs/SSIDs. Merging <code class="language-plaintext highlighter-rouge">SURVEILLANCE</code> and <code class="language-plaintext highlighter-rouge">IOT_NO_INTERNET</code> would be a good first start and it would allow me to enable
UniFi Meshing. Merging <code class="language-plaintext highlighter-rouge">IOT</code> and <code class="language-plaintext highlighter-rouge">GUEST</code> is probably also feasible.</p>

<!-- --------------------------------------------------------------------------------------------------------------- -->

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:tomato" role="doc-endnote">
      <p>Which seems to be deprecated and archived. <a href="#fnref:tomato" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:netflix" role="doc-endnote">
      <p>One of the things that did not work was Netflix. I just got an error message saying something to the effect
of: “You’re using a VPN. Disable it and try again.” I even called Netflix customer service and asked for a list of
IPs/domains so I could configure my network to <em>not</em> route those via the VPN. They refused and I ended up disabling
the VPN for the sake of domestic peace. <a href="#fnref:netflix" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ssid" role="doc-endnote">
      <p>I have understood this to be more of a hardware problem than a software one. The number of (or the setup of
the) radios in the access points simply cannot handle more than 4 SSIDs. This is one of several reasons that I want
to refactor my network. <a href="#fnref:ssid" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[This is a follow-up post to my State of the Homelab 2026 (Servers Edition)-post, which talks about my storage / compute philosophy, how my server game has evolved over the years as well as what hardware I use as of January 2026.]]></summary></entry><entry><title type="html">State of the Homelab 2026 (Servers edition)</title><link href="https://thorsell.io/2026/01/06/state-of-the-home-lab.html" rel="alternate" type="text/html" title="State of the Homelab 2026 (Servers edition)" /><published>2026-01-06T00:00:00+01:00</published><updated>2026-01-06T00:00:00+01:00</updated><id>https://thorsell.io/2026/01/06/state-of-the-home-lab</id><content type="html" xml:base="https://thorsell.io/2026/01/06/state-of-the-home-lab.html"><![CDATA[<p><em>This post is long overdue, but I’m planning some upgrades for 2026 so I figured I’d document the setup that has been
serving me very well for the past 5 years.</em></p>

<blockquote>
  <p>As my posts tend to be, this is another quite lengthy one. If you only care about my current setup, have a look at the
<a href="#current-setup">Current Setup</a> section.</p>
</blockquote>

<h1 id="background">Background</h1>

<p>Back in 2011 I bought my first NAS, a <a href="https://global.download.synology.com/download/Document/Hardware/DataSheet/DiskStation/13-year/DS213j/enu/Synology_DS213j_Data_Sheet_enu.pdf">Synology
DS213j</a>.
(This scrawny little machine is <em>still</em> serving!) A couple of years later I built my first “home server” which mostly
just acted as a router. I did however host a ZNC Bouncer and an OpenVPN Server. (I had Gbit Internet and the little 2
core, Intel J CPU, actually managed to provide almost 500Mbit both ways over VPN!)</p>

<h2 id="one-server-to-rule-them-all">One server to rule them all</h2>

<p>The fall of 2014 I built my first <em>real</em> server. The purpose was to supersede the Synology NAS and build something
reasonably “production grade”. (Albeit, as a second year university student, I was not familiar with the concept of
<em>production grade</em>.) I did a lot of reading and listened to a lot of podcasts on the subject. At the time, I was a huge
fan of the various <a href="https://www.jupiterbroadcasting.com/">Jupiter Broadcasting</a> shows. In particular, I found the <a href="https://www.bsdnow.tv/">BSD
Now</a> podcast (then hosted by: <a href="http://www.allanjude.com">Allan Jude</a>) very fascinating.
Persuaded by the BSD folks, I decided to go with FreeNAS (which would be renamed to <a href="https://en.wikipedia.org/wiki/TrueNAS">TrueNAS
Core</a> in 2021) as my server OS.</p>

<p>I went all in on FreeNAS and BSD and hosted a handful of applications in jails. At some point I was unable to build some
application for FreeBSD and struggled <em>a lot</em> with running Linux VM:s under FreeNAS to get Docker support. It worked,
but it was not particularly pleasant. In 2016 the woman who would become my wife moved to Sweden, and in with me,
meaning I now had an additional issue: Another user, who had uptime and availability expectations on the few services
she enjoyed using (mostly Plex)… Safe to say, I was becoming more and more frustrated with FreeNAS as an “application
hosting OS”.</p>

<h2 id="a-change-of-philosophy">A change of philosophy</h2>

<p>About the same time as FreeNAS morphed into TrueNAS Core, I was really getting sick and tired of the poor Docker support
in FreeNAS. I was also pushing my little server to its limit compute-wise. So I pulled the peripherals out of my gaming
computer (running Ubuntu) and put it in the bookshelf next to the server. Since I had done a pretty good job creating
unique datasets for my various applications, exposing them as NFS shares and mounting them on the gaming computer was
not unbearably difficult.</p>

<p>Back then, I did this out of frustration and necessity. Today, I encourage everyone who wants to self-host to separate
<em>storage</em> and <em>compute</em>. Doing so allows you to separate your concerns in a very nice way. You can use more purpose
built tools to solve your respective problems and you can dimension your hardware more appropriately.</p>

<p>For instance, back in 2014 I ran FreeNAS on a USB drive (as was customary) but apart from doing two RMA:s on the PSU
(Silverstone sure had some issues with their early SFX units) and migrating to a boot SSD a couple of years later, I am
still using the same hardware today. When I hosted jails and VM:s under FreeNAS, my CPU and RAM utilisation would
frequently be at capacity. Now, more than 10 years later, only serving NFS shares, my storage server is barely breaking
a sweat.</p>

<blockquote>
  <p>Maybe the most important thing is that you can – if things really go south – unplug and nuke your compute machine
without worrying about losing data!</p>
</blockquote>

<h1 id="current-setup">Current setup</h1>

<p>With the background out of the way, you probably (hopefully) understand <em>why</em> I have my homelab setup the way I do.
Let’s have a look at what’s actually running in there.</p>

<h2 id="storage-server">Storage server</h2>

<p>As mentioned earlier, my storage server has remained virtually unchanged for over 10 years. (I’m still running TrueNAS
Core though. I should really take the time to upgrade to Scale.) I am <em>extremely</em> pleased with the Supermicro
motherboard. IPMI is excellent and the four core C3558 CPU is perfect for my workload. I also really like the
Silverstone case.</p>

<p>Parts:</p>
<ul>
  <li>Case: <a href="https://www.silverstonetek.com/en/product/info/server-nas/DS380/">Silverstone DS380</a></li>
  <li>Motherboard w/ integrated CPU: <a href="https://www.supermicro.com/en/products/motherboard/a2sdi-4c-hln4f">Supermicro A2SDi-4C-HLN4F</a></li>
  <li>RAM: 2x 8GB Samsung DDR4 ECC REG @2400MHz</li>
  <li>Boot: 120GB SSD</li>
  <li>PSU: Silverstone SST-ST45SF-G 450W</li>
</ul>

<p>Initially, for storage, I used 4x 3TB but back in 2020 I upgraded to 4x 8TB. I run four disks in Raid-Z2, which might be
a little bit unconventional, but with <a href="https://www.truenas.com/docs/scale/scaletutorials/storage/managepoolsscale/#expanding-a-pool">ZFS vdev
expansion</a> being a
reasonably new thing, I’m looking forward to adding two more 8TB disks to my server in the near future.</p>

<h3 id="a-note-on-data-integrity">A note on data integrity</h3>

<p>I frequently talk to people who ask for advice building their first server or NAS. One common question is that of ECC
memory: <em>Is it worth it?</em></p>

<p>If you’re building a computer box for storing data that is important to you, I believe you should do whatever you can to
protect that data.</p>

<ul>
  <li>Setup your storage solution with parity to handle disk failures. They WILL happen!<sup id="fnref:disks" role="doc-noteref"><a href="#fn:disks" class="footnote" rel="footnote">1</a></sup></li>
  <li>Use a file system that mitigates data corruption (such as ZFS).</li>
  <li>Use ECC memory to protect yourself from corruption when data is written from RAM to disk.</li>
  <li>Backup your data to a second physical location!</li>
</ul>

<h2 id="compute-server">Compute server</h2>

<p>The Ubuntu gaming computer has been the work horse of my homelab for several years. After re-purposing it from gaming to
running Docker services, I sometimes contemplate replacing it with something more “server grade”, but since all data I
care about lives on the TrueNAS machine I’m not too concerned.</p>

<p>Parts:</p>
<ul>
  <li>Case: Fractal Design Era</li>
  <li>Motherboard: Gigabyte B450</li>
  <li>CPU: AMD Ryzen 5 3600 @3.6GHz</li>
  <li>GPU: Asus Radeon RX5500 XT 8GB</li>
  <li>RAM: 2x 16GB Corsair DDR4 @2666MHz</li>
  <li>PSU: EVGA SuperNOVA GM 450W</li>
</ul>

<h2 id="how-i-run-my-services">How I run my services</h2>

<p>I run all of my services as Docker containers, managed using Docker Compose.<sup id="fnref:k8s" role="doc-noteref"><a href="#fn:k8s" class="footnote" rel="footnote">2</a></sup> On the TrueNAS machine, I have one
ZFS Pool which is divided into several datasets. (I don’t quite have a 1:1 mapping between dataset and service, but some
day I might take the time to fix that.) The various datasets are then exposed as NFS Shares and mounted on the Ubuntu
machine.</p>

<p>Some shares are mounted directly by Docker, such as my Nextcloud instance:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumes</span><span class="pi">:</span>
  <span class="na">files</span><span class="pi">:</span>
    <span class="na">driver_opts</span><span class="pi">:</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">nfs"</span>
      <span class="na">o</span><span class="pi">:</span> <span class="s2">"</span><span class="s">addr=truenas.home.arpa,rw"</span>
      <span class="na">device</span><span class="pi">:</span> <span class="s2">"</span><span class="s">:/mnt/volume1/nextcloud"</span>
</code></pre></div></div>

<p>whereas some less critical services use a common bind mount:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">frigate</span><span class="pi">:</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/mnt/container_volumes/frigate-runtime/:/media/frigate</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Networking?</strong></p>

  <p>I have a quite elaborate network setup too. If you’re interested in another post , shoot me an email!</p>
</blockquote>

<!-- --------------------------------------------------------------------------------------------------------------- -->

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:disks" role="doc-endnote">
      <p>I believe I have had four disk failures over the past 10 years. One WD Red (the old version, before there
     was a Pro variant) and three Toshiba N300s. The two 2TB WD Red disks I put in my DS213j are still going strong.
     I feel reckless even writing this though. I really need to replace that machine… <a href="#fnref:disks" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:k8s" role="doc-endnote">
      <p>Every now and then I contemplate migrating to Kubernetes. Maybe k3s? Maybe a single-node-cluster based on Talos?
Maybe I should just go all in and buy three mini PC:s? But my current setup works and it works quite well, so I’m in
no hurry. <a href="#fnref:k8s" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[This post is long overdue, but I’m planning some upgrades for 2026 so I figured I’d document the setup that has been serving me very well for the past 5 years.]]></summary></entry><entry><title type="html">Kubernetes exposes your DevOps debt</title><link href="https://thorsell.io/2025/12/16/devops-debt-and-kubernetes.html" rel="alternate" type="text/html" title="Kubernetes exposes your DevOps debt" /><published>2025-12-16T00:00:00+01:00</published><updated>2025-12-16T00:00:00+01:00</updated><id>https://thorsell.io/2025/12/16/devops-debt-and-kubernetes</id><content type="html" xml:base="https://thorsell.io/2025/12/16/devops-debt-and-kubernetes.html"><![CDATA[<p>Kubernetes is a <em>point of contention</em>.</p>

<p>Some look at it in awe and believe <em>it</em> will solve most (if not all) of their application’s problems.</p>

<p>If the app does not scale under load: <em>Use Kubernetes.</em><br />
If the app cannot handle zero-down-time upgrades: <em>Use Kubernetes.</em><br />
If the team working on the app is too slow: <strong><em>Use Kubernetes!!</em></strong></p>

<p>Others look at Kubernetes with a mix of disgust and fear.</p>

<p><em>Kubernetes</em> is a huge beast of complexity.<br />
<em>Kubernetes</em> requires an entire new operational skill set that most teams do not have.<br />
<em>Kubernetes</em> is not even necessary!</p>

<p>What both sides tend to miss is not what Kubernetes <em>is</em>, but what it is <em>for</em>.</p>

<p>Kubernetes does not magically make applications scalable, resilient, or fast to change. Instead, it provides a powerful
execution environment for applications that are already designed, built, and operated with those qualities in mind. This
is why organisations migrating to Kubernetes should spend far less time debating YAML manifests and far more time asking
how well their applications and teams adhere to the fundamentals of DevOps.</p>

<h2 id="devops-fundamentals">DevOps fundamentals</h2>

<p>As I have said before:</p>

<blockquote>
  <p>DevOps is everything you need, technically and organisationally, to successfully manage the entire life cycle of your
product.</p>
</blockquote>

<p>By better understanding: <a href="https://itrevolution.com/articles/the-three-ways-principles-underpinning-devops/">Flow, Feedback and Continual Learning; the three fundamental principles that underpin the
entire DevOps movement</a>, we become
better informed about all aspects of the Software Development Life Cycle (SDLC). As we grasp the importance of
<em>feedback</em> we can understand why and how we gather and act upon information from our users. When we prioritise <em>flow</em>,
we start to find – and remove – bottlenecks in our processes. Lastly, when we have a way of working where we feel
comfortable making frequent changes to our products, we can start to experiment and continuously learn new things which
will help us win the marketplace.</p>

<p>The best part? Your organisation can build on top of the three DevOps principles without even looking at Kubernetes.
Here are just a few examples, organisational and technical:</p>

<ol>
  <li>Ensure there is a clear mapping between each development team and their output, meaning each team takes
responsibility for the entire SDLC of the component, service, or application they develop.</li>
  <li>Make your releases smaller and do them more frequently.</li>
  <li>Visualise your work and ensure <em>operational improvement</em> is prioritised.</li>
  <li>Automate. Automate. Automate! Declarative delivery and GitOps-style workflows do not require Kubernetes.</li>
  <li>Build your application with observability and fault tolerance in mind.</li>
</ol>

<p>Adhering to the above will yield a better product and as a bonus you are well positioned to migrate to Kubernetes,
should the need arise.</p>

<h2 id="what-is-kubernetes-really">What is Kubernetes, really?</h2>

<p>Kubernetes is a container orchestrator. No more. No less. I don’t mean to sell Kubernetes short – it’s an amazing feat
of engineering – but you must not be led astray.</p>

<p>Kubernetes helps you deploy an arbitrary number of containerised applications across an arbitrary number of nodes,
according to arbitrary rules concerning resource utilisation. Kubernetes follows your arbitrary rules to scale up/down
the number of application instances. Kubernetes even helps you roll out upgrades of your applications in different ways
that ensures there is always at least one instance available. If you tell Kubernetes exactly how to handle your
<em>deployment</em>, it will do so – completely automatically!</p>

<p><em>Arbitrary.</em></p>

<p>Kubernetes is an extremely competent rule engine, but you have to write the rules! You are responsible for taking
everything arbitrary about your application and make it explicit. By better understanding how Kubernetes works, we can
learn how to write these rules, but Kubernetes does not:</p>

<ol>
  <li>Tell you how the architecture for your application should look in order to handle zero-downtime upgrades, properly
utilise scaling, or be more fault tolerant;</li>
  <li>Help you implement the architecture;</li>
  <li>Create or enforce any of the five examples I listed under <a href="#devops-fundamentals">DevOps fundamentals</a>.</li>
</ol>

<p>If you have five teams building a monolithic .NET application which is then handed over to a separate build team to be
containerised and deployed in Kubernetes, you have gained absolutely nothing. On the contrary, you have added an
enormous amount of complexity (i.e. Kubernetes) without attaining any benefits from the system.</p>

<p>If you have ever thought: “We need to use Kubernetes to solve &lt;any of the issues above&gt;”, you will be disappointed.
The answer is not in Kubernetes itself.</p>

<h2 id="kubernetes-as-a-mirror-for-your-devops-debt">Kubernetes as a mirror for your DevOps debt</h2>

<p>Kubernetes does not <em>introduce</em> most of the problems organisations struggle with, but if you want to use Kubernetes
properly you will inevitably be exposed to them.</p>

<p>Poor (or non-existing) <em>Configuration as Code</em> gets turned into poorly defined Kubernetes resources (the arbitrary rules
are not specified properly for your application). Fragile deployment practices surface when rollouts require manual
intervention and deployments reboot endlessly when applications are not built to handle statelessness. Not to mention
troubleshooting when you rely on logs from a pod that continuously keeps rebooting.</p>

<p>These are all consequences of having a large DevOps debt.</p>

<p>Just like technical debt, DevOps debt is accumulated slowly. Manual processes that “work for now”. Releases that are
large and far between, because doing them feels risky. Being OK with a way of working that relies on heroics. None of
this breaks immediately. In fact, many of these patterns are actively rewarded in the short term.</p>

<p>If you start using Kubernetes, you will be made painfully aware of your DevOps debt.</p>

<h2 id="so-should-you-use-kubernetes">So, should you use Kubernetes?</h2>

<p>Maybe.</p>

<p>I really want to stress the importance of the DevOps fundamentals. You will not have a good time in Kubernetes if you
fail to follow these principles. I don’t believe you will have a good time outside of Kubernetes either, but you will at
least not be fighting your entire runtime environment.</p>

<h3 id="if-you-are-starting-from-scratch">If you are starting from scratch</h3>

<p>If you have Kubernetes competence in your organisation and you foresee that you will benefit from the capabilities
provided by Kubernetes, go for it! Greenfield development is lovely and you have the opportunity to build something
properly <em>cloud native</em>. Just make sure you strike a good balance between: “Building the perfect Kubernetes application.”
and “Building an application that your users want to use.”. It’s all too easy to get stuck in the weeds.</p>

<p>If you do not have Kubernetes competence or don’t intend to build an application that need its features, don’t bother. I
<em>would</em> however recommend that you containerise your application. Doing so helps you think about interfaces,
dependencies, building and deploying your applications early in your SDLC.</p>

<h3 id="if-you-are-migrating">If you are migrating</h3>

<p>Start with refactoring! If your application is currently composed of tightly coupled services, without clear interfaces,
and code being inherited all over the place; start by fixing this. Create an application that <em>can</em> be containerised by
its different components, <em>then</em> do the actual containerisation, <em>then</em> evaluate whether you can fix your issues without
Kubernetes.</p>

<p>If you cannot – and only if you cannot – start migrating your newly containerised services to Kubernetes.</p>

<blockquote>
  <p>Kubernetes is not a shortcut to DevOps maturity. It is a very efficient way of discovering how much you are missing.</p>
</blockquote>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[Kubernetes is a point of contention.]]></summary></entry><entry><title type="html">Estimates – a necessary evil?</title><link href="https://thorsell.io/2025/12/07/estimates.html" rel="alternate" type="text/html" title="Estimates – a necessary evil?" /><published>2025-12-07T00:00:00+01:00</published><updated>2025-12-07T00:00:00+01:00</updated><id>https://thorsell.io/2025/12/07/estimates</id><content type="html" xml:base="https://thorsell.io/2025/12/07/estimates.html"><![CDATA[<blockquote>
  <p><strong>Product Owner:</strong> Hey, how long do you believe <code class="language-plaintext highlighter-rouge">Feature F</code> will take?</p>

  <p><strong>Developer:</strong> Idk. We haven’t even started working on it and it’s bound to stir up some old issues.</p>
</blockquote>

<p>Estimates come in various disguises, but when you peek under the trench coat there is always the question:</p>

<p align="center">
"How long -- and using what amount of resources -- will be required to do <code>X</code>?"
</p>

<p>When I wear the <em>developer hat</em>, it can be infuriating to attempt to give an answer. It’s difficult to estimate (or the
product owner could do it themselves) and a lot of the time it can be difficult to see why the estimate is even
important.</p>

<p>When I wear the <em>product owner hat</em>, estimates are a crucial piece of the puzzle that must be laid in an attempt to plan
the short <em>and</em> long term life cycle of a product.</p>

<p>In this post I want to attempt to explore and elaborate on both sides, in an attempt to make developers understand <em>why
estimates are important to product owners</em> and in order to help product owners see <em>why developers so often despise
having to estimate their work</em>.</p>

<h2 id="why-the-po-wants-you-to-estimate">Why the PO wants you to estimate</h2>

<p>As a Product Owner (PO), I am responsible for <em>learning the market and customers’ needs</em> and translating these into
<em>feature requests which developers can turn into actual features in our products</em>. The means varies, but most
organisations have some sort of backlog in which <em>things to be acted upon</em> are placed while they await being <em>picked up</em>
by some developer or development team. We call these <em>things</em> user stories, issues, tickets, tasks, <em>and probably many
other things</em>… The important thing for this discussion is that the items in the backlog are candidates for being
implemented in our product and it’s the PO’s job to prioritise the backlog.</p>

<p>Why does the backlog need to be prioritised?</p>

<p>Because the inflow of items to the backlog is (pretty much always) higher than the speed at which the developers can
implement them. Ergo, if the PO does not constantly <em>learn the market and customers’ needs</em> and prioritise the backlog
accordingly, the developers might implement features that the users of the product are not interested in. Worst case?
Existing users stop using the product and no new users buy it which will ultimately lead to bankruptcy.</p>

<h3 id="but-what-about-the-estimates">But what about the estimates?</h3>

<p>The above makes sense – I hope – but it doesn’t really pinpoint the need for estimates. Unfortunately, the job of a PO
is not as easy as always prioritising in accordance to whatever the market wants. More often than not, the PO must also
consider pre-communicated release dates and manage expectations.</p>

<blockquote>
  <p>I hate when release dates are communicated in advance. The only thing worse than release dates that are set in stone
months ahead of time (I’m looking at you, Mr 12-week-increments-SAFe) are releases with pre-communicated content.
Unfortunately, both are common. Often combined.</p>
</blockquote>

<p>Imagine a backlog in which resides a really big feature. Something that is sought after, but will take a lot of time and
resources to implement. The same backlog has a handful of smaller features which are not as requested as the big one.
The PO would really like to include the big feature in the next release, but the next release date is not so far away.
If the PO prioritises the big feature but it’s not done in time for <em>the already communicated release date</em>, the release
will be severely lacking and considered a failure. In that case, the PO would rather include a couple of the smaller
features. A safer bet, but the payoff is smaller.</p>

<p><strong>THIS</strong> is why estimates matter so much to product owners. They must constantly run the above equation when they
prioritise the teams’ backlogs. A constant risk/reward balancing act. They undoubtedly need help from the experts (the
developers) to better understand the ramifications of the features they are proposing. If POs do not understand how big
different <em>work packages</em> are, they cannot do their jobs in an effective way.</p>

<h3 id="it-gets-worse">It gets worse</h3>

<p>Instead of one PO there are now a couple of them. They are responsible for different <em>parts</em> of a larger product which
requires the POs to coordinate both the date <em>and</em> the content of their releases. There is probably a <em>main backlog</em>
describing upcoming features in the final product, as well as <em>team backlogs</em> where each team are assigned puzzle pieces
which must be implemented and integrated in a coordinated fashion.</p>

<p>This is painful in multiple ways, but the most obvious issue is that – in order to have a functioning release – the
POs must agree on the prioritisation of the <em>main backlog</em> and this will in turn affect the prioritisation of the <em>team
backlogs</em>. The POs must each acquire information about how long it will take (and how costly it will be) to implement
and to integrate the puzzle piece(s) they are responsible for into a cohesive feature. The tool for acquiring this
<em>idea</em>?</p>

<p align="center">
    Estimates.
</p>

<h2 id="technical-debt">Technical debt</h2>

<p>Programming is a craft. An art. My art, to some extent. I’m in my happy place when I get to succumb to a tricky task and
surface a couple of days later with a solution to a problem that initially seemed impossible. As a developer, I want to
build the best possible product. I dislike shortcuts. Half-arsed solutions. <em>Fixes.</em> Not because a single shortcut or
fix will destroy a product, but because the <a href="https://en.wikipedia.org/wiki/Technical_debt"><em>technical debt</em></a> they
incur will accumulate over time and eventually erode the product from the inside out; making it ever more difficult to
work with it and ultimately cause it to break.</p>

<p>Technical debt is – I believe – the main reason for conflict between a PO and a development team. A not so technically
inclined PO will fail to see how detrimental technical debt is to the product and how painful it is for the developers
to work in a code base with a high amount of debt.</p>

<p>Put in other words: If I’m tasked with implementing a new feature and I come across something in the code that is
obviously smelly, error prone, or just not very good, I want to leave the code in better shape than I found it. Not
taking time to “payoff” such debt <em>once</em> might not be the end of the world, but the hard coded quick-fix that you know
ought to be generalised will likely bite you down the road. And if you have ignored updating dependencies for a couple
of months and find yourself in a situation where you <em>need</em> to upgrade <code class="language-plaintext highlighter-rouge">Package 1</code>, but it depends on a newer version of
<code class="language-plaintext highlighter-rouge">Packages 2 &amp; 3</code>, which in turn requires a framework upgrade… Let’s just say the feature you’re working on will take
a while longer.</p>

<h2 id="why-developers-hate-estimates">Why developers HATE estimates</h2>

<p>When a PO asks: “How long will it take to implement <code class="language-plaintext highlighter-rouge">Feature F</code>?”, they aren’t just asking the developers to estimate
the amount of time they think it will take to write the code for the feature. A good PO understands that implementing a
new feature is an iterative process and that <em>integration hell</em> is a thing. An even better PO understands that they are
also asking the team to estimate how many unforeseen issues they will encounter while implementing the feature.</p>

<p>This detail: <em>The unforeseen issues</em>, which the PO asks the developers to foresee, is key. It is – per definition –
not possible to foresee something unforeseeable.</p>

<p>Many developers I’ve met dislike uncertainty. One of the things they appreciate most about coding is the deterministic
aspect of it. You run the same program again and again and it returns the same results.<sup id="fnref:determinism" role="doc-noteref"><a href="#fn:determinism" class="footnote" rel="footnote">1</a></sup> The journey on
which we travel while writing the code is, however, not particularly deterministic.</p>

<p>It is true, that the more you code and the more familiar you get with a codebase, the more accurate your estimates will
be. However, just the other day I was working on an issue which I had estimated would take <em>approximately two days</em>. All
of a sudden, I realised that the simple change required updating a shared component that had been tightly coupled years
ago. When I touched that code, dozens of failing tests appeared, each revealing another hidden dependency. Fixing those
uncovered yet another module depending on outdated patterns. Halfway through, we decided we had to refactor the entire
flow just to make the original change safe. My “two-day task” turned into two weeks of archaeological software
excavation.</p>

<p>Could we have solved this quicker by not caring so much about the amount of technical debt we left in our wake?
Probably.</p>

<p>Would we have encountered a two <em>month</em> excavation in the future? Probably.</p>

<h3 id="it-gets-worse-1">It gets worse</h3>

<p><a href="https://www.merriam-webster.com/dictionary/estimate">According to Merriam-Webster</a>: <em>estimate</em> is defined as:</p>

<blockquote>
  <p>To judge tentatively or approximately the value, worth, or significance of.</p>
</blockquote>

<p>The very definition of <em>estimates</em> tells us that they are either <em>tentative</em> or <em>approximate</em>. As a developer, I choose
to interpret the <em>or</em> as meaning that it could even be both.</p>

<p>When I started my career as a software developer, I really did not have an issue with estimates. We would refine our
backlog and I would gladly give an estimate on various items. (1) Because I was fresh out of university and wanted to
prove myself by doing a good job and not being too difficult, but more importantly: (2) because I had not understood
that my estimates would soon be used against me.</p>

<p>I soon learned that my team’s estimates were not interpreted and used as <em>estimates</em>. They were used as <em>deadlines</em>. If
we broke down a feature into its reasonable components (an error prone science, which introduces uncertainties, on its
own) and estimated the parts accordingly, the PO would often take the sum of the parts and communicate it to their
colleagues as: “This is the time we will be done.”</p>

<p>Two things came out of this:</p>

<ol>
  <li>My team (consisting mostly of newly graduated developers) became much more reluctant to estimate.</li>
  <li>When we estimated we always padded our <em>actual beliefs</em>, significantly, to give ourselves a buffer.</li>
</ol>

<p>The estimates stopped being estimates. They became safety railings against being held accountable for unreasonable
expectations.</p>

<h2 id="the-clash">The clash</h2>

<p>Do you see the problem?</p>

<p>Do you see a solution?</p>

<p>I believe the overarching problem with estimates stems from expectations. Somewhere, someone, communicates <em>something</em>
to the users/customers of the product, which sets expectations the rest of the organisation are then forced to live up
to. In a small company, it might very well be the PO who does that communication but in a larger organisation the PO is
likely as helpless as the developers w.r.t. having a say about the product’s roadmap.</p>

<p>The “solution” is simple: Stop communicating new features in advance. Stop setting more or less arbitrary
deadlines<sup id="fnref:deadlines" role="doc-noteref"><a href="#fn:deadlines" class="footnote" rel="footnote">2</a></sup>. Let the PO tell the developers what features they want, in what order, and let the developers do
what they do best: Code!</p>

<p>But these deadlines are there for a reason. If your company builds a product which assists people doing their yearly tax
returns, a missed delivery window will result in the entire revenue opportunity for that year being missed. Resources
(most often in terms of salaries to employees) will have been poured into a project and if there’s no payoff in terms
of additional sales, it could lead to a need for finding other ways to reclaim those resources; often in terms of
reduced costs, which universally means: lay-offs.</p>

<p>Therefore, it’s in everyone’s best interest to play along. We play the estimates game even though it’s a bad way (but
also the best we know of) to help each other do our respective jobs.</p>

<h1 id="what-about-devops">What about DevOps?</h1>

<p><em>You didn’t think I’d miss an opportunity to talk about DevOps, did you?</em></p>

<p><em>Flow</em> is a key concept within DevOps which describes an organisation’s ability to reduce bottlenecks and increase the
pace at which they are able to deliver new versions of their product(s). High flow is synonymous with frequent
deliveries and updates of our product(s).</p>

<p>The concepts from DevOps do not directly address the issue with estimates, but there are tools which can be used to
reduce the risk associated with delivering software. Flow can inform how we tackle technical debt and how we make sure
we don’t fall behind on our dependencies. Flow can also help us identify issues in our product’s life cycle as well as
help us understand how to get rid of the issues.</p>

<p>Flow is one of <a href="https://itrevolution.com/articles/the-three-ways-principles-underpinning-devops/"><em>The Three Ways</em></a> in
DevOps and if you want to learn more, feel free to reach out. I give presentations on various topics related to DevOps
and I can come to your company and give a course about DevOps tailored to your company’s needs.</p>

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

<p>Estimates – as defined in the English language – isn’t really the problem here. The problem is when <em>estimates</em> are
treated as predictions, deadlines, and used to put pressure on developers who are just trying to do their jobs.
Estimates – the way they are used in our industry today – hurts people and reduces the psychological safety in our
organisations. I believe we would be better off if we could work in a way that allows developers to be transparent and
continuously communicate updated estimates as development progresses.</p>

<p>Then again, product owners are people too! As developers we must understand that POs are under pressure too. We must
help them and the best way to help them is to continuously provide them with updates about how development is
progressing and whether we have encountered anything that we believe will significantly alter the original estimate we
gave.</p>

<!-- -->

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:determinism" role="doc-endnote">
      <p>If you ever find yourself in a situation where this is not true, you’re either dealing with
concurrency or undefined behaviour – in which case all bets are off. At that point, the computer is no longer a
machine, it’s a mischievous roommate. <a href="#fnref:determinism" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:deadlines" role="doc-endnote">
      <p>These deadlines are more often than not informed by quarterly reports (at least in publicly traded
companies), holidays, or other external events. Calling them arbitrary might be unjust. <a href="#fnref:deadlines" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[Product Owner: Hey, how long do you believe Feature F will take? Developer: Idk. We haven’t even started working on it and it’s bound to stir up some old issues.]]></summary></entry><entry><title type="html">Writeup for Cert.se’s CTF 2025</title><link href="https://thorsell.io/2025/10/02/cert-ctf.html" rel="alternate" type="text/html" title="Writeup for Cert.se’s CTF 2025" /><published>2025-10-02T00:00:00+02:00</published><updated>2025-10-02T00:00:00+02:00</updated><id>https://thorsell.io/2025/10/02/cert-ctf</id><content type="html" xml:base="https://thorsell.io/2025/10/02/cert-ctf.html"><![CDATA[<p>This is a writeup for <a href="https://www.cert.se/2025/10/antar-du-var-utmaning.html">CERT-SE’s CTF from 2025</a>.</p>

<h1 id="the-goal">The goal</h1>

<p>Quoted from the CERT-SE site:</p>

<blockquote>
  <p>In the .zip file below is a network dump (PCAP) that contains a total of ten flags. All of these have the format
<code class="language-plaintext highlighter-rouge">ctf[string]</code> or <code class="language-plaintext highlighter-rouge">CTF[STRING]</code>.</p>
</blockquote>

<h1 id="low-hanging-fruits">Low hanging fruits</h1>

<p>The lowest hanging fruit possible would be any plain text flags in the actual .pcap, so:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> strings cert-se_ctf2025.pcap | rg <span class="nt">-i</span> <span class="s1">'ctf\['</span>

ctf[strings_or_cat?]
</code></pre></div></div>

<p>We have flag: <code class="language-plaintext highlighter-rouge">ctf[strings_or_cat?]</code></p>

<h1 id="the-file-is-corrupt">The file is corrupt?!</h1>

<p>The very first thing I noticed when I opened the <code class="language-plaintext highlighter-rouge">.pcap</code> in Wireshark was an error message:</p>

<blockquote>
  <p>The capture file appears to be damaged or corrupt. (pcap: File has 16795209-byte packet, bigger than maximum of
262144)</p>
</blockquote>

<p>I double checked that I had not done anything stupid when I downloaded the ZIP archive. Also, I found it <em>very</em> unlikely
that CERT-SE would provide a broken .pcap by mistake. There must be a flag here.</p>

<p>Each packet in a .pcap starts with a 16-byte header (timestamp, captured length, original length). By walking through
those headers and stopping when the captured length (incl_len) was larger than the mentioned maximum, I could pinpoint
where parsing fails.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">sys</span><span class="p">,</span> <span class="n">struct</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">gh</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="mi">24</span><span class="p">)</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">gh</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">24</span><span class="p">:</span>
        <span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="s">"not a pcap (too short)"</span><span class="p">)</span>
    <span class="n">m</span> <span class="o">=</span> <span class="n">gh</span><span class="p">[:</span><span class="mi">4</span><span class="p">]</span>
    <span class="k">if</span>   <span class="n">m</span> <span class="o">==</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd4\xc3\xb2\xa1</span><span class="s">"</span><span class="p">:</span> <span class="n">end</span> <span class="o">=</span> <span class="s">"&lt;"</span>   <span class="c1"># LE, usec
</span>    <span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xa1\xb2\xc3\xd4</span><span class="s">"</span><span class="p">:</span> <span class="n">end</span> <span class="o">=</span> <span class="s">"&gt;"</span>   <span class="c1"># BE, usec
</span>    <span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x4d\x3c\xb2\xa1</span><span class="s">"</span><span class="p">:</span> <span class="n">end</span> <span class="o">=</span> <span class="s">"&lt;"</span>   <span class="c1"># LE, nsec
</span>    <span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xa1\xb2\x3c\x4d</span><span class="s">"</span><span class="p">:</span> <span class="n">end</span> <span class="o">=</span> <span class="s">"&gt;"</span>   <span class="c1"># BE, nsec
</span>    <span class="k">else</span><span class="p">:</span> <span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="s">"unknown magic"</span><span class="p">)</span>

    <span class="n">pkt</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">off</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">tell</span><span class="p">()</span>
        <span class="n">hdr</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>

        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">hdr</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"EOF at packet"</span><span class="p">,</span> <span class="n">pkt</span><span class="p">)</span>
            <span class="k">break</span>

        <span class="n">ts_sec</span><span class="p">,</span> <span class="n">ts_usec</span><span class="p">,</span> <span class="n">incl</span><span class="p">,</span> <span class="n">orig</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="n">unpack</span><span class="p">(</span><span class="n">end</span> <span class="o">+</span> <span class="s">"IIII"</span><span class="p">,</span> <span class="n">hdr</span><span class="p">)</span>

        <span class="k">if</span> <span class="n">incl</span> <span class="o">&gt;</span> <span class="mi">262144</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"Broken:"</span><span class="p">,</span> <span class="n">pkt</span><span class="p">)</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"offset:"</span><span class="p">,</span> <span class="n">off</span><span class="p">,</span> <span class="s">"incl_len:"</span><span class="p">,</span> <span class="n">incl</span><span class="p">,</span> <span class="s">"orig_len:"</span><span class="p">,</span> <span class="n">orig</span><span class="p">)</span>
            <span class="k">break</span>

        <span class="n">f</span><span class="p">.</span><span class="n">seek</span><span class="p">(</span><span class="n">incl</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">pkt</span> <span class="o">+=</span> <span class="mi">1</span>
</code></pre></div></div>

<p>Out the other end came:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> python script.py cert-se_ctf2025.pcap

Broken: 3011
offset: 29102359 incl_len: 16795209 orig_len: 1207959809
</code></pre></div></div>

<p>So at byte <code class="language-plaintext highlighter-rouge">29 102 359</code> (package 3011) something starts that is <em>not</em> a regular 16 byte .pcap payload.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">$&gt;</span> <span class="nb">dd </span><span class="k">if</span><span class="o">=</span>cert-se_ctf2025.pcap <span class="nv">bs</span><span class="o">=</span>1 <span class="nv">skip</span><span class="o">=</span>29102359 <span class="nv">count</span><span class="o">=</span>16 2&gt;/dev/null | xxd

00000000: ffd8 ffe0 0010 4a46 4946 0001 0101 0048  ......JFIF.....H
</code></pre></div></div>

<p align="center">
    That is likely an image!!
</p>

<p>I hoped for the best and went for it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>cert-se_ctf2025.pcap <span class="nv">of</span><span class="o">=</span>image.jpg <span class="nv">bs</span><span class="o">=</span>1 <span class="nv">skip</span><span class="o">=</span>29102359 <span class="nv">status</span><span class="o">=</span>none
</code></pre></div></div>

<p>and out the other came:</p>

<p align="center">
  <img src="https://thorsell.io/assets/images/certse-ctf25-9-solution.jpg" />
</p>

<p>We have flag: <code class="language-plaintext highlighter-rouge">ctf[carve_carve]</code></p>

<h1 id="following-tcp-streams">Following TCP Streams</h1>

<p>My goto from last year (that I figured I’d try again this year) was to simply go through the various TCP streams in the
.pcap.</p>

<h2 id="tcp-stream-6">TCP Stream 6</h2>

<p>TCP Stream 6 contains some HTTP data. More precisely, an index.html file.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span> 
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">sv</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;</span> 
  <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width,initial-scale=1"</span><span class="nt">&gt;</span> 
  <span class="nt">&lt;title&gt;</span>The sky above the port was the color of television, tuned to a dead channel.<span class="nt">&lt;/title&gt;</span>
  <span class="nt">&lt;style&gt;</span>

  <span class="nt">body</span> <span class="p">{</span>
      <span class="nl">background-color</span><span class="p">:</span> <span class="no">black</span><span class="p">;</span>
      <span class="nl">margin</span><span class="p">:</span> <span class="m">3em</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nt">span</span> <span class="p">{</span>
      <span class="nl">height</span><span class="p">:</span> <span class="m">1em</span><span class="p">;</span>
      <span class="nl">width</span><span class="p">:</span> <span class="m">1em</span><span class="p">;</span>
      <span class="nl">background-color</span><span class="p">:</span> <span class="no">darkgreen</span><span class="p">;</span>
      <span class="nl">margin</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
      <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
      <span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span>
      <span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="p">}</span>

<span class="nt">span</span><span class="nd">:nth-of-type</span><span class="o">(</span><span class="err">10</span><span class="o">),</span> <span class="nt">span</span><span class="nd">:n-th-of-type</span><span class="o">(</span><span class="err">1002</span><span class="o">),</span> <span class="o">[...</span> <span class="nt">a</span> <span class="nt">lot</span> <span class="nt">of</span> <span class="nt">this</span><span class="o">]</span>

<span class="p">{</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="no">lightgreen</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">&lt;/style&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
  <span class="nt">&lt;span&gt;&lt;/span&gt;</span> <span class="nt">&lt;span&gt;&lt;/span&gt;</span> [a lot of this too]
</code></pre></div></div>

<h3 id="a-qr-code">A QR Code?</h3>

<p>I don’t read fluent HTML so I just opened the file in my browser instead. The page showed boxes – many boxes –
coloured light green and dark green respectively. My immediate idea was to zoom in/out until the boxes re-ordered
themselves into a flag. Well, no luck.</p>

<p align="center">
  <img src="https://thorsell.io/assets/images/certse-ctf25-6-grid.png" />
</p>

<h3 id="lets-dig-deeper">Let’s dig deeper</h3>

<p>The page also has a title:</p>

<blockquote>
  <p>The sky above the port was the color of television, tuned to a dead channel.</p>
</blockquote>

<p>Which seems to be a quote from Neuromancer, by William Gibson.</p>

<p>Being born in the 90’s, a TV tuned to a dead channel would show “the war of the ants” (black and white, static). Hence,
I believe I am actually on to something w.r.t. my QR Code above. At least, I believe, I should be working with the boxes
as pixels/binary data.</p>

<h4 id="all-the-ways-to-look-at-pixels">All the ways to look at pixels</h4>

<p>I wrote a Python script to generate as many permutations as I could think about.</p>

<ul>
  <li>Different widths</li>
  <li>Inversions</li>
  <li>Rotations</li>
  <li>Use odd / even columns / rows in different ways</li>
</ul>

<p>Nothing yielded an image with a clear <code class="language-plaintext highlighter-rouge">CTF[</code> in it :(</p>

<ul>
  <li>Think of the data as binary</li>
  <li>Maybe binary + compression?</li>
  <li>zlib</li>
  <li>gzip</li>
  <li>lz4</li>
</ul>

<p>No bueno.</p>

<p>What about Morse code?</p>

<p>Nope.</p>

<h4 id="back-to-the-indexhtml-itself">Back to the index.html itself</h4>

<p>What about looking at only the light green boxes? Is there something with their indices? There seems to be 1 471 light
green boxes and 2 625 dark green boxes. 1 471 is prime – is that a hint?!</p>

<p><em>At this point I moved on.</em></p>

<h2 id="tcp-stream-7">TCP Stream 7</h2>

<p>TCP Stream 7 seems to be the same session (is that the correct nomenclature?) as we saw in Stream 6, but instead of
doing a <code class="language-plaintext highlighter-rouge">GET</code> of <code class="language-plaintext highlighter-rouge">/index.html</code> it’s a <code class="language-plaintext highlighter-rouge">GET</code> of <code class="language-plaintext highlighter-rouge">/favicon.ico</code>.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /favicon.ico HTTP/1.1
Host: 192.168.122.1:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://192.168.122.1:8000/index.html
Priority: u=6

HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.3
Date: Thu, 25 Sep 2025 14:05:02 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

&lt;!DOCTYPE HTML&gt;
&lt;html lang="en"&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;title&gt;Error response&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;Error response&lt;/h1&gt;
        &lt;p&gt;Error code: 404&lt;/p&gt;
        &lt;p&gt;Message: File not found.&lt;/p&gt;
        &lt;p&gt;Error code explanation: 404 - Nothing matches the given URI.&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>I don’t really know what to look for here.</p>

<h2 id="tcp-8">TCP 8</h2>

<p>TCP Stream 8 contains an email sent from CTF Tech Support to dear user Nessie, who needs help with her tabs! If we take
a look at the data we can find Nessie’s plea for help:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>From: hdesk@ctftechsupport.se
To: nessie@loch.com
Subject: RE: I LOST MY TABS??? [Ticket#478291]

--===============0296359107785540648==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Dear Nessie! I have recovered your beloved tabs and you should be able to resume your work,
whatever you are doing. See attachment and follow the guide I gave to you by the dock earlier today.
--===============0296359107785540648==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="sessionstore.jsonlz4"
Content-Type: application/octet-stream

bW96THo0MADWUgAA8AF7InZlcnNpb24iOlsic2VzCwDyDXJlc3RvcmUiLDFdLCJ3aW5kb3dzIjpb
eyJ0YWIJAGJlbnRyaWUMAPR9dXJsIjoiYWJvdXQ6aG9tZSIsInRpdGxlIjoiTmV3IFRhYiIsImNh
...
</code></pre></div></div>

<p>The issue at hand seems simple enough: Decode the base64 encoded text and decompress with LZ4. Out the other ends come
browser tab data:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"sessionrestore"</span><span class="p">,</span><span class="w">
    </span><span class="mi">1</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"windows"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"tabs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"entries"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
              </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://cert.se/#16n4[2t"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CERT-SE - Sveriges nationella CSIRT"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"cacheKey"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
              </span><span class="nl">"ID"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docshellUUID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{abcd136f-fad2-46b9-a387-683841f7043b}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"referrerInfo"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BBoSnxDOS9qmDeAnom1e0AAAAAAAAAAAwAAAAAAAAEYAAAAAAAAAAAABAQAAAAABAA=="</span><span class="p">,</span><span class="w">
              </span><span class="nl">"originalURI"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://cert.se/#16n4[2t"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"resultPrincipalURI"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://cert.se/#16n4[2t"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"loadReplace"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"loadReplace2"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"contentType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text/html"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"principalToInherit_base64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2">:{</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">moz-nullprincipal:{228b1ff6-8da2-4570-baf6-aaee44c51321}?http://cert.se</span><span class="se">\"</span><span class="s2">}}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"hasUserInteraction"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
              </span><span class="nl">"triggeringPrincipal_base64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">3</span><span class="se">\"</span><span class="s2">:{}}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docIdentifier"</span><span class="p">:</span><span class="w"> </span><span class="mi">17</span><span class="p">,</span><span class="w">
              </span><span class="nl">"transient"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">],</span><span class="w">
          </span><span class="nl">"lastAccessed"</span><span class="p">:</span><span class="w"> </span><span class="mi">1758129005308</span><span class="p">,</span><span class="w">
          </span><span class="nl">"hidden"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
          </span><span class="nl">"searchMode"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
          </span><span class="nl">"userContextId"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
          </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
          </span><span class="nl">"requestedIndex"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"image"</span><span class="p">:</span><span class="w"> </span><span class="s2">"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAo0lEQVQ4je2TsQ3CMBBF3zFBPAEL4UyQxlvRUSHRh54lWIAmbU6hI1JMgWPZSoTijoJf2b73nyxZFu89AHo1n8XGVIdeAHYlpbX8Bb8meBX0IpsK7gWCyKaCS4EgsqngCHQbyl1gc4Gx+gQaYPxSHoEmsIsbYKzeAAdMK+UJcIGJWTyjsXoGamBIjgegDrMsMn9nEckGfVvtgVPYOmP1kc7n3hthhC5vh/jF6AAAAABJRU5ErkJggg=="</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"entries"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
              </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"about:newtab"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"New Tab"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"cacheKey"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
              </span><span class="nl">"ID"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docshellUUID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{260eaf8d-7661-45e2-b43d-b8e932daa87b}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"resultPrincipalURI"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
              </span><span class="nl">"hasUserInteraction"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"triggeringPrincipal_base64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">3</span><span class="se">\"</span><span class="s2">:{}}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docIdentifier"</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w">
              </span><span class="nl">"transient"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="p">{</span><span class="w">
              </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://cert.se/#13t1c3f"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CERT-SE - Sveriges nationella CSIRT"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"cacheKey"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
              </span><span class="nl">"ID"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docshellUUID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{260eaf8d-7661-45e2-b43d-b8e932daa87b}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"referrerInfo"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BBoSnxDOS9qmDeAnom1e0AAAAAAAAAAAwAAAAAAAAEYAAAAAAAAAAAABAQAAAAABAA=="</span><span class="p">,</span><span class="w">
              </span><span class="nl">"originalURI"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://cert.se/#13t1c3f"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"resultPrincipalURI"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://cert.se/#13t1c3f"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"loadReplace"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"loadReplace2"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"contentType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text/html"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"principalToInherit_base64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2">:{</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">moz-nullprincipal:{c03e15bd-fdef-4036-8224-de4d2c6cdcff}?http://cert.se</span><span class="se">\"</span><span class="s2">}}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"hasUserInteraction"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
              </span><span class="nl">"triggeringPrincipal_base64"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">3</span><span class="se">\"</span><span class="s2">:{}}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"docIdentifier"</span><span class="p">:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
              </span><span class="nl">"transient"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">],</span><span class="w">
          </span><span class="nl">"lastAccessed"</span><span class="p">:</span><span class="w"> </span><span class="mi">1758129009974</span><span class="p">,</span><span class="w">
          </span><span class="nl">"hidden"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
          </span><span class="nl">"searchMode"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
          </span><span class="nl">"userContextId"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
          </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
          </span><span class="nl">"requestedIndex"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"image"</span><span class="p">:</span><span class="w"> </span><span class="s2">"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAo0lEQVQ4je2TsQ3CMBBF3zFBPAEL4UyQxlvRUSHRh54lWIAmbU6hI1JMgWPZSoTijoJf2b73nyxZFu89AHo1n8XGVIdeAHYlpbX8Bb8meBX0IpsK7gWCyKaCS4EgsqngCHQbyl1gc4Gx+gQaYPxSHoEmsIsbYKzeAAdMK+UJcIGJWTyjsXoGamBIjgegDrMsMn9nEckGfVvtgVPYOmP1kc7n3hthhC5vh/jF6AAAAABJRU5ErkJggg=="</span><span class="w">
        </span><span class="p">},</span><span class="w">
 </span><span class="err">&lt;REDACTED</span><span class="w"> </span><span class="err">DATA</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">there</span><span class="w"> </span><span class="err">were</span><span class="w"> </span><span class="err">a</span><span class="w"> </span><span class="err">lot</span><span class="w"> </span><span class="err">more</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">same&gt;</span><span class="w">
</span><span class="err">}</span><span class="w">
</span></code></pre></div></div>

<p>In this JSON blob there are several tabs for various cert.se domains, each with its unique little snippet appended to
the URL. In the truncated output above the URL <code class="language-plaintext highlighter-rouge">https://cert.se/#16n4[2t</code> can be found. Let’s start by extracting all
those snippets.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> rg <span class="nt">-o</span> <span class="s1">'https://cert\.se[^" ]*'</span> out.decoded | <span class="nb">uniq</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">'#'</span> <span class="nt">-f2</span>
16n4[2t
13t1c3f
17]12c5r
6e8u11e
15o7s9r
10r14i
</code></pre></div></div>

<p>Inspecting the snippets they seem to be indices. They go from 1 to 17 and each number is always succeeded by a
character.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>16: n
4: [
2: t
13: t
1: c
3: f
17: ]
12: c
5: r
6: e
8: u
11: e
15: o
7: s
9: r
10: r
14: i
</code></pre></div></div>

<p>Order the characters according to their index, aaaaand:</p>

<p>We have flag: <code class="language-plaintext highlighter-rouge">ctf[resurrection]</code></p>

<h2 id="tcp-stream-9">TCP Stream 9</h2>

<p>TCP Stream 9 is a connection between the same two hosts as we saw in TCP Stream 6 and 7. This time, we’re doing a <code class="language-plaintext highlighter-rouge">GET</code>
towards <code class="language-plaintext highlighter-rouge">/CERT-SE.jpg</code> and we can just fetch the image straight from the body of the response.</p>

<p align="center">
  <img src="https://thorsell.io/assets/images/certse-ctf25-9.jpg" />
</p>

<p>This is an English version of the same image we got from the corrupted part of the .pcap. Surely the <code class="language-plaintext highlighter-rouge">ctf[ bunch of
pixels ]</code> must be a clue that there’s a flag to be found here?</p>

<p>The slightly tinted, yellow(?) box, with all the coloured pixels contains three rows with <code class="language-plaintext highlighter-rouge">[3×20, 3×20, 3×2]</code> pixels
respectively.</p>

<h3 id="throw-everything-at-it">Throw everything at it</h3>

<p>I tried a couple of different things on the file itself, because I did not want to tackle the colour code thingymajing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> strings CERT-SE.jpg | rg <span class="nt">-i</span> ctf

no output
</code></pre></div></div>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; xxd CERT-SE.jpg | rg -i ctf -A 5 -B 5

0003f480: c398 0088 11f0 88db a7cc 7b9f 7e32 7f9c  ..........{.~2..
0003f490: 74ab 93ab 8333 b349 b0fa fe57 7eed c40b  t....3.I...W~...
0003f4a0: 87e4 6c2a cf71 a592 b2ef afb4 0281 3113  ..l*.q........1.
0003f4b0: db78 8dbc cb78 8bc5 6629 de5f 6366 2a5a  .x...x..f)._cf*Z
0003f4c0: 7d37 1a66 638d 5e51 5cd6 e15b 623b 3708  }7.fc.^Q\..[b;7.
0003f4d0: 9c43 5446 a3dd 6651 3f31 ee7d f8c9 fe71  .CTF..fQ?1.}...q   &lt;-- It says CTF here, but I don't think that's important
0003f4e0: d3e6 3dcf bf19 3fce 3a7c c7b9 f7e3 27f9  ..=...?.:|....'.
0003f4f0: c74f 98f7 3efc 64ff 0038 e88b 2382 6b4a  .O..&gt;.d..8..#.kJ
0003f500: bb41 a2bb 392c 858a e64b 2e21 1721 8f95  .A..9,...K.!.!..
0003f510: b95b c77d 4d12 5b23 bac1 2099 8981 1881  .[.}M.[#.. .....
0003f520: 1188 8888 8da2 2239 4444 4784 4799 6693  ......"9DDG.G.f.
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> binwalk CERT-SE.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>
0             0x0             JPEG image data, JFIF standard 1.01
</code></pre></div></div>

<h3 id="the-slightly-less-lazy-approach">The slightly less lazy approach</h3>

<p>Then I surrendered and started to look at the colourful grid. Unfortunately, the encoded/coloured area does not include
<code class="language-plaintext highlighter-rouge">ctf[]</code>, meaning we cannot leverage a known crib for decoding…</p>

<p>My initial idea was that each column corresponds to a character. That is, <code class="language-plaintext highlighter-rouge">red pink blue</code> would be the first character,
then <code class="language-plaintext highlighter-rouge">green yellow light-blue</code>, etc. I created a colour map accordingly:</p>

<table>
  <thead>
    <tr>
      <th>Pixel Colour</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Red</td>
      <td>1</td>
    </tr>
    <tr>
      <td>Green</td>
      <td>2</td>
    </tr>
    <tr>
      <td>Yellow</td>
      <td>3</td>
    </tr>
    <tr>
      <td>Blue</td>
      <td>4</td>
    </tr>
    <tr>
      <td>Light-blue</td>
      <td>5</td>
    </tr>
    <tr>
      <td>Pink</td>
      <td>6</td>
    </tr>
  </tbody>
</table>

<p>Using this table for decoding the pixels yields the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 2 3 4 4 5 3 2 1 2 4 5 4 5 4 5 2 3 1 2
6 3 4 6 6 1 4 5 3 4 6 1 3 6 6 3 1 4 3 4
4 5 2 1 2 3 6 1 6 5 2 3 1 2 1 2 5 6 6 5

5 6 6 1 1 2 1 2 1 2 2 3 3 4 6 1 3 2 1 2
1 4 2 3 3 6 3 4 6 3 4 1 2 5 2 3 4 5 3 4
2 3 4 5 4 5 6 5 4 5 5 6 6 1 4 5 6 1 6 5

4 5
6 3
1 2
</code></pre></div></div>

<p>An initial idea is that the triplet <code class="language-plaintext highlighter-rouge">2 4 5</code> is the <code class="language-plaintext highlighter-rouge">space</code> character (because it’s frequent and reasonably spaced).
Meaning that the above can be rewritten as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 2 3 4 4 5 3 2 1  4 5 4 5 4 5 2 3 1  5 6 6 1 1 2 1  1 2  3 3 4 6 1 3 2 1  4 5
6 3 4 6 6 1 4 5 3  6 1 3 6 6 3 1 4 3  1 4 2 3 3 6 3  6 3  1 2 5 2 3 4 5 3  6 3
4 5 2 1 2 3 6 1 6  2 3 1 2 1 2 5 6 6  2 3 4 5 4 5 6  4 5  6 6 1 4 5 6 1 6  1 2
</code></pre></div></div>

<p>Which in turn would result in a six word flag, each word with these numbers of characters respectively: <code class="language-plaintext highlighter-rouge">9 chars + 9
chars + 7 chars + 2 chars + 8 chars + 2 chars</code>. At this point though, I’m not quite sure how to proceed. Frequency
analysis? The number of characters in four of the words are high, so maybe that’s advantageous?</p>

<h2 id="tcp-10">TCP 10</h2>

<p>In TCP 10 Stream 10 we find Nessie again – poor soul.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>From: m.a.wetherell@duke.com
To: nessie@loch.com
Subject: Lost your SD card?

--===============7889887308178902226==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Dear Nessie! When I was filming a movie I found a SD card near the Loch Ness.
There was some inscription with your email so I am sending you the data extracted
from the SD card. I hope everything is intact. Regards, Marmaduke

--===============7889887308178902226==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="PHOTOS.img"
Content-Type: application/octet-stream

6zyQbWtmcy5mYXQAAgQEAAIAAgCg+CgAIABAAAAIAAAAAAAAgAEp0Wb5qlBIT1RPUyAgICAgRkFU
MTYgICAOH75bfKwiwHQLVrQOuwcAzRBe6/Ay5M0WzRnr/lRoaXMgaXMgbm90IGEgYm9vdGFibGUg
</code></pre></div></div>

<p>Seems to be base64 encoded data from an SD card.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">base64</span> <span class="nt">-d</span> out.b64 <span class="o">&gt;</span> out.img

<span class="nv">$&gt;</span> file out.img
out.img: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID <span class="s2">"mkfs.fat"</span>,
  sectors/cluster 4, reserved sectors 4, root entries 512, sectors 40960 <span class="o">(</span>volumes &lt;<span class="o">=</span>32 MB<span class="o">)</span>,
  Media descriptor 0xf8, sectors/FAT 40, sectors/track 32, heads 64, hidden sectors 2048,
  reserved 0x1, serial number 0xaaf966d1, label: <span class="s2">"PHOTOS     "</span>, FAT <span class="o">(</span>16 bit<span class="o">)</span>

<span class="nv">$&gt;</span> binwalk <span class="nt">-e</span> out.img

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>
61440         0xF000          PNG image, 819 x 1227, 8-bit/color RGBA, non-interlaced
61481         0xF029          Zlib compressed data, default compression
</code></pre></div></div>

<p>Out the other end comes a directory and two files:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_out.img.extracted/
.rw-rw-r--    0 erik  5 Oct 13:00 F029
.rw-rw-r-- 1.2M erik  5 Oct 13:00 F029.zlib
</code></pre></div></div>

<p>Maybe I’m just miss understanding <code class="language-plaintext highlighter-rouge">binwalk</code>, but I would have expected a PNG image too… so I tried another approach:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> binwalk <span class="nt">--dd</span><span class="o">=</span><span class="s1">'png image:png'</span> out.img

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>
61440         0xF000          PNG image, 819 x 1227, 8-bit/color RGBA, non-interlaced
61481         0xF029          Zlib compressed data, default compression
</code></pre></div></div>

<p>This time I got the result I think(?) I expected.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> ll _out.img-0.extracted/
.rw-rw-r-- 1.2M erik  5 Oct 13:07 F000.png
.rw-rw-r--    0 erik  5 Oct 13:07 F029
.rw-rw-r-- 1.2M erik  5 Oct 13:07 F029.zlib
</code></pre></div></div>

<p>Opening the image we see a cute Loch Ness monster(?), but the image seems a little bit broken.</p>

<p align="center">
  <img width="500" src="https://thorsell.io/assets/images/certse-ctf25-10.png" />
</p>

<p><code class="language-plaintext highlighter-rouge">binwalk</code> worked before, so let’s try again:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> binwalk <span class="nt">-e</span> _out.img-0.extracted/F000.png

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>
0             0x0             PNG image, 819 x 1227, 8-bit/color RGBA, non-interlaced
41            0x29            Zlib compressed data, default compression

<span class="nv">$&gt;</span> ll _out.img-0.extracted/_F000.png.extracted/
.rw-rw-r--    0 erik  5 Oct 13:12 29
.rw-rw-r-- 1.2M erik  5 Oct 13:12 29.zlib
</code></pre></div></div>

<p>It’s the same size as before (1.2M) which leads me to believe we’re just looping around on the same data. I don’t really
know though.</p>

<h2 id="tcp-11">TCP 11</h2>

<p>This stream contained a file called <code class="language-plaintext highlighter-rouge">monstrum_piscis_tres</code> containing 420 lines of data which just looks like garbage:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/+NIxAAAAAAAAAAAAFhpbmcAAAAPAAAA+wAAXWAABggLDhASFBYYHCAiIyYoLC40NTg5PUFFR0lK
TU5UVVhZXF1iZWdpa21xdHZ4en6Cg4aIio6Rk5WXmJucoaOmp6qtsbO1t7m8wMHDxcfKzc7R0tXX
...
VVVVVVVV/+MYxMQAAANIAcAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
VVVVVVVVVVVVVVVVVVVVVVVVVVVV
</code></pre></div></div>

<p align="center">
Monstrum Piscis Tres -&gt; MP3
</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">tr</span> <span class="nt">-d</span> <span class="s1">'\n'</span> &lt; blob.txt | <span class="nb">base64</span> <span class="nt">-d</span> <span class="o">&gt;</span> monstrum_piscis_tres.mp3

<span class="nv">$&gt;</span> file monstrum_piscis_tres.mp3
monstrum_piscis_tres.mp3: MPEG ADTS, layer III,  v2.5,  32 kbps, 8 kHz, Monaural
</code></pre></div></div>

<p>Playing back the file you hear a beep and something in the background.</p>

<p>I used SoX to create a spectogram of the file:</p>

<p align="center">
  <img src="https://thorsell.io/assets/images/certse-ctf25-11-spec.png" />
</p>

<p>The spectogram clearly shows that there’s a constant tone at 1kHz. I used <code class="language-plaintext highlighter-rouge">ffmpeg</code> to remove the constant tone:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> ffmpeg <span class="nt">-i</span> monstrum_piscis_tres.mp3 <span class="nt">-af</span> <span class="s2">"equalizer=f=1000:width_type=h:width=100:g=-60"</span> no1k.wav
</code></pre></div></div>

<p>Playing back this file, it’s quite clear that someone <em>is</em> saying something. The voice has that obvious “speaking
backwards” sound though, so:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> ffmpeg <span class="nt">-i</span> no1k.wav <span class="nt">-af</span> areverse reversed.wav
</code></pre></div></div>

<p>Playing <em>this</em> back we can hear that someone is reading:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>I N K &lt;pause&gt; E M W &lt;pause&gt; 2 G J F J U Q X J A
</code></pre></div></div>

<p>This matches the <em>Base32</em> alphabet:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">echo</span> <span class="nt">-n</span> <span class="s1">'INKEMW2GJFJUQXJA'</span> | <span class="nb">base32</span> <span class="nt">--decode</span>

CTF[FISH]
</code></pre></div></div>

<p>We have flag: <code class="language-plaintext highlighter-rouge">CTF[FISH]</code></p>

<h2 id="tcp-12---16">TCP 12 - 16</h2>

<p>TCP Stream 12 shows some communication with an FTP Server. From the succeeding streams it’s possible to extract two
files:</p>

<h3 id="ctftxt"><code class="language-plaintext highlighter-rouge">ctf.txt</code></h3>

<p><code class="language-plaintext highlighter-rouge">ctf.txt</code> contains these sentences:</p>

<blockquote>
  <p>If you take the filename excluding the file extension, three letters you know, you have the start of the flag as you
know. Maybe you can take, uhm, the last two words in the fourth sentence and make a flag of that, do not forget the
characters [] surrounding the flag. You do not have to insert anything between the two words.</p>
</blockquote>

<p>Let’s break this down:</p>

<blockquote>
  <p>If you take the filename excluding the file extension, three letters you know, you have the start of the flag as you
know.</p>
</blockquote>

<p>This yields: <code class="language-plaintext highlighter-rouge">ctf</code>.</p>

<blockquote>
  <p>Maybe you can take, uhm, the last two words in the fourth sentence and make a flag of that,</p>
</blockquote>

<p>This should be: <code class="language-plaintext highlighter-rouge">a guess</code></p>

<blockquote>
  <p>do not forget the characters [] surrounding the flag. You do not have to insert anything between the two words.</p>
</blockquote>

<p>We have flag: <code class="language-plaintext highlighter-rouge">ctf[a guess]</code> – I guess?</p>

<blockquote>
  <p>Ps: If anyone from CERT-SE reads this write-up. Per your recommendation, I tried just dumping the whole text into
ChatGPT 5:</p>

  <blockquote>
    <p>Can you find a flag on the format ctf[] or CTF[] in this text?</p>

    <p>Capture the Flag (CTF) is more than just a competitive game. It’s a …</p>
  </blockquote>

  <p>and the LLM returned:</p>

  <blockquote>
    <p><em>Thought for 38s</em></p>

    <p>ctf[a guess]</p>

    <p>(From “ctf[” as the filename hint + the last two words of the 4th sentence: “a guess”.)</p>
  </blockquote>
</blockquote>

<h3 id="passwd_policytxt"><code class="language-plaintext highlighter-rouge">passwd_policy.txt</code></h3>

<blockquote>
  <p>It is important to secure and protect our data. This is done using SUPERSECURE passwords.
Lucky for us, we have gotten hold of a great password list. The policy is as following:</p>

  <ol>
    <li>Always use passwords from our password list, “rockyou.txt”, you can find it online.</li>
    <li>Do not alter the password from the list.</li>
    <li>Never change your password.</li>
    <li>The shorter the better, so you remember them.</li>
    <li>Always add the flag format surrounding your password.</li>
    <li>When you need to change your password, fill in the password reset form in the lobby of the building.</li>
  </ol>
</blockquote>

<p>I’m not quite sure whether this is a red herring or something useful. If I come across an encrypted file, I guess I know
where to get my passwords, but I have not found such a file yet.</p>

<h2 id="following-the-udp-streams">Following the UDP Streams</h2>

<p>Alongside the TCP streams there are 93 UDP streams. They were all quite short so I figured I’d see what ASCII data was
extractable from them. I started by converting (is that the right word?) the UDP stream pcap-files to binary data:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> streams
<span class="k">for </span>f <span class="k">in</span> <span class="k">*</span>.pcap<span class="p">;</span> <span class="k">do
  </span><span class="nv">base</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">f</span><span class="p">%.pcap</span><span class="k">}</span><span class="s2">"</span>
  tshark <span class="nt">-r</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> <span class="nt">-Y</span> udp <span class="nt">-T</span> fields <span class="nt">-e</span> data 2&gt;/dev/null | <span class="nb">sed</span> <span class="s1">'/^$/d'</span> | <span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> h<span class="p">;</span> <span class="k">do
    </span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="nv">$h</span><span class="s2">"</span> | xxd <span class="nt">-r</span> <span class="nt">-p</span> <span class="o">&gt;&gt;</span> <span class="s2">"streams/</span><span class="k">${</span><span class="nv">base</span><span class="k">}</span><span class="s2">.bin"</span>
  <span class="k">done
done</span>
</code></pre></div></div>

<p>and then I ran through all files and base64 decoded everything:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>0 92<span class="si">)</span><span class="p">;</span> <span class="k">do
  </span><span class="nv">f</span><span class="o">=</span>streams/udp_stream_<span class="k">${</span><span class="nv">i</span><span class="k">}</span>.bin
  <span class="o">[</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="k">continue
  </span><span class="nb">base64</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true
  echo
</span><span class="k">done

</span>space
5
3
2
2
enter
mouse_1
mouse_1
mouse_1
w
w
w
<span class="nb">.</span>
c
e
r
t
<span class="nb">.</span>
s
e
enter
mouse_1
scroll_dwn
mouse_1
scroll_dwn
mouse_2
ctrl
c
mouse_1
ctrl
v
mouse_1
c
t
f
<span class="o">[</span>
k
e
y
l
o
g
_
o
v
e
r
_
u
d
p
<span class="o">]</span>
enter
mouse_1
mouse_1
mouse_1
scroll_up
mouse_1
mouse_2
enter
n
e
s
s
i
e
space
i
s
space
n
o
t
space
a
space
m
o
n
s
t
e
r
</code></pre></div></div>

<p>Did you catch that?!</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c
t
f
[
k
e
y
l
o
g
_
o
v
e
r
_
u
d
p
]
</code></pre></div></div>

<p>We have flag: <code class="language-plaintext highlighter-rouge">ctf[keylog_over_udp]</code></p>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[This is a writeup for CERT-SE’s CTF from 2025.]]></summary></entry><entry><title type="html">Automate everything and document why</title><link href="https://thorsell.io/2025/07/17/worth-the-time.html" rel="alternate" type="text/html" title="Automate everything and document why" /><published>2025-07-17T00:00:00+02:00</published><updated>2025-07-17T00:00:00+02:00</updated><id>https://thorsell.io/2025/07/17/worth-the-time</id><content type="html" xml:base="https://thorsell.io/2025/07/17/worth-the-time.html"><![CDATA[<p>Almost 2 000 comics (and over 12 years) ago<sup id="fnref:numcomics" role="doc-noteref"><a href="#fn:numcomics" class="footnote" rel="footnote">1</a></sup>, Randall Munroe created the XKCD Comic <a href="https://xkcd.com/1205/">Is it worth the
time?</a> (seen below).</p>

<p align="center">
  <img src="https://imgs.xkcd.com/comics/is_it_worth_the_time.png" />
</p>

<p>I have carried this table in my mental hip pocket, always ready to show a doubting customer or team member how much
<em>time</em> we can save if we automate a specific task.<sup id="fnref:automate" role="doc-noteref"><a href="#fn:automate" class="footnote" rel="footnote">2</a></sup> However automation is not solely about saving
time. I would even argue that the time saved by running an automation, instead of doing the automated tasks manually, is
just a positive side effect of the automation.</p>

<blockquote>
  <p>The primary reason to automate something is <strong>not</strong> to save time, it is to codify knowledge and ensure
reproducibility.</p>
</blockquote>

<p>As an example, the table tells us the following:</p>

<ul>
  <li>Frequency of task: Monthly</li>
  <li>Time shaved off task: 5 minutes</li>
  <li>Total time saved over 5 years: 5 hours</li>
</ul>

<p>One could look at this and think: “As long as we believe we will do this task for the next five years and we spend less
than five hours automating the task, it is worth doing.”, but I think that mindset is wrong. There is more to be gained!
By automating a task, we create a step-by-step guide which tells a computer (and therefore us and our future colleagues)
<em>exactly</em> how to perform the task. The code we write is unambiguous<sup id="fnref:code" role="doc-noteref"><a href="#fn:code" class="footnote" rel="footnote">3</a></sup>, whereas most documentation I have read
(and written) is not. In order for our automation to be useful, it must omit nothing; but authors of documentation
frequently forget things or simply ignore to add steps they consider trivial.</p>

<p>Another important thing about the automation is that it – if written properly and executed regularly  – will break and
alert us that something about our target system has changed. Even the most well written runbook can become outdated and
my experience is that <em>ensuring all of our documentation in Confluence is still up to date and accurate</em>, is an
infrequent issue in most teams’ sprints.</p>

<h2 id="the-code-is-the-documentation">The code is the documentation</h2>

<p>I have always been a proponent of documentation. Having worked as a consultant the majority of my career, few things
make me happier than a well functioning internal wiki, to kick start an assignment and getting up to speed. I am also
really fond of <em>writing</em> documentation; leave me alone with <a href="https://www.drawio.com/">draw.io</a> and a markdown editor
and I’ll be in my happy-place. Over time though, I have steered away from writing <em>pure documentation</em> (i.e., a separate
text file describing a resource or process) and instead focused on trying to always incorporate the “documentation” into
the resource itself.</p>

<p>In the light of the introduction of this post, I think the reason is obvious. If given the option, I will <em>always</em>
choose to codify my knowledge in an executable format; because the code is truth.</p>

<p>I’m sure you have heard the phrase:</p>

<blockquote>
  <p>The code is the documentation.</p>
</blockquote>

<p>You probably have an opinion about the phrase too.</p>

<p>I think there are developers who throw this phrase around simply because they dislike writing documentation, both as
comments in their code but also as standalone documents. Maybe it’s out of laziness, incompetence, or they see the
reasoning I lay out in this post and believe it’s reasonable to take it to its utmost extreme. I do not believe this
extreme is reasonable. Most of the time, we shall not write code, leave it without comments, and expect our future
selves and colleagues to understand it. At least not benefit from it fully.</p>

<p>However, I also think there are developers out there who has internalised the same conclusion as I have: It is very
difficult (maybe even impossible) to write documentation that describes <em>anything</em> in a more exact and concise way than
the source code itself already does. Therefore, any documentation we write must be well thought through, kept up to
date, and describe <strong>why</strong> we chose to write a particular piece of code. There’s almost never any reason to describe
<strong>what</strong> the code does.</p>

<h2 id="what-when-and-how-i-document">What, when and how I document</h2>

<p>To wrap up this post, I want to give a short list of <em>kinds of documentation</em> and how I use them.</p>

<h3 id="runbooks">Runbooks</h3>

<p>The only time you should write a <a href="https://en.wikipedia.org/wiki/Runbook">runbook</a> is as a notes-document, the first time
you step through a procedure. That way, when you automate the task you can reference your runbook before you delete it.</p>

<p>Naturally, only Sith deal in absolutes.</p>

<h3 id="way-of-working-documents">Way of Working documents</h3>

<p>Early in my career I held a lot of scrum master/team lead positions. I also (therefore?) wrote <em>a lot</em> of documentation
about different processes. My closest manager(s) did and the product owners usually did too. They usually loved them.
Likely because they did not talk to the team every day, the documents became the window through which they tried to
understand why the teams made certain decisions and worked in certain ways. PO:s in particular also loved to use the
way-of-working documents in their argumentation when there was a disagreement.</p>

<p>Adherence to those documents from the team? Let’s just say it was not 100%.</p>

<p>What I have come to learn is that it’s futile to attempt to change <em>ways of working</em> by defining processes. Writing down
the current ways of working is also just busy work, because it is close to impossible to describe all intricacies that
goes on in a team on the daily. Should you succeed, the document is just a snapshot and ways of working are dynamic. It
is bound to become outdated.</p>

<h3 id="source-code">Source code</h3>

<p>If your code achieves what it is meant to do, you should – within reason – not have to document what it does. However,
it is certainly a good idea to spend quite a lot of time writing your code in a way that is understandable.</p>

<blockquote>
  <p>Programs must be written for people to read, and only incidentally for machines to execute.</p>

  <p>– Harold Abelson (<a href="https://www.goodreads.com/work/quotes/871745-structure-and-interpretation-of-computer-programs">Structure and Interpretation of Computer Programs</a>)</p>
</blockquote>

<p>Once I have unlocked the <em>“understandable and functional-code”</em> achievement, I usually spend some extra time going over
the code to see if it’s clear <em>why</em> I wrote the code in this way. I tend to err on the side of caution and comment a
little bit more than what is necessary, but I think this is reasonable approach.</p>

<h3 id="architectural-decision-records-adrs">Architectural Decision Records (ADR:s)</h3>

<p>Sometimes a team or organisation makes a decision that is worth remembering. Well, not only the decision itself, but the
rationale behind the decision and also the consequences of making the decision. Adding a comment to a source code file
is a bad idea, because it would probably result in a comment longer than the combined code in that file but also because
it hides the decision in a stowed away file in a specific project.</p>

<p>The concept of <a href="https://adr.github.io/">Architectural Decision Records</a> is a great way to document such decisions. I
usually propose that ADR:s contain the following four headings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Context

Decision

Rationale

Consequence
</code></pre></div></div>

<p>A pleasant side effect of this practice is that your aggregated ADR:s is likely to inform your team’s and/or
organisation’s ways of working. For instance, in my current assignment, we write specific <em>Team ADR:s</em> to document
decisions we have made about concepts, tools, or best practices. We keep these ADR:s in a dedicated repository and
frequently reference them. When an ADR becomes outdated we update it and make it clear it is no longer to be adhered to,
but we leave it in the repo and if there’s a new ADR replacing the old one we reference the new one.</p>

<h3 id="git">Git</h3>

<p>My opinion is that developers are usually not great at neither writing nor reading commit messages. If we utilise our
commit messages to convey meaningful information about our commits – as opposed to just writing <code class="language-plaintext highlighter-rouge">fix</code> and <code class="language-plaintext highlighter-rouge">add feature
x</code> – we can really help our future selves and our colleagues.</p>

<p>As I wrote earlier, if we write understandable source code and include comments that explains the context for
particularly intricate pieces of code, that is good. We can improve on this further by adding more prose like
explanations and motivations in the commit message.</p>

<p>I am a fan of <a href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits</a>.</p>

<!-- --------------------------------------------------------------------------------------------------------------- -->

<!-- FEET NOTES -->

<!-- REFERENCES -->
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:numcomics" role="doc-endnote">
      <p>At the time of committing this, the latest XKCD is number 3116: <a href="https://xkcd.com/3116/">Echo Chamber</a>. <a href="#fnref:numcomics" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:automate" role="doc-endnote">
      <p>The comic does not actually say that automation is the goal, but as someone who spends most of their work
days automating things, this is where I was taken by the comic. <a href="#fnref:automate" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:code" role="doc-endnote">
      <p>I’m sure you can come up with an example where this is not true, but that’s beside my point. <a href="#fnref:code" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[Almost 2 000 comics (and over 12 years) ago1, Randall Munroe created the XKCD Comic Is it worth the time? (seen below). At the time of committing this, the latest XKCD is number 3116: Echo Chamber. &#8617;]]></summary></entry><entry><title type="html">Adopt UniFi Devices with VLANs</title><link href="https://thorsell.io/2025/07/05/adopt-unifi-with-vlan.html" rel="alternate" type="text/html" title="Adopt UniFi Devices with VLANs" /><published>2025-07-05T00:00:00+02:00</published><updated>2025-07-05T00:00:00+02:00</updated><id>https://thorsell.io/2025/07/05/adopt-unifi-with-vlan</id><content type="html" xml:base="https://thorsell.io/2025/07/05/adopt-unifi-with-vlan.html"><![CDATA[<p><em>This is mostly a quick note to myself, for future reference.</em></p>

<p>If you plug in a new UniFi device on your network and it does not show up for adoption in your UniFi Network Application
UI, try switching the VLAN of the port in the upstream switch to be the same as the VLAN where you have your controller.</p>

<p>I am fairly sure I have been able to adopt devices using <code class="language-plaintext highlighter-rouge">Default (1)</code> as the setting on the switch, but I recently
bought a couple of
<a href="https://eu.store.ui.com/eu/en/category/switching-utility/products/usw-flex-2-5g-5">USW-Flex-2.5G-5</a> switches as well
as two <a href="https://eu.store.ui.com/eu/en/category/all-wifi/products/u7-pro-max">U7-Pro-Max</a> to extend my lab for work and
the first switch refused to show up under <em>UniFi Devices</em>. When I switched the upstream switch’s port to the same VLAN
as where I run my Home Assistance instance (which in turn hosts my <a href="https://github.com/hassio-addons/addon-unifi">UniFi Network
Application</a>) it showed up straight away.</p>

<p>Once the device has been adopted (and potentially updated) you can switch it to whichever VLAN you want.</p>

<!-- --------------------------------------------------------------------------------------------------------------- -->]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[This is mostly a quick note to myself, for future reference.]]></summary></entry><entry><title type="html">I replaced both OpenVPN and Wireguard with Tailscale on pfSense</title><link href="https://thorsell.io/2025/06/30/tailscale.html" rel="alternate" type="text/html" title="I replaced both OpenVPN and Wireguard with Tailscale on pfSense" /><published>2025-06-30T00:00:00+02:00</published><updated>2025-06-30T00:00:00+02:00</updated><id>https://thorsell.io/2025/06/30/tailscale</id><content type="html" xml:base="https://thorsell.io/2025/06/30/tailscale.html"><![CDATA[<p>Ever since I got my own place I have been hosting my own VPN. The purpose has been both to (1) ensure I can reach <em>my
stuff</em> when I’m out and about, and (2) have a way to tunnel traffic when connecting to networks I’m not in
control of<sup id="fnref:firstvpn" role="doc-noteref"><a href="#fn:firstvpn" class="footnote" rel="footnote">1</a></sup>. I started my VPN-journey with my good ‘ol <a href="https://openwrt.org/toh/asus/rt-n66u">Asus
RT-N66U</a> which had a rudimentary PPTP VPN built in, but I quickly replaced the
default firmware with <a href="https://advancedtomato.com/">Tomato</a> and IIRC I ran an OpenVPN Server for a while. I say <em>“for a
while”</em>, because I soon upgraded(?) my setup significantly, by building my own Debian-based router where I – again –
ran OpenVPN.</p>

<h2 id="pfsense--openvpn--wireguard">pfSense + OpenVPN &amp; Wireguard</h2>

<p>Since a couple of years back, I’m running <a href="https://github.com/pfsense">pfSense</a><sup id="fnref:netgate" role="doc-noteref"><a href="#fn:netgate" class="footnote" rel="footnote">2</a></sup> and when I setup my
first pfSense box I naturally deployed an <a href="https://docs.netgate.com/pfsense/en/latest/vpn/openvpn/index.html">OpenVPN
Server</a> to go with it. Certificate based
auth was just a couple of clicks away and the service has been serving my household very well for several years. I did
dabble a little bit with <a href="https://docs.netgate.com/pfsense/en/latest/vpn/wireguard/index.html">Wireguard</a> too, but truth
be told I never really got it to work the way I wanted it to.</p>

<h2 id="tailscale-as-a-mesh-network">Tailscale as a mesh network</h2>

<p>A couple of years ago, I first heard about <a href="https://tailscale.com/">Tailscale</a>. I figured it seemed like a nice product,
but I didn’t really have a need for such a solution at the time. This all changed when I wanted to have a NAS at my
mother’s place to act as an off-site backup. Her crappy, ISP-provided, little router/firewall/switch combo-box did not
have the possibility to run a VPN Server. I also doubt her ISP would provide a public IP for her. Lastly, even if both
of these assumptions proved to be incorrect, I would not want to setup port forwarding in her firewall.</p>

<p>Well….</p>

<p>I started to look into Tailscale a little bit more and found that it was perfectly suited for this use case. I sat
down to install Tailscale on the NAS and on my laptop, deployed a simple <code class="language-plaintext highlighter-rouge">Allow all</code> ACL configuration, and they could
talk within minutes. I joined my phone and all three devices were now able to talk to each other as if they were on the
same LAN.</p>

<p><em>Sweet.</em></p>

<h2 id="tailscale-as-a-regular-vpn">Tailscale as a regular VPN</h2>

<p>Tailscale solved the problem:</p>

<blockquote>
  <p>How should I be able to communicate with a device which is not really accessible over the public Internet?</p>
</blockquote>

<p>but I still ran my OpenVPN Server in parallel for a long time (years), simply because I wasn’t sure whether Tailscale
would be able to satisfy my other VPN-needs.</p>

<p><em>Until this past weekend.</em></p>

<p>I did some more reading and learned about two critical features for me:</p>

<ol>
  <li><a href="https://tailscale.com/kb/1019/subnets">Subnet routers</a></li>
  <li><a href="https://tailscale.com/kb/1103/exit-nodes">Exit nodes</a></li>
</ol>

<p>The former let you extend your Tailscale network (known as a tailnet) to include devices that don’t or can’t run the
Tailscale client, and the latter can be used to tell your device to route <em>all traffic</em> through your tailnet – not only
the traffic that is going between Tailscale-adopted devices.</p>

<h3 id="my-tailscale-configuration">My Tailscale configuration</h3>

<p>In pfSense, I have installed the <a href="https://tailscale.com/kb/1146/pfsense">Tailscale package</a> and configured it s.t. it
offers to be an exit node <em>and</em> advertises routes. More specifically, I have advertised the subnet corresponding to the
VLAN where I have <em>the things I want to be able to connect to</em> as well as <code class="language-plaintext highlighter-rouge">/32</code>-CIDR which points at the IP of my
reverse proxy. I have also enabled both <em>Accept DNS</em> and <em>Accept Subnet Routes</em>.</p>

<p>On the Tailscale side I have the following grants:</p>

<pre><code class="language-hujson">{
 "grants": [
    // Allow admin-devices to reach everything
    {
      "src": ["tag:admin-devices"],
      "dst": ["*"],
      "ip": ["*"]
    },

    // Allow family-devices to reach HA Proxy VIP
    {
      "src": ["tag:family-devices"],
      "dst": ["10.10.11.1/32"],
      "ip": ["*:80", "*:443"]
    },

    // Allow admins and family members to use Tailscale as an exit node
    {
      "src": ["tag:admin-devices", "tag:family-devices"],
      "dst": ["autogroup:internet"],
      "ip": ["*"]
    },

    // Grant access to Synology NAS @ mom's
    {
      "src": ["tag:admin-devices", "tag:pfsense"],
      "dst": ["tag:synology-mom"],
      "ip": ["*"]
    }
  ]
}
</code></pre>

<blockquote>
  <p><strong>NOTE:</strong> The last grant, paired with a NAT rule in pfSense, allows <em>devices on specific IP:s</em> to communicate directly
with the off-site NAS even though they do not have Tailscale installed on them. Very convenient.</p>
</blockquote>

<p>All in all, my configuration has proven itself over the past week and I have disabled my OpenVPN Server. My only complaint
thusfar is that the Tailscale iOS client is quite the battery drainer, so the <em>VPN On Demand</em> feature isn’t really useful.</p>

<!-- --------------------------------------------------------------------------------------------------------------- -->

<!-- FEET NOTES -->

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:firstvpn" role="doc-endnote">
      <p>More recent use cases also includes (3) being able to stream content only available from Sweden, when
going abroad. <a href="#fnref:firstvpn" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:netgate" role="doc-endnote">
      <p>This is not a post about Netgate politics and controversies, maybe another post… <a href="#fnref:netgate" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[Ever since I got my own place I have been hosting my own VPN. The purpose has been both to (1) ensure I can reach my stuff when I’m out and about, and (2) have a way to tunnel traffic when connecting to networks I’m not in control of1. I started my VPN-journey with my good ‘ol Asus RT-N66U which had a rudimentary PPTP VPN built in, but I quickly replaced the default firmware with Tomato and IIRC I ran an OpenVPN Server for a while. I say “for a while”, because I soon upgraded(?) my setup significantly, by building my own Debian-based router where I – again – ran OpenVPN. More recent use cases also includes (3) being able to stream content only available from Sweden, when &#8617;]]></summary></entry><entry><title type="html">Writeup for Cert.se’s CTF 2024</title><link href="https://thorsell.io/2024/09/30/cert-ctf-2024.html" rel="alternate" type="text/html" title="Writeup for Cert.se’s CTF 2024" /><published>2024-09-30T00:00:00+02:00</published><updated>2024-09-30T00:00:00+02:00</updated><id>https://thorsell.io/2024/09/30/cert-ctf-2024</id><content type="html" xml:base="https://thorsell.io/2024/09/30/cert-ctf-2024.html"><![CDATA[<p><em>I made an attempt to get as many flags I could in one day because that’s the amount of time I could set aside for
this.
I got five flags out of nine.
I think I might be close to one more and I think the Recycle Bin has one.
The last two flags though – I have no idea.</em></p>

<h1 id="start">Start</h1>

<p>First thing I did was to check for anything readable in the provided <code class="language-plaintext highlighter-rouge">.pcap</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>strings CERT-SE_CTF2024.pcap | less
</code></pre></div></div>

<p>and I quickly found an IRC conversation!
Super fun, especially since I use IRC on a daily basis.
I noticed that the IRC conversation was between two users (<code class="language-plaintext highlighter-rouge">An4lys3r</code> and <code class="language-plaintext highlighter-rouge">D3f3nd3r</code>) and by filtering the output based
on users’ IPs I was able to get something quite readable.</p>

<h1 id="extracting-the-irc-conversation">Extracting the IRC conversation</h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>strings CERT-SE_CTF2024.pcap | grep '@10.0.0.' | less
</code></pre></div></div>

<p>I have trimmed some excessive data to make the conversation more readable.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:irc.ctf.alt 001 D3f3nd3r :Welcome to the Internet Relay Network D3f3nd3r!~user1@10.0.0.20
L:D3f3nd3r!~user1@10.0.0.20 JOIN :#emergency
:irc.ctf.alt 001 An4lys3r :Welcome to the Internet Relay Network An4lys3r!~user1@10.0.0.10
:An4lys3r!~user1@10.0.0.10 JOIN :#emergency
n:An4lys3r!~user1@10.0.0.10 JOIN :#emergency
D3f3nd3r :hello
An4lys3r :aaaah, at last, it's working!
D3f3nd3r :well done setting up the emergency environment so fast!
An4lys3r :Thanks. I'm glad we did a test run a couple of months ago
D3f3nd3r :Yes, I agree
An4lys3r :So what do we know so far, why is our production environment not accessible
D3f3nd3r :Well, I'm in the data center now and the consoles I managed to look at is showing a ransom note.....
An4lys3r :so it's that bad
D3f3nd3r :yup
An4lys3r :Have you found any clue to what group and ransom strain is used, if we are lucky enough there is a free decryptor available
D3f3nd3r :I would not bet on it, but I'll transfer the ransom note to you shortly.
D3f3nd3r :SHA-256 checksum for /home/user/emergency_net/DCC/RANSOM_NOTE.gz (remote): 7113f236b43d1672d881c6993a8a582691ed4beb4c7d49befbceb1fddfb14909
An4lys3r :Thanks, I'll have a look.
D3f3nd3r :I managed to access our FPC system and it looks like it is untouched. I will try carving out pcaps from days / weeks before the encryption started from different segments.
An4lys3r :good, put them on the ftp, I'll have a look at that later.
D3f3nd3r :by the way, Christine came by and handed me a disk image from one of the clients, see if they left any clues on disk. I'll upload it to the ftp shortly.
An4lys3r :We totally need external help, restoring and investigating this incident will be a massive task. You don't happen to have the contact info to Allsafe, I think our rep is called Elliot?
D3f3nd3r :Agree, I already called them in. He is here now sharing some interesting stuff.
D3f3nd3r :They picked up some info on a closed forum regarding our situation, someone posted a WORDLIST scraped from our public website. And they where ranting about recording network traffic from a windows workstation called CTF-PC01. I'll upload the file to the ftp.
D3f3nd3r :He also handed over a strange looking string CTF[E65D46AD10F92508F500944B53168930], does it make sense to you?
An4lys3r :not really, but why don't you ask john?
D3f3nd3r :Alright, I'll see what I can do with it.
An4lys3r :To keep in mind, he also mention they have intel about a suspicious IP address involved in C2 and exfiltration activities lately, the IP is 195.200.72.82
</code></pre></div></div>

<h1 id="flag-in-irc-conversation">Flag in IRC conversation</h1>

<p>Looking at the IRC conversation above, the first flag was right there.</p>

<p>Flag: <code class="language-plaintext highlighter-rouge">CTF[E65D46AD10F92508F500944B53168930]</code></p>

<h1 id="flag-in-pcap">Flag in <code class="language-plaintext highlighter-rouge">.pcap</code></h1>

<p>While using <code class="language-plaintext highlighter-rouge">strings</code> I tried to just grep for more flags:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; strings ../CERT-SE_CTF2024.pcap | grep 'CTF\['
7PASS CTF[AES128]
!PASS CTF[AES128]
W_PRIVMSG #emergency :He also handed over a strange looking string CTF[E65D46AD10F92508F500944B53168930], does it make sense to you?
r:D3f3nd3r!~user1@10.0.0.20 PRIVMSG #emergency :He also handed over a strange looking string CTF[E65D46AD10F92508F500944B53168930], does it make sense to you?
</code></pre></div></div>

<p>Idk if this is too obvious, but the instructions from Cert say flags should have the format <code class="language-plaintext highlighter-rouge">CTF[...]</code>, so…</p>

<p>Flag: <code class="language-plaintext highlighter-rouge">CTF[AES128]</code></p>

<h1 id="flag-in-ransome_notegz-dcc">Flag in RANSOME_NOTE.gz (DCC)</h1>

<p>In their IRC conversation, the users talk about a file <code class="language-plaintext highlighter-rouge">RANSOM_NOTE.gz</code> which they have apparently received from the
hackers.
For some reason they opted to send this using <a href="https://modern.ircdocs.horse/dcc#dcc-send">DCC</a> instead of using the
FTP-server they’ll be using throughout the rest of their conversation :p</p>

<p>By grepping for <code class="language-plaintext highlighter-rouge">DCC</code> in the .pcap I was able to find the initialisation message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DCC SEND RANSOM_NOTE.gz 167772170 40899 95285 221
</code></pre></div></div>

<p>The above contains the integer representation of the IP address (<code class="language-plaintext highlighter-rouge">167772170</code> -&gt; <code class="language-plaintext highlighter-rouge">10.0.0.10</code>) and the port being used
for the transfer (<code class="language-plaintext highlighter-rouge">40899</code>).
The documentation does not mention the last two numbers that is part of the message, but I presume they are the size of
the transfer (<code class="language-plaintext highlighter-rouge">95285</code>) and maybe the block size used(?).</p>

<p>I opened up Wireshark and filtered accordingly:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip.addr == 10.0.0.10 &amp;&amp; tcp.port == 40899
</code></pre></div></div>

<p>whereafter I right-clicked on the first package in the list, chose <code class="language-plaintext highlighter-rouge">Follow</code> -&gt; <code class="language-plaintext highlighter-rouge">TCP Stream</code>, made sure to go to the
bottom left of the dialogue and choose only one side of the conversation (the one that is 95kB), choose <em>Show data as
<code class="language-plaintext highlighter-rouge">Raw</code></em> and then save the data to a file called <code class="language-plaintext highlighter-rouge">RANSOM_NOTE.gz</code>.</p>

<p>The IRC conversation kindly contains the <code class="language-plaintext highlighter-rouge">sha256sum</code> for the ransom note and I was able to verify that my file had the
same hash: <code class="language-plaintext highlighter-rouge">7113f236b43d1672d881c6993a8a582691ed4beb4c7d49befbceb1fddfb14909</code>.</p>

<p>After decompressing the file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gunzip RANSOM_NOTE.gz
</code></pre></div></div>

<p>I got a <code class="language-plaintext highlighter-rouge">279943 byte</code> large text file, starting like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CCCCCCCCCCCTCCCCCFCCCCC[CCCCCOCCCCCRCCCCTCCCCCTTCCCCTFCCCCT[CCCCTOCCCCTRCCCCFCCCCCFTCCCCFFCCCCF[CCCCFOCCCCFRCCCC[CCCCC[TCCCC[FCCCC[[CCCC
</code></pre></div></div>

<p>I tried a handful of different things to no avail, but after talking a little bit to a friend he said there was only one
<code class="language-plaintext highlighter-rouge">]</code> in the entire file (something I thought I had already checked) and because of this I tested to just grep for flags:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; grep -o -E 'CTF\[[a-zA-Z0-9]*\]' ransom_note
CTF[OR]
</code></pre></div></div>

<p>I felt so stupid.
I really thought I checked this one.</p>

<p>Flag: <code class="language-plaintext highlighter-rouge">CTF[OR]</code></p>

<h1 id="ftp-files-in-original-pcap">FTP Files in original .pcap</h1>

<p>In the IRC conversation we can see multiple references to files being uploaded to an FTP server.
By filtering on <code class="language-plaintext highlighter-rouge">ftp</code> in Wireshark we can practically follow the progress of all of these files (gotta love encryption,
right?).</p>

<p><em>I show the entire communication for file 1, below, but for the other files I have only included the relevant parts.</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; tshark -r CERT-SE_CTF2024.pcap -Y "ftp" -T fields -e _ws.col.Info

-- file 1 --

Response: 220 INetSim FTP Service ready.
Request: USER anonymous
Response: 331 Please specify the password.
Request: PASS NcFTP@
Response: 230 Login successful.
Request: PWD
Response: 257 "/"
Request: FEAT
Response: 500 Unknown command.
Request: HELP SITE
Response: 214-The following commands are recognized.
Response:  ABOR CDUP CWD  DELE HELP LIST MKD  MODE
Request: CLNT NcFTPPut 3.2.6 linux-x86_64-libc5
Response: 500 Unknown command.
Request: CWD /
Response: 250 Directory successfully changed.
Request: TYPE I
Response: 200 Switching to BINARY mode.
Request: SIZE corp_net1.pcap
Response: 500 Unknown command.
Request: PORT 10,0,0,20,143,63
Response: 200 PORT command successful.
Request: STOR corp_net1.pcap
Response: 150 Ok to send data.
Response: 226 File receive OK.
Request: MDTM 20240904045628 corp_net1.pcap
Response: 500 Unknown command.
Request: QUIT
Response: 221 Goodbye.

-- file 2 --

Response: 220 INetSim FTP Service ready.
Request: SIZE corp_net2.pcap
Request: PORT 10,0,0,20,151,169

-- file 3 --

Request: SIZE disk1.img.gz
Request: PORT 10,0,0,20,228,239

-- file 4 --

Request: SIZE WORDLIST.txt
Request: PORT 10,0,0,20,146,93
</code></pre></div></div>

<p>The approach for fetching the content of each file is the same:</p>

<ol>
  <li>
    <p>Find the IP:port being used, which in this case is <code class="language-plaintext highlighter-rouge">10.0.0.20</code> followed by <code class="language-plaintext highlighter-rouge">&lt;first integer&gt; * 256 + &lt;second
integer&gt;</code>.
For the first file the port is: <code class="language-plaintext highlighter-rouge">143 * 256 + 63 = 36 671</code>.</p>
  </li>
  <li>
    <p>Use Wireshark the same way we got the DCC file:</p>
    <ul>
      <li>Follow</li>
      <li>TCP Stream</li>
      <li>Choose one side of the communication</li>
      <li>Save Raw data</li>
    </ul>
  </li>
</ol>

<h1 id="corp_net1pcap-from-ftp-in-original-pcap">corp_net1.pcap (from FTP in original .pcap)</h1>

<p>Let’s have a look at the content.</p>

<p>I used the same approach for this <code class="language-plaintext highlighter-rouge">.pcap</code> as I did for the first one: <code class="language-plaintext highlighter-rouge">strings</code>.
After quickly scanning through the file I noticed more FTP traffic.
More specifically, this (truncated):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; tshark -r corp_net1.pcap -Y "ftp" -T fields -e _ws.col.Info

Request: SIZE puzzle.exe
Request: PORT 192,168,137,28,136,69

Request: SIZE Recycle-Bin.zip
Request: PORT 192,168,137,28,199,239

Request: SIZE archive
Request: PORT 192,168,137,28,206,7
</code></pre></div></div>

<p>As before, I retrieved the files by following the TCP Streams.</p>

<h1 id="flag-in-dns-traffic-in-corp_net1pcap">Flag in DNS traffic (in corp_net1.pcap)</h1>

<p>I scrolled to the bottom of the file and saw some funny looking DNS traffic.
In particular, a bunch of packages containing the word <code class="language-plaintext highlighter-rouge">OPT</code>.</p>

<blockquote>
  <p>[…] EDNS adds information to DNS messages in the form of pseudo-Resource Records (“pseudo-RR”s) included in the
“additional data” section of a DNS message. Note that this section exists in both requests and responses.</p>

  <p>EDNS introduces a single pseudo-RR type: OPT.</p>

  <p>As pseudo-RRs, OPT type RRs never appear in any zone file; they exist only in messages, fabricated by the DNS
participants.</p>

  <p><a href="https://en.wikipedia.org/wiki/Extension_Mechanisms_for_DNS">Wikipedia: Extension Mechanisms for DNS</a></p>
</blockquote>

<p>Well, maybe there’s something there:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; tshark -r corp_net1.pcap -Y "dns.opt" -T fields -e dns.qry.name

RFIE4RYNBINAUAAAAAGUSSCEKIAAAAUAAAAADYAIAIAAAAF2
WNF3GAAAAABXGQSJKQEAQCG34FH6
AAAABOKUSRCBKR4NV3O
[a bunch of more rows]
AC
BQAAEDAABAAAAOAAAA4AAABQAAQMAABAYAFU6ELWSNASK
RT5R57UAAAAAACJIVHEJLSCMCBA====
</code></pre></div></div>

<p>At first, I got very excited.
It’s Base64!!!
But Base64 can never have more than two equal signs for padding :(</p>

<p>What about Base32 encoding?</p>

<p>Looking at the first line retrieved it seems like we’re onto something:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; base32 --decode &lt;&lt;&lt; RFIE4RYNBINAUAAAAAGUSSCEKIAAAAUAAAAADYAIAIAAAAF2
�PNG
�
IHDR��%    
</code></pre></div></div>

<p>Could there be an image hidden here?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; while read -r line; do echo "$line" | base32 --decode &gt;&gt; image.png; done &lt; opt_strings

base32: invalid input
base32: invalid input
base32: invalid input
[many more rows]
</code></pre></div></div>

<p>Hmm.</p>

<p>I manually tested the second row:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; base64 --decode &lt;&lt;&lt; WNF3GAAAAABXGQSJKQEAQCG34FH6                
X�wW�)@!��Q�%  
</code></pre></div></div>

<p>at least not an error…
But the third line could not be decoded with either <code class="language-plaintext highlighter-rouge">base32</code> nor <code class="language-plaintext highlighter-rouge">base64</code>.</p>

<h1 id="flag-in-puzzleexe-from-ftp-in-corp_net1pcap">Flag in puzzle.exe (from FTP in corp_net1.pcap)</h1>

<p>A natural approach here would be to execute the program, but I don’t own a Windows PC….</p>

<p>Instead, the first thing I tried was to <code class="language-plaintext highlighter-rouge">hexdump</code> the file.
The last segment of the dump showed this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>01239050  2e 2e 77 2e 2e 2e 3f 2e  2e 2e 37 2e 2e 2e 2e 38  |..w...?...7....8|
01239060  70 79 74 68 6f 6e 33 31  32 2e 64 6c 6c 2e 2e 2e  |python312.dll...|
01239070  2e 2e 2e 2e 2e 2e 2e 2e  2e 2e 2e 2e 2e 2e 2e 2e  |................|
*
012390a0
</code></pre></div></div>

<p>and</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; strings your_program.exe | grep -i pyinstaller

._pyi_main_co....Traceback is disabled via bootloader option.....PYINSTALLER_RESET_ENVIRONMENT...1......._PYI_ARCHIVE_FILE......._PYI_APPLICATION_HOME_DIR......._PYI_PARENT_PROCESS_LEVEL......._PYI_SPLASH_IPC.0.......Invalid value in _PYI_PARENT_PROCESS_LEVEL: %s
.PYINSTALLER_STRICT_UNPACK_MODE..Failed to initialize security descriptor for temporary directory!
.....pyi-python-flag.Py_GIL_DISABLED.pyi-runtime-tmpdir......pyi-contents-directory..pyi-disable-windowed-traceback..PYINSTALLER_SUPPRESS_SPLASH_SCREEN......Failed to load splash screen resources!
.........Could not load PyInstaller's embedded PKG archive from the executable (%s)
.....Could not side-load PyInstaller's PKG archive from external file (%s)
.._pyinstaller_pyz........PYZ archive entry not found in the TOC!
</code></pre></div></div>

<p>indicates that the <code class="language-plaintext highlighter-rouge">.exe</code> was created using PyInstaller.</p>

<p>I tried <a href="https://github.com/extremecoders-re/pyinstxtractor">pyinstxtractor.py</a> but it throws the error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[+] Processing puzzle.exe
[!] Error : Missing cookie, unsupported pyinstaller version or not a pyinstaller archive]
</code></pre></div></div>

<p>and my conclusion is that pyinstxtractor.py does not support Python 3.12 (which seems to be the version used for the
<code class="language-plaintext highlighter-rouge">.exe</code>).</p>

<hr />

<p>Second thing I tried was actually starting the <code class="language-plaintext highlighter-rouge">.exe</code> in Wine.
That did not work, so I tried dosbox.
That did not work either…</p>

<hr />

<p>Third thing was to enlist a friend with a Windows computer!
He was able to start the application and found that it’s a puzzle game (who would’ve thought…).
If you do enough of the puzzle the flag can be read of the screen.</p>

<p>Flag: <code class="language-plaintext highlighter-rouge">CTF[HAPPYBIRTHDAY]</code> (<em>potentially with a space between <code class="language-plaintext highlighter-rouge">Y</code> and <code class="language-plaintext highlighter-rouge">B</code></em>)</p>

<h1 id="recycle-binzip-from-ftp-in-corp_net1pcap">Recycle-Bin.zip (from FTP in corp_net1.pcap)</h1>

<p>I started with the obvious:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; unzip Recycle-Bin.zip 
Archive:  Recycle-Bin.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of Recycle-Bin.zip or
        Recycle-Bin.zip.zip, and cannot find Recycle-Bin.zip.ZIP, period.
</code></pre></div></div>

<p>I tried repairing the archive:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; zip -FF Recycle-Bin.zip --out Recycle-Bin-fixed.zip &amp;&amp; unzip Recycle-Bin-fixed.zip
</code></pre></div></div>

<p>which yielded a new directory: <code class="language-plaintext highlighter-rouge">C</code> in which I could find some stuff:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C/$Recycle.Bin/S-1-5-21-2433278764-4034921937-2507072421-1164 $&gt; ll
total 1.8M
'$I0BN3IA.pdf'
'$I0DKZRU.txt'
'$I0G1V6C.txt'
'$I0P8KGD.txt'
'$I0S90LB.txt'
'$I1341CT.txt'
'$I15ZRRO.txt'
'$I161W3Y.txt'
'$I18MLB2.txt'
'$I1APZ4K.txt'
'$I1E5HE8.txt'
'$I1QVT66.txt'
'$I27B4VA.txt'
'$I283B2L.txt'
'$I2UBK07.jpg'
-- and much more --
</code></pre></div></div>

<p>I wrote a simple script to check whether any flags were hiding in plain sight:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find . -type f | while read -r file; do
    grep -Eo 'CTF' "$file" &amp;&amp; echo "Found in: $file"
done
</code></pre></div></div>

<p>but was unable to find anything.</p>

<p>Then I wrote another script to check the output of <code class="language-plaintext highlighter-rouge">file</code> for each file.
The output is summarised below:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- the majority of files were identified as Matlab files --
$I0BN3IA.pdf Matlab v4 mat-file (little endian) \330\367\332\0018, sparse, rows 0, columns 17051
$IZQJF50.txt Matlab v4 mat-file (little endian) \326\367\332\001=, sparse, rows 0, columns 4
...

-- second most common file type was ASCII or ASCII with CRLF line terminators -- 
$R0G1V6C.txt ASCII text
$R0P8KGD.txt ASCII text, with CRLF line terminators
...

-- then I found some images and some other files --
$R2UBK07.jpg JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 614x460, components 3
$R5QEV86.jpg JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 614x460, components 3
$RAUCOEZ.jpg JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 614x460, components 3
$RKKFUN8.jpg JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 614x460, components 3

$RDE2VJ7.ps1 Unicode text, UTF-8 (with BOM) text, with no line terminators
$R7ZRFQY.ps1 Unicode text, UTF-8 (with BOM) text, with no line terminators

$RCXHUFJ.zip Zip archive data, at least v2.0 to extract, compression method=deflate

$RL7SXXI.exe PE32 executable (GUI) Intel 80386, for MS Windows

$R0BN3IA.pdf PDF document, version 1.7, 1 pages (zip deflate encoded)
$R92DY1S.pdf PDF document, version 1.7, 1 pages (zip deflate encoded)
$RGRASUC.pdf PDF document, version 1.7, 1 pages (zip deflate encoded)
$RJLEGV5.pdf PDF document, version 1.7, 1 pages (zip deflate encoded)
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">.pdf</code> files listed above all contained a lorem ipsum paragraph and nothing more.</p>

<p>The images were hand drawn images of cats and dogs (and one image just had CAT written by hand in it).</p>

<p>Both powershell scripts contained: <code class="language-plaintext highlighter-rouge">Write-Host "Räkna ut din riktiga ålder"</code></p>

<p>The zip archive contained a <code class="language-plaintext highlighter-rouge">NewFileTime.txt</code> with the following content:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Server:

https://www.softwareok.de/?Download=NewFileTime
https://www.softwareok.com/?Download=NewFileTime
https://www.softwareok.eu/?Download=NewFileTime

http://www.softwareok.de/?Download=NewFileTime
http://www.softwareok.com/?Download=NewFileTime
http://www.softwareok.eu/?Download=NewFileTime
</code></pre></div></div>

<p>and a <code class="language-plaintext highlighter-rouge">NewFileTime_x64.exe</code>.</p>

<p>Running the <code class="language-plaintext highlighter-rouge">NewFileTime_x64.exe</code> seems to launch the application which can be downloaded from
<a href="https://www.softwareok.com/?Download=NewFileTime">softwareok.com</a>.
What a lovely website – but I don’t know what good the program is for…</p>

<hr />

<p>I then checked the dates for the files created.
Two files stood out as they were created years ago (not weeks like the other files).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; ls -lat
160 Jun  6  1984 '$I80Z3YX.txt'
 84 Jun  6  1984 '$R80Z3YX.txt'
</code></pre></div></div>

<p>and the contents of those files are:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; cat \$I80Z3YX.txt       
T�)�����BC:\Users\aleksej.pazjitnov\Downloads\INKEMW2QIVHFIT2NJFHE6U25.txt%

$&gt; cat \$R80Z3YX.txt       
Jamborino Jamboro Archipelago Island Apple Horse
Monkey Dog
Kit Kat House Flower
</code></pre></div></div>

<p><a href="https://sv.wikipedia.org/wiki/Aleksej_Pazjitnov">Aleksej Pazjitnov</a> created Tetris <em>in 1984</em>.
This has to be some sort of clue?</p>

<h1 id="flag-in-archive-from-ftp-in-corp_net1pcap">Flag in archive (from FTP in corp_net1.pcap)</h1>

<p>According to <code class="language-plaintext highlighter-rouge">file</code>, <code class="language-plaintext highlighter-rouge">archive</code> is a gzip compressed archive:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; file archive
archive: gzip compressed data, from Unix, original size modulo 2^32 847
</code></pre></div></div>

<p>so I added a <code class="language-plaintext highlighter-rouge">.gz</code> extension to the file and attempted to unpack it using <code class="language-plaintext highlighter-rouge">gunzip</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; gunzip archive.gz    
gzip: archive already exists; do you wish to overwrite (y or n)? y
</code></pre></div></div>

<p>Out the other end came a new file, which was also a gzip archive.
And then another one.
And another one.</p>

<p>But I kept at it, running my one liner:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; mv archive archive.gz &amp;&amp; gunzip archive.gz &amp;&amp; file archive &amp;&amp; cat archive
</code></pre></div></div>

<p>until finally:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; mv archive archive.gz &amp;&amp; gunzip archive.gz &amp;&amp; file archive &amp;&amp; cat archive
archive: POSIX tar archive (GNU)
input0000664000175000017500000000100714661054721010664 0ustar  useruser�����`f�S�����͌&gt;��9��H���%00�S:v��G��c!�wO����1?��~R���?�S�f���ykr�ey�g��~wnl^����kq�١��e��b�"6&gt;|��:����WS�'9�{ċ^��'g���I��k�ѓ����&gt;ޗ�ss��Uǚ]��_������mk��������U֍�ߛ�?�z�����I����]Ͽ[[yi���=����*��������R![]�������n����z�����������ﻷ�[_U����+w����������/�?U7��z}����ìs�~�x������u�������)�o�~����7u�_o.���2���/m�~�����}��ׂSU�k��ߟ[��w׷/�O���o����������[?��r��ӐM_sS�O&amp;�1V�UTr�y~���]BB�\F��������[m�&lt;f��-�f��੭u�������|�9@����W6iј���46+�y�����%       
</code></pre></div></div>

<p>A TAR!!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; tar xvf archive
input

$&gt; cat input 
�����`f�S�����͌&gt;��9��H���%00�S:v��G��c!�wO����1?��~R���?�S�f���ykr�ey�g��~wnl^����kq�١��e��b�"6&gt;|��:����WS�'9�{ċ^��'g���I��k�ѓ����&gt;ޗ�ss��Uǚ]��_������mk��������U֍�ߛ�?�z�����I����]Ͽ[[yi���=����*��������R![]�������n����z�����������ﻷ�[_U����+w����������/�?U7��z}����ìs�~�x������u�������)�o�~����7u�_o.���2���/m�~�����}��ׂSU�k��ߟ[��w׷/�O���o����������[?��r��ӐM_sS�O&amp;�1V�UTr�y~���]BB�\F��������[m�&lt;f��-�f��੭u�������|�9@����W6iј���46+�y�����%                                   erik@pop-os: ~/Nextcloud/-shared/ctf $&gt; file input 
input: gzip compressed data, from Unix, original size modulo 2^32 496
</code></pre></div></div>

<p><em>really ?</em></p>

<p>After repeating the process of alternating <code class="language-plaintext highlighter-rouge">gunzip</code> and <code class="language-plaintext highlighter-rouge">tar</code> I finally was rewarded:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; mv input input.gz &amp;&amp; gunzip input.gz &amp;&amp; file input &amp;&amp; cat input
input: ASCII text
CTF[IRRITATING]
</code></pre></div></div>

<p>Flag: <code class="language-plaintext highlighter-rouge">CTF[IRRITATING]</code></p>

<h1 id="corp_net2pcap-from-ftp-in-original-pcap">corp_net2.pcap (from FTP in original .pcap)</h1>

<p>A bunch of TLS traffic.
Haven’t investigated this further.</p>

<h1 id="disk1imggz-from-ftp-in-corp_net1pcap">disk1.img.gz (from FTP in corp_net1.pcap)</h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; file disk1.img
disk1.img: DOS/MBR boot sector; partition 1 : ID=0xc, start-CHS (0x0,32,33), end-CHS (0x82,138,8), startsector 2048, 2095104 sectors
</code></pre></div></div>

<p>Let’s mount this using the offset calculated as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Offset = Start Sector × Sector Size
       = 2048 × 512 bytes
       = 1,048,576 bytes
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; sudo mount -o loop,offset=1048576 disk1.img $(pwd)/diskmount

$&gt; ls -la diskmount
total 12
drwxr-xr-x 2 root root 4096 Jan  1  1970 .
drwxrwxr-x 3 erik erik 4096 Oct  1 10:39 ..
-rwxr-xr-x 1 root root   48 Sep 24 09:54 secret.encrypted

$&gt; file diskmount/secret.encrypted 
diskmount/secret.encrypted: openssl enc'd data with salted password
</code></pre></div></div>

<p>I fetched <code class="language-plaintext highlighter-rouge">openssl2john.py</code> from
<a href="https://raw.githubusercontent.com/openwall/john/bleeding-jumbo/run/openssl2john.py">GitHub</a> and used it to convert the 
encrypted secret to a hash.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python openssl2john.py diskmount/secret.encrypted &gt; secret.hash
</code></pre></div></div>

<p>I then downloaded <a href="https://github.com/openwall/john">John the Ripper</a> and tried to use the <code class="language-plaintext highlighter-rouge">WORDLIST.txt</code> we found
earlier to see if we could find a match:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; ./john --wordlist=WORDLIST.txt secret.hash
Loaded 1 password hash (openssl-enc, OpenSSL "enc" encryption [32/64])
NATHAN           (secret.encrypted)     
1g 0:00:00:00 DONE (2024-10-01 11:24) 50.00g/s 10000p/s 10000c/s 10000C/s 123456..SEPTEMBER
Session completed. 
</code></pre></div></div>

<p>But I believe <code class="language-plaintext highlighter-rouge">NATHAN</code> was a false positive as I was unable to use it for decrypting the <code class="language-plaintext highlighter-rouge">secret.encrypeted</code> file.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; openssl enc -d -aes-256-cbc -md sha256 -in diskmount/secret.encrypted -pass pass:NATHAN
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
bad decrypt
40F7EEBFD27E0000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:124:
� �P6�i�@�^EiR%   
</code></pre></div></div>

<p>I also tried adding the pbkdf2 flag:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; openssl enc -d -pbkdf2 -aes-256-cbc -md sha256 -in diskmount/secret.encrypted -pass pass:NATHAN
bad decrypt
40572AB1327D0000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:124:
˺4�G�ҹ��8=�%
</code></pre></div></div>

<p>I then skipped wrote a little script that tried all passwords in <code class="language-plaintext highlighter-rouge">WORDLIST.txt</code> and found several matches (<code class="language-plaintext highlighter-rouge">NICOLE</code>, <code class="language-plaintext highlighter-rouge">BEAUTIFUL</code>,
…) but they all turned out to be false positives.</p>

<p>When I tested John the Ripper with <code class="language-plaintext highlighter-rouge">rockyou.txt</code> I got <em>thousands</em> of false positives and somewhere around here I gave
up for now.</p>

<p>I also tried all passwords in <code class="language-plaintext highlighter-rouge">WORDLIST.txt</code> (after removing the brackets) in combination with all ciphers and digests
using this script:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash

ENCRYPTED_FILE="diskmount/secret.encrypted"
WORDLIST="WORDLIST.txt"
OUTPUT_FILE="secret.decrypted"
CIPHERS=("aes-256-cbc" "aes-192-cbc" "aes-128-cbc" "des3" "des")
DIGESTS=("md5" "sha1" "sha256" "sha512")

check_decryption() {
    FILE_TYPE=$(file -b "$1")
    if [[ "$FILE_TYPE" != "data" ]]; then
        return 0
    else
        return 1
    fi
}

while IFS= read -r PASSWORD; do
    for CIPHER in "${CIPHERS[@]}"; do
        for DIGEST in "${DIGESTS[@]}"; do
            openssl enc -d -"${CIPHER}" -md "${DIGEST}" -in "${ENCRYPTED_FILE}" -out "${OUTPUT_FILE}" -pass pass:"${PASSWORD}" 2&gt;/dev/null
            if [ $? -eq 0 ] &amp;&amp; check_decryption "${OUTPUT_FILE}"; then
                echo "Success with password: $PASSWORD, cipher: $CIPHER, digest: $DIGEST"
                echo "Content of decrypted file:"
                echo "$(cat ${OUTPUT_FILE})"
                mv ${OUTPUT_FILE} "${OUTPUT_FILE}$(date)"
            fi
        done
    done
done &lt; "${WORDLIST}"
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>erik@pop-os: ~/Nextcloud/-shared/ctf/disk1 $&gt; bash script.sh
Success with password: BEAUTIFUL, cipher: aes-128-cbc, digest: sha256
Content of decrypted file:
���70̙�Kt��
��[f�
�W��0jo d�_3
</code></pre></div></div>

<p>Note that the bash script found <code class="language-plaintext highlighter-rouge">BEAUTIFUL</code> with cipher aes-128-cbc which I sort of take as a hint that I’m on the right
way, because one flag was literally: <code class="language-plaintext highlighter-rouge">CTF[AES128]</code>.
That has to be a hint?!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$&gt; file secret.decrypted 
secret.decrypted: Non-ISO extended-ASCII text
</code></pre></div></div>

<p>I actually tried running <em>this</em> file through the same script (maybe it’s double-encrypted?!), but got nothing out of it.</p>

<h2 id="wordlisttxt-from-ftp-in-corp_net1pcap">WORDLIST.txt (from FTP in corp_net1.pcap)</h2>

<p>I think it makes sense that this file should be used to decrypt the file in the image mounted above…</p>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[I made an attempt to get as many flags I could in one day because that’s the amount of time I could set aside for this. I got five flags out of nine. I think I might be close to one more and I think the Recycle Bin has one. The last two flags though – I have no idea.]]></summary></entry><entry><title type="html">Install a GUI application in Docker using Wine</title><link href="https://thorsell.io/2024/09/10/wine-in-docker.html" rel="alternate" type="text/html" title="Install a GUI application in Docker using Wine" /><published>2024-09-10T00:00:00+02:00</published><updated>2024-09-10T00:00:00+02:00</updated><id>https://thorsell.io/2024/09/10/wine-in-docker</id><content type="html" xml:base="https://thorsell.io/2024/09/10/wine-in-docker.html"><![CDATA[<p>Here’s a post I never thought I’d write…</p>

<p>I’m currently working with a client who has a very extensive support agreement with their users.
The client develops embedded software for a plethora of different PLCs and because of this it’s not always super easy to
find compilers.
(Or, it’s rather the case that the company that makes the PLC also creates a compiler and you need both in order to
create your product.)
Because of the way my client offer support, they must ensure that they can build and patch their software for years to
come.
It is not infeasible that my client will need to re-build and patch a product they released back in 2010.
So how do I help my client make sure they can do this?</p>

<p>Over the past couple of years – and long before I came into the picture – the client has built a very good build
system based on Docker.
In essence, the build system checks a config file in the product’s repository, downloads the appropriate Docker images
and executes the build/test commands specified in the config.
Any solution I come up with must be compatible with this way of working.
I have therefore spent the past couple of days creating a new Docker image + config to build yet another (old) product.</p>

<h1 id="windows-for-real">Windows. For real?</h1>

<p>When my client told me that <em>part of</em> the build included a compiler which only runs on Windows, my heart stopped a
little.
Note the <em>part of</em>.
The product is cross-compiled, makes use of compilers that only run on Linux and uses one compiler which only runs on
Windows – <em>yay</em>!</p>

<p>A predecessor of mine had configured a way of building this project in a Linux environment using
<a href="https://www.winehq.org/">Wine</a> for the Windows compiler and my instructions from the client were clear:</p>

<blockquote>
  <p>Try to replicate the old build system as closely as possible!</p>
</blockquote>

<p>So… the task was to create a Docker image and a build config, capable of running both the Linux compilers <em>and</em> the
super old Windows-only compiler.</p>

<h1 id="wine-in-docker">Wine in Docker</h1>

<p>The first thing I did was to check whether anyone else had been crazy enough to attempt this already.
Naturally, someone had -&gt; <a href="https://github.com/scottyhardy/docker-wine/blob/master/Dockerfile">scottyhardy/docker-wine</a>.</p>

<p>I looked through Scotty’s code and borrowed the Wine installation step he cooked up:</p>

<pre><code class="language-YAML"># Install Wine
RUN wget -nv -O- https://dl.winehq.org/wine-builds/winehq.key | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 apt-key add - \
    &amp;&amp; echo "deb https://dl.winehq.org/wine-builds/ubuntu/ $(grep VERSION_CODENAME= /etc/os-release | cut -d= -f2) main" &gt;&gt; /etc/apt/sources.list \
    &amp;&amp; dpkg --add-architecture i386 \
    &amp;&amp; apt-get update \
    &amp;&amp; DEBIAN_FRONTEND="noninteractive" apt-get install -y --install-recommends winehq-stable \
    &amp;&amp; rm -rf /var/lib/apt/lists/*
</code></pre>

<p>Thereafter I needed to figure out how to install the Windows compiler.
Naturally, the compiler cannot be installed headless but makes use of an install wizard in which you must click: <code class="language-plaintext highlighter-rouge">Next &gt;
Next &gt; Next &gt; ...</code>.
How do you do that while building a Docker image?</p>

<p>Well, you use <a href="https://en.wikipedia.org/wiki/Xvfb">Xvfb</a> to create a virtual framebuffer in your build process and open
Wine inside of it, in order to trick the installer that it’s actually running in a GUI and that someone is repeatedly 
clicking along in the wizard.</p>

<pre><code class="language-YAML">ENV DISPLAY=:0
RUN xvfb-run --auto-servernum --server-args="-screen 0 1024x768x16" bash -c \
    "wine /opt/compiler.exe &amp; \
    pid=\$! &amp;&amp; while kill -0 \$pid 2&gt; /dev/null; do \
    xdotool key Return; \
    echo 'Pressed Enter'; \
    sleep 15; \
    done &amp;&amp; wait \$pid &amp;&amp; echo 'Wine process finished'"
</code></pre>

<p>In Swedish, you can say that you “do violence” on something, and I think this is exactly what that expression is meant
to be used for.
I feel like I have done violence on both Docker and Wine and the poor compiler.
But it works(!) and the client is happy.</p>]]></content><author><name>Erik Thorsell</name></author><summary type="html"><![CDATA[Here’s a post I never thought I’d write…]]></summary></entry></feed>