<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: iamsopotatoe</title>
    <description>The latest articles on DEV Community by iamsopotatoe (@iamsopotatoecoder).</description>
    <link>https://dev.to/iamsopotatoecoder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3608549%2Fe9eaea80-529a-4e2d-a441-97b7a73c82f1.jpeg</url>
      <title>DEV Community: iamsopotatoe</title>
      <link>https://dev.to/iamsopotatoecoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamsopotatoecoder"/>
    <language>en</language>
    <item>
      <title>x86 CALL is relative. that's why your PE compressor leaves money on the table</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Thu, 20 Aug 2026 17:38:34 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/x86-call-is-relative-thats-why-your-pe-compressor-leaves-money-on-the-table-27k3</link>
      <guid>https://dev.to/iamsopotatoecoder/x86-call-is-relative-thats-why-your-pe-compressor-leaves-money-on-the-table-27k3</guid>
      <description>&lt;p&gt;compress a Windows exe with gzip or with a "normal" LZ77 and the ratio is worse than the file deserves. not because PE is random. because the most repeated thing in the file — &lt;code&gt;call some_helper&lt;/code&gt; — is encoded as a &lt;em&gt;different&lt;/em&gt; 5-byte sequence every time it appears.&lt;/p&gt;

&lt;p&gt;this is the E8/E9 filter. UPX has done it for decades. I didn't invent it. I finally put it in TinyLoad and the compressor stopped being the embarrassing part of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  the encoding that fights LZ
&lt;/h2&gt;

&lt;p&gt;on x86-64, a direct near call is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;E8&lt;/span&gt; &lt;span class="nv"&gt;xx&lt;/span&gt; &lt;span class="nv"&gt;xx&lt;/span&gt; &lt;span class="nv"&gt;xx&lt;/span&gt; &lt;span class="nv"&gt;xx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;E8&lt;/code&gt; then a 32-bit &lt;strong&gt;displacement from the next instruction&lt;/strong&gt;. same for &lt;code&gt;E9&lt;/code&gt; (jmp).&lt;/p&gt;

&lt;p&gt;so this C:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not become three copies of the same bytes. it becomes three calls whose displacements differ by the distance between the call sites.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;offset&lt;/span&gt; &lt;span class="mh"&gt;0x100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nv"&gt;E8&lt;/span&gt;  &lt;span class="nv"&gt;FB&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;     &lt;span class="c1"&gt;; foo is at 0x1000,  0x1000 - 0x105 = 0xEFB&lt;/span&gt;
&lt;span class="nf"&gt;offset&lt;/span&gt; &lt;span class="mh"&gt;0x120&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nv"&gt;E8&lt;/span&gt;  &lt;span class="nv"&gt;DB&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;     &lt;span class="c1"&gt;; 0x1000 - 0x125 = 0xEDB&lt;/span&gt;
&lt;span class="nf"&gt;offset&lt;/span&gt; &lt;span class="mh"&gt;0x140&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nv"&gt;E8&lt;/span&gt;  &lt;span class="nv"&gt;BB&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;     &lt;span class="c1"&gt;; 0x1000 - 0x145 = 0xEBB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LZ77 lives on repeated byte strings. those three 5-byte sequences share &lt;code&gt;E8&lt;/code&gt; and then diverge. the compressor emits three almost-literals instead of one match. multiply that by every call in &lt;code&gt;.text&lt;/code&gt; and you have paid a tax on the densest, most redundant part of the binary.&lt;/p&gt;

&lt;p&gt;the destinations were the same. the encoding hid that.&lt;/p&gt;

&lt;h2&gt;
  
  
  the 15-line fix
&lt;/h2&gt;

