Skip to content

Instantly share code, notes, and snippets.

@mrh1997
Last active January 21, 2024 17:29
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mrh1997/717b14f5783b49ca14310419fa7f03f6 to your computer and use it in GitHub Desktop.
Save mrh1997/717b14f5783b49ca14310419fa7f03f6 to your computer and use it in GitHub Desktop.
Retrieve Windows Credential via Python
#!python3
"""
Access windows credentials
"""
from typing import Tuple
import ctypes as CT
import ctypes.wintypes as WT
CRED_TYPE_GENERIC = 0x01
LPBYTE = CT.POINTER(WT.BYTE)
LPWSTR = WT.LPWSTR
LPCWSTR = WT.LPWSTR
class CREDENTIAL_ATTRIBUTE(CT.Structure):
_fields_ = [
('Keyword', LPWSTR),
('Flags', WT.DWORD),
('ValueSize', WT.DWORD),
('Value', LPBYTE)]
PCREDENTIAL_ATTRIBUTE = CT.POINTER(CREDENTIAL_ATTRIBUTE)
class CREDENTIAL(CT.Structure):
_fields_ = [
('Flags', WT.DWORD),
('Type', WT.DWORD),
('TargetName', LPWSTR),
('Comment', LPWSTR),
('LastWritten', WT.FILETIME),
('CredentialBlobSize', WT.DWORD),
('CredentialBlob', LPBYTE),
('Persist', WT.DWORD),
('AttributeCount', WT.DWORD),
('Attributes', PCREDENTIAL_ATTRIBUTE),
('TargetAlias', LPWSTR),
('UserName', LPWSTR)]
PCREDENTIAL = CT.POINTER(CREDENTIAL)
advapi32 = CT.WinDLL('Advapi32.dll')
advapi32.CredReadA.restype = WT.BOOL
advapi32.CredReadA.argtypes = [LPCWSTR, WT.DWORD, WT.DWORD, CT.POINTER(PCREDENTIAL)]
def GetGenericCredential(name:str) -> Tuple[str, str]:
"""
Returns a Tuple of Name and Password of a Generic Windows Credential
Uses bytes in Py3 and str in Py2 for url, name and password.
"""
cred_ptr = PCREDENTIAL()
if advapi32.CredReadW(name, CRED_TYPE_GENERIC, 0, CT.byref(cred_ptr)):
username = cred_ptr.contents.UserName
cred_blob = cred_ptr.contents.CredentialBlob
cred_blob_size = cred_ptr.contents.CredentialBlobSize
cred_str = CT.string_at(cred_blob, cred_blob_size)
password = cred_str.decode('utf-16le', errors='ignore')
advapi32.CredFree(cred_ptr)
return username, password
else:
raise IOError("Failure reading credential")
def main():
name, pwd = GetGenericCredential('git:https://github.com')
print("GITHUB NAME:", name)
print("GITHUB PASSWORD:", pwd)
if __name__ == '__main__':
main()
@exhuma
Copy link

exhuma commented Jun 24, 2019

Line 42 looks dodgy:

unicodePassword = credPtr.contents.CredentialBlob[:passwordSize:2]

My instinct tells me that this is probably a byte-value representing a text encoded using something other than UTF-8. Maybe UTF-16? Using the step-size "2" and then converting the bytes blindly using the chr builtin is in that case erroneous at best, or even a security risk at worst.

If my feeling is correct, it would be better to find out which encoding is used in the credential store and then simply use credPtr.contents.CredentialBlob.decode(<encoding-name>)

@mrh1997
Copy link
Author

mrh1997 commented Jun 24, 2019

Actually this code was written for Python 2.7 ignoring Unicode. For real Unicode support there is more to do:

  • replace CreadReadA by CreadReadW and thus supporting unicode urls
  • replacing all LPTSTR by LPWSTR. This is especially important for UserName as it would be returned as unicode then
  • credPtr.contents.CredentialBlob is of type LP_c_byte. Thus you cannot use the method decode.

I updated the code to support unicode correctly (and switched to Python 3 compatibility)

@exhuma
Copy link

exhuma commented Jun 25, 2019

I'm curious as I am not familiar with the Windows API: Why is there a step-size of 2 when retrieving the value from the credentials blob?

Do you have a link to the documentation? Or was this reverse engineered?

And thanks a bunch for updating this! I'll give it a go ;)

@exhuma
Copy link

exhuma commented Jun 25, 2019

I aligned the code closer to PEP8 and made other things a bit more "pythonic" over at https://gist.github.com/exhuma/a310f927d878b3e5646dc67dfa509b42

In case you agree with the changes, you can merge them back if you like. Unfortunatly it's not possible to do PRs on gists yet.

@rolangom
Copy link

I'm using this script in a node package. See: https://github.com/rolangom/wincred

@burque505
Copy link

I used a scarcely modified version of your code, posted here, to allow automating Box.com with Robin, the RPA language.
Works great, thank you!

@apolkosnik
Copy link

A little improvement, I think...

import codecs

password = CT.string_at(cred_blob,cred_blob_size).decode('utf-16le', errors='ignore')

@mrh1997
Copy link
Author

mrh1997 commented Oct 6, 2020

@apolkosnik: Thx for this tip. I incorporated it into my code...

@HGStyle
Copy link

HGStyle commented Sep 28, 2022

Question : how can I get the Windows Session Password using Python ?

@mrh1997
Copy link
Author

mrh1997 commented Sep 28, 2022

Do you mean the current windows users password? I am pretty sure that this is not possible (neither with python nor with C). It would be a big security flaw.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment