Skip to content

Instantly share code, notes, and snippets.

@exhuma
Forked from mrh1997/wincred.py
Last active September 2, 2024 15:57
Show Gist options
  • Select an option

  • Save exhuma/a310f927d878b3e5646dc67dfa509b42 to your computer and use it in GitHub Desktop.

Select an option

Save exhuma/a310f927d878b3e5646dc67dfa509b42 to your computer and use it in GitHub Desktop.
Retrieve Windows Credential via Python
"""
Access windows credentials
Based on https://gist.github.com/mrh1997/717b14f5783b49ca14310419fa7f03f6
"""
from typing import Tuple
import ctypes as ct
import ctypes.wintypes as wt
from enum import Enum
LPBYTE = ct.POINTER(wt.BYTE)
def as_pointer(cls):
"""
Class decorator which converts the class to ta ctypes pointer
:param cls: The class to decorate
:return: The class as pointer
"""
output = ct.POINTER(cls)
return output
class CredType(Enum):
"""
Enumeration for different credential types.
See https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentiala
"""
GENERIC = 0x01
DOMAIN_PASSWORD = 0x02
DOMAIN_CERTIFICATE = 0x03
DOMAIN_VISIBLE_PASSWORD = 0x04
GENERIC_CERTIFICATE = 0x05
DOMAIN_EXTENDED = 0x06
MAXIMUM = 0x07
MAXIMUM_EX = MAXIMUM + 1000
@as_pointer
class CredentialAttribute(ct.Structure):
_fields_ = [
('Keyword', wt.LPWSTR),
('Flags', wt.DWORD),
('ValueSize', wt.DWORD),
('Value', LPBYTE)]
@as_pointer
class Credential(ct.Structure):
_fields_ = [
('Flags', wt.DWORD),
('Type', wt.DWORD),
('TargetName', wt.LPWSTR),
('Comment', wt.LPWSTR),
('LastWritten', wt.FILETIME),
('CredentialBlobSize', wt.DWORD),
('CredentialBlob', LPBYTE),
('Persist', wt.DWORD),
('AttributeCount', wt.DWORD),
('Attributes', CredentialAttribute),
('TargetAlias', wt.LPWSTR),
('UserName', wt.LPWSTR)]
def get_generic_credential(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.
"""
advapi32 = ct.WinDLL('Advapi32.dll')
advapi32.CredReadA.restype = wt.BOOL
advapi32.CredReadA.argtypes = [wt.LPCWSTR, wt.DWORD, wt.DWORD, Credential]
cred_ptr = Credential()
if advapi32.CredReadW(name, CredType.GENERIC.value, 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 = get_generic_credential('foobar')
print("GITHUB NAME:", name)
print("GITHUB PASSWORD:", pwd)
if __name__ == '__main__':
main()
@RodneyRichardson
Copy link

I've made a small update to set restype and argtypes for CredReadW (where this is setting them for the unused CredReadA).

Also fixed links to Windows documentation, and added docstrings.

https://gist.github.com/RodneyRichardson/c1049d1b92f263109428542b94dd255c

@exhuma
Copy link
Author

exhuma commented Jul 22, 2021

Thanks for the update. I merged your changes

@spdkils
Copy link

spdkils commented Jan 21, 2024

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

@exhuma
Copy link
Author

exhuma commented May 12, 2024

@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.

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