&lt;p&gt;before matching, walk the buffer and add the file position into every apparent rel32:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;lzflt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0xE8&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0xE9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after the filter the three calls become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;E8&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="nf"&gt;E8&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="nf"&gt;E8&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(assuming &lt;code&gt;foo&lt;/code&gt; landed at file offset 0x1000 — the exact number doesn't matter. what matters is they are &lt;strong&gt;identical&lt;/strong&gt;.)&lt;/p&gt;

&lt;p&gt;now LZ77 sees a 5-byte match with distance 0x20. that is what a compressor is for.&lt;/p&gt;

&lt;p&gt;undo it after inflate, same loop, subtract instead of add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;lzunflt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0xE8&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0xE9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the transform is its own inverse. you do not store extra metadata. if you filtered, you unfilter. the bitstream does not know this happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  it is a heuristic, and that is the interesting part
&lt;/h2&gt;

&lt;p&gt;the loop does not disassemble. it does not look at section flags. if it sees &lt;code&gt;E8&lt;/code&gt; and four bytes, it treats them as a rel32.&lt;/p&gt;

&lt;p&gt;that is wrong some of the time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;immediate constants that happen to start with &lt;code&gt;E8&lt;/code&gt; / &lt;code&gt;E9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.rdata&lt;/code&gt; / &lt;code&gt;.data&lt;/code&gt; / overlay bytes&lt;/li&gt;
&lt;li&gt;x64 RIP-relative addressing is &lt;code&gt;48 8B 05 rel32&lt;/code&gt; etc. — those rel32s sit behind a different opcode, so this particular filter misses them (and should, unless you also scan for those prefixes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;false positives are not fatal. you add &lt;code&gt;i+5&lt;/code&gt; on the way in and subtract it on the way out, so the bytes come back bit-identical even if that &lt;code&gt;E8&lt;/code&gt; was never a call. you only lose if the false-positive locations &lt;strong&gt;don't&lt;/strong&gt; form a repeating pattern, in which case you turned four data bytes into four other data bytes and LZ is about as happy as before.&lt;/p&gt;

&lt;p&gt;the real miss is the other way: calls you didn't catch. TinyLoad still runs this over the &lt;strong&gt;whole file&lt;/strong&gt;, not just executable sections. that is the honest limitation. a PE-aware pass that only filters &lt;code&gt;IMAGE_SCN_MEM_EXECUTE&lt;/code&gt; sections would cut false hits in data and still get the calls. I have not done that yet. the 15 lines were the 80%.&lt;/p&gt;

&lt;h2&gt;
  
  
  once the calls look the same, lazy LZ77 is the next bottleneck
&lt;/h2&gt;

&lt;p&gt;the old TinyLoad compressor was: hash chain, 64KB window, lazy eval (look one or two bytes ahead). that is a fine first compressor. it is also the one every tutorial writes.&lt;/p&gt;

&lt;p&gt;two things were still leaving matches on the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. greedy/lazy parse is not optimal parse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;at position &lt;code&gt;i&lt;/code&gt; you might have a length-5 match and, two bytes later, a length-40 match. taking the 5-byte match can block the 40. lazy eval catches the obvious version of this. it does not catch "emit two literals so a later match uses a distance I already used."&lt;/p&gt;

&lt;p&gt;the current parse is reverse DP: from the end of the buffer, each position stores the cheapest (match or literal) given a cost model, then we retune the cost model from the parse and do it again. six passes. four different initial cost guesses, keep the best bitstream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for variant in 4 cost guesses:
    for pass in 6:
        for i from n-1 down to 0:
            cost[i] = min(
                bits(literal at i) + cost[i+1],
                bits(match of length L, dist D) + cost[i+L]
                for each candidate (L,D) at i
            )
        rebuild huffman lengths from that parse
        use those lengths as the next cost model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this is the same idea as Zopfli / "optimal parse LZ", just smaller and angrier because it has to live in the same .cpp as a PE loader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. distances repeat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PE code does not jump around uniformly. you call the same import thunks, you copy the same stack setup. DEFLATE noticed this in 1993: keep the last 4 distances and give them their own symbols (&lt;code&gt;rep0..rep3&lt;/code&gt;). if the next match uses a distance you just used, you spend a short code instead of re-encoding the distance.&lt;/p&gt;

&lt;p&gt;TinyLoad's distance alphabet is: 21 length-bucket symbols for a 1MB window, plus 4 repeat-distance symbols. same trick, smaller table.&lt;/p&gt;

&lt;p&gt;then the literals and the (length, extra-bits) symbols go through a depth-limited canonical Huffman tree (max 15 bits, same cap as DEFLATE). code lengths are packed two nibbles per byte in the header so the decoder can rebuild the tree without a second format.&lt;/p&gt;

&lt;p&gt;none of that helps much &lt;strong&gt;until&lt;/strong&gt; the E8 filter makes the repeated calls actually repeated. you can optimal-parse all day; if the bytes don't match, there is nothing to parse.&lt;/p&gt;

&lt;h2&gt;
  
  
  what this is not
&lt;/h2&gt;

&lt;p&gt;it is not a new algorithm. UPX's filter 0x26 / 0x36 / 0x46 family is this idea with more prefixes and more care. cabarc, some game packers, anything that has compressed x86 for a living.&lt;/p&gt;

&lt;p&gt;it is also not "PE-aware compression" in the sense I keep promising. PE-aware would mean: don't waste match budget on the zero-padding between sections, maybe delta-encode relocs, maybe treat the import name table as its own stream. the E8 filter is the one transform that is both stupid-cheap and aimed at the actual instruction encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  why I'm writing this instead of "TinyLoad v7.4 dropped"
&lt;/h2&gt;

&lt;p&gt;because the interesting object here is the instruction encoding, not the repo. a relative call is a good idea for a linker and a hostile input for a compressor. once you see that, you start seeing the rest of PE the same way — RVAs, import thunks, reloc blocks — and the compressor becomes a series of "undo the encoding, then LZ, then redo it."&lt;/p&gt;

&lt;p&gt;source, including &lt;code&gt;lzflt&lt;/code&gt; / &lt;code&gt;lzunflt&lt;/code&gt; and the parse, is in one file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;https://github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;if you build it, &lt;code&gt;--c&lt;/code&gt; is the compressor. &lt;code&gt;--vm&lt;/code&gt; is a different article.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>compression</category>
      <category>pe</category>
      <category>maldev</category>
    </item>
    <item>
      <title>TinyLoad v7 — VEH page-fault decryption and a fully encrypted overlay, what's new in TinyLoad v7.0, my open-source PE packer for Windows</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Sun, 31 May 2026 06:44:05 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/tinyload-v7-veh-page-fault-decryption-and-a-fully-encrypted-overlay-whats-new-in-tinyload-v70-2f18</link>
      <guid>https://dev.to/iamsopotatoecoder/tinyload-v7-veh-page-fault-decryption-and-a-fully-encrypted-overlay-whats-new-in-tinyload-v70-2f18</guid>
      <description>&lt;p&gt;TinyLoad v7 is out. if you haven't seen it before — it's a PE&lt;br&gt;
packer for Windows. one .cpp file, no dependencies, MIT.&lt;br&gt;
repo here.&lt;br&gt;
v6 killed the switch statement and split the opcode table. v7 goes&lt;br&gt;
after the two things that were still exploitable — a readable&lt;br&gt;
overlay and a dumpable memory image. both are gone now.&lt;/p&gt;

&lt;p&gt;VEH page-fault decryption&lt;br&gt;
this is the headline feature. instead of decrypting the payload&lt;br&gt;
into a normal RWX allocation and jumping to it, v7 maps every&lt;br&gt;
section as PAGE_NOACCESS after loading:&lt;br&gt;
cpp// after mapping sections, mark them inaccessible&lt;br&gt;
for each section:&lt;br&gt;
    VirtualProtect(page, 0x1000, PAGE_NOACCESS, &amp;amp;old);&lt;br&gt;
    vehPageXor(page, 0x1000, g_vehKey, (uintptr_t)page);&lt;br&gt;
a vectored exception handler catches every ACCESS_VIOLATION,&lt;br&gt;
checks if the fault address is inside the payload range, decrypts&lt;br&gt;
the faulting page, and marks it PAGE_EXECUTE_READWRITE:&lt;br&gt;
cppLONG CALLBACK vehHandler(EXCEPTION_POINTERS* ex) {&lt;br&gt;
    uintptr_t fault = ex-&amp;gt;ExceptionRecord-&amp;gt;ExceptionInformation[1];&lt;br&gt;
    uintptr_t pg = fault &amp;amp; ~(uintptr_t)0xFFF;&lt;br&gt;
    VirtualProtect((LPVOID)pg, 0x1000, PAGE_READWRITE, &amp;amp;old);&lt;br&gt;
    vehPageXor((BYTE*)pg, 0x1000, g_vehKey, pg);&lt;br&gt;
    VirtualProtect((LPVOID)pg, 0x1000, PAGE_EXECUTE_READWRITE, &amp;amp;old);&lt;br&gt;
    // add to LRU cache...&lt;br&gt;
    return EXCEPTION_CONTINUE_EXECUTION;&lt;br&gt;
}&lt;br&gt;
a background watchdog thread scans the LRU cache every 50ms and&lt;br&gt;
re-encrypts any page that hasn't been touched in 200ms, setting it&lt;br&gt;
back to PAGE_NOACCESS. the cache holds 256 slots with&lt;br&gt;
thread-safe eviction.&lt;br&gt;
the result is that at any point in time only the currently&lt;br&gt;
executing page (and recently accessed pages) are ever in plaintext&lt;br&gt;
in memory. a full memory dump gives you at most a few hot pages.&lt;br&gt;
the rest is encrypted noise.&lt;br&gt;
each page uses a unique key: g_vehKey ^ (addr &amp;gt;&amp;gt; 12) — so even&lt;br&gt;
if you recover one page's key you can't decrypt the others.&lt;/p&gt;

&lt;p&gt;fully encrypted overlay&lt;br&gt;
in v6 the overlay was still partially readable — the signature,&lt;br&gt;
metadata fields, and VM bytecode were sitting in the clear. v7&lt;br&gt;
encrypts everything with per-file stub-derived keys:&lt;br&gt;
[stub binary]&lt;br&gt;
[encrypted Tail struct]   — sig, origSz, packSz, flags, dispKey all XOR'ed&lt;br&gt;
[encrypted VM bytecode]   — full bytecode blob XOR'ed with stubKey stream&lt;br&gt;
[chunk 0][junk][chunk 1][junk][chunk 2][junk][chunk 3]  — payload split into 4&lt;br&gt;
[encrypted tail offset]   — 4-byte EOF pointer XOR'ed&lt;br&gt;
the stubKey is derived from a hash of the stub binary itself —&lt;br&gt;
so it's different for every build and can't be precomputed without&lt;br&gt;
the stub. even the 4-byte tail offset at the very end of the file&lt;br&gt;
is encrypted, so the overlay start address isn't readable from a&lt;br&gt;
hex dump.&lt;br&gt;
zero-filler interleaving pads the overlay at a 3:1 ratio giving&lt;br&gt;
roughly 6.73 bits/byte entropy, making it blend visually with&lt;br&gt;
normal PE sections rather than standing out as high-entropy packed&lt;br&gt;
data.&lt;/p&gt;

&lt;p&gt;canary integrity chain&lt;br&gt;
8 canary values are embedded in the VM bytecode. each one is&lt;br&gt;
checked in sequence — if any check fails, a corruption mask&lt;br&gt;
escalates from 1 bit to 8 bits and gets XORed into the decrypted&lt;br&gt;
plaintext. tampering with the overlay produces garbage output&lt;br&gt;
rather than a clean crash, which makes it harder to know if your&lt;br&gt;
modification worked.&lt;/p&gt;

&lt;p&gt;payload chunk splitting&lt;br&gt;
the payload is split into 4 chunks with random junk gaps of&lt;br&gt;
128–640 bytes between them. automated overlay scanners that look&lt;br&gt;
for a contiguous payload region won't find one.&lt;/p&gt;

&lt;p&gt;usage&lt;br&gt;
v7 adds a new --veh flag:&lt;br&gt;
TinyLoad.exe --i myapp.exe --vm --c --veh&lt;br&gt;
build from source:&lt;br&gt;
g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s&lt;br&gt;
grab the binary from&lt;br&gt;
releases.&lt;/p&gt;

&lt;p&gt;what's left&lt;br&gt;
the main thing still missing is payload stub dependency — making&lt;br&gt;
the payload actively call back into the stub at runtime so a&lt;br&gt;
reconstructed dump is broken without the stub mapped. that's v8.&lt;br&gt;
if you find files it breaks on, open an issue. star helps a lot ❤️&lt;br&gt;
repo: github.com/iamsopotatoe-coder/TinyLoad&lt;br&gt;
blog: iamsopotatoe-coder.github.io/TinyLoad/#blog&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>security</category>
      <category>windows</category>
      <category>lowlevel</category>
    </item>
    <item>
      <title>TinyLoad v6 — split opcode tables, encrypted dispatch, and control flow flattening</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Tue, 26 May 2026 11:40:13 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/tinyload-v6-split-opcode-tables-encrypted-dispatch-and-control-flow-flattening-mkm</link>
      <guid>https://dev.to/iamsopotatoecoder/tinyload-v6-split-opcode-tables-encrypted-dispatch-and-control-flow-flattening-mkm</guid>
      <description>&lt;p&gt;TinyLoad v6 is out. if you haven't seen it before — it's a PE packer for Windows. one &lt;code&gt;.cpp&lt;/code&gt; file, no dependencies, MIT. &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;repo here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;v5 hardened the stub itself with encrypted strings, IAT wiping, and opmap obfuscation. v6 goes after the two biggest remaining fingerprints: the switch statement in the VM interpreter, and the single contiguous opcode table. both are gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  the problem with a switch statement
&lt;/h2&gt;

&lt;p&gt;every version of TinyLoad up to v5 had a &lt;code&gt;vmRun&lt;/code&gt; function with a giant &lt;code&gt;switch(op)&lt;/code&gt; dispatching 28 opcode handlers. this is the most fingerprint-able thing in any custom VM — disassemblers recognise the pattern immediately, and once you have the handler layout you can reconstruct what each opcode does without ever running the code.&lt;/p&gt;

&lt;p&gt;v6 replaces it entirely with a &lt;strong&gt;computed-goto dispatch table&lt;/strong&gt; using GCC's &lt;code&gt;&amp;amp;&amp;amp;label&lt;/code&gt; extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_tbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;  &lt;span class="c1"&gt;// file scope, filled at runtime&lt;/span&gt;

&lt;span class="c1"&gt;// pack time: read live label addresses, encrypt with random key&lt;/span&gt;
&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;dispKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng3&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;s_tbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;s_tbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;dispKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// store dispKey in tail&lt;/span&gt;

&lt;span class="c1"&gt;// runtime: decrypt and jump&lt;/span&gt;
&lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decodeOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;s_tbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;dispKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the label addresses are never plaintext in the packed binary. the packer reads them from its own running process, XORs them with a random key, and stores the result in the tail struct. the packed stub decrypts and recomputes the table at runtime. there's no static jump table to dump, no switch to fingerprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  split opcode decoder
&lt;/h2&gt;

&lt;p&gt;v5 had a single 32-entry opmap, encrypted with one FNV-derived key. one key crack = full opcode table.&lt;/p&gt;

&lt;p&gt;v6 splits 28 opcodes across &lt;strong&gt;four independent 8-entry subtables&lt;/strong&gt;, each with its own key derived from different slices of the payload and VM bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 4 subtables, each XOR-encrypted with independent key&lt;/span&gt;
&lt;span class="n"&gt;BYTE&lt;/span&gt; &lt;span class="n"&gt;sub0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sub1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sub2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sub3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// key for each subtable derived from different data slices&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;k0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;packSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0..7&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vmCodeSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0..7&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmCodeSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;origSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8..15&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;k3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origSz&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;packSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vmCodeSz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8..15&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sub0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;k0&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;sub1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cracking one subtable key reveals at most 8 of 28 opcodes. the other 20 are behind three different keys derived from different data. opcodes are encoded as &lt;code&gt;(subtable_index &amp;lt;&amp;lt; 3) | slot&lt;/code&gt; — every packed file gets a completely different encoding across all four tables.&lt;/p&gt;




&lt;h2&gt;
  
  
  staged entry point and control flow noise
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tryRun&lt;/code&gt; and &lt;code&gt;runInMem&lt;/code&gt; are both broken into stages dispatched through function pointer tables — no linear flow to trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tryRun: s_chk → s_ld → s_prs → s_vm → s_dc → s_ex
runInMem: sp_hdr → sp_map → sp_reloc → sp_import → sp_go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on top of that, &lt;code&gt;noiseDecrypt()&lt;/code&gt; fires at every stage transition and every 64 VM iterations — calling &lt;code&gt;sdec2&lt;/code&gt; on throwaway buffers with random keys. in a dynamic trace the real string decryption calls (the IAT hook lookups) are buried in identical-looking noise. you can't tell which call matters without executing every path.&lt;/p&gt;




&lt;h2&gt;
  
  
  other stuff in v6
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;full resource cloning&lt;/strong&gt; — switched from hardcoded &lt;code&gt;RT_ICON&lt;/code&gt;/&lt;code&gt;RT_VERSION&lt;/code&gt;/&lt;code&gt;RT_MANIFEST&lt;/code&gt; to &lt;code&gt;EnumResourceTypesA&lt;/code&gt;, so all resource types survive packing now&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LZ compressor fix&lt;/strong&gt; — found a hash-chain self-loop bug that was silently degrading match quality, compression improved ~2% across tested files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PE loader hardening&lt;/strong&gt; — SizeOfBlock underflow guard, reloc bounds validation, negative e_lfanew rejection, import thunk iteration cap, better error propagation throughout&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  current usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TinyLoad.exe --i myapp.exe --vm --c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;build from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;grab the binary from &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  what's left for v7
&lt;/h2&gt;

&lt;p&gt;the one criticism from the RE community that's still open — making a dump worthless. right now once the payload is decrypted in RAM it's self-contained. making it call back into the stub at runtime so a dump without the stub is broken is the hard problem v7 needs to solve.&lt;/p&gt;

&lt;p&gt;also thinking about more opaque predicate variety and bytecode encryption on top of the split subtables.&lt;/p&gt;

&lt;p&gt;if you find files it breaks on, open an issue. star helps a lot ❤️&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;br&gt;
blog: &lt;a href="https://iamsopotatoe-coder.github.io/TinyLoad/#blog" rel="noopener noreferrer"&gt;iamsopotatoe-coder.github.io/TinyLoad/#blog&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;don't use this to pack malware — legitimate use only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>windows</category>
      <category>packer</category>
      <category>security</category>
    </item>
    <item>
      <title>TinyLoad v5 — encrypted strings, opmap obfuscation, and IAT wiping</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Tue, 19 May 2026 12:02:12 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/tinyload-v5-encrypted-strings-opmap-obfuscation-and-iat-wiping-26fd</link>
      <guid>https://dev.to/iamsopotatoecoder/tinyload-v5-encrypted-strings-opmap-obfuscation-and-iat-wiping-26fd</guid>
      <description>&lt;p&gt;TinyLoad v5 is out. if you haven't seen the project before — it's a PE packer for Windows. you give it an &lt;code&gt;.exe&lt;/code&gt;, it compresses and VM-encrypts it into a self-extracting stub that runs the original entirely in RAM. one &lt;code&gt;.cpp&lt;/code&gt; file, no dependencies, MIT. &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;repo here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;v4 added opaque predicates, anti-debug, and section scrambling. v5 is about hardening the stub itself — the code that actually runs when the packed exe launches. three main additions: &lt;strong&gt;encrypted API strings&lt;/strong&gt;, &lt;strong&gt;content-derived opmap obfuscation&lt;/strong&gt;, and &lt;strong&gt;IAT wiping post-load&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  quick recap
&lt;/h2&gt;

&lt;p&gt;when you run a TinyLoad packed exe, the stub spins up a custom 32-opcode VM interpreter, executes a decryption bytecode program against the payload, then manually maps the original PE into memory and runs it. every packed file gets a randomly shuffled opcode table so the bytecode looks different every time.&lt;/p&gt;

&lt;p&gt;v5 makes the stub itself harder to analyse statically.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. encrypted API strings
&lt;/h2&gt;

&lt;p&gt;in v4 the DLL and function names the stub needs — &lt;code&gt;kernel32.dll&lt;/code&gt;, &lt;code&gt;GetModuleHandleA&lt;/code&gt;, &lt;code&gt;VirtualAlloc&lt;/code&gt; etc — were sitting as plaintext strings in the binary. any static analysis tool would immediately see what APIs the stub calls just from strings view.&lt;/p&gt;

&lt;p&gt;v5 XOR-encrypts all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt; &lt;span class="n"&gt;_ed_k32&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mh"&gt;0x3A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x3A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x3A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x6A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x3E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x30&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt; &lt;span class="n"&gt;_ed_gmha&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mh"&gt;0x70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x5D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x4D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x5A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x2C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x07&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt; &lt;span class="n"&gt;_ed_gpa&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mh"&gt;0x06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x2D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x2E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x39&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x3E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x3D&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each one decrypts at runtime with a rolling XOR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sdec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no readable strings in the binary anymore. strings view on the packed output is clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. opmap obfuscation via FNV hash
&lt;/h2&gt;

&lt;p&gt;this one directly addresses feedback from the r/ReverseEngineering community who pointed out that the opmap decode table was sitting plaintext right behind the stub — effectively a silver platter for static analysis.&lt;/p&gt;

&lt;p&gt;v5 derives a per-file XOR mask for the opmap using FNV-1a hash, seeded from the file's own content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;xorOpmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;opmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Tail&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x811C9DC5u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mh"&gt;0x01000193u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origSz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packSz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vmCodeSz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;vmLim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vmCodeSz&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vmCodeSz&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;vmLim&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmCode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;payLim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packSz&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packSz&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;payLim&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NUM_OPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;opmap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the hash feeds on origSz, packSz, vmCodeSz, and the first 32 bytes of both the VM bytecode and the payload. the resulting mask is different for every single packed file — you can't extract the opmap from one sample and apply it to another. the stub runs xorOpmap at unpack time to recover the real table before passing it to the VM.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. IAT wiping post-load
&lt;/h2&gt;

&lt;p&gt;after the stub manually maps the packed PE into memory and resolves all its imports, v5 zeroes out the import structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// kill recovery&lt;/span&gt;
&lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OriginalFirstThunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataDirectory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;IMAGE_DIRECTORY_ENTRY_IMPORT&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataDirectory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;IMAGE_DIRECTORY_ENTRY_IMPORT&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;once imports are resolved the IAT entries aren't needed anymore. zeroing them means if someone dumps the process from memory after load, the import directory is gone — no DLL names, no function names, no reconstruction path from the dump alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. dead code and junk VM instructions
&lt;/h2&gt;

&lt;p&gt;v5 also adds dead code functions in the stub (&lt;code&gt;__attribute__((used))&lt;/code&gt; to force the compiler to keep them despite &lt;code&gt;-O2&lt;/code&gt;) and junk &lt;code&gt;NOP&lt;/code&gt; and self-mov instructions scattered through the VM bytecode to inflate and confuse disassembly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;NOP_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// junk&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;MOV_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// junk — mov r6, r6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;minor individually but they add noise on top of everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  current usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;TinyLoad&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt; &lt;span class="na"&gt;--i &lt;/span&gt;&lt;span class="kd"&gt;myapp&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt; &lt;span class="na"&gt;--vm --c
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;build from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;g++ &lt;span class="nt"&gt;-o&lt;/span&gt; TinyLoad.exe TinyLoad.cpp &lt;span class="nt"&gt;-static&lt;/span&gt; &lt;span class="nt"&gt;-O2&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or grab the binary from &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;if you run into files it doesn't pack correctly, open an issue. and if you find it useful, a star helps a lot ❤️&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;br&gt;
blog + changelogs: &lt;a href="https://iamsopotatoe-coder.github.io/TinyLoad/#blog" rel="noopener noreferrer"&gt;iamsopotatoe-coder.github.io/TinyLoad/#blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>security</category>
      <category>windows</category>
      <category>lowlevel</category>
    </item>
    <item>
      <title>i added opaque predicates, anti-debug, and section obfuscation to my PE packer published</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Fri, 15 May 2026 08:21:29 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/i-added-opaque-predicates-anti-debug-and-section-obfuscation-to-my-pe-packerpublished-17bd</link>
      <guid>https://dev.to/iamsopotatoecoder/i-added-opaque-predicates-anti-debug-and-section-obfuscation-to-my-pe-packerpublished-17bd</guid>
      <description>&lt;p&gt;TinyLoad v4 is out — here's what i added and why it actually matters: &lt;/p&gt;

&lt;p&gt;so TinyLoad v4 just dropped. if you don't know TinyLoad — it's my open-source PE packer for Windows. you throw an exe at it, it compresses and encrypts it with a custom VM, and spits out a self-extracting stub that runs the original entirely in RAM. no temp files, no installer, nothing written to disk. one &lt;code&gt;.cpp&lt;/code&gt; file, no dependencies. &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;here's the repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;v3 was already a decent jump because that's when the custom VM came in — randomised opcode shuffling so every packed file speaks a different instruction set. v4 is more focused. three specific additions that each solve a different analysis problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM opaque predicates&lt;/li&gt;
&lt;li&gt;anti-debug checks&lt;/li&gt;
&lt;li&gt;PE section name obfuscation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;let me actually explain each one instead of just listing them.&lt;/p&gt;




&lt;h2&gt;
  
  
  opaque predicates — confusing static analysis
&lt;/h2&gt;

&lt;p&gt;when someone's trying to reverse a packed binary statically, they're usually building a control flow graph — figuring out which code actually runs and in what order. an opaque predicate is a branch where the outcome is predetermined (always taken, or never taken) but looks like it could go either way without executing anything.&lt;/p&gt;

&lt;p&gt;in v4 the VM bytecode that gets generated for decryption starts with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// load two constants into registers&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;LDI_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;e64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x1337&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;LDI_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;e64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x1338&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// compare them — r8 will always be 1 since 0x1337 &amp;lt; 0x1338&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;CMP_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// jump if nonzero — always jumps&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;JNZ_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="n"&gt;eR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;opqPatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;e32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// this HLT is dead code — never reached&lt;/span&gt;
&lt;span class="n"&gt;eOp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;HLT_I&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// real decryption loop starts here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the halt never actually executes. but a static analyser that doesn't evaluate the constants just sees a branch with two possible destinations — one being an immediate halt before any decryption happens. it has to either run the code to know the truth, or assume both paths are possible.&lt;/p&gt;

&lt;p&gt;what makes this play nicely with the rest of TinyLoad is that opcodes are randomly shuffled at pack time. so &lt;code&gt;0x1337&lt;/code&gt; and &lt;code&gt;0x1338&lt;/code&gt; appear as completely different byte values in every packed file. no two samples look the same, which makes pattern matching across samples a lot harder too.&lt;/p&gt;




&lt;h2&gt;
  
  
  anti-debug — making dynamic analysis awkward
&lt;/h2&gt;

&lt;p&gt;static analysis not working? fine, let's just run it in a debugger. v4 adds two checks before the loader does anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;isDebugged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsDebuggerPresent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;CheckRemoteDebuggerPresent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetCurrentProcess&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IsDebuggerPresent&lt;/code&gt; catches the obvious stuff — x64dbg, WinDbg attached locally. &lt;code&gt;CheckRemoteDebuggerPresent&lt;/code&gt; goes a level deeper and catches kernel debuggers and remote sessions that the first check doesn't see.&lt;/p&gt;

&lt;p&gt;if either one fires, the loader just returns false silently. no error, no crash, no messagebox. the packed exe runs and does absolutely nothing. that kind of silent failure is more disorienting than a visible error because it's not immediately obvious &lt;em&gt;why&lt;/em&gt; nothing happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  section obfuscation — breaking heuristic scanners
&lt;/h2&gt;

&lt;p&gt;PE packers often get detected not from what the code does but from what the file &lt;em&gt;looks like&lt;/em&gt; — and section names are one of the things scanners latch onto. if a packer consistently leaves behind a section called &lt;code&gt;.tinyld&lt;/code&gt; or whatever the compiler decided to name things, that's a trivial signature.&lt;/p&gt;

&lt;p&gt;after writing the packed output, v4 goes back into the PE headers and renames every section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;scrambleSections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IMAGE_NT_HEADERS64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_NT_HEADERS64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dos&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;e_lfanew&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;IMAGE_SECTION_HEADER&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IMAGE_FIRST_SECTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;".text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".rdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".bss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".idata"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;FileHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfSections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;strncpy&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the payload itself lives in an overlay &lt;em&gt;after&lt;/em&gt; the sections, not inside any of them, so renaming changes nothing about how the exe actually runs. but from the outside the file looks like any other plain Windows binary — &lt;code&gt;.text&lt;/code&gt;, &lt;code&gt;.data&lt;/code&gt;, &lt;code&gt;.rdata&lt;/code&gt;. nothing to fingerprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  putting it all together
&lt;/h2&gt;

&lt;p&gt;grab the binary from &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt; or build it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;g++ &lt;span class="nt"&gt;-o&lt;/span&gt; TinyLoad.exe TinyLoad.cpp &lt;span class="nt"&gt;-static&lt;/span&gt; &lt;span class="nt"&gt;-O2&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;usage hasn't changed much from v3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TinyLoad.exe --i myapp.exe --vm --c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--vm&lt;/code&gt; is the custom VM encryption (now with the opaque predicates and anti-debug baked in). &lt;code&gt;--c&lt;/code&gt; is LZ77 compression. compression runs first, then VM encryption goes on top — so the compressed stream's patterns get hidden too. you need at least one of the two flags.&lt;/p&gt;




&lt;p&gt;if you find files it breaks on, open an issue. and if you use it, a star really does help ❤️&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;br&gt;
blog + changelogs: &lt;a href="https://iamsopotatoe-coder.github.io/TinyLoad/#blog" rel="noopener noreferrer"&gt;iamsopotatoe-coder.github.io/TinyLoad/#blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>security</category>
      <category>lowlevel</category>
      <category>windows</category>
    </item>
    <item>
      <title>How I load an exe directly into memory without touching disk — manual PE mapping</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Wed, 06 May 2026 12:15:13 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/how-i-load-an-exe-directly-into-memory-without-touching-disk-manual-pe-mapping-24le</link>
      <guid>https://dev.to/iamsopotatoecoder/how-i-load-an-exe-directly-into-memory-without-touching-disk-manual-pe-mapping-24le</guid>
      <description>&lt;p&gt;most people think running an exe means writing it to disk first. it doesn't. &lt;/p&gt;

&lt;p&gt;as part of building TinyLoad, a Windows PE packer, I had to write a PE loader that maps an executable directly into memory and runs it without ever creating a file. here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  what is a PE file
&lt;/h2&gt;

&lt;p&gt;PE (Portable Executable) is the format Windows uses for .exe and .dll files. it's basically a structured blob with a header describing how to load it, followed by sections containing code, data, resources etc.&lt;/p&gt;

&lt;p&gt;to run a PE file manually you have to do what the Windows loader does — but yourself, in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  step 1: parse the headers
&lt;/h2&gt;

&lt;p&gt;every PE starts with a DOS header, then an NT header. the NT header tells you everything you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SizeOfImage&lt;/code&gt; — how much memory to allocate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ImageBase&lt;/code&gt; — where the linker expected the binary to live&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AddressOfEntryPoint&lt;/code&gt; — where to jump to start execution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SizeOfHeaders&lt;/code&gt; — how much of the front to copy as-is
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;IMAGE_DOS_HEADER&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_DOS_HEADER&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;IMAGE_NT_HEADERS64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_NT_HEADERS64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dos&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;e_lfanew&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  step 2: allocate memory and copy sections
&lt;/h2&gt;

&lt;p&gt;allocate a block of memory the size of the image, then copy the headers in. after that, iterate the section table and copy each section to its virtual address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VirtualAlloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOfImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MEM_COMMIT&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;MEM_RESERVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PAGE_EXECUTE_READWRITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOfHeaders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;IMAGE_SECTION_HEADER&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IMAGE_FIRST_SECTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;FileHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfSections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;SizeOfRawData&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;PointerToRawData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;sect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;SizeOfRawData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  step 3: fix relocations
&lt;/h2&gt;

&lt;p&gt;the linker assumed the binary would load at &lt;code&gt;ImageBase&lt;/code&gt;. if it lands somewhere else (which it usually does since ASLR), every absolute address in the binary is wrong by &lt;code&gt;delta = actual_base - preferred_base&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;the relocation table tells you exactly which addresses need fixing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageBase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_BASE_RELOCATION&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;relDir&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;WORD&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORD&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SizeOfBlock&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_BASE_RELOCATION&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IMAGE_REL_BASED_DIR64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_BASE_RELOCATION&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SizeOfBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  step 4: resolve imports
&lt;/h2&gt;

&lt;p&gt;the import directory tells you which DLLs the binary needs and which functions to load from each. iterate the import descriptors, load each DLL, resolve each function by name or ordinal, and write the function addresses into the import address table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_IMPORT_DESCRIPTOR&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;impDir&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;VirtualAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HMODULE&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoadLibraryA&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;thunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_THUNK_DATA64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;FirstThunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_THUNK_DATA64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OriginalFirstThunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddressOfData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_SNAP_BY_ORDINAL64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;thunk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GetProcAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_IMPORT_BY_NAME&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddressOfData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;thunk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;GetProcAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;thunk&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  step 5: jump to entry point
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;EntryPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WINAPI&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
&lt;span class="n"&gt;EntryPoint&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;OptionalHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddressOfEntryPoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that's it. the exe runs directly from the allocated memory block, no file on disk, no Windows loader involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  where this is used in TinyLoad
&lt;/h2&gt;

&lt;p&gt;TinyLoad packs your exe with LZ77 compression and VM encryption. when the packed stub runs, it decrypts and decompresses the original exe in memory, then calls this loader directly. the original exe never exists as a file — it goes straight from encrypted bytes to running process.&lt;/p&gt;

&lt;p&gt;full source (single .cpp file, no deps): &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;https://github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>programming</category>
      <category>security</category>
      <category>packing</category>
    </item>
    <item>
      <title>I built a PE packer with a custom VM that randomizes its own instruction set every build — single .cpp file, no deps</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Tue, 05 May 2026 13:26:48 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/i-built-a-pe-packer-with-a-custom-vm-that-randomizes-its-own-instruction-set-every-build-single-2285</link>
      <guid>https://dev.to/iamsopotatoecoder/i-built-a-pe-packer-with-a-custom-vm-that-randomizes-its-own-instruction-set-every-build-single-2285</guid>
      <description>&lt;p&gt;PE packers are one of those things that sound complex until you actually build one. then they sound complex again but for different reasons.&lt;/p&gt;

&lt;p&gt;I built TinyLoad — a Windows PE packer in a single C++ file with no external dependencies. v3 just shipped and the main change is replacing rolling XOR with a custom virtual machine for decryption.&lt;/p&gt;

&lt;h2&gt;
  
  
  what it does
&lt;/h2&gt;

&lt;p&gt;you give it an exe. it spits out a new exe that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;stores your original exe compressed and VM-encrypted inside itself&lt;/li&gt;
&lt;li&gt;at runtime, spins up a tiny VM interpreter, runs the decryption bytecode, and loads the result directly into RAM via manual PE mapping&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the packed exe never writes anything to disk. the original binary just materializes in memory and runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  why a VM instead of XOR
&lt;/h2&gt;

&lt;p&gt;rolling XOR is trivially broken. an analyst sets a breakpoint on &lt;code&gt;VirtualAlloc&lt;/code&gt;, runs the stub, and dumps the decrypted payload from memory. takes about 30 seconds. the decryption loop is native x86 and immediately recognizable to any disassembler.&lt;/p&gt;

&lt;p&gt;the VM changes the problem completely. instead of decrypting with native x86 instructions, the stub runs a custom bytecode interpreter. the decryption logic is stored as VM bytecode — not x86. there's no native decryption loop to find.&lt;/p&gt;

&lt;p&gt;more importantly: the opcode table is randomly shuffled at pack time and baked into the binary. every packed file has a completely different instruction set. an analyst can't reuse their work across builds. they have to reverse the interpreter first, then figure out what the bytecode is doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  how the VM works
&lt;/h2&gt;

&lt;p&gt;20 opcodes: loads, moves, arithmetic, bitwise ops, memory read/write, jumps, halt. nothing exotic.&lt;/p&gt;

&lt;p&gt;at pack time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate a random permutation of the 20 opcode IDs&lt;/li&gt;
&lt;li&gt;emit the decryption program using the shuffled opcode table&lt;/li&gt;
&lt;li&gt;encrypt the payload with the matching key&lt;/li&gt;
&lt;li&gt;store: opmap + bytecode + encrypted payload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read the opmap from the binary&lt;/li&gt;
&lt;li&gt;run the interpreter against the bytecode&lt;/li&gt;
&lt;li&gt;the bytecode decrypts the payload byte by byte&lt;/li&gt;
&lt;li&gt;load and execute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the cipher is a 128-bit stream cipher using rotl/rotr key mixing. the keys are embedded as immediates directly inside the VM program — there are no separate key fields in the file format.&lt;/p&gt;

&lt;h2&gt;
  
  
  the compression
&lt;/h2&gt;

&lt;p&gt;custom LZ77 with hash-chain matching, 64KB sliding window, and lazy evaluation. runs on the raw PE first, then VM encryption goes on top. on a 3MB test exe it compressed down to ~878KB before encryption, so the final packed output was ~1.9MB including the ~1MB stub.&lt;/p&gt;

&lt;p&gt;PE files compress well because of their structure — repeated import patterns, padding sections, predictable headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  single file, no dependencies
&lt;/h2&gt;

&lt;p&gt;the whole thing compiles with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;g++ &lt;span class="nt"&gt;-o&lt;/span&gt; TinyLoad.exe TinyLoad.cpp &lt;span class="nt"&gt;-static&lt;/span&gt; &lt;span class="nt"&gt;-O2&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no cmake, no vcpkg, no submodules. the LZ77, VM interpreter, bytecode emitter, PE loader, and resource cloner are all in one ~400 line .cpp file.&lt;/p&gt;

&lt;h2&gt;
  
  
  usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TinyLoad.exe --i myapp.exe --vm --c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--vm&lt;/code&gt; for VM encryption, &lt;code&gt;--c&lt;/code&gt; for LZ77 compression. you need at least one.&lt;/p&gt;

&lt;h2&gt;
  
  
  what's next
&lt;/h2&gt;

&lt;p&gt;v4 is going to be PE-aware compression — preprocessing the binary structure before LZ77 to get better ratios on code sections specifically. probably also a more complex key schedule inside the VM program.&lt;/p&gt;

&lt;p&gt;source: &lt;a href="https://github.com/iamsopotatoe-coder/TinyLoad" rel="noopener noreferrer"&gt;https://github.com/iamsopotatoe-coder/TinyLoad&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pepacker</category>
      <category>compression</category>
      <category>packer</category>
      <category>virtualmachine</category>
    </item>
    <item>
      <title>Why I Built My Own Windows Installer in C#</title>
      <dc:creator>iamsopotatoe</dc:creator>
      <pubDate>Wed, 12 Nov 2025 18:39:24 +0000</pubDate>
      <link>https://dev.to/iamsopotatoecoder/why-i-built-my-own-windows-installer-in-c-5ap9</link>
      <guid>https://dev.to/iamsopotatoecoder/why-i-built-my-own-windows-installer-in-c-5ap9</guid>
      <description>&lt;p&gt;Ive been working on C# projects lately, and I kept running into the same problem: making a simple installer was way too complicated. NSIS, Inno Setup, WiX etc.. were way to complicated.&lt;/p&gt;

&lt;p&gt;So I decided to build my own: Flex Installer.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;p&gt;Downloads your app from Dropbox (GitHub support coming soon)&lt;/p&gt;

&lt;p&gt;Installs it on Windows&lt;/p&gt;

&lt;p&gt;Adds an uninstaller entry in Windows Settings&lt;/p&gt;

&lt;p&gt;Fully customizable via a config file&lt;/p&gt;

&lt;p&gt;Its simple: customize the config file to your liking and add your dropbox link and done!&lt;/p&gt;

&lt;p&gt;Its open source on GitHub: &lt;a href="https://github.com/iamsopotatoe-coder/Flex-Installer" rel="noopener noreferrer"&gt;https://github.com/iamsopotatoe-coder/Flex-Installer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still in early development but it works.&lt;/p&gt;

&lt;p&gt;Id love to hear feedback, suggestions, or ideas for features from anyone!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>csharp</category>
      <category>resources</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
