LSA authentication packages are a part of the core security ecosystem in Windows NT. LSA APs are provided with credentials by logon applications, such as Winlogon, authenticate these credentials and provide the logon application with a logon session handle if authentication was successful. Two authentication packages provided with Windows are of special interest. MSV1_0 authenticates user credentials against the local SAM database or against a domain controller through the legacy logon protocols. Kerberos.dll uses the Kerberos protocol with modern domain controllers (or third party KDCs, etc.) for establishing logon sessions.

Applications that want to provide value-added logon functionality wish to become involved in the logon process. Winlogon provides the GINA facility before Windows Vista and the new Credential Providers in Windows Vista and later to allow involvement in the end-user logon experience.

While customizing the logon process is done at the logon application side with GINAs or CPs, sometimes customization is required at the authentication side, in the Local Security Authority subsystem. For instance, a fingerprint reader may customize the logon UI to allow fingerprint scanning as a means of authentication. The customized logon application will call LsaLogonUser with the fingerprint scanner’s custom LSA authentication package as the desired authenticator. The fingerprint LSA AP will scan a fingerprint database it maintains to perform the authentication process. That would be an independent LSA authenticaion package.

There are other scenarios in which the regular MSV1_0/Kerberos based logon authentication process is desired, but special pre or post-processing is required. In Windows XP, the problematic GINA architecture makes replacing the GINA cumbersome, as multiple third-party applications may attempt to install conflicting GINA replacements. Indeed, Microsoft documentation has been modified retroactively to recommend GINA hooks and stubs in lieu of outright replacement of the MSGINA implementation previously suggested.

With the ability to instrument the logon application at the GINA side limited and version dependent (a totally different approach is required for Vista’s CPs), the alternative of instrumentation at the LSA side warrants exploration. We can implement an LSA proxy authentication package. Such a package would appear to the LSA server as an ordinary package, but would be implemented by delegating to original APs like MSV1_0 and Kerberos, providing it with monitoring and instrumentation capabilities.

LSA authentication packages are loaded by LSASS at system startup by enumerating them from the registry value HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages. Both LSA authentication packages (APs) and security providers (SSPs) are registered at that location. For the purposes of this post, we’ll ignore pure SSPs and focus only on APs. We will register our proxy authentication package instead of MSV1_0 (and possibly Kerberos) at this location, and leave loading of the original APs to our proxy AP implementation.

Let us examine the system’s core APs, MSV1_0 and Kerberos:



C:\WINDOWS\system32>dumpbin /exports msv1_0.dll

Microsoft (R) COFF/PE Dumper Version 9.00.30729.01

Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file msv1_0.dll

File Type: DLL

Section contains the following exports for msv1_0.dll

00000000 characteristics

48025C84 time date stamp Sun Apr 13 22:18:28 2008

0.00 version

1 ordinal base

32 number of functions

17 number of names

ordinal hint RVA name

5 0 0000175C LsaApCallPackage = _LsaApCallPackage@28

6 1 00014BC8 LsaApCallPackagePassthrough = _LsaApCallPackagePassthr

ough@28

7 2 00014A59 LsaApCallPackageUntrusted = _LsaApCallPackageUntrusted

@28

8 3 0000BDBB LsaApInitializePackage = _LsaApInitializePackage@20

9 4 0000F7FE LsaApLogonTerminated = _LsaApLogonTerminated@4

10 5 00007498 LsaApLogonUserEx2 = _LsaApLogonUserEx2@64

11 6 0001A7E5 Msv1_0ExportSubAuthenticationRoutine = _Msv1_0ExportSu

bAuthenticationRoutine@40

12 7 0001A7C6 Msv1_0SubAuthenticationPresent = _Msv1_0SubAuthenticat

ionPresent@4

13 8 00016E63 MsvGetLogonAttemptCount = _MsvGetLogonAttemptCount@0

2 9 0001B704 MsvIsLocalhostAliases = ?MsvIsLocalhostAliases@@YGHPAU

_UNICODE_STRING@@@Z (int __stdcall MsvIsLocalhostAliases(struct _UNICODE_STRING

*))

14 A 00016E59 MsvSamLogoff = _MsvSamLogoff@12

15 B 0000A7BA MsvSamValidate = _MsvSamValidate@52

16 C 00016E6E MsvValidateTarget = _MsvValidateTarget@12

1 D 0000E415 SpInitialize = _SpInitialize@12

32 E 00006BC2 SpInstanceInit = _SpInstanceInit@12

3 F 0000F08F SpLsaModeInitialize = ?SpLsaModeInitialize@@YGJKPAKPAP

AU_SECPKG_FUNCTION_TABLE@@0@Z (long __stdcall SpLsaModeInitialize(unsigned long,

unsigned long *,struct _SECPKG_FUNCTION_TABLE * *,unsigned long *))

4 10 00006AE0 SpUserModeInitialize = ?SpUserModeInitialize@@YGJKPAKP

