diff --git a/art/drawhearts/index.html b/art/drawhearts/index.html index f7700009..f035b50f 100644 --- a/art/drawhearts/index.html +++ b/art/drawhearts/index.html @@ -33,7 +33,7 @@ void main (void) { gl_FragColor = vec4(color,1.0); } I tinkered around for quite a while before discovering that I can intersect two xyxy skewed ellipses -with the absolute value operator. Here’s my custom equation for the heart shape.'>

Jan 18, 2026

Step to the πŸ’— beat

My first procedurally generated animation using shaders.

The shader can be visualized with glslViewer.

uniform vec2 u_mouse;
+with the absolute value operator. Here’s my custom equation for the heart shape.'>

Jan 18, 2026

Step to the πŸ’— beat

My first procedurally generated animation using shaders.

The shader can be visualized with glslViewer.

uniform vec2 u_mouse;
 uniform vec2 u_resolution;
 uniform float u_time;
 
diff --git a/art/index.xml b/art/index.xml
index b2d7ecbf..8ed41e15 100644
--- a/art/index.xml
+++ b/art/index.xml
@@ -1,6 +1,6 @@
 Art on lavafrothhttps://lavafroth.is-a.dev/art/Recent content in Art on lavafrothHugoen-usSun, 18 Jan 2026 08:06:06 +0530Step to the πŸ’— beathttps://lavafroth.is-a.dev/art/drawhearts/Sun, 18 Jan 2026 08:06:06 +0530https://lavafroth.is-a.dev/art/drawhearts/<p>My first procedurally generated animation using shaders.</p>
 <p>The shader can be visualized with <code>glslViewer</code>.</p>
-<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-glsl" data-lang="glsl"><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_mouse;
+<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-glsl" data-lang="glsl"><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_mouse;
 </span></span><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_resolution;
 </span></span><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">float</span> u_time;
 </span></span><span style="display:flex;"><span>
diff --git a/easy-ssh-tunnel/index.html b/easy-ssh-tunnel/index.html
new file mode 100644
index 00000000..f4313b20
--- /dev/null
+++ b/easy-ssh-tunnel/index.html
@@ -0,0 +1,38 @@
+- lavafroth

Easy SSH tunnel
+ + + + +
+
\ No newline at end of file diff --git a/index.html b/index.html index f34cd535..4430cb59 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ -lavafroth

Working With LUKS File Stashes


Linux +lavafroth

Algebraic Python Enums


Python diff --git a/index.xml b/index.xml index b7510c26..f33fc6b0 100644 --- a/index.xml +++ b/index.xml @@ -1,6 +1,6 @@ lavafrothhttps://lavafroth.is-a.dev/Recent content on lavafrothHugoen-usSun, 18 Jan 2026 08:06:06 +0530Step to the πŸ’— beathttps://lavafroth.is-a.dev/art/drawhearts/Sun, 18 Jan 2026 08:06:06 +0530https://lavafroth.is-a.dev/art/drawhearts/<p>My first procedurally generated animation using shaders.</p> <p>The shader can be visualized with <code>glslViewer</code>.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-glsl" data-lang="glsl"><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_mouse; +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-glsl" data-lang="glsl"><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_mouse; </span></span><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">vec2</span> u_resolution; </span></span><span style="display:flex;"><span><span style="color:#66d9ef">uniform</span> <span style="color:#66d9ef">float</span> u_time; </span></span><span style="display:flex;"><span> @@ -40,7 +40,7 @@ without having to completely format the drive anew.</p> with LUKS container files that are encrypted at rest and can be decrypted on demand with knowledge of the passphrase.</p> <h2 id="creating-the-image-base">Creating the image base</h2> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>head --bytes<span style="color:#f92672">=</span>4G /dev/urandom &gt; stash.img +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>head --bytes<span style="color:#f92672">=</span>4G /dev/urandom &gt; stash.img </span></span></code></pre></div><h2 id="format-the-image">Format the image</h2> <p>The image can be formatted by either including the header in the image itself or keeping a detached header.</p>Algebraic Python Enumshttps://lavafroth.is-a.dev/post/algebraic-python-enums/Sun, 02 Nov 2025 19:08:46 +0530https://lavafroth.is-a.dev/post/algebraic-python-enums/<p>University has compelled me to use Python despite my preference for Rust, @@ -657,12 +657,12 @@ the last branch which returns instead of calling the decryption subroutine.</ <p>I found some hex in a file called fleg, but I’m not sure how it’s encoded. I’m pretty sure it’s some kind of xor…</p> <h1 id="exploration">Exploration</h1> <p>We begin by creating a new rust project.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cargo new amateurs +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cargo new amateurs </span></span><span style="display:flex;"><span>cd amateurs </span></span><span style="display:flex;"><span>cargo add hex </span></span><span style="display:flex;"><span>cargo add itertools </span></span></code></pre></div><p>Let&rsquo;s decode the hexadecimal contents of the file using the following Rust code:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; Result<span style="color:#f92672">&lt;</span>(), Box<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">dyn</span> std::error::Error<span style="color:#f92672">&gt;&gt;</span> { +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">main</span>() -&gt; Result<span style="color:#f92672">&lt;</span>(), Box<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">dyn</span> std::error::Error<span style="color:#f92672">&gt;&gt;</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> bytes <span style="color:#f92672">=</span> hex::decode(<span style="color:#e6db74">&#34;610c6115651072014317463d73127613732c73036102653a6217742b701c61086e1a651d742b69075f2f6c0d69075f2c690e681c5f673604650364023944&#34;</span>)<span style="color:#f92672">?</span>; </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">let</span> stream <span style="color:#f92672">=</span> String::from_utf8_lossy(<span style="color:#f92672">&amp;</span>bytes); </span></span><span style="display:flex;"><span> <span style="color:#a6e22e">println!</span>(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{:?}</span><span style="color:#e6db74">&#34;</span>, stream); @@ -677,7 +677,7 @@ reference meant.</p> <p>In the list of functions under the Symbol Tree, we can navigate to the <code>entry</code> function which looks like:</p>Waiting an Eternityhttps://lavafroth.is-a.dev/post/wait-an-eternity-web-challenge-amateursctf-2023/Wed, 19 Jul 2023 07:53:17 +0530https://lavafroth.is-a.dev/post/wait-an-eternity-web-challenge-amateursctf-2023/<p>This was a fairly straightforward and fun challenge that required a bit of common sense to solve. We are given the URL <a href="https://waiting-an-eternity.amt.rs">https://waiting-an-eternity.amt.rs</a> to begin with.</p> <p>Let&rsquo;s use <code>curl</code> with its verbose flag to fetch this URL.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>curl -v <span style="color:#e6db74">&#34;https://waiting-an-eternity.amt.rs&#34;</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>curl -v <span style="color:#e6db74">&#34;https://waiting-an-eternity.amt.rs&#34;</span> </span></span></code></pre></div><p>We get a response that tells us to wait an enternity.</p> <pre tabindex="0"><code>&gt; GET / HTTP/2 &gt; Host: waiting-an-eternity.amt.rs @@ -700,7 +700,7 @@ Tired of this workflow that I somehow spiraled into, I&rsquo;m now seeking t Linux way of doing things. It has an amazing package manager as well as <a href="https://aur.archlinux.org">a user repository</a> for extra software unavailable in the official repositories. It&rsquo;s rather easy to setup Arch for gaming, thanks to programs like <a href="https://lutris.net/">Lutris</a> and <a href="https://usebottles.com/">Bottles</a>.</p>Twosumhttps://lavafroth.is-a.dev/post/picoctf-binary-exploitation-twosum/Mon, 10 Apr 2023 08:44:28 +0530https://lavafroth.is-a.dev/post/picoctf-binary-exploitation-twosum/<p>This is a rather simple binary exploitation challenge. We are given the following source code for the program running on the remote server:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;stdio.h&gt;</span><span style="color:#75715e"> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;stdio.h&gt;</span><span style="color:#75715e"> </span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;stdlib.h&gt;</span><span style="color:#75715e"> </span></span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">int</span> <span style="color:#a6e22e">addIntOvf</span>(<span style="color:#66d9ef">int</span> result, <span style="color:#66d9ef">int</span> a, <span style="color:#66d9ef">int</span> b) { @@ -757,7 +757,7 @@ the second. Adding them would cause the result to wrap around and become negativ We are also given the source code of the application.</p> <p>Taking a look at the <code>src/main/java/io/github/nandandesai/pico/security</code> subdirectory of the project, we see that it uses JWT.</p> <p>Interestingly, the file <code>SecretGenerator.java</code> in the aforementioned directory contains a weak hardcoded <em>&ldquo;random&rdquo;</em> value 😱.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Service</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Service</span> </span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">SecretGenerator</span> { </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">private</span> Logger logger <span style="color:#f92672">=</span> LoggerFactory.<span style="color:#a6e22e">getLogger</span>(SecretGenerator.<span style="color:#a6e22e">class</span>); </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">final</span> String SERVER_SECRET_FILENAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;server_secret.txt&#34;</span>; @@ -793,7 +793,7 @@ We are also given the source code of the application.</p> going to be client side. We are asked to visit the <a href="http://jupiter.challenges.picoctf.org:42899/">challenge page</a>.</p> <p>From here, we can view the source code of the page.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt; +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt; </span></span><span style="display:flex;"><span> &lt;<span style="color:#f92672">head</span>&gt; </span></span><span style="display:flex;"><span> &lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;jquery-3.3.1.min.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt; </span></span><span style="display:flex;"><span> &lt;<span style="color:#f92672">script</span>&gt; @@ -845,7 +845,7 @@ about.</p> <a href="http://mercury.picoctf.net:60022/index.html">http://mercury.picoctf.net:60022/index.html</a> where we find a simple textbox prompting us to submit the flag.</p> <p>Looking at the page source by pressing <code>ctrl</code> <code>u</code>, we see that it is sourcing javascript code from <code>rTEuOmSfG3.js</code>.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rTEuOmSfG3.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt; +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rTEuOmSfG3.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt; </span></span></code></pre></div><p>While examining the javascript, we will notice that it is obfuscated and packed. Put this through <a href="https://lelinhtinh.github.io/de4js/">de4js</a> to prettify it.</p>Kringlecon 2022 Writeuphttps://lavafroth.is-a.dev/post/kringlecon-2022-writeup/Mon, 09 Jan 2023 10:36:35 +0530https://lavafroth.is-a.dev/post/kringlecon-2022-writeup/<p>This writeup is rather haphazard as I jumped around from one place to another @@ -855,7 +855,7 @@ before diving in.</p> <h3 id="clone-with-a-difference">Clone with a Difference</h3> <p>This challenge wants us to clone a git repository. It&rsquo;s using git with ssh for cloning which doesn&rsquo;t seem to work.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git clone git@haugfactory.com:asnowball/aws_scripts.git +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git clone git@haugfactory.com:asnowball/aws_scripts.git </span></span></code></pre></div><p>We can clone this the HTTPS way:</p>Pixelatedhttps://lavafroth.is-a.dev/post/picoctf-cryptography-pixelated/Tue, 22 Nov 2022 09:25:20 +0530https://lavafroth.is-a.dev/post/picoctf-cryptography-pixelated/<p>This challenge gives use two images and asks us if we can make a flag out of them. At first glance, both the images look like noise. Upon a quick web lookup of <a href="https://en.wikipedia.org/wiki/Visual_cryptography">visual cryptography</a>, it appears @@ -875,17 +875,17 @@ given 2 seconds to SHA512 hash the message represented by the binary provided string. We must send the response with the request parameter <code>r</code>. Let&rsquo;s write a go program to do that.</p> <p>First let&rsquo;s declare the url as a constant.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uri</span> = <span style="color:#e6db74">&#34;http://challenges.ringzer0team.com:10014/&#34;</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uri</span> = <span style="color:#e6db74">&#34;http://challenges.ringzer0team.com:10014/&#34;</span> </span></span></code></pre></div><p>We fetch the challenge page and defer closing its body once the program ends.</p>Hash Me Pleasehttps://lavafroth.is-a.dev/post/r0-hash-me-please/Fri, 19 Aug 2022 09:57:00 +0530https://lavafroth.is-a.dev/post/r0-hash-me-please/<p>In this RingZer0 challenge, we are asked to visit <a href="http://challenges.ringzer0team.com:10013/">http://challenges.ringzer0team.com:10013/</a> and are given 2 seconds to hash the provided message using the SHA512 algorithm. We must send the response as <a href="http://challenges.ringzer0team.com:10013/?r=response">http://challenges.ringzer0team.com:10013/?r=<em>response</em></a> and to do that, we&rsquo;ll be using some Golang.</p> <p>Let&rsquo;s declare the URI as a constant.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uri</span> = <span style="color:#e6db74">&#34;http://challenges.ringzer0team.com:10013/&#34;</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uri</span> = <span style="color:#e6db74">&#34;http://challenges.ringzer0team.com:10013/&#34;</span> </span></span></code></pre></div><p>We fetch the challenge page using the <code>Get</code> function from the <code>http</code> standard library, checking for errors along the way.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">uri</span>) +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">uri</span>) </span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> { </span></span><span style="display:flex;"><span> <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalln</span>(<span style="color:#a6e22e">err</span>) </span></span><span style="display:flex;"><span>} @@ -903,7 +903,7 @@ Before you walk away saying, <a href="https://www.youtube.com/watch?v=RXS1sJm The redirection, however, is done using javascript instead of regular HTTP status codes like 302. This meant, one couldn&rsquo;t simply run the following and expect to see a video.</p>Bash Jail 3https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail3/Sun, 24 Jul 2022 12:29:56 +0530https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail3/<h1 id="the-challenge">The challenge</h1> <p>Logging into the box we are told that the flag is located at <code>/home/level3/flag.txt</code>.</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> check_space <span style="color:#f92672">{</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> check_space <span style="color:#f92672">{</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $1 <span style="color:#f92672">==</span> *<span style="color:#f92672">[</span>bdksc<span style="color:#f92672">]</span>* <span style="color:#f92672">]]</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span> @@ -928,7 +928,7 @@ This meant, one couldn&rsquo;t simply run the following and expect to see a which means we cannot exfiltrate the flag from <code>stderr</code> since it is blocked.</p>Bash Jail 2https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail2/Sun, 24 Jul 2022 12:28:56 +0530https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail2/<h1 id="the-challenge">The challenge</h1> <p>Logging into the box we are told that the flag is located at <code>/home/level2/flag.txt</code></p> <h3 id="challenge-bash-code">Challenge bash code</h3> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> check_space <span style="color:#f92672">{</span> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> check_space <span style="color:#f92672">{</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $1 <span style="color:#f92672">==</span> *<span style="color:#f92672">[</span>bdks<span style="color:#e6db74">&#39;;&#39;&#39;&amp;&#39;&#39; &#39;</span><span style="color:#f92672">]</span>* <span style="color:#f92672">]]</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span> @@ -955,7 +955,7 @@ string among <code>b</code>,<code>d</code>,<code>k return 1, we get a &ldquo;restricted characters&rdquo; message and no further processing happens.</p>Bash Jail 1https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail1/Sun, 24 Jul 2022 12:27:56 +0530https://lavafroth.is-a.dev/post/ringzer0ctf-bash-jail1/<h1 id="the-challenge">The challenge</h1> <p>Upon SSHing into the box, we are told that the flag is located at <code>/home/level1/flag.txt</code></p> <p>Challenge bash code:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">while</span> : +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">while</span> : </span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> </span></span><span style="display:flex;"><span> echo <span style="color:#e6db74">&#34;Your input:&#34;</span> </span></span><span style="display:flex;"><span> read input @@ -979,7 +979,7 @@ installing the suite of tools.</p> <h1 id="operation-oni">Operation Oni</h1> <p>The challenge has an associated instance which we&rsquo;ll need to log into using SSH using the following command:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh -i key_file -p <span style="color:#ae81ff">61948</span> ctf-player@saturn.picoctf.net +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh -i key_file -p <span style="color:#ae81ff">61948</span> ctf-player@saturn.picoctf.net </span></span></code></pre></div><p>We are provided with a compressed disk image <code>disk.img.gz</code> which we&rsquo;ll decompress with:</p>JAuthhttps://lavafroth.is-a.dev/post/picoctf-web-challenge-jauth/Tue, 22 Feb 2022 14:49:34 +0530https://lavafroth.is-a.dev/post/picoctf-web-challenge-jauth/<p>The challenge description states that most web application developers use third party components without testing their security. It mentions some past affected companies, then asks us to identify and exploit the vulnerable component for the challenge at <a href="http://saturn.picoctf.net:52025/">http://saturn.picoctf.net:52025/</a></p> <p>The goal is to become an <code>admin</code>. @@ -992,12 +992,12 @@ We are provied with the username <code>test</code> and the password <p>Since the mileage for second step might vary from person to person, I&rsquo;ll elaborate on the first step.</p> <p>I chose <a href="https://codeberg.org/jbruchon/jdupes">jdupes</a> for deleting the duplicates because it&rsquo;s open-source and is cross platform.</p> <p>For a given folder we would run the following to wipe the duplicates:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>jdupes --recurse --delete --no-prompt --zero<span style="color:#f92672">-match</span> . +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>jdupes --recurse --delete --no-prompt --zero<span style="color:#f92672">-match</span> . </span></span></code></pre></div><p>This will recursively delete all the duplicates except the source file without prompting for a confirmation. It will also consider zero length files to be duplicates.</p>Notepadhttps://lavafroth.is-a.dev/post/picoctf-web-challenge-notepad/Mon, 21 Feb 2022 09:24:30 +0530https://lavafroth.is-a.dev/post/picoctf-web-challenge-notepad/<p>At first glance the webapp looks like a stripped down version of Pastebin where we can post a text / code snippet. After submitting the query, we are redirected to an html page containing the content of the post.</p> <p>The first thing I tried was triggering XSS (cross site scripting) with the following:</p> -<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;<span style="color:#a6e22e">alert</span>(<span style="color:#ae81ff">1</span>)&lt;/<span style="color:#f92672">script</span>&gt; +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;<span style="color:#a6e22e">alert</span>(<span style="color:#ae81ff">1</span>)&lt;/<span style="color:#f92672">script</span>&gt; </span></span></code></pre></div><p>The application source directory tree looks like the following:</p> <pre tabindex="0"><code>. β”œβ”€β”€ app.py @@ -1013,7 +1013,138 @@ After submitting the query, we are redirected to an html page containing the con Additionally some might even keep <code>builtins</code> and <code>eval</code> out of reach.</p> <p><a href="https://www.youtube.com/watch?v=SN6EVIG4c-0">Here</a> is a cool video explanation by @pwnfunction on server side template injection wherein he mentions a way to &ldquo;gadget&rdquo; our way out of Flask&rsquo;s Jinja2 backend to get remote code execution.</p> -<p>Here&rsquo;s the gist of it:</p><link>https://lavafroth.is-a.dev/project_mana/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lavafroth.is-a.dev/project_mana/</guid><description><div id="stage"> +<p>Here&rsquo;s the gist of it:</p></description></item><item><title/><link>https://lavafroth.is-a.dev/easy-ssh-tunnel/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lavafroth.is-a.dev/easy-ssh-tunnel/</guid><description><!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8" /> + <title>Easy SSH tunnel</title> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="description" content="" /> + <style> + * { + padding: 0; + margin: 0; + font-family: monospace; + } + body { + background: oklch(29.3% 0.136 325.661); + font-size: 2rem; + } + + input { + outline: none; + font-size: 2rem; + color: #eee; + border: none; + padding: .5rem; + background: oklch(29.3% 0.136 325.661); + &:invalid { + background: oklch(40.8% 0.153 2.432); + } + } + + .post-content > div { + margin: 1rem auto; + + label:not([for=direction]) { + padding: .5rem; + background: lab(78.5378% 39.3533 -32.9615); + color: oklch(29.3% 0.136 325.661); + } + + } + + .grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(2, minmax(0, 1fr)); + grid-auto-flow: column; + + justify-content: space-between; + align-items: center; + + * { + text-align: center; + } + + label[for=direction] { + user-select: none; + grid-row: -1 / 1; + color: #eee; + &::after { + display: block; + content: '👠'; + } + } + + input[type="checkbox"] { + opacity: 0; + position: absolute; + pointer-events: none; + } + + &:has(input:checked) { + label[for=direction]::after { + content: '👒'; + } + } + } + + #remotehost { + width: 62rem; + } + #command { + background: oklch(45.2% 0.211 324.591); + margin-top: 1rem; + padding: 0.5rem; + color: #eee; + } + </style> +</head> +<body> + + <div class="grid"> + + <label for="localport">Local port</label> + <input id="localport" type="number" min="0" max="65535" step=1 value=8888> + + <input type=checkbox id="direction"> + <label for="direction">Traffic direction</label> + + <label for="remoteport">Remote port</label> + <input id="remoteport" type="number" min="0" max="65535" step=1 value=8888> + </div> + + <div> + <label for="remotehost">connecting to</label> + <input id="remotehost" type="text" placeholder="user@remote.host"> + <div id='command'></div> + </div> +</body> + +<script> + const localPort = document.querySelector('#localport'); + const remotePort = document.querySelector('#remoteport'); + const remoteHost = document.querySelector('#remotehost'); + const direction = document.querySelector('#direction'); + const command = document.querySelector('#command'); + + function updateCommand() { + const localPortValue = localPort.value; + const remotePortValue = remotePort.value; + const directionString = direction.checked ? "R" : "L"; + const remoteHostValue = remoteHost.value || remoteHost.placeholder; + command.textContent = `ssh -f -N -${directionString} ${localPortValue}:localhost:${remotePortValue} ${remoteHostValue}`; + } + + localPort.addEventListener('input', updateCommand); + direction.addEventListener('change', updateCommand); + remotePort.addEventListener('input', updateCommand); + remoteHost.addEventListener('input', updateCommand); + + updateCommand() + </script> +</html></description></item><item><title/><link>https://lavafroth.is-a.dev/project_mana/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://lavafroth.is-a.dev/project_mana/</guid><description><div id="stage"> </div> <style> canvas { diff --git a/page/2/index.html b/page/2/index.html index 3ad79033..3f74d1ec 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -1,4 +1,4 @@ -<!doctype html><html lang=en-us><head><meta name=generator content="Hugo 0.154.5"><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>lavafroth

