-
-
Save mrh1997/717b14f5783b49ca14310419fa7f03f6 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 | |
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() |
Actually this code was written for Python 2.7 ignoring Unicode. For real Unicode support there is more to do:
- replace
CreadReadA
byCreadReadW
and thus supporting unicode urls - replacing all
LPTSTR
byLPWSTR
. This is especially important forUserName
as it would be returned as unicode then credPtr.contents.CredentialBlob
is of typeLP_c_byte
. Thus you cannot use the methoddecode
.
I updated the code to support unicode correctly (and switched to Python 3 compatibility)
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 ;)
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.
I'm using this script in a node package. See: https://github.com/rolangom/wincred
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!
A little improvement, I think...
import codecs
password = CT.string_at(cred_blob,cred_blob_size).decode('utf-16le', errors='ignore')
@apolkosnik: Thx for this tip. I incorporated it into my code...
Question : how can I get the Windows Session Password using Python ?
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.
Line 42 looks dodgy:
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>)