Skip to content

Instantly share code, notes, and snippets.

@odzhan
Last active July 31, 2022 23:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save odzhan/81b73e8218701d85a33215c16db9a70f to your computer and use it in GitHub Desktop.
Save odzhan/81b73e8218701d85a33215c16db9a70f to your computer and use it in GitHub Desktop.
LSA Extension Internals

LSA Extension Internals

About

I want to use lsasrv!LsaProtectMemory() inside the LSASS process to encrypt a block of memory and return the ciphertext. It's part of the LsapLsasrvIfTable interface in lsasrv.dll, but unless I'm mistaken can only be accessed by another LSA extension using the lsasrv!QueryLsaInterface() function. The following text is some basic information about the internal structures.

LsapLsasrvIfTable:
  dq offset LsaProtectMemory
  dq offset LsaUnprotectMemory
  dq offset LsaIFreeReturnBuffer
  dq offset LsapSubProv_FreeRoutine(_RTL_BALANCED_NODE *,void *)
  dq offset LsaICallPackage
  dq offset LsaIfGetAuthDataForUser(_UNICODE_STRING *,ulong,_UNICODE_STRING *,uchar * *,ulong *,_UNICODE_STRING *)
  dq offset LsaConvertAuthDataToToken
  dq offset LsaIQueryPackageAttrInLogonSession
  dq offset LsaINotifyPasswordChanged
  dq offset LsaINoMoreWin2KDomain
  dq offset LsaIAuditDPAPIEvent
  dq offset LsaIQueryInformationPolicyTrusted
  dq offset LsaIFree_LSAPR_POLICY_INFORMATION
  dq offset LsapGetCredentialKey(_LUID *,_GUID *,_CREDENTIAL_KEY * *)
  dq offset LsapIsDomainUserDPAPI(void *)
  dq offset LsapDPAPIPasswordChangeForGMSA
  dq offset LsapAllocateULong
  dq offset LsapDecryptDPAPIMasterKey(_LUID *,LSAI_DPAPI_KEY_TYPE,uchar *,ulong,uchar *,ulong,uchar * *,ulong *)

Extension Internals

Support for LSA extensions first appeared in Windows 7, but for some reason still aren't properly documented. If we look inside LSASS.EXE, there's three initial functions of interest:

* lsass!LsapInitializeExtensionConfig() initializes two LIST_ENTRY structures: gLsapIfList, gLsapExtensionList
* lsass!LsapLoadRequiredExtensions() reads and loads DLL from HKLM\System\CurrentControlSet\Control\LsaExtensionConfig\LsaSrv
* lsass!LsapLoadLsaInterfaces() reads and loads DLL from HKLM\System\CurrentControlSet\Control\LsaExtensionConfig\Interfaces

LsapLoadRequiredExtensions

Two are currently installed on my system. One for the Local Security Authority (LSA) and the other for Encrypting File System (EFS).

reg query "HKLM\System\CurrentControlSet\Control\LsaExtensionConfig\LsaSrv"

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LsaExtensionConfig\LsaSrv
	Extensions    REG_MULTI_SZ    lsasrv.dll\0efslsaext.dll

These DLL both export InitializeLsaExtension(). Internally is allocation of what I defined LSAP_EXTENSION_ENTRY allocated on the heap. Some of the fields are self-explanatory.

enum LSA_INTERFACE_TYPE {   
	LsasrvIfTable = 1001,
	DpapiIfTable = 1002,
};
	
enum LSA_EXTENSION_INIT_STAGE {
	LsaExtensionLoad = 1,    // After InitializeLsaExtension(0) inside LsapLoadExtension()
	LsaExtensionNotify = 2,  // After InitializeLsaExtension(1) inside LsapNotifyExtensionsLoadComplete()
	LsaExtensionStart = 3    // After InitializeLsaExtension(2) inside LsapStartExtensions()
};

typedef NTSTATUS (WINAPI *PINITIALIZELSAEXTENSION)(LSA_EXTENSION_INIT_STAGE);
typedef NTSTATUS (WINAPI *PQUERYLSAINTERFACE)(LSA_INTERFACE_TYPE, PVOID*);

typedef struct _LSAP_EXTENSION_ENTRY {
	LIST_ENTRY               Links;
	LIST_ENTRY               InterfaceLinks;          // LSAP_INTERFACE_ENTRY.ExtentionLinks
	UNICODE_STRING           DllName;                 // name of extension. e.g. lsasrv.dll
	PVOID                    DllBase;                 // set by LoadLibraryExW();
	LSA_EXTENSION_INIT_STAGE Stage;
	DWORD                    Win32Error;
	NTSTATUS                 NtStatus;
	PINITIALIZELSAEXTENSION  InitializeLsaExtension;  // set by GetProcAddress(hExtension, "InitializeLsaExtension");
	PQUERYLSAINTERFACE       QueryLsaInterface;       // set by GetProcAddress(hExtension, "QueryLsaInterface");
} LSAP_EXTENSION_ENTRY, *PLSAP_EXTENSION_ENTRY;

Once an entry is created, it's inserted into the global list lsass!gLsapExtensionList.

LsapLoadLsaInterfaces

Here we see two entries. 1001 and 1002 are unique to these interfaces and are actually LSA_INTERFACE_TYPE.

reg query "HKLM\System\CurrentControlSet\Control\LsaExtensionConfig\Interfaces"

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LsaExtensionConfig\Interfaces\1001
	Extension    REG_SZ    lsasrv.dll
	Name         REG_SZ    LsaLsasrvInterface
	
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LsaExtensionConfig\Interfaces\1002
	Extension    REG_SZ    dpapisrv.dll
	Name         REG_SZ    LsaDpapiInterface

Internally is allocation of what I call LSAP_INTERFACE_ENTRY, which is allocated on the heap.

typedef struct _LSAP_INTERFACE_ENTRY {
	LIST_ENTRY               Links;
	LSA_INTERFACE_TYPE       Type;            // This is a unique ID used when calling QueryLsaInterface.
	PLSAP_EXTENSION_ENTRY    Extension;       // extension this interface belongs to
	LIST_ENTRY               ExtensionLinks;  // linked LSA_ENTENSION_ENTRY object
	PVOID                    FunctionTable;   // points to array of functions
} LSAP_INTERFACE_ENTRY, *PLSAP_INTERFACE_ENTRY;

QueryLsaInterface

For the LSASRV interface, we have something like the following:

NTSTATUS 
QueryLsaInterface(LSA_INTERFACE_TYPE Type, PVOID *FunctionTable) {
	if (Type != LsasrvIfTable)
		return STATUS_NOT_SUPPORTED;

	*FunctionTable = &LsapLsasrvIfTable;
	return STATUS_SUCCESS;
}

Based on the internals of LSA extensions, it's not available via RPC, but perhaps there's another way?

@modexpblog

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