HKLM\SYSTEM\Setup\sMarTdEpLoY – The (Static) Keys to Abusing PDQ SmartDeploy
Aug 12 2025
By: Garrett Foster • 10 min read
TL;DR: Prior to version 3.0.2046, PDQ SmartDeploy used static, hardcoded, and universal encryption keys for secure credential storage. Low-privileged users may recover and decrypt privileged credentials, such as Local Administrator or Active Directory domain-joined accounts, from the registry of managed devices or from operating system (OS) deployment files stored on deployment servers.
Introduction
PDQ SmartDeploy is a Windows OS deployment (OSD) solution with some light software deployment capabilities via agent management. The product is advertised as a more accessible SCCM-adjacent OSD product with a lower learning curve, which I can attest to. The workflow for generating a golden image is pretty straightforward and the application includes an image capture wizard to make creating a golden image relatively painless. During my testing, I had a working image in about an hour.
With a prepared image, admins may choose to set up supporting platform or application packs for hardware based driver installation and baseline software installs. Both features can be leveraged to pull from a PDQ managed repository of driver and software versions or choose to install their own custom versions. To facilitate installations, SmartDeploy uses a custom version of Answer Files.
Answer Files
Answer Files are granular XML files with a lot of configuration options that I won’t go too far in depth on, but the key things to know are that they can be configured with a lot of credentials:
- Optional attended or unattended image deployment
- Autologon credentials
- Network credentials to access the images
- AD domain join credentials to automate domain enrollment
Answer File contents
Here’s how the encrypted credentials appear in the generated XML file:
# AutoLogon Credentials
<autologon>
<autologon_enabled>true</autologon_enabled>
<autologon_username>domainadmin</autologon_username>
<autologon_domain>ludus</autologon_domain>
<autologon_password>DB63ECD3A1DAD99CE8934A18B2A8564E</autologon_password>
<autologon_count>10</autologon_count>
</autologon>
# Domain Join Credentials
<join_domain_credentials skip="0">
<username>domainjoin</username>
<domain>ludus</domain>
<password>DB63ECD3A1DAD99CE8934A18B2A8564E</password>
</join_domain_credentials>
# Network Credentials
<network_share>
<local_name>Z:</local_name>
<remote_name>\\PDQ-SD2\SDShare</remote_name>
<username>SDShareUser</username>
<domain>PDQ-SD2</domain>
<password>0C4B1854C476E3627CDC2AA3C5E4AF2B74FC8ABBDA42779FF4C1081F3A1AA9A97E15E9EB08CEE428D72B6BABFA936FDC</password>
</network_share>
Deployment Options
There are three deployment options depending on the type of boot media that’s being used:
- Boot media
- Designed for network installation or integration with WDS
- “Sneakernet” installation via USB
- Deployment packages
- Uses the SmartDeploy console for installation
- Deploy an OS image to an already managed device
- Offline deployment
- Allow install without LAN network connectivity
- Admittedly, I did not test this, so I can’t speak to it much
Credential Storage
Searching online, I couldn’t find any information on how credentials were stored while using SmartDeploy. The closest thing I found was how cloud storage provider credentials were not stored but instead used a generated auth token via app consent. That’s about it.
Luckily, the software is primarily built in C#, so finding the encryption routines wasn’t too difficult. Searching for the string “decrypt” in all the service binaries lands on the SDCommon.dll
.NET assembly with some relevant matches:
------------------------
File: ./SmartDeploy/Resources/SDCommon.dll
Type: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
Matches:
Decrypt
DecryptRijndaelSmartCOM
CreateDecryptor
set_DefaultDecryptionPassword
------------------------
SmartAssembly Obfuscation
The encryption routines can be found in the Encryption
class from the SmartDeploy.Common.Security
namespace. Taking a look at the Decrypt
method, it’s apparent there’s some type of obfuscation being used and the assembly info confirms the source code was obfuscated using Redgate’s SmartAssembly.

This was my first time dealing with SmartAssembly, so I had to do a bit of research on how to go about deobfuscating it. There are some good sources floating around, but I found this blog the most useful for understanding how to walk back the obfuscation; particularly the unicode obfuscation I was seeing. In the blog, the author mentions trying to use the tool de4dot to automate the deobfuscation, but I ran into errors doing so. Fortunately, a maintained forked version exists that has no issues deobfuscating the DLL.
Encryption Routines
When opening up the “cleaned” DLL, the unicode obfuscation is removed. The Decrypt
method uses typical AES-256-CBC encryption. There are a couple interesting details, though:
- Tries to avoid a debugger
- The AES key is decoded, encoded, then decoded again from
byte_1
- The IV is decoded from
byte_0
- The ciphertext is base64 decoded with some string replacement if it was used for XML
- The decrypted value is returned

Turns out, the variables for the AES key and IV are hardcoded into the application, which the SmartAssembly obfuscation previously hid from view.


You probably already noticed something’s not matching up. The Decrypt
method expects the ciphertext to be base64-encoded, but from the snippets from the Answer File, the ciphertext is hex encoded. There is another encryption routine that uses AES ECB that expects the ciphertext as hex. Looking at the DecryptRijndaelSmartCOM
method:

- The method uses an encryption key from
string_0
- The method sets an IV value for some reason at
byte_2
- Decrypts the ciphertext using the hardcoded key, notice the null since ECB doesn’t even use an IV
- Parses the decrypted value and strips the null bytes used for padding
The encryption key is hardcoded and visible in the original obfuscated DLL and decryption of the hex encoded ciphertext is trivial.


Credential Distribution
Now that we understand how credentials are encrypted in the Answer Files, let’s dig into how the various methods are distributed during OS deployment (and how they may be recovered).
Windows Deployment Services (WDS) Integration
The SmartDeploy integration with WDS is straightforward and the process is documented well. Prior to importing the image to WDS, admins must first create a WDS deployment package. During this process, admins may choose to configure platform and application packages as well as application packs to support unattended PXE boot installation of the image.

Once built, it’s as simple as following the import guide for the generated .WIM image file to add to the WDS server. Once this stage is complete, any authenticated user will have access to the image file. This is due to the default configuration for the WDS RemoteInstallation (REMINST) share to grant Authenticated Users read access to the share and its contents.

If WDS is integrated with Active Directory, a ConnectionPoint object is published as a child object to the WDS server’s machine account. This makes it trivial to narrow down computers on the network configured with this role.
Depending on how the admins have configured WDS, you’ll potentially come across a .WIM file stored at:
- \\targethost\REMINST\Images
- \\targethost\REMINST\Boot\{arch}\Images

From there, it’s as simple as opening the .WIM with a tool like 7zip to extract the Answer File and recover any encrypted credentials that may be present.

Console Deployment
Similar to the WDS deployment requirements, admins will need to configure a deployment package when using the SmartDeploy console installation method. After that, console deployment is as simple as selecting a managed target computer from the Computer Management menu and clicking “Deploy”. Shortly after, the target system will enter the PXE boot environment and begin deployment of the OS.

On the freshly deployed OS, the registry key at Computer\HKEY_LOCAL_MACHINE\SYSTEM\Setup\SmartDeploy contains the credentials used to support deploying the OS including the Local Administrator credentials set during the initial golden image capture.

The registry key allows low-privileged users to read, and therefore decrypt, the stored values which could lead to privilege escalation on the local or remote hosts. As far as I can tell, based on observations in client environments, these credentials persist indefinitely.

Cloud Storage
SmartDeploy also includes integrations for cloud hosted image storage deployment.

I haven’t personally tested each provider, but the overall deployment process is similar to the previous methods. You’ll need to have a prepared image file that will be stored in the cloud provider and a credential will be encrypted and stored in the corresponding Answer File. The permission consents are pretty broad depending on your point of view. If nothing else, it’s a cloud credential that introduces hybrid attack paths..
Here are the permissions for OneDrive:

And for Google:

Fortunately, the account credentials being used to provide SmartDeploy with access to the various cloud providers aren’t stored or replicated; however, the generated token is. Just like the others, it can be decrypted granting all of the above permissions to anyone with access to the Answer Files.
SmartDeploy Privilege Escalation
Returning to the original decryption routine, I highlighted that the method expects base64-encoded ciphertext. On the SmartDeploy host server, there are configuration-related .JSON files that contain base64-encoded blobs stored at C:\Program Files\SmartDeploy\SmartDeploy\Resources\Configuration. Of particular interest is the auth.json file that contains a base64 blob.

Decrypting this blob reveals JSON for credential material of users that have signed in with their SmartDeploy credentials which are then associated with the user’s signed in local or AD user account. On at least one previous assessment, we’ve leveraged credentials stored in this blob to gain administrative control of the SmartDeploy application and therefore control of all managed devices. Furthermore, the SmartDeploy user accounts are supposed to be associated with a valid license to authenticate to the service. This was not the case, as the account we used was several years old, not associated with a license, and the user didn’t even work for our client anymore.
The decryption proof-of-concept scripts are available here.
Conclusion
- Refer to PDQ’s release notes for guidance on installing version 3.0.2046.
- Be aware of the risks that persist with domain-joined accounts and object ownership. Object ownership in AD is a complicated subject. I strongly encourage you to reference Jim Sykora’s white paper Owned or Pwnd? and Chris Thompson’s blog Do You Own Your Permissions, or Do Your Permissions Own You? for guidance and strategies on identifying and mitigating the risks associated with these types of accounts.
- Use LAPS for local administrator password management; otherwise, ensure the Local Administrator password used for the image is different from anything in production.
Disclosure Timeline
2025-04-28 – Initial disclosure to PDQ
2025-04-29 – PDQ confirmed receipt
2025-04-30 – Updated disclosure document with additional information
2025-05-07 – Followed up on update
2025-05-07 – PDQ confirmed receipt of the update, indicated they were aware of the behavior
2025-05-21 – PDQ responded the issue required interactive access to the SmartDeploy console which appeared to disregard the client issues
2025-05-21 – Followed up to stress the severity and to ensure understanding of the initial disclosure
2025-05-29 – PDQ responded they agreed with the security concerns and were working on a fix
2025-05-29 – CVE assignment requested with MITRE
2025-06-30 – Checked in to ensure a patch would be available by disclosure date
2025-07-01 – PDQ confirmed patches would be in place by the disclosure date
2025-07-11 – MITRE assigned CVE-2025-52094 and CVE-2025-52095
2025-07-16 – PDQ requested an extension to the disclosure timeline
2025-08-07 – PDQ released patched version 3.0.2046
2025-08-12 – Public disclosure