NixOS Secureboot Shenanigans


Nix +lavafroth

NixOS Secureboot Shenanigans


Nix NixOS Secureboot sbctl diff --git a/page/3/index.html b/page/3/index.html index dbc77f1d..c45a0eab 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -1,4 +1,4 @@ -lavafroth

Modeling More Realistic Keybinds With Modifiers


EBNF +lavafroth

Modeling More Realistic Keybinds With Modifiers


EBNF Google Summer of Code Rust SWHKD diff --git a/page/4/index.html b/page/4/index.html index d8671830..df5eb52f 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -1,4 +1,4 @@ -lavafroth

Headache


AmateursCTF +lavafroth

Compact XOR


AmateursCTF diff --git a/page/5/index.html b/page/5/index.html index 07c1dbed..166a7f37 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -1,4 +1,4 @@ -lavafroth

Pixelated


Cryptography +lavafroth

Pixelated


Cryptography CTF Image Reconstruction PicoCTF diff --git a/page/6/index.html b/page/6/index.html index 4602a4fd..ac9c9e18 100644 --- a/page/6/index.html +++ b/page/6/index.html @@ -1,4 +1,4 @@ -lavafroth

JAuth


Authentication Bypass +lavafroth

JAuth


Authentication Bypass CTF JWT PicoCTF diff --git a/post/a-tale-of-a-frugal-home-server/index.html b/post/a-tale-of-a-frugal-home-server/index.html index 50f77134..a5a1defa 100644 --- a/post/a-tale-of-a-frugal-home-server/index.html +++ b/post/a-tale-of-a-frugal-home-server/index.html @@ -12,7 +12,7 @@ ribbon cable because we are aiming for a headless setup.

