<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Tomy Hsieh on Tomy's Blog</title><link>https://blog.tomy.me/en/</link><description>Tomy Hsieh's marvelous blog</description><language>en</language><copyright>Copyright © 2022 Tomy Hsieh.</copyright><pubDate>Thu, 04 Aug 2022 06:25:42 +0800</pubDate><lastBuildDate>Thu, 25 Jun 2026 03:16:29 +0000</lastBuildDate><category>🍫 Life</category><category>🤖 Development</category><generator>Hugo -- gohugo.io (customized by tomy0000000)</generator><docs>https://www.rssboard.org/rss-specification</docs><ttl>60</ttl><atom:link href="https://blog.tomy.me/en/index.xml" rel="self" type="application/rss+xml"/><item><title>How I built my first side project: Tubee, in 2017</title><link>https://blog.tomy.me/en/posts/tubee/</link><description>no AI, just SSH, Stack Overflow, and pure stubbornness</description><content:encoded><![CDATA[<p>This is the story of my first side project—a journey of discovery, frustration, and ultimately, some valuable lessons learned.</p>
<h2 id="what" class="relative group">What? </h2><p>As a CS college student, watching YouTube videos was part of my daily routine. This was around the time YouTube began transitioning from a simple video hosting platform to a social network. Naturally, people became skeptical about the recommendation algorithms. That&rsquo;s when the basic idea formed: a YouTube feed that I could actually trust.</p>
<p>I also wanted to answer a specific question:</p>
<blockquote>
<p>Does YouTube strategically withhold notifications for new videos from subscribed channels to prevent attention fatigue?</p>
</blockquote>
<p>More broadly, I aimed to develop a solution that would not only answer this question but enhance the entire experience of discovering and consuming YouTube content.</p>
<p>The result was <a href="https://github.com/tomy0000000/tubee" target="_blank" rel="noreferrer">Tubee<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>: a comprehensive YouTube content tracking platform.</p>
<p><a href="tubee-architecture.png">







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/tubee/tubee-architecture.svg"
      class="mx-auto my-0 rounded-md"
      alt="Tubee Architecture"
      loading="lazy" decoding="async"
    />
  </picture>