APAU_SECPKG_USER_FUNCTION_TABLE@@0@Z (long __stdcall SpUserModeInitialize(unsign

ed long,unsigned long *,struct _SECPKG_USER_FUNCTION_TABLE * *,unsigned long *))

Summary

2000 .data

2000 .reloc

2000 .rsrc

1D000 .text

C:\WINDOWS\system32>dumpbin /exports kerberos.dll

Microsoft (R) COFF/PE Dumper Version 9.00.30729.01

Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file kerberos.dll

File Type: DLL

Section contains the following exports for Kerberos.dll

00000000 characteristics

48025C4A time date stamp Sun Apr 13 22:17:30 2008

0.00 version

1 ordinal base

32 number of functions

10 number of names

ordinal hint RVA name

5 0 00026F8A KerbCreateTokenFromTicket = _KerbCreateTokenFromTicket

@44

2 1 00025773 KerbDomainChangeCallback = ?KerbDomainChangeCallback@@

YGXW4_POLICY_NOTIFICATION_INFORMATION_CLASS@@@Z (void __stdcall KerbDomainChange

Callback(enum _POLICY_NOTIFICATION_INFORMATION_CLASS))

6 2 000018B0 KerbFree = _KerbFree@4

7 3 00020A71 KerbIsInitialized = _KerbIsInitialized@0

8 4 00020A7C KerbKdcCallBack = _KerbKdcCallBack@0

9 5 0000380B KerbMakeKdcCall = _KerbMakeKdcCall@32

1 6 00013CAD SpInitialize = _SpInitialize@12

32 7 0000EDDF SpInstanceInit = _SpInstanceInit@12

3 8 000151DE SpLsaModeInitialize = ?SpLsaModeInitialize@@YGJKPAKPAP

AU_SECPKG_FUNCTION_TABLE@@0@Z (long __stdcall SpLsaModeInitialize(unsigned long,

unsigned long *,struct _SECPKG_FUNCTION_TABLE * *,unsigned long *))

4 9 0000ED1E SpUserModeInitialize = ?SpUserModeInitialize@@YGJKPAKP

APAU_SECPKG_USER_FUNCTION_TABLE@@0@Z (long __stdcall SpUserModeInitialize(unsign

ed long,unsigned long *,struct _SECPKG_USER_FUNCTION_TABLE * *,unsigned long *))

Summary

2000 .data

3000 .reloc

3000 .rsrc

43000 .text

Both DLLs export the SpInitialize function used for SSP initialization and for AP initialization export SpLsaModeInitialize and SpUserModeInitialize, called in LSASS context and in logon application context by secur32.dll, respectively.

As can be seen in MSV1_0’s export table, it provides a subauthentication package facility for those interested in becoming involved in the logon process. Clearly, this is the facility Microsoft intended third-parties to use, rather than creating proxy authentication packages. Unfortunately, subauthentication packages only provide a subset of authentication package functionality, primarily failing to provide access to the interactive logon (as opposed to the network logon) process. You should carefully consider your requirements. If they can be met with a subauthentication package, it is best to opt for that approach as it is far more likely to remain intact in Windows 7 and beyond. That said, the proxy authentication package model is given some legitimacy by this diagram, illustrating delegation to MSV1_0 by a hypothetical third-party custom authentication package.

It is interesting to note that MSV1_0 chooses to export the various LSA AP functions, such as LsaApLogonUserEx2, etc., via its DLL export table, while Kerberos opts not to export them, though quick examination of Kerberos.dll’s public symbols clearly illustrates the presence of the AP functions. So how does the LSA server know how to find and call the various AP functions? They are provided in a function dispatch table filled by the AP at initialization time, when SpLsaModeInitialize or SpUserModeInitialize are invoked (depending on the context of the AP’s use).

Keeping in mind that MSV1_0 was around before Kerberos was added to Windows NT in the Windows 2000 release and therefore may have remaining legacy cruft, reviewing the disassembly of the LSA server DLL, LSASRV, reveals that LSA first calls the Initialize functions to let the AP fill the function dispatch table, but then promptly examines the AP’s export table with GetProcAddress in the aptly named lsasrv!BindOldPackage function. This function appears to be invoked for the AP even when the function dispatch table has been filled. However, empirical evidence appears to suggest that the function dispatch table takes precedence over the contents of the export table. Indeed, Kerberos opts to do away with the export table entirely.

It appears that the export-based model was replaced with the dispatch table based model at some point during the lifetime of Windows NT. The export model has an obvious disadvantage of allowing a single DLL to implement only a single authentication package, a restriction will come back to later. The dispatch table model, on the other hand, allows the initialization function to return multiple dispatch tables, for multiple authentication packages, from a single DLL. This may yet prove useful for a proxy package.