Removing the disp Yet others take this further with dedicated operating systems like CasaOS to install containerized services in a single click.

Sure, these solutions might be easy but they are certainly not simple. Containers introduce the overhead of Linux kernel namespaces. This means accessing files on the host additionally requires creating a mount namespace.

To avoid all of that overhead, I opted for NixOS.

With NixOS, I can define the state of my system in a single configuration file, ensuring that the services -are running close to bare metal without any abstractions. Most of the services require adding something along the lines of

services.myservicename.enable = true;
+are running close to bare metal without any abstractions. Most of the services require adding something along the lines of

services.myservicename.enable = true;
 

to the configuration file and issuing a system rebuild with the nixos-rebuild command.

Storage Management

Initially, I had configured three different routes to transfer files to the server. Of these, only one service is in use today.

Syncthing

I had enabled Syncthing to automatically synchronize media from my phone. While it is a decent solution for a lot of use cases, it does not support partially sharing the contents of a directory. This annoying ‘all or nothing’ nature diff --git a/post/abstracting-structured-patterns-in-concurrent-programming/index.html b/post/abstracting-structured-patterns-in-concurrent-programming/index.html index a4c23db6..92eb34d6 100644 --- a/post/abstracting-structured-patterns-in-concurrent-programming/index.html +++ b/post/abstracting-structured-patterns-in-concurrent-programming/index.html @@ -40,7 +40,7 @@ multiplexer to pass them to the respective functions. However, this route is less ergonomic since the programmer has to define a field in their class or struct whose name is decided by the API.

