Not so SmartDeploy
Earlier this year on mgeeky’s Initial Access Guild Discord server, @almartiros posted about discovering some encrypted credentials in a .wim disk image file from a REMINST share of a server. This isn’t all that unusual but the creds were stored in what he later discovered to be a PDQ SmartDeploy Answer File and what may have been this service’s equivalent of SCCM’s Network Access Account (NAA). If you’re unfamiliar, NAAs are service accounts intended to provide network access to Windows hosts during SCCM’s operating system deployment process and are constantly granted excessive privileges – even domain administrator. Naturally, he wanted to decrypt these creds too.

If you know me you know I like endpoint management software and, having seen this myself on past engagements, I was nerd sniped. In this blog I’ll introduce what SmartDeploy is at a high level, share a POC for credential decryption, and then share the various locations the credentials are stored – including an opportunity for LPE on imaged machines.
What is SmartDeploy?
PDQ SmartDeploy first and foremost is a Windows operating system deployment (OSD) solution with software deployment capabilities. 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 to work with as painless as possible. During my testing, I had a working image in about an hour.
With a working image, admins can choose to setup supporting platform packs or applications packs for platform based drivers 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.
Answer Files
To facilitate installations, SmartDeploy uses a feature called Answer Files that are best compared to SCCM task sequences. These XML files are actually pretty granular with a lot of configuration options that I won’t go too far in depth on but the key things to know are:
- Can be configured for attended or unattended image deployment
- Platform/Application packages can be set for installation
- Can be configured with autologon credentials
- Can be configured with network credentials to access the images
- Can be configured with domain join credentials to automate domain enrollment
- Can be configured to automatically install the SmartDeploy client
- Can be configured with post deployment command line options
Answer File contents
I set up an answer file populating the settings detailed above. Here’s how the encrypted credentials appear in the generated XML file:
- AutoLogon Cred
<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 Cred
<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 despite network connectivity
- Admittedly I did not test this so I can’t speak to it much
Credential Decryption
Unlike my previous blog for PDQ Deploy/Inventory, I couldn’t find much reference to how credentials were stored using SmartDeploy. The closest thing I found was how cloud storage provider credentials were not stored but instead a token generated and cached. That’s about it.
Luckily, the software is primarily built in C# so finding the encryption routines should hopefully be easy. Searching for the string “decrypt” in all the service binaries lands on the SDCommon.dll .NET assembly with some interesting 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
Popping the DLL into dnSpy, we can find the encryption routines in the Encryption class from the SmartDeploy.Common.Security namespace. Taking a look at the Decrypt method it quickly becomes apparent there’s some type of obfuscation being used and the assembly info for the DLL 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’s 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. The author mentions trying to use the tool de4dot to automate the deobfuscation but ran into errors doing so. Fortunately, a maintained forked version exists that had no issues at all deobfuscating our DLL.

Decryption Routine
Now when opening up the “cleaned” DLL, the unicode obfuscation is removed and we can see the method and variable names. The Decrypt method turns out to use a pretty standard AES algorithm with a 256 bit key and 128 bit IV with a CBC cipher mode. There’s a couple interesting details though:
- The method looks like it tries to avoid debuggers by checking for the presence of a
SDConstants.SecretKeyand returns null if it’s missing - The AES key is derived from
byte_1 - The IV is derived from
byte_2 - The cipher 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, as well as the anti debugger SecretKey, are hardcoded into the application which the SmartAssembly obfuscation previously hid from view.


You probably already noticed something’s not quite right. The Decrypt method expects the ciphertext to be base64 encoded, but from the snippets above the encrypted blobs are stored 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
nullsince ECB doesn’t even use an IV and is part of why it’s deprecated - Parses the decrypted value and strips the null bytes used for padding
The encryption key is hardcoded and visible in the original obfuscated DLL:

With this in mind, the credentials can be decrypted using the following:
def decrypt_password(hex_ciphertext):
key_str = "8BEE8F53EDD48b3z"
key_bytes = key_str.encode('utf-8')
ciphertext = binascii.unhexlify(hex_ciphertext)
cipher = AES.new(key_bytes, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
if b'\x00' in decrypted:
decrypted = decrypted.split(b'\x00')[0]
return decrypted.decode('utf-8', errors='replace')

I’ve used multiple versions for testing the SmartDeploy software but all have at least been on the 3.0 major release. I can’t say for certain how long this key has existed in this manner but I can share that a friend of mine was able to pull archived data from assessment performed in 2022 and successfully decrypted a credential with this key. There is this in the assembly info though: [assembly: TargetPlatform("Windows7.0")].
Credential Distribution
Now that we understand how the credentials are encrypted in the Answer Files, let’s dig into how the credentials are distributed during operating system deployment.
WDS
The SmartDeploy integration with WDS is pretty straight forward and the process is documented pretty well. All things considered, the software really does simplify the OSD task when compared to SCCM. 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 a simple as following the import guide for the generated .wim to add to the WDS server. Once this stage is complete any authenticated user will have access to the .wim. The default configuration for the WDS RemoteInstallation share is to grant Authenticated Users read access to the share and subdirectories.

Also, if WDS is integrated with Active Directory, a serviceConnectionPoint object is published is published as a child object to the WDS server’s machine account object. This makes it fairly trivial to narrow down computers on the network configured with this role since that’s exactly what the clientse use it for.

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 a 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 operating system, 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 these will persist indefinitely.

Cloud Storage
So a deployment option that kinda gets missed in the documentaiton for SmartDeploy is the software’s support for cloud hosted image storage for 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 permissions the application requests seem pretty excessive to me.
Here’s OneDrive consent:

And Google:

Fortunately, the account credentials being used to provide SmartDeploy with access to the various cloud providers aren’t stored or replicated. The token being generated is though. And, just like he rest, can easily be decrypted granting all of the above permissions to anyone with access to the Answer Files.

Attacking SmartDeploy
I won’t go into too much detail but I’ll share a few notes I kept track of while I was digging into all of this.
-
Hunting down WDS servers is pretty trivial in AD like I said earlier. Something like this filter can get the job done.
(&(objectclass=connectionPoint)(netbootserver=*))This is useful for OSD credential recovery in general. -
If you compromise a host with the running SmartDeploy service, the API creds for the SmartDeploy server can be dumped from the process’s memory
- This one is b64 encoded so I bet that first key decrypts it, haven’t bothered to check
-
I’m pretty confident you could spoof the PXE boot process similar to CRED-1 and grab creds
-
If you get access to the golden image .wim the local admin creds are sitting in an XML file in the .wim that can be decrypted the same way
Defensive guidance
-
Use LAPS for local administrator password management. If you can’t, rotate the credentials away from what the golden image(s) use
-
Principal of least privilege should be enforced for the various service accounts being used
-
Be aware of the risks that persist with domain join accounts and object ownership
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