<figcaption class="text-center">Tubee Architecture (Click to view in new tab)</figcaption>
</figure>
</a></p>
<p>Following the blue path, users subscribe to YouTube channels on Tubee just like they would on YouTube (of course, subscription import exists). The background celery worker will consolidate required channel metadatas in the background (The green path). Finally, users can then define &ldquo;Actions&rdquo; that execute automatically when new videos are uploaded, which are denoted by the red path. There are three types of actions:</p>
<ul>
<li>Add to playlist on YouTube</li>
<li>Push notifications via:
<ul>
<li>Pushover</li>
<li>LINE Notify</li>
</ul>
</li>
<li>Download video to Dropbox</li>
</ul>
<p>Actions form a generalized framework that can easily integrate with more complex use cases. You can also bind actions to tags attached to subscriptions. For example, I could create an &ldquo;Add to nonfiction playlist&rdquo; action tagged as &ldquo;Non-fiction&rdquo; and apply it to channels like <a href="https://www.youtube.com/halfasinteresting" target="_blank" rel="noreferrer">Half as Interesting<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> and <a href="https://www.youtube.com/@3blue1brown" target="_blank" rel="noreferrer">3Blue1Brown<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, eliminating the need for individual configuration.</p>
<p>If you&rsquo;re familiar with IFTTT or n8n, think of this as similar automation but at scale. Most importantly, everything works through webhooks rather than polling, ensuring zero-delay triggers. Notifications often arrived as fast as (or faster than) the official YouTube mobile app.</p>
<h2 id="how-it-all-begins" class="relative group">How it all begins </h2><p>It was 2017, and I was still a CS sophomore. After spending my freshman year writing &ldquo;basic&rdquo; CS homework and dozens of tiny one-off tools, I wanted to move on to actually &ldquo;building&rdquo; something—a project challenging enough to push my skills and useful enough that I&rsquo;d want to use it myself.</p>
<p>A friend mentioned that Google Cloud was offering free compute engine credits, and GitHub Student provided additional perks like free domains and developer tools. That covered the infrastructure needs.</p>
<h2 id="off-we-go" class="relative group">Off We Go </h2><p>From my freshman web programming course, I had basic knowledge of HTTP concepts, HTML, CSS, JavaScript, PHP, and jQuery. I started with a simple &ldquo;hello world&rdquo; website and deployed it to a remote server. Since I preferred Python over PHP, I found <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-apache-mysql-and-python-lamp-server-without-frameworks-on-ubuntu-14-04#step-4-installing-apache-2" target="_blank" rel="noreferrer">this DigitalOcean tutorial<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> that helped me set up the stack I wanted.</p>
<p>My development workflow was&hellip; unconventional:</p>
<ul>
<li>Edit Python source code using <a href="https://panic.com/transmit/" target="_blank" rel="noreferrer">Transmit<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> (an SFTP client) and <a href="https://www.sublimetext.com/" target="_blank" rel="noreferrer">Sublime Text<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></li>
<li>Save changes, which Transmit automatically synced to the server</li>
<li>SSH into the server in another terminal</li>
<li>Run <code>sudo systemctl restart apache2</code> to test changes</li>
<li>Backup? Just zip the entire project as <code>tuber-20180315.zip</code> and call it a day</li>
</ul>
<p>And Boooom! I was now a &ldquo;Full-stack Python Web Application engineer&rdquo;&hellip; or so I thought.</p>
<p>Through divide-and-conquer, I gradually worked toward my goal, albeit slowly. Here are some key milestones I achieved:</p>
<ul>
<li>Learning how to use git (guess why 🙃)</li>
<li>Center align a <code>&lt;div&gt;</code> (by <code>float</code>ing it)</li>
<li>Understand &ldquo;DevOps&rdquo;, and set up <a href="https://www.travis-ci.com/" target="_blank" rel="noreferrer">Travis CI<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
<li>Receive YouTube notification by facilitating the <a href="https://pubsubhubbub.appspot.com/" target="_blank" rel="noreferrer">PubSubHubbub<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> protocol. <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li>Witnessing the sunrise shine on my screen</li>
<li>Authenticate with YouTube Data API using OAuth 2.0 (took me like, a week?)</li>
<li>Containerization with Docker and Docker compose, surprisingly not difficult</li>
</ul>
<p>It was countless nights of trial and error, programming and debugging, and an awful lot of bad technical decisions—like changing the web server from Apache to nginx without even a clear reason.</p>
<p>More often than not, I found myself starting somewhere trying to achieve one thing, but ending up diving deep into something I never anticipated—for instance, reading through all <code>ssl_ciphers</code> available in nginx.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<h2 id="expansion" class="relative group">Expansion </h2><p>After fulfilling the initial requirements, I moved on to experimenting with other engineering aspects of software projects after reading more and more engineering blogs.</p>
<p>Even though I didn&rsquo;t implement all of them perfectly, here are some of my key takeaways from hindsight:</p>
<ul>
<li>Figure out at least ~85% of the requirements first, before making architectural decisions. Changing requirements is one of the biggest risks a software project may encounter, potentially making the end product beyond repair.</li>
<li>Avoiding refactoring is nearly impossible. Even predicting what might change is difficult. However, breaking down your functions and components and following <abbr title="Single-responsibility principle">SRP</abbr> will make your life <em>a lot</em> easier.</li>
<li>Documentation skills take time to develop, just like writing a good resume. Some of the best documentation I&rsquo;ve read includes <a href="https://fastapi.tiangolo.com/" target="_blank" rel="noreferrer">FastAPI<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, <a href="https://docusaurus.io/docs" target="_blank" rel="noreferrer">Docusaurus<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, and <a href="https://resend.com/docs/api-reference" target="_blank" rel="noreferrer">Resend<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</li>
</ul>
<p>On the other hand, there were also more technical advancements, developed from natural requirements, such as:</p>
<ul>
<li>Logging, and I mean &ldquo;structured logging&rdquo;, that allows searching and matching.</li>
<li>User authentication, and learning basic security principles to protect user information.</li>
<li>Staging environment, so I don&rsquo;t always mess up production during deployment.</li>
</ul>
<p>Many years later, I learned that these are all part of the <a href="https://12factor.net/" target="_blank" rel="noreferrer">Twelve-factor app<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> methodology, and I somehow achieved many of these principles without knowing they existed ¯\_(ツ)_/¯</p>
<p>At this point, I felt like my hard-earned skills were well developed and practically useful for building production-grade applications. Why should I bother with theoretical CS knowledge that barely exists in the real world&hellip; right?</p>
<h2 id="a-case-deep-dive" class="relative group">A case deep dive </h2><p>Circling back to the existing architecture, upon startup, Tubee would loop through all subscribed channels and sign up for notifications for new videos. Since subscriptions only last for five days, I had to re-subscribe every five days. That&rsquo;s where I brought in <a href="https://github.com/agronholm/apscheduler" target="_blank" rel="noreferrer">APScheduler<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, a crontab-like background worker module which does exactly that.</p>
<p>I plugged it in, made it work locally, and shipped the feature to production, crossing my fingers, only to discover a peculiar issue: The logs showed that every job I set up was running multiple times when invoked, instead of just once.</p>
<p>Even though this wasn&rsquo;t a big issue for my use case (re-subscribing five times wouldn&rsquo;t hurt), I took this as a challenge.</p>
<p>At first glance, I thought I must have messed something up. Maybe the logger was misconfigured? Was I logging multiple times? But wait! I had fully tested this in development, and everything went smoothly, which meant something must be wrong in production. It turns out that Gunicorn<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, the WSGI server I was using, follows a pre-fork model. This means that even though there were barely any requests, it spawns multiple workers to handle incoming requests. APScheduler gets initialized in each worker and executes jobs multiple times.</p>
<p>This is where the journey became rough. I started by looking for a way to define &ldquo;custom initialization&rdquo; that could set up the master process with APScheduler and skip it for worker processes, but this failed. I then looked for methods that would allow workers to communicate with each other (inter-process communication) and figured out a way to initialize only one APScheduler instance among them—the notorious &ldquo;leader election&rdquo; problem. This never really worked.</p>
<p>It wasn&rsquo;t until I finally gave up after (literally) months of trying that I decided I should seek help from the community for the first time. I posted <a href="https://stackoverflow.com/questions/62083481/scheduling-interval-cron-job-in-a-multi-worker-environment" target="_blank" rel="noreferrer">this Stack Overflow question<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> which drew little to no attention, because I was terrible at phrasing and clearly articulating my question.</p>
<p>But guess what—I was fortunately helped by two pivotal people. The first was Alex, the author of APScheduler, who was so kind and helpful that our discussion became almost like a one-on-one project consultancy session, answering the long-standing question that had stumped me for so long. A few days later, Miguel, author of &ldquo;Flask Web Development,&rdquo; also pointed me in the same direction.</p>
<p>It turns out that if I had grasped the basics of distributed systems, I would have immediately known that supporting modules like APScheduler should have been their own component from the start, and the nightmare I encountered wouldn&rsquo;t have been an issue at all.</p>
<p>Lessons learned:</p>
<ul>
<li>Seek help early. In my experience, a problem that persists over two to three long breaks is typically a good sign that you should seek help.</li>
<li>Foundational knowledge is equally important and should be treated as invisible technical debt. This is especially true if your goal is to become a true full-stack developer or engineer.</li>
</ul>
<h2 id="sunset" class="relative group">Sunset </h2><p>At peak, I tracked ~250 YouTube channel and process 600+ new videos per day, but that&rsquo;s the furthest it ever gets.</p>
<p>A few months later, as I shifted my focus towards research projects at the NLP Lab, I gradually lost time to put much effort into Tubee. Even more, I also didn&rsquo;t have time to watch all the YouTube videos I was interested in at all.</p>
<p>Having less spare time was merely a fact that led to the end of the project. The underlying real issue, however, was that the foundation of this requirement wasn&rsquo;t concrete enough. The problem statement of</p>
<blockquote>
<p>Solving the algorithm bias by re-introducing the chronologically-ordered video feed</p>
</blockquote>
<p>&hellip;never held up, and here&rsquo;s why it failed:</p>
<ul>
<li>It failed to draw my attention. It may be a feature, not a bug in some sense, but this is just how it works naturally.</li>
<li>It doesn&rsquo;t scale. People have limited time; scrolling through all subscribed uploads makes no sense.</li>
<li>It doesn&rsquo;t encourage exploration. Why try new channels when you can barely finish your existing watch list?</li>
</ul>
<p>Ultimately, it failed to solve a real-world problem for the end user (ironically, myself).</p>
<p>As the requirements faded away, so did the development. At the time of writing, I&rsquo;m officially declaring the death of my first side project, but deep down I know it has been dead for a very long time.</p>
<p>Circling back to the original question:</p>
<blockquote>
<p>Does YouTube strategically mute notifications, to prevent causing attention fatigue?</p>
</blockquote>
<p>Well, not much evidence was found. There are cases where content creators upload videos and later take them down within a short time. People who haven&rsquo;t been notified may not receive notifications, and some might have their notifications rescinded. But &ldquo;deliberately&rdquo; silencing notifications? Not that I could find.</p>
<h2 id="reflections" class="relative group">Reflections </h2><p>Now that it&rsquo;s gone (for what it&rsquo;s worth), I&rsquo;d like to reflect on what I probably could (or should) have done differently:</p>
<ul>
<li><strong>Share it / Collaborate</strong>: Even though I never intended to make the service public, I don&rsquo;t think I ever demoed it to anyone—not my family, not my friends, not anyone.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> This significantly reduced my chances of receiving feedback, both technical and product-wise.</li>
<li><strong>Move faster, Break more things</strong>: I think I read this early in the project and always kept it in mind, but I felt like I never really understood or achieved this. Perhaps what really drives this is having collaborators working on the same project.</li>
<li><strong>Wear different hats</strong>: Being the sole contributor to the entire project meant I had to handle roadmapping, design, implementation, testing, and feedback for every feature. The difficult part wasn&rsquo;t doing all of these, but separating myself from working on all of them at once. It wasn&rsquo;t until recently that I learned I should wear different hats and act in different roles for specific periods of time.</li>
</ul>
<p>So yep, this post is my first attempt at sharing my project and experience with the public.</p>
<h2 id="last-few-words" class="relative group">Last few words </h2><p>Nevertheless, I gained a few crucial traits from this experience.</p>
<p>It&rsquo;s only been a couple of years since AI coding became the new norm, and I&rsquo;ve almost forgotten what it&rsquo;s like to debug without AI assistance—working through problems with Google searches, reading Stack Overflow, and most importantly, paging through documentation.</p>
<p>Although not as often as I used to, I still enjoy reading excellent documentation. There are wonderful tutorials out there that go way beyond just explaining how things work. They&rsquo;re like reading a story that recounts the past, explains the why, and reveals how something came to be. Building knowledge and understanding around core concepts develops the ability to reason about behavior and usage, and perhaps how things will evolve. Hence: &ldquo;A great engineer knows their tools.&rdquo;</p>
<p>It&rsquo;s also the mindset of boundaryless exploration that I embraced, which accumulated considerable invisible technical capital that comes in handy for new projects and even at work. I&rsquo;m confident enough to predict:</p>
<blockquote>
<p>Talent that can <abbr title="Read The Fucking Manual">RTFM</abbr> and work through Stack Overflow and GitHub issues will become the next scarce resource in the tech job market over the coming decades.</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Remember, GitHub Actions wasn&rsquo;t a thing until 2019.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I&rsquo;m surprised that this is still <a href="https://developers.google.com/youtube/v3/guides/push_notifications" target="_blank" rel="noreferrer">the way<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> a web service subscribes to YouTube channel, in 2025.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Don&rsquo;t try this at home, especially if you&rsquo;re allergic to math. Just use <a href="https://www.digitalocean.com/community/tools/nginx" target="_blank" rel="noreferrer">NGINXConfig<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> and call it a day.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Notice I changed my WSGI server here? Again, another example of changing the tech stack for absolutely no reason. 🙃&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Funny to say, Tubee had a built-in, bullet-proof user authentication system with just two registered users: me and someone at Google responsible for auditing YouTube Data API usage.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded><category>🤖 Development</category><guid isPermaLink="false">tubee</guid><pubDate>Sun, 10 Aug 2025 13:26:30 -0700</pubDate></item><item><title>PyCon US 2024</title><link>https://blog.tomy.me/en/posts/pycon-us-2024/</link><description>My First PyCon in US 🫶🏻</description><content:encoded><![CDATA[<p>After PyCon TW and DE, I&rsquo;m finally landing at the largest PyCon on Earth: PyCon US 🇺🇸.</p>
<p>What&rsquo;s amazing is that this year, PyCon US is taking place right here in Pittsburgh, the steel city where I&rsquo;m currently pursuing my Master&rsquo;s degree. This means I can attend the event without the hassle of airfare and hotel expenses.</p>
<p>So let&rsquo;s get straight into my findings 🚀</p>
<h2 id="-python-313" class="relative group">🐍 Python 3.13 </h2><p>is coming later this year!</p>
<h3 id="-type-annotation" class="relative group">🔖 Type Annotation </h3><p>Improving type annotation syntax has been the single most important change, a trend that has been ongoing for at least 10 years in Python. Its impact is so significant that there is even a dedicated Typing summit as well as a typing committee to address this challenge.</p>
<p>4 PEPs have been accepted and implemented this year. At first glance, they may seem hard to understand, but when used wisely, they will help your editors and linters better understand what you&rsquo;re trying to achieve, thus giving you better auto-complete suggestions.</p>
<h3 id="-the-removal-of-gil" class="relative group">🔓 The Removal of GIL </h3><p>GIL is one of (if not most) the critical components to prevent race conditions in Python by only allowing one thread to execute simultaneously, which is beneficial for most cases. However, for tasks that require intensive computation, which could&rsquo;ve been partitioned and parallelized execution, this became a significant drawback as GIL only facilitates one core when there are a lot more just sitting around doing nothing. However, GIL has existed in Python since day one, and many core-level components also depend on it, which makes the removal process harder.</p>
<p>To cope with this issue, Python 3.12 introduced the <a href="https://docs.python.org/3.12/whatsnew/3.12.html#pep-684-a-per-interpreter-gil" target="_blank" rel="noreferrer">sub-interpreter<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>. This allows tasks to be divided into multiple chunks and executed in multiple sub-interpreters.</p>
<p>However, this is just a timely step as it does not solve the fundamental issue. What we want is the complete removal of GIL, the so-called &ldquo;<a href="https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython" target="_blank" rel="noreferrer">Free-threaded<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>,&rdquo; to unleash Python&rsquo;s full potential.</p>
<p>Last year, a group of Meta engineers had a <a href="https://discuss.python.org/t/a-fast-free-threading-python/27903/99" target="_blank" rel="noreferrer">discussion<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> about how to implement such a huge change, and they gained some support from the company to work on this issue at full pace.</p>
<p>So, a roadmap is planned. <a href="https://peps.python.org/pep-0703/" target="_blank" rel="noreferrer">PEP 703<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> was drafted, and the <a href="https://github.com/python/cpython/pull/116338" target="_blank" rel="noreferrer">first PR<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> has been merged into the main branch. Python 3.13 will be the first official Python version you can execute without GIL.</p>
<p>However, there are constraints, like a lot. So be sure to check out <a href="https://gist.github.com/tonybaloney/24d545ed855a3c90f844209152835f07" target="_blank" rel="noreferrer">this talk<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> before you start walking on the wire.</p>
<h3 id="-calver" class="relative group">📅 CalVer? </h3><p>Hugo, the release manager of Python 3.14 and 3.15, opened the discussion during the language summit about changing Python&rsquo;s versioning to <a href="https://calver.org/" target="_blank" rel="noreferrer">CalVer<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<p>This means if this proposal is accepted, instead of 3.14 and 3.15, we will have 3.25 and 3.26.</p>
<p>Python has traditionally been considered versioning with <a href="https://semver.org/" target="_blank" rel="noreferrer">SemVer<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, but SemVer wasn&rsquo;t even a thing when Python was created. It was a time when Python did not have stable release cycles, so versioning just sort of conforms to the idea of SemVer.</p>
<p>However, as Python now has a one-year release cycle, switching to CalVer can have some added benefits. If interested, check out <a href="https://hugovk.github.io/python-calver/" target="_blank" rel="noreferrer">Hugo&rsquo;s talk<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<h2 id="-new-packages-on-the-horizon" class="relative group">🌅 New packages on the horizon </h2><p>Although these packages have been around for a while, they&rsquo;re once again making headlines at the conference this year.</p>
<ul>
<li><a href="https://github.com/pyscript/pyscript" target="_blank" rel="noreferrer">PyScript<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>: As WebAssembly becomes more popular and mature, more people start using PyScript to embed Python directly into websites. For pure Python packages, they can now embed playgrounds or sandboxes into their documentation, just like most JavaScript modules.</li>
<li><a href="https://github.com/PyO3/pyo3" target="_blank" rel="noreferrer">PyO3<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>: Rust is gaining popularity, and so will PyO3. By creating an interface wrapper around Rust using PyO3, existing Python applications or packages can intuitively exploit Rust performance.</li>
<li><a href="https://github.com/duckdb/duckdb" target="_blank" rel="noreferrer">DuckDB<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>: SQLite aims to be a small, self-contained variant in place of RDBMS like MySQL and Postgres. DuckDB seeks to be the same for analytical data. Although only a little user has put them into production usage, the initial developer feedback is quite positive. They&rsquo;ve also announced that they will roll out managed services lately and claim that it will be more than just another instance running on the cloud. Instead, the client will work with the cloud instance to execute some minor computation locally while leaving data-intensive queries to the cloud instance to achieve a high overall performance. That sounds interesting to me, and it may be worth paying attention to check it out in the near future.</li>
</ul>
<h2 id="-some-other-thoughts" class="relative group">🍭 Some other thoughts </h2><p>These are some other random thoughts that I find interesting to jot them down.</p>
<h3 id="-the-foods" class="relative group">🥗 The foods </h3><p>As I&rsquo;ve mentioned, I&rsquo;ve been to PyCon DE in 2021. A common thing about both conferences is that I can hardly understand their food, even when looking at the signs. However, in terms of the flavor, oh man, that&rsquo;s a whole lot different. I&rsquo;m unsure who is to blame for such a tasteless lunch. It could be PSF did not put sufficient funds or effort into the catering (which is understandable, and I&rsquo;m not here to judge), or maybe Americans were just so used to having these disgusting nutrition compounds, so they stopped trying anyhow.</p>
<p>I genuinely wish they could prove me wrong next year.</p>
<h3 id="-why-is-there-no-pants" class="relative group">👖 Why is there no pants </h3><p>Strolling around sponsored booths, I got lots of SWAGs, T-shirts, hats, and even socks. This got me thinking: Why is it that no tech company is giving out SWAG pants? Is it because programmers don&rsquo;t wear pants? because they all work from home?</p>
<h2 id="-thats-a-wrap" class="relative group">🌯 That’s a wrap </h2><p>PyCon US will still be hosted in Pittsburgh. Although I should have graduated and moved on by then, the date is close to my graduation commencement, so I suppose it will be a grand return for two events in a go then.</p>
<p>See you next year!</p>
<div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900">
  <span class="pe-3 text-primary-400">
    <span class="icon relative inline-block px-1 align-text-bottom">
      <span class="icon relative inline-block px-1 align-text-bottom"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
    </span>
  </span>
  <span class="dark:text-neutral-300">
    I’m actively seeking oppertunities for Full-time SWE roles starting in Spring 2025. If you liked my content,
    <a href="https://www.linkedin.com/in/tomy0000000" target="_blank" rel="noreferrer">Let’s Talk</a>.
  </span>