The second route takes advantage of Rust’s type system. In Rust, enums can have different structures inside each variant. This allows -defining a macro that matches an enum variant to its respective function.

Here’s how the use of the API might look like:

let m = multiplexer!(MyEnum {
+defining a macro that matches an enum variant to its respective function.

Here’s how the use of the API might look like:

let m = multiplexer!(MyEnum {
     Left(StructA) => function_a,
     Right(StructB) => function_b,
 });
@@ -49,7 +49,7 @@ a channel.
  • Spawns these wrapped functions in their own coroutines, joini them.
  • Optionally take a context as an input for cancellation of itself and coroutines spawned by it.
  • Uses a match block to dispatch the inner value of a struct to the respective channel.
  • The macro would use the quote! macro to generate the match arm for each -variant like the following:

    quote!(
    +variant like the following:

    quote!(
         match #enum_ident {
             // loop over the arms in the macro call
             #variant(#inner) => #inner_chan_tx.send(#inner);
    @@ -67,7 +67,7 @@ is to return an object with an enum attribute inside it or having a 2 element tu
     the enum variant and the struct akin to function return types in Go.

    Another way is to wrap the structures inside enum variants and explicitly tell the API which variant implies further processing and which one implies a finalized output.

    I advocate for defining a trait on an enum that wraps the output type of the function which allows the API to call the associated method on the object to -know whether it is a input or a final output.

    Consider the following trait:

    pub trait PartiallyOpenLoop {
    +know whether it is a input or a final output.

    Consider the following trait:

    pub trait PartiallyOpenLoop {
       fn is_final(&self) -> bool;
     }
     

    This allows the API to call the is_final method of the object after each pass diff --git a/post/algebraic-python-enums/index.html b/post/algebraic-python-enums/index.html index 4b4369cb..f31472ac 100644 --- a/post/algebraic-python-enums/index.html +++ b/post/algebraic-python-enums/index.html @@ -17,7 +17,7 @@ that can encapsulate various other data types.

    Although Python has the ans structural match expressions in recent versions, most tutorials will suggest Union types as the equivalent to Rust’s enums.

    I highly encourage you to try out the code snippets and follow along with this article. -Use the collapse explanation button to copy multiple code blocks in one go.

    Naive draft

    # glass_enum.py
    +Use the collapse explanation button to copy multiple code blocks in one go.

    Naive draft

    # glass_enum.py
     
     from dataclasses import dataclass
     
    @@ -30,15 +30,15 @@ Use the collapse explanation button to copy multiple code blocks in one go.

    <
    drink: str Glass = Empty | Full -

    This allows us to define functions that ingest the Glass datatype.

    def report_drink(glass: Glass) -> str:
    +

    This allows us to define functions that ingest the Glass datatype.

    def report_drink(glass: Glass) -> str:
       match glass:
         case Empty():
           return "Whoops, looks like you've finished your drink!"
         case Full(drink):
           return f"Ah a {drink}, what a fine taste!"
    -

    For example

    dr_pepper = Full('Dr. Pepper')
    +

    For example

    dr_pepper = Full('Dr. Pepper')
     print(report_drink(dr_pepper))
    -
    # glass_enum.py
    +
    # glass_enum.py
     
     from dataclasses import dataclass
     
    @@ -62,7 +62,7 @@ Use the collapse explanation button to copy multiple code blocks in one go.

    <
    dr_pepper = Full('Dr. Pepper') print(report_drink(dr_pepper))

    Will output

    Ah a Dr. Pepper, what a fine taste!
    -

    Pitfalls

    No direct variant access

    What if we had another Union with same variant names in the same file?

    @dataclass
    +

    Pitfalls

    No direct variant access

    What if we had another Union with same variant names in the same file?

    @dataclass
     class Full:
       gold: int
       gems: int
    @@ -73,8 +73,8 @@ Use the collapse explanation button to copy multiple code blocks in one go.

    <
    Inventory = Full | Empty player_inventory = Full(500, 50) -

    Now we try to instantiate a Glass Full of lemonade.

    lemonade = Full("lemonade")
    -
    @dataclass
    +

    Now we try to instantiate a Glass Full of lemonade.

    lemonade = Full("lemonade")
    +
    @dataclass
     class Full:
       gold: int
       gems: int
    @@ -91,25 +91,25 @@ union type Inventory.

    Traceback (most rece
       File "<python-input-11>", line 1, in <module>
         lemonade = Full("lemonade")
     TypeError: Full.__init__() missing 1 required positional argument: 'gems'
    -

    We can’t instantiate variants as members of the Glass namespace. The following code does not work.

    dr_pepper = Glass.Full("Dr. Pepper")
    +

    We can’t instantiate variants as members of the Glass namespace. The following code does not work.

    dr_pepper = Glass.Full("Dr. Pepper")
     

    This can be partially solved by keeping just the Glass type inside a module. Here we have saved the file as glass_enum.py. From a different module we can -access the variants as glass_enum.Empty and glass_enum.Full.

    # main.py
    +access the variants as glass_enum.Empty and glass_enum.Full.

    # main.py
     import glass_enum
     
     fanta = glass_enum.Full('Fanta')
     empty = glass_enum.Empty()
    -

    Now any function outside the module has to ingest a rather confusing argument of type glass_enum.Glass.

    def refill(glass: glass_enum.Glass) -> glass_enum.Glass:
    +

    Now any function outside the module has to ingest a rather confusing argument of type glass_enum.Glass.

    def refill(glass: glass_enum.Glass) -> glass_enum.Glass:
       # ...
       return glass
     

    Since module namespacing only causes more confusion, we will discard this idea.

    No methods on the enum itself

    Python also disallows methods from being defined on Union types. In the case of our concrete example, we can’t add methods to the Glass type.

    The following code uses a hypothetical is_empty() method on the Glass union -type which is not allowed. Hence the code won’t run.

    def refill(glass: Glass) -> Glass:
    +type which is not allowed. Hence the code won’t run.

    def refill(glass: Glass) -> Glass:
       if glass.is_empty(): # can't implement on type `Glass` directly
         return Full('water')
       return glass
     

    To define a method like is_empty(), it must be implemented on both the classes -Full and Empty. This gets tedious for 3 or more variants.

    Python is a sneaky language

    Last week I discovered that Python allows creating nested classes to keep things organized.

    from dataclasses import dataclass
    +Full and Empty. This gets tedious for 3 or more variants.

    Python is a sneaky language

    Last week I discovered that Python allows creating nested classes to keep things organized.

    from dataclasses import dataclass
     
     class Glass:
       @dataclass
    @@ -119,8 +119,8 @@ type which is not allowed. Hence the code won’t run.

    @dataclass class Full: drink: str -

    Python will happily run the above code and we can access the “variants” under the Glass namespace.

    lemonade = Glass.Full('lemonade')
    -

    If only we could register the variants as the Glass type itself and inherit all its methods.

    Redecorate

    We can define a decorator that takes all of the nested dataclasses and makes them inherit the outer class.

    def AlgebraicEnum(cls):
    +

    Python will happily run the above code and we can access the “variants” under the Glass namespace.

    lemonade = Glass.Full('lemonade')
    +

    If only we could register the variants as the Glass type itself and inherit all its methods.

    Redecorate

    We can define a decorator that takes all of the nested dataclasses and makes them inherit the outer class.

    def AlgebraicEnum(cls):
         for name, nested in cls.__dict__.items():
             if isinstance(nested, type):
                 setattr(cls, name, type(name, (cls, nested), {}))
    @@ -130,7 +130,7 @@ type which is not allowed. Hence the code won’t run.

    and any object of a nested class isinstance of the outer class.

    That’s all there is to the magic! Simply adding this decorator above the previous class declaration make variants like Glass.Empty and Glass.Full -inherit Glass.

    from dataclasses import dataclass
    +inherit Glass.

    from dataclasses import dataclass
     
     @AlgebraicEnum
     class Glass:
    @@ -155,12 +155,12 @@ inherit Glass.

            return True
         return False
     

    Note how the report_drink method accepts a self of type Glass and the match arms -compare it with Glass.Empty and Glass.Full.

    The following code runs just fine.

    diet_coke = Glass.Full('diet coke')
    +compare it with Glass.Empty and Glass.Full.

    The following code runs just fine.

    diet_coke = Glass.Full('diet coke')
     empty = Glass.Empty()
     
     print(diet_coke.report_drink())
     print(empty.is_empty())
    -
    from dataclasses import dataclass
    +
    from dataclasses import dataclass
     
     
     def AlgebraicEnum(cls):
    diff --git a/post/android-phone-for-webcam-nixos/index.html b/post/android-phone-for-webcam-nixos/index.html
    index 6cd11818..03031229 100644
    --- a/post/android-phone-for-webcam-nixos/index.html
    +++ b/post/android-phone-for-webcam-nixos/index.html
    @@ -4,9 +4,9 @@ While my PC did have a decent microphone, the built-in camera has been damaged t
     No, it’s not a close-up of the moon, it’s the refraction caused by the scuffs to the lens plus other sciency stuff I’m not qualified enough to explain to you.'>

    Using an Android Phone as a webcam in NixOS

    I recently had to attend an online meeting for a software development event. While my PC did have a decent microphone, the built-in camera has been damaged to the extent that the best it can capture is this:

    A blurry image taken from my scuffed camera

    No, it’s not a close-up of the moon, it’s the refraction caused by the scuffs to the lens plus other sciency stuff I’m not qualified enough to explain to you.

    I was aware that one can use ADB to use an Android phone’s camera as a makeshift webcam. Since I would need this ability for any future meetings as well, it was worth having the functionality packaged into a one click tool.

    Enter NixOS. I have praised NixOS before and I’ll do it again because of the sheer ease with which it allows me to create desktop entries for small scripts. This is going to be relevant later but I’m going to assume that you’re running NixOS with home-manager enabled if you’re following along. First we have to enable developer mode and USB debugging on our phone.

    To interact with our phone, we will need adb and scrcpy as dependencies. -If you have enabled flakes run the following:

    nix shell nixpkgs#scrcpy nixpkgs#android-tools
    -

    If you don’t have flakes enabled, run the following:

    nix-shell -p scrcpy android-tools
    -

    Next, we connect our phone to our PC with a cable (or through ADB TCP/IP) and list all the cameras by running the following:

    scrcpy --list-cameras
    +If you have enabled flakes run the following:

    nix shell nixpkgs#scrcpy nixpkgs#android-tools
    +

    If you don’t have flakes enabled, run the following:

    nix-shell -p scrcpy android-tools
    +

    Next, we connect our phone to our PC with a cable (or through ADB TCP/IP) and list all the cameras by running the following:

    scrcpy --list-cameras
     

    You must allow any prompt on your phone requesting access to it from the computer, after which, you should see an output like the following:

    [server] INFO: List of cameras:
         --camera-id=0    (back, 4608x3456, fps=[10, 15, 24, 30])
    @@ -17,7 +17,7 @@ computer, after which, you should see an output like the following:

    Note down the number associated with the camera you want to use. Alternatively, you can also note whether the camera you wish to use is the front or the back camera.

    Add the following to your configuration.nix:

    boot = {
    +

    Note down the number associated with the camera you want to use. Alternatively, you can also note whether the camera you wish to use is the front or the back camera.

    Add the following to your configuration.nix:

    boot = {
       kernelModules = [ "v4l2loopback" ];
       extraModulePackages = [ pkgs.linuxPackages.v4l2loopback ];
       extraModprobeConfig = ''
    @@ -26,7 +26,7 @@ computer, after which, you should see an output like the following:

    };
     

    This enables the v4l2loopback kernel module to create a dummy video interface which allows us to route any video to this virtual camera. In the extra options for this module, we have to add exclusive_caps=1 to make sure that the virtual camera exclusively announces itself as an output device. -This is important for compatibility with services like Zoom and Google Meet.

    In the home-manager config for your user add the following:

    home.xdg.desktopEntries.andcam = {
    +This is important for compatibility with services like Zoom and Google Meet.

    In the home-manager config for your user add the following:

    home.xdg.desktopEntries.andcam = {
       name = "Android Virtual Camera";
       exec = "${pkgs.writeScript "andcam" ''
         ${pkgs.android-tools}/bin/adb start-server
    @@ -37,7 +37,7 @@ This is important for compatibility with services like Zoom and Google Meet.

    the script in the exec field.

    Here’s a breakdown of the script:

    • The first line starts an ADB server required for scrcpy to pick up our device.
    • The second line runs scrcpy to pass the phone’s camera to the dummy virtual camera spawned by the v4l2loopback kernel module.

    We can use a named camera with the --camera-facing flag as I did here for the back camera using --camera-facing=back. If you noted a camera ID eariler, you can replace the --camera-facing=back with --camera-id= followed by the -identifying number.

    For example, if you were to use the camera with the ID 0, you would add the following instead:

    home.xdg.desktopEntries.andcam = {
    +identifying number.

    For example, if you were to use the camera with the ID 0, you would add the following instead:

    home.xdg.desktopEntries.andcam = {
       name = "Android Virtual Camera";
       exec = "${pkgs.writeScript "andcam" ''
         ${pkgs.android-tools}/bin/adb start-server
    diff --git a/post/changing-recents-provider-on-eos/index.html b/post/changing-recents-provider-on-eos/index.html
    index ccea5229..6a8a3088 100644
    --- a/post/changing-recents-provider-on-eos/index.html
    +++ b/post/changing-recents-provider-on-eos/index.html
    @@ -7,16 +7,16 @@ Icons can’t be rearranged
     Pull down from top opens search instead of notification shade
     Consumes a lot of RAM
     
    -I started using Lawnchair as my default launcher but this did not change the recents provider (quickstep) from BlissLauncher to Lawnchair.'>

    Guide: Changing Recents Provider on /e/OS

    Overview

    Over the past month I have been daily driving my new phone, the Nothing CMF 1 flashed with /e/OS after I unlocked its bootloader. It’s a very pleasant experience except for the default Bliss launcher (home app).

    Reasons I do not prefer it:

    • iOS like feel
    • Icons can’t be rearranged
    • Pull down from top opens search instead of notification shade
    • Consumes a lot of RAM

    I started using Lawnchair as my default launcher but this did not change the recents provider (quickstep) from BlissLauncher to Lawnchair.

    Two problems with this:

    • Bliss has to keep running in the background to act as the recents provider.
    • When the screen is horizontally oriented and gesture navigation is used to go to the home screen, Bliss keeps crashing.

    Note for Bliss devs: This behavior only occurs when using a different launcher as default.

    I read a post on the forums about replacing Bliss completely but there was no step by step guide. Here, I document the steps I took to get Lawnchair as my default launcher and recents provider.

    Guide

    This guide assumes you have a basic understanding of adb and fastboot. Ensure that USB debugging is enabled and your device is visible in the output of adb devices.

    Apatch

    Extracting boot.img

    Assuming you have the image used to flash /e/OS on your phone, extract the boot.img from it. This can be done with the 7zip command

    7z e IMG-e-3.0.4-a14-20250708507308-official-tetris.zip -o. boot.img
    +I started using Lawnchair as my default launcher but this did not change the recents provider (quickstep) from BlissLauncher to Lawnchair.'>

    Guide: Changing Recents Provider on /e/OS

    Overview

    Over the past month I have been daily driving my new phone, the Nothing CMF 1 flashed with /e/OS after I unlocked its bootloader. It’s a very pleasant experience except for the default Bliss launcher (home app).

    Reasons I do not prefer it:

    • iOS like feel
    • Icons can’t be rearranged
    • Pull down from top opens search instead of notification shade
    • Consumes a lot of RAM

    I started using Lawnchair as my default launcher but this did not change the recents provider (quickstep) from BlissLauncher to Lawnchair.

    Two problems with this:

    • Bliss has to keep running in the background to act as the recents provider.
    • When the screen is horizontally oriented and gesture navigation is used to go to the home screen, Bliss keeps crashing.

    Note for Bliss devs: This behavior only occurs when using a different launcher as default.

    I read a post on the forums about replacing Bliss completely but there was no step by step guide. Here, I document the steps I took to get Lawnchair as my default launcher and recents provider.

    Guide

    This guide assumes you have a basic understanding of adb and fastboot. Ensure that USB debugging is enabled and your device is visible in the output of adb devices.

    Apatch

    Extracting boot.img

    Assuming you have the image used to flash /e/OS on your phone, extract the boot.img from it. This can be done with the 7zip command

    7z e IMG-e-3.0.4-a14-20250708507308-official-tetris.zip -o. boot.img
     

    Here IMG-e-3.0.4-a14-20250708507308-official-tetris.zip is the image for my phone. Replace this with the appropriate image name.This extract the boot.img to the current directory.

    Push this image to your device with

    adb push boot.img /storage/emulated/0/Download/
    -

    NOTE: You could also push the image over MTP (file transfer) but it may corrupt files and is hence discouraged.

    Installing APatch

    These instructions are also available in the APatch Docs.

    1. Download the latest version of APatch Manager from GitHub.
    2. Click on the patch button at the top right corner, and click Select a boot image to patch.
    3. Select the boot.img we pushed to the Download directory.
    4. Set a SuperKey at “SuperKey” card. The SuperKey should be 8-63 characters long and include numbers and letters, but no special characters. It will be used later to unlock root privileges.
    5. Click on “Start” and wait for a minute. After the patch is successful, the patched boot.img path will be displayed. For example: /storage/emulated/0/Download/apatch_version_version_randomletter.img.

    Flashing

    1. Pull the patched boot.img with the command. Replace apatch_version_version_randomletter.img with the appropriate filename.
    adb pull /storage/emulated/0/Download/apatch_version_version_randomletter.img
    -
    1. Reboot into fastboot mode.
    adb reboot bootloader
    -
    1. Flash the patched boot.img.
    fastboot flash boot apatch_version_version_randomletter.img
    -
    1. Reboot the device
    fastboot reboot
    -

    QuickSwitch

    Installation

    1. Download the latest zip of QuickSwitch-fork to your phone from GitHub.
    2. Open the APatch app.
    3. Authenticate with the SuperKey you had set earlier.
    4. Enable APModule.
    5. Under the APModule tab click on the install button in the bottom right corner.
    6. Select the QuickSwitch-fork.zip you downloaded.
    7. Reboot your device for the changes to take effect.

    Switching the Recents Provider

    In this step I am assuming you are using a launcher that supports QuickStep, as in, it can act as a recents provider.

    I have tested this on Lawnchair 14.3 beta. DO NOT USE Lawnchair 15.1 beta, it’s QuickStep provider is a bit buggy.

    Get a shell on the phone with

    adb shell
    -

    Inside adb shell

    1. Change the recents provider. Replace app.lawnchair with your launcher’s package name.
    su -c /data/adb/modules/quickswitch/quickswitch --ch=app.lawnchair
    -
    1. (Optional) Remove Bliss launcher.
    for pkg in $(pm list packages | grep bliss | cut -d: -f 2); do pm uninstall --user 0 $pkg; done
    -
    1. Reboot the device.
    reboot
    +

    NOTE: You could also push the image over MTP (file transfer) but it may corrupt files and is hence discouraged.

    Installing APatch

    These instructions are also available in the APatch Docs.

    1. Download the latest version of APatch Manager from GitHub.
    2. Click on the patch button at the top right corner, and click Select a boot image to patch.
    3. Select the boot.img we pushed to the Download directory.
    4. Set a SuperKey at “SuperKey” card. The SuperKey should be 8-63 characters long and include numbers and letters, but no special characters. It will be used later to unlock root privileges.
    5. Click on “Start” and wait for a minute. After the patch is successful, the patched boot.img path will be displayed. For example: /storage/emulated/0/Download/apatch_version_version_randomletter.img.

    Flashing

    1. Pull the patched boot.img with the command. Replace apatch_version_version_randomletter.img with the appropriate filename.
    adb pull /storage/emulated/0/Download/apatch_version_version_randomletter.img
    +
    1. Reboot into fastboot mode.
    adb reboot bootloader
    +
    1. Flash the patched boot.img.
    fastboot flash boot apatch_version_version_randomletter.img
    +
    1. Reboot the device
    fastboot reboot
    +

    QuickSwitch

    Installation

    1. Download the latest zip of QuickSwitch-fork to your phone from GitHub.
    2. Open the APatch app.
    3. Authenticate with the SuperKey you had set earlier.
    4. Enable APModule.
    5. Under the APModule tab click on the install button in the bottom right corner.
    6. Select the QuickSwitch-fork.zip you downloaded.
    7. Reboot your device for the changes to take effect.

    Switching the Recents Provider

    In this step I am assuming you are using a launcher that supports QuickStep, as in, it can act as a recents provider.

    I have tested this on Lawnchair 14.3 beta. DO NOT USE Lawnchair 15.1 beta, it’s QuickStep provider is a bit buggy.

    Get a shell on the phone with

    adb shell
    +

    Inside adb shell

    1. Change the recents provider. Replace app.lawnchair with your launcher’s package name.
    su -c /data/adb/modules/quickswitch/quickswitch --ch=app.lawnchair
    +
    1. (Optional) Remove Bliss launcher.
    for pkg in $(pm list packages | grep bliss | cut -d: -f 2); do pm uninstall --user 0 $pkg; done
    +
    1. Reboot the device.
    reboot
     

    Profit!

    That’s all there is to it. Now you should see you preferred app as the recents provider. Goodbye!