[{"data":1,"prerenderedAt":9334},["ShallowReactive",2],{"post-windows-2fa-inside-lsa":3},{"id":4,"title":5,"body":6,"category":9313,"date":9314,"description":9315,"draft":9316,"extension":9317,"image":9318,"links":9319,"meta":9324,"navigation":754,"path":9325,"readingTime":758,"seo":9326,"stem":9327,"tags":9328,"__hash__":9333},"posts\u002Fposts\u002Fwindows-2fa-inside-lsa.md","Build Your Own 2FA for the Windows Logon, Part 2: Inside LSA",{"type":7,"value":8,"toc":9272},"minimark",[9,14,24,32,35,42,52,59,96,106,110,153,157,160,166,172,185,191,197,208,212,215,221,245,248,254,258,263,270,362,365,369,372,379,386,390,393,492,495,501,505,509,512,520,523,549,556,562,1162,1185,1189,1198,1334,1337,1341,1348,1367,1370,1374,1385,1399,1402,1413,1419,1599,1604,2723,2729,3389,3396,3422,3429,3433,3439,3446,3452,3459,3464,6422,6425,6505,6509,6515,6519,6534,6537,6540,6544,6547,6627,6637,6640,6650,6654,6657,6808,6811,6851,6858,6862,7173,7180,7184,8376,8380,8386,8388,8452,8456,8459,8465,8471,8485,8489,8496,8499,8513,8516,8520,8530,8554,8564,8570,8574,8589,8593,8604,8620,8631,8635,8638,8647,8650,8724,8731,8735,8742,8762,8766,8769,8773,8783,8802,8815,8819,8865,8872,8879,8890,8896,8900,8912,8918,8924,8931,8934,8938,8950,8955,8961,8978,8995,8998,9002,9009,9016,9020,9032,9035,9044,9050,9064,9067,9078,9082,9085,9122,9126,9133,9147,9158,9170,9187,9190,9210,9213,9224,9228,9231,9246,9258,9261,9268],[10,11,13],"h2",{"id":12},"foreword","Foreword",[15,16,17,18,23],"p",{},"This is the second part. In ",[19,20,22],"a",{"href":21},"\u002Fp\u002Fwindows-2fa-credential-provider\u002F","Part 1"," we built a working TOTP second factor for the Windows logon: a credential provider with a tile, a filter that hides the other tiles, replay protection, a lockout and logging.",[15,25,26,27,31],{},"At the end of that article I kept repeating the same limit. The credential provider only sits in the UI, so it only sees the UI. Network logon over SMB, ",[28,29,30],"code",{},"runas",", scheduled tasks, WinRM, service logons: none of them touch our tile, so none of them ask for a code. I wrote back then that the check would have to move into LSA, and that maybe I would try that in another article.",[15,33,34],{},"This is that article.",[15,36,37,38,41],{},"So this time we write code that runs inside ",[28,39,40],{},"lsass.exe",". And this is a different kind of dangerous than Part 1. A broken credential provider costs you a tile and you can still get in over Safe Mode. A broken authentication package can stop LSA from starting, and a machine without LSA does not boot. Not even in Safe Mode.",[43,44,45],"blockquote",{},[15,46,47,51],{},[48,49,50],"strong",{},"Important:"," Everything in this article is a throwaway-VM exercise. Take a snapshot before every single reboot. Have your BitLocker recovery key ready and know how to edit the registry offline from WinRE. If you only want to harden a real machine, read the chapter \"Two ways to close the non-interactive paths\" and then stop.",[15,53,54,55,58],{},"I also want to be honest about the state of the code. It compiles for x64, and the exports are checked. I have ",[48,56,57],{},"not"," run it on a live LSA. Don't trust it until you have, under a kernel debugger, in a VM you can roll back.",[43,60,61],{},[15,62,63,66,67,70,71,75,76,79,80,83,84,87,88,91,92,95],{},[48,64,65],{},"Correction, October 2026:"," The first version of this article used the wrong hook for the MSV1_0 part. It used ",[28,68,69],{},"Msv1_0SubAuthenticationRoutine"," and said that MSV1_0 still checks the password after the routine says yes. That's wrong. If this routine is used, the password check is ",[72,73,74],"em",{},"its"," job, and my routine never did one. So for a logon that selected it, \"yes\" meant: approved without a password. A code audit found this. The code and this article now use ",[28,77,78],{},"Msv1_0SubAuthenticationFilter",". It runs ",[72,81,82],{},"after"," MSV1_0 checked the password and can only say no. If you installed the old ",[28,85,86],{},"TacSubAuth.dll",", run the new ",[28,89,90],{},"install-subauth.ps1"," or ",[28,93,94],{},"uninstall-subauth.ps1",". Both remove the old registration. The chapter \"The sub-authentication filter\" explains what went wrong.",[15,97,98,99,105],{},"All the code is in this article again. The project is on GitHub: ",[19,100,104],{"href":101,"rel":102},"https:\u002F\u002Fgithub.com\u002FNobrac\u002Fdoppio",[103],"nofollow","Doppio",".",[10,107,109],{"id":108},"prerequisites","Prerequisites",[111,112,113,117,120,123,126,129,144,150],"ul",{},[114,115,116],"li",{},"Part 1, or at least the repo. The LSA code uses the same secret store.",[114,118,119],{},"A Windows 10\u002F11 or Server VM that you can delete. Not a VM you care about.",[114,121,122],{},"A snapshot. Before every reboot, not once at the beginning.",[114,124,125],{},"Visual Studio 2026 or 2022 (Community is enough) or the Build Tools, with the \"Desktop development with C++\" workload.",[114,127,128],{},"Your BitLocker recovery key, if the VM is encrypted.",[114,130,131,132,135,136,139,140,143],{},"A WinRE way to edit the registry offline. Boot the install media, open a command prompt, ",[28,133,134],{},"reg load"," the offline ",[28,137,138],{},"SYSTEM"," hive. Practice this ",[72,141,142],{},"before"," you need it.",[114,145,146,149],{},[28,147,148],{},"RunAsPPL"," turned off on the test VM. More about this in the install chapter, because it will otherwise cost you an evening.",[114,151,152],{},"Ideally a kernel debugger. You cannot attach Visual Studio to lsass.",[10,154,156],{"id":155},"what-this-closes-and-what-not","What this closes and what not",[15,158,159],{},"Same as in Part 1, I want this clear before any code.",[15,161,162,165],{},[48,163,164],{},"Closed, if MSV1_0 calls our filter:"," Network logons for an enrolled account, by the sub-authentication filter. Those are the SMB and remote-access paths from the list above. The \"if\" is real. Microsoft documents the filter for domain controllers, and whether MSV1_0 also calls it for local accounts on your workstation is the first thing you have to check in the VM.",[15,167,168,171],{},[48,169,170],{},"Not closed:"," Batch and service logons. They don't arrive at MSV1_0 as network logons, so our filter has no objection. For those you still need the user rights from the next chapter.",[15,173,174,177,178,180,181,184],{},[48,175,176],{},"Also not closed:"," ",[28,179,30],{},", the UAC credential prompt and any program that calls ",[28,182,183],{},"LogonUser"," interactively. Those are interactive logons without our tile. No user right separates them from the console logon, and the filter has no reason to refuse them. They still take the password alone.",[15,186,187,190],{},[48,188,189],{},"A side effect:"," RDP with Network Level Authentication checks the password with a network logon before the session starts. For an enrolled account the filter refuses that, just like the deny right does. More about this in the install chapter.",[15,192,193,196],{},[48,194,195],{},"And the authentication package closes nothing in practice."," It is only ever called when a logon addresses it by package id, which no normal logon does. It is in the project because it teaches the layer. I will not pretend otherwise.",[15,198,199,200,203,204,207],{},"So the honest summary: the LSA code here is where the ",[72,201,202],{},"decision"," lives. The user rights are where the ",[72,205,206],{},"enforcement"," lives. You want both.",[10,209,211],{"id":210},"two-ways-to-close-the-non-interactive-paths","Two ways to close the non-interactive paths",[15,213,214],{},"There are two ways to do this, and it is worth being clear which is which.",[15,216,217,220],{},[48,218,219],{},"The supported way needs no code at all."," Windows already has the switch: the \"Deny log on ...\" user rights. There is one each for network, batch, services and RDP. Set them for the account and those paths are closed, while the console logon with the 2FA tile keeps working. There is a script for it in the repo:",[222,223,228],"pre",{"className":224,"code":225,"language":226,"meta":227,"style":227},"language-powershell shiki shiki-themes github-dark",".\\deny-noninteractive.ps1 alice\n","powershell","",[28,229,230],{"__ignoreMap":227},[231,232,235,238,242],"span",{"class":233,"line":234},"line",1,[231,236,105],{"class":237},"s95oV",[231,239,241],{"class":240},"sDLfK","\\deny-noninteractive.ps1",[231,243,244],{"class":237}," alice\n",[15,246,247],{},"On a real machine this is the answer. It survives reboots, it cannot crash anything, and it took me ten minutes to write. If your goal is hardening, do this and skip the rest of the article.",[15,249,250,253],{},[48,251,252],{},"The educational way"," is to write the code that makes that decision yourself, inside LSA. You do not need it for the hardening above. You want it if you want to understand the layer, or build toward something bigger later: push approval, central policy, a TPM-bound secret. That is the rest of this article.",[10,255,257],{"id":256},"how-lsa-fits-together","How LSA fits together",[259,260,262],"h3",{"id":261},"the-extension-points","The extension points",[15,264,265,266,269],{},"LSA is a collection of packages. ",[28,267,268],{},"MSV1_0"," checks passwords against the local SAM, Kerberos does domain tickets, Negotiate picks between them. Each one is a DLL, and Windows has a few documented ways to add your own. For sub-authentication there are two different hooks, and the difference matters a lot. I learned that the hard way:",[271,272,273,289],"table",{},[274,275,276],"thead",{},[277,278,279,283,286],"tr",{},[280,281,282],"th",{},"Extension point",[280,284,285],{},"Registry",[280,287,288],{},"Called",[290,291,292,306,323,336,349],"tbody",{},[277,293,294,298,303],{},[295,296,297],"td",{},"Authentication package",[295,299,300],{},[28,301,302],{},"...\\Lsa\\Authentication Packages",[295,304,305],{},"only for logons that address it by id",[277,307,308,311,320],{},[295,309,310],{},"Sub-authentication routine",[295,312,313,316,317],{},[28,314,315],{},"...\\Lsa\\MSV1_0\\Auth1"," ... ",[28,318,319],{},"AuthN",[295,321,322],{},"by MSV1_0, only for logons that select it, and then it has to check the password itself",[277,324,325,328,333],{},[295,326,327],{},"Sub-authentication filter",[295,329,330],{},[28,331,332],{},"...\\Lsa\\MSV1_0\\Auth0",[295,334,335],{},"by MSV1_0, after it has checked the password, to veto",[277,337,338,341,346],{},[295,339,340],{},"Security Support Provider (SSP\u002FAP)",[295,342,343],{},[28,344,345],{},"...\\Lsa\\Security Packages",[295,347,348],{},"for logons across the board",[277,350,351,354,359],{},[295,352,353],{},"Password filter",[295,355,356],{},[28,357,358],{},"...\\Lsa\\Notification Packages",[295,360,361],{},"on password changes",[15,363,364],{},"We build the authentication package and the filter. The SSP\u002FAP is the interesting and dangerous one, and it gets its own chapter at the end, including why I am not building it.",[259,366,368],{"id":367},"where-our-two-pieces-sit","Where our two pieces sit",[15,370,371],{},"This is the part that took me longest to get straight, so here it is as a picture:",[15,373,374],{},[375,376],"img",{"alt":377,"src":378},"A logon arrives at LSA. One branch goes to our authentication package, which almost nothing takes. The normal path goes to MSV1_0, which checks the password, asks our sub-authentication filter for a veto and then builds the token.","\u002Fimages\u002Fposts\u002Fwindows-2fa-inside-lsa\u002Flsa-logon-flow.svg",[15,380,381,382,385],{},"The important difference is who calls whom. Our authentication package waits to be addressed and is therefore mostly idle. Our sub-auth filter is called ",[72,383,384],{},"by MSV1_0",", from inside the processing of a logon that MSV1_0 is already handling, after the password check. That is why it sees traffic the other one never will.",[10,387,389],{"id":388},"architecture-of-the-lsa-part","Architecture of the LSA part",[15,391,392],{},"Part 2 adds these files to the project:",[271,394,395,405],{},[274,396,397],{},[277,398,399,402],{},[280,400,401],{},"File",[280,403,404],{},"Purpose",[290,406,407,417,435,448,461,472,482],{},[277,408,409,414],{},[295,410,411],{},[28,412,413],{},"ap.h\u002F.cpp",[295,415,416],{},"The authentication package. Denies non-interactive logons addressed to it",[277,418,419,428],{},[295,420,421,424,425],{},[28,422,423],{},"subauth_filter.h",", ",[28,426,427],{},"subauth.cpp",[295,429,430,431,434],{},"The MSV1_0 sub-authentication filter (",[28,432,433],{},"Auth0","). The part that can actually refuse something",[277,436,437,445],{},[295,438,439,424,442],{},[28,440,441],{},"TacAuthPackage.def",[28,443,444],{},"TacSubAuth.def",[295,446,447],{},"The exports, because LSA looks them up by name",[277,449,450,458],{},[295,451,452,424,455],{},[28,453,454],{},"install-authpackage.ps1",[28,456,457],{},"uninstall-authpackage.ps1",[295,459,460],{},"Registration of the authentication package",[277,462,463,469],{},[295,464,465,424,467],{},[28,466,90],{},[28,468,94],{},[295,470,471],{},"Registration of the sub-auth filter",[277,473,474,479],{},[295,475,476],{},[28,477,478],{},"deny-noninteractive.ps1",[295,480,481],{},"The supported way, no code in LSA",[277,483,484,489],{},[295,485,486],{},[28,487,488],{},"build-lsa.bat",[295,490,491],{},"Builds only the two LSA DLLs",[15,493,494],{},"And it uses two small functions in the store from Part 1. They turned out to be the whole difficulty of this article, so they get their own chapter.",[15,496,497],{},[375,498],{"alt":499,"src":500},"enroll.exe writes the DPAPI-encrypted secret under the SID, and two presence markers: one keyed by the RID of the account, one by its name. The credential provider reads and decrypts the secret. The LSA code reads a marker with a single registry call, because DPAPI and account lookups would call back into LSA.","\u002Fimages\u002Fposts\u002Fwindows-2fa-inside-lsa\u002Flsa-secret-store.svg",[10,502,504],{"id":503},"the-authentication-package","The authentication package",[259,506,508],{"id":507},"what-lsa-calls","What LSA calls",[15,510,511],{},"You add your DLL's base name to a single registry value:",[222,513,518],{"className":514,"code":516,"language":517,"meta":227},[515],"language-text","HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\n    Authentication Packages = msv1_0   (REG_MULTI_SZ)\n","text",[28,519,516],{"__ignoreMap":227},[15,521,522],{},"LSA loads it at boot and calls the functions it exports, by name. The ones that matter:",[111,524,525,531,537,543],{},[114,526,527,530],{},[28,528,529],{},"LsaApInitializePackage"," runs once, at load. You get a dispatch table with LSA's own heap allocator, and you hand back your package name.",[114,532,533,536],{},[28,534,535],{},"LsaApLogonUserEx2"," runs once per logon that targets your package. This is where the decision happens.",[114,538,539,542],{},[28,540,541],{},"LsaApCallPackage"," and its two siblings are a side channel for tools to talk to the package later. We stub them.",[114,544,545,548],{},[28,546,547],{},"LsaApLogonTerminated"," is cleanup when a logon session ends.",[15,550,551,552,555],{},"Because the exports are looked up by name, they are plain C linkage and listed in a ",[28,553,554],{},".def"," file. Nothing decorated.",[15,557,558,561],{},[28,559,560],{},"ap.h",":",[222,563,567],{"className":564,"code":565,"language":566,"meta":227,"style":227},"language-cpp shiki shiki-themes github-dark","#pragma once\n\u002F\u002F\n\u002F\u002F Doppio LSA authentication package (SKELETON).\n\u002F\u002F\n\u002F\u002F This runs inside LSA (lsass.exe), not in LogonUI. An authentication package\n\u002F\u002F is only invoked for logons that address it by package id, so by itself it\n\u002F\u002F does NOT see the standard network\u002Fbatch logon paths - those go to MSV1_0 \u002F\n\u002F\u002F Negotiate. To gate those, see the sub-authentication filter (subauth.cpp),\n\u002F\u002F which MSV1_0 calls after it has validated a logon.\n\u002F\u002F\n\u002F\u002F Policy of this skeleton: for an enrolled account, deny every NON-interactive\n\u002F\u002F logon that reaches us. Interactive and unlock are left to the credential\n\u002F\u002F provider, which already asks for the TOTP.\n\u002F\u002F\n\u002F\u002F Be clear about what that is worth: NOTHING as a security control. A logon\n\u002F\u002F only reaches this package if the caller asks for it by package id, and every\n\u002F\u002F such logon fails anyway - denied, or declined with STATUS_NOT_IMPLEMENTED,\n\u002F\u002F because this package never builds a token. No real logon path is closed by\n\u002F\u002F loading it. It is here to show the LSA package interface and the logging,\n\u002F\u002F nothing more. The path that actually matters is the sub-authentication\n\u002F\u002F filter (subauth.cpp) or, on a real machine, the user rights.\n\u002F\u002F\n\u002F\u002F WARNING: this code runs in lsass.exe. A bug here does not fail one tile - it\n\u002F\u002F can crash LSA and leave the machine unbootable, past Safe Mode. Only ever\n\u002F\u002F load it in a throwaway VM with a snapshot, and debug it over a kernel\n\u002F\u002F debugger (WinDbg on a second machine). See the article chapter\n\u002F\u002F \"2FA inside LSA\" for the setup and the honest limits.\n\u002F\u002F\n\u002F\u002F The exports are looked up by name by LSA, so they are C-linkage and listed\n\u002F\u002F in TacAuthPackage.def.\n\n#define SECURITY_WIN32\n#include \u003Cwindows.h>\n#include \u003Csspi.h>\n#include \u003Cntsecapi.h>\n#include \u003Cntsecpkg.h>\n\nextern \"C\"\n{\n    NTSTATUS NTAPI LsaApInitializePackage(\n        ULONG AuthenticationPackageId,\n        PLSA_DISPATCH_TABLE LsaDispatchTable,\n        PLSA_STRING Database,\n        PLSA_STRING Confidentiality,\n        PLSA_STRING* AuthenticationPackageName);\n\n    NTSTATUS NTAPI LsaApLogonUserEx2(\n        PLSA_CLIENT_REQUEST ClientRequest,\n        SECURITY_LOGON_TYPE LogonType,\n        PVOID ProtocolSubmitBuffer,\n        PVOID ClientBufferBase,\n        ULONG SubmitBufferSize,\n        PVOID* ProfileBuffer,\n        PULONG ProfileBufferSize,\n        PLUID LogonId,\n        PNTSTATUS SubStatus,\n        PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,\n        PVOID* TokenInformation,\n        PUNICODE_STRING* AccountName,\n        PUNICODE_STRING* AuthenticatingAuthority,\n        PUNICODE_STRING* MachineName,\n        PSECPKG_PRIMARY_CRED PrimaryCredentials,\n        PSECPKG_SUPPLEMENTAL_CRED_ARRAY* CachedCredentials);\n\n    NTSTATUS NTAPI LsaApCallPackage(\n        PLSA_CLIENT_REQUEST ClientRequest, PVOID ProtocolSubmitBuffer,\n        PVOID ClientBufferBase, ULONG SubmitBufferLength,\n        PVOID* ProtocolReturnBuffer, PULONG ReturnBufferLength,\n        PNTSTATUS ProtocolStatus);\n\n    NTSTATUS NTAPI LsaApCallPackageUntrusted(\n        PLSA_CLIENT_REQUEST ClientRequest, PVOID ProtocolSubmitBuffer,\n        PVOID ClientBufferBase, ULONG SubmitBufferLength,\n        PVOID* ProtocolReturnBuffer, PULONG ReturnBufferLength,\n        PNTSTATUS ProtocolStatus);\n\n    NTSTATUS NTAPI LsaApCallPackagePassthrough(\n        PLSA_CLIENT_REQUEST ClientRequest, PVOID ProtocolSubmitBuffer,\n        PVOID ClientBufferBase, ULONG SubmitBufferLength,\n        PVOID* ProtocolReturnBuffer, PULONG ReturnBufferLength,\n        PNTSTATUS ProtocolStatus);\n\n    VOID NTAPI LsaApLogonTerminated(PLUID LogonId);\n}\n","cpp",[28,568,569,579,586,592,597,603,609,615,621,627,632,638,644,650,655,661,667,673,679,685,691,697,702,708,714,720,726,732,737,743,749,756,765,775,783,791,799,804,813,819,834,840,846,852,858,870,875,887,893,899,905,911,917,928,934,940,946,952,962,973,983,993,999,1010,1015,1027,1033,1039,1049,1055,1060,1072,1077,1082,1091,1096,1101,1113,1118,1123,1132,1137,1142,1156],{"__ignoreMap":227},[231,570,571,575],{"class":233,"line":234},[231,572,574],{"class":573},"snl16","#pragma",[231,576,578],{"class":577},"svObZ"," once\n",[231,580,582],{"class":233,"line":581},2,[231,583,585],{"class":584},"sAwPA","\u002F\u002F\n",[231,587,589],{"class":233,"line":588},3,[231,590,591],{"class":584},"\u002F\u002F Doppio LSA authentication package (SKELETON).\n",[231,593,595],{"class":233,"line":594},4,[231,596,585],{"class":584},[231,598,600],{"class":233,"line":599},5,[231,601,602],{"class":584},"\u002F\u002F This runs inside LSA (lsass.exe), not in LogonUI. An authentication package\n",[231,604,606],{"class":233,"line":605},6,[231,607,608],{"class":584},"\u002F\u002F is only invoked for logons that address it by package id, so by itself it\n",[231,610,612],{"class":233,"line":611},7,[231,613,614],{"class":584},"\u002F\u002F does NOT see the standard network\u002Fbatch logon paths - those go to MSV1_0 \u002F\n",[231,616,618],{"class":233,"line":617},8,[231,619,620],{"class":584},"\u002F\u002F Negotiate. To gate those, see the sub-authentication filter (subauth.cpp),\n",[231,622,624],{"class":233,"line":623},9,[231,625,626],{"class":584},"\u002F\u002F which MSV1_0 calls after it has validated a logon.\n",[231,628,630],{"class":233,"line":629},10,[231,631,585],{"class":584},[231,633,635],{"class":233,"line":634},11,[231,636,637],{"class":584},"\u002F\u002F Policy of this skeleton: for an enrolled account, deny every NON-interactive\n",[231,639,641],{"class":233,"line":640},12,[231,642,643],{"class":584},"\u002F\u002F logon that reaches us. Interactive and unlock are left to the credential\n",[231,645,647],{"class":233,"line":646},13,[231,648,649],{"class":584},"\u002F\u002F provider, which already asks for the TOTP.\n",[231,651,653],{"class":233,"line":652},14,[231,654,585],{"class":584},[231,656,658],{"class":233,"line":657},15,[231,659,660],{"class":584},"\u002F\u002F Be clear about what that is worth: NOTHING as a security control. A logon\n",[231,662,664],{"class":233,"line":663},16,[231,665,666],{"class":584},"\u002F\u002F only reaches this package if the caller asks for it by package id, and every\n",[231,668,670],{"class":233,"line":669},17,[231,671,672],{"class":584},"\u002F\u002F such logon fails anyway - denied, or declined with STATUS_NOT_IMPLEMENTED,\n",[231,674,676],{"class":233,"line":675},18,[231,677,678],{"class":584},"\u002F\u002F because this package never builds a token. No real logon path is closed by\n",[231,680,682],{"class":233,"line":681},19,[231,683,684],{"class":584},"\u002F\u002F loading it. It is here to show the LSA package interface and the logging,\n",[231,686,688],{"class":233,"line":687},20,[231,689,690],{"class":584},"\u002F\u002F nothing more. The path that actually matters is the sub-authentication\n",[231,692,694],{"class":233,"line":693},21,[231,695,696],{"class":584},"\u002F\u002F filter (subauth.cpp) or, on a real machine, the user rights.\n",[231,698,700],{"class":233,"line":699},22,[231,701,585],{"class":584},[231,703,705],{"class":233,"line":704},23,[231,706,707],{"class":584},"\u002F\u002F WARNING: this code runs in lsass.exe. A bug here does not fail one tile - it\n",[231,709,711],{"class":233,"line":710},24,[231,712,713],{"class":584},"\u002F\u002F can crash LSA and leave the machine unbootable, past Safe Mode. Only ever\n",[231,715,717],{"class":233,"line":716},25,[231,718,719],{"class":584},"\u002F\u002F load it in a throwaway VM with a snapshot, and debug it over a kernel\n",[231,721,723],{"class":233,"line":722},26,[231,724,725],{"class":584},"\u002F\u002F debugger (WinDbg on a second machine). See the article chapter\n",[231,727,729],{"class":233,"line":728},27,[231,730,731],{"class":584},"\u002F\u002F \"2FA inside LSA\" for the setup and the honest limits.\n",[231,733,735],{"class":233,"line":734},28,[231,736,585],{"class":584},[231,738,740],{"class":233,"line":739},29,[231,741,742],{"class":584},"\u002F\u002F The exports are looked up by name by LSA, so they are C-linkage and listed\n",[231,744,746],{"class":233,"line":745},30,[231,747,748],{"class":584},"\u002F\u002F in TacAuthPackage.def.\n",[231,750,752],{"class":233,"line":751},31,[231,753,755],{"emptyLinePlaceholder":754},true,"\n",[231,757,759,762],{"class":233,"line":758},32,[231,760,761],{"class":573},"#define",[231,763,764],{"class":577}," SECURITY_WIN32\n",[231,766,768,771],{"class":233,"line":767},33,[231,769,770],{"class":573},"#include",[231,772,774],{"class":773},"sU2Wk"," \u003Cwindows.h>\n",[231,776,778,780],{"class":233,"line":777},34,[231,779,770],{"class":573},[231,781,782],{"class":773}," \u003Csspi.h>\n",[231,784,786,788],{"class":233,"line":785},35,[231,787,770],{"class":573},[231,789,790],{"class":773}," \u003Cntsecapi.h>\n",[231,792,794,796],{"class":233,"line":793},36,[231,795,770],{"class":573},[231,797,798],{"class":773}," \u003Cntsecpkg.h>\n",[231,800,802],{"class":233,"line":801},37,[231,803,755],{"emptyLinePlaceholder":754},[231,805,807,810],{"class":233,"line":806},38,[231,808,809],{"class":573},"extern",[231,811,812],{"class":773}," \"C\"\n",[231,814,816],{"class":233,"line":815},39,[231,817,818],{"class":237},"{\n",[231,820,822,825,828,831],{"class":233,"line":821},40,[231,823,824],{"class":237},"    NTSTATUS ",[231,826,827],{"class":577},"NTAPI",[231,829,830],{"class":577}," LsaApInitializePackage",[231,832,833],{"class":237},"(\n",[231,835,837],{"class":233,"line":836},41,[231,838,839],{"class":237},"        ULONG AuthenticationPackageId,\n",[231,841,843],{"class":233,"line":842},42,[231,844,845],{"class":237},"        PLSA_DISPATCH_TABLE LsaDispatchTable,\n",[231,847,849],{"class":233,"line":848},43,[231,850,851],{"class":237},"        PLSA_STRING Database,\n",[231,853,855],{"class":233,"line":854},44,[231,856,857],{"class":237},"        PLSA_STRING Confidentiality,\n",[231,859,861,864,867],{"class":233,"line":860},45,[231,862,863],{"class":237},"        PLSA_STRING",[231,865,866],{"class":573},"*",[231,868,869],{"class":237}," AuthenticationPackageName);\n",[231,871,873],{"class":233,"line":872},46,[231,874,755],{"emptyLinePlaceholder":754},[231,876,878,880,882,885],{"class":233,"line":877},47,[231,879,824],{"class":237},[231,881,827],{"class":577},[231,883,884],{"class":577}," LsaApLogonUserEx2",[231,886,833],{"class":237},[231,888,890],{"class":233,"line":889},48,[231,891,892],{"class":237},"        PLSA_CLIENT_REQUEST ClientRequest,\n",[231,894,896],{"class":233,"line":895},49,[231,897,898],{"class":237},"        SECURITY_LOGON_TYPE LogonType,\n",[231,900,902],{"class":233,"line":901},50,[231,903,904],{"class":237},"        PVOID ProtocolSubmitBuffer,\n",[231,906,908],{"class":233,"line":907},51,[231,909,910],{"class":237},"        PVOID ClientBufferBase,\n",[231,912,914],{"class":233,"line":913},52,[231,915,916],{"class":237},"        ULONG SubmitBufferSize,\n",[231,918,920,923,925],{"class":233,"line":919},53,[231,921,922],{"class":237},"        PVOID",[231,924,866],{"class":573},[231,926,927],{"class":237}," ProfileBuffer,\n",[231,929,931],{"class":233,"line":930},54,[231,932,933],{"class":237},"        PULONG ProfileBufferSize,\n",[231,935,937],{"class":233,"line":936},55,[231,938,939],{"class":237},"        PLUID LogonId,\n",[231,941,943],{"class":233,"line":942},56,[231,944,945],{"class":237},"        PNTSTATUS SubStatus,\n",[231,947,949],{"class":233,"line":948},57,[231,950,951],{"class":237},"        PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,\n",[231,953,955,957,959],{"class":233,"line":954},58,[231,956,922],{"class":237},[231,958,866],{"class":573},[231,960,961],{"class":237}," TokenInformation,\n",[231,963,965,968,970],{"class":233,"line":964},59,[231,966,967],{"class":237},"        PUNICODE_STRING",[231,969,866],{"class":573},[231,971,972],{"class":237}," AccountName,\n",[231,974,976,978,980],{"class":233,"line":975},60,[231,977,967],{"class":237},[231,979,866],{"class":573},[231,981,982],{"class":237}," AuthenticatingAuthority,\n",[231,984,986,988,990],{"class":233,"line":985},61,[231,987,967],{"class":237},[231,989,866],{"class":573},[231,991,992],{"class":237}," MachineName,\n",[231,994,996],{"class":233,"line":995},62,[231,997,998],{"class":237},"        PSECPKG_PRIMARY_CRED PrimaryCredentials,\n",[231,1000,1002,1005,1007],{"class":233,"line":1001},63,[231,1003,1004],{"class":237},"        PSECPKG_SUPPLEMENTAL_CRED_ARRAY",[231,1006,866],{"class":573},[231,1008,1009],{"class":237}," CachedCredentials);\n",[231,1011,1013],{"class":233,"line":1012},64,[231,1014,755],{"emptyLinePlaceholder":754},[231,1016,1018,1020,1022,1025],{"class":233,"line":1017},65,[231,1019,824],{"class":237},[231,1021,827],{"class":577},[231,1023,1024],{"class":577}," LsaApCallPackage",[231,1026,833],{"class":237},[231,1028,1030],{"class":233,"line":1029},66,[231,1031,1032],{"class":237},"        PLSA_CLIENT_REQUEST ClientRequest, PVOID ProtocolSubmitBuffer,\n",[231,1034,1036],{"class":233,"line":1035},67,[231,1037,1038],{"class":237},"        PVOID ClientBufferBase, ULONG SubmitBufferLength,\n",[231,1040,1042,1044,1046],{"class":233,"line":1041},68,[231,1043,922],{"class":237},[231,1045,866],{"class":573},[231,1047,1048],{"class":237}," ProtocolReturnBuffer, PULONG ReturnBufferLength,\n",[231,1050,1052],{"class":233,"line":1051},69,[231,1053,1054],{"class":237},"        PNTSTATUS ProtocolStatus);\n",[231,1056,1058],{"class":233,"line":1057},70,[231,1059,755],{"emptyLinePlaceholder":754},[231,1061,1063,1065,1067,1070],{"class":233,"line":1062},71,[231,1064,824],{"class":237},[231,1066,827],{"class":577},[231,1068,1069],{"class":577}," LsaApCallPackageUntrusted",[231,1071,833],{"class":237},[231,1073,1075],{"class":233,"line":1074},72,[231,1076,1032],{"class":237},[231,1078,1080],{"class":233,"line":1079},73,[231,1081,1038],{"class":237},[231,1083,1085,1087,1089],{"class":233,"line":1084},74,[231,1086,922],{"class":237},[231,1088,866],{"class":573},[231,1090,1048],{"class":237},[231,1092,1094],{"class":233,"line":1093},75,[231,1095,1054],{"class":237},[231,1097,1099],{"class":233,"line":1098},76,[231,1100,755],{"emptyLinePlaceholder":754},[231,1102,1104,1106,1108,1111],{"class":233,"line":1103},77,[231,1105,824],{"class":237},[231,1107,827],{"class":577},[231,1109,1110],{"class":577}," LsaApCallPackagePassthrough",[231,1112,833],{"class":237},[231,1114,1116],{"class":233,"line":1115},78,[231,1117,1032],{"class":237},[231,1119,1121],{"class":233,"line":1120},79,[231,1122,1038],{"class":237},[231,1124,1126,1128,1130],{"class":233,"line":1125},80,[231,1127,922],{"class":237},[231,1129,866],{"class":573},[231,1131,1048],{"class":237},[231,1133,1135],{"class":233,"line":1134},81,[231,1136,1054],{"class":237},[231,1138,1140],{"class":233,"line":1139},82,[231,1141,755],{"emptyLinePlaceholder":754},[231,1143,1145,1148,1150,1153],{"class":233,"line":1144},83,[231,1146,1147],{"class":237},"    VOID ",[231,1149,827],{"class":577},[231,1151,1152],{"class":577}," LsaApLogonTerminated",[231,1154,1155],{"class":237},"(PLUID LogonId);\n",[231,1157,1159],{"class":233,"line":1158},84,[231,1160,1161],{"class":237},"}\n",[15,1163,1164,1165,1168,1169,1172,1173,1176,1177,1180,1181,1184],{},"The includes need a word of explanation, because I lost time on them. ",[28,1166,1167],{},"ntsecpkg.h"," does not compile on its own. You need ",[28,1170,1171],{},"SECURITY_WIN32"," defined and ",[28,1174,1175],{},"sspi.h"," included before it, otherwise you get a wall of errors about undeclared types. And ",[28,1178,1179],{},"ntstatus.h"," clashes with ",[28,1182,1183],{},"windows.h",", so instead of including it I define the status values I return by hand.",[259,1186,1188],{"id":1187},"the-logon-type","The logon type",[15,1190,1191,1193,1194,1197],{},[28,1192,535],{}," gets handed the ",[28,1195,1196],{},"SECURITY_LOGON_TYPE"," directly. That is an enum, and it tells you exactly what kind of logon this is without parsing anything:",[271,1199,1200,1213],{},[274,1201,1202],{},[277,1203,1204,1207,1210],{},[280,1205,1206],{},"Value",[280,1208,1209],{},"Name",[280,1211,1212],{},"What it is",[290,1214,1215,1228,1241,1254,1267,1280,1293,1308,1321],{},[277,1216,1217,1220,1225],{},[295,1218,1219],{},"2",[295,1221,1222],{},[28,1223,1224],{},"Interactive",[295,1226,1227],{},"console logon",[277,1229,1230,1233,1238],{},[295,1231,1232],{},"3",[295,1234,1235],{},[28,1236,1237],{},"Network",[295,1239,1240],{},"SMB and most remote access",[277,1242,1243,1246,1251],{},[295,1244,1245],{},"4",[295,1247,1248],{},[28,1249,1250],{},"Batch",[295,1252,1253],{},"scheduled tasks",[277,1255,1256,1259,1264],{},[295,1257,1258],{},"5",[295,1260,1261],{},[28,1262,1263],{},"Service",[295,1265,1266],{},"service accounts",[277,1268,1269,1272,1277],{},[295,1270,1271],{},"7",[295,1273,1274],{},[28,1275,1276],{},"Unlock",[295,1278,1279],{},"unlock after locking the screen",[277,1281,1282,1285,1290],{},[295,1283,1284],{},"8",[295,1286,1287],{},[28,1288,1289],{},"NetworkCleartext",[295,1291,1292],{},"e.g. IIS basic auth",[277,1294,1295,1298,1303],{},[295,1296,1297],{},"9",[295,1299,1300],{},[28,1301,1302],{},"NewCredentials",[295,1304,1305],{},[28,1306,1307],{},"runas \u002Fnetonly",[277,1309,1310,1313,1318],{},[295,1311,1312],{},"10",[295,1314,1315],{},[28,1316,1317],{},"RemoteInteractive",[295,1319,1320],{},"RDP",[277,1322,1323,1326,1331],{},[295,1324,1325],{},"11",[295,1327,1328],{},[28,1329,1330],{},"CachedInteractive",[295,1332,1333],{},"cached domain credentials",[15,1335,1336],{},"Interactive, Unlock and RemoteInteractive all have a UI where our tile from Part 1 asks for the code. Those we let through. The five in the middle have no UI, so for an enrolled account there is no second factor possible, and the honest answer is to refuse them.",[259,1338,1340],{"id":1339},"reading-the-account-name-from-the-submit-buffer","Reading the account name from the submit buffer",[15,1342,1343,1344,1347],{},"In my first draft I left one thing deliberately off: reading ",[72,1345,1346],{},"which"," account is logging in. Without it the package can only deny everyone, which is useless. With it, doing it safely is the whole lesson.",[15,1349,1350,1351,1354,1355,1358,1359,1362,1363,1366],{},"The account name lives in the submit buffer, in a ",[28,1352,1353],{},"UNICODE_STRING",". The catch is that its ",[28,1356,1357],{},"Buffer"," pointer is valid in the ",[48,1360,1361],{},"client's"," address space, not ours. LSA hands us ",[28,1364,1365],{},"ClientBufferBase",", the address the buffer had over there, so we can relocate it into our own copy.",[15,1368,1369],{},"Get this wrong and you dereference a wild pointer inside lsass, at boot. So every step is bounds-checked, and anything that does not add up returns an empty string, which makes the caller treat the account as not enrolled and let the logon pass.",[259,1371,1373],{"id":1372},"why-the-check-is-a-plain-registry-read","Why the check is a plain registry read",[15,1375,1376,1377,1380,1381,1384],{},"My first version did the obvious thing. Resolve the name to a SID with ",[28,1378,1379],{},"LookupAccountName",", then call ",[28,1382,1383],{},"LoadSecretKey"," from Part 1 to decrypt the secret and confirm it exists. That works on a bench, and it is exactly how you get a boot loop.",[15,1386,1387,1388,1391,1392,1394,1395,1398],{},"The reason is where this code runs. The sub-authentication filter runs ",[72,1389,1390],{},"inside"," MSV1_0's logon processing, inside lsass, with LSA's locks held. ",[28,1393,1379],{}," calls back into LSA. ",[28,1396,1397],{},"CryptUnprotectData"," with machine scope calls back into lsass's own DPAPI. Re-entering LSA from a call LSA is already in the middle of is how you deadlock the logon path.",[15,1400,1401],{},"Think this through for a second. You will not see it in a user-mode test, because in a test your process is not lsass and there is no lock to deadlock on. You see it when the VM hangs at boot with no error and no event, and you reach for the snapshot.",[15,1403,1404,1405,1408,1409,1412],{},"So the decision has to run without re-entering LSA. The secret stays keyed by SID and DPAPI-encrypted, for the credential provider. On top of that, ",[28,1406,1407],{},"StoreSecret"," also writes two tiny ",[48,1410,1411],{},"indexes"," under a locked-down key: one keyed by the RID of the account, one by its name. Just markers, no secret. The hot path does a single registry read, which is served by the kernel's configuration manager and not by LSA's auth logic, so it is safe to call from here.",[15,1414,1415,1416,561],{},"Added to ",[28,1417,1418],{},"store.h",[222,1420,1422],{"className":564,"code":1421,"language":566,"meta":227,"style":227},"    \u002F\u002F The last sub-authority of a SID string, the RID (\"...-1001\" -> 1001).\n    \u002F\u002F Returns false for anything that is not a well-formed SID string.\n    bool RidFromSidString(const std::wstring& sid, DWORD& rid);\n\n    \u002F\u002F Hot-path enrollment checks for code that runs INSIDE lsass. Each is a\n    \u002F\u002F single registry read: no DPAPI decrypt and no account-name or SID lookup,\n    \u002F\u002F so nothing calls back into LSA and the logon path cannot deadlock.\n    \u002F\u002F\n    \u002F\u002F IsEnrolledByRid is the one to use whenever the caller has the account's\n    \u002F\u002F RID from the SAM (the MSV1_0 sub-authentication filter gets it in\n    \u002F\u002F USER_ALL_INFORMATION). A RID survives a rename and the SAM never hands\n    \u002F\u002F out a RID twice, so this index can neither miss a renamed account nor\n    \u002F\u002F match a different, re-created one.\n    bool IsEnrolledByRid(DWORD rid);\n\n    \u002F\u002F IsEnrolledByName is only for callers that have nothing but the name a\n    \u002F\u002F client typed (the authentication package). It is weaker, and fails OPEN\n    \u002F\u002F in one case: after an enrolled account is renamed, the index still holds\n    \u002F\u002F the old name, so the new name reads as \"not enrolled\" until\n    \u002F\u002F `enroll \u002Freindex` runs. A re-created account with an old name reads as\n    \u002F\u002F enrolled (more restrictive). Prefer IsEnrolledByRid.\n    bool IsEnrolledByName(const std::wstring& user);\n",[28,1423,1424,1429,1434,1477,1481,1486,1491,1496,1501,1506,1511,1516,1521,1526,1541,1545,1550,1555,1560,1565,1570,1575],{"__ignoreMap":227},[231,1425,1426],{"class":233,"line":234},[231,1427,1428],{"class":584},"    \u002F\u002F The last sub-authority of a SID string, the RID (\"...-1001\" -> 1001).\n",[231,1430,1431],{"class":233,"line":581},[231,1432,1433],{"class":584},"    \u002F\u002F Returns false for anything that is not a well-formed SID string.\n",[231,1435,1436,1439,1442,1445,1448,1451,1454,1457,1460,1464,1466,1469,1471,1474],{"class":233,"line":588},[231,1437,1438],{"class":573},"    bool",[231,1440,1441],{"class":577}," RidFromSidString",[231,1443,1444],{"class":237},"(",[231,1446,1447],{"class":573},"const",[231,1449,1450],{"class":577}," std",[231,1452,1453],{"class":237},"::",[231,1455,1456],{"class":577},"wstring",[231,1458,1459],{"class":573},"&",[231,1461,1463],{"class":1462},"s9osk"," sid",[231,1465,424],{"class":237},[231,1467,1468],{"class":577},"DWORD",[231,1470,1459],{"class":573},[231,1472,1473],{"class":1462}," rid",[231,1475,1476],{"class":237},");\n",[231,1478,1479],{"class":233,"line":594},[231,1480,755],{"emptyLinePlaceholder":754},[231,1482,1483],{"class":233,"line":599},[231,1484,1485],{"class":584},"    \u002F\u002F Hot-path enrollment checks for code that runs INSIDE lsass. Each is a\n",[231,1487,1488],{"class":233,"line":605},[231,1489,1490],{"class":584},"    \u002F\u002F single registry read: no DPAPI decrypt and no account-name or SID lookup,\n",[231,1492,1493],{"class":233,"line":611},[231,1494,1495],{"class":584},"    \u002F\u002F so nothing calls back into LSA and the logon path cannot deadlock.\n",[231,1497,1498],{"class":233,"line":617},[231,1499,1500],{"class":584},"    \u002F\u002F\n",[231,1502,1503],{"class":233,"line":623},[231,1504,1505],{"class":584},"    \u002F\u002F IsEnrolledByRid is the one to use whenever the caller has the account's\n",[231,1507,1508],{"class":233,"line":629},[231,1509,1510],{"class":584},"    \u002F\u002F RID from the SAM (the MSV1_0 sub-authentication filter gets it in\n",[231,1512,1513],{"class":233,"line":634},[231,1514,1515],{"class":584},"    \u002F\u002F USER_ALL_INFORMATION). A RID survives a rename and the SAM never hands\n",[231,1517,1518],{"class":233,"line":640},[231,1519,1520],{"class":584},"    \u002F\u002F out a RID twice, so this index can neither miss a renamed account nor\n",[231,1522,1523],{"class":233,"line":646},[231,1524,1525],{"class":584},"    \u002F\u002F match a different, re-created one.\n",[231,1527,1528,1530,1533,1535,1537,1539],{"class":233,"line":652},[231,1529,1438],{"class":573},[231,1531,1532],{"class":577}," IsEnrolledByRid",[231,1534,1444],{"class":237},[231,1536,1468],{"class":577},[231,1538,1473],{"class":1462},[231,1540,1476],{"class":237},[231,1542,1543],{"class":233,"line":657},[231,1544,755],{"emptyLinePlaceholder":754},[231,1546,1547],{"class":233,"line":663},[231,1548,1549],{"class":584},"    \u002F\u002F IsEnrolledByName is only for callers that have nothing but the name a\n",[231,1551,1552],{"class":233,"line":669},[231,1553,1554],{"class":584},"    \u002F\u002F client typed (the authentication package). It is weaker, and fails OPEN\n",[231,1556,1557],{"class":233,"line":675},[231,1558,1559],{"class":584},"    \u002F\u002F in one case: after an enrolled account is renamed, the index still holds\n",[231,1561,1562],{"class":233,"line":681},[231,1563,1564],{"class":584},"    \u002F\u002F the old name, so the new name reads as \"not enrolled\" until\n",[231,1566,1567],{"class":233,"line":687},[231,1568,1569],{"class":584},"    \u002F\u002F `enroll \u002Freindex` runs. A re-created account with an old name reads as\n",[231,1571,1572],{"class":233,"line":693},[231,1573,1574],{"class":584},"    \u002F\u002F enrolled (more restrictive). Prefer IsEnrolledByRid.\n",[231,1576,1577,1579,1582,1584,1586,1588,1590,1592,1594,1597],{"class":233,"line":699},[231,1578,1438],{"class":573},[231,1580,1581],{"class":577}," IsEnrolledByName",[231,1583,1444],{"class":237},[231,1585,1447],{"class":573},[231,1587,1450],{"class":577},[231,1589,1453],{"class":237},[231,1591,1456],{"class":577},[231,1593,1459],{"class":573},[231,1595,1596],{"class":1462}," user",[231,1598,1476],{"class":237},[15,1600,1415,1601,561],{},[28,1602,1603],{},"store.cpp",[222,1605,1607],{"className":564,"code":1606,"language":566,"meta":227,"style":227},"    \u002F\u002F Enrollment indexes for the LSA hot path (presence only, no secret).\n    static const wchar_t kNamePath[]  = L\"SOFTWARE\\\\TheAdminCafe\\\\2FA\\\\Names\";\n    static const wchar_t kRidPath[]   = L\"SOFTWARE\\\\TheAdminCafe\\\\2FA\\\\Rids\";\n\n    bool RidFromSidString(const std::wstring& sid, DWORD& rid)\n    {\n        rid = 0;\n        if (!IsSidString(sid))\n            return false;\n        size_t dash = sid.find_last_of(L'-');\n        if (dash == std::wstring::npos || dash + 1 >= sid.size() || sid.size() - dash - 1 > 10)\n            return false;\n\n        uint64_t value = 0;\n        for (size_t i = dash + 1; i \u003C sid.size(); ++i)\n        {\n            if (sid[i] \u003C L'0' || sid[i] > L'9')\n                return false;\n            value = value * 10 + static_cast\u003Cuint64_t>(sid[i] - L'0');\n        }\n        if (value == 0 || value > 0xFFFFFFFFull)\n            return false;\n        rid = static_cast\u003CDWORD>(value);\n        return true;\n    }\n\n    \u002F\u002F SID string -> normalized local account name. Used only at enroll time (a\n    \u002F\u002F console app), never on the LSA hot path.\n    static bool NameFromSid(const std::wstring& sidStr, std::wstring& name)\n    {\n        name.clear();\n        PSID psid = nullptr;\n        if (!ConvertStringSidToSidW(sidStr.c_str(), &psid))\n            return false;\n\n        WCHAR n[256] = {}; DWORD cn = ARRAYSIZE(n);\n        WCHAR d[256] = {}; DWORD cd = ARRAYSIZE(d);\n        SID_NAME_USE use;\n        BOOL ok = LookupAccountSidW(nullptr, psid, n, &cn, d, &cd, &use);\n        LocalFree(psid);\n        if (!ok)\n            return false;\n\n        name = NormalizeUser(n);\n        return !name.empty();\n    }\n\n    bool IsEnrolledByRid(DWORD rid)\n    {\n        if (rid == 0)\n            return false;\n\n        \u002F\u002F A single registry read, like IsEnrolledByName below.\n        HKEY hKey = nullptr;\n        if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kRidPath, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)\n            return false;\n\n        WCHAR value[16];\n        swprintf_s(value, L\"%lu\", static_cast\u003Cunsigned long>(rid));\n        LSTATUS status = RegQueryValueExW(hKey, value, nullptr, nullptr, nullptr, nullptr);\n        RegCloseKey(hKey);\n        return status == ERROR_SUCCESS;\n    }\n\n    bool IsEnrolledByName(const std::wstring& user)\n    {\n        std::wstring name = NormalizeUser(user);\n        if (name.empty())\n            return false;\n\n        \u002F\u002F A single registry read. No DPAPI, no LookupAccount* - nothing that\n        \u002F\u002F re-enters LSA - so this is safe to call from inside lsass on the\n        \u002F\u002F logon path.\n        HKEY hKey = nullptr;\n        if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kNamePath, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)\n            return false;\n\n        LSTATUS status = RegQueryValueExW(hKey, name.c_str(), nullptr, nullptr, nullptr, nullptr);\n        RegCloseKey(hKey);\n        return status == ERROR_SUCCESS;\n    }\n",[28,1608,1609,1614,1653,1683,1687,1718,1723,1735,1752,1762,1785,1851,1859,1863,1877,1917,1922,1949,1958,1986,1991,2019,2027,2043,2053,2058,2062,2067,2072,2113,2117,2128,2143,2168,2176,2180,2204,2225,2233,2269,2277,2288,2296,2300,2312,2327,2331,2335,2349,2353,2366,2374,2378,2383,2397,2426,2434,2438,2452,2480,2512,2520,2531,2535,2539,2561,2565,2584,2596,2604,2608,2613,2618,2623,2635,2658,2666,2670,2703,2709,2719],{"__ignoreMap":227},[231,1610,1611],{"class":233,"line":234},[231,1612,1613],{"class":584},"    \u002F\u002F Enrollment indexes for the LSA hot path (presence only, no secret).\n",[231,1615,1616,1619,1622,1625,1628,1631,1634,1637,1640,1642,1645,1647,1650],{"class":233,"line":581},[231,1617,1618],{"class":573},"    static",[231,1620,1621],{"class":573}," const",[231,1623,1624],{"class":573}," wchar_t",[231,1626,1627],{"class":237}," kNamePath[]  ",[231,1629,1630],{"class":573},"=",[231,1632,1633],{"class":773}," L\"SOFTWARE",[231,1635,1636],{"class":240},"\\\\",[231,1638,1639],{"class":773},"TheAdminCafe",[231,1641,1636],{"class":240},[231,1643,1644],{"class":773},"2FA",[231,1646,1636],{"class":240},[231,1648,1649],{"class":773},"Names\"",[231,1651,1652],{"class":237},";\n",[231,1654,1655,1657,1659,1661,1664,1666,1668,1670,1672,1674,1676,1678,1681],{"class":233,"line":588},[231,1656,1618],{"class":573},[231,1658,1621],{"class":573},[231,1660,1624],{"class":573},[231,1662,1663],{"class":237}," kRidPath[]   ",[231,1665,1630],{"class":573},[231,1667,1633],{"class":773},[231,1669,1636],{"class":240},[231,1671,1639],{"class":773},[231,1673,1636],{"class":240},[231,1675,1644],{"class":773},[231,1677,1636],{"class":240},[231,1679,1680],{"class":773},"Rids\"",[231,1682,1652],{"class":237},[231,1684,1685],{"class":233,"line":594},[231,1686,755],{"emptyLinePlaceholder":754},[231,1688,1689,1691,1693,1695,1697,1699,1701,1703,1705,1707,1709,1711,1713,1715],{"class":233,"line":599},[231,1690,1438],{"class":573},[231,1692,1441],{"class":577},[231,1694,1444],{"class":237},[231,1696,1447],{"class":573},[231,1698,1450],{"class":577},[231,1700,1453],{"class":237},[231,1702,1456],{"class":577},[231,1704,1459],{"class":573},[231,1706,1463],{"class":1462},[231,1708,424],{"class":237},[231,1710,1468],{"class":577},[231,1712,1459],{"class":573},[231,1714,1473],{"class":1462},[231,1716,1717],{"class":237},")\n",[231,1719,1720],{"class":233,"line":605},[231,1721,1722],{"class":237},"    {\n",[231,1724,1725,1728,1730,1733],{"class":233,"line":611},[231,1726,1727],{"class":237},"        rid ",[231,1729,1630],{"class":573},[231,1731,1732],{"class":240}," 0",[231,1734,1652],{"class":237},[231,1736,1737,1740,1743,1746,1749],{"class":233,"line":617},[231,1738,1739],{"class":573},"        if",[231,1741,1742],{"class":237}," (",[231,1744,1745],{"class":573},"!",[231,1747,1748],{"class":577},"IsSidString",[231,1750,1751],{"class":237},"(sid))\n",[231,1753,1754,1757,1760],{"class":233,"line":623},[231,1755,1756],{"class":573},"            return",[231,1758,1759],{"class":240}," false",[231,1761,1652],{"class":237},[231,1763,1764,1767,1770,1772,1775,1778,1780,1783],{"class":233,"line":629},[231,1765,1766],{"class":573},"        size_t",[231,1768,1769],{"class":237}," dash ",[231,1771,1630],{"class":573},[231,1773,1774],{"class":237}," sid.",[231,1776,1777],{"class":577},"find_last_of",[231,1779,1444],{"class":237},[231,1781,1782],{"class":773},"L'-'",[231,1784,1476],{"class":237},[231,1786,1787,1789,1792,1795,1797,1799,1801,1804,1807,1809,1812,1815,1818,1820,1823,1826,1828,1830,1832,1834,1837,1839,1841,1843,1846,1849],{"class":233,"line":634},[231,1788,1739],{"class":573},[231,1790,1791],{"class":237}," (dash ",[231,1793,1794],{"class":573},"==",[231,1796,1450],{"class":577},[231,1798,1453],{"class":237},[231,1800,1456],{"class":577},[231,1802,1803],{"class":237},"::npos ",[231,1805,1806],{"class":573},"||",[231,1808,1769],{"class":237},[231,1810,1811],{"class":573},"+",[231,1813,1814],{"class":240}," 1",[231,1816,1817],{"class":573}," >=",[231,1819,1774],{"class":237},[231,1821,1822],{"class":577},"size",[231,1824,1825],{"class":237},"() ",[231,1827,1806],{"class":573},[231,1829,1774],{"class":237},[231,1831,1822],{"class":577},[231,1833,1825],{"class":237},[231,1835,1836],{"class":573},"-",[231,1838,1769],{"class":237},[231,1840,1836],{"class":573},[231,1842,1814],{"class":240},[231,1844,1845],{"class":573}," >",[231,1847,1848],{"class":240}," 10",[231,1850,1717],{"class":237},[231,1852,1853,1855,1857],{"class":233,"line":640},[231,1854,1756],{"class":573},[231,1856,1759],{"class":240},[231,1858,1652],{"class":237},[231,1860,1861],{"class":233,"line":646},[231,1862,755],{"emptyLinePlaceholder":754},[231,1864,1865,1868,1871,1873,1875],{"class":233,"line":652},[231,1866,1867],{"class":573},"        uint64_t",[231,1869,1870],{"class":237}," value ",[231,1872,1630],{"class":573},[231,1874,1732],{"class":240},[231,1876,1652],{"class":237},[231,1878,1879,1882,1884,1887,1890,1892,1894,1896,1898,1901,1904,1906,1908,1911,1914],{"class":233,"line":657},[231,1880,1881],{"class":573},"        for",[231,1883,1742],{"class":237},[231,1885,1886],{"class":573},"size_t",[231,1888,1889],{"class":237}," i ",[231,1891,1630],{"class":573},[231,1893,1769],{"class":237},[231,1895,1811],{"class":573},[231,1897,1814],{"class":240},[231,1899,1900],{"class":237},"; i ",[231,1902,1903],{"class":573},"\u003C",[231,1905,1774],{"class":237},[231,1907,1822],{"class":577},[231,1909,1910],{"class":237},"(); ",[231,1912,1913],{"class":573},"++",[231,1915,1916],{"class":237},"i)\n",[231,1918,1919],{"class":233,"line":663},[231,1920,1921],{"class":237},"        {\n",[231,1923,1924,1927,1930,1932,1935,1938,1941,1944,1947],{"class":233,"line":669},[231,1925,1926],{"class":573},"            if",[231,1928,1929],{"class":237}," (sid[i] ",[231,1931,1903],{"class":573},[231,1933,1934],{"class":773}," L'0'",[231,1936,1937],{"class":573}," ||",[231,1939,1940],{"class":237}," sid[i] ",[231,1942,1943],{"class":573},">",[231,1945,1946],{"class":773}," L'9'",[231,1948,1717],{"class":237},[231,1950,1951,1954,1956],{"class":233,"line":675},[231,1952,1953],{"class":573},"                return",[231,1955,1759],{"class":240},[231,1957,1652],{"class":237},[231,1959,1960,1963,1965,1967,1969,1971,1974,1977,1980,1982,1984],{"class":233,"line":681},[231,1961,1962],{"class":237},"            value ",[231,1964,1630],{"class":573},[231,1966,1870],{"class":237},[231,1968,866],{"class":573},[231,1970,1848],{"class":240},[231,1972,1973],{"class":573}," +",[231,1975,1976],{"class":573}," static_cast\u003Cuint64_t>",[231,1978,1979],{"class":237},"(sid[i] ",[231,1981,1836],{"class":573},[231,1983,1934],{"class":773},[231,1985,1476],{"class":237},[231,1987,1988],{"class":233,"line":687},[231,1989,1990],{"class":237},"        }\n",[231,1992,1993,1995,1998,2000,2002,2004,2006,2008,2011,2014,2017],{"class":233,"line":693},[231,1994,1739],{"class":573},[231,1996,1997],{"class":237}," (value ",[231,1999,1794],{"class":573},[231,2001,1732],{"class":240},[231,2003,1937],{"class":573},[231,2005,1870],{"class":237},[231,2007,1943],{"class":573},[231,2009,2010],{"class":573}," 0x",[231,2012,2013],{"class":240},"FFFFFFFF",[231,2015,2016],{"class":573},"ull",[231,2018,1717],{"class":237},[231,2020,2021,2023,2025],{"class":233,"line":699},[231,2022,1756],{"class":573},[231,2024,1759],{"class":240},[231,2026,1652],{"class":237},[231,2028,2029,2031,2033,2036,2038,2040],{"class":233,"line":704},[231,2030,1727],{"class":237},[231,2032,1630],{"class":573},[231,2034,2035],{"class":573}," static_cast\u003C",[231,2037,1468],{"class":237},[231,2039,1943],{"class":573},[231,2041,2042],{"class":237},"(value);\n",[231,2044,2045,2048,2051],{"class":233,"line":710},[231,2046,2047],{"class":573},"        return",[231,2049,2050],{"class":240}," true",[231,2052,1652],{"class":237},[231,2054,2055],{"class":233,"line":716},[231,2056,2057],{"class":237},"    }\n",[231,2059,2060],{"class":233,"line":722},[231,2061,755],{"emptyLinePlaceholder":754},[231,2063,2064],{"class":233,"line":728},[231,2065,2066],{"class":584},"    \u002F\u002F SID string -> normalized local account name. Used only at enroll time (a\n",[231,2068,2069],{"class":233,"line":734},[231,2070,2071],{"class":584},"    \u002F\u002F console app), never on the LSA hot path.\n",[231,2073,2074,2076,2079,2082,2084,2086,2088,2090,2092,2094,2097,2099,2102,2104,2106,2108,2111],{"class":233,"line":739},[231,2075,1618],{"class":573},[231,2077,2078],{"class":573}," bool",[231,2080,2081],{"class":577}," NameFromSid",[231,2083,1444],{"class":237},[231,2085,1447],{"class":573},[231,2087,1450],{"class":577},[231,2089,1453],{"class":237},[231,2091,1456],{"class":577},[231,2093,1459],{"class":573},[231,2095,2096],{"class":1462}," sidStr",[231,2098,424],{"class":237},[231,2100,2101],{"class":577},"std",[231,2103,1453],{"class":237},[231,2105,1456],{"class":577},[231,2107,1459],{"class":573},[231,2109,2110],{"class":1462}," name",[231,2112,1717],{"class":237},[231,2114,2115],{"class":233,"line":745},[231,2116,1722],{"class":237},[231,2118,2119,2122,2125],{"class":233,"line":751},[231,2120,2121],{"class":237},"        name.",[231,2123,2124],{"class":577},"clear",[231,2126,2127],{"class":237},"();\n",[231,2129,2130,2133,2136,2138,2141],{"class":233,"line":758},[231,2131,2132],{"class":577},"        PSID",[231,2134,2135],{"class":237}," psid ",[231,2137,1630],{"class":573},[231,2139,2140],{"class":240}," nullptr",[231,2142,1652],{"class":237},[231,2144,2145,2147,2149,2151,2154,2157,2160,2163,2165],{"class":233,"line":767},[231,2146,1739],{"class":573},[231,2148,1742],{"class":237},[231,2150,1745],{"class":573},[231,2152,2153],{"class":577},"ConvertStringSidToSidW",[231,2155,2156],{"class":237},"(sidStr.",[231,2158,2159],{"class":577},"c_str",[231,2161,2162],{"class":237},"(), ",[231,2164,1459],{"class":573},[231,2166,2167],{"class":237},"psid))\n",[231,2169,2170,2172,2174],{"class":233,"line":777},[231,2171,1756],{"class":573},[231,2173,1759],{"class":240},[231,2175,1652],{"class":237},[231,2177,2178],{"class":233,"line":785},[231,2179,755],{"emptyLinePlaceholder":754},[231,2181,2182,2185,2188,2191,2193,2196,2198,2201],{"class":233,"line":793},[231,2183,2184],{"class":237},"        WCHAR n[",[231,2186,2187],{"class":240},"256",[231,2189,2190],{"class":237},"] ",[231,2192,1630],{"class":573},[231,2194,2195],{"class":237}," {}; DWORD cn ",[231,2197,1630],{"class":573},[231,2199,2200],{"class":577}," ARRAYSIZE",[231,2202,2203],{"class":237},"(n);\n",[231,2205,2206,2209,2211,2213,2215,2218,2220,2222],{"class":233,"line":801},[231,2207,2208],{"class":237},"        WCHAR d[",[231,2210,2187],{"class":240},[231,2212,2190],{"class":237},[231,2214,1630],{"class":573},[231,2216,2217],{"class":237}," {}; DWORD cd ",[231,2219,1630],{"class":573},[231,2221,2200],{"class":577},[231,2223,2224],{"class":237},"(d);\n",[231,2226,2227,2230],{"class":233,"line":806},[231,2228,2229],{"class":577},"        SID_NAME_USE",[231,2231,2232],{"class":237}," use;\n",[231,2234,2235,2238,2241,2243,2246,2248,2251,2254,2256,2259,2261,2264,2266],{"class":233,"line":815},[231,2236,2237],{"class":577},"        BOOL",[231,2239,2240],{"class":237}," ok ",[231,2242,1630],{"class":573},[231,2244,2245],{"class":577}," LookupAccountSidW",[231,2247,1444],{"class":237},[231,2249,2250],{"class":240},"nullptr",[231,2252,2253],{"class":237},", psid, n, ",[231,2255,1459],{"class":573},[231,2257,2258],{"class":237},"cn, d, ",[231,2260,1459],{"class":573},[231,2262,2263],{"class":237},"cd, ",[231,2265,1459],{"class":573},[231,2267,2268],{"class":237},"use);\n",[231,2270,2271,2274],{"class":233,"line":821},[231,2272,2273],{"class":577},"        LocalFree",[231,2275,2276],{"class":237},"(psid);\n",[231,2278,2279,2281,2283,2285],{"class":233,"line":836},[231,2280,1739],{"class":573},[231,2282,1742],{"class":237},[231,2284,1745],{"class":573},[231,2286,2287],{"class":237},"ok)\n",[231,2289,2290,2292,2294],{"class":233,"line":842},[231,2291,1756],{"class":573},[231,2293,1759],{"class":240},[231,2295,1652],{"class":237},[231,2297,2298],{"class":233,"line":848},[231,2299,755],{"emptyLinePlaceholder":754},[231,2301,2302,2305,2307,2310],{"class":233,"line":854},[231,2303,2304],{"class":237},"        name ",[231,2306,1630],{"class":573},[231,2308,2309],{"class":577}," NormalizeUser",[231,2311,2203],{"class":237},[231,2313,2314,2316,2319,2322,2325],{"class":233,"line":860},[231,2315,2047],{"class":573},[231,2317,2318],{"class":573}," !",[231,2320,2321],{"class":237},"name.",[231,2323,2324],{"class":577},"empty",[231,2326,2127],{"class":237},[231,2328,2329],{"class":233,"line":872},[231,2330,2057],{"class":237},[231,2332,2333],{"class":233,"line":877},[231,2334,755],{"emptyLinePlaceholder":754},[231,2336,2337,2339,2341,2343,2345,2347],{"class":233,"line":889},[231,2338,1438],{"class":573},[231,2340,1532],{"class":577},[231,2342,1444],{"class":237},[231,2344,1468],{"class":577},[231,2346,1473],{"class":1462},[231,2348,1717],{"class":237},[231,2350,2351],{"class":233,"line":895},[231,2352,1722],{"class":237},[231,2354,2355,2357,2360,2362,2364],{"class":233,"line":901},[231,2356,1739],{"class":573},[231,2358,2359],{"class":237}," (rid ",[231,2361,1794],{"class":573},[231,2363,1732],{"class":240},[231,2365,1717],{"class":237},[231,2367,2368,2370,2372],{"class":233,"line":907},[231,2369,1756],{"class":573},[231,2371,1759],{"class":240},[231,2373,1652],{"class":237},[231,2375,2376],{"class":233,"line":913},[231,2377,755],{"emptyLinePlaceholder":754},[231,2379,2380],{"class":233,"line":919},[231,2381,2382],{"class":584},"        \u002F\u002F A single registry read, like IsEnrolledByName below.\n",[231,2384,2385,2388,2391,2393,2395],{"class":233,"line":930},[231,2386,2387],{"class":577},"        HKEY",[231,2389,2390],{"class":237}," hKey ",[231,2392,1630],{"class":573},[231,2394,2140],{"class":240},[231,2396,1652],{"class":237},[231,2398,2399,2401,2403,2406,2409,2412,2415,2417,2420,2423],{"class":233,"line":936},[231,2400,1739],{"class":573},[231,2402,1742],{"class":237},[231,2404,2405],{"class":577},"RegOpenKeyExW",[231,2407,2408],{"class":237},"(HKEY_LOCAL_MACHINE, kRidPath, ",[231,2410,2411],{"class":240},"0",[231,2413,2414],{"class":237},", KEY_QUERY_VALUE, ",[231,2416,1459],{"class":573},[231,2418,2419],{"class":237},"hKey) ",[231,2421,2422],{"class":573},"!=",[231,2424,2425],{"class":237}," ERROR_SUCCESS)\n",[231,2427,2428,2430,2432],{"class":233,"line":942},[231,2429,1756],{"class":573},[231,2431,1759],{"class":240},[231,2433,1652],{"class":237},[231,2435,2436],{"class":233,"line":948},[231,2437,755],{"emptyLinePlaceholder":754},[231,2439,2440,2443,2446,2449],{"class":233,"line":954},[231,2441,2442],{"class":577},"        WCHAR",[231,2444,2445],{"class":237}," value[",[231,2447,2448],{"class":240},"16",[231,2450,2451],{"class":237},"];\n",[231,2453,2454,2457,2460,2463,2466,2469,2471,2474,2477],{"class":233,"line":964},[231,2455,2456],{"class":577},"        swprintf_s",[231,2458,2459],{"class":237},"(value, ",[231,2461,2462],{"class":773},"L\"",[231,2464,2465],{"class":240},"%lu",[231,2467,2468],{"class":773},"\"",[231,2470,424],{"class":237},[231,2472,2473],{"class":573},"static_cast\u003Cunsigned",[231,2475,2476],{"class":573}," long>",[231,2478,2479],{"class":237},"(rid));\n",[231,2481,2482,2485,2488,2490,2493,2496,2498,2500,2502,2504,2506,2508,2510],{"class":233,"line":975},[231,2483,2484],{"class":577},"        LSTATUS",[231,2486,2487],{"class":237}," status ",[231,2489,1630],{"class":573},[231,2491,2492],{"class":577}," RegQueryValueExW",[231,2494,2495],{"class":237},"(hKey, value, ",[231,2497,2250],{"class":240},[231,2499,424],{"class":237},[231,2501,2250],{"class":240},[231,2503,424],{"class":237},[231,2505,2250],{"class":240},[231,2507,424],{"class":237},[231,2509,2250],{"class":240},[231,2511,1476],{"class":237},[231,2513,2514,2517],{"class":233,"line":985},[231,2515,2516],{"class":577},"        RegCloseKey",[231,2518,2519],{"class":237},"(hKey);\n",[231,2521,2522,2524,2526,2528],{"class":233,"line":995},[231,2523,2047],{"class":573},[231,2525,2487],{"class":237},[231,2527,1794],{"class":573},[231,2529,2530],{"class":237}," ERROR_SUCCESS;\n",[231,2532,2533],{"class":233,"line":1001},[231,2534,2057],{"class":237},[231,2536,2537],{"class":233,"line":1012},[231,2538,755],{"emptyLinePlaceholder":754},[231,2540,2541,2543,2545,2547,2549,2551,2553,2555,2557,2559],{"class":233,"line":1017},[231,2542,1438],{"class":573},[231,2544,1581],{"class":577},[231,2546,1444],{"class":237},[231,2548,1447],{"class":573},[231,2550,1450],{"class":577},[231,2552,1453],{"class":237},[231,2554,1456],{"class":577},[231,2556,1459],{"class":573},[231,2558,1596],{"class":1462},[231,2560,1717],{"class":237},[231,2562,2563],{"class":233,"line":1029},[231,2564,1722],{"class":237},[231,2566,2567,2570,2572,2574,2577,2579,2581],{"class":233,"line":1035},[231,2568,2569],{"class":577},"        std",[231,2571,1453],{"class":237},[231,2573,1456],{"class":577},[231,2575,2576],{"class":237}," name ",[231,2578,1630],{"class":573},[231,2580,2309],{"class":577},[231,2582,2583],{"class":237},"(user);\n",[231,2585,2586,2588,2591,2593],{"class":233,"line":1041},[231,2587,1739],{"class":573},[231,2589,2590],{"class":237}," (name.",[231,2592,2324],{"class":577},[231,2594,2595],{"class":237},"())\n",[231,2597,2598,2600,2602],{"class":233,"line":1051},[231,2599,1756],{"class":573},[231,2601,1759],{"class":240},[231,2603,1652],{"class":237},[231,2605,2606],{"class":233,"line":1057},[231,2607,755],{"emptyLinePlaceholder":754},[231,2609,2610],{"class":233,"line":1062},[231,2611,2612],{"class":584},"        \u002F\u002F A single registry read. No DPAPI, no LookupAccount* - nothing that\n",[231,2614,2615],{"class":233,"line":1074},[231,2616,2617],{"class":584},"        \u002F\u002F re-enters LSA - so this is safe to call from inside lsass on the\n",[231,2619,2620],{"class":233,"line":1079},[231,2621,2622],{"class":584},"        \u002F\u002F logon path.\n",[231,2624,2625,2627,2629,2631,2633],{"class":233,"line":1084},[231,2626,2387],{"class":577},[231,2628,2390],{"class":237},[231,2630,1630],{"class":573},[231,2632,2140],{"class":240},[231,2634,1652],{"class":237},[231,2636,2637,2639,2641,2643,2646,2648,2650,2652,2654,2656],{"class":233,"line":1093},[231,2638,1739],{"class":573},[231,2640,1742],{"class":237},[231,2642,2405],{"class":577},[231,2644,2645],{"class":237},"(HKEY_LOCAL_MACHINE, kNamePath, ",[231,2647,2411],{"class":240},[231,2649,2414],{"class":237},[231,2651,1459],{"class":573},[231,2653,2419],{"class":237},[231,2655,2422],{"class":573},[231,2657,2425],{"class":237},[231,2659,2660,2662,2664],{"class":233,"line":1098},[231,2661,1756],{"class":573},[231,2663,1759],{"class":240},[231,2665,1652],{"class":237},[231,2667,2668],{"class":233,"line":1103},[231,2669,755],{"emptyLinePlaceholder":754},[231,2671,2672,2674,2676,2678,2680,2683,2685,2687,2689,2691,2693,2695,2697,2699,2701],{"class":233,"line":1115},[231,2673,2484],{"class":577},[231,2675,2487],{"class":237},[231,2677,1630],{"class":573},[231,2679,2492],{"class":577},[231,2681,2682],{"class":237},"(hKey, name.",[231,2684,2159],{"class":577},[231,2686,2162],{"class":237},[231,2688,2250],{"class":240},[231,2690,424],{"class":237},[231,2692,2250],{"class":240},[231,2694,424],{"class":237},[231,2696,2250],{"class":240},[231,2698,424],{"class":237},[231,2700,2250],{"class":240},[231,2702,1476],{"class":237},[231,2704,2705,2707],{"class":233,"line":1120},[231,2706,2516],{"class":577},[231,2708,2519],{"class":237},[231,2710,2711,2713,2715,2717],{"class":233,"line":1125},[231,2712,2047],{"class":573},[231,2714,2487],{"class":237},[231,2716,1794],{"class":573},[231,2718,2530],{"class":237},[231,2720,2721],{"class":233,"line":1134},[231,2722,2057],{"class":237},[15,2724,2725,2726,2728],{},"And ",[28,2727,1407],{}," writes the indexes first and the secret second:",[222,2730,2732],{"className":564,"code":2731,"language":566,"meta":227,"style":227},"    \u002F\u002F Writes a presence flag (REG_DWORD 1) under one of the index keys.\n    static bool SetFlag(const wchar_t* path, const std::wstring& value)\n    {\n        HKEY hKey = nullptr;\n        if (OpenProtectedKey(path, &hKey) != ERROR_SUCCESS)\n            return false;\n        DWORD one = 1;\n        bool ok = RegSetValueExW(hKey, value.c_str(), 0, REG_DWORD,\n                                 reinterpret_cast\u003Cconst BYTE*>(&one), sizeof(one)) == ERROR_SUCCESS;\n        RegCloseKey(hKey);\n        return ok;\n    }\n\n    \u002F\u002F Writes the RID index and, if the SID still resolves to a name, the name\n    \u002F\u002F index for one enrolled SID.\n    static void WriteIndexes(const std::wstring& sid, bool& ridOk, bool& nameOk)\n    {\n        ridOk = nameOk = false;\n\n        DWORD rid = 0;\n        if (RidFromSidString(sid, rid))\n            ridOk = SetFlag(kRidPath, std::to_wstring(rid));\n\n        std::wstring name;\n        if (NameFromSid(sid, name))\n            nameOk = SetFlag(kNamePath, name);\n    }\n\n    bool StoreSecret(const std::wstring& sid, const std::string& base32Secret)\n    {\n        if (!IsSidString(sid))\n            return false;\n\n        std::vector\u003CBYTE> blob;\n        if (!Protect(base32Secret, blob))\n            return false;\n\n        \u002F\u002F The indexes the LSA hot path reads come first. If they cannot be\n        \u002F\u002F written, nothing else is touched, so a re-enrollment that fails\n        \u002F\u002F leaves the old enrollment working. If the secret then fails, the\n        \u002F\u002F index entries stay behind: that only makes the LSA packages refuse\n        \u002F\u002F MORE (network logon of an account without a secret), never less,\n        \u002F\u002F and `enroll \u002Fremove` clears them.\n        bool ridOk = false, nameOk = false;\n        WriteIndexes(sid, ridOk, nameOk);\n        if (!ridOk || !nameOk)\n            return false;\n\n        HKEY hKey = nullptr;\n        LSTATUS status = OpenProtectedKey(kKeyPath, &hKey);\n        if (status == ERROR_SUCCESS)\n        {\n            status = RegSetValueExW(hKey, sid.c_str(), 0, REG_BINARY,\n                                    blob.data(), static_cast\u003CDWORD>(blob.size()));\n            RegCloseKey(hKey);\n        }\n        return status == ERROR_SUCCESS;\n    }\n",[28,2733,2734,2739,2775,2779,2791,2811,2819,2833,2857,2885,2891,2898,2902,2906,2911,2916,2957,2961,2977,2981,2994,3006,3027,3031,3042,3054,3066,3070,3074,3113,3117,3129,3137,3141,3158,3172,3180,3184,3189,3194,3199,3204,3209,3214,3234,3242,3260,3268,3272,3284,3303,3314,3318,3339,3364,3371,3375,3385],{"__ignoreMap":227},[231,2735,2736],{"class":233,"line":234},[231,2737,2738],{"class":584},"    \u002F\u002F Writes a presence flag (REG_DWORD 1) under one of the index keys.\n",[231,2740,2741,2743,2745,2748,2750,2752,2755,2758,2760,2762,2764,2766,2768,2770,2773],{"class":233,"line":581},[231,2742,1618],{"class":573},[231,2744,2078],{"class":573},[231,2746,2747],{"class":577}," SetFlag",[231,2749,1444],{"class":237},[231,2751,1447],{"class":573},[231,2753,2754],{"class":573}," wchar_t*",[231,2756,2757],{"class":1462}," path",[231,2759,424],{"class":237},[231,2761,1447],{"class":573},[231,2763,1450],{"class":577},[231,2765,1453],{"class":237},[231,2767,1456],{"class":577},[231,2769,1459],{"class":573},[231,2771,2772],{"class":1462}," value",[231,2774,1717],{"class":237},[231,2776,2777],{"class":233,"line":588},[231,2778,1722],{"class":237},[231,2780,2781,2783,2785,2787,2789],{"class":233,"line":594},[231,2782,2387],{"class":577},[231,2784,2390],{"class":237},[231,2786,1630],{"class":573},[231,2788,2140],{"class":240},[231,2790,1652],{"class":237},[231,2792,2793,2795,2797,2800,2803,2805,2807,2809],{"class":233,"line":599},[231,2794,1739],{"class":573},[231,2796,1742],{"class":237},[231,2798,2799],{"class":577},"OpenProtectedKey",[231,2801,2802],{"class":237},"(path, ",[231,2804,1459],{"class":573},[231,2806,2419],{"class":237},[231,2808,2422],{"class":573},[231,2810,2425],{"class":237},[231,2812,2813,2815,2817],{"class":233,"line":605},[231,2814,1756],{"class":573},[231,2816,1759],{"class":240},[231,2818,1652],{"class":237},[231,2820,2821,2824,2827,2829,2831],{"class":233,"line":611},[231,2822,2823],{"class":577},"        DWORD",[231,2825,2826],{"class":237}," one ",[231,2828,1630],{"class":573},[231,2830,1814],{"class":240},[231,2832,1652],{"class":237},[231,2834,2835,2838,2840,2842,2845,2848,2850,2852,2854],{"class":233,"line":617},[231,2836,2837],{"class":573},"        bool",[231,2839,2240],{"class":237},[231,2841,1630],{"class":573},[231,2843,2844],{"class":577}," RegSetValueExW",[231,2846,2847],{"class":237},"(hKey, value.",[231,2849,2159],{"class":577},[231,2851,2162],{"class":237},[231,2853,2411],{"class":240},[231,2855,2856],{"class":237},", REG_DWORD,\n",[231,2858,2859,2862,2865,2868,2870,2872,2875,2878,2881,2883],{"class":233,"line":623},[231,2860,2861],{"class":573},"                                 reinterpret_cast\u003Cconst",[231,2863,2864],{"class":237}," BYTE",[231,2866,2867],{"class":573},"*>",[231,2869,1444],{"class":237},[231,2871,1459],{"class":573},[231,2873,2874],{"class":237},"one), ",[231,2876,2877],{"class":573},"sizeof",[231,2879,2880],{"class":237},"(one)) ",[231,2882,1794],{"class":573},[231,2884,2530],{"class":237},[231,2886,2887,2889],{"class":233,"line":629},[231,2888,2516],{"class":577},[231,2890,2519],{"class":237},[231,2892,2893,2895],{"class":233,"line":634},[231,2894,2047],{"class":573},[231,2896,2897],{"class":237}," ok;\n",[231,2899,2900],{"class":233,"line":640},[231,2901,2057],{"class":237},[231,2903,2904],{"class":233,"line":646},[231,2905,755],{"emptyLinePlaceholder":754},[231,2907,2908],{"class":233,"line":652},[231,2909,2910],{"class":584},"    \u002F\u002F Writes the RID index and, if the SID still resolves to a name, the name\n",[231,2912,2913],{"class":233,"line":657},[231,2914,2915],{"class":584},"    \u002F\u002F index for one enrolled SID.\n",[231,2917,2918,2920,2923,2926,2928,2930,2932,2934,2936,2938,2940,2942,2945,2948,2950,2952,2955],{"class":233,"line":663},[231,2919,1618],{"class":573},[231,2921,2922],{"class":573}," void",[231,2924,2925],{"class":577}," WriteIndexes",[231,2927,1444],{"class":237},[231,2929,1447],{"class":573},[231,2931,1450],{"class":577},[231,2933,1453],{"class":237},[231,2935,1456],{"class":577},[231,2937,1459],{"class":573},[231,2939,1463],{"class":1462},[231,2941,424],{"class":237},[231,2943,2944],{"class":573},"bool&",[231,2946,2947],{"class":1462}," ridOk",[231,2949,424],{"class":237},[231,2951,2944],{"class":573},[231,2953,2954],{"class":1462}," nameOk",[231,2956,1717],{"class":237},[231,2958,2959],{"class":233,"line":669},[231,2960,1722],{"class":237},[231,2962,2963,2966,2968,2971,2973,2975],{"class":233,"line":675},[231,2964,2965],{"class":237},"        ridOk ",[231,2967,1630],{"class":573},[231,2969,2970],{"class":237}," nameOk ",[231,2972,1630],{"class":573},[231,2974,1759],{"class":240},[231,2976,1652],{"class":237},[231,2978,2979],{"class":233,"line":681},[231,2980,755],{"emptyLinePlaceholder":754},[231,2982,2983,2985,2988,2990,2992],{"class":233,"line":687},[231,2984,2823],{"class":577},[231,2986,2987],{"class":237}," rid ",[231,2989,1630],{"class":573},[231,2991,1732],{"class":240},[231,2993,1652],{"class":237},[231,2995,2996,2998,3000,3003],{"class":233,"line":693},[231,2997,1739],{"class":573},[231,2999,1742],{"class":237},[231,3001,3002],{"class":577},"RidFromSidString",[231,3004,3005],{"class":237},"(sid, rid))\n",[231,3007,3008,3011,3013,3015,3018,3020,3022,3025],{"class":233,"line":699},[231,3009,3010],{"class":237},"            ridOk ",[231,3012,1630],{"class":573},[231,3014,2747],{"class":577},[231,3016,3017],{"class":237},"(kRidPath, ",[231,3019,2101],{"class":577},[231,3021,1453],{"class":237},[231,3023,3024],{"class":577},"to_wstring",[231,3026,2479],{"class":237},[231,3028,3029],{"class":233,"line":704},[231,3030,755],{"emptyLinePlaceholder":754},[231,3032,3033,3035,3037,3039],{"class":233,"line":710},[231,3034,2569],{"class":577},[231,3036,1453],{"class":237},[231,3038,1456],{"class":577},[231,3040,3041],{"class":237}," name;\n",[231,3043,3044,3046,3048,3051],{"class":233,"line":716},[231,3045,1739],{"class":573},[231,3047,1742],{"class":237},[231,3049,3050],{"class":577},"NameFromSid",[231,3052,3053],{"class":237},"(sid, name))\n",[231,3055,3056,3059,3061,3063],{"class":233,"line":722},[231,3057,3058],{"class":237},"            nameOk ",[231,3060,1630],{"class":573},[231,3062,2747],{"class":577},[231,3064,3065],{"class":237},"(kNamePath, name);\n",[231,3067,3068],{"class":233,"line":728},[231,3069,2057],{"class":237},[231,3071,3072],{"class":233,"line":734},[231,3073,755],{"emptyLinePlaceholder":754},[231,3075,3076,3078,3081,3083,3085,3087,3089,3091,3093,3095,3097,3099,3101,3103,3106,3108,3111],{"class":233,"line":739},[231,3077,1438],{"class":573},[231,3079,3080],{"class":577}," StoreSecret",[231,3082,1444],{"class":237},[231,3084,1447],{"class":573},[231,3086,1450],{"class":577},[231,3088,1453],{"class":237},[231,3090,1456],{"class":577},[231,3092,1459],{"class":573},[231,3094,1463],{"class":1462},[231,3096,424],{"class":237},[231,3098,1447],{"class":573},[231,3100,1450],{"class":577},[231,3102,1453],{"class":237},[231,3104,3105],{"class":577},"string",[231,3107,1459],{"class":573},[231,3109,3110],{"class":1462}," base32Secret",[231,3112,1717],{"class":237},[231,3114,3115],{"class":233,"line":745},[231,3116,1722],{"class":237},[231,3118,3119,3121,3123,3125,3127],{"class":233,"line":751},[231,3120,1739],{"class":573},[231,3122,1742],{"class":237},[231,3124,1745],{"class":573},[231,3126,1748],{"class":577},[231,3128,1751],{"class":237},[231,3130,3131,3133,3135],{"class":233,"line":758},[231,3132,1756],{"class":573},[231,3134,1759],{"class":240},[231,3136,1652],{"class":237},[231,3138,3139],{"class":233,"line":767},[231,3140,755],{"emptyLinePlaceholder":754},[231,3142,3143,3145,3147,3150,3152,3155],{"class":233,"line":777},[231,3144,2569],{"class":577},[231,3146,1453],{"class":237},[231,3148,3149],{"class":577},"vector",[231,3151,1903],{"class":237},[231,3153,3154],{"class":577},"BYTE",[231,3156,3157],{"class":237},"> blob;\n",[231,3159,3160,3162,3164,3166,3169],{"class":233,"line":785},[231,3161,1739],{"class":573},[231,3163,1742],{"class":237},[231,3165,1745],{"class":573},[231,3167,3168],{"class":577},"Protect",[231,3170,3171],{"class":237},"(base32Secret, blob))\n",[231,3173,3174,3176,3178],{"class":233,"line":793},[231,3175,1756],{"class":573},[231,3177,1759],{"class":240},[231,3179,1652],{"class":237},[231,3181,3182],{"class":233,"line":801},[231,3183,755],{"emptyLinePlaceholder":754},[231,3185,3186],{"class":233,"line":806},[231,3187,3188],{"class":584},"        \u002F\u002F The indexes the LSA hot path reads come first. If they cannot be\n",[231,3190,3191],{"class":233,"line":815},[231,3192,3193],{"class":584},"        \u002F\u002F written, nothing else is touched, so a re-enrollment that fails\n",[231,3195,3196],{"class":233,"line":821},[231,3197,3198],{"class":584},"        \u002F\u002F leaves the old enrollment working. If the secret then fails, the\n",[231,3200,3201],{"class":233,"line":836},[231,3202,3203],{"class":584},"        \u002F\u002F index entries stay behind: that only makes the LSA packages refuse\n",[231,3205,3206],{"class":233,"line":842},[231,3207,3208],{"class":584},"        \u002F\u002F MORE (network logon of an account without a secret), never less,\n",[231,3210,3211],{"class":233,"line":848},[231,3212,3213],{"class":584},"        \u002F\u002F and `enroll \u002Fremove` clears them.\n",[231,3215,3216,3218,3221,3223,3225,3228,3230,3232],{"class":233,"line":854},[231,3217,2837],{"class":573},[231,3219,3220],{"class":237}," ridOk ",[231,3222,1630],{"class":573},[231,3224,1759],{"class":240},[231,3226,3227],{"class":237},", nameOk ",[231,3229,1630],{"class":573},[231,3231,1759],{"class":240},[231,3233,1652],{"class":237},[231,3235,3236,3239],{"class":233,"line":860},[231,3237,3238],{"class":577},"        WriteIndexes",[231,3240,3241],{"class":237},"(sid, ridOk, nameOk);\n",[231,3243,3244,3246,3248,3250,3253,3255,3257],{"class":233,"line":872},[231,3245,1739],{"class":573},[231,3247,1742],{"class":237},[231,3249,1745],{"class":573},[231,3251,3252],{"class":237},"ridOk ",[231,3254,1806],{"class":573},[231,3256,2318],{"class":573},[231,3258,3259],{"class":237},"nameOk)\n",[231,3261,3262,3264,3266],{"class":233,"line":877},[231,3263,1756],{"class":573},[231,3265,1759],{"class":240},[231,3267,1652],{"class":237},[231,3269,3270],{"class":233,"line":889},[231,3271,755],{"emptyLinePlaceholder":754},[231,3273,3274,3276,3278,3280,3282],{"class":233,"line":895},[231,3275,2387],{"class":577},[231,3277,2390],{"class":237},[231,3279,1630],{"class":573},[231,3281,2140],{"class":240},[231,3283,1652],{"class":237},[231,3285,3286,3288,3290,3292,3295,3298,3300],{"class":233,"line":901},[231,3287,2484],{"class":577},[231,3289,2487],{"class":237},[231,3291,1630],{"class":573},[231,3293,3294],{"class":577}," OpenProtectedKey",[231,3296,3297],{"class":237},"(kKeyPath, ",[231,3299,1459],{"class":573},[231,3301,3302],{"class":237},"hKey);\n",[231,3304,3305,3307,3310,3312],{"class":233,"line":907},[231,3306,1739],{"class":573},[231,3308,3309],{"class":237}," (status ",[231,3311,1794],{"class":573},[231,3313,2425],{"class":237},[231,3315,3316],{"class":233,"line":913},[231,3317,1921],{"class":237},[231,3319,3320,3323,3325,3327,3330,3332,3334,3336],{"class":233,"line":919},[231,3321,3322],{"class":237},"            status ",[231,3324,1630],{"class":573},[231,3326,2844],{"class":577},[231,3328,3329],{"class":237},"(hKey, sid.",[231,3331,2159],{"class":577},[231,3333,2162],{"class":237},[231,3335,2411],{"class":240},[231,3337,3338],{"class":237},", REG_BINARY,\n",[231,3340,3341,3344,3347,3349,3352,3354,3356,3359,3361],{"class":233,"line":930},[231,3342,3343],{"class":237},"                                    blob.",[231,3345,3346],{"class":577},"data",[231,3348,2162],{"class":237},[231,3350,3351],{"class":573},"static_cast\u003C",[231,3353,1468],{"class":237},[231,3355,1943],{"class":573},[231,3357,3358],{"class":237},"(blob.",[231,3360,1822],{"class":577},[231,3362,3363],{"class":237},"()));\n",[231,3365,3366,3369],{"class":233,"line":936},[231,3367,3368],{"class":577},"            RegCloseKey",[231,3370,2519],{"class":237},[231,3372,3373],{"class":233,"line":942},[231,3374,1990],{"class":237},[231,3376,3377,3379,3381,3383],{"class":233,"line":948},[231,3378,2047],{"class":573},[231,3380,2487],{"class":237},[231,3382,1794],{"class":573},[231,3384,2530],{"class":237},[231,3386,3387],{"class":233,"line":954},[231,3388,2057],{"class":237},[15,3390,3391,3392,3395],{},"The order matters. A secret without an index is bad: the tile works, and the LSA part silently thinks the account is not enrolled. My first version wrote the secret first and deleted it again if the index failed. When you enrolled someone again, that destroyed an enrollment that worked. Now the indexes come first. If they can't be written, nothing else is changed and the old enrollment keeps working. If the secret fails after the indexes, the markers stay. That only makes the LSA side refuse more, never less, and ",[28,3393,3394],{},"enroll.exe \u002Fremove"," deletes them.",[15,3397,3398,3399,3402,3403,3405,3406,3408,3409,3412,3413,3415,3416,3418,3419,3421],{},"Why two indexes? The first version only had the name. Part 1 has a whole chapter on why the secret belongs to the SID, and the name index brought the old weakness back. I wrote that it only fails in the strict direction. That was wrong. If you delete ",[28,3400,3401],{},"alice"," and create a new ",[28,3404,3401],{},", the new account reads as enrolled. Too strict, fine. But if you rename ",[28,3407,3401],{}," to ",[28,3410,3411],{},"alicia",", the index still says ",[28,3414,3401],{},". Now ",[28,3417,3411],{}," reads as ",[72,3420,57],{}," enrolled, and her network logons work with the password alone. Too loose.",[15,3423,3424,3425,3428],{},"The filter doesn't need the name at all. MSV1_0 gives it the account record from the SAM, and the RID in there is the last part of the SID. It stays the same after a rename, and the SAM never gives the same RID to a new account. So the filter asks the RID index. That one really is as safe as I thought the name index was. Only the authentication package still reads the name, because it only has what the client typed. There it doesn't matter, as you will see in the next chapter. After a rename, or after an update from the old version, run ",[28,3426,3427],{},"enroll.exe \u002Freindex"," once.",[259,3430,3432],{"id":3431},"why-it-can-only-deny","Why it can only deny",[15,3434,3435,3436,3438],{},"Now the part that decides how big the project gets. In ",[28,3437,535],{}," you can do two things: deny, or let the logon succeed.",[15,3440,3441,3442,3445],{},"Denying is one line. You return ",[28,3443,3444],{},"STATUS_ACCOUNT_RESTRICTION"," and LSA refuses.",[15,3447,3448,3449,3451],{},"Letting a logon succeed is a completely different amount of work. On success your package has to build the whole result: a logon token, a profile buffer, the account SID, the group memberships, everything the real packages spend thousands of lines producing. You do not want to reimplement ",[28,3450,268],{},". And a package that sits transparently in front of MSV1_0 to pass credentials through is exactly the shim that credential-theft tooling uses. MITRE ATT&CK lists it under T1556, the same entry as the malicious credential provider from Part 1. I am not going to write that.",[15,3453,3454,3455,3458],{},"So the package only ever denies or declines. The decline case returns ",[28,3456,3457],{},"STATUS_NOT_IMPLEMENTED",", and that one is easy to misread, so to be precise: it means \"this package does not handle this logon\". It is not a transparent retry against MSV1_0. A caller that selected us by package id and gets this back has its logon fail right there. We are not politely stepping aside in a chain, we are declining to claim a logon that is not ours.",[15,3460,3461,561],{},[28,3462,3463],{},"ap.cpp",[222,3465,3467],{"className":564,"code":3466,"language":566,"meta":227,"style":227},"#include \"ap.h\"\n#include \"store.h\"\n#include \u003Cstring>\n#include \u003Cvector>\n\n#pragma comment(lib, \"advapi32.lib\")\n\n\u002F\u002F NTSTATUS values we return. They live in ntstatus.h, which clashes with\n\u002F\u002F windows.h, so we define the handful we need.\n#ifndef STATUS_SUCCESS\n#define STATUS_SUCCESS            ((NTSTATUS)0x00000000L)\n#endif\n#ifndef STATUS_ACCOUNT_RESTRICTION\n#define STATUS_ACCOUNT_RESTRICTION ((NTSTATUS)0xC000006EL)  \u002F\u002F \"account not allowed to log on here\u002Fnow\"\n#endif\n#ifndef STATUS_NOT_IMPLEMENTED\n#define STATUS_NOT_IMPLEMENTED    ((NTSTATUS)0xC0000002L)\n#endif\n#ifndef STATUS_INVALID_PARAMETER\n#define STATUS_INVALID_PARAMETER  ((NTSTATUS)0xC000000DL)\n#endif\n#ifndef STATUS_INSUFFICIENT_RESOURCES\n#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL)\n#endif\n\n\u002F\u002F Every export below runs inside lsass.exe. The helpers use std::wstring,\n\u002F\u002F which can throw std::bad_alloc; each export therefore has a catch-all that\n\u002F\u002F turns any C++ exception into a failure status. An exception that reached\n\u002F\u002F LSA would take lsass, and with it the whole machine, down.\n\n\u002F\u002F Kerberos message types we recognise in a submit buffer.\n#ifndef KerbInteractiveLogon\n#define KerbInteractiveLogon        2\n#endif\n#ifndef KerbWorkstationUnlockLogon\n#define KerbWorkstationUnlockLogon  7\n#endif\n\nnamespace\n{\n    PLSA_DISPATCH_TABLE g_lsa = nullptr;\n    ULONG               g_packageId = 0;\n\n    \u002F\u002F Logging from inside lsass. OutputDebugStringW is the channel you actually\n    \u002F\u002F read under a kernel debugger. ReportEvent is best-effort on top, guarded\n    \u002F\u002F so a logging failure can never affect a logon. We never log a password or\n    \u002F\u002F a code, only metadata.\n    void ApLog(WORD type, DWORD id, const std::wstring& text)\n    {\n        OutputDebugStringW(L\"[Doppio-AP] \");\n        OutputDebugStringW(text.c_str());\n        OutputDebugStringW(L\"\\n\");\n\n        HANDLE h = RegisterEventSourceW(nullptr, L\"TheAdminCafe 2FA\");\n        if (h)\n        {\n            LPCWSTR s[1] = { text.c_str() };\n            ReportEventW(h, type, 0, id, nullptr, 1, 0, s, nullptr);\n            DeregisterEventSource(h);\n        }\n    }\n\n    bool IsNonInteractive(SECURITY_LOGON_TYPE t)\n    {\n        switch (t)\n        {\n        case Network:            \u002F\u002F 3\n        case Batch:              \u002F\u002F 4\n        case Service:            \u002F\u002F 5\n        case NetworkCleartext:   \u002F\u002F 8\n        case NewCredentials:     \u002F\u002F 9\n            return true;\n        default:\n            return false;\n        }\n    }\n\n    const wchar_t* LogonTypeName(SECURITY_LOGON_TYPE t)\n    {\n        switch (t)\n        {\n        case Interactive:       return L\"Interactive\";\n        case Network:           return L\"Network\";\n        case Batch:             return L\"Batch\";\n        case Service:           return L\"Service\";\n        case Unlock:            return L\"Unlock\";\n        case NetworkCleartext:  return L\"NetworkCleartext\";\n        case NewCredentials:    return L\"NewCredentials\";\n        case RemoteInteractive: return L\"RemoteInteractive\";\n        case CachedInteractive: return L\"CachedInteractive\";\n        default:                return L\"Other\";\n        }\n    }\n\n    \u002F\u002F A UNICODE_STRING inside the submit buffer has a Buffer pointer that is\n    \u002F\u002F valid in the CLIENT process, not ours. LSA gives us ClientBufferBase (the\n    \u002F\u002F address the buffer had in the client) so we can relocate it into our copy.\n    \u002F\u002F Everything is bounds-checked against the submit buffer; anything off\n    \u002F\u002F returns an empty string, and the caller fails safe.\n    std::wstring ReadClientString(const UNICODE_STRING& us,\n                                  const void* submitBase, const void* clientBase,\n                                  ULONG submitSize)\n    {\n        if (!us.Buffer || us.Length == 0 || (us.Length % sizeof(WCHAR)) != 0)\n            return std::wstring();\n\n        const ULONG_PTR p    = reinterpret_cast\u003CULONG_PTR>(us.Buffer);\n        const ULONG_PTR base = reinterpret_cast\u003CULONG_PTR>(clientBase);\n        if (p \u003C base)\n            return std::wstring();\n\n        const ULONG_PTR off = p - base;\n        if (off > submitSize || us.Length > submitSize - off)\n            return std::wstring();\n\n        const WCHAR* s = reinterpret_cast\u003Cconst WCHAR*>(\n            static_cast\u003Cconst BYTE*>(submitBase) + off);\n        return std::wstring(s, us.Length \u002F sizeof(WCHAR));\n    }\n\n    \u002F\u002F The account check. We read ONLY the user name from the submit buffer\n    \u002F\u002F (never the password field) and ask the store, with a single registry\n    \u002F\u002F read, whether that name is enrolled. IsEnrolledByName does no DPAPI and\n    \u002F\u002F no LSA name\u002FSID lookup, so it is safe on the logon path. Any\n    \u002F\u002F inconsistency returns false (treat as not enrolled -> defer), so a\n    \u002F\u002F malformed buffer cannot crash us.\n    \u002F\u002F\n    \u002F\u002F The name is what the CLIENT typed, and the name index is not updated by\n    \u002F\u002F a rename (see store.h), so this check is weaker than the RID check in\n    \u002F\u002F the sub-authentication filter. It does not matter here - this package\n    \u002F\u002F never lets a logon through either way (see ap.h) - but do not copy it\n    \u002F\u002F into anything that does.\n    bool IsEnrolledAccount(PVOID submit, ULONG size, PVOID clientBase)\n    {\n        if (!submit || size \u003C sizeof(KERB_INTERACTIVE_LOGON))\n            return false;\n\n        auto* k = static_cast\u003Cconst KERB_INTERACTIVE_LOGON*>(submit);\n        if (k->MessageType != KerbInteractiveLogon &&\n            k->MessageType != KerbWorkstationUnlockLogon)\n            return false;\n\n        std::wstring user = ReadClientString(k->UserName, submit, clientBase, size);\n        if (user.empty())\n            return false;\n\n        return tac::IsEnrolledByName(user);\n    }\n}\n\nNTSTATUS NTAPI LsaApInitializePackage(\n    ULONG AuthenticationPackageId,\n    PLSA_DISPATCH_TABLE LsaDispatchTable,\n    PLSA_STRING \u002F*Database*\u002F,\n    PLSA_STRING \u002F*Confidentiality*\u002F,\n    PLSA_STRING* AuthenticationPackageName)\n{\n    if (!LsaDispatchTable || !AuthenticationPackageName)\n        return STATUS_INVALID_PARAMETER;\n    *AuthenticationPackageName = nullptr;\n\n    g_lsa       = LsaDispatchTable;\n    g_packageId = AuthenticationPackageId;\n\n    static const char kName[] = \"TacAuthPackage\";\n    const USHORT len = sizeof(kName) - 1;\n\n    char* buf = static_cast\u003Cchar*>(g_lsa->AllocateLsaHeap(len + 1));\n    if (!buf)\n        return STATUS_NO_MEMORY;\n    memcpy(buf, kName, len + 1);\n\n    LSA_STRING* name = static_cast\u003CLSA_STRING*>(g_lsa->AllocateLsaHeap(sizeof(LSA_STRING)));\n    if (!name)\n    {\n        g_lsa->FreeLsaHeap(buf);\n        return STATUS_NO_MEMORY;\n    }\n    name->Length        = len;\n    name->MaximumLength = len + 1;\n    name->Buffer        = buf;\n    *AuthenticationPackageName = name;\n\n    try\n    {\n        ApLog(EVENTLOG_INFORMATION_TYPE, 200,\n              L\"LSA authentication package loaded (id \" + std::to_wstring(AuthenticationPackageId) + L\").\");\n    }\n    catch (...)\n    {\n        \u002F\u002F The log line is optional; the package is loaded either way.\n    }\n    return STATUS_SUCCESS;\n}\n\n\u002F\u002F We only ever DENY or decline here; we never mint a token. The deny path\n\u002F\u002F returns STATUS_ACCOUNT_RESTRICTION. Otherwise we return STATUS_NOT_IMPLEMENTED\n\u002F\u002F to say \"this package does not handle this logon\". Note: a caller that selects\n\u002F\u002F this package by id and gets STATUS_NOT_IMPLEMENTED has its logon fail here; it\n\u002F\u002F is not transparently retried against MSV1_0. Standard network\u002Fbatch logons do\n\u002F\u002F not address this package at all - that is what the sub-authentication filter\n\u002F\u002F (subauth.cpp) is for. See the article, \"deny is easy, success is hard\".\nstatic NTSTATUS LogonUserEx2Impl(SECURITY_LOGON_TYPE LogonType, PVOID ProtocolSubmitBuffer,\n                                 PVOID ClientBufferBase, ULONG SubmitBufferSize, PNTSTATUS SubStatus)\n{\n    const bool nonInteractive = IsNonInteractive(LogonType);\n    const bool enrolled       = nonInteractive &&\n                                IsEnrolledAccount(ProtocolSubmitBuffer, SubmitBufferSize, ClientBufferBase);\n\n    if (nonInteractive && enrolled)\n    {\n        ApLog(EVENTLOG_WARNING_TYPE, 201,\n              std::wstring(L\"Denied a \") + LogonTypeName(LogonType) +\n              L\" logon for an enrolled account. This path has no second factor.\");\n        if (SubStatus) *SubStatus = STATUS_ACCOUNT_RESTRICTION;\n        return STATUS_ACCOUNT_RESTRICTION;\n    }\n\n    \u002F\u002F Not \"passed on\": nothing is forwarded anywhere. The caller asked for this\n    \u002F\u002F package, and this package never builds a token, so the logon fails.\n    ApLog(EVENTLOG_INFORMATION_TYPE, 202,\n          std::wstring(L\"Declined a \") + LogonTypeName(LogonType) +\n          L\" logon addressed to this package. It fails; this package never builds a token.\");\n    return STATUS_NOT_IMPLEMENTED;\n}\n\nNTSTATUS NTAPI LsaApLogonUserEx2(\n    PLSA_CLIENT_REQUEST \u002F*ClientRequest*\u002F,\n    SECURITY_LOGON_TYPE LogonType,\n    PVOID ProtocolSubmitBuffer,\n    PVOID ClientBufferBase,\n    ULONG SubmitBufferSize,\n    PVOID* ProfileBuffer,\n    PULONG ProfileBufferSize,\n    PLUID \u002F*LogonId*\u002F,\n    PNTSTATUS SubStatus,\n    PLSA_TOKEN_INFORMATION_TYPE \u002F*TokenInformationType*\u002F,\n    PVOID* \u002F*TokenInformation*\u002F,\n    PUNICODE_STRING* AccountName,\n    PUNICODE_STRING* AuthenticatingAuthority,\n    PUNICODE_STRING* MachineName,\n    PSECPKG_PRIMARY_CRED \u002F*PrimaryCredentials*\u002F,\n    PSECPKG_SUPPLEMENTAL_CRED_ARRAY* CachedCredentials)\n{\n    if (ProfileBuffer)            *ProfileBuffer = nullptr;\n    if (ProfileBufferSize)        *ProfileBufferSize = 0;\n    if (SubStatus)                *SubStatus = STATUS_SUCCESS;\n    if (AccountName)              *AccountName = nullptr;\n    if (AuthenticatingAuthority)  *AuthenticatingAuthority = nullptr;\n    if (MachineName)              *MachineName = nullptr;\n    if (CachedCredentials)        *CachedCredentials = nullptr;\n\n    try\n    {\n        return LogonUserEx2Impl(LogonType, ProtocolSubmitBuffer, ClientBufferBase,\n                                SubmitBufferSize, SubStatus);\n    }\n    catch (...)\n    {\n        if (SubStatus) *SubStatus = STATUS_INSUFFICIENT_RESOURCES;\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n}\n\nstatic NTSTATUS CallStub(PVOID* ProtocolReturnBuffer, PULONG ReturnBufferLength,\n                         PNTSTATUS ProtocolStatus)\n{\n    if (ProtocolReturnBuffer) *ProtocolReturnBuffer = nullptr;\n    if (ReturnBufferLength)   *ReturnBufferLength = 0;\n    if (ProtocolStatus)       *ProtocolStatus = STATUS_NOT_IMPLEMENTED;\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS NTAPI LsaApCallPackage(PLSA_CLIENT_REQUEST, PVOID, PVOID, ULONG,\n                                PVOID* rb, PULONG rl, PNTSTATUS ps)\n{ return CallStub(rb, rl, ps); }\n\nNTSTATUS NTAPI LsaApCallPackageUntrusted(PLSA_CLIENT_REQUEST, PVOID, PVOID, ULONG,\n                                         PVOID* rb, PULONG rl, PNTSTATUS ps)\n{ return CallStub(rb, rl, ps); }\n\nNTSTATUS NTAPI LsaApCallPackagePassthrough(PLSA_CLIENT_REQUEST, PVOID, PVOID, ULONG,\n                                           PVOID* rb, PULONG rl, PNTSTATUS ps)\n{ return CallStub(rb, rl, ps); }\n\nVOID NTAPI LsaApLogonTerminated(PLUID \u002F*LogonId*\u002F)\n{\n}\n",[28,3468,3469,3476,3483,3490,3497,3501,3520,3524,3529,3534,3542,3563,3568,3575,3598,3602,3609,3628,3632,3639,3658,3662,3669,3687,3691,3695,3700,3705,3710,3715,3719,3724,3731,3741,3745,3752,3762,3766,3770,3775,3779,3790,3801,3805,3810,3815,3820,3825,3865,3869,3881,3893,3908,3912,3936,3943,3947,3967,3997,4005,4009,4013,4017,4033,4037,4045,4049,4060,4070,4080,4090,4100,4108,4116,4124,4128,4132,4136,4154,4158,4164,4168,4183,4197,4211,4226,4241,4256,4271,4286,4301,4316,4321,4326,4331,4337,4343,4349,4355,4361,4389,4412,4423,4428,4469,4482,4487,4512,4533,4546,4559,4564,4584,4610,4623,4628,4652,4670,4692,4697,4702,4708,4714,4720,4726,4732,4738,4743,4749,4755,4761,4767,4773,4805,4810,4834,4843,4848,4870,4886,4897,4906,4911,4930,4942,4951,4956,4971,4976,4981,4986,4998,5004,5010,5021,5031,5041,5046,5066,5074,5089,5094,5105,5116,5121,5141,5165,5170,5200,5212,5220,5235,5240,5270,5282,5287,5299,5306,5311,5322,5338,5349,5360,5365,5371,5376,5390,5414,5419,5428,5433,5439,5444,5453,5458,5463,5469,5475,5481,5487,5493,5499,5505,5533,5559,5564,5581,5597,5606,5611,5625,5630,5643,5671,5679,5697,5704,5709,5714,5720,5726,5739,5764,5772,5780,5785,5790,5801,5812,5818,5824,5830,5836,5846,5852,5863,5869,5880,5892,5902,5911,5920,5931,5942,5947,5966,5985,6001,6020,6039,6058,6077,6082,6087,6092,6102,6108,6113,6120,6125,6141,6148,6153,6158,6163,6192,6203,6208,6227,6246,6263,6270,6275,6280,6292,6303,6316,6321,6332,6342,6353,6358,6369,6379,6390,6395,6412,6417],{"__ignoreMap":227},[231,3470,3471,3473],{"class":233,"line":234},[231,3472,770],{"class":573},[231,3474,3475],{"class":773}," \"ap.h\"\n",[231,3477,3478,3480],{"class":233,"line":581},[231,3479,770],{"class":573},[231,3481,3482],{"class":773}," \"store.h\"\n",[231,3484,3485,3487],{"class":233,"line":588},[231,3486,770],{"class":573},[231,3488,3489],{"class":773}," \u003Cstring>\n",[231,3491,3492,3494],{"class":233,"line":594},[231,3493,770],{"class":573},[231,3495,3496],{"class":773}," \u003Cvector>\n",[231,3498,3499],{"class":233,"line":599},[231,3500,755],{"emptyLinePlaceholder":754},[231,3502,3503,3505,3508,3510,3513,3515,3518],{"class":233,"line":605},[231,3504,574],{"class":573},[231,3506,3507],{"class":577}," comment",[231,3509,1444],{"class":237},[231,3511,3512],{"class":577},"lib",[231,3514,424],{"class":237},[231,3516,3517],{"class":773},"\"advapi32.lib\"",[231,3519,1717],{"class":237},[231,3521,3522],{"class":233,"line":611},[231,3523,755],{"emptyLinePlaceholder":754},[231,3525,3526],{"class":233,"line":617},[231,3527,3528],{"class":584},"\u002F\u002F NTSTATUS values we return. They live in ntstatus.h, which clashes with\n",[231,3530,3531],{"class":233,"line":623},[231,3532,3533],{"class":584},"\u002F\u002F windows.h, so we define the handful we need.\n",[231,3535,3536,3539],{"class":233,"line":629},[231,3537,3538],{"class":573},"#ifndef",[231,3540,3541],{"class":577}," STATUS_SUCCESS\n",[231,3543,3544,3546,3549,3552,3555,3558,3561],{"class":233,"line":634},[231,3545,761],{"class":573},[231,3547,3548],{"class":577}," STATUS_SUCCESS",[231,3550,3551],{"class":237},"            ((NTSTATUS)",[231,3553,3554],{"class":573},"0x",[231,3556,3557],{"class":240},"00000000",[231,3559,3560],{"class":573},"L",[231,3562,1717],{"class":237},[231,3564,3565],{"class":233,"line":640},[231,3566,3567],{"class":573},"#endif\n",[231,3569,3570,3572],{"class":233,"line":646},[231,3571,3538],{"class":573},[231,3573,3574],{"class":577}," STATUS_ACCOUNT_RESTRICTION\n",[231,3576,3577,3579,3582,3585,3587,3590,3592,3595],{"class":233,"line":652},[231,3578,761],{"class":573},[231,3580,3581],{"class":577}," STATUS_ACCOUNT_RESTRICTION",[231,3583,3584],{"class":237}," ((NTSTATUS)",[231,3586,3554],{"class":573},[231,3588,3589],{"class":240},"C000006E",[231,3591,3560],{"class":573},[231,3593,3594],{"class":237},")",[231,3596,3597],{"class":584},"  \u002F\u002F \"account not allowed to log on here\u002Fnow\"\n",[231,3599,3600],{"class":233,"line":657},[231,3601,3567],{"class":573},[231,3603,3604,3606],{"class":233,"line":663},[231,3605,3538],{"class":573},[231,3607,3608],{"class":577}," STATUS_NOT_IMPLEMENTED\n",[231,3610,3611,3613,3616,3619,3621,3624,3626],{"class":233,"line":669},[231,3612,761],{"class":573},[231,3614,3615],{"class":577}," STATUS_NOT_IMPLEMENTED",[231,3617,3618],{"class":237},"    ((NTSTATUS)",[231,3620,3554],{"class":573},[231,3622,3623],{"class":240},"C0000002",[231,3625,3560],{"class":573},[231,3627,1717],{"class":237},[231,3629,3630],{"class":233,"line":675},[231,3631,3567],{"class":573},[231,3633,3634,3636],{"class":233,"line":681},[231,3635,3538],{"class":573},[231,3637,3638],{"class":577}," STATUS_INVALID_PARAMETER\n",[231,3640,3641,3643,3646,3649,3651,3654,3656],{"class":233,"line":687},[231,3642,761],{"class":573},[231,3644,3645],{"class":577}," STATUS_INVALID_PARAMETER",[231,3647,3648],{"class":237},"  ((NTSTATUS)",[231,3650,3554],{"class":573},[231,3652,3653],{"class":240},"C000000D",[231,3655,3560],{"class":573},[231,3657,1717],{"class":237},[231,3659,3660],{"class":233,"line":693},[231,3661,3567],{"class":573},[231,3663,3664,3666],{"class":233,"line":699},[231,3665,3538],{"class":573},[231,3667,3668],{"class":577}," STATUS_INSUFFICIENT_RESOURCES\n",[231,3670,3671,3673,3676,3678,3680,3683,3685],{"class":233,"line":704},[231,3672,761],{"class":573},[231,3674,3675],{"class":577}," STATUS_INSUFFICIENT_RESOURCES",[231,3677,3584],{"class":237},[231,3679,3554],{"class":573},[231,3681,3682],{"class":240},"C000009A",[231,3684,3560],{"class":573},[231,3686,1717],{"class":237},[231,3688,3689],{"class":233,"line":710},[231,3690,3567],{"class":573},[231,3692,3693],{"class":233,"line":716},[231,3694,755],{"emptyLinePlaceholder":754},[231,3696,3697],{"class":233,"line":722},[231,3698,3699],{"class":584},"\u002F\u002F Every export below runs inside lsass.exe. The helpers use std::wstring,\n",[231,3701,3702],{"class":233,"line":728},[231,3703,3704],{"class":584},"\u002F\u002F which can throw std::bad_alloc; each export therefore has a catch-all that\n",[231,3706,3707],{"class":233,"line":734},[231,3708,3709],{"class":584},"\u002F\u002F turns any C++ exception into a failure status. An exception that reached\n",[231,3711,3712],{"class":233,"line":739},[231,3713,3714],{"class":584},"\u002F\u002F LSA would take lsass, and with it the whole machine, down.\n",[231,3716,3717],{"class":233,"line":745},[231,3718,755],{"emptyLinePlaceholder":754},[231,3720,3721],{"class":233,"line":751},[231,3722,3723],{"class":584},"\u002F\u002F Kerberos message types we recognise in a submit buffer.\n",[231,3725,3726,3728],{"class":233,"line":758},[231,3727,3538],{"class":573},[231,3729,3730],{"class":577}," KerbInteractiveLogon\n",[231,3732,3733,3735,3738],{"class":233,"line":767},[231,3734,761],{"class":573},[231,3736,3737],{"class":577}," KerbInteractiveLogon",[231,3739,3740],{"class":240},"        2\n",[231,3742,3743],{"class":233,"line":777},[231,3744,3567],{"class":573},[231,3746,3747,3749],{"class":233,"line":785},[231,3748,3538],{"class":573},[231,3750,3751],{"class":577}," KerbWorkstationUnlockLogon\n",[231,3753,3754,3756,3759],{"class":233,"line":793},[231,3755,761],{"class":573},[231,3757,3758],{"class":577}," KerbWorkstationUnlockLogon",[231,3760,3761],{"class":240},"  7\n",[231,3763,3764],{"class":233,"line":801},[231,3765,3567],{"class":573},[231,3767,3768],{"class":233,"line":806},[231,3769,755],{"emptyLinePlaceholder":754},[231,3771,3772],{"class":233,"line":815},[231,3773,3774],{"class":573},"namespace\n",[231,3776,3777],{"class":233,"line":821},[231,3778,818],{"class":237},[231,3780,3781,3784,3786,3788],{"class":233,"line":836},[231,3782,3783],{"class":237},"    PLSA_DISPATCH_TABLE g_lsa ",[231,3785,1630],{"class":573},[231,3787,2140],{"class":240},[231,3789,1652],{"class":237},[231,3791,3792,3795,3797,3799],{"class":233,"line":842},[231,3793,3794],{"class":237},"    ULONG               g_packageId ",[231,3796,1630],{"class":573},[231,3798,1732],{"class":240},[231,3800,1652],{"class":237},[231,3802,3803],{"class":233,"line":848},[231,3804,755],{"emptyLinePlaceholder":754},[231,3806,3807],{"class":233,"line":854},[231,3808,3809],{"class":584},"    \u002F\u002F Logging from inside lsass. OutputDebugStringW is the channel you actually\n",[231,3811,3812],{"class":233,"line":860},[231,3813,3814],{"class":584},"    \u002F\u002F read under a kernel debugger. ReportEvent is best-effort on top, guarded\n",[231,3816,3817],{"class":233,"line":872},[231,3818,3819],{"class":584},"    \u002F\u002F so a logging failure can never affect a logon. We never log a password or\n",[231,3821,3822],{"class":233,"line":877},[231,3823,3824],{"class":584},"    \u002F\u002F a code, only metadata.\n",[231,3826,3827,3830,3833,3835,3838,3841,3843,3845,3848,3850,3852,3854,3856,3858,3860,3863],{"class":233,"line":889},[231,3828,3829],{"class":573},"    void",[231,3831,3832],{"class":577}," ApLog",[231,3834,1444],{"class":237},[231,3836,3837],{"class":577},"WORD",[231,3839,3840],{"class":1462}," type",[231,3842,424],{"class":237},[231,3844,1468],{"class":577},[231,3846,3847],{"class":1462}," id",[231,3849,424],{"class":237},[231,3851,1447],{"class":573},[231,3853,1450],{"class":577},[231,3855,1453],{"class":237},[231,3857,1456],{"class":577},[231,3859,1459],{"class":573},[231,3861,3862],{"class":1462}," text",[231,3864,1717],{"class":237},[231,3866,3867],{"class":233,"line":895},[231,3868,1722],{"class":237},[231,3870,3871,3874,3876,3879],{"class":233,"line":901},[231,3872,3873],{"class":577},"        OutputDebugStringW",[231,3875,1444],{"class":237},[231,3877,3878],{"class":773},"L\"[Doppio-AP] \"",[231,3880,1476],{"class":237},[231,3882,3883,3885,3888,3890],{"class":233,"line":907},[231,3884,3873],{"class":577},[231,3886,3887],{"class":237},"(text.",[231,3889,2159],{"class":577},[231,3891,3892],{"class":237},"());\n",[231,3894,3895,3897,3899,3901,3904,3906],{"class":233,"line":913},[231,3896,3873],{"class":577},[231,3898,1444],{"class":237},[231,3900,2462],{"class":773},[231,3902,3903],{"class":240},"\\n",[231,3905,2468],{"class":773},[231,3907,1476],{"class":237},[231,3909,3910],{"class":233,"line":919},[231,3911,755],{"emptyLinePlaceholder":754},[231,3913,3914,3917,3920,3922,3925,3927,3929,3931,3934],{"class":233,"line":930},[231,3915,3916],{"class":577},"        HANDLE",[231,3918,3919],{"class":237}," h ",[231,3921,1630],{"class":573},[231,3923,3924],{"class":577}," RegisterEventSourceW",[231,3926,1444],{"class":237},[231,3928,2250],{"class":240},[231,3930,424],{"class":237},[231,3932,3933],{"class":773},"L\"TheAdminCafe 2FA\"",[231,3935,1476],{"class":237},[231,3937,3938,3940],{"class":233,"line":936},[231,3939,1739],{"class":573},[231,3941,3942],{"class":237}," (h)\n",[231,3944,3945],{"class":233,"line":942},[231,3946,1921],{"class":237},[231,3948,3949,3952,3955,3957,3959,3962,3964],{"class":233,"line":948},[231,3950,3951],{"class":237},"            LPCWSTR s[",[231,3953,3954],{"class":240},"1",[231,3956,2190],{"class":237},[231,3958,1630],{"class":573},[231,3960,3961],{"class":237}," { text.",[231,3963,2159],{"class":577},[231,3965,3966],{"class":237},"() };\n",[231,3968,3969,3972,3975,3977,3980,3982,3984,3986,3988,3990,3993,3995],{"class":233,"line":954},[231,3970,3971],{"class":577},"            ReportEventW",[231,3973,3974],{"class":237},"(h, type, ",[231,3976,2411],{"class":240},[231,3978,3979],{"class":237},", id, ",[231,3981,2250],{"class":240},[231,3983,424],{"class":237},[231,3985,3954],{"class":240},[231,3987,424],{"class":237},[231,3989,2411],{"class":240},[231,3991,3992],{"class":237},", s, ",[231,3994,2250],{"class":240},[231,3996,1476],{"class":237},[231,3998,3999,4002],{"class":233,"line":964},[231,4000,4001],{"class":577},"            DeregisterEventSource",[231,4003,4004],{"class":237},"(h);\n",[231,4006,4007],{"class":233,"line":975},[231,4008,1990],{"class":237},[231,4010,4011],{"class":233,"line":985},[231,4012,2057],{"class":237},[231,4014,4015],{"class":233,"line":995},[231,4016,755],{"emptyLinePlaceholder":754},[231,4018,4019,4021,4024,4026,4028,4031],{"class":233,"line":1001},[231,4020,1438],{"class":573},[231,4022,4023],{"class":577}," IsNonInteractive",[231,4025,1444],{"class":237},[231,4027,1196],{"class":577},[231,4029,4030],{"class":1462}," t",[231,4032,1717],{"class":237},[231,4034,4035],{"class":233,"line":1012},[231,4036,1722],{"class":237},[231,4038,4039,4042],{"class":233,"line":1017},[231,4040,4041],{"class":573},"        switch",[231,4043,4044],{"class":237}," (t)\n",[231,4046,4047],{"class":233,"line":1029},[231,4048,1921],{"class":237},[231,4050,4051,4054,4057],{"class":233,"line":1035},[231,4052,4053],{"class":573},"        case",[231,4055,4056],{"class":237}," Network:",[231,4058,4059],{"class":584},"            \u002F\u002F 3\n",[231,4061,4062,4064,4067],{"class":233,"line":1041},[231,4063,4053],{"class":573},[231,4065,4066],{"class":237}," Batch:",[231,4068,4069],{"class":584},"              \u002F\u002F 4\n",[231,4071,4072,4074,4077],{"class":233,"line":1051},[231,4073,4053],{"class":573},[231,4075,4076],{"class":237}," Service:",[231,4078,4079],{"class":584},"            \u002F\u002F 5\n",[231,4081,4082,4084,4087],{"class":233,"line":1057},[231,4083,4053],{"class":573},[231,4085,4086],{"class":237}," NetworkCleartext:",[231,4088,4089],{"class":584},"   \u002F\u002F 8\n",[231,4091,4092,4094,4097],{"class":233,"line":1062},[231,4093,4053],{"class":573},[231,4095,4096],{"class":237}," NewCredentials:",[231,4098,4099],{"class":584},"     \u002F\u002F 9\n",[231,4101,4102,4104,4106],{"class":233,"line":1074},[231,4103,1756],{"class":573},[231,4105,2050],{"class":240},[231,4107,1652],{"class":237},[231,4109,4110,4113],{"class":233,"line":1079},[231,4111,4112],{"class":573},"        default",[231,4114,4115],{"class":237},":\n",[231,4117,4118,4120,4122],{"class":233,"line":1084},[231,4119,1756],{"class":573},[231,4121,1759],{"class":240},[231,4123,1652],{"class":237},[231,4125,4126],{"class":233,"line":1093},[231,4127,1990],{"class":237},[231,4129,4130],{"class":233,"line":1098},[231,4131,2057],{"class":237},[231,4133,4134],{"class":233,"line":1103},[231,4135,755],{"emptyLinePlaceholder":754},[231,4137,4138,4141,4143,4146,4148,4150,4152],{"class":233,"line":1115},[231,4139,4140],{"class":573},"    const",[231,4142,2754],{"class":573},[231,4144,4145],{"class":577}," LogonTypeName",[231,4147,1444],{"class":237},[231,4149,1196],{"class":577},[231,4151,4030],{"class":1462},[231,4153,1717],{"class":237},[231,4155,4156],{"class":233,"line":1120},[231,4157,1722],{"class":237},[231,4159,4160,4162],{"class":233,"line":1125},[231,4161,4041],{"class":573},[231,4163,4044],{"class":237},[231,4165,4166],{"class":233,"line":1134},[231,4167,1921],{"class":237},[231,4169,4170,4172,4175,4178,4181],{"class":233,"line":1139},[231,4171,4053],{"class":573},[231,4173,4174],{"class":237}," Interactive:       ",[231,4176,4177],{"class":573},"return",[231,4179,4180],{"class":773}," L\"Interactive\"",[231,4182,1652],{"class":237},[231,4184,4185,4187,4190,4192,4195],{"class":233,"line":1144},[231,4186,4053],{"class":573},[231,4188,4189],{"class":237}," Network:           ",[231,4191,4177],{"class":573},[231,4193,4194],{"class":773}," L\"Network\"",[231,4196,1652],{"class":237},[231,4198,4199,4201,4204,4206,4209],{"class":233,"line":1158},[231,4200,4053],{"class":573},[231,4202,4203],{"class":237}," Batch:             ",[231,4205,4177],{"class":573},[231,4207,4208],{"class":773}," L\"Batch\"",[231,4210,1652],{"class":237},[231,4212,4214,4216,4219,4221,4224],{"class":233,"line":4213},85,[231,4215,4053],{"class":573},[231,4217,4218],{"class":237}," Service:           ",[231,4220,4177],{"class":573},[231,4222,4223],{"class":773}," L\"Service\"",[231,4225,1652],{"class":237},[231,4227,4229,4231,4234,4236,4239],{"class":233,"line":4228},86,[231,4230,4053],{"class":573},[231,4232,4233],{"class":237}," Unlock:            ",[231,4235,4177],{"class":573},[231,4237,4238],{"class":773}," L\"Unlock\"",[231,4240,1652],{"class":237},[231,4242,4244,4246,4249,4251,4254],{"class":233,"line":4243},87,[231,4245,4053],{"class":573},[231,4247,4248],{"class":237}," NetworkCleartext:  ",[231,4250,4177],{"class":573},[231,4252,4253],{"class":773}," L\"NetworkCleartext\"",[231,4255,1652],{"class":237},[231,4257,4259,4261,4264,4266,4269],{"class":233,"line":4258},88,[231,4260,4053],{"class":573},[231,4262,4263],{"class":237}," NewCredentials:    ",[231,4265,4177],{"class":573},[231,4267,4268],{"class":773}," L\"NewCredentials\"",[231,4270,1652],{"class":237},[231,4272,4274,4276,4279,4281,4284],{"class":233,"line":4273},89,[231,4275,4053],{"class":573},[231,4277,4278],{"class":237}," RemoteInteractive: ",[231,4280,4177],{"class":573},[231,4282,4283],{"class":773}," L\"RemoteInteractive\"",[231,4285,1652],{"class":237},[231,4287,4289,4291,4294,4296,4299],{"class":233,"line":4288},90,[231,4290,4053],{"class":573},[231,4292,4293],{"class":237}," CachedInteractive: ",[231,4295,4177],{"class":573},[231,4297,4298],{"class":773}," L\"CachedInteractive\"",[231,4300,1652],{"class":237},[231,4302,4304,4306,4309,4311,4314],{"class":233,"line":4303},91,[231,4305,4112],{"class":573},[231,4307,4308],{"class":237},":                ",[231,4310,4177],{"class":573},[231,4312,4313],{"class":773}," L\"Other\"",[231,4315,1652],{"class":237},[231,4317,4319],{"class":233,"line":4318},92,[231,4320,1990],{"class":237},[231,4322,4324],{"class":233,"line":4323},93,[231,4325,2057],{"class":237},[231,4327,4329],{"class":233,"line":4328},94,[231,4330,755],{"emptyLinePlaceholder":754},[231,4332,4334],{"class":233,"line":4333},95,[231,4335,4336],{"class":584},"    \u002F\u002F A UNICODE_STRING inside the submit buffer has a Buffer pointer that is\n",[231,4338,4340],{"class":233,"line":4339},96,[231,4341,4342],{"class":584},"    \u002F\u002F valid in the CLIENT process, not ours. LSA gives us ClientBufferBase (the\n",[231,4344,4346],{"class":233,"line":4345},97,[231,4347,4348],{"class":584},"    \u002F\u002F address the buffer had in the client) so we can relocate it into our copy.\n",[231,4350,4352],{"class":233,"line":4351},98,[231,4353,4354],{"class":584},"    \u002F\u002F Everything is bounds-checked against the submit buffer; anything off\n",[231,4356,4358],{"class":233,"line":4357},99,[231,4359,4360],{"class":584},"    \u002F\u002F returns an empty string, and the caller fails safe.\n",[231,4362,4364,4367,4369,4371,4374,4376,4378,4381,4383,4386],{"class":233,"line":4363},100,[231,4365,4366],{"class":577},"    std",[231,4368,1453],{"class":237},[231,4370,1456],{"class":577},[231,4372,4373],{"class":577}," ReadClientString",[231,4375,1444],{"class":237},[231,4377,1447],{"class":573},[231,4379,4380],{"class":577}," UNICODE_STRING",[231,4382,1459],{"class":573},[231,4384,4385],{"class":1462}," us",[231,4387,4388],{"class":237},",\n",[231,4390,4392,4395,4398,4401,4403,4405,4407,4410],{"class":233,"line":4391},101,[231,4393,4394],{"class":573},"                                  const",[231,4396,4397],{"class":573}," void*",[231,4399,4400],{"class":1462}," submitBase",[231,4402,424],{"class":237},[231,4404,1447],{"class":573},[231,4406,4397],{"class":573},[231,4408,4409],{"class":1462}," clientBase",[231,4411,4388],{"class":237},[231,4413,4415,4418,4421],{"class":233,"line":4414},102,[231,4416,4417],{"class":577},"                                  ULONG",[231,4419,4420],{"class":1462}," submitSize",[231,4422,1717],{"class":237},[231,4424,4426],{"class":233,"line":4425},103,[231,4427,1722],{"class":237},[231,4429,4431,4433,4435,4437,4440,4442,4445,4447,4449,4451,4454,4457,4460,4463,4465,4467],{"class":233,"line":4430},104,[231,4432,1739],{"class":573},[231,4434,1742],{"class":237},[231,4436,1745],{"class":573},[231,4438,4439],{"class":237},"us.Buffer ",[231,4441,1806],{"class":573},[231,4443,4444],{"class":237}," us.Length ",[231,4446,1794],{"class":573},[231,4448,1732],{"class":240},[231,4450,1937],{"class":573},[231,4452,4453],{"class":237}," (us.Length ",[231,4455,4456],{"class":573},"%",[231,4458,4459],{"class":573}," sizeof",[231,4461,4462],{"class":237},"(WCHAR)) ",[231,4464,2422],{"class":573},[231,4466,1732],{"class":240},[231,4468,1717],{"class":237},[231,4470,4472,4474,4476,4478,4480],{"class":233,"line":4471},105,[231,4473,1756],{"class":573},[231,4475,1450],{"class":577},[231,4477,1453],{"class":237},[231,4479,1456],{"class":577},[231,4481,2127],{"class":237},[231,4483,4485],{"class":233,"line":4484},106,[231,4486,755],{"emptyLinePlaceholder":754},[231,4488,4490,4493,4496,4499,4501,4504,4507,4509],{"class":233,"line":4489},107,[231,4491,4492],{"class":573},"        const",[231,4494,4495],{"class":577}," ULONG_PTR",[231,4497,4498],{"class":237}," p    ",[231,4500,1630],{"class":573},[231,4502,4503],{"class":573}," reinterpret_cast\u003C",[231,4505,4506],{"class":237},"ULONG_PTR",[231,4508,1943],{"class":573},[231,4510,4511],{"class":237},"(us.Buffer);\n",[231,4513,4515,4517,4519,4522,4524,4526,4528,4530],{"class":233,"line":4514},108,[231,4516,4492],{"class":573},[231,4518,4495],{"class":577},[231,4520,4521],{"class":237}," base ",[231,4523,1630],{"class":573},[231,4525,4503],{"class":573},[231,4527,4506],{"class":237},[231,4529,1943],{"class":573},[231,4531,4532],{"class":237},"(clientBase);\n",[231,4534,4536,4538,4541,4543],{"class":233,"line":4535},109,[231,4537,1739],{"class":573},[231,4539,4540],{"class":237}," (p ",[231,4542,1903],{"class":573},[231,4544,4545],{"class":237}," base)\n",[231,4547,4549,4551,4553,4555,4557],{"class":233,"line":4548},110,[231,4550,1756],{"class":573},[231,4552,1450],{"class":577},[231,4554,1453],{"class":237},[231,4556,1456],{"class":577},[231,4558,2127],{"class":237},[231,4560,4562],{"class":233,"line":4561},111,[231,4563,755],{"emptyLinePlaceholder":754},[231,4565,4567,4569,4571,4574,4576,4579,4581],{"class":233,"line":4566},112,[231,4568,4492],{"class":573},[231,4570,4495],{"class":577},[231,4572,4573],{"class":237}," off ",[231,4575,1630],{"class":573},[231,4577,4578],{"class":237}," p ",[231,4580,1836],{"class":573},[231,4582,4583],{"class":237}," base;\n",[231,4585,4587,4589,4592,4594,4597,4599,4601,4603,4605,4607],{"class":233,"line":4586},113,[231,4588,1739],{"class":573},[231,4590,4591],{"class":237}," (off ",[231,4593,1943],{"class":573},[231,4595,4596],{"class":237}," submitSize ",[231,4598,1806],{"class":573},[231,4600,4444],{"class":237},[231,4602,1943],{"class":573},[231,4604,4596],{"class":237},[231,4606,1836],{"class":573},[231,4608,4609],{"class":237}," off)\n",[231,4611,4613,4615,4617,4619,4621],{"class":233,"line":4612},114,[231,4614,1756],{"class":573},[231,4616,1450],{"class":577},[231,4618,1453],{"class":237},[231,4620,1456],{"class":577},[231,4622,2127],{"class":237},[231,4624,4626],{"class":233,"line":4625},115,[231,4627,755],{"emptyLinePlaceholder":754},[231,4629,4631,4633,4636,4638,4641,4643,4646,4648,4650],{"class":233,"line":4630},116,[231,4632,4492],{"class":573},[231,4634,4635],{"class":577}," WCHAR",[231,4637,866],{"class":573},[231,4639,4640],{"class":237}," s ",[231,4642,1630],{"class":573},[231,4644,4645],{"class":573}," reinterpret_cast\u003Cconst",[231,4647,4635],{"class":237},[231,4649,2867],{"class":573},[231,4651,833],{"class":237},[231,4653,4655,4658,4660,4662,4665,4667],{"class":233,"line":4654},117,[231,4656,4657],{"class":573},"            static_cast\u003Cconst",[231,4659,2864],{"class":237},[231,4661,2867],{"class":573},[231,4663,4664],{"class":237},"(submitBase) ",[231,4666,1811],{"class":573},[231,4668,4669],{"class":237}," off);\n",[231,4671,4673,4675,4677,4679,4681,4684,4687,4689],{"class":233,"line":4672},118,[231,4674,2047],{"class":573},[231,4676,1450],{"class":577},[231,4678,1453],{"class":237},[231,4680,1456],{"class":577},[231,4682,4683],{"class":237},"(s, us.Length ",[231,4685,4686],{"class":573},"\u002F",[231,4688,4459],{"class":573},[231,4690,4691],{"class":237},"(WCHAR));\n",[231,4693,4695],{"class":233,"line":4694},119,[231,4696,2057],{"class":237},[231,4698,4700],{"class":233,"line":4699},120,[231,4701,755],{"emptyLinePlaceholder":754},[231,4703,4705],{"class":233,"line":4704},121,[231,4706,4707],{"class":584},"    \u002F\u002F The account check. We read ONLY the user name from the submit buffer\n",[231,4709,4711],{"class":233,"line":4710},122,[231,4712,4713],{"class":584},"    \u002F\u002F (never the password field) and ask the store, with a single registry\n",[231,4715,4717],{"class":233,"line":4716},123,[231,4718,4719],{"class":584},"    \u002F\u002F read, whether that name is enrolled. IsEnrolledByName does no DPAPI and\n",[231,4721,4723],{"class":233,"line":4722},124,[231,4724,4725],{"class":584},"    \u002F\u002F no LSA name\u002FSID lookup, so it is safe on the logon path. Any\n",[231,4727,4729],{"class":233,"line":4728},125,[231,4730,4731],{"class":584},"    \u002F\u002F inconsistency returns false (treat as not enrolled -> defer), so a\n",[231,4733,4735],{"class":233,"line":4734},126,[231,4736,4737],{"class":584},"    \u002F\u002F malformed buffer cannot crash us.\n",[231,4739,4741],{"class":233,"line":4740},127,[231,4742,1500],{"class":584},[231,4744,4746],{"class":233,"line":4745},128,[231,4747,4748],{"class":584},"    \u002F\u002F The name is what the CLIENT typed, and the name index is not updated by\n",[231,4750,4752],{"class":233,"line":4751},129,[231,4753,4754],{"class":584},"    \u002F\u002F a rename (see store.h), so this check is weaker than the RID check in\n",[231,4756,4758],{"class":233,"line":4757},130,[231,4759,4760],{"class":584},"    \u002F\u002F the sub-authentication filter. It does not matter here - this package\n",[231,4762,4764],{"class":233,"line":4763},131,[231,4765,4766],{"class":584},"    \u002F\u002F never lets a logon through either way (see ap.h) - but do not copy it\n",[231,4768,4770],{"class":233,"line":4769},132,[231,4771,4772],{"class":584},"    \u002F\u002F into anything that does.\n",[231,4774,4776,4778,4781,4783,4786,4789,4791,4794,4797,4799,4801,4803],{"class":233,"line":4775},133,[231,4777,1438],{"class":573},[231,4779,4780],{"class":577}," IsEnrolledAccount",[231,4782,1444],{"class":237},[231,4784,4785],{"class":577},"PVOID",[231,4787,4788],{"class":1462}," submit",[231,4790,424],{"class":237},[231,4792,4793],{"class":577},"ULONG",[231,4795,4796],{"class":1462}," size",[231,4798,424],{"class":237},[231,4800,4785],{"class":577},[231,4802,4409],{"class":1462},[231,4804,1717],{"class":237},[231,4806,4808],{"class":233,"line":4807},134,[231,4809,1722],{"class":237},[231,4811,4813,4815,4817,4819,4822,4824,4827,4829,4831],{"class":233,"line":4812},135,[231,4814,1739],{"class":573},[231,4816,1742],{"class":237},[231,4818,1745],{"class":573},[231,4820,4821],{"class":237},"submit ",[231,4823,1806],{"class":573},[231,4825,4826],{"class":237}," size ",[231,4828,1903],{"class":573},[231,4830,4459],{"class":573},[231,4832,4833],{"class":237},"(KERB_INTERACTIVE_LOGON))\n",[231,4835,4837,4839,4841],{"class":233,"line":4836},136,[231,4838,1756],{"class":573},[231,4840,1759],{"class":240},[231,4842,1652],{"class":237},[231,4844,4846],{"class":233,"line":4845},137,[231,4847,755],{"emptyLinePlaceholder":754},[231,4849,4851,4854,4857,4859,4862,4865,4867],{"class":233,"line":4850},138,[231,4852,4853],{"class":573},"        auto*",[231,4855,4856],{"class":237}," k ",[231,4858,1630],{"class":573},[231,4860,4861],{"class":573}," static_cast\u003Cconst",[231,4863,4864],{"class":237}," KERB_INTERACTIVE_LOGON",[231,4866,2867],{"class":573},[231,4868,4869],{"class":237},"(submit);\n",[231,4871,4873,4875,4878,4880,4883],{"class":233,"line":4872},139,[231,4874,1739],{"class":573},[231,4876,4877],{"class":237}," (k->MessageType ",[231,4879,2422],{"class":573},[231,4881,4882],{"class":237}," KerbInteractiveLogon ",[231,4884,4885],{"class":573},"&&\n",[231,4887,4889,4892,4894],{"class":233,"line":4888},140,[231,4890,4891],{"class":237},"            k->MessageType ",[231,4893,2422],{"class":573},[231,4895,4896],{"class":237}," KerbWorkstationUnlockLogon)\n",[231,4898,4900,4902,4904],{"class":233,"line":4899},141,[231,4901,1756],{"class":573},[231,4903,1759],{"class":240},[231,4905,1652],{"class":237},[231,4907,4909],{"class":233,"line":4908},142,[231,4910,755],{"emptyLinePlaceholder":754},[231,4912,4914,4916,4918,4920,4923,4925,4927],{"class":233,"line":4913},143,[231,4915,2569],{"class":577},[231,4917,1453],{"class":237},[231,4919,1456],{"class":577},[231,4921,4922],{"class":237}," user ",[231,4924,1630],{"class":573},[231,4926,4373],{"class":577},[231,4928,4929],{"class":237},"(k->UserName, submit, clientBase, size);\n",[231,4931,4933,4935,4938,4940],{"class":233,"line":4932},144,[231,4934,1739],{"class":573},[231,4936,4937],{"class":237}," (user.",[231,4939,2324],{"class":577},[231,4941,2595],{"class":237},[231,4943,4945,4947,4949],{"class":233,"line":4944},145,[231,4946,1756],{"class":573},[231,4948,1759],{"class":240},[231,4950,1652],{"class":237},[231,4952,4954],{"class":233,"line":4953},146,[231,4955,755],{"emptyLinePlaceholder":754},[231,4957,4959,4961,4964,4966,4969],{"class":233,"line":4958},147,[231,4960,2047],{"class":573},[231,4962,4963],{"class":577}," tac",[231,4965,1453],{"class":237},[231,4967,4968],{"class":577},"IsEnrolledByName",[231,4970,2583],{"class":237},[231,4972,4974],{"class":233,"line":4973},148,[231,4975,2057],{"class":237},[231,4977,4979],{"class":233,"line":4978},149,[231,4980,1161],{"class":237},[231,4982,4984],{"class":233,"line":4983},150,[231,4985,755],{"emptyLinePlaceholder":754},[231,4987,4989,4992,4994,4996],{"class":233,"line":4988},151,[231,4990,4991],{"class":237},"NTSTATUS ",[231,4993,827],{"class":577},[231,4995,830],{"class":577},[231,4997,833],{"class":237},[231,4999,5001],{"class":233,"line":5000},152,[231,5002,5003],{"class":237},"    ULONG AuthenticationPackageId,\n",[231,5005,5007],{"class":233,"line":5006},153,[231,5008,5009],{"class":237},"    PLSA_DISPATCH_TABLE LsaDispatchTable,\n",[231,5011,5013,5016,5019],{"class":233,"line":5012},154,[231,5014,5015],{"class":237},"    PLSA_STRING",[231,5017,5018],{"class":584}," \u002F*Database*\u002F",[231,5020,4388],{"class":237},[231,5022,5024,5026,5029],{"class":233,"line":5023},155,[231,5025,5015],{"class":237},[231,5027,5028],{"class":584}," \u002F*Confidentiality*\u002F",[231,5030,4388],{"class":237},[231,5032,5034,5036,5038],{"class":233,"line":5033},156,[231,5035,5015],{"class":237},[231,5037,866],{"class":573},[231,5039,5040],{"class":237}," AuthenticationPackageName)\n",[231,5042,5044],{"class":233,"line":5043},157,[231,5045,818],{"class":237},[231,5047,5049,5052,5054,5056,5059,5061,5063],{"class":233,"line":5048},158,[231,5050,5051],{"class":573},"    if",[231,5053,1742],{"class":237},[231,5055,1745],{"class":573},[231,5057,5058],{"class":237},"LsaDispatchTable ",[231,5060,1806],{"class":573},[231,5062,2318],{"class":573},[231,5064,5065],{"class":237},"AuthenticationPackageName)\n",[231,5067,5069,5071],{"class":233,"line":5068},159,[231,5070,2047],{"class":573},[231,5072,5073],{"class":237}," STATUS_INVALID_PARAMETER;\n",[231,5075,5077,5080,5083,5085,5087],{"class":233,"line":5076},160,[231,5078,5079],{"class":573},"    *",[231,5081,5082],{"class":237},"AuthenticationPackageName ",[231,5084,1630],{"class":573},[231,5086,2140],{"class":240},[231,5088,1652],{"class":237},[231,5090,5092],{"class":233,"line":5091},161,[231,5093,755],{"emptyLinePlaceholder":754},[231,5095,5097,5100,5102],{"class":233,"line":5096},162,[231,5098,5099],{"class":237},"    g_lsa       ",[231,5101,1630],{"class":573},[231,5103,5104],{"class":237}," LsaDispatchTable;\n",[231,5106,5108,5111,5113],{"class":233,"line":5107},163,[231,5109,5110],{"class":237},"    g_packageId ",[231,5112,1630],{"class":573},[231,5114,5115],{"class":237}," AuthenticationPackageId;\n",[231,5117,5119],{"class":233,"line":5118},164,[231,5120,755],{"emptyLinePlaceholder":754},[231,5122,5124,5126,5128,5131,5134,5136,5139],{"class":233,"line":5123},165,[231,5125,1618],{"class":573},[231,5127,1621],{"class":573},[231,5129,5130],{"class":573}," char",[231,5132,5133],{"class":237}," kName[] ",[231,5135,1630],{"class":573},[231,5137,5138],{"class":773}," \"TacAuthPackage\"",[231,5140,1652],{"class":237},[231,5142,5144,5146,5149,5152,5154,5156,5159,5161,5163],{"class":233,"line":5143},166,[231,5145,4140],{"class":573},[231,5147,5148],{"class":577}," USHORT",[231,5150,5151],{"class":237}," len ",[231,5153,1630],{"class":573},[231,5155,4459],{"class":573},[231,5157,5158],{"class":237},"(kName) ",[231,5160,1836],{"class":573},[231,5162,1814],{"class":240},[231,5164,1652],{"class":237},[231,5166,5168],{"class":233,"line":5167},167,[231,5169,755],{"emptyLinePlaceholder":754},[231,5171,5173,5176,5179,5181,5184,5187,5190,5193,5195,5197],{"class":233,"line":5172},168,[231,5174,5175],{"class":573},"    char*",[231,5177,5178],{"class":237}," buf ",[231,5180,1630],{"class":573},[231,5182,5183],{"class":573}," static_cast\u003Cchar*>",[231,5185,5186],{"class":237},"(g_lsa->",[231,5188,5189],{"class":577},"AllocateLsaHeap",[231,5191,5192],{"class":237},"(len ",[231,5194,1811],{"class":573},[231,5196,1814],{"class":240},[231,5198,5199],{"class":237},"));\n",[231,5201,5203,5205,5207,5209],{"class":233,"line":5202},169,[231,5204,5051],{"class":573},[231,5206,1742],{"class":237},[231,5208,1745],{"class":573},[231,5210,5211],{"class":237},"buf)\n",[231,5213,5215,5217],{"class":233,"line":5214},170,[231,5216,2047],{"class":573},[231,5218,5219],{"class":237}," STATUS_NO_MEMORY;\n",[231,5221,5223,5226,5229,5231,5233],{"class":233,"line":5222},171,[231,5224,5225],{"class":577},"    memcpy",[231,5227,5228],{"class":237},"(buf, kName, len ",[231,5230,1811],{"class":573},[231,5232,1814],{"class":240},[231,5234,1476],{"class":237},[231,5236,5238],{"class":233,"line":5237},172,[231,5239,755],{"emptyLinePlaceholder":754},[231,5241,5243,5246,5248,5250,5252,5254,5257,5259,5261,5263,5265,5267],{"class":233,"line":5242},173,[231,5244,5245],{"class":577},"    LSA_STRING",[231,5247,866],{"class":573},[231,5249,2576],{"class":237},[231,5251,1630],{"class":573},[231,5253,2035],{"class":573},[231,5255,5256],{"class":237},"LSA_STRING",[231,5258,2867],{"class":573},[231,5260,5186],{"class":237},[231,5262,5189],{"class":577},[231,5264,1444],{"class":237},[231,5266,2877],{"class":573},[231,5268,5269],{"class":237},"(LSA_STRING)));\n",[231,5271,5273,5275,5277,5279],{"class":233,"line":5272},174,[231,5274,5051],{"class":573},[231,5276,1742],{"class":237},[231,5278,1745],{"class":573},[231,5280,5281],{"class":237},"name)\n",[231,5283,5285],{"class":233,"line":5284},175,[231,5286,1722],{"class":237},[231,5288,5290,5293,5296],{"class":233,"line":5289},176,[231,5291,5292],{"class":237},"        g_lsa->",[231,5294,5295],{"class":577},"FreeLsaHeap",[231,5297,5298],{"class":237},"(buf);\n",[231,5300,5302,5304],{"class":233,"line":5301},177,[231,5303,2047],{"class":573},[231,5305,5219],{"class":237},[231,5307,5309],{"class":233,"line":5308},178,[231,5310,2057],{"class":237},[231,5312,5314,5317,5319],{"class":233,"line":5313},179,[231,5315,5316],{"class":237},"    name->Length        ",[231,5318,1630],{"class":573},[231,5320,5321],{"class":237}," len;\n",[231,5323,5325,5328,5330,5332,5334,5336],{"class":233,"line":5324},180,[231,5326,5327],{"class":237},"    name->MaximumLength ",[231,5329,1630],{"class":573},[231,5331,5151],{"class":237},[231,5333,1811],{"class":573},[231,5335,1814],{"class":240},[231,5337,1652],{"class":237},[231,5339,5341,5344,5346],{"class":233,"line":5340},181,[231,5342,5343],{"class":237},"    name->Buffer        ",[231,5345,1630],{"class":573},[231,5347,5348],{"class":237}," buf;\n",[231,5350,5352,5354,5356,5358],{"class":233,"line":5351},182,[231,5353,5079],{"class":573},[231,5355,5082],{"class":237},[231,5357,1630],{"class":573},[231,5359,3041],{"class":237},[231,5361,5363],{"class":233,"line":5362},183,[231,5364,755],{"emptyLinePlaceholder":754},[231,5366,5368],{"class":233,"line":5367},184,[231,5369,5370],{"class":573},"    try\n",[231,5372,5374],{"class":233,"line":5373},185,[231,5375,1722],{"class":237},[231,5377,5379,5382,5385,5388],{"class":233,"line":5378},186,[231,5380,5381],{"class":577},"        ApLog",[231,5383,5384],{"class":237},"(EVENTLOG_INFORMATION_TYPE, ",[231,5386,5387],{"class":240},"200",[231,5389,4388],{"class":237},[231,5391,5393,5396,5398,5400,5402,5404,5407,5409,5412],{"class":233,"line":5392},187,[231,5394,5395],{"class":773},"              L\"LSA authentication package loaded (id \"",[231,5397,1973],{"class":573},[231,5399,1450],{"class":577},[231,5401,1453],{"class":237},[231,5403,3024],{"class":577},[231,5405,5406],{"class":237},"(AuthenticationPackageId) ",[231,5408,1811],{"class":573},[231,5410,5411],{"class":773}," L\").\"",[231,5413,1476],{"class":237},[231,5415,5417],{"class":233,"line":5416},188,[231,5418,2057],{"class":237},[231,5420,5422,5425],{"class":233,"line":5421},189,[231,5423,5424],{"class":573},"    catch",[231,5426,5427],{"class":237}," (...)\n",[231,5429,5431],{"class":233,"line":5430},190,[231,5432,1722],{"class":237},[231,5434,5436],{"class":233,"line":5435},191,[231,5437,5438],{"class":584},"        \u002F\u002F The log line is optional; the package is loaded either way.\n",[231,5440,5442],{"class":233,"line":5441},192,[231,5443,2057],{"class":237},[231,5445,5447,5450],{"class":233,"line":5446},193,[231,5448,5449],{"class":573},"    return",[231,5451,5452],{"class":237}," STATUS_SUCCESS;\n",[231,5454,5456],{"class":233,"line":5455},194,[231,5457,1161],{"class":237},[231,5459,5461],{"class":233,"line":5460},195,[231,5462,755],{"emptyLinePlaceholder":754},[231,5464,5466],{"class":233,"line":5465},196,[231,5467,5468],{"class":584},"\u002F\u002F We only ever DENY or decline here; we never mint a token. The deny path\n",[231,5470,5472],{"class":233,"line":5471},197,[231,5473,5474],{"class":584},"\u002F\u002F returns STATUS_ACCOUNT_RESTRICTION. Otherwise we return STATUS_NOT_IMPLEMENTED\n",[231,5476,5478],{"class":233,"line":5477},198,[231,5479,5480],{"class":584},"\u002F\u002F to say \"this package does not handle this logon\". Note: a caller that selects\n",[231,5482,5484],{"class":233,"line":5483},199,[231,5485,5486],{"class":584},"\u002F\u002F this package by id and gets STATUS_NOT_IMPLEMENTED has its logon fail here; it\n",[231,5488,5490],{"class":233,"line":5489},200,[231,5491,5492],{"class":584},"\u002F\u002F is not transparently retried against MSV1_0. Standard network\u002Fbatch logons do\n",[231,5494,5496],{"class":233,"line":5495},201,[231,5497,5498],{"class":584},"\u002F\u002F not address this package at all - that is what the sub-authentication filter\n",[231,5500,5502],{"class":233,"line":5501},202,[231,5503,5504],{"class":584},"\u002F\u002F (subauth.cpp) is for. See the article, \"deny is easy, success is hard\".\n",[231,5506,5508,5511,5514,5517,5519,5521,5524,5526,5528,5531],{"class":233,"line":5507},203,[231,5509,5510],{"class":573},"static",[231,5512,5513],{"class":577}," NTSTATUS",[231,5515,5516],{"class":577}," LogonUserEx2Impl",[231,5518,1444],{"class":237},[231,5520,1196],{"class":577},[231,5522,5523],{"class":1462}," LogonType",[231,5525,424],{"class":237},[231,5527,4785],{"class":577},[231,5529,5530],{"class":1462}," ProtocolSubmitBuffer",[231,5532,4388],{"class":237},[231,5534,5536,5539,5542,5544,5546,5549,5551,5554,5557],{"class":233,"line":5535},204,[231,5537,5538],{"class":577},"                                 PVOID",[231,5540,5541],{"class":1462}," ClientBufferBase",[231,5543,424],{"class":237},[231,5545,4793],{"class":577},[231,5547,5548],{"class":1462}," SubmitBufferSize",[231,5550,424],{"class":237},[231,5552,5553],{"class":577},"PNTSTATUS",[231,5555,5556],{"class":1462}," SubStatus",[231,5558,1717],{"class":237},[231,5560,5562],{"class":233,"line":5561},205,[231,5563,818],{"class":237},[231,5565,5567,5569,5571,5574,5576,5578],{"class":233,"line":5566},206,[231,5568,4140],{"class":573},[231,5570,2078],{"class":573},[231,5572,5573],{"class":237}," nonInteractive ",[231,5575,1630],{"class":573},[231,5577,4023],{"class":577},[231,5579,5580],{"class":237},"(LogonType);\n",[231,5582,5584,5586,5588,5591,5593,5595],{"class":233,"line":5583},207,[231,5585,4140],{"class":573},[231,5587,2078],{"class":573},[231,5589,5590],{"class":237}," enrolled       ",[231,5592,1630],{"class":573},[231,5594,5573],{"class":237},[231,5596,4885],{"class":573},[231,5598,5600,5603],{"class":233,"line":5599},208,[231,5601,5602],{"class":577},"                                IsEnrolledAccount",[231,5604,5605],{"class":237},"(ProtocolSubmitBuffer, SubmitBufferSize, ClientBufferBase);\n",[231,5607,5609],{"class":233,"line":5608},209,[231,5610,755],{"emptyLinePlaceholder":754},[231,5612,5614,5616,5619,5622],{"class":233,"line":5613},210,[231,5615,5051],{"class":573},[231,5617,5618],{"class":237}," (nonInteractive ",[231,5620,5621],{"class":573},"&&",[231,5623,5624],{"class":237}," enrolled)\n",[231,5626,5628],{"class":233,"line":5627},211,[231,5629,1722],{"class":237},[231,5631,5633,5635,5638,5641],{"class":233,"line":5632},212,[231,5634,5381],{"class":577},[231,5636,5637],{"class":237},"(EVENTLOG_WARNING_TYPE, ",[231,5639,5640],{"class":240},"201",[231,5642,4388],{"class":237},[231,5644,5646,5649,5651,5653,5655,5658,5661,5663,5665,5668],{"class":233,"line":5645},213,[231,5647,5648],{"class":577},"              std",[231,5650,1453],{"class":237},[231,5652,1456],{"class":577},[231,5654,1444],{"class":237},[231,5656,5657],{"class":773},"L\"Denied a \"",[231,5659,5660],{"class":237},") ",[231,5662,1811],{"class":573},[231,5664,4145],{"class":577},[231,5666,5667],{"class":237},"(LogonType) ",[231,5669,5670],{"class":573},"+\n",[231,5672,5674,5677],{"class":233,"line":5673},214,[231,5675,5676],{"class":773},"              L\" logon for an enrolled account. This path has no second factor.\"",[231,5678,1476],{"class":237},[231,5680,5682,5684,5687,5689,5692,5694],{"class":233,"line":5681},215,[231,5683,1739],{"class":573},[231,5685,5686],{"class":237}," (SubStatus) ",[231,5688,866],{"class":573},[231,5690,5691],{"class":237},"SubStatus ",[231,5693,1630],{"class":573},[231,5695,5696],{"class":237}," STATUS_ACCOUNT_RESTRICTION;\n",[231,5698,5700,5702],{"class":233,"line":5699},216,[231,5701,2047],{"class":573},[231,5703,5696],{"class":237},[231,5705,5707],{"class":233,"line":5706},217,[231,5708,2057],{"class":237},[231,5710,5712],{"class":233,"line":5711},218,[231,5713,755],{"emptyLinePlaceholder":754},[231,5715,5717],{"class":233,"line":5716},219,[231,5718,5719],{"class":584},"    \u002F\u002F Not \"passed on\": nothing is forwarded anywhere. The caller asked for this\n",[231,5721,5723],{"class":233,"line":5722},220,[231,5724,5725],{"class":584},"    \u002F\u002F package, and this package never builds a token, so the logon fails.\n",[231,5727,5729,5732,5734,5737],{"class":233,"line":5728},221,[231,5730,5731],{"class":577},"    ApLog",[231,5733,5384],{"class":237},[231,5735,5736],{"class":240},"202",[231,5738,4388],{"class":237},[231,5740,5742,5745,5747,5749,5751,5754,5756,5758,5760,5762],{"class":233,"line":5741},222,[231,5743,5744],{"class":577},"          std",[231,5746,1453],{"class":237},[231,5748,1456],{"class":577},[231,5750,1444],{"class":237},[231,5752,5753],{"class":773},"L\"Declined a \"",[231,5755,5660],{"class":237},[231,5757,1811],{"class":573},[231,5759,4145],{"class":577},[231,5761,5667],{"class":237},[231,5763,5670],{"class":573},[231,5765,5767,5770],{"class":233,"line":5766},223,[231,5768,5769],{"class":773},"          L\" logon addressed to this package. It fails; this package never builds a token.\"",[231,5771,1476],{"class":237},[231,5773,5775,5777],{"class":233,"line":5774},224,[231,5776,5449],{"class":573},[231,5778,5779],{"class":237}," STATUS_NOT_IMPLEMENTED;\n",[231,5781,5783],{"class":233,"line":5782},225,[231,5784,1161],{"class":237},[231,5786,5788],{"class":233,"line":5787},226,[231,5789,755],{"emptyLinePlaceholder":754},[231,5791,5793,5795,5797,5799],{"class":233,"line":5792},227,[231,5794,4991],{"class":237},[231,5796,827],{"class":577},[231,5798,884],{"class":577},[231,5800,833],{"class":237},[231,5802,5804,5807,5810],{"class":233,"line":5803},228,[231,5805,5806],{"class":237},"    PLSA_CLIENT_REQUEST",[231,5808,5809],{"class":584}," \u002F*ClientRequest*\u002F",[231,5811,4388],{"class":237},[231,5813,5815],{"class":233,"line":5814},229,[231,5816,5817],{"class":237},"    SECURITY_LOGON_TYPE LogonType,\n",[231,5819,5821],{"class":233,"line":5820},230,[231,5822,5823],{"class":237},"    PVOID ProtocolSubmitBuffer,\n",[231,5825,5827],{"class":233,"line":5826},231,[231,5828,5829],{"class":237},"    PVOID ClientBufferBase,\n",[231,5831,5833],{"class":233,"line":5832},232,[231,5834,5835],{"class":237},"    ULONG SubmitBufferSize,\n",[231,5837,5839,5842,5844],{"class":233,"line":5838},233,[231,5840,5841],{"class":237},"    PVOID",[231,5843,866],{"class":573},[231,5845,927],{"class":237},[231,5847,5849],{"class":233,"line":5848},234,[231,5850,5851],{"class":237},"    PULONG ProfileBufferSize,\n",[231,5853,5855,5858,5861],{"class":233,"line":5854},235,[231,5856,5857],{"class":237},"    PLUID",[231,5859,5860],{"class":584}," \u002F*LogonId*\u002F",[231,5862,4388],{"class":237},[231,5864,5866],{"class":233,"line":5865},236,[231,5867,5868],{"class":237},"    PNTSTATUS SubStatus,\n",[231,5870,5872,5875,5878],{"class":233,"line":5871},237,[231,5873,5874],{"class":237},"    PLSA_TOKEN_INFORMATION_TYPE",[231,5876,5877],{"class":584}," \u002F*TokenInformationType*\u002F",[231,5879,4388],{"class":237},[231,5881,5883,5885,5887,5890],{"class":233,"line":5882},238,[231,5884,5841],{"class":237},[231,5886,866],{"class":573},[231,5888,5889],{"class":584}," \u002F*TokenInformation*\u002F",[231,5891,4388],{"class":237},[231,5893,5895,5898,5900],{"class":233,"line":5894},239,[231,5896,5897],{"class":237},"    PUNICODE_STRING",[231,5899,866],{"class":573},[231,5901,972],{"class":237},[231,5903,5905,5907,5909],{"class":233,"line":5904},240,[231,5906,5897],{"class":237},[231,5908,866],{"class":573},[231,5910,982],{"class":237},[231,5912,5914,5916,5918],{"class":233,"line":5913},241,[231,5915,5897],{"class":237},[231,5917,866],{"class":573},[231,5919,992],{"class":237},[231,5921,5923,5926,5929],{"class":233,"line":5922},242,[231,5924,5925],{"class":237},"    PSECPKG_PRIMARY_CRED",[231,5927,5928],{"class":584}," \u002F*PrimaryCredentials*\u002F",[231,5930,4388],{"class":237},[231,5932,5934,5937,5939],{"class":233,"line":5933},243,[231,5935,5936],{"class":237},"    PSECPKG_SUPPLEMENTAL_CRED_ARRAY",[231,5938,866],{"class":573},[231,5940,5941],{"class":237}," CachedCredentials)\n",[231,5943,5945],{"class":233,"line":5944},244,[231,5946,818],{"class":237},[231,5948,5950,5952,5955,5957,5960,5962,5964],{"class":233,"line":5949},245,[231,5951,5051],{"class":573},[231,5953,5954],{"class":237}," (ProfileBuffer)            ",[231,5956,866],{"class":573},[231,5958,5959],{"class":237},"ProfileBuffer ",[231,5961,1630],{"class":573},[231,5963,2140],{"class":240},[231,5965,1652],{"class":237},[231,5967,5969,5971,5974,5976,5979,5981,5983],{"class":233,"line":5968},246,[231,5970,5051],{"class":573},[231,5972,5973],{"class":237}," (ProfileBufferSize)        ",[231,5975,866],{"class":573},[231,5977,5978],{"class":237},"ProfileBufferSize ",[231,5980,1630],{"class":573},[231,5982,1732],{"class":240},[231,5984,1652],{"class":237},[231,5986,5988,5990,5993,5995,5997,5999],{"class":233,"line":5987},247,[231,5989,5051],{"class":573},[231,5991,5992],{"class":237}," (SubStatus)                ",[231,5994,866],{"class":573},[231,5996,5691],{"class":237},[231,5998,1630],{"class":573},[231,6000,5452],{"class":237},[231,6002,6004,6006,6009,6011,6014,6016,6018],{"class":233,"line":6003},248,[231,6005,5051],{"class":573},[231,6007,6008],{"class":237}," (AccountName)              ",[231,6010,866],{"class":573},[231,6012,6013],{"class":237},"AccountName ",[231,6015,1630],{"class":573},[231,6017,2140],{"class":240},[231,6019,1652],{"class":237},[231,6021,6023,6025,6028,6030,6033,6035,6037],{"class":233,"line":6022},249,[231,6024,5051],{"class":573},[231,6026,6027],{"class":237}," (AuthenticatingAuthority)  ",[231,6029,866],{"class":573},[231,6031,6032],{"class":237},"AuthenticatingAuthority ",[231,6034,1630],{"class":573},[231,6036,2140],{"class":240},[231,6038,1652],{"class":237},[231,6040,6042,6044,6047,6049,6052,6054,6056],{"class":233,"line":6041},250,[231,6043,5051],{"class":573},[231,6045,6046],{"class":237}," (MachineName)              ",[231,6048,866],{"class":573},[231,6050,6051],{"class":237},"MachineName ",[231,6053,1630],{"class":573},[231,6055,2140],{"class":240},[231,6057,1652],{"class":237},[231,6059,6061,6063,6066,6068,6071,6073,6075],{"class":233,"line":6060},251,[231,6062,5051],{"class":573},[231,6064,6065],{"class":237}," (CachedCredentials)        ",[231,6067,866],{"class":573},[231,6069,6070],{"class":237},"CachedCredentials ",[231,6072,1630],{"class":573},[231,6074,2140],{"class":240},[231,6076,1652],{"class":237},[231,6078,6080],{"class":233,"line":6079},252,[231,6081,755],{"emptyLinePlaceholder":754},[231,6083,6085],{"class":233,"line":6084},253,[231,6086,5370],{"class":573},[231,6088,6090],{"class":233,"line":6089},254,[231,6091,1722],{"class":237},[231,6093,6095,6097,6099],{"class":233,"line":6094},255,[231,6096,2047],{"class":573},[231,6098,5516],{"class":577},[231,6100,6101],{"class":237},"(LogonType, ProtocolSubmitBuffer, ClientBufferBase,\n",[231,6103,6105],{"class":233,"line":6104},256,[231,6106,6107],{"class":237},"                                SubmitBufferSize, SubStatus);\n",[231,6109,6111],{"class":233,"line":6110},257,[231,6112,2057],{"class":237},[231,6114,6116,6118],{"class":233,"line":6115},258,[231,6117,5424],{"class":573},[231,6119,5427],{"class":237},[231,6121,6123],{"class":233,"line":6122},259,[231,6124,1722],{"class":237},[231,6126,6128,6130,6132,6134,6136,6138],{"class":233,"line":6127},260,[231,6129,1739],{"class":573},[231,6131,5686],{"class":237},[231,6133,866],{"class":573},[231,6135,5691],{"class":237},[231,6137,1630],{"class":573},[231,6139,6140],{"class":237}," STATUS_INSUFFICIENT_RESOURCES;\n",[231,6142,6144,6146],{"class":233,"line":6143},261,[231,6145,2047],{"class":573},[231,6147,6140],{"class":237},[231,6149,6151],{"class":233,"line":6150},262,[231,6152,2057],{"class":237},[231,6154,6156],{"class":233,"line":6155},263,[231,6157,1161],{"class":237},[231,6159,6161],{"class":233,"line":6160},264,[231,6162,755],{"emptyLinePlaceholder":754},[231,6164,6166,6168,6170,6173,6175,6177,6179,6182,6184,6187,6190],{"class":233,"line":6165},265,[231,6167,5510],{"class":573},[231,6169,5513],{"class":577},[231,6171,6172],{"class":577}," CallStub",[231,6174,1444],{"class":237},[231,6176,4785],{"class":577},[231,6178,866],{"class":573},[231,6180,6181],{"class":1462}," ProtocolReturnBuffer",[231,6183,424],{"class":237},[231,6185,6186],{"class":577},"PULONG",[231,6188,6189],{"class":1462}," ReturnBufferLength",[231,6191,4388],{"class":237},[231,6193,6195,6198,6201],{"class":233,"line":6194},266,[231,6196,6197],{"class":577},"                         PNTSTATUS",[231,6199,6200],{"class":1462}," ProtocolStatus",[231,6202,1717],{"class":237},[231,6204,6206],{"class":233,"line":6205},267,[231,6207,818],{"class":237},[231,6209,6211,6213,6216,6218,6221,6223,6225],{"class":233,"line":6210},268,[231,6212,5051],{"class":573},[231,6214,6215],{"class":237}," (ProtocolReturnBuffer) ",[231,6217,866],{"class":573},[231,6219,6220],{"class":237},"ProtocolReturnBuffer ",[231,6222,1630],{"class":573},[231,6224,2140],{"class":240},[231,6226,1652],{"class":237},[231,6228,6230,6232,6235,6237,6240,6242,6244],{"class":233,"line":6229},269,[231,6231,5051],{"class":573},[231,6233,6234],{"class":237}," (ReturnBufferLength)   ",[231,6236,866],{"class":573},[231,6238,6239],{"class":237},"ReturnBufferLength ",[231,6241,1630],{"class":573},[231,6243,1732],{"class":240},[231,6245,1652],{"class":237},[231,6247,6249,6251,6254,6256,6259,6261],{"class":233,"line":6248},270,[231,6250,5051],{"class":573},[231,6252,6253],{"class":237}," (ProtocolStatus)       ",[231,6255,866],{"class":573},[231,6257,6258],{"class":237},"ProtocolStatus ",[231,6260,1630],{"class":573},[231,6262,5779],{"class":237},[231,6264,6266,6268],{"class":233,"line":6265},271,[231,6267,5449],{"class":573},[231,6269,5452],{"class":237},[231,6271,6273],{"class":233,"line":6272},272,[231,6274,1161],{"class":237},[231,6276,6278],{"class":233,"line":6277},273,[231,6279,755],{"emptyLinePlaceholder":754},[231,6281,6283,6285,6287,6289],{"class":233,"line":6282},274,[231,6284,4991],{"class":237},[231,6286,827],{"class":577},[231,6288,1024],{"class":577},[231,6290,6291],{"class":237},"(PLSA_CLIENT_REQUEST, PVOID, PVOID, ULONG,\n",[231,6293,6295,6298,6300],{"class":233,"line":6294},275,[231,6296,6297],{"class":237},"                                PVOID",[231,6299,866],{"class":573},[231,6301,6302],{"class":237}," rb, PULONG rl, PNTSTATUS ps)\n",[231,6304,6306,6309,6311,6313],{"class":233,"line":6305},276,[231,6307,6308],{"class":237},"{ ",[231,6310,4177],{"class":573},[231,6312,6172],{"class":577},[231,6314,6315],{"class":237},"(rb, rl, ps); }\n",[231,6317,6319],{"class":233,"line":6318},277,[231,6320,755],{"emptyLinePlaceholder":754},[231,6322,6324,6326,6328,6330],{"class":233,"line":6323},278,[231,6325,4991],{"class":237},[231,6327,827],{"class":577},[231,6329,1069],{"class":577},[231,6331,6291],{"class":237},[231,6333,6335,6338,6340],{"class":233,"line":6334},279,[231,6336,6337],{"class":237},"                                         PVOID",[231,6339,866],{"class":573},[231,6341,6302],{"class":237},[231,6343,6345,6347,6349,6351],{"class":233,"line":6344},280,[231,6346,6308],{"class":237},[231,6348,4177],{"class":573},[231,6350,6172],{"class":577},[231,6352,6315],{"class":237},[231,6354,6356],{"class":233,"line":6355},281,[231,6357,755],{"emptyLinePlaceholder":754},[231,6359,6361,6363,6365,6367],{"class":233,"line":6360},282,[231,6362,4991],{"class":237},[231,6364,827],{"class":577},[231,6366,1110],{"class":577},[231,6368,6291],{"class":237},[231,6370,6372,6375,6377],{"class":233,"line":6371},283,[231,6373,6374],{"class":237},"                                           PVOID",[231,6376,866],{"class":573},[231,6378,6302],{"class":237},[231,6380,6382,6384,6386,6388],{"class":233,"line":6381},284,[231,6383,6308],{"class":237},[231,6385,4177],{"class":573},[231,6387,6172],{"class":577},[231,6389,6315],{"class":237},[231,6391,6393],{"class":233,"line":6392},285,[231,6394,755],{"emptyLinePlaceholder":754},[231,6396,6398,6401,6403,6405,6408,6410],{"class":233,"line":6397},286,[231,6399,6400],{"class":237},"VOID ",[231,6402,827],{"class":577},[231,6404,1152],{"class":577},[231,6406,6407],{"class":237},"(PLUID",[231,6409,5860],{"class":584},[231,6411,1717],{"class":237},[231,6413,6415],{"class":233,"line":6414},287,[231,6416,818],{"class":237},[231,6418,6420],{"class":233,"line":6419},288,[231,6421,1161],{"class":237},[15,6423,6424],{},"Some things I want to point out:",[111,6426,6427,6430,6448,6454,6466,6480,6502],{},[114,6428,6429],{},"All the out parameters are set at the top, before any decision. If LSA reads an uninitialised pointer because some path forgot to set it, you do not get a clean failure, you get a crash in lsass.",[114,6431,6432,6435,6436,6439,6440,6443,6444,6447],{},[28,6433,6434],{},"ReadClientString"," checks ",[28,6437,6438],{},"off > submitSize"," first and only then ",[28,6441,6442],{},"us.Length > submitSize - off",". The order matters. The other way around, ",[28,6445,6446],{},"submitSize - off"," would underflow on an out-of-range offset and the check would pass.",[114,6449,6450,6453],{},[28,6451,6452],{},"IsEnrolledAccount"," only parses two message types. An unknown one returns false, which means \"not enrolled\", which means the logon is declined rather than denied. Unknown input has to fail in the harmless direction.",[114,6455,6456,6458,6459,6462,6463,6465],{},[28,6457,5189],{}," is LSA's allocator from the dispatch table, not ",[28,6460,6461],{},"new",". The package name you hand back belongs to LSA afterwards. If the second allocation fails, the first one has to be freed with ",[28,6464,5295],{},", which is the kind of bookkeeping you normally would not bother with and here absolutely should.",[114,6467,6468,6471,6472,6475,6476,6479],{},[28,6469,6470],{},"ApLog"," writes to ",[28,6473,6474],{},"OutputDebugStringW"," first and the event log second. The event log is best effort. If ",[28,6477,6478],{},"RegisterEventSourceW"," fails inside early boot, the logon must not care.",[114,6481,6482,6483,6485,6486,6489,6490,6493,6494,6497,6498,6501],{},"Every export catches every C++ exception. ",[28,6484,6434],{}," and the log lines use ",[28,6487,6488],{},"std::wstring",", and that can throw ",[28,6491,6492],{},"std::bad_alloc",". If an exception goes back into LSA, lsass goes down, and the machine with it. Now it becomes ",[28,6495,6496],{},"STATUS_INSUFFICIENT_RESOURCES",". The real work is in ",[28,6499,6500],{},"LogonUserEx2Impl",", the export is just the safety net around it.",[114,6503,6504],{},"The log line for the decline says what really happens: \"Declined ... It fails\". My first version said \"Passed a ... logon to the normal packages\", which is exactly the misreading the paragraph above warns about.",[15,6506,6507,561],{},[28,6508,441],{},[222,6510,6513],{"className":6511,"code":6512,"language":517,"meta":227},[515],"LIBRARY   TacAuthPackage\nEXPORTS\n    LsaApInitializePackage\n    LsaApLogonUserEx2\n    LsaApCallPackage\n    LsaApCallPackageUntrusted\n    LsaApCallPackagePassthrough\n    LsaApLogonTerminated\n",[28,6514,6512],{"__ignoreMap":227},[259,6516,6518],{"id":6517},"the-limit-that-pushes-us-one-layer-deeper","The limit that pushes us one layer deeper",[15,6520,6521,6522,6525,6526,6529,6530,6533],{},"Here is the thing that makes the whole package above mostly an exercise. A top-level authentication package is only called for logons that ",[48,6523,6524],{},"address it by package id",". A caller does ",[28,6527,6528],{},"LsaLookupAuthenticationPackage(\"TacAuthPackage\")"," and then passes that id to ",[28,6531,6532],{},"LsaLogonUser",". A normal SMB logon asks for MSV1_0, or for Negotiate. Nobody asks for us.",[15,6535,6536],{},"So by itself this package never even sees the network path it is supposed to guard. That is not a bug in the code, that is how the layer works. And it is exactly why real products do the fragile MSV1_0 wrap.",[15,6538,6539],{},"There is a cleaner answer.",[10,6541,6543],{"id":6542},"the-sub-authentication-filter","The sub-authentication filter",[15,6545,6546],{},"MSV1_0 has a documented way to hook into its own logon processing. Actually two of them. I picked the wrong one in the first version of this article, so first both side by side:",[271,6548,6549,6559],{},[274,6550,6551],{},[277,6552,6553,6555,6557],{},[280,6554],{},[280,6556,310],{},[280,6558,327],{},[290,6560,6561,6574,6591,6601,6612],{},[277,6562,6563,6566,6570],{},[295,6564,6565],{},"Export",[295,6567,6568],{},[28,6569,69],{},[295,6571,6572],{},[28,6573,78],{},[277,6575,6576,6579,6586],{},[295,6577,6578],{},"Registered as",[295,6580,6581,316,6584],{},[28,6582,6583],{},"MSV1_0\\Auth1",[28,6585,319],{},[295,6587,6588],{},[28,6589,6590],{},"MSV1_0\\Auth0",[277,6592,6593,6595,6598],{},[295,6594,288],{},[295,6596,6597],{},"only for logons that select package N",[295,6599,6600],{},"after MSV1_0 has validated a logon",[277,6602,6603,6606,6609],{},[295,6604,6605],{},"The password check",[295,6607,6608],{},"is its own job",[295,6610,6611],{},"is already done by MSV1_0",[277,6613,6614,6621,6624],{},[295,6615,6616,6617,6620],{},"Returning ",[28,6618,6619],{},"STATUS_SUCCESS"," means",[295,6622,6623],{},"\"authenticated\"",[295,6625,6626],{},"\"no objection\"",[15,6628,6629,6630,6633,6634,6636],{},"My first version used the routine. I thought it only adds a decision and MSV1_0 still checks the password afterwards. Microsoft's documentation says the opposite: \"When subauthentication is used, authentication is the responsibility of the subauthentication DLL.\" The routine gets the challenge-response and the password hashes so that ",[72,6631,6632],{},"it"," can check them. MSV1_0 just trusts the answer. My routine only read the name and returned ",[28,6635,6619],{}," for everything except an enrolled network logon. So for a logon that selected it, this was an approval without any password check. A code audit found it. That's why there is the correction at the top.",[15,6638,6639],{},"For a second factor you want the filter. MSV1_0 does the password check, the account restrictions and the token, exactly as without us. The filter only gets to say \"no\". That is the property I claimed for the routine, and only the filter has it.",[15,6641,6642,6643,6645,6646,6649],{},"There is one catch, and I don't want to hide it. Microsoft documents ",[28,6644,433],{}," for the registry of a domain controller. Whether MSV1_0 also calls the filter for local accounts on a Windows 10\u002F11 workstation, I could not check yet. Every call writes a ",[28,6647,6648],{},"[Doppio-SubAuth]"," line to the kernel debugger, so this is the first thing to check in your VM. If you never see a line, the filter is loaded but never asked. Then the user rights from the beginning are your only enforcement.",[259,6651,6653],{"id":6652},"why-this-one-and-not-the-authentication-package","Why this one and not the authentication package",[15,6655,6656],{},"Side by side, with the tile from Part 1 in the first column for orientation:",[271,6658,6659,6673],{},[274,6660,6661],{},[277,6662,6663,6665,6668,6670],{},[280,6664],{},[280,6666,6667],{},"Credential provider",[280,6669,297],{},[280,6671,6672],{},"Sub-auth filter",[290,6674,6675,6692,6710,6729,6743,6757,6769,6783,6795],{},[277,6676,6677,6679,6684,6688],{},[295,6678,401],{},[295,6680,6681],{},[28,6682,6683],{},"CTacCredential.cpp",[295,6685,6686],{},[28,6687,3463],{},[295,6689,6690],{},[28,6691,427],{},[277,6693,6694,6697,6702,6706],{},[295,6695,6696],{},"Runs in",[295,6698,6699],{},[28,6700,6701],{},"LogonUI.exe",[295,6703,6704],{},[28,6705,40],{},[295,6707,6708],{},[28,6709,40],{},[277,6711,6712,6715,6720,6725],{},[295,6713,6714],{},"Registered under",[295,6716,6717],{},[28,6718,6719],{},"Credential Providers",[295,6721,6722],{},[28,6723,6724],{},"Authentication Packages",[295,6726,6727],{},[28,6728,6590],{},[277,6730,6731,6734,6737,6740],{},[295,6732,6733],{},"Called by",[295,6735,6736],{},"Winlogon \u002F LogonUI",[295,6738,6739],{},"LSA, if addressed by id",[295,6741,6742],{},"MSV1_0, after the password check",[277,6744,6745,6748,6751,6754],{},[295,6746,6747],{},"Sees interactive logon",[295,6749,6750],{},"yes",[295,6752,6753],{},"only if addressed",[295,6755,6756],{},"yes, if MSV1_0 calls it",[277,6758,6759,6762,6765,6767],{},[295,6760,6761],{},"Sees network logon",[295,6763,6764],{},"no",[295,6766,6764],{},[295,6768,6756],{},[277,6770,6771,6774,6777,6780],{},[295,6772,6773],{},"Credential material",[295,6775,6776],{},"collects it, it has to",[295,6778,6779],{},"sits in the submit buffer",[295,6781,6782],{},"never touched, account id only",[277,6784,6785,6788,6790,6793],{},[295,6786,6787],{},"Must build a token",[295,6789,6764],{},[295,6791,6792],{},"yes, on success",[295,6794,6764],{},[277,6796,6797,6800,6803,6806],{},[295,6798,6799],{},"A bug costs you",[295,6801,6802],{},"one tile",[295,6804,6805],{},"the machine",[295,6807,6805],{},[15,6809,6810],{},"Four things make this the easier layer:",[111,6812,6813,6833,6839,6845],{},[114,6814,6815,6818,6819,6822,6823,5660,6826,6829,6830,6832],{},[48,6816,6817],{},"The account comes from the SAM, not from the client."," The filter gets ",[28,6820,6821],{},"USER_ALL_INFORMATION",", the account record that MSV1_0 read from the SAM. The RID in there (",[28,6824,6825],{},"UserId",[72,6827,6828],{},"is"," the account, no matter how the client wrote the name. No pointer relocation, no ",[28,6831,6434],{},", and no problem with renames.",[114,6834,6835,6838],{},[48,6836,6837],{},"No token to build."," We return a veto or no objection, and MSV1_0 produces the token, the profile and the group memberships.",[114,6840,6841,6844],{},[48,6842,6843],{},"It runs after the password check."," \"No objection\" can never let anything through that MSV1_0 refused. A bug here can at worst miss a veto. It can never accept a wrong password. That's exactly what you want from code you can't test on the bench.",[114,6846,6847,6850],{},[48,6848,6849],{},"It never needs the credentials."," The decision needs the account id, and that plus the name for the log is all we read. There is no point in this design where a password or a hash has to pass through our code.",[15,6852,6853,6854,6857],{},"One thing I do not want to blur, though. \"The safest way to do this inside LSA\" is not the same claim as \"the safe way to do this\". The user rights from the beginning of the article run no code in lsass at all and cannot take a machine down. This runs inside lsass. It is the right way to do it ",[72,6855,6856],{},"in LSA",". It is not the low-risk way to harden a machine you care about.",[15,6859,6860,561],{},[28,6861,423],{},[222,6863,6865],{"className":564,"code":6864,"language":566,"meta":227,"style":227},"#pragma once\n\u002F\u002F\n\u002F\u002F (Named subauth_filter.h, not subauth.h: with the project folder on the\n\u002F\u002F include path, a project header called subauth.h would hide the SDK header\n\u002F\u002F of the same name that this file includes.)\n\u002F\u002F\n\u002F\u002F Doppio MSV1_0 sub-authentication FILTER (SKELETON).\n\u002F\u002F\n\u002F\u002F MSV1_0 has two sub-authentication hooks, and they are very different:\n\u002F\u002F\n\u002F\u002F   Msv1_0SubAuthenticationRoutine (Auth1..AuthN)\n\u002F\u002F       Called only for logons whose request explicitly selects package N in\n\u002F\u002F       ParameterControl - ordinary SMB\u002FNTLM logons never do. And when it IS\n\u002F\u002F       called, authentication is its job: MSV1_0 hands it the challenge\n\u002F\u002F       response and the SAM hashes and trusts its answer. Returning\n\u002F\u002F       STATUS_SUCCESS there approves the logon without any password check.\n\u002F\u002F       That is the wrong seam for a veto, and this project no longer uses it.\n\u002F\u002F\n\u002F\u002F   Msv1_0SubAuthenticationFilter (Auth0)\n\u002F\u002F       Called AFTER MSV1_0 has validated the logon, as an additional check.\n\u002F\u002F       STATUS_SUCCESS means \"no objection, proceed\", a failure status vetoes.\n\u002F\u002F       MSV1_0 keeps the password check, the account restrictions and the\n\u002F\u002F       token. This is the seam a second-factor veto belongs in.\n\u002F\u002F\n\u002F\u002F Policy of this skeleton: refuse a NETWORK logon for an enrolled account (no UI\n\u002F\u002F to type a code there), and raise no objection to anything else.\n\u002F\u002F\n\u002F\u002F The account is identified by the RID that MSV1_0 read from the SAM\n\u002F\u002F (USER_ALL_INFORMATION::UserId), not by the name the client sent. The RID\n\u002F\u002F survives a rename and cannot be spelled differently.\n\u002F\u002F\n\u002F\u002F It is registered under:\n\u002F\u002F   HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\MSV1_0\n\u002F\u002F     Auth0 (REG_SZ) = TacSubAuth\n\u002F\u002F Microsoft documents Auth0 for the domain controller's registry. Whether the\n\u002F\u002F filter also runs for local SAM accounts on a workstation is exactly what to\n\u002F\u002F verify in the test VM: every call writes a [Doppio-SubAuth] OutputDebugString\n\u002F\u002F line, readable under the kernel debugger.\n\u002F\u002F\n\u002F\u002F WARNING: runs in lsass.exe. A bug can leave the machine unbootable. Throwaway\n\u002F\u002F VM, snapshot, kernel debugger only.\n\n#define SECURITY_WIN32\n#include \u003Cwindows.h>\n#include \u003Csspi.h>\n#include \u003Cntsecapi.h>\n#include \u003Csubauth.h>\n\nextern \"C\"\n{\n    NTSTATUS NTAPI Msv1_0SubAuthenticationFilter(\n        NETLOGON_LOGON_INFO_CLASS LogonLevel,\n        PVOID LogonInformation,\n        ULONG Flags,\n        PUSER_ALL_INFORMATION UserAll,\n        PULONG WhichFields,\n        PULONG UserFlags,\n        PBOOLEAN Authoritative,\n        PLARGE_INTEGER LogoffTime,\n        PLARGE_INTEGER KickoffTime);\n}\n",[28,6866,6867,6873,6877,6882,6887,6892,6896,6901,6905,6910,6914,6919,6924,6929,6934,6939,6944,6949,6953,6958,6963,6968,6973,6978,6982,6987,6992,6996,7001,7006,7011,7015,7020,7025,7030,7035,7040,7045,7050,7054,7059,7064,7068,7074,7080,7086,7092,7099,7103,7109,7113,7124,7129,7134,7139,7144,7149,7154,7159,7164,7169],{"__ignoreMap":227},[231,6868,6869,6871],{"class":233,"line":234},[231,6870,574],{"class":573},[231,6872,578],{"class":577},[231,6874,6875],{"class":233,"line":581},[231,6876,585],{"class":584},[231,6878,6879],{"class":233,"line":588},[231,6880,6881],{"class":584},"\u002F\u002F (Named subauth_filter.h, not subauth.h: with the project folder on the\n",[231,6883,6884],{"class":233,"line":594},[231,6885,6886],{"class":584},"\u002F\u002F include path, a project header called subauth.h would hide the SDK header\n",[231,6888,6889],{"class":233,"line":599},[231,6890,6891],{"class":584},"\u002F\u002F of the same name that this file includes.)\n",[231,6893,6894],{"class":233,"line":605},[231,6895,585],{"class":584},[231,6897,6898],{"class":233,"line":611},[231,6899,6900],{"class":584},"\u002F\u002F Doppio MSV1_0 sub-authentication FILTER (SKELETON).\n",[231,6902,6903],{"class":233,"line":617},[231,6904,585],{"class":584},[231,6906,6907],{"class":233,"line":623},[231,6908,6909],{"class":584},"\u002F\u002F MSV1_0 has two sub-authentication hooks, and they are very different:\n",[231,6911,6912],{"class":233,"line":629},[231,6913,585],{"class":584},[231,6915,6916],{"class":233,"line":634},[231,6917,6918],{"class":584},"\u002F\u002F   Msv1_0SubAuthenticationRoutine (Auth1..AuthN)\n",[231,6920,6921],{"class":233,"line":640},[231,6922,6923],{"class":584},"\u002F\u002F       Called only for logons whose request explicitly selects package N in\n",[231,6925,6926],{"class":233,"line":646},[231,6927,6928],{"class":584},"\u002F\u002F       ParameterControl - ordinary SMB\u002FNTLM logons never do. And when it IS\n",[231,6930,6931],{"class":233,"line":652},[231,6932,6933],{"class":584},"\u002F\u002F       called, authentication is its job: MSV1_0 hands it the challenge\n",[231,6935,6936],{"class":233,"line":657},[231,6937,6938],{"class":584},"\u002F\u002F       response and the SAM hashes and trusts its answer. Returning\n",[231,6940,6941],{"class":233,"line":663},[231,6942,6943],{"class":584},"\u002F\u002F       STATUS_SUCCESS there approves the logon without any password check.\n",[231,6945,6946],{"class":233,"line":669},[231,6947,6948],{"class":584},"\u002F\u002F       That is the wrong seam for a veto, and this project no longer uses it.\n",[231,6950,6951],{"class":233,"line":675},[231,6952,585],{"class":584},[231,6954,6955],{"class":233,"line":681},[231,6956,6957],{"class":584},"\u002F\u002F   Msv1_0SubAuthenticationFilter (Auth0)\n",[231,6959,6960],{"class":233,"line":687},[231,6961,6962],{"class":584},"\u002F\u002F       Called AFTER MSV1_0 has validated the logon, as an additional check.\n",[231,6964,6965],{"class":233,"line":693},[231,6966,6967],{"class":584},"\u002F\u002F       STATUS_SUCCESS means \"no objection, proceed\", a failure status vetoes.\n",[231,6969,6970],{"class":233,"line":699},[231,6971,6972],{"class":584},"\u002F\u002F       MSV1_0 keeps the password check, the account restrictions and the\n",[231,6974,6975],{"class":233,"line":704},[231,6976,6977],{"class":584},"\u002F\u002F       token. This is the seam a second-factor veto belongs in.\n",[231,6979,6980],{"class":233,"line":710},[231,6981,585],{"class":584},[231,6983,6984],{"class":233,"line":716},[231,6985,6986],{"class":584},"\u002F\u002F Policy of this skeleton: refuse a NETWORK logon for an enrolled account (no UI\n",[231,6988,6989],{"class":233,"line":722},[231,6990,6991],{"class":584},"\u002F\u002F to type a code there), and raise no objection to anything else.\n",[231,6993,6994],{"class":233,"line":728},[231,6995,585],{"class":584},[231,6997,6998],{"class":233,"line":734},[231,6999,7000],{"class":584},"\u002F\u002F The account is identified by the RID that MSV1_0 read from the SAM\n",[231,7002,7003],{"class":233,"line":739},[231,7004,7005],{"class":584},"\u002F\u002F (USER_ALL_INFORMATION::UserId), not by the name the client sent. The RID\n",[231,7007,7008],{"class":233,"line":745},[231,7009,7010],{"class":584},"\u002F\u002F survives a rename and cannot be spelled differently.\n",[231,7012,7013],{"class":233,"line":751},[231,7014,585],{"class":584},[231,7016,7017],{"class":233,"line":758},[231,7018,7019],{"class":584},"\u002F\u002F It is registered under:\n",[231,7021,7022],{"class":233,"line":767},[231,7023,7024],{"class":584},"\u002F\u002F   HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\MSV1_0\n",[231,7026,7027],{"class":233,"line":777},[231,7028,7029],{"class":584},"\u002F\u002F     Auth0 (REG_SZ) = TacSubAuth\n",[231,7031,7032],{"class":233,"line":785},[231,7033,7034],{"class":584},"\u002F\u002F Microsoft documents Auth0 for the domain controller's registry. Whether the\n",[231,7036,7037],{"class":233,"line":793},[231,7038,7039],{"class":584},"\u002F\u002F filter also runs for local SAM accounts on a workstation is exactly what to\n",[231,7041,7042],{"class":233,"line":801},[231,7043,7044],{"class":584},"\u002F\u002F verify in the test VM: every call writes a [Doppio-SubAuth] OutputDebugString\n",[231,7046,7047],{"class":233,"line":806},[231,7048,7049],{"class":584},"\u002F\u002F line, readable under the kernel debugger.\n",[231,7051,7052],{"class":233,"line":815},[231,7053,585],{"class":584},[231,7055,7056],{"class":233,"line":821},[231,7057,7058],{"class":584},"\u002F\u002F WARNING: runs in lsass.exe. A bug can leave the machine unbootable. Throwaway\n",[231,7060,7061],{"class":233,"line":836},[231,7062,7063],{"class":584},"\u002F\u002F VM, snapshot, kernel debugger only.\n",[231,7065,7066],{"class":233,"line":842},[231,7067,755],{"emptyLinePlaceholder":754},[231,7069,7070,7072],{"class":233,"line":848},[231,7071,761],{"class":573},[231,7073,764],{"class":577},[231,7075,7076,7078],{"class":233,"line":854},[231,7077,770],{"class":573},[231,7079,774],{"class":773},[231,7081,7082,7084],{"class":233,"line":860},[231,7083,770],{"class":573},[231,7085,782],{"class":773},[231,7087,7088,7090],{"class":233,"line":872},[231,7089,770],{"class":573},[231,7091,790],{"class":773},[231,7093,7094,7096],{"class":233,"line":877},[231,7095,770],{"class":573},[231,7097,7098],{"class":773}," \u003Csubauth.h>\n",[231,7100,7101],{"class":233,"line":889},[231,7102,755],{"emptyLinePlaceholder":754},[231,7104,7105,7107],{"class":233,"line":895},[231,7106,809],{"class":573},[231,7108,812],{"class":773},[231,7110,7111],{"class":233,"line":901},[231,7112,818],{"class":237},[231,7114,7115,7117,7119,7122],{"class":233,"line":907},[231,7116,824],{"class":237},[231,7118,827],{"class":577},[231,7120,7121],{"class":577}," Msv1_0SubAuthenticationFilter",[231,7123,833],{"class":237},[231,7125,7126],{"class":233,"line":913},[231,7127,7128],{"class":237},"        NETLOGON_LOGON_INFO_CLASS LogonLevel,\n",[231,7130,7131],{"class":233,"line":919},[231,7132,7133],{"class":237},"        PVOID LogonInformation,\n",[231,7135,7136],{"class":233,"line":930},[231,7137,7138],{"class":237},"        ULONG Flags,\n",[231,7140,7141],{"class":233,"line":936},[231,7142,7143],{"class":237},"        PUSER_ALL_INFORMATION UserAll,\n",[231,7145,7146],{"class":233,"line":942},[231,7147,7148],{"class":237},"        PULONG WhichFields,\n",[231,7150,7151],{"class":233,"line":948},[231,7152,7153],{"class":237},"        PULONG UserFlags,\n",[231,7155,7156],{"class":233,"line":954},[231,7157,7158],{"class":237},"        PBOOLEAN Authoritative,\n",[231,7160,7161],{"class":233,"line":964},[231,7162,7163],{"class":237},"        PLARGE_INTEGER LogoffTime,\n",[231,7165,7166],{"class":233,"line":975},[231,7167,7168],{"class":237},"        PLARGE_INTEGER KickoffTime);\n",[231,7170,7171],{"class":233,"line":985},[231,7172,1161],{"class":237},[15,7174,7175,7176,7179],{},"The header used to be called ",[28,7177,7178],{},"subauth.h",". That's also the name of the SDK header it includes. If the compiler has the project folder on its include path, it finds our file instead of the system one, and you get a very confusing recursive include. Under MinGW that cost me a while. Now it has its own name.",[15,7181,7182,561],{},[28,7183,427],{},[222,7185,7187],{"className":564,"code":7186,"language":566,"meta":227,"style":227},"#include \"subauth_filter.h\"\n#include \"store.h\"\n#include \u003Ccstdio>\n\n#pragma comment(lib, \"advapi32.lib\")\n\n#ifndef STATUS_SUCCESS\n#define STATUS_SUCCESS             ((NTSTATUS)0x00000000L)\n#endif\n#ifndef STATUS_ACCOUNT_RESTRICTION\n#define STATUS_ACCOUNT_RESTRICTION ((NTSTATUS)0xC000006EL)\n#endif\n\n\u002F\u002F Everything in this file runs inside lsass.exe, on MSV1_0's logon path. It is\n\u002F\u002F written to allocate nothing on the heap: fixed stack buffers, no std::wstring.\n\u002F\u002F No allocation means no std::bad_alloc, and the export still has a catch-all\n\u002F\u002F in case a later edit adds one. A C++ exception that reaches LSA would take\n\u002F\u002F lsass, and with it the whole machine, down.\n\nnamespace\n{\n    \u002F\u002F The SAM account name, for the log only. Bounded like any other input.\n    void SamName(const USER_ALL_INFORMATION* ua, WCHAR (&out)[129])\n    {\n        out[0] = L'?';\n        out[1] = L'\\0';\n        const UNICODE_STRING& us = ua->UserName;\n        if (!us.Buffer || us.Length == 0 || (us.Length % sizeof(WCHAR)) != 0)\n            return;\n        size_t cch = us.Length \u002F sizeof(WCHAR);\n        if (cch > 128)\n            cch = 128;\n        memcpy(out, us.Buffer, cch * sizeof(WCHAR));\n        out[cch] = L'\\0';\n    }\n\n    \u002F\u002F Debug output for every decision; the event log only for refusals. The\n    \u002F\u002F filter sees every MSV1_0 logon it is called for, so logging approvals to\n    \u002F\u002F the Application log would flood it from inside lsass.\n    void Report(bool refused, ULONG rid, const WCHAR* name, bool isNetwork)\n    {\n        WCHAR text[256];\n        if (refused)\n            swprintf_s(text, L\"Refused a network logon for enrolled account '%s' (RID %lu). \"\n                             L\"No second factor on this path.\", name, rid);\n        else\n            swprintf_s(text, L\"No objection to a %s logon for '%s' (RID %lu).\",\n                       isNetwork ? L\"network\" : L\"non-network\", name, rid);\n\n        WCHAR debug[288];\n        swprintf_s(debug, L\"[Doppio-SubAuth] %s\\n\", text);\n        OutputDebugStringW(debug);\n\n        if (!refused)\n            return;\n        HANDLE h = RegisterEventSourceW(nullptr, L\"TheAdminCafe 2FA\");\n        if (h)\n        {\n            LPCWSTR s[1] = { text };\n            ReportEventW(h, EVENTLOG_WARNING_TYPE, 0, 210, nullptr, 1, 0, s, nullptr);\n            DeregisterEventSource(h);\n        }\n    }\n}\n\n\u002F\u002F MSV1_0 calls this AFTER it has validated the logon. We only add a veto;\n\u002F\u002F password check, account restrictions and token stay with MSV1_0.\n\u002F\u002F\n\u002F\u002F The output parameters are left as MSV1_0 passed them in, except WhichFields\n\u002F\u002F (nothing to write back to the SAM) and Authoritative on a refusal. In\n\u002F\u002F particular LogoffTime and KickoffTime are NOT overwritten: they carry the\n\u002F\u002F account's logon hours, and a filter has no business extending them.\n\u002F\u002F\n\u002F\u002F IMPORTANT: we read ONLY the RID and the account name. We never touch the\n\u002F\u002F password hashes in UserAll or the challenge-response in LogonInformation.\n\u002F\u002F Reading and keeping that credential material is exactly the theft pattern\n\u002F\u002F this project refuses to build - the decision never needs it.\nNTSTATUS NTAPI Msv1_0SubAuthenticationFilter(\n    NETLOGON_LOGON_INFO_CLASS LogonLevel,\n    PVOID \u002F*LogonInformation*\u002F,\n    ULONG \u002F*Flags*\u002F,\n    PUSER_ALL_INFORMATION UserAll,\n    PULONG WhichFields,\n    PULONG \u002F*UserFlags*\u002F,\n    PBOOLEAN Authoritative,\n    PLARGE_INTEGER \u002F*LogoffTime*\u002F,\n    PLARGE_INTEGER \u002F*KickoffTime*\u002F)\n{\n    if (WhichFields)\n        *WhichFields = 0;\n\n    const bool isNetwork = (LogonLevel == NetlogonNetworkInformation ||\n                            LogonLevel == NetlogonNetworkTransitiveInformation);\n\n    try\n    {\n        \u002F\u002F No SAM record, no decision: MSV1_0 has already validated the logon,\n        \u002F\u002F so \"no objection\" does not let anything through that MSV1_0 refused.\n        if (!UserAll)\n            return STATUS_SUCCESS;\n\n        const ULONG rid = UserAll->UserId;\n\n        \u002F\u002F A single registry read - no DPAPI, no LSA name\u002FSID lookup - so\n        \u002F\u002F nothing here re-enters LSA from inside MSV1_0.\n        const bool refuse = isNetwork && tac::IsEnrolledByRid(rid);\n\n        WCHAR name[129];\n        SamName(UserAll, name);\n        Report(refuse, rid, name, isNetwork);\n\n        if (refuse)\n        {\n            if (Authoritative)\n                *Authoritative = TRUE;          \u002F\u002F do not retry elsewhere\n            return STATUS_ACCOUNT_RESTRICTION;\n        }\n        return STATUS_SUCCESS;\n    }\n    catch (...)\n    {\n        \u002F\u002F Should be unreachable (nothing above allocates). If it ever happens:\n        \u002F\u002F fail closed for the path this filter exists for, and stay out of the\n        \u002F\u002F way of everything else.\n        if (isNetwork)\n        {\n            if (Authoritative)\n                *Authoritative = TRUE;\n            return STATUS_ACCOUNT_RESTRICTION;\n        }\n        return STATUS_SUCCESS;\n    }\n}\n\nBOOL APIENTRY DllMain(HMODULE h, DWORD reason, LPVOID)\n{\n    if (reason == DLL_PROCESS_ATTACH)\n        DisableThreadLibraryCalls(h);\n    return TRUE;\n}\n",[28,7188,7189,7196,7202,7209,7213,7229,7233,7239,7256,7260,7266,7282,7286,7290,7295,7300,7305,7310,7315,7319,7323,7327,7332,7369,7373,7389,7410,7426,7460,7466,7484,7498,7509,7523,7538,7542,7546,7551,7556,7561,7601,7605,7616,7623,7645,7653,7658,7683,7702,7706,7718,7736,7743,7747,7758,7764,7784,7790,7794,7807,7839,7845,7849,7853,7857,7861,7866,7871,7875,7880,7885,7890,7895,7899,7904,7909,7914,7919,7929,7934,7943,7953,7958,7963,7973,7978,7988,7997,8001,8008,8022,8026,8048,8058,8062,8066,8070,8075,8080,8091,8097,8101,8115,8119,8124,8129,8159,8163,8174,8182,8190,8194,8201,8205,8212,8228,8234,8238,8244,8248,8254,8258,8263,8268,8273,8280,8284,8290,8301,8307,8311,8317,8321,8325,8329,8343,8347,8359,8366,8372],{"__ignoreMap":227},[231,7190,7191,7193],{"class":233,"line":234},[231,7192,770],{"class":573},[231,7194,7195],{"class":773}," \"subauth_filter.h\"\n",[231,7197,7198,7200],{"class":233,"line":581},[231,7199,770],{"class":573},[231,7201,3482],{"class":773},[231,7203,7204,7206],{"class":233,"line":588},[231,7205,770],{"class":573},[231,7207,7208],{"class":773}," \u003Ccstdio>\n",[231,7210,7211],{"class":233,"line":594},[231,7212,755],{"emptyLinePlaceholder":754},[231,7214,7215,7217,7219,7221,7223,7225,7227],{"class":233,"line":599},[231,7216,574],{"class":573},[231,7218,3507],{"class":577},[231,7220,1444],{"class":237},[231,7222,3512],{"class":577},[231,7224,424],{"class":237},[231,7226,3517],{"class":773},[231,7228,1717],{"class":237},[231,7230,7231],{"class":233,"line":605},[231,7232,755],{"emptyLinePlaceholder":754},[231,7234,7235,7237],{"class":233,"line":611},[231,7236,3538],{"class":573},[231,7238,3541],{"class":577},[231,7240,7241,7243,7245,7248,7250,7252,7254],{"class":233,"line":617},[231,7242,761],{"class":573},[231,7244,3548],{"class":577},[231,7246,7247],{"class":237},"             ((NTSTATUS)",[231,7249,3554],{"class":573},[231,7251,3557],{"class":240},[231,7253,3560],{"class":573},[231,7255,1717],{"class":237},[231,7257,7258],{"class":233,"line":623},[231,7259,3567],{"class":573},[231,7261,7262,7264],{"class":233,"line":629},[231,7263,3538],{"class":573},[231,7265,3574],{"class":577},[231,7267,7268,7270,7272,7274,7276,7278,7280],{"class":233,"line":634},[231,7269,761],{"class":573},[231,7271,3581],{"class":577},[231,7273,3584],{"class":237},[231,7275,3554],{"class":573},[231,7277,3589],{"class":240},[231,7279,3560],{"class":573},[231,7281,1717],{"class":237},[231,7283,7284],{"class":233,"line":640},[231,7285,3567],{"class":573},[231,7287,7288],{"class":233,"line":646},[231,7289,755],{"emptyLinePlaceholder":754},[231,7291,7292],{"class":233,"line":652},[231,7293,7294],{"class":584},"\u002F\u002F Everything in this file runs inside lsass.exe, on MSV1_0's logon path. It is\n",[231,7296,7297],{"class":233,"line":657},[231,7298,7299],{"class":584},"\u002F\u002F written to allocate nothing on the heap: fixed stack buffers, no std::wstring.\n",[231,7301,7302],{"class":233,"line":663},[231,7303,7304],{"class":584},"\u002F\u002F No allocation means no std::bad_alloc, and the export still has a catch-all\n",[231,7306,7307],{"class":233,"line":669},[231,7308,7309],{"class":584},"\u002F\u002F in case a later edit adds one. A C++ exception that reaches LSA would take\n",[231,7311,7312],{"class":233,"line":675},[231,7313,7314],{"class":584},"\u002F\u002F lsass, and with it the whole machine, down.\n",[231,7316,7317],{"class":233,"line":681},[231,7318,755],{"emptyLinePlaceholder":754},[231,7320,7321],{"class":233,"line":687},[231,7322,3774],{"class":573},[231,7324,7325],{"class":233,"line":693},[231,7326,818],{"class":237},[231,7328,7329],{"class":233,"line":699},[231,7330,7331],{"class":584},"    \u002F\u002F The SAM account name, for the log only. Bounded like any other input.\n",[231,7333,7334,7336,7339,7341,7343,7346,7348,7351,7353,7356,7358,7360,7363,7366],{"class":233,"line":704},[231,7335,3829],{"class":573},[231,7337,7338],{"class":577}," SamName",[231,7340,1444],{"class":237},[231,7342,1447],{"class":573},[231,7344,7345],{"class":577}," USER_ALL_INFORMATION",[231,7347,866],{"class":573},[231,7349,7350],{"class":1462}," ua",[231,7352,424],{"class":237},[231,7354,7355],{"class":577},"WCHAR",[231,7357,1742],{"class":237},[231,7359,1459],{"class":573},[231,7361,7362],{"class":237},"out)[",[231,7364,7365],{"class":240},"129",[231,7367,7368],{"class":237},"])\n",[231,7370,7371],{"class":233,"line":710},[231,7372,1722],{"class":237},[231,7374,7375,7378,7380,7382,7384,7387],{"class":233,"line":716},[231,7376,7377],{"class":237},"        out[",[231,7379,2411],{"class":240},[231,7381,2190],{"class":237},[231,7383,1630],{"class":573},[231,7385,7386],{"class":773}," L'?'",[231,7388,1652],{"class":237},[231,7390,7391,7393,7395,7397,7399,7402,7405,7408],{"class":233,"line":722},[231,7392,7377],{"class":237},[231,7394,3954],{"class":240},[231,7396,2190],{"class":237},[231,7398,1630],{"class":573},[231,7400,7401],{"class":773}," L'",[231,7403,7404],{"class":240},"\\0",[231,7406,7407],{"class":773},"'",[231,7409,1652],{"class":237},[231,7411,7412,7414,7416,7418,7421,7423],{"class":233,"line":728},[231,7413,4492],{"class":573},[231,7415,4380],{"class":577},[231,7417,1459],{"class":573},[231,7419,7420],{"class":237}," us ",[231,7422,1630],{"class":573},[231,7424,7425],{"class":237}," ua->UserName;\n",[231,7427,7428,7430,7432,7434,7436,7438,7440,7442,7444,7446,7448,7450,7452,7454,7456,7458],{"class":233,"line":734},[231,7429,1739],{"class":573},[231,7431,1742],{"class":237},[231,7433,1745],{"class":573},[231,7435,4439],{"class":237},[231,7437,1806],{"class":573},[231,7439,4444],{"class":237},[231,7441,1794],{"class":573},[231,7443,1732],{"class":240},[231,7445,1937],{"class":573},[231,7447,4453],{"class":237},[231,7449,4456],{"class":573},[231,7451,4459],{"class":573},[231,7453,4462],{"class":237},[231,7455,2422],{"class":573},[231,7457,1732],{"class":240},[231,7459,1717],{"class":237},[231,7461,7462,7464],{"class":233,"line":739},[231,7463,1756],{"class":573},[231,7465,1652],{"class":237},[231,7467,7468,7470,7473,7475,7477,7479,7481],{"class":233,"line":745},[231,7469,1766],{"class":573},[231,7471,7472],{"class":237}," cch ",[231,7474,1630],{"class":573},[231,7476,4444],{"class":237},[231,7478,4686],{"class":573},[231,7480,4459],{"class":573},[231,7482,7483],{"class":237},"(WCHAR);\n",[231,7485,7486,7488,7491,7493,7496],{"class":233,"line":751},[231,7487,1739],{"class":573},[231,7489,7490],{"class":237}," (cch ",[231,7492,1943],{"class":573},[231,7494,7495],{"class":240}," 128",[231,7497,1717],{"class":237},[231,7499,7500,7503,7505,7507],{"class":233,"line":758},[231,7501,7502],{"class":237},"            cch ",[231,7504,1630],{"class":573},[231,7506,7495],{"class":240},[231,7508,1652],{"class":237},[231,7510,7511,7514,7517,7519,7521],{"class":233,"line":767},[231,7512,7513],{"class":577},"        memcpy",[231,7515,7516],{"class":237},"(out, us.Buffer, cch ",[231,7518,866],{"class":573},[231,7520,4459],{"class":573},[231,7522,4691],{"class":237},[231,7524,7525,7528,7530,7532,7534,7536],{"class":233,"line":777},[231,7526,7527],{"class":237},"        out[cch] ",[231,7529,1630],{"class":573},[231,7531,7401],{"class":773},[231,7533,7404],{"class":240},[231,7535,7407],{"class":773},[231,7537,1652],{"class":237},[231,7539,7540],{"class":233,"line":785},[231,7541,2057],{"class":237},[231,7543,7544],{"class":233,"line":793},[231,7545,755],{"emptyLinePlaceholder":754},[231,7547,7548],{"class":233,"line":801},[231,7549,7550],{"class":584},"    \u002F\u002F Debug output for every decision; the event log only for refusals. The\n",[231,7552,7553],{"class":233,"line":806},[231,7554,7555],{"class":584},"    \u002F\u002F filter sees every MSV1_0 logon it is called for, so logging approvals to\n",[231,7557,7558],{"class":233,"line":815},[231,7559,7560],{"class":584},"    \u002F\u002F the Application log would flood it from inside lsass.\n",[231,7562,7563,7565,7568,7570,7573,7576,7578,7580,7582,7584,7586,7588,7590,7592,7594,7596,7599],{"class":233,"line":821},[231,7564,3829],{"class":573},[231,7566,7567],{"class":577}," Report",[231,7569,1444],{"class":237},[231,7571,7572],{"class":573},"bool",[231,7574,7575],{"class":1462}," refused",[231,7577,424],{"class":237},[231,7579,4793],{"class":577},[231,7581,1473],{"class":1462},[231,7583,424],{"class":237},[231,7585,1447],{"class":573},[231,7587,4635],{"class":577},[231,7589,866],{"class":573},[231,7591,2110],{"class":1462},[231,7593,424],{"class":237},[231,7595,7572],{"class":573},[231,7597,7598],{"class":1462}," isNetwork",[231,7600,1717],{"class":237},[231,7602,7603],{"class":233,"line":836},[231,7604,1722],{"class":237},[231,7606,7607,7609,7612,7614],{"class":233,"line":842},[231,7608,2442],{"class":577},[231,7610,7611],{"class":237}," text[",[231,7613,2187],{"class":240},[231,7615,2451],{"class":237},[231,7617,7618,7620],{"class":233,"line":848},[231,7619,1739],{"class":573},[231,7621,7622],{"class":237}," (refused)\n",[231,7624,7625,7628,7631,7634,7637,7640,7642],{"class":233,"line":854},[231,7626,7627],{"class":577},"            swprintf_s",[231,7629,7630],{"class":237},"(text, ",[231,7632,7633],{"class":773},"L\"Refused a network logon for enrolled account '",[231,7635,7636],{"class":240},"%s",[231,7638,7639],{"class":773},"' (RID ",[231,7641,2465],{"class":240},[231,7643,7644],{"class":773},"). \"\n",[231,7646,7647,7650],{"class":233,"line":860},[231,7648,7649],{"class":773},"                             L\"No second factor on this path.\"",[231,7651,7652],{"class":237},", name, rid);\n",[231,7654,7655],{"class":233,"line":872},[231,7656,7657],{"class":573},"        else\n",[231,7659,7660,7662,7664,7667,7669,7672,7674,7676,7678,7681],{"class":233,"line":877},[231,7661,7627],{"class":577},[231,7663,7630],{"class":237},[231,7665,7666],{"class":773},"L\"No objection to a ",[231,7668,7636],{"class":240},[231,7670,7671],{"class":773}," logon for '",[231,7673,7636],{"class":240},[231,7675,7639],{"class":773},[231,7677,2465],{"class":240},[231,7679,7680],{"class":773},").\"",[231,7682,4388],{"class":237},[231,7684,7685,7688,7691,7694,7697,7700],{"class":233,"line":889},[231,7686,7687],{"class":237},"                       isNetwork ",[231,7689,7690],{"class":573},"?",[231,7692,7693],{"class":773}," L\"network\"",[231,7695,7696],{"class":573}," :",[231,7698,7699],{"class":773}," L\"non-network\"",[231,7701,7652],{"class":237},[231,7703,7704],{"class":233,"line":895},[231,7705,755],{"emptyLinePlaceholder":754},[231,7707,7708,7710,7713,7716],{"class":233,"line":901},[231,7709,2442],{"class":577},[231,7711,7712],{"class":237}," debug[",[231,7714,7715],{"class":240},"288",[231,7717,2451],{"class":237},[231,7719,7720,7722,7725,7728,7731,7733],{"class":233,"line":907},[231,7721,2456],{"class":577},[231,7723,7724],{"class":237},"(debug, ",[231,7726,7727],{"class":773},"L\"[Doppio-SubAuth] ",[231,7729,7730],{"class":240},"%s\\n",[231,7732,2468],{"class":773},[231,7734,7735],{"class":237},", text);\n",[231,7737,7738,7740],{"class":233,"line":913},[231,7739,3873],{"class":577},[231,7741,7742],{"class":237},"(debug);\n",[231,7744,7745],{"class":233,"line":919},[231,7746,755],{"emptyLinePlaceholder":754},[231,7748,7749,7751,7753,7755],{"class":233,"line":930},[231,7750,1739],{"class":573},[231,7752,1742],{"class":237},[231,7754,1745],{"class":573},[231,7756,7757],{"class":237},"refused)\n",[231,7759,7760,7762],{"class":233,"line":936},[231,7761,1756],{"class":573},[231,7763,1652],{"class":237},[231,7765,7766,7768,7770,7772,7774,7776,7778,7780,7782],{"class":233,"line":942},[231,7767,3916],{"class":577},[231,7769,3919],{"class":237},[231,7771,1630],{"class":573},[231,7773,3924],{"class":577},[231,7775,1444],{"class":237},[231,7777,2250],{"class":240},[231,7779,424],{"class":237},[231,7781,3933],{"class":773},[231,7783,1476],{"class":237},[231,7785,7786,7788],{"class":233,"line":948},[231,7787,1739],{"class":573},[231,7789,3942],{"class":237},[231,7791,7792],{"class":233,"line":954},[231,7793,1921],{"class":237},[231,7795,7796,7798,7800,7802,7804],{"class":233,"line":964},[231,7797,3951],{"class":237},[231,7799,3954],{"class":240},[231,7801,2190],{"class":237},[231,7803,1630],{"class":573},[231,7805,7806],{"class":237}," { text };\n",[231,7808,7809,7811,7814,7816,7818,7821,7823,7825,7827,7829,7831,7833,7835,7837],{"class":233,"line":975},[231,7810,3971],{"class":577},[231,7812,7813],{"class":237},"(h, EVENTLOG_WARNING_TYPE, ",[231,7815,2411],{"class":240},[231,7817,424],{"class":237},[231,7819,7820],{"class":240},"210",[231,7822,424],{"class":237},[231,7824,2250],{"class":240},[231,7826,424],{"class":237},[231,7828,3954],{"class":240},[231,7830,424],{"class":237},[231,7832,2411],{"class":240},[231,7834,3992],{"class":237},[231,7836,2250],{"class":240},[231,7838,1476],{"class":237},[231,7840,7841,7843],{"class":233,"line":985},[231,7842,4001],{"class":577},[231,7844,4004],{"class":237},[231,7846,7847],{"class":233,"line":995},[231,7848,1990],{"class":237},[231,7850,7851],{"class":233,"line":1001},[231,7852,2057],{"class":237},[231,7854,7855],{"class":233,"line":1012},[231,7856,1161],{"class":237},[231,7858,7859],{"class":233,"line":1017},[231,7860,755],{"emptyLinePlaceholder":754},[231,7862,7863],{"class":233,"line":1029},[231,7864,7865],{"class":584},"\u002F\u002F MSV1_0 calls this AFTER it has validated the logon. We only add a veto;\n",[231,7867,7868],{"class":233,"line":1035},[231,7869,7870],{"class":584},"\u002F\u002F password check, account restrictions and token stay with MSV1_0.\n",[231,7872,7873],{"class":233,"line":1041},[231,7874,585],{"class":584},[231,7876,7877],{"class":233,"line":1051},[231,7878,7879],{"class":584},"\u002F\u002F The output parameters are left as MSV1_0 passed them in, except WhichFields\n",[231,7881,7882],{"class":233,"line":1057},[231,7883,7884],{"class":584},"\u002F\u002F (nothing to write back to the SAM) and Authoritative on a refusal. In\n",[231,7886,7887],{"class":233,"line":1062},[231,7888,7889],{"class":584},"\u002F\u002F particular LogoffTime and KickoffTime are NOT overwritten: they carry the\n",[231,7891,7892],{"class":233,"line":1074},[231,7893,7894],{"class":584},"\u002F\u002F account's logon hours, and a filter has no business extending them.\n",[231,7896,7897],{"class":233,"line":1079},[231,7898,585],{"class":584},[231,7900,7901],{"class":233,"line":1084},[231,7902,7903],{"class":584},"\u002F\u002F IMPORTANT: we read ONLY the RID and the account name. We never touch the\n",[231,7905,7906],{"class":233,"line":1093},[231,7907,7908],{"class":584},"\u002F\u002F password hashes in UserAll or the challenge-response in LogonInformation.\n",[231,7910,7911],{"class":233,"line":1098},[231,7912,7913],{"class":584},"\u002F\u002F Reading and keeping that credential material is exactly the theft pattern\n",[231,7915,7916],{"class":233,"line":1103},[231,7917,7918],{"class":584},"\u002F\u002F this project refuses to build - the decision never needs it.\n",[231,7920,7921,7923,7925,7927],{"class":233,"line":1115},[231,7922,4991],{"class":237},[231,7924,827],{"class":577},[231,7926,7121],{"class":577},[231,7928,833],{"class":237},[231,7930,7931],{"class":233,"line":1120},[231,7932,7933],{"class":237},"    NETLOGON_LOGON_INFO_CLASS LogonLevel,\n",[231,7935,7936,7938,7941],{"class":233,"line":1125},[231,7937,5841],{"class":237},[231,7939,7940],{"class":584}," \u002F*LogonInformation*\u002F",[231,7942,4388],{"class":237},[231,7944,7945,7948,7951],{"class":233,"line":1134},[231,7946,7947],{"class":237},"    ULONG",[231,7949,7950],{"class":584}," \u002F*Flags*\u002F",[231,7952,4388],{"class":237},[231,7954,7955],{"class":233,"line":1139},[231,7956,7957],{"class":237},"    PUSER_ALL_INFORMATION UserAll,\n",[231,7959,7960],{"class":233,"line":1144},[231,7961,7962],{"class":237},"    PULONG WhichFields,\n",[231,7964,7965,7968,7971],{"class":233,"line":1158},[231,7966,7967],{"class":237},"    PULONG",[231,7969,7970],{"class":584}," \u002F*UserFlags*\u002F",[231,7972,4388],{"class":237},[231,7974,7975],{"class":233,"line":4213},[231,7976,7977],{"class":237},"    PBOOLEAN Authoritative,\n",[231,7979,7980,7983,7986],{"class":233,"line":4228},[231,7981,7982],{"class":237},"    PLARGE_INTEGER",[231,7984,7985],{"class":584}," \u002F*LogoffTime*\u002F",[231,7987,4388],{"class":237},[231,7989,7990,7992,7995],{"class":233,"line":4243},[231,7991,7982],{"class":237},[231,7993,7994],{"class":584}," \u002F*KickoffTime*\u002F",[231,7996,1717],{"class":237},[231,7998,7999],{"class":233,"line":4258},[231,8000,818],{"class":237},[231,8002,8003,8005],{"class":233,"line":4273},[231,8004,5051],{"class":573},[231,8006,8007],{"class":237}," (WhichFields)\n",[231,8009,8010,8013,8016,8018,8020],{"class":233,"line":4288},[231,8011,8012],{"class":573},"        *",[231,8014,8015],{"class":237},"WhichFields ",[231,8017,1630],{"class":573},[231,8019,1732],{"class":240},[231,8021,1652],{"class":237},[231,8023,8024],{"class":233,"line":4303},[231,8025,755],{"emptyLinePlaceholder":754},[231,8027,8028,8030,8032,8035,8037,8040,8042,8045],{"class":233,"line":4318},[231,8029,4140],{"class":573},[231,8031,2078],{"class":573},[231,8033,8034],{"class":237}," isNetwork ",[231,8036,1630],{"class":573},[231,8038,8039],{"class":237}," (LogonLevel ",[231,8041,1794],{"class":573},[231,8043,8044],{"class":237}," NetlogonNetworkInformation ",[231,8046,8047],{"class":573},"||\n",[231,8049,8050,8053,8055],{"class":233,"line":4323},[231,8051,8052],{"class":237},"                            LogonLevel ",[231,8054,1794],{"class":573},[231,8056,8057],{"class":237}," NetlogonNetworkTransitiveInformation);\n",[231,8059,8060],{"class":233,"line":4328},[231,8061,755],{"emptyLinePlaceholder":754},[231,8063,8064],{"class":233,"line":4333},[231,8065,5370],{"class":573},[231,8067,8068],{"class":233,"line":4339},[231,8069,1722],{"class":237},[231,8071,8072],{"class":233,"line":4345},[231,8073,8074],{"class":584},"        \u002F\u002F No SAM record, no decision: MSV1_0 has already validated the logon,\n",[231,8076,8077],{"class":233,"line":4351},[231,8078,8079],{"class":584},"        \u002F\u002F so \"no objection\" does not let anything through that MSV1_0 refused.\n",[231,8081,8082,8084,8086,8088],{"class":233,"line":4357},[231,8083,1739],{"class":573},[231,8085,1742],{"class":237},[231,8087,1745],{"class":573},[231,8089,8090],{"class":237},"UserAll)\n",[231,8092,8093,8095],{"class":233,"line":4363},[231,8094,1756],{"class":573},[231,8096,5452],{"class":237},[231,8098,8099],{"class":233,"line":4391},[231,8100,755],{"emptyLinePlaceholder":754},[231,8102,8103,8105,8108,8110,8112],{"class":233,"line":4414},[231,8104,4492],{"class":573},[231,8106,8107],{"class":577}," ULONG",[231,8109,2987],{"class":237},[231,8111,1630],{"class":573},[231,8113,8114],{"class":237}," UserAll->UserId;\n",[231,8116,8117],{"class":233,"line":4425},[231,8118,755],{"emptyLinePlaceholder":754},[231,8120,8121],{"class":233,"line":4430},[231,8122,8123],{"class":584},"        \u002F\u002F A single registry read - no DPAPI, no LSA name\u002FSID lookup - so\n",[231,8125,8126],{"class":233,"line":4471},[231,8127,8128],{"class":584},"        \u002F\u002F nothing here re-enters LSA from inside MSV1_0.\n",[231,8130,8131,8133,8135,8138,8140,8142,8145,8147,8149,8152,8154,8157],{"class":233,"line":4484},[231,8132,4492],{"class":573},[231,8134,2078],{"class":573},[231,8136,8137],{"class":237}," refuse ",[231,8139,1630],{"class":573},[231,8141,7598],{"class":577},[231,8143,8144],{"class":573}," &&",[231,8146,4963],{"class":577},[231,8148,1453],{"class":237},[231,8150,8151],{"class":577},"IsEnrolledByRid",[231,8153,1444],{"class":237},[231,8155,8156],{"class":577},"rid",[231,8158,1476],{"class":237},[231,8160,8161],{"class":233,"line":4489},[231,8162,755],{"emptyLinePlaceholder":754},[231,8164,8165,8167,8170,8172],{"class":233,"line":4514},[231,8166,2442],{"class":577},[231,8168,8169],{"class":237}," name[",[231,8171,7365],{"class":240},[231,8173,2451],{"class":237},[231,8175,8176,8179],{"class":233,"line":4535},[231,8177,8178],{"class":577},"        SamName",[231,8180,8181],{"class":237},"(UserAll, name);\n",[231,8183,8184,8187],{"class":233,"line":4548},[231,8185,8186],{"class":577},"        Report",[231,8188,8189],{"class":237},"(refuse, rid, name, isNetwork);\n",[231,8191,8192],{"class":233,"line":4561},[231,8193,755],{"emptyLinePlaceholder":754},[231,8195,8196,8198],{"class":233,"line":4566},[231,8197,1739],{"class":573},[231,8199,8200],{"class":237}," (refuse)\n",[231,8202,8203],{"class":233,"line":4586},[231,8204,1921],{"class":237},[231,8206,8207,8209],{"class":233,"line":4612},[231,8208,1926],{"class":573},[231,8210,8211],{"class":237}," (Authoritative)\n",[231,8213,8214,8217,8220,8222,8225],{"class":233,"line":4625},[231,8215,8216],{"class":573},"                *",[231,8218,8219],{"class":237},"Authoritative ",[231,8221,1630],{"class":573},[231,8223,8224],{"class":237}," TRUE;",[231,8226,8227],{"class":584},"          \u002F\u002F do not retry elsewhere\n",[231,8229,8230,8232],{"class":233,"line":4630},[231,8231,1756],{"class":573},[231,8233,5696],{"class":237},[231,8235,8236],{"class":233,"line":4654},[231,8237,1990],{"class":237},[231,8239,8240,8242],{"class":233,"line":4672},[231,8241,2047],{"class":573},[231,8243,5452],{"class":237},[231,8245,8246],{"class":233,"line":4694},[231,8247,2057],{"class":237},[231,8249,8250,8252],{"class":233,"line":4699},[231,8251,5424],{"class":573},[231,8253,5427],{"class":237},[231,8255,8256],{"class":233,"line":4704},[231,8257,1722],{"class":237},[231,8259,8260],{"class":233,"line":4710},[231,8261,8262],{"class":584},"        \u002F\u002F Should be unreachable (nothing above allocates). If it ever happens:\n",[231,8264,8265],{"class":233,"line":4716},[231,8266,8267],{"class":584},"        \u002F\u002F fail closed for the path this filter exists for, and stay out of the\n",[231,8269,8270],{"class":233,"line":4722},[231,8271,8272],{"class":584},"        \u002F\u002F way of everything else.\n",[231,8274,8275,8277],{"class":233,"line":4728},[231,8276,1739],{"class":573},[231,8278,8279],{"class":237}," (isNetwork)\n",[231,8281,8282],{"class":233,"line":4734},[231,8283,1921],{"class":237},[231,8285,8286,8288],{"class":233,"line":4740},[231,8287,1926],{"class":573},[231,8289,8211],{"class":237},[231,8291,8292,8294,8296,8298],{"class":233,"line":4745},[231,8293,8216],{"class":573},[231,8295,8219],{"class":237},[231,8297,1630],{"class":573},[231,8299,8300],{"class":237}," TRUE;\n",[231,8302,8303,8305],{"class":233,"line":4751},[231,8304,1756],{"class":573},[231,8306,5696],{"class":237},[231,8308,8309],{"class":233,"line":4757},[231,8310,1990],{"class":237},[231,8312,8313,8315],{"class":233,"line":4763},[231,8314,2047],{"class":573},[231,8316,5452],{"class":237},[231,8318,8319],{"class":233,"line":4769},[231,8320,2057],{"class":237},[231,8322,8323],{"class":233,"line":4775},[231,8324,1161],{"class":237},[231,8326,8327],{"class":233,"line":4807},[231,8328,755],{"emptyLinePlaceholder":754},[231,8330,8331,8334,8337,8340],{"class":233,"line":4812},[231,8332,8333],{"class":237},"BOOL ",[231,8335,8336],{"class":577},"APIENTRY",[231,8338,8339],{"class":577}," DllMain",[231,8341,8342],{"class":237},"(HMODULE h, DWORD reason, LPVOID)\n",[231,8344,8345],{"class":233,"line":4836},[231,8346,818],{"class":237},[231,8348,8349,8351,8354,8356],{"class":233,"line":4845},[231,8350,5051],{"class":573},[231,8352,8353],{"class":237}," (reason ",[231,8355,1794],{"class":573},[231,8357,8358],{"class":237}," DLL_PROCESS_ATTACH)\n",[231,8360,8361,8364],{"class":233,"line":4850},[231,8362,8363],{"class":577},"        DisableThreadLibraryCalls",[231,8365,4004],{"class":237},[231,8367,8368,8370],{"class":233,"line":4872},[231,8369,5449],{"class":573},[231,8371,8300],{"class":237},[231,8373,8374],{"class":233,"line":4888},[231,8375,1161],{"class":237},[15,8377,8378,561],{},[28,8379,444],{},[222,8381,8384],{"className":8382,"code":8383,"language":517,"meta":227},[515],"LIBRARY   TacSubAuth\nEXPORTS\n    Msv1_0SubAuthenticationFilter\n",[28,8385,8383],{"__ignoreMap":227},[15,8387,6424],{},[111,8389,8390,8413,8419,8436,8442],{},[114,8391,8392,8395,8396,8398,8399,8402,8403,8405,8406,8408,8409,8412],{},[48,8393,8394],{},"No heap."," Everything in the filter uses fixed buffers on the stack. The name is copied into 129 ",[28,8397,7355],{},"s, the log line is built with ",[28,8400,8401],{},"swprintf_s",". No ",[28,8404,6488],{},", so no ",[28,8407,6492],{},". The export still has a ",[28,8410,8411],{},"catch (...)",", in case a later change adds something that throws. Then a network logon is refused and everything else gets no objection. An exception that reaches LSA would take lsass down, and the machine with it.",[114,8414,8415,8418],{},[48,8416,8417],{},"Only refusals go to the event log."," The filter sees every logon MSV1_0 calls it for. An event for every approval, written from inside lsass, would flood the Application log. The debugger line is written for every call.",[114,8420,8421,8424,8425,8428,8429,8432,8433,8435],{},[48,8422,8423],{},"We step over the credentials."," For a network logon ",[28,8426,8427],{},"LogonInformation"," points to the challenge and the response. ",[28,8430,8431],{},"UserAll"," has the password hashes from the SAM, right next to the RID and the name we read. The filter reads neither. It doesn't even give the ",[28,8434,8427],{}," parameter a name. That one restraint is the line between a 2FA control and a password stealer.",[114,8437,8438,8441],{},[48,8439,8440],{},"The name is only for the log."," It is cut to 128 characters, just so a corrupt length can't run past the buffer. The decision is made on the RID alone.",[114,8443,8444,8447,8448,8451],{},[28,8445,8446],{},"DisableThreadLibraryCalls"," in ",[28,8449,8450],{},"DllMain",". Without it every thread lsass creates calls into our DLL for nothing.",[259,8453,8455],{"id":8454},"the-out-parameters","The out-parameters",[15,8457,8458],{},"The pointers MSV1_0 hands in are not decoration. My first version set all of them at the top of the function. The filter now leaves them alone, with two exceptions.",[15,8460,8461,8464],{},[28,8462,8463],{},"WhichFields = 0"," says we are not asking MSV1_0 to write back any of the account's fields. We could update things like the bad-password count. We deliberately don't, because the lockout counter from Part 1 lives with the credential provider and one owner is better than two.",[15,8466,8467,8470],{},[28,8468,8469],{},"Authoritative = TRUE"," is set only when we refuse. It tells MSV1_0 not to look for another opinion.",[15,8472,8473,8476,8477,8480,8481,8484],{},[28,8474,8475],{},"LogoffTime"," and ",[28,8478,8479],{},"KickoffTime"," are not touched anymore. They contain the logon hours of the account, as MSV1_0 calculated them. My first version set both to \"never\". That would have quietly extended every session past the logon hours the admin set. A veto has no business doing that. ",[28,8482,8483],{},"UserFlags"," also stays as MSV1_0 set it.",[259,8486,8488],{"id":8487},"which-logons-it-sees","Which logons it sees",[15,8490,8491,8492,8495],{},"The routine from the first version had a limit that I described myself: the logon selects it. The ",[28,8493,8494],{},"ParameterControl"," field of the logon information carries the package number in its upper bits, and a normal SMB logon doesn't set them. So the routine never saw the logons it should gate. And the ones that did select it were approved without a password. Two good reasons why it was the wrong hook.",[15,8497,8498],{},"The filter is not selected by anybody. MSV1_0 calls it for the logons it handles, after its own checks. Two things follow from that:",[111,8500,8501,8507],{},[114,8502,8503,8506],{},[48,8504,8505],{},"RDP with NLA."," Network Level Authentication checks the password with a network logon (NTLM for a local account) before the session starts. For an enrolled account the filter refuses that, exactly like the deny right for network logons. So for RDP you either turn NLA off, then the session goes straight to the 2FA tile from Part 1, or you use the console.",[114,8508,8509,8512],{},[48,8510,8511],{},"Batch and service."," Those don't arrive as network logons, so the filter has no objection. That's what the user rights are for.",[15,8514,8515],{},"That's why the two halves of this article belong together. The filter is where the decision lives, and it's the part worth understanding and extending. The \"Deny log on ...\" user rights are where the enforcement lives. LSA checks them no matter which package a logon asks for, and they don't depend on MSV1_0 calling anything. Build the filter to learn the layer and to have a place for real logic. And keep the deny rights on.",[259,8517,8519],{"id":8518},"what-i-could-actually-test","What I could actually test",[15,8521,8522,8523,8525,8526,8529],{},"For the first version I loaded ",[28,8524,86],{},", built ",[28,8527,8528],{},"NETLOGON_*"," structures by hand and called the routine directly. All four results matched what I expected, including this one:",[271,8531,8532,8542],{},[274,8533,8534],{},[277,8535,8536,8539],{},[280,8537,8538],{},"Input",[280,8540,8541],{},"Result",[290,8543,8544],{},[277,8545,8546,8549],{},[295,8547,8548],{},"non-enrolled account, network logon",[295,8550,8551,8553],{},[28,8552,6619],{}," (defer)",[15,8555,8556,8557,8559,8560,8563],{},"Looking back, that row was the bug. From a routine that is responsible for the password check, ",[28,8558,6619],{}," means \"authenticated\". The test passed because it tested what I ",[72,8561,8562],{},"thought"," the return value means, not what MSV1_0 does with it. A test of your own logic can't tell you that you got the contract wrong.",[15,8565,8566,8567,8569],{},"For the filter I can say this much: it compiles for x64, it exports exactly ",[28,8568,78],{},", and the compiled code doesn't allocate anything on the heap. The important part I can't stand behind yet: that MSV1_0 calls it on your build of Windows, for local accounts, that it survives Credential Guard and that a reboot comes back up. That is yours to check under a kernel debugger, in a VM you can roll back.",[10,8571,8573],{"id":8572},"build","Build",[15,8575,8576,8577,8580,8581,8584,8585,8588],{},"Both LSA pieces are plain C++ with nothing beyond the Windows SDK behind them, and they build the same way as Part 1. You need the ",[48,8578,8579],{},"\"x64 Native Tools Command Prompt\""," from Visual Studio or the Build Tools. A normal ",[28,8582,8583],{},"cmd"," or PowerShell does not work, because ",[28,8586,8587],{},"cl.exe"," and the SDK headers are not on the PATH there.",[259,8590,8592],{"id":8591},"option-1-buildbat","Option 1: build.bat",[222,8594,8598],{"className":8595,"code":8596,"language":8597,"meta":227,"style":227},"language-bat shiki shiki-themes github-dark","build.bat\n","bat",[28,8599,8600],{"__ignoreMap":227},[231,8601,8602],{"class":233,"line":234},[231,8603,8596],{"class":237},[15,8605,8606,8607,8476,8610,8613,8614,8476,8617,8619],{},"That gives you all four binaries: ",[28,8608,8609],{},"TacProvider.dll",[28,8611,8612],{},"enroll.exe"," from Part 1, plus ",[28,8615,8616],{},"TacAuthPackage.dll",[28,8618,86],{}," from this one.",[15,8621,8622,8623,8626,8627,8630],{},"Everything is built with ",[28,8624,8625],{},"\u002FMT",", so the C++ runtime is linked statically and the target VM needs no redistributable. In Part 1 that was convenience. Here it matters more: a DLL that LSA loads at boot and that then cannot find its runtime is a DLL that stops the machine from booting. And everything is built with ",[28,8628,8629],{},"\u002FO2 \u002Fguard:cf \u002FW4 \u002Fsdl",", so Control Flow Guard is on in both DLLs that lsass loads.",[259,8632,8634],{"id":8633},"option-2-build-lsabat","Option 2: build-lsa.bat",[15,8636,8637],{},"While you iterate on the LSA code you do not want to rebuild the credential provider every pass:",[222,8639,8641],{"className":8595,"code":8640,"language":8597,"meta":227,"style":227},"build-lsa.bat\n",[28,8642,8643],{"__ignoreMap":227},[231,8644,8645],{"class":233,"line":234},[231,8646,8640],{"class":237},[15,8648,8649],{},"Same flags, just the two LSA DLLs:",[222,8651,8653],{"className":8595,"code":8652,"language":8597,"meta":227,"style":227},"cl \u002Fnologo \u002FLD \u002FMT \u002FO2 \u002Fguard:cf \u002FEHsc \u002Fstd:c++17 \u002FW4 \u002Fsdl \u002FDUNICODE \u002FD_UNICODE ^\n   ap.cpp store.cpp totp.cpp ^\n   \u002FFe:TacAuthPackage.dll ^\n   \u002Flink \u002Fguard:cf \u002FDEF:TacAuthPackage.def ^\n   advapi32.lib crypt32.lib bcrypt.lib\n\ncl \u002Fnologo \u002FLD \u002FMT \u002FO2 \u002Fguard:cf \u002FEHsc \u002Fstd:c++17 \u002FW4 \u002Fsdl \u002FDUNICODE \u002FD_UNICODE ^\n   subauth.cpp store.cpp totp.cpp ^\n   \u002FFe:TacSubAuth.dll ^\n   \u002Flink \u002Fguard:cf \u002FDEF:TacSubAuth.def ^\n   advapi32.lib crypt32.lib bcrypt.lib\n",[28,8654,8655,8663,8670,8677,8684,8689,8693,8699,8706,8713,8720],{"__ignoreMap":227},[231,8656,8657,8660],{"class":233,"line":234},[231,8658,8659],{"class":237},"cl \u002Fnologo \u002FLD \u002FMT \u002FO2 \u002Fguard:cf \u002FEHsc \u002Fstd:c++17 \u002FW4 \u002Fsdl \u002FDUNICODE \u002FD_UNICODE ",[231,8661,8662],{"class":240},"^\n",[231,8664,8665,8668],{"class":233,"line":581},[231,8666,8667],{"class":237},"   ap.cpp store.cpp totp.cpp ",[231,8669,8662],{"class":240},[231,8671,8672,8675],{"class":233,"line":588},[231,8673,8674],{"class":237},"   \u002FFe:TacAuthPackage.dll ",[231,8676,8662],{"class":240},[231,8678,8679,8682],{"class":233,"line":594},[231,8680,8681],{"class":237},"   \u002Flink \u002Fguard:cf \u002FDEF:TacAuthPackage.def ",[231,8683,8662],{"class":240},[231,8685,8686],{"class":233,"line":599},[231,8687,8688],{"class":237},"   advapi32.lib crypt32.lib bcrypt.lib\n",[231,8690,8691],{"class":233,"line":605},[231,8692,755],{"emptyLinePlaceholder":754},[231,8694,8695,8697],{"class":233,"line":611},[231,8696,8659],{"class":237},[231,8698,8662],{"class":240},[231,8700,8701,8704],{"class":233,"line":617},[231,8702,8703],{"class":237},"   subauth.cpp store.cpp totp.cpp ",[231,8705,8662],{"class":240},[231,8707,8708,8711],{"class":233,"line":623},[231,8709,8710],{"class":237},"   \u002FFe:TacSubAuth.dll ",[231,8712,8662],{"class":240},[231,8714,8715,8718],{"class":233,"line":629},[231,8716,8717],{"class":237},"   \u002Flink \u002Fguard:cf \u002FDEF:TacSubAuth.def ",[231,8719,8662],{"class":240},[231,8721,8722],{"class":233,"line":634},[231,8723,8688],{"class":237},[15,8725,8726,8727,8730],{},"If you have not done it yet, run ",[28,8728,8729],{},"new-guids.ps1"," once before any of this goes anywhere, so you are not using this repository's CLSIDs. That is a Part 1 concern but easy to forget when you come back.",[10,8732,8734],{"id":8733},"install","Install",[15,8736,8737,8738,8741],{},"This is the part I would have found most confusing reading it back, so let me be blunt. The two ways from the chapter above turn into ",[48,8739,8740],{},"three independent things you can install, and they are not three steps."," You do not install \"the LSA half\". You pick one, and two of the three only ever inside a VM you can throw away.",[15,8743,8744,8745,8748,8749,8752,8753,8755,8756,8758,8759,8761],{},"There is also no ",[28,8746,8747],{},".reg"," file for any of it, unlike Part 1. That is on purpose. The authentication package has to be ",[72,8750,8751],{},"appended"," to a value that already has entries in it, and a ",[28,8754,8747],{}," import replaces. Getting that wrong on ",[28,8757,6724],{}," is exactly how you end up with a machine where nobody can authenticate at all. And ",[28,8760,433],{}," holds exactly one DLL, which may already belong to somebody else. So they are PowerShell scripts that read the current values, check they look sane, and only then add ours.",[259,8763,8765],{"id":8764},"_1-snapshot","1. Snapshot",[15,8767,8768],{},"Before anything else. And again before every reboot that follows, not just this one.",[259,8770,8772],{"id":8771},"_2-check-runasppl","2. Check RunAsPPL",[15,8774,8775,8776,8778,8779,8782],{},"This one will cost you an evening if you skip it. If ",[28,8777,148],{}," is on, lsass runs as a Protected Process Light and ",[48,8780,8781],{},"will not load an unsigned DLL into itself",". Your package does not crash, does not error, does not log a thing. It simply is not there. You reboot, nothing happens, and then you debug code that was never given the chance to run.",[222,8784,8786],{"className":224,"code":8785,"language":226,"meta":227,"style":227},"reg query \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\" \u002Fv RunAsPPL\n",[28,8787,8788],{"__ignoreMap":227},[231,8789,8790,8793,8796,8799],{"class":233,"line":234},[231,8791,8792],{"class":237},"reg query ",[231,8794,8795],{"class":773},"\"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\"",[231,8797,8798],{"class":573}," \u002F",[231,8800,8801],{"class":237},"v RunAsPPL\n",[15,8803,8804,8805,91,8808,8811,8812,8814],{},"If that comes back ",[28,8806,8807],{},"0x1",[28,8809,8810],{},"0x2",", set it to ",[28,8813,2411],{}," for the test and put it back when you are done. The install scripts check this too now and refuse to run while it is on. And notice what that step actually is. You just turned off a real hardening feature in order to load an unsigned module into lsass. On a throwaway VM that is fine. Anywhere else it is the trade you must never make.",[259,8816,8818],{"id":8817},"_3-the-user-rights-if-you-only-want-the-hardening","3. The user rights, if you only want the hardening",[222,8820,8822],{"className":224,"code":8821,"language":226,"meta":227,"style":227},".\\deny-noninteractive.ps1 alice\n.\\deny-noninteractive.ps1 alice -KeepNetwork   # keeps RDP with NLA, see below\n.\\deny-noninteractive.ps1 alice -Remove        # undo\n",[28,8823,8824,8832,8849],{"__ignoreMap":227},[231,8825,8826,8828,8830],{"class":233,"line":234},[231,8827,105],{"class":237},[231,8829,241],{"class":240},[231,8831,244],{"class":237},[231,8833,8834,8836,8838,8841,8843,8846],{"class":233,"line":581},[231,8835,105],{"class":237},[231,8837,241],{"class":240},[231,8839,8840],{"class":237}," alice ",[231,8842,1836],{"class":573},[231,8844,8845],{"class":237},"KeepNetwork   ",[231,8847,8848],{"class":584},"# keeps RDP with NLA, see below\n",[231,8850,8851,8853,8855,8857,8859,8862],{"class":233,"line":588},[231,8852,105],{"class":237},[231,8854,241],{"class":240},[231,8856,8840],{"class":237},[231,8858,1836],{"class":573},[231,8860,8861],{"class":237},"Remove        ",[231,8863,8864],{"class":584},"# undo\n",[15,8866,8867,8868,8871],{},"Sets \"Deny log on\" for network, batch and service on that account. The script only accepts a local user, not a group. If you type ",[28,8869,8870],{},"Users"," by mistake, it would otherwise deny all of this to every user on the machine. It takes effect at the next logon attempt, no reboot, and nothing of yours runs inside lsass.",[15,8873,8874,8875,8878],{},"The RDP right is left alone on purpose, because the tile from Part 1 shows up over RDP and can ask for a code there. But watch out for NLA. In the first version of this article I wrote that RDP keeps working. With NLA, which is on by default, it doesn't. NLA checks the password with a network logon before the session starts, and that is now denied. So you have two options. Turn NLA off, then RDP goes straight to the 2FA tile, with a bit more attack surface on port 3389. Or run the script with ",[28,8876,8877],{},"-KeepNetwork",". Then RDP with NLA works, but SMB and WinRM still take the password alone for this account. There is no setting that gives you both.",[15,8880,8881,8882,8885,8886,8889],{},"The script exports the current policy first and merges the account's SID into each right, instead of writing the right from scratch. That detail matters. ",[28,8883,8884],{},"secedit \u002Fconfigure"," replaces a right's entire member list, so a naive version of this script would quietly strip every other account off ",[28,8887,8888],{},"SeDenyNetworkLogonRight"," on the way past.",[15,8891,8892,8893,8895],{},"Enroll the account in the tile ",[72,8894,142],{}," you deny its other paths, and keep a second admin that still works. A broken tile plus a denied network logon is a locked machine.",[259,8897,8899],{"id":8898},"_4-the-authentication-package","4. The authentication package",[222,8901,8903],{"className":224,"code":8902,"language":226,"meta":227,"style":227},".\\install-authpackage.ps1\n",[28,8904,8905],{"__ignoreMap":227},[231,8906,8907,8909],{"class":233,"line":234},[231,8908,105],{"class":237},[231,8910,8911],{"class":240},"\\install-authpackage.ps1\n",[15,8913,8914,8915,8917],{},"Copies ",[28,8916,8616],{}," into System32 and appends to:",[222,8919,8922],{"className":8920,"code":8921,"language":517,"meta":227},[515],"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\n    Authentication Packages = msv1_0 TacAuthPackage   (REG_MULTI_SZ)\n",[28,8923,8921],{"__ignoreMap":227},[15,8925,8926,8927,8930],{},"The script refuses to write at all if ",[28,8928,8929],{},"msv1_0"," is not already in that list. That is a cheap guard against turning a typo into an unbootable machine. Reboot to load it, because LSA reads this value once, at boot.",[15,8932,8933],{},"Remember what this option actually buys you, from the chapter above: a package that is only ever called when a logon addresses it by id, which normal logons do not do. Load it to watch it load. Not because it closes anything.",[259,8935,8937],{"id":8936},"_5-the-sub-authentication-filter","5. The sub-authentication filter",[222,8939,8941],{"className":224,"code":8940,"language":226,"meta":227,"style":227},".\\install-subauth.ps1\n",[28,8942,8943],{"__ignoreMap":227},[231,8944,8945,8947],{"class":233,"line":234},[231,8946,105],{"class":237},[231,8948,8949],{"class":240},"\\install-subauth.ps1\n",[15,8951,8914,8952,8954],{},[28,8953,86],{}," into System32 and registers it as the filter:",[222,8956,8959],{"className":8957,"code":8958,"language":517,"meta":227},[515],"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\MSV1_0\n    Auth0 (REG_SZ) = TacSubAuth\n",[28,8960,8958],{"__ignoreMap":227},[15,8962,8963,8964,8966,8967,3408,8970,8973,8974,8977],{},"If ",[28,8965,433],{}," already belongs to another DLL, the script stops. If you installed the first version of this article, it also removes the old ",[28,8968,8969],{},"Auth1",[28,8971,8972],{},"Auth4"," entry of ",[28,8975,8976],{},"TacSubAuth",". Then index the existing enrollments by RID, because that's what the filter reads, and reboot:",[222,8979,8981],{"className":224,"code":8980,"language":226,"meta":227,"style":227},"& \"C:\\Program Files\\TheAdminCafe\\enroll.exe\" \u002Freindex\n",[28,8982,8983],{"__ignoreMap":227},[231,8984,8985,8987,8990,8992],{"class":233,"line":234},[231,8986,1459],{"class":573},[231,8988,8989],{"class":773}," \"C:\\Program Files\\TheAdminCafe\\enroll.exe\"",[231,8991,8798],{"class":573},[231,8993,8994],{"class":237},"reindex\n",[15,8996,8997],{},"This is the one worth loading. Steps 4 and 5 do not depend on each other, you can have either, both or neither registered.",[259,8999,9001],{"id":9000},"_6-remove","6. Remove",[15,9003,9004,8476,9006,9008],{},[28,9005,457],{},[28,9007,94],{},", each followed by a reboot.",[15,9010,9011,9012,9015],{},"Order matters on the way out: deregister, reboot, ",[72,9013,9014],{},"then"," delete the DLL from System32. Deleting the file while it is still registered is a good way to make LSA unhappy on the next boot.",[10,9017,9019],{"id":9018},"debugging-and-what-to-do-if-lsa-will-not-start","Debugging and what to do if LSA will not start",[15,9021,9022,9023,9025,9026,8476,9029,9031],{},"You cannot attach Visual Studio to lsass. The way you debug this is a kernel debugger over the network to a second machine or the host. Both packages log every step with ",[28,9024,6474],{},", prefixed ",[28,9027,9028],{},"[Doppio-AP]",[28,9030,6648],{},", plus best-effort events in the Application log under source \"TheAdminCafe 2FA\".",[15,9033,9034],{},"If LSA does not start after a reboot:",[9036,9037,9038,9041],"ol",{},[114,9039,9040],{},"Restore the snapshot. This is why you took it.",[114,9042,9043],{},"If you have no snapshot, boot WinRE from the install media, open a command prompt and load the offline hive:",[222,9045,9048],{"className":9046,"code":9047,"language":517,"meta":227},[515],"reg load HKLM\\OFF C:\\Windows\\System32\\config\\SYSTEM\nreg query HKLM\\OFF\\ControlSet001\\Control\\Lsa \u002Fv \"Authentication Packages\"\n",[28,9049,9047],{"__ignoreMap":227},[15,9051,9052,9053,9055,9056,9059,9060,9063],{},"Remove your DLL from the value, or delete the ",[28,9054,433],{}," value under ",[28,9057,9058],{},"...\\Lsa\\MSV1_0",", then ",[28,9061,9062],{},"reg unload HKLM\\OFF"," and boot.",[15,9065,9066],{},"Two rules that keep this from ruining a machine:",[111,9068,9069,9075],{},[114,9070,9071,9072,9074],{},"Never overwrite those values, only append or remove your own entry. ",[28,9073,8929],{}," must stay in the package list, or nobody on the machine can authenticate.",[114,9076,9077],{},"Change one thing per reboot. If you register both packages at once and the machine does not come back, you do not know which one did it.",[10,9079,9081],{"id":9080},"limits","Limits",[15,9083,9084],{},"The LSA part is real code, but it is a skeleton and I want to be precise about what it is not:",[111,9086,9087,9090,9093,9096,9104,9107,9110,9116,9119],{},[114,9088,9089],{},"Untested on a live LSA. Compiles, exports checked, nothing more.",[114,9091,9092],{},"Microsoft documents the filter for domain controllers. Whether MSV1_0 calls it for local accounts on a workstation is untested.",[114,9094,9095],{},"Only network logons are refused. Batch and service get no objection, so the user rights are still needed for those.",[114,9097,9098,9100,9101,9103],{},[28,9099,30],{},", the UAC credential prompt and ",[28,9102,183],{}," are interactive logons without the tile. Nothing in either part closes them.",[114,9105,9106],{},"The filter also blocks RDP with NLA for enrolled accounts, just like the deny right.",[114,9108,9109],{},"The authentication package closes nothing in practice, because normal logons never address it. It still reads the name, which has the rename weakness. The filter uses the RID and doesn't.",[114,9111,9112,9113,9115],{},"It will not load at all with ",[28,9114,148],{}," on, because the DLLs are unsigned. The install scripts check this.",[114,9117,9118],{},"No lockout, no counters, no state on the LSA side. All of that lives with the credential provider from Part 1.",[114,9120,9121],{},"A bug here does not fail a logon, it stops the machine from booting. There is no gentle failure mode at this layer.",[10,9123,9125],{"id":9124},"what-real-products-do-differently","What real products do differently",[15,9127,9128,9129,9132],{},"So how does AuthLite or Duo see ",[72,9130,9131],{},"every"," logon, including the ones neither our tile nor our sub-auth filter reliably catches? And no, the answer is not \"they inject into lsass\". That phrase gets thrown around a lot, so let me be precise, because the distinction is the whole point.",[15,9134,9135,9136,9138,9139,9142,9143,9146],{},"A product does not push code into a running ",[28,9137,40],{},". It drops a DLL in a registry key and ",[48,9140,9141],{},"LSA loads it itself at boot",", as part of lsass, running as SYSTEM. That is a documented, supported extension point, the same kind we used above. The thing that actually injects into a live lsass, writing a package into its memory at runtime with debug rights like ",[28,9144,9145],{},"mimikatz misc::memssp",", is the attacker's version. Same seam in the OS, two completely different doors. One is opened by configuration, the other is forced from outside.",[15,9148,9149,9150,9153,9154,9157],{},"The \"see everything\" power lives in the third extension point from the table at the start: the ",[48,9151,9152],{},"SSP\u002FAP",". An SSP sits in the Negotiate authentication flow, and LSA calls its ",[28,9155,9156],{},"SpAcceptCredentials"," with the credentials for logons across the board: network, interactive, service, the lot. That is exactly where a product hangs its second factor or its policy. The classic LSA proxy approach wraps MSV1_0 so the credentials route through the product's code first.",[15,9159,9160,9163,9164,9166,9167,9169],{},[48,9161,9162],{},"And this is the line I stopped at on purpose."," That same ",[28,9165,9156],{}," seam, with the credentials in hand, is precisely what credential-stealing code abuses. Mimilib is an SSP whose ",[28,9168,9156],{}," just writes the plaintext to a file. The mechanism is identical to a legitimate wrapper. The only difference is the one line that keeps the password.",[15,9171,9172,9173,9176,9177,8476,9182,105],{},"Doppio deliberately does not build that component, and the reason is not squeamishness. For a 2FA ",[72,9174,9175],{},"gate"," it adds nothing over what the sub-auth filter already does. The filter decides whether to refuse without ever needing the credential material. The wrapper's only marginal capability over what we have is sitting in the plaintext path, and that capability is a credential-interception primitive, not a second factor. If you want to study the SSP\u002FAP boilerplate itself, read it at the source: Microsoft's ",[19,9178,9181],{"href":9179,"rel":9180},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fsecauthn\u002Fcreating-custom-security-packages",[103],"Creating Custom Security Packages",[19,9183,9186],{"href":9184,"rel":9185},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fsecauthn\u002Flsa-mode-initialization",[103],"LSA Mode Initialization",[15,9188,9189],{},"Even if you do go the SSP route, three things make it hard on a modern system:",[111,9191,9192,9198,9204],{},[114,9193,9194,9197],{},[48,9195,9196],{},"PPL."," You already met this in the install chapter. On a hardened machine lsass only loads a DLL that is properly signed for that protection level. An unsigned SSP is simply refused, which is also why \"sign your own DLL\" was on the checklist in Part 1, and why an unsigned module showing up in lsass is exactly what your EDR should scream about.",[114,9199,9200,9203],{},[48,9201,9202],{},"Credential Guard."," The secrets you might imagine reaching are not there to reach. Credential Guard isolates them in a VSM\u002FLSAIso enclave that your SSP, running in ordinary lsass, cannot touch. It is a different trust boundary.",[114,9205,9206,9209],{},[48,9207,9208],{},"A crash is a bugcheck."," An exception in an SSP does not fail a logon, it takes lsass down, and a dead lsass is a reboot. In a loop, if it faults at startup.",[15,9211,9212],{},"Put together: the SSP\u002FAP is how you would cover every logon type, it is the fragile and dangerous path, and its credential-facing core is indistinguishable from malware. That is the honest reason Doppio ends at a sub-authentication filter that only ever reads an account id and returns a veto.",[15,9214,9215,9216,9219,9220,9223],{},"I might still take the SSP\u002FAP on in a ",[48,9217,9218],{},"Part 3",", but done the way it should be: a properly signed, PPL-compatible package that only ever makes an enforcement decision, that works ",[72,9221,9222],{},"with"," Credential Guard instead of poking at it, and that never keeps a credential it was handed. The interesting part of that article would not be the hook, it would be everything around it. Signing for PPL, surviving a reboot, failing without a bugcheck. The plaintext-logging line that turns this seam into Mimilib stays out, in Part 3 as here.",[10,9225,9227],{"id":9226},"where-this-goes-next","Where this goes next",[15,9229,9230],{},"The skeleton stops at the decision, and that is a clean base to continue from. A few directions, if you want to take it further:",[111,9232,9233,9236,9243],{},[114,9234,9235],{},"Pair the sub-auth filter with the \"Deny log on as a batch job\" and \"as a service\" rights, so the paths it does not cover are covered anyway.",[114,9237,9238,9239,9242],{},"Replace the flat deny with ",[48,9240,9241],{},"push approval",", so a scheduled task or a remote logon can be approved from your phone. That needs a small service and an endpoint, a project of its own.",[114,9244,9245],{},"Move the lockout counter and the secret into that service, so there is one policy across many machines instead of one per machine, and the secret can live in a TPM.",[15,9247,9248,9249,9251,9252,9254,9255,9257],{},"Across both parts we now have a 2FA for local Windows accounts that you can build yourself. A credential provider with a filter, replay protection, a lockout and logging in ",[19,9250,22],{"href":21},", and here the LSA half: an authentication package that teaches the layer, and a sub-authentication filter that refuses network logons for enrolled accounts. MSV1_0 still does the real credential work and the token. It is not a 2FA for ",[72,9253,9131],{}," logon. ",[28,9256,30],{}," and UAC still take the password alone, and whether the filter is called on your workstation is something you have to check in the debugger.",[15,9259,9260],{},"And you know exactly what each piece protects, what it does not, and the one line it refuses to cross. I got one of these pieces wrong the first time, and an audit found it. That's the second lesson of this article: in this layer it's not enough to test your own logic. You also need to have the contract right.",[15,9262,9263,9264,9267],{},"All of it is on GitHub as ",[19,9265,104],{"href":101,"rel":9266},[103],", MIT licensed. If you build on it, I'd like to hear about it.",[9269,9270,9271],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":227,"searchDepth":588,"depth":588,"links":9273},[9274,9275,9276,9277,9278,9282,9283,9291,9297,9301,9309,9310,9311,9312],{"id":12,"depth":581,"text":13},{"id":108,"depth":581,"text":109},{"id":155,"depth":581,"text":156},{"id":210,"depth":581,"text":211},{"id":256,"depth":581,"text":257,"children":9279},[9280,9281],{"id":261,"depth":588,"text":262},{"id":367,"depth":588,"text":368},{"id":388,"depth":581,"text":389},{"id":503,"depth":581,"text":504,"children":9284},[9285,9286,9287,9288,9289,9290],{"id":507,"depth":588,"text":508},{"id":1187,"depth":588,"text":1188},{"id":1339,"depth":588,"text":1340},{"id":1372,"depth":588,"text":1373},{"id":3431,"depth":588,"text":3432},{"id":6517,"depth":588,"text":6518},{"id":6542,"depth":581,"text":6543,"children":9292},[9293,9294,9295,9296],{"id":6652,"depth":588,"text":6653},{"id":8454,"depth":588,"text":8455},{"id":8487,"depth":588,"text":8488},{"id":8518,"depth":588,"text":8519},{"id":8572,"depth":581,"text":8573,"children":9298},[9299,9300],{"id":8591,"depth":588,"text":8592},{"id":8633,"depth":588,"text":8634},{"id":8733,"depth":581,"text":8734,"children":9302},[9303,9304,9305,9306,9307,9308],{"id":8764,"depth":588,"text":8765},{"id":8771,"depth":588,"text":8772},{"id":8817,"depth":588,"text":8818},{"id":8898,"depth":588,"text":8899},{"id":8936,"depth":588,"text":8937},{"id":9000,"depth":588,"text":9001},{"id":9018,"depth":581,"text":9019},{"id":9080,"depth":581,"text":9081},{"id":9124,"depth":581,"text":9125},{"id":9226,"depth":581,"text":9227},"windows","2026-10-01","The credential provider from Part 1 only sees the logon screen. Part 2 goes into LSA to close the paths it can't reach, with an authentication package and an MSV1_0 sub-authentication filter in C++.",false,"md","\u002Fimages\u002Fposts\u002Fwindows-2fa-inside-lsa\u002Fcover.webp",[9320],{"title":9321,"description":9322,"website":101,"image":9323},"Doppio on GitHub","The complete source code of both parts. MIT licensed.","https:\u002F\u002Fgithub.githubassets.com\u002Fimages\u002Fmodules\u002Flogos_page\u002FGitHub-Mark.png",{},"\u002Fposts\u002Fwindows-2fa-inside-lsa",{"title":5,"description":9315},"posts\u002Fwindows-2fa-inside-lsa",[9313,9329,9330,9331,9332],"credential-provider","lsa","2fa","security","IS_wf9pwmJ2zltpRo3XfGaRmazOshH11XIeyLGR9GhM",1790968625686]