</div>

<hr>
<h2 id="-ackownledgement" class="relative group">🎀 Ackownledgement </h2><p>A special thank goes to CMU&rsquo;s Office of Graduate and Postdoctoral Affairs for funding my admission ticket through the Professional Engagement Funding.</p>
]]></content:encoded><category>🤖 Development</category><guid isPermaLink="false">pycon-us-2024</guid><pubDate>Mon, 27 May 2024 15:13:03 -0700</pubDate></item><item><title>Affordable price with Purchasing Parity Power</title><link>https://blog.tomy.me/en/posts/affordable-price-with-purchasing-parity-power/</link><description>Breakdown of Gumroad and Dracula UI's Purchasing Parity Power Discounting Logic</description><content:encoded><![CDATA[<p>So I&rsquo;ve been wanting to get the <a href="https://draculatheme.com/ui" target="_blank" rel="noreferrer">Dracula UI<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> for a while. Since I was building up my personal blog now, and I really doesn&rsquo;t want to just follow the default that comes with the theme, so I decided to get the palette from it, and see if I can make it work on my blog.</p>
<p>Just when I&rsquo;m about to hit the buying button, this lovely banner bumps into my eyes:</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/affordable-price-with-purchasing-parity-power/banner.png"
      class="mx-auto my-0 rounded-md"
      alt="Banner of PPP discount"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>I honestly don&rsquo;t recall seeing that before, because 51% off is a huge price gap that should have made me buy it without a doubt when I first saw it. Nevertheless, before I make my way to checkout, I thought this is a good chance to try out the VPN subscription that I’ve been paying for a while. Although switching countries is just a click away, it’s still a bit of a hassle, so after playing around with some neighboring countries, I dive deep into the code to see if there’s anything I can facilitate. First I found an API endpoint at <a href="https://ppp.dracula.workers.dev" target="_blank" rel="noreferrer">https://ppp.dracula.workers.dev<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, which is an API that responds the discount based on the client’s IP address. Good enough, but can be better.</p>
