Files
naksyn-PythonMemoryModule/pythonmemorymodule/windows/crypto/certificate.py
T
naksyn db1893910c command line support (partial) via PEB stomping
This update include support to passing command line parameters to unmanaged exe via PEB stomping.
This technique is not working with every executable since it depends on which functions are used to pass arguments.
Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here:
https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp

However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping.
On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
2023-07-27 06:44:29 -07:00

585 lines
21 KiB
Python

import itertools
import ctypes
import windows
from windows import winproxy
import windows.generated_def as gdef
from windows.crypto import DEFAULT_ENCODING
import windows.crypto.cryptmsg
CRYPT_OBJECT_FORMAT_TYPE = [
gdef.CERT_QUERY_OBJECT_FILE,
gdef.CERT_QUERY_OBJECT_BLOB,
gdef.CERT_QUERY_CONTENT_CERT,
gdef.CERT_QUERY_CONTENT_CTL,
gdef.CERT_QUERY_CONTENT_CRL,
gdef.CERT_QUERY_CONTENT_SERIALIZED_STORE,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CERT,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CTL,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CRL,
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED,
gdef.CERT_QUERY_CONTENT_PKCS7_UNSIGNED,
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED,
gdef.CERT_QUERY_CONTENT_PKCS10,
gdef.CERT_QUERY_CONTENT_PFX,
gdef.CERT_QUERY_CONTENT_CERT_PAIR,
gdef.CERT_QUERY_CONTENT_PFX_AND_LOAD
]
CRYPT_OBJECT_FORMAT_TYPE_DICT = gdef.FlagMapper(*CRYPT_OBJECT_FORMAT_TYPE)
## Move CryptObject to new .py ?
class CryptObject(object):
"""Extract information from an CryptoAPI object.
(see `CryptQueryObject <https://msdn.microsoft.com/en-us/library/windows/desktop/aa380264(v=vs.85).aspx>`_)
Current main use is extracting the signers certificates from a PE file.
"""
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD}
def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL):
# No other API than filename for now..
self.filename = filename
dwEncoding = gdef.DWORD()
dwContentType = gdef.DWORD()
dwFormatType = gdef.DWORD()
hStore = CertificateStore()
hMsg = windows.crypto.CryptMessage()
winproxy.CryptQueryObject(gdef.CERT_QUERY_OBJECT_FILE,
gdef.LPWSTR(filename),
# filename,
content_type,
gdef.CERT_QUERY_FORMAT_FLAG_BINARY,
0,
dwEncoding,
dwContentType,
dwFormatType,
hStore,
hMsg,
None)
self.cert_store = hStore if hStore else None
"""The :class:`CertificateStore` that includes all of the certificates, CRLs, and CTLs in the object"""
self.crypt_msg = hMsg if hMsg else None #: yolo
"""The :class:`CryptMessage` for any ``PKCS7`` content in the object"""
self.encoding = dwEncoding
self.content_type = CRYPT_OBJECT_FORMAT_TYPE_DICT[dwContentType.value]
"""The type of the opened message"""
def _signers_and_certs_generator(self):
if self.crypt_msg is None:
return
for signer in self.crypt_msg.signers:
# We could directly extract the certificates from the 'crypt_msg' (I guess)
# But 'CryptQueryObject' had the sympathy of already opening a CertificateStore
# for us. So we use it.
# I am open to counter-argument on this methodology.
cert = self.cert_store.find(signer.Issuer, signer.SerialNumber)
yield signer, cert
@property
def signers_and_certs(self):
"""The list of signer info and certificates signing the object.
:rtype: [(:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO`, :class:`Certificate`)]
.. note::
:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO` might be changed to a wrapping-subclass.
"""
return list(self._signers_and_certs_generator())
def __repr__(self):
return '<{0} "{1}" content_type={2!r}>'.format(type(self).__name__, self.filename, self.content_type)
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa382037(v=vs.85).aspx
class CertificateStore(gdef.HCERTSTORE):
"""A certificate store"""
@property
def certs(self):
"""The list of certificates in the store
:type: [:class:`Certificate`] -- A list of certificate
"""
res = []
last = None
while True:
try:
cert = winproxy.CertEnumCertificatesInStore(self, last)
except winproxy.WinproxyError as e:
if (e.winerror & 0xffffffff) in (gdef.CRYPT_E_NOT_FOUND,):
return tuple(res)
raise
# Need to duplicate as CertEnumCertificatesInStore will free the context 'last'
ecert = windows.crypto.Certificate.from_pointer(cert)
res.append(ecert.duplicate())
last = ecert
raise RuntimeError("Out of infinit loop")
def add_certificate(self, certificate):
"""Add a certificate to the store"""
winproxy.CertAddCertificateContextToStore(self, certificate, gdef.CERT_STORE_ADD_NEW, None)
@classmethod
def from_file(cls, filename):
"""Create a new :class:`CertificateStore` from ``filename``"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_FILENAME_A, DEFAULT_ENCODING, None, gdef.CERT_STORE_OPEN_EXISTING_FLAG, filename)
return ctypes.cast(res, cls)
def _yolo(self):
x = winproxy.CertEnumCTLsInStore(self, None)
title = None
windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CTL_CONTEXT, x, None, title, 0, None)
return x
# See https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx
@classmethod
def from_system_store(cls, store_name):
"""Create a new :class:`CertificateStore` from system store ``store_name``
(see `System Store Locations <https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx>`_)
"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_SYSTEM_A, DEFAULT_ENCODING, None, gdef.CERT_SYSTEM_STORE_LOCAL_MACHINE | gdef.CERT_STORE_READONLY_FLAG, store_name)
return ctypes.cast(res, cls)
@classmethod
def new_in_memory(cls):
"""Create a new temporary :class:`CertificateStore` in memory"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None)
return ctypes.cast(res, cls)
# TODO: a more complete search API ?
def find(self, issuer, serialnumber):
"""Return the certificate that match `issuer` and `serialnumber`
:return: :class:`Certificate` -- ``None`` if certificate is not found
"""
# data = self.get_signer_data(index)
cert_info = gdef.CERT_INFO()
cert_info.Issuer = issuer
cert_info.SerialNumber = serialnumber
try:
rawcertcontext = winproxy.CertFindCertificateInStore(self, DEFAULT_ENCODING, 0, gdef.CERT_FIND_SUBJECT_CERT, ctypes.byref(cert_info), None)
except WindowsError as e:
if not e.winerror & 0xffffffff == gdef.CRYPT_E_NOT_FOUND:
raise
return None
return Certificate.from_pointer(rawcertcontext)
def __del__(self):
return winproxy.CertCloseStore(self, 0)
# PKCS12_NO_PERSIST_KEY -> do not save it in a key container on disk
# Without it, a key container is created at 'C:\Users\USERNAME\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-3241049326-165485355-1070449050-1001'
# More about this:
# If you use 'PKCS12_NO_PERSIST_KEY' the key are indeed NOT STORED but there is a problem
# If you use an algo like 'szOID_NIST_AES256_CBC' the function 'CryptDecryptMessage' won't be able to decrypt the message
# Unless you also specify the 'PKCS12_ALWAYS_CNG_KSP' flags.
# My guess: somewhere 'CryptDecryptMessage' ask for each (CNG_KSP | CSP ?) to try to decrypt with the keys
# BUT: as we DID NOT EXPORT the keys, they are not able to get the key from memory and expect them on disk.
# By forcing PKCS12_ALWAYS_CNG_KSP we remove this as the key are directly linked to the correct CNG_KSP in the CertStore
# Look like it's based on this part of the PFX:
# Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
# BUT this will not allow to decrypt RSA_RC4 ?
def import_pfx(pfx, password=None, flags=gdef.CRYPT_USER_KEYSET | gdef.PKCS12_NO_PERSIST_KEY | gdef.PKCS12_ALWAYS_CNG_KSP):
"""Import the file ``pfx`` with the ``password``.
``default flags = PKCS12_NO_PERSIST_KEY | CRYPT_USER_KEYSET``.
``PKCS12_NO_PERSIST_KEY`` tells ``CryptoAPI`` to NOT save the keys in a on-disk container.
:return: :class:`CertificateStore`
"""
if isinstance(pfx, windows.pycompat.anybuff) or isinstance(pfx, bytearray):
pfx = gdef.CRYPT_DATA_BLOB.from_string(pfx)
cert_store = winproxy.PFXImportCertStore(pfx, password, flags)
return CertificateStore(cert_store)
class Certificate(gdef.CERT_CONTEXT):
"""Represent a Certificate """
@property
def raw_serial(self):
"""The raw serial number of the certificate.
:type: [:class:`int`]: A list of int ``0 <= x <= 255``"""
serial_number = self.pCertInfo[0].SerialNumber
return [(c & 0xff) for c in serial_number.pbData[:serial_number.cbData][::-1]]
@property
def serial(self):
"""The string representation of the certificate's serial.
:type: :class:`str`
"""
serial_bytes = self.raw_serial
return " ".join("{:02x}".format(x) for x in serial_bytes)
def get_name(self, nametype=gdef.CERT_NAME_SIMPLE_DISPLAY_TYPE, param_type=0, flags=0):
"""Retrieve the subject or issuer name of the certificate.
See `CertGetNameStringA <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086(v=vs.85).aspx>`_
:returns: :class:`str`
"""
if nametype == gdef.CERT_NAME_RDN_TYPE:
param_type = gdef.DWORD(param_type)
param_type = gdef.LPDWORD(param_type)
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, None, 0)
namebuff = ctypes.c_buffer(size)
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, namebuff, size)
return namebuff[:-1]
name = property(get_name)
"""The name of the certificate.
:type: :class:`str`"""
def raw_hash(self):
size = gdef.DWORD(100)
buffer = ctypes.c_buffer(size.value)
winproxy.CryptHashCertificate(None, 0, 0, self.pbCertEncoded, self.cbCertEncoded, ctypes.cast(buffer, gdef.LPBYTE), size)
return buffer[:size.value]
@property
def thumbprint(self):
"""The thumbprint of the certificate (which is the sha1 of the encoded cert).
Example:
>>> x
<Certificate "YOLO2" serial="6f 1d 3e 7d d9 77 59 a9 4c 1c 53 dc 80 db 0c fe">
>>> x.thumbprint
'E2 A2 DB 76 A1 DD 8E 70 0D C6 9F CB 71 CF 29 12 C6 D9 78 97'
:type: :class:`str`
"""
return " ".join("{:02X}".format(x) for x in bytearray(self.raw_hash()))
@property
def distinguished_name(self):
"""The distinguished name (DN) of the certificate.
Example:
>>> x
<Certificate "Microsoft Windows Production PCA 2011" serial="61 07 76 56 00 00 00 00 00 08">
>>> x.distinguished_name
'C=US, S=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011'
:type: :class:`str`
"""
return self.get_name(gdef.CERT_NAME_RDN_TYPE, gdef.CERT_X500_NAME_STR)
@property
def issuer(self):
"""The name of the certificate's issuer.
:type: :class:`str`"""
return self.get_name(flags=gdef.CERT_NAME_ISSUER_FLAG)
@property
def store(self):
"""The certificate store that contains the certificate
:type: :class:`CertificateStore`
"""
return CertificateStore(self.hCertStore)
def get_raw_certificate_chains(self): # Rename to all_chains ?
chain_context = EPCCERT_CHAIN_CONTEXT()
enhkey_usage = gdef.CERT_ENHKEY_USAGE()
enhkey_usage.cUsageIdentifier = 0
enhkey_usage.rgpszUsageIdentifier = None
cert_usage = gdef.CERT_USAGE_MATCH()
cert_usage.dwType = gdef.USAGE_MATCH_TYPE_AND
cert_usage.Usage = enhkey_usage
chain_para = gdef.CERT_CHAIN_PARA()
chain_para.cbSize = ctypes.sizeof(chain_para)
chain_para.RequestedUsage = cert_usage
winproxy.CertGetCertificateChain(None, self, None, self.hCertStore, ctypes.byref(chain_para), 0, None, ctypes.byref(chain_context))
# Lower chains ?
# winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, ctypes.byref(chain_para), 0x80, None, ctypes.byref(chain_context))
#return CertficateChain(chain_context)
return chain_context
@property # fixedproperty ?
def chains(self):
"""The list of chain context available for this certificate. Each elements of this list is a list of ``Certificate`` that should
go from the ``self`` certificate to a trusted certificate.
:type: [[:class:`Certificate`]] -- A list of chain (list) of :class:`Certificate`
"""
chain_context = self.get_raw_certificate_chains()
res = []
for chain in chain_context.chains:
chain_res = [elt.cert for elt in chain.elements]
res.append(chain_res)
return res
# API Arround CertSelectCertificateChains ?
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd433797(v=vs.85).aspx
def duplicate(self):
"""Duplicate the certificate by incrementing the internal refcount. (see `CertDuplicateCertificateContext <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx>`_)
note: The object returned is ``self``
:return: :class:`Certificate`
"""
res = winproxy.CertDuplicateCertificateContext(self)
# Check what the doc says: the pointer returned is actually the PCERT in parameter
# Only the refcount is incremented
# This postulate allow us to return 'self' directly
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx
if not ctypes.addressof(res[0]) == ctypes.addressof(self):
raise ValueError("CertDuplicateCertificateContext did not returned the argument (check doc)")
return self
def view(self, title=None):
return windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CERTIFICATE_CONTEXT, ctypes.byref(self), None, title, 0, None)
KNOWN_PROPERTIES_VALUES = gdef.FlagMapper(
gdef.CERT_KEY_PROV_HANDLE_PROP_ID,
gdef.CERT_KEY_PROV_INFO_PROP_ID,
gdef.CERT_SHA1_HASH_PROP_ID,
gdef.CERT_MD5_HASH_PROP_ID,
gdef.CERT_HASH_PROP_ID,
gdef.CERT_KEY_CONTEXT_PROP_ID,
gdef.CERT_KEY_SPEC_PROP_ID,
gdef.CERT_IE30_RESERVED_PROP_ID,
gdef.CERT_PUBKEY_HASH_RESERVED_PROP_ID,
gdef.CERT_ENHKEY_USAGE_PROP_ID,
gdef.CERT_CTL_USAGE_PROP_ID,
gdef.CERT_NEXT_UPDATE_LOCATION_PROP_ID,
gdef.CERT_FRIENDLY_NAME_PROP_ID,
gdef.CERT_PVK_FILE_PROP_ID,
gdef.CERT_DESCRIPTION_PROP_ID,
gdef.CERT_ACCESS_STATE_PROP_ID,
gdef.CERT_SIGNATURE_HASH_PROP_ID,
gdef.CERT_SMART_CARD_DATA_PROP_ID,
gdef.CERT_EFS_PROP_ID,
gdef.CERT_FORTEZZA_DATA_PROP_ID,
gdef.CERT_ARCHIVED_PROP_ID,
gdef.CERT_KEY_IDENTIFIER_PROP_ID,
gdef.CERT_AUTO_ENROLL_PROP_ID,
gdef.CERT_PUBKEY_ALG_PARA_PROP_ID,
gdef.CERT_CROSS_CERT_DIST_POINTS_PROP_ID,
gdef.CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID,
gdef.CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID,
gdef.CERT_ENROLLMENT_PROP_ID,
gdef.CERT_DATE_STAMP_PROP_ID,
gdef.CERT_ISSUER_SERIAL_NUMBER_MD5_HASH_PROP_ID,
gdef.CERT_SUBJECT_NAME_MD5_HASH_PROP_ID,
gdef.CERT_EXTENDED_ERROR_INFO_PROP_ID,
gdef.CERT_RENEWAL_PROP_ID,
gdef.CERT_ARCHIVED_KEY_HASH_PROP_ID,
gdef.CERT_AUTO_ENROLL_RETRY_PROP_ID,
gdef.CERT_AIA_URL_RETRIEVED_PROP_ID,
gdef.CERT_AUTHORITY_INFO_ACCESS_PROP_ID,
gdef.CERT_BACKED_UP_PROP_ID,
gdef.CERT_OCSP_RESPONSE_PROP_ID,
gdef.CERT_REQUEST_ORIGINATOR_PROP_ID,
gdef.CERT_SOURCE_LOCATION_PROP_ID)
def enum_properties(self):
prop = 0
res = []
while True:
prop = winproxy.CertEnumCertificateContextProperties(self, prop)
if not prop:
return res
res.append(self.KNOWN_PROPERTIES_VALUES[prop])
raise RuntimeError("Unreachable code")
properties = property(enum_properties)
"""The properties of the certificate
:type: [:class:`int` or :class:`~windows.generated_def.Flag`] -- A list of property ID
"""
#def get_property(self):
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376079(v=vs.85).aspx
# - Usefull:
# CERT_SHA1_HASH_PROP_ID
def get_property(self, prop):
"TODO: DOC :D + auto-type ?"
datasize = gdef.DWORD()
windows.winproxy.CertGetCertificateContextProperty(self, prop, None, datasize)
buf = (gdef.BYTE * datasize.value)()
windows.winproxy.CertGetCertificateContextProperty(self, prop, buf, datasize)
return bytearray(buf)
@property
def encoded(self):
"""The encoded certificate.
:type: :class:`bytearray`"""
return bytearray(self.pbCertEncoded[:self.cbCertEncoded])
@property
def version(self):
"""The version number of the certificate
:type: :class:`int`
"""
return self.pCertInfo[0].dwVersion
@classmethod
def from_file(cls, filename):
"""Create a :class:`Certificate` from the file ``filename``
:return: :class:`Certificate`
"""
with open(filename, "rb") as f:
data = f.read()
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
return cls.from_pointer(pcert)
@classmethod
def from_buffer(cls, data):
"""Create a :class:`Certificate` from the buffer ``data``
:return: :class:`Certificate`
"""
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
return cls.from_pointer(pcert)
@classmethod
def from_pointer(self, ptr):
return ctypes.cast(ptr, ctypes.POINTER(Certificate))[0]
def __eq__(self, other):
if not isinstance(other, Certificate):
return NotImplemented
return windows.winproxy.CertCompareCertificate(DEFAULT_ENCODING, self.pCertInfo, other.pCertInfo)
def __repr__(self):
return '<{0} "{1}" serial="{2}">'.format(type(self).__name__, self.name, self.serial)
# class CertficateChain(object):
# def __init__(self, pc_chain_context):
# self.chain = pc_chain_context[0]
#
# def to_list(self):
# res = []
# for i in range(self.chain.rgpChain[0][0].cElement):
# res.append(CertificateContext(self.chain.rgpChain[0][0].rgpElement[i][0].pCertContext[0]))
# return res
# Those classes are more of a POC than anything else
# Should be the struct itself (like Certificate ?)
class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT):
_type_ = gdef.PCCERT_CHAIN_CONTEXT._type_
@property
def chains(self):
res = []
# if (self[0].cLowerQualityChainContext):
# print("LOL")
# import pdb;pdb.set_trace()
# if self[0].cChain > 1:
# print("HAAAAA")
# import pdb;pdb.set_trace()
for i in range(self[0].cChain):
simple_chain = ctypes.cast(self[0].rgpChain[i], EPCCERT_SIMPLE_CHAIN)
res.append(simple_chain)
return res
@property
def all_cert(self):
res = []
for chain in self.chains:
ch = []
res.append(ch)
for element in chain.elements:
ch.append(element.cert)
return res
class EPCCERT_SIMPLE_CHAIN(gdef.PCCERT_SIMPLE_CHAIN):
_type_ = gdef.PCCERT_SIMPLE_CHAIN._type_
@property
def elements(self):
res = []
for i in range(self[0].cElement):
element = ctypes.cast(self[0].rgpElement[i], EPCERT_CHAIN_ELEMENT)
res.append(element)
return res
class EPCERT_CHAIN_ELEMENT(gdef.PCERT_CHAIN_ELEMENT):
_type_ = gdef.PCERT_CHAIN_ELEMENT._type_
@property
def cert(self):
return Certificate.from_pointer(self[0].pCertContext)
# Move this in another .py ?
class CryptContext(gdef.HCRYPTPROV):
""" A context manager arround ``CryptAcquireContextW`` & ``CryptReleaseContext``
.. note::
see usage in sample :ref:`sample_crypto_encryption` (function ``genkeys``)
"""
_type_ = gdef.HCRYPTPROV._type_
def __init__(self, pszContainer=None, pszProvider=None, dwProvType=0, dwFlags=0, retrycreate=False):
self.pszContainer = pszContainer
self.pszProvider = pszProvider
self.dwProvType = dwProvType
self.dwFlags = dwFlags
self.retrycreate = True
#self.value = HCRYPTPROV()
pass
def __enter__(self):
self.acquire()
return self
def __exit__(self, *args):
self.release()
def acquire(self):
try:
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags)
except WindowsError as e:
if not self.retrycreate:
raise
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags | gdef.CRYPT_NEWKEYSET)
def release(self):
return winproxy.CryptReleaseContext(self, False)