In order to explore the feasibility of creating a proxy LSA authentication package, I wrote a test DLL, dubbed “LsaPrxAp”. This DLL provides the SpInitialize, SpLsaModeInitialize and SpUserModeInitialize exports. When invoked, it loads msv1_0 if it is not yet loaded. The DLL’s Initialize functions call the original msv1_0’s Initialize functions and then hook the authentication package by replacing function pointers with pointers to implementations of the LSA AP functions residing in LSAPrxAp. The original function pointers are saved in a global data structure so that LSAPrxAp’s stubs can invoke them while having value-added processing during the logon sequence. Since the name of the authentication package is the one provided in the SpLsaModeInitialize function by the AP, rather than the DLL’s name, it is easy to proxy the MSV1_0 module and become involved in the logon process. I did not bother with supporting Kerberos in my test, but hypothetically the proxy AP can call the Initialize functions for both MSV1_0 and Kerberos and return two dispatch tables as the parameter model for it allows.

Proxying a known authentication package of a known revision is one thing, but a more generic approach warrants special consideration. In both XP and Vista, the APs provide the latest LsaApLogonUserEx2 function. However, it is preceeded by LsaApLogonUserEx and LsaApLogonUser. The documentation for AP developers seems to indicate it is still legitimate to only support one of the older variants. Therefore, the proxy should take note that NULL function pointers in the dispatch table provided by the original APs are possible and should react accordingly.

As I investigated the behavior of the LSA server with respect to the export-based model for locating LSA AP functions vs. the function dispatch table model, I considered the problem of implementing a generic proxy if the dispatch table model was not preferred over the export table. The proxy AP would have to load the original AP and only have an LsaApLogonUserEx2 function if the original AP has. If the original AP has no LsaApLogonUser function exported, neither should the proxy AP. This presents special complication since compiling a different proxy AP for every possible set of exports is not feasible. I reiterate that the problem is theoretical since the function dispatch table model has no such fallacy.

In order to deal with this issue, I devised an approach where the proxy AP would initially export all possible LSA AP functions. At runtime, as the actual exports of the original AP is detected, functions that are missing from the original AP can be unexported from the proxy AP. I discuss the technique of unexporting functions through name obfuscation in the previous post.

Successfully compiling an LSA AP DLL required numerous acts of header juggling. For reference purposes, I provide the sequence I used:

#include <ntstatus.h> #define WIN32_NO_STATUS #include <Windows.h> #include <NTSecAPI.h> #define SECURITY_WIN32 #include <SSPI.h> #ifndef NT_SUCCESS #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #endif // LsaApCallPackageUntrusted is partially missing from NTSecPkg.h typedef NTSTATUS (NTAPI LSA_AP_CALL_PACKAGE_UNTRUSTED)( __in PLSA_CLIENT_REQUEST ClientRequest, __in PVOID ProtocolSubmitBuffer, __in PVOID ClientBufferBase, __in ULONG SubmitBufferLength, __out PVOID* ProtocolReturnBuffer, __out PULONG ReturnBufferLength, __out PNTSTATUS ProtocolStatus );

The WIN32_NO_STATUS definition is required to prevent Windows.h from defining the NTSTATUS values. Letting ntstatus.h define them is required because Windows.h only defines a subset of the required values. SSPI.h requires choosing between Win32, Kernel-mode and Macintosh Classic (heh…) usage, resulting in the SECURITY_WIN32 preprocessor definition. The NTSecPkg.h header included with the Windows SDK I’m using makes the embarrassing error of missing the prototype for LsaApCallPackageUntrusted, while curiously attempting to provide the function pointer type for it.

It is wise to avoid loading the original AP DLLs during DllMain, due to potential loader lock issues. The first functions invoked after an AP DLL has been mapped to a process are those that fill the function dispatch table; SpInitialize for the SSP case, SpLsaModeInitialize for the LSASS-side AP case and SpUserModeInitialize for the secur32.dll logon application (Winlogon) side case. All three functions ought to load the original AP if required and perform any required hooking of the dispatch tables, pointing to your own proxy functions.

The initialization functions should also extract the function pointers for the original AP implementations and preserve them in global data. This can later be used to forward calls to the original implementation. Consider the implementation of LsaApCallPackage in my LsaPrxAp test DLL:

NTSTATUS NTAPI LsaApCallPackage( __in PLSA_CLIENT_REQUEST ClientRequest, __in PVOID ProtocolSubmitBuffer, __in PVOID ClientBufferBase, __in ULONG SubmitBufferLength, __out PVOID* ProtocolReturnBuffer, __out PULONG ReturnBufferLength, __out PNTSTATUS ProtocolStatus ) { OutputDebugString(TEXT("lsaaprxap LsaApCallPackage

")); return g_LsaPrxApFunctionTable.pLsaApCallPackage( ClientRequest, ProtocolSubmitBuffer, ClientBufferBase, SubmitBufferLength, ProtocolReturnBuffer, ReturnBufferLength, ProtocolStatus); }

Obviously a real-world proxy AP would perform required processing before or after forwarding to the original function pointer preserved in global data, as well.

Hopefully this primer on implementing an LSA proxy AP will prove to be a welcome addition to Microsoft’s sparse documentation on LSA authentication packages.