<h2 id="-automate" class="relative group">🪄 Automate </h2><p>Then I suddenly realize, that the API is the single source of truth of the discount, since the merchant dealing the payment does not verify the code when the user is applying the code, and the code is just a country code suffix with the fixed string of <code>UI</code>, so I can just brute force the code with the <a href="https://en.wikipedia.org/wiki/ISO_3166-1" target="_blank" rel="noreferrer">ISO 3166-1 code<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<p>Here is the results:</p>
<details>
<summary>List of discount in different countries</summary>
<table>
  <thead>
      <tr>
          <th>Country</th>
          <th>ISO 3166-1</th>
          <th>Discount</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>🇮🇩 Indonesia</td>
          <td>ID</td>
          <td>69%</td>
      </tr>
      <tr>
          <td>🇺🇬 Uganda</td>
          <td>UG</td>
          <td>69%</td>
      </tr>
      <tr>
          <td>🇦🇫 Afghanistan</td>
          <td>AF</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇩🇿 Algeria</td>
          <td>DZ</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇦🇿 Azerbaijan</td>
          <td>AZ</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇱🇾 Libya</td>
          <td>LY</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇸🇸 South Sudan</td>
          <td>SS</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇸🇷 Suriname</td>
          <td>SR</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>🇦🇴 Angola</td>
          <td>AO</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇧🇾 Belarus</td>
          <td>BY</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇪🇬 Egypt</td>
          <td>EG</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇮🇶 Iraq</td>
          <td>IQ</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇰🇬 Kyrgyzstan</td>
          <td>KG</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇲🇲 Myanmar</td>
          <td>MM</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇵🇰 Pakistan</td>
          <td>PK</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇱🇰 Sri Lanka</td>
          <td>LK</td>
          <td>67%</td>
      </tr>
      <tr>
          <td>🇹🇿 Tanzania, United Republic of</td>
          <td>TZ</td>
          <td>66%</td>
      </tr>
      <tr>
          <td>🇻🇳 Viet Nam</td>
          <td>VN</td>
          <td>66%</td>
      </tr>
      <tr>
          <td>🇰🇭 Cambodia</td>
          <td>KH</td>
          <td>65%</td>
      </tr>
      <tr>
          <td>🇬🇲 Gambia</td>
          <td>GM</td>
          <td>65%</td>
      </tr>
      <tr>
          <td>🇲🇬 Madagascar</td>
          <td>MG</td>
          <td>65%</td>
      </tr>
      <tr>
          <td>🇲🇷 Mauritania</td>
          <td>MR</td>
          <td>65%</td>
      </tr>
      <tr>
          <td>🇳🇮 Nicaragua</td>
          <td>NI</td>
          <td>65%</td>
      </tr>
      <tr>
          <td>🇦🇷 Argentina</td>
          <td>AR</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇰🇿 Kazakhstan</td>
          <td>KZ</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇲🇾 Malaysia</td>
          <td>MY</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇲🇳 Mongolia</td>
          <td>MN</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇸🇨 Seychelles</td>
          <td>SC</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇹🇯 Tajikistan</td>
          <td>TJ</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇹🇷 Türkiye</td>
          <td>TR</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇺🇦 Ukraine</td>
          <td>UA</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇺🇿 Uzbekistan</td>
          <td>UZ</td>
          <td>64%</td>
      </tr>
      <tr>
          <td>🇧🇹 Bhutan</td>
          <td>BT</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇩🇴 Dominican Republic</td>
          <td>DO</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇸🇿 Eswatini</td>
          <td>SZ</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇪🇹 Ethiopia</td>
          <td>ET</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇬🇪 Georgia</td>
          <td>GE</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇬🇭 Ghana</td>
          <td>GH</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇮🇳 India</td>
          <td>IN</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇱🇦 Lao People&rsquo;s Democratic Republic</td>
          <td>LA</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇲🇼 Malawi</td>
          <td>MW</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇲🇿 Mozambique</td>
          <td>MZ</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇳🇵 Nepal</td>
          <td>NP</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇳🇬 Nigeria</td>
          <td>NG</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇷🇼 Rwanda</td>
          <td>RW</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇸🇱 Sierra Leone</td>
          <td>SL</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇸🇩 Sudan</td>
          <td>SD</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇹🇳 Tunisia</td>
          <td>TN</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇿🇼 Zimbabwe</td>
          <td>ZW</td>
          <td>63%</td>
      </tr>
      <tr>
          <td>🇧🇫 Burkina Faso</td>
          <td>BF</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇹🇩 Chad</td>
          <td>TD</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇴🇲 Oman</td>
          <td>OM</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇵🇾 Paraguay</td>
          <td>PY</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇷🇺 Russian Federation</td>
          <td>RU</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇿🇲 Zambia</td>
          <td>ZM</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>🇧🇯 Benin</td>
          <td>BJ</td>
          <td>61%</td>
      </tr>
      <tr>
          <td>🇬🇳 Guinea</td>
          <td>GN</td>
          <td>61%</td>
      </tr>
      <tr>
          <td>🇱🇸 Lesotho</td>
          <td>LS</td>
          <td>61%</td>
      </tr>
      <tr>
          <td>🇵🇭 Philippines</td>
          <td>PH</td>
          <td>61%</td>
      </tr>
      <tr>
          <td>🇸🇦 Saudi Arabia</td>
          <td>SA</td>
          <td>61%</td>
      </tr>
      <tr>
          <td>🇦🇲 Armenia</td>
          <td>AM</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇧🇩 Bangladesh</td>
          <td>BD</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇲🇱 Mali</td>
          <td>ML</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇲🇦 Morocco</td>
          <td>MA</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇲🇰 North Macedonia</td>
          <td>MK</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇹🇬 Togo</td>
          <td>TG</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>🇧🇳 Brunei Darussalam</td>
          <td>BN</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇨🇲 Cameroon</td>
          <td>CM</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇨🇬 Congo</td>
          <td>CG</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇨🇮 Côte d&rsquo;Ivoire</td>
          <td>CI</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇳🇪 Niger</td>
          <td>NE</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇹🇭 Thailand</td>
          <td>TH</td>
          <td>59%</td>
      </tr>
      <tr>
          <td>🇧🇬 Bulgaria</td>
          <td>BG</td>
          <td>58%</td>
      </tr>
      <tr>
          <td>🇲🇺 Mauritius</td>
          <td>MU</td>
          <td>58%</td>
      </tr>
      <tr>
          <td>🇸🇳 Senegal</td>
          <td>SN</td>
          <td>58%</td>
      </tr>
      <tr>
          <td>🇦🇱 Albania</td>
          <td>AL</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>🇧🇦 Bosnia and Herzegovina</td>
          <td>BA</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>🇰🇼 Kuwait</td>
          <td>KW</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>🇿🇦 South Africa</td>
          <td>ZA</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>🇹🇲 Turkmenistan</td>
          <td>TM</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>🇧🇼 Botswana</td>
          <td>BW</td>
          <td>56%</td>
      </tr>
      <tr>
          <td>🇧🇮 Burundi</td>
          <td>BI</td>
          <td>56%</td>
      </tr>
      <tr>
          <td>🇨🇴 Colombia</td>
          <td>CO</td>
          <td>56%</td>
      </tr>
      <tr>
          <td>🇷🇸 Serbia</td>
          <td>RS</td>
          <td>56%</td>
      </tr>
      <tr>
          <td>🇬🇦 Gabon</td>
          <td>GA</td>
          <td>55%</td>
      </tr>
      <tr>
          <td>🇵🇪 Peru</td>
          <td>PE</td>
          <td>55%</td>
      </tr>
      <tr>
          <td>🇬🇶 Equatorial Guinea</td>
          <td>GQ</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>🇬🇼 Guinea-Bissau</td>
          <td>GW</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>🇯🇴 Jordan</td>
          <td>JO</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>🇲🇩 Moldova, Republic of</td>
          <td>MD</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>🇹🇱 Timor-Leste</td>
          <td>TL</td>
          <td>54%</td>
      </tr>
      <tr>
          <td>🇧🇷 Brazil</td>
          <td>BR</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇭🇺 Hungary</td>
          <td>HU</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇲🇽 Mexico</td>
          <td>MX</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇲🇪 Montenegro</td>
          <td>ME</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇳🇦 Namibia</td>
          <td>NA</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇵🇱 Poland</td>
          <td>PL</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>🇸🇻 El Salvador</td>
          <td>SV</td>
          <td>52%</td>
      </tr>
      <tr>
          <td>🇮🇷 Iran (Islamic Republic of)</td>
          <td>IR</td>
          <td>52%</td>
      </tr>
      <tr>
          <td>🇷🇴 Romania</td>
          <td>RO</td>
          <td>52%</td>
      </tr>
      <tr>
          <td>🇹🇼 Taiwan, Province of China</td>
          <td>TW</td>
          <td>51%</td>
      </tr>
      <tr>
          <td>🇹🇹 Trinidad and Tobago</td>
          <td>TT</td>
          <td>51%</td>
      </tr>
      <tr>
          <td>🇧🇴 Bolivia (Plurinational State of)</td>
          <td>BO</td>
          <td>50%</td>
      </tr>
      <tr>
          <td>🇨🇻 Cabo Verde</td>
          <td>CV</td>
          <td>50%</td>
      </tr>
      <tr>
          <td>🇭🇳 Honduras</td>
          <td>HN</td>
          <td>50%</td>
      </tr>
      <tr>
          <td>🇰🇪 Kenya</td>
          <td>KE</td>
          <td>50%</td>
      </tr>
      <tr>
          <td>🇧🇭 Bahrain</td>
          <td>BH</td>
          <td>49%</td>
      </tr>
      <tr>
          <td>🇬🇾 Guyana</td>
          <td>GY</td>
          <td>49%</td>
      </tr>
      <tr>
          <td>🇪🇨 Ecuador</td>
          <td>EC</td>
          <td>48%</td>
      </tr>
      <tr>
          <td>🇭🇹 Haiti</td>
          <td>HT</td>
          <td>48%</td>
      </tr>
      <tr>
          <td>🇶🇦 Qatar</td>
          <td>QA</td>
          <td>48%</td>
      </tr>
      <tr>
          <td>🇨🇳 China</td>
          <td>CN</td>
          <td>46%</td>
      </tr>
      <tr>
          <td>🇰🇲 Comoros</td>
          <td>KM</td>
          <td>46%</td>
      </tr>
      <tr>
          <td>🇯🇲 Jamaica</td>
          <td>JM</td>
          <td>46%</td>
      </tr>
      <tr>
          <td>🇩🇯 Djibouti</td>
          <td>DJ</td>
          <td>45%</td>
      </tr>
      <tr>
          <td>🇬🇹 Guatemala</td>
          <td>GT</td>
          <td>45%</td>
      </tr>
      <tr>
          <td>🇧🇿 Belize</td>
          <td>BZ</td>
          <td>44%</td>
      </tr>
      <tr>
          <td>🇨🇱 Chile</td>
          <td>CL</td>
          <td>44%</td>
      </tr>
      <tr>
          <td>🇨🇩 Congo, Democratic Republic of the</td>
          <td>CD</td>
          <td>44%</td>
      </tr>
      <tr>
          <td>🇭🇷 Croatia</td>
          <td>HR</td>
          <td>43%</td>
      </tr>
      <tr>
          <td>🇱🇹 Lithuania</td>
          <td>LT</td>
          <td>43%</td>
      </tr>
      <tr>
          <td>🇸🇰 Slovakia</td>
          <td>SK</td>
          <td>42%</td>
      </tr>
      <tr>
          <td>🇺🇾 Uruguay</td>
          <td>UY</td>
          <td>41%</td>
      </tr>
      <tr>
          <td>🇫🇯 Fiji</td>
          <td>FJ</td>
          <td>40%</td>
      </tr>
      <tr>
          <td>🇰🇳 Saint Kitts and Nevis</td>
          <td>KN</td>
          <td>40%</td>
      </tr>
      <tr>
          <td>🇵🇦 Panama</td>
          <td>PA</td>
          <td>39%</td>
      </tr>
      <tr>
          <td>🇦🇪 United Arab Emirates</td>
          <td>AE</td>
          <td>39%</td>
      </tr>
      <tr>
          <td>🇨🇿 Czechia</td>
          <td>CZ</td>
          <td>38%</td>
      </tr>
      <tr>
          <td>🇦🇬 Antigua and Barbuda</td>
          <td>AG</td>
          <td>37%</td>
      </tr>
      <tr>
          <td>🇨🇫 Central African Republic</td>
          <td>CF</td>
          <td>37%</td>
      </tr>
      <tr>
          <td>🇱🇻 Latvia</td>
          <td>LV</td>
          <td>37%</td>
      </tr>
      <tr>
          <td>🇱🇧 Lebanon</td>
          <td>LB</td>
          <td>37%</td>
      </tr>
      <tr>
          <td>🇻🇨 Saint Vincent and the Grenadines</td>
          <td>VC</td>
          <td>37%</td>
      </tr>
      <tr>
          <td>🇦🇼 Aruba</td>
          <td>AW</td>
          <td>36%</td>
      </tr>
      <tr>
          <td>🇵🇬 Papua New Guinea</td>
          <td>PG</td>
          <td>36%</td>
      </tr>
      <tr>
          <td>🇸🇬 Singapore</td>
          <td>SG</td>
          <td>36%</td>
      </tr>
      <tr>
          <td>🇨🇷 Costa Rica</td>
          <td>CR</td>
          <td>34%</td>
      </tr>
      <tr>
          <td>🇲🇻 Maldives</td>
          <td>MV</td>
          <td>33%</td>
      </tr>
      <tr>
          <td>🇸🇹 Sao Tome and Principe</td>
          <td>ST</td>
          <td>33%</td>
      </tr>
      <tr>
          <td>🇬🇩 Grenada</td>
          <td>GD</td>
          <td>32%</td>
      </tr>
      <tr>
          <td>🇩🇲 Dominica</td>
          <td>DM</td>
          <td>30%</td>
      </tr>
      <tr>
          <td>🇲🇴 Macao</td>
          <td>MO</td>
          <td>30%</td>
      </tr>
      <tr>
          <td>🇪🇪 Estonia</td>
          <td>EE</td>
          <td>29%</td>
      </tr>
      <tr>
          <td>🇲🇹 Malta</td>
          <td>MT</td>
          <td>29%</td>
      </tr>
      <tr>
          <td>🇬🇷 Greece</td>
          <td>GR</td>
          <td>28%</td>
      </tr>
      <tr>
          <td>🇨🇾 Cyprus</td>
          <td>CY</td>
          <td>27%</td>
      </tr>
      <tr>
          <td>🇳🇷 Nauru</td>
          <td>NR</td>
          <td>27%</td>
      </tr>
      <tr>
          <td>🇸🇮 Slovenia</td>
          <td>SI</td>
          <td>27%</td>
      </tr>
      <tr>
          <td>🇪🇷 Eritrea</td>
          <td>ER</td>
          <td>26%</td>
      </tr>
      <tr>
          <td>🇱🇨 Saint Lucia</td>
          <td>LC</td>
          <td>26%</td>
      </tr>
      <tr>
          <td>🇵🇹 Portugal</td>
          <td>PT</td>
          <td>25%</td>
      </tr>
      <tr>
          <td>🇰🇷 Korea, Republic of</td>
          <td>KR</td>
          <td>24%</td>
      </tr>
      <tr>
          <td>🇹🇴 Tonga</td>
          <td>TO</td>
          <td>24%</td>
      </tr>
      <tr>
          <td>🇭🇰 Hong Kong</td>
          <td>HK</td>
          <td>23%</td>
      </tr>
      <tr>
          <td>🇵🇷 Puerto Rico</td>
          <td>PR</td>
          <td>21%</td>
      </tr>
      <tr>
          <td>🇼🇸 Samoa</td>
          <td>WS</td>
          <td>21%</td>
      </tr>
      <tr>
          <td>🇪🇸 Spain</td>
          <td>ES</td>
          <td>21%</td>
      </tr>
      <tr>
          <td>🇰🇮 Kiribati</td>
          <td>KI</td>
          <td>18%</td>
      </tr>
      <tr>
          <td>🇸🇲 San Marino</td>
          <td>SM</td>
          <td>17%</td>
      </tr>
      <tr>
          <td>🇮🇹 Italy</td>
          <td>IT</td>
          <td>12%</td>
      </tr>
      <tr>
          <td>🇦🇽 Åland Islands</td>
          <td>AX</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇸 American Samoa</td>
          <td>AS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇩 Andorra</td>
          <td>AD</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇮 Anguilla</td>
          <td>AI</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇶 Antarctica</td>
          <td>AQ</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇺 Australia</td>
          <td>AU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇦🇹 Austria</td>
          <td>AT</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇸 Bahamas</td>
          <td>BS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇧 Barbados</td>
          <td>BB</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇪 Belgium</td>
          <td>BE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇲 Bermuda</td>
          <td>BM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>Bonaire, Sint Eustatius and Saba</td>
          <td>BQ</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇻 Bouvet Island</td>
          <td>BV</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇮🇴 British Indian Ocean Territory</td>
          <td>IO</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇦 Canada</td>
          <td>CA</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇰🇾 Cayman Islands</td>
          <td>KY</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇽 Christmas Island</td>
          <td>CX</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇨 Cocos (Keeling) Islands</td>
          <td>CC</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇰 Cook Islands</td>
          <td>CK</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇺 Cuba</td>
          <td>CU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇼 Curaçao</td>
          <td>CW</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇩🇰 Denmark</td>
          <td>DK</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇫🇰 Falkland Islands (Malvinas)</td>
          <td>FK</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇫🇴 Faroe Islands</td>
          <td>FO</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇫🇮 Finland</td>
          <td>FI</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇫🇷 France</td>
          <td>FR</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇫 French Guiana</td>
          <td>GF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇵🇫 French Polynesia</td>
          <td>PF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇹🇫 French Southern Territories</td>
          <td>TF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇩🇪 Germany</td>
          <td>DE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇮 Gibraltar</td>
          <td>GI</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇱 Greenland</td>
          <td>GL</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇵 Guadeloupe</td>
          <td>GP</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇺 Guam</td>
          <td>GU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇬 Guernsey</td>
          <td>GG</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇭🇲 Heard Island and McDonald Islands</td>
          <td>HM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>Holy See</td>
          <td>VA</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇮🇸 Iceland</td>
          <td>IS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇮🇪 Ireland</td>
          <td>IE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇮🇲 Isle of Man</td>
          <td>IM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇮🇱 Israel</td>
          <td>IL</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇯🇵 Japan</td>
          <td>JP</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇯🇪 Jersey</td>
          <td>JE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇰🇵 Korea (Democratic People&rsquo;s Republic of)</td>
          <td>KP</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇱🇷 Liberia</td>
          <td>LR</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇱🇮 Liechtenstein</td>
          <td>LI</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇱🇺 Luxembourg</td>
          <td>LU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇭 Marshall Islands</td>
          <td>MH</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇶 Martinique</td>
          <td>MQ</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇾🇹 Mayotte</td>
          <td>YT</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇫🇲 Micronesia (Federated States of)</td>
          <td>FM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇨 Monaco</td>
          <td>MC</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇸 Montserrat</td>
          <td>MS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇱 Netherlands</td>
          <td>NL</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇨 New Caledonia</td>
          <td>NC</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇿 New Zealand</td>
          <td>NZ</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇺 Niue</td>
          <td>NU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇫 Norfolk Island</td>
          <td>NF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇵 Northern Mariana Islands</td>
          <td>MP</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇳🇴 Norway</td>
          <td>NO</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇵🇼 Palau</td>
          <td>PW</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇵🇸 Palestine, State of</td>
          <td>PS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇵🇳 Pitcairn</td>
          <td>PN</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇷🇪 Réunion</td>
          <td>RE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇧🇱 Saint Barthélemy</td>
          <td>BL</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇭 Saint Helena, Ascension and Tristan da Cunha</td>
          <td>SH</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇲🇫 Saint Martin (French part)</td>
          <td>MF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇵🇲 Saint Pierre and Miquelon</td>
          <td>PM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇽 Sint Maarten (Dutch part)</td>
          <td>SX</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇧 Solomon Islands</td>
          <td>SB</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇴 Somalia</td>
          <td>SO</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇸 South Georgia and the South Sandwich Islands</td>
          <td>GS</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇯 Svalbard and Jan Mayen</td>
          <td>SJ</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇪 Sweden</td>
          <td>SE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇨🇭 Switzerland</td>
          <td>CH</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇸🇾 Syrian Arab Republic</td>
          <td>SY</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇹🇰 Tokelau</td>
          <td>TK</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇹🇨 Turks and Caicos Islands</td>
          <td>TC</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇹🇻 Tuvalu</td>
          <td>TV</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇬🇧 United Kingdom of Great Britain and Northern Ireland</td>
          <td>GB</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇺🇲 United States Minor Outlying Islands</td>
          <td>UM</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇺🇸 United States of America</td>
          <td>US</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇻🇺 Vanuatu</td>
          <td>VU</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇻🇪 Venezuela (Bolivarian Republic of)</td>
          <td>VE</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇻🇬 Virgin Islands (British)</td>
          <td>VG</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇻🇮 Virgin Islands (U.S.)</td>
          <td>VI</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇼🇫 Wallis and Futuna</td>
          <td>WF</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇪🇭 Western Sahara</td>
          <td>EH</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>🇾🇪 Yemen</td>
          <td>YE</td>
          <td>0%</td>
      </tr>
  </tbody>
