-
-
Save exhuma/a310f927d878b3e5646dc67dfa509b42 to your computer and use it in GitHub Desktop.
| #!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 | |
| password_as_list = [int.from_bytes(cred_blob[pos:pos+2], 'little') | |
| for pos in range(0, cred_blob_size, 2)] | |
| password = ''.join(map(chr, password_as_list)) | |
| 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() |
Thanks for the update. I merged your changes
the fact you're using chr means you can't decode emoji, and other valid things for a password.
Is there a reason you moved away from the 'source' material, and didn't want to use the decode that can handle those?
python cred_str = ct.string_at(cred_blob, cred_blob_size) password = cred_str.decode('utf-16le', errors='ignore') return Credential(username, password)
NAME: test
PASSWORD: awesomeđź«
vs
PASSWORD: Traceback (most recent call last):
File "windows_creds_interface\wincreds2.py", line 161, in
main()
File "windows_creds_interface\wincreds2.py", line 155, in main
print("PASSWORD:", result.password)
UnicodeEncodeError: 'utf-8' codec can't encode characters in position 7-8: surrogates not allowed
Windows 11
Python 3.12.1
@spdkils unfortunately I don't remember the reasons why I deviated from that. But I don't feel particularly comfortable about using unicode in passwords. I don't see a technical reason why you shouldn't allow it, but unicode has plenty of invisible control-characters that could mess with the data. Considering that there's no real intereset in ever displaying the passwords that shouldn't be an issue. Nevertheless, I feel safer not opening up that pandora's box.
Having said that, your code uses error='ignore' which modifies the resulting string if things break. Meaning that you get different data back from whatever was stored. This leads to hard to debug errors in any application code using this. I would stick to the default (i.e. not specifying the error argument). That will raise an exception which will lead you on the right track whenever it happens.
I've made a small update to set
restypeandargtypesforCredReadW(where this is setting them for the unusedCredReadA).Also fixed links to Windows documentation, and added docstrings.
https://gist.github.com/RodneyRichardson/c1049d1b92f263109428542b94dd255c