</table>
</details>
<p>The results are rather interesting. If you compare the results with the <a href="https://en.wikipedia.org/wiki/List_of_countries_by_GDP_%28PPP%29_per_capita" target="_blank" rel="noreferrer">List of countries by GDP (PPP) per Capita<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, you can see there are some mismatches:</p>
<ul>
<li>🇸🇬 Singapore ranked with the 4th most purchasing power, but still has a 36% of discount</li>
<li>Same goes with 🇭🇰 Hong Kong, ranked around 11th ~ 12th with a 23% of discount</li>
<li>Many African has a high percentage of discount (e.g. 🇺🇬 Uganda has 69%), but some barely have any (🇱🇷 Liberia, 🇸🇴 Somalia). Although some might be due to the sanctions, that&rsquo;s just my guess.</li>
</ul>
<h2 id="conclusion" class="relative group">Conclusion </h2><p>After some Googling, I&rsquo;ve also discovered that <a href="https://help.gumroad.com/article/315-dynamic-discounts-with-parity-deals" target="_blank" rel="noreferrer">Gumroad itself had built-in support for location deals<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, with some customizable options. Therefore, judging from the results, the author of Dracula UI not only grants the discount based on the purchasing power, but the disproportionate discount for some countries also shows that he is probably also granting the discount to reach more users that the theme is not yet popular in.</p>
<p>Nevertheless, this is a good chance and opportunity for me to dig into more about the purchasing power of different countries. Moreover, this mechanism is also a good way to bring equality to the world as an individual, and I would like to see more people doing this.</p>
]]></content:encoded><category>🍫 Life</category><guid isPermaLink="false">affordable-price-with-purchasing-parity-power</guid><pubDate>Wed, 24 Aug 2022 03:09:42 +0800</pubDate></item><item><title>AIS3 2020 PreExam Write Up</title><link>https://blog.tomy.me/en/posts/ais3-2020-pre-exam-write-up/</link><description>My first experience with CTF, and probably the last one.</description><content:encoded><![CDATA[<h2 id="-problems" class="relative group">📋 Problems </h2><table>
  <thead>
      <tr>
          <th>Category</th>
          <th>Problem</th>
          <th>Final Score</th>
          <th>Success Solved</th>
          <th>Write Up</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>🐧 Misc</td>
          <td>💤 Piquero</td>
          <td>100</td>
          <td>347</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🐧 Misc</td>
          <td>🐥 Karuego</td>
          <td>100</td>
          <td>245</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🐧 Misc</td>
          <td>🌱 Soy</td>
          <td>139</td>
          <td>172</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🐧 Misc</td>
          <td>👑 Saburo</td>
          <td>359</td>
          <td>108</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🐧 Misc</td>
          <td>👿 Shichirou</td>
          <td>450</td>
          <td>65</td>
          <td></td>
      </tr>
      <tr>
          <td>🐧 Misc</td>
          <td>🧸 Clara</td>
          <td>500</td>
          <td>2</td>
          <td></td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🍍 TsaiBro</td>
          <td>100</td>
          <td>281</td>
          <td></td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🎹 Fallen Beat</td>
          <td>144</td>
          <td>171</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🧠 Stand up!Brain</td>
          <td>455</td>
          <td>62</td>
          <td></td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🍹 Long Island Iced Tea</td>
          <td>498</td>
          <td>15</td>
          <td></td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🌹 La vie en rose</td>
          <td>499</td>
          <td>12</td>
          <td></td>
      </tr>
      <tr>
          <td>♻️ Reverse</td>
          <td>🐉 Uroboros</td>
          <td>500</td>
          <td>9</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>👻 BOF</td>
          <td>100</td>
          <td>189</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>📃 Nonsense</td>
          <td>474</td>
          <td>47</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>🔫 Portal gun</td>
          <td>491</td>
          <td>28</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>🏫 Morty school</td>
          <td>498</td>
          <td>14</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>🔮 Death crystal</td>
          <td>499</td>
          <td>10</td>
          <td></td>
      </tr>
      <tr>
          <td>💥 Pwn</td>
          <td>📦 Meeseeks box</td>
          <td>500</td>
          <td>8</td>
          <td></td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🦕 Brontosaurus</td>
          <td>100</td>
          <td>380</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🦖 T-Rex</td>
          <td>100</td>
          <td>381</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🐙 Octopus</td>
          <td>372</td>
          <td>103</td>
          <td></td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🐡 Blowfish</td>
          <td>480</td>
          <td>42</td>
          <td></td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🐪 Camel</td>
          <td>497</td>
          <td>18</td>
          <td></td>
      </tr>
      <tr>
          <td>🙊 Crypto</td>
          <td>🐢 Turtle</td>
          <td>498</td>
          <td>14</td>
          <td></td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🐿️ Squirrel</td>
          <td>100</td>
          <td>220</td>
          <td></td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🦈 Shark</td>
          <td>100</td>
          <td>261</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🐘 Elephant</td>
          <td>168</td>
          <td>165</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🐍 Snake</td>
          <td>272</td>
          <td>137</td>
          <td></td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🦉 Owl</td>
          <td>492</td>
          <td>27</td>
          <td></td>
      </tr>
      <tr>
          <td>🌐 Web</td>
          <td>🦏 Rhino</td>
          <td>494</td>
          <td>24</td>
          <td></td>
      </tr>
  </tbody>
</table>
<h2 id="-misc" class="relative group">🐧 Misc </h2><h3 id="-piquero" class="relative group">💤 Piquero </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%90%a7%20Misc/%f0%9f%92%a4%20Piquero/Piquero_99c9aa83fe492df8d52229017d4dca92297c9aeb.jpg"
      class="mx-auto my-0 rounded-md"
      alt="Piquero Braille"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>It seems quite obvious to me, this is indeed braille.</p>
<p>So the only thing you need to do is open up wikipedia,</p>
<p>lookup from the table and translate it into flag.</p>
<p>There are still some technique worth mentioned.</p>
<p>For instance, each capital letters, digits, punctuation marks have a special pre-block for indication,</p>
<p>and can be hard to lookup from regular table.</p>
<p>The solution, one way is to guess from other content, another way, for example:</p>
<p>if you&rsquo;re trying to lookup <code>⠸</code> (dots on 4, 5, 6)</p>
<p>go to <a href="https://en.wikipedia.org/wiki/Braille_pattern_dots-456" target="_blank" rel="noreferrer">https://en.wikipedia.org/wiki/Braille_pattern_dots-456<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<p>Wikipedia will list all possible decode in different language/system.</p>
<p>Flag：<code>AIS3{I_feel_sleepy_Good_Night!!!}</code></p>
<h3 id="-karuego" class="relative group">🐥 Karuego </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%90%a7%20Misc/%f0%9f%90%a5%20Karuego/Karuego_0d9f4a9262326e0150272debfd4418aaa600ffe4.png"
      class="mx-auto my-0 rounded-md"
      alt="Karuego"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>This is a png image given by the problem.</p>
<p>First thing to do is decode the compressed zip file within the image with <code>binwalk</code>,</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">binwalk -e Karuego_0d9f4a9262326e0150272debfd4418aaa600ffe4.png
</span></span></code></pre></div><p>and then retrieve the password of the zip file with <code>zsteg</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ zsteg Karuego_0d9f4a9262326e0150272debfd4418aaa600ffe4.png
</span></span><span class="line"><span class="cl"><span class="o">[</span>?<span class="o">]</span> <span class="m">1201353</span> bytes of extra data after image end <span class="o">(</span>IEND<span class="o">)</span>, <span class="nv">offset</span> <span class="o">=</span> 0x1f6d30
</span></span><span class="line"><span class="cl">extradata:0         .. file: Zip archive data, at least v1.0 to extract
</span></span><span class="line"><span class="cl">    00000000: <span class="m">50</span> 4b <span class="m">03</span> <span class="m">04</span> 0a <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>  <span class="m">00</span> <span class="m">00</span> <span class="m">40</span> 8a bd <span class="m">50</span> <span class="m">00</span> <span class="m">00</span>  <span class="p">|</span>PK........@..P..<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000010: <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>  <span class="m">00</span> <span class="m">00</span> <span class="m">06</span> <span class="m">00</span> 1c <span class="m">00</span> <span class="m">66</span> <span class="m">69</span>  <span class="p">|</span>..............fi<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000020: 6c <span class="m">65</span> <span class="m">73</span> 2f <span class="m">55</span> <span class="m">54</span> <span class="m">09</span> <span class="m">00</span>  <span class="m">03</span> <span class="m">47</span> d3 d0 5e ed d3 d0  <span class="p">|</span>les/UT...G..^...<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000030: 5e <span class="m">75</span> <span class="m">78</span> 0b <span class="m">00</span> <span class="m">01</span> <span class="m">04</span> f5  <span class="m">01</span> <span class="m">00</span> <span class="m">00</span> <span class="m">04</span> <span class="m">14</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>  <span class="p">|</span>^ux.............<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000040: <span class="m">50</span> 4b <span class="m">03</span> <span class="m">04</span> <span class="m">14</span> <span class="m">00</span> <span class="m">09</span> <span class="m">00</span>  <span class="m">08</span> <span class="m">00</span> 6f <span class="m">21</span> <span class="m">07</span> <span class="m">49</span> ac 2c  <span class="p">|</span>PK........o!.I.,<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000050: <span class="m">71</span> 1f 7c b9 <span class="m">01</span> <span class="m">00</span> d6 b9  <span class="m">01</span> <span class="m">00</span> <span class="m">35</span> <span class="m">00</span> 1c <span class="m">00</span> <span class="m">66</span> <span class="m">69</span>  <span class="p">|</span>q.<span class="p">|</span>.......5...fi<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000060: 6c <span class="m">65</span> <span class="m">73</span> 2f <span class="m">33</span> <span class="m">61</span> <span class="m">36</span> <span class="m">36</span>  <span class="m">66</span> <span class="m">61</span> <span class="m">35</span> <span class="m">38</span> <span class="m">38</span> <span class="m">37</span> <span class="m">62</span> <span class="m">63</span>  <span class="p">|</span>les/3a66fa5887bc<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000070: <span class="m">62</span> <span class="m">37</span> <span class="m">34</span> <span class="m">30</span> <span class="m">34</span> <span class="m">33</span> <span class="m">38</span> <span class="m">66</span>  <span class="m">31</span> <span class="m">66</span> <span class="m">62</span> <span class="m">34</span> <span class="m">39</span> <span class="m">66</span> <span class="m">37</span> <span class="m">38</span>  <span class="p">|</span>b740438f1fb49f78<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000080: <span class="m">35</span> <span class="m">36</span> <span class="m">39</span> <span class="m">63</span> <span class="m">62</span> <span class="m">35</span> <span class="m">36</span> <span class="m">65</span>  <span class="m">39</span> <span class="m">32</span> <span class="m">33</span> <span class="m">33</span> 5f <span class="m">68</span> <span class="m">71</span> 2e  <span class="p">|</span>569cb56e9233_hq.<span class="p">|</span>
</span></span><span class="line"><span class="cl">    00000090: 6a <span class="m">70</span> <span class="m">67</span> <span class="m">55</span> <span class="m">54</span> <span class="m">09</span> <span class="m">00</span> <span class="m">03</span>  <span class="m">71</span> <span class="m">44</span> a6 <span class="m">57</span> <span class="m">48</span> d3 d0 5e  <span class="p">|</span>jpgUT...qD.WH..^<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000a0: <span class="m">75</span> <span class="m">78</span> 0b <span class="m">00</span> <span class="m">01</span> <span class="m">04</span> f5 <span class="m">01</span>  <span class="m">00</span> <span class="m">00</span> <span class="m">04</span> <span class="m">14</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> 1d  <span class="p">|</span>ux..............<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000b0: <span class="nb">cd</span> <span class="m">40</span> aa <span class="m">87</span> <span class="m">37</span> 8a <span class="m">57</span> <span class="m">93</span>  <span class="m">85</span> a9 4c b3 cf fd <span class="m">58</span> 2d  <span class="p">|</span>.@..7.W...L...X-<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000c0: <span class="m">38</span> <span class="m">10</span> a2 2a dc <span class="m">61</span> 3e 8a  <span class="m">67</span> bf 4c c1 e1 cb d0 d0  <span class="p">|</span>8..*.a&gt;.g.L.....<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000d0: <span class="m">32</span> <span class="m">65</span> <span class="m">97</span> d0 b5 ef f0 a5  2a <span class="m">04</span> a6 <span class="m">00</span> af b2 <span class="m">63</span> e9  <span class="p">|</span>2e......*.....c.<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000e0: <span class="m">19</span> c2 6a 7b de <span class="m">79</span> a3 a2  3f 0a b3 e8 <span class="m">74</span> <span class="m">67</span> <span class="m">35</span> ec  <span class="p">|</span>..j<span class="o">{</span>.y..?...tg5.<span class="p">|</span>
</span></span><span class="line"><span class="cl">    000000f0: 1b 8b <span class="m">90</span> c9 <span class="m">76</span> <span class="m">30</span> ad ab  <span class="m">18</span> 7a e9 9a ed f7 8e 7c  <span class="p">|</span>....v0...z.....<span class="o">||</span>
</span></span><span class="line"><span class="cl">b1,rgb,lsb,xy       .. text: <span class="s2">&#34;The key is : lafire&#34;</span>
</span></span><span class="line"><span class="cl">b1,bgr,lsb,xy       .. &lt;wbStego <span class="nv">size</span><span class="o">=</span>865108, <span class="nv">data</span><span class="o">=</span><span class="s2">&#34;;M\xCD!,\xC5\xA0:\x807&#34;</span>..., <span class="nv">even</span><span class="o">=</span>false, <span class="nv">enc</span><span class="o">=</span><span class="s2">&#34;wbStego 2.x/3.x&#34;</span>, <span class="nv">controlbyte</span><span class="o">=</span><span class="s2">&#34;\x80&#34;</span>&gt;
</span></span><span class="line"><span class="cl">b3,r,lsb,xy         .. file: gfxboot compiled html <span class="nb">help</span> file
</span></span><span class="line"><span class="cl">b3,bgr,msb,xy       .. file: Applesoft BASIC program data, first line number <span class="m">2</span>
</span></span><span class="line"><span class="cl">b4,r,lsb,xy         .. file: PDP-11 UNIX/RT ldp
</span></span><span class="line"><span class="cl">b4,g,lsb,xy         .. file: a.out VAX demand paged <span class="o">(</span>first page unmapped<span class="o">)</span> pure executable not stripped
</span></span><span class="line"><span class="cl">b4,b,lsb,xy         .. file: Targa image data - Map <span class="m">16</span> x <span class="m">4096</span> x <span class="m">16</span> +16 +4353 <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">b4,rgb,lsb,xy       .. file: Targa image data - Map <span class="o">(</span>256-4112<span class="o">)</span> <span class="m">4096</span> x <span class="m">65536</span> x <span class="m">1</span> +4097 +257 - right <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">b4,bgr,lsb,xy       .. file: PDP-11 UNIX/RT ldp
</span></span></code></pre></div><p>In this case, the password is <code>lafire</code>,</p>
<p>decompress the zip file, there will be an image which contains the flag.</p>
<p>If you&rsquo;re wondering how to find out all these, simply put, Googling.</p>
<p>To be more specific, Google keyword <code>CTF Image</code>.</p>
<p>Be careful of letter <code>l</code> and digit <code>1</code>, it took me some time to distinguish those&hellip;.</p>
<p>Flag：<code>AIS3{Ar3_y0u_r34l1y_r34dy_t0_sumnn0n_4_D3m0n?}</code></p>
<h3 id="-soy" class="relative group">🌱 Soy </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%90%a7%20Misc/%f0%9f%8c%b1%20Soy/Soy_b692c44dd2a32b30eee8a9315091d79f7dd8c8a8.png"
      class="mx-auto my-0 rounded-md"
      alt="Soy QR Code"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>QR Code fixing, nothing special.</p>
<p>It is, however, going to be helpful, if you have some prior knowledge to QR Code.</p>
<p>Take this empty QR Code as example:</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/soy-blank.png"
      class="mx-auto my-0 rounded-md"
      alt="Blank QR Code"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Red section is for positioning, blue section is <a href="https://en.wikipedia.org/wiki/QR_code#Encoding" target="_blank" rel="noreferrer">Format Info<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<p>I fix the QR Code with <a href="https://merricx.github.io/qrazybox/" target="_blank" rel="noreferrer">QRazyBox<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>, clicking the blue section will list all possible format.</p>
<p>Compare from the QR Code given, Error Correction Level: <code>L</code>，Mask Pattern: <code>0</code> is the only possible solution</p>
<p>The rest is just filling the blocks, once you have fill enough blocks, you&rsquo;ll be able to decode the flag</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/soy-decrypted.png"
      class="mx-auto my-0 rounded-md"
      alt="Decrypted"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Flag：<code>AIS3{H0w_c4n_y0u_f1nd_me?!?!?!!}</code></p>
<h3 id="-saburo" class="relative group">👑 Saburo </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%90%a7%20Misc/%f0%9f%91%91%20Saburo/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="Problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>I didn&rsquo;t really get the idea of this problem at first.</p>
<p>The only thing I discovered is you can get a slightly higher delay in milliseconds with <code>AIS3{</code> as input.</p>
<p>I kind of know how to acquire the flag, but doesn&rsquo;t seems legit to me, so I leave the puzzle for later.</p>
<p>Until the second night of the contest, I found that many contestent were complaining that the results were unstable.</p>
<p>I then realize, brute force is probably the only solution.</p>
<p>So I draft a Python script to iterate all ASCII Pritable letters, and do a while true loop until the presence of <code>}</code>.</p>
<p>Something look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s2">&#34;60.250.197.227&#34;</span><span class="p">,</span> <span class="mi">11001</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span>
</span></span></code></pre></div><p>However it doesn&rsquo;t work as I expected it to like it is in terminal, the delay stays low.</p>
<p>My second thought is to convert the script into a shell script, but I just can&rsquo;t, I failed.</p>
<p>Out of the desperation, this is what I came up with:</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/saburo-keyboard-maestro.png"
      class="mx-auto my-0 rounded-md"
      alt="Keyboard Maestro"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>If you don&rsquo;t have a clue, this is called QuickMacro.</p>
<p>It&rsquo;s basically just automatically executing a pre-record keyboard/mouse event.</p>
<p>I&rsquo;m using Keyboard Maestro on Mac, but I believe any QuickMacro on any platform has the ability to achieve something similar.</p>
<p>It&rsquo;ll work like this:</p>

<script
        src="https://asciinema.org/a/339381.js"
        id="asciicast-339381"
        data-startAt="0"
        data-autoplay="false"
        data-preload="true"
        data-loop="false"
        data-speed="1"
        
        
        
        
        async
      ></script>
<p>During this iteration, the corrrect letter is <code>i</code>, which is kind of hard to find the difference.</p>
<p>According to author, each letter tops up 5ms of noise.</p>
<p>So at the final stage of decoding, you have to run 10 times for each letter, calculate mean value to really find out the correct letter&hellip;.</p>
<p>or really, just, use your</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//media/BQUITFiYVtNte/giphy.gif"
      class="mx-auto my-0 rounded-md"
      alt="imagination"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>You are my Ene&hellip;. energy?</p>
<p>If you&rsquo;re lack of imagination (like me)</p>
<p>just&hellip;. try it harder than.</p>
<p>The final letter has about 400ms of delay.</p>
<p>Flag：<code>AIS3{A1r1ght_U_4r3_my_3n3nnies}</code></p>
<h2 id="-reverse" class="relative group">♻️ Reverse </h2><h3 id="-fallen-beat" class="relative group">🎹 Fallen Beat </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%e2%99%bb%ef%b8%8f%20Reverse/%f0%9f%8e%b9%20Fallen%20Beat/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="Problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Decompress the zip archive, fire up jar executable, you&rsquo;ll soon realize this is a custom music game.</p>
<p>To acquire the flag, you&rsquo;ll have to outbeat a full combo.</p>
<p>For those who really are trying:</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/fallen-beat-full-combo.png"
      class="mx-auto my-0 rounded-md"
      alt="Full Combo"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Don&rsquo;t even think about it.</p>
<p>First thing first, decompile jar executable.</p>
<p>There are dozen ways to achieve that,</p>
<p>my entry-level-approach will be&hellip;.</p>
<p>online decompile, which doesn&rsquo;t suck, to be honest.</p>
<p>The directory contains many java source code file,</p>
<p>global search for keyword <code>flag</code>,</p>
<p>you&rsquo;ll find a method <code>setValue</code> within <code>Visual/PanelEnding.java</code> contain somthing like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">t</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">mc</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">0</span><span class="p">;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">cache</span><span class="p">.</span><span class="na">size</span><span class="p">();</span><span class="w"> </span><span class="o">++</span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">final</span><span class="w"> </span><span class="kt">byte</span><span class="o">[]</span><span class="w"> </span><span class="n">flag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="na">flag</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">final</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="na">flag</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">flag</span><span class="o">[</span><span class="n">n</span><span class="o">]</span><span class="w"> </span><span class="o">^=</span><span class="w"> </span><span class="p">(</span><span class="kt">byte</span><span class="p">)(</span><span class="n">Object</span><span class="p">)</span><span class="n">cache</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">fff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">String</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">flag</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">this</span><span class="p">.</span><span class="na">text</span><span class="o">[</span><span class="n">0</span><span class="o">]</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">&#34;Flag: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">fff</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>You can see that flag is not stored with plain text, it&rsquo;s generated by one of the class attributes <code>flag</code></p>
<p>and xor with a <code>cache</code> that got pass in. Therefore, we&rsquo;ll need to trace what is the <code>cache</code> that got passed in.</p>
<p>Do a second global search with <code>SetValue</code>, the result indicate that <code>SetValue</code> is only called in <code>Control/GameControl.java</code>.</p>
<p>Take some time, you realize the <code>cache</code> is the class attribute within class,</p>
<p>so searching <code>this.cache</code> in this file can find out that <code>this.cache</code> is decalared inside the constructor</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">final</span><span class="w"> </span><span class="n">FileReader</span><span class="w"> </span><span class="n">fr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FileReader</span><span class="p">(</span><span class="n">fumenPath</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">final</span><span class="w"> </span><span class="n">BufferedReader</span><span class="w"> </span><span class="n">br</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">BufferedReader</span><span class="p">(</span><span class="n">fr</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">this</span><span class="p">.</span><span class="na">cache</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">Integer</span><span class="o">&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="n">br</span><span class="p">.</span><span class="na">ready</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">br</span><span class="p">.</span><span class="na">readLine</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">0</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="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">cache</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">a</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Another problem here is that <code>fumenPath</code> is also a parameter that got pass in during the initialze of the class instance.</p>
<p>So, we&rsquo;ll do a thrid global search with keyword <code>GameControl</code>.</p>
<p>Luckily, <code>GameControl</code> is only initalized once inside<code>Control/Frame.java</code>.</p>
<p>Finally, we found out that <code>fumenPath</code> is <code>songs/gekkou/hell.txt</code>.</p>
<p>Repacking eveything above into <a href="https://github.com/tomy0000000/AIS3-2020-PreExam/blob/main/Solving%20Source%20Code/%F0%9F%8E%B9%20Fallen%20Beat/Exploit.java" target="_blank" rel="noreferrer"><code>Exploit.java</code><svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>,</p>
<p>and move <code>songs/gekkou/hell.txt</code> to the same directory, then execute the java source code, you&rsquo;ll be able to retrieve the flag.</p>
<p>Be careful that the <code>br</code> object that was used to read <code>cache</code>, reads an additional line before reading <code>cache</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">this</span><span class="p">.</span><span class="na">bpm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">br</span><span class="p">.</span><span class="na">readLine</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>so you&rsquo;ll add this into <code>Expolit.java</code>, to prevent decoding error.</p>
<p>Flag：<code>AIS3{Wow_how_m4ny_h4nds_do_you_h4ve}</code></p>
<h2 id="-crypto" class="relative group">🙊 Crypto </h2><h3 id="-brontosaurus" class="relative group">🦕 Brontosaurus </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%99%8a%20Crypto/%f0%9f%a6%95%20Brontosaurus/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="Problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>It&rsquo;s abvious that the final keyword <code>KcufsJ</code> is a <em>reverse</em> spelling of <code>JsfucK</code></p>
<p>(well actually, you can findout that with Google, too)</p>
<p>It&rsquo;s a custom JavaScript, you can found a decompiler with Google easily.</p>
<p>Fill in the code, which wouldn&rsquo;t work, is beacuse you have to <em>reverse</em> it first.</p>
<p>Flag：<code>AIS3{Br0n7Os4uru5_ch3at_3asi1Y}</code></p>
<h3 id="-t-rex" class="relative group">🦖 T-Rex </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%99%8a%20Crypto/%f0%9f%a6%96%20T-Rex/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="Problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>The keyword of this problem is <code>nihilist</code>,</p>
<p>which defined a person that who had a philosophical view that all knowledge and values are baseless,</p>
<p>which in fact, does not help solving this problem, at all.</p>
<p>But nihilist cipher did.</p>
<p>If you&rsquo;re wondering how do I came up with an additonal keyword, cipher, the keyword is <code>nihilist CTF</code> or <code>nihilist encoding</code>.</p>
<p>Nihilist cipher are composed of two components, a polybius square and a encrypted text.</p>
<p>You can decode the text by matching the corresponding block in the square mattrix.</p>
<p>There are a lot of online tools, but I don&rsquo;t find any being helpful,</p>
<p>so I made my own version with Python, take a look at <a href="https://github.com/tomy0000000/AIS3-2020-PreExam/blob/main/Solving%20Source%20Code/%F0%9F%A6%96%20T-Rex/T-Rex.py" target="_blank" rel="noreferrer"><code>T-Rex.py</code><svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> if you&rsquo;re interested.</p>
<p>Flag：</p>
<pre tabindex="0"><code>AIS3{TYR4NN0S4URU5_R3X_GIV3_Y0U_SOMETHING_RANDOM_5TD6XQIVN3H7EUF8ODET4T3H907HUC69L6LTSH4KN3EURN49BIOUY6HBFCVJRZP0O83FWM0Z59IISJ5A2VFQG1QJ0LECYLA0A1UYIHTIIT1IWH0JX4T3ZJ1KSBRM9GED63CJVBQHQORVEJZELUJW5UG78B9PP1SIRM1IF500H52USDPIVRK7VGZULBO3RRE1OLNGNALX}
</code></pre><h2 id="-web" class="relative group">🌐 Web </h2><h3 id="-shark" class="relative group">🦈 Shark </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%8c%90%20Web/%f0%9f%a6%88%20Shark/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>First look at the main page</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/shark-home.png"
      class="mx-auto my-0 rounded-md"
      alt="home"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Source code doesn&rsquo;t reveal anything interesting, try clicking the link</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/shark-hint-txt.png"
      class="mx-auto my-0 rounded-md"
      alt="hint.txt"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>The hint says that the flag is not located on the server that we&rsquo;re visiting, but a server located in the same local network.</p>
<p>By obeserving the URL, you can assume that the <code>path</code> parameter can be used to view some file on the server.</p>
<p>I managed to know that unix system tends to store local network informations in <code>/proc/net/fib_trie</code>.</p>
<p>Therefore, our first attempt:</p>
<p><a href="https://shark.ais3.org/?path=/proc/net/fib_trie" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=/proc/net/fib_trie<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>The access is forbidden, try somethign else:</p>
<p><a href="https://shark.ais3.org/?path=index.php" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=index.php<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/shark-index-php.png"
      class="mx-auto my-0 rounded-md"
      alt="index.php"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Well, that worked.</p>
<p>You can see that the php source code blocks any access that make use of absolute path and any relative path starts with <code>..</code>.</p>
<p>This is where <a href="https://www.php.net/manual/en/wrappers.php.php" target="_blank" rel="noreferrer">php://<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a> came in.</p>
<p><code>php://</code> is a pseudo protocal, which is used to proces data stream</p>
<p>For instance, the exmaple given in php documentation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">readfile</span><span class="p">(</span><span class="s2">&#34;php://filter/resource=http://www.example.com&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>loads the content of <a href="http://www.example.com" target="_blank" rel="noreferrer">http://www.example.com<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>.</p>
<p>So we changed the original URL from</p>
<p><a href="https://shark.ais3.org/?path=/proc/net/fib_trie" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=/proc/net/fib_trie<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>into</p>
<p><a href="https://shark.ais3.org/?path=php://filter/resource=/proc/net/fib_trie" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=php://filter/resource=/proc/net/fib_trie<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/shark-proc-net-fib-trie.png"
      class="mx-auto my-0 rounded-md"
      alt="/proc/net/fib_trie"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>TA-DA!!!</p>
<p>All we need to do now is to analyze the content.</p>
<p>There are many tutorial on Google,</p>
<p>but an obvious way is by guessing that</p>
<p><code>0.0.0.0</code> is for unicast,</p>
<p><code>127.0.0.0</code> is for something realted to localhost.</p>
<p>The only possible answer are those begin with 172.</p>
<p>Combine with the hint given previously,</p>
<p>try this</p>
<p><a href="https://shark.ais3.org/?path=php://filter/resource=http://172.22.0.1/flag" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=php://filter/resource=http://172.22.0.1/flag<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>doesn&rsquo;t work.</p>
<p>Try again</p>
<p><a href="https://shark.ais3.org/?path=php://filter/resource=http://172.22.0.2/flag" target="_blank" rel="noreferrer">https://shark.ais3.org/?path=php://filter/resource=http://172.22.0.2/flag<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a></p>
<p>BINGO🎉</p>
<p>Flag：<code>AIS3{5h4rk5_d0n'7_5w1m_b4ckw4rd5}</code></p>
<h3 id="-elephant" class="relative group">🐘 Elephant </h3><p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up//tomy0000000/AIS3-2020-PreExam/main/Problems/%f0%9f%8c%90%20Web/%f0%9f%90%98%20Elephant/Problem.png"
      class="mx-auto my-0 rounded-md"
      alt="Problem"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Let&rsquo;s see the landing page</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/elephant-home.png"
      class="mx-auto my-0 rounded-md"
      alt="home"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>not a clue, try a random name</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/elephant-login.png"
      class="mx-auto my-0 rounded-md"
      alt="login"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>from the keyword <code>token</code>, I assume, the previosu page is login interface,</p>
<p>output the flag according to the name given.</p>
<p>Normally, there will be a cookie after login, let&rsquo;s see.</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/elephant-cookie.png"
      class="mx-auto my-0 rounded-md"
      alt="cookie"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>PHP cookies are normally base64 encoded, decode them first.</p>
<pre tabindex="0"><code>O:4:&#34;User&#34;:2:{s:4:&#34;name&#34;;s:4:&#34;Tomy&#34;;s:11:&#34;?User?token&#34;;s:32:&#34;8bd5e99e146d0af7cc75e8f2bcc9693e&#34;;}
</code></pre><p>Next, according to a hint from this cheatsheet: <a href="https://github.com/w181496/Web-CTF-Cheatsheet#php---serialize--unserialize" target="_blank" rel="noreferrer">w181496 / Web-CTF-Cheatsheet<svg xmlns="http://www.w3.org/2000/svg" fill='#000000' viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" /></svg></a>,</p>
<p>and the knowledge of any PHP string == boolean true.</p>
<p>By modifying the cookie into</p>
<pre tabindex="0"><code>O:4:&#34;User&#34;:2:{s:4:&#34;name&#34;;s:4:&#34;Tomy&#34;;s:11:&#34;?User?token&#34;;b:1;}
</code></pre><p>then base64 encode, fill back into the browser.</p>
<p>







  
  
<figure>
  
    





  
  <picture  class="mx-auto my-0 rounded-md" >
    <img
      src="https://img.tomy.me/blog/ais3-2020-pre-exam-write-up/elephant-flag.png"
      class="mx-auto my-0 rounded-md"
      alt="flag"
      loading="lazy" decoding="async"
    />
  </picture>


</figure>
</p>
<p>Flag：<code>AIS3{0nly_3l3ph4n75_5h0uld_0wn_1v0ry}</code></p>
]]></content:encoded><category>🤖 Development</category><guid isPermaLink="false">ais3-2020-pre-exam-write-up</guid><pubDate>Mon, 06 Jul 2020 03:52:35 +0800</pubDate></item></channel></rss>