Photoline 21 500. Microsoft word 15 30 – popular productivity suite. Windows AppLocker is a technology first introduced in Windows 7 that allow you to restrict which programs users can execute based on the program's attributes. In enterprise environments it is typically configured via Group Policy, however we can leverage the XML it creates to easily build our own custom policies that perform many of the same. Appked AppLocker (Password lock apps) 2.6.0 AppLocker (Password lock apps) 2.7.0 AppLocker Pro Apps Appstore best iTunes Keygen links macapps macOS MacUpdate OS X P2P Paid serial Special K torrent previous post: OmmWriter 1.56.
This is part 4 in a short series on the internals of AppLocker (AL). Part 1 is here, part 2 here and part 3 here. As I've mentioned before this is how AL works on Windows 10 1909, it might differ on other versions of Windows.
In the first three parts of this series I covered the basics of how AL blocked process creation. We can now tackle another, optional component, blocking DLL loading. If you dig into the Group Policy Editor for Windows you will find a fairly strong warning about enabling DLL rules for AL: It seems MS doesn't necessarily recommend enabling DLL blocking rules, but we'll dig in anyway as I can't find any official documentation on how it works and it's always interesting to better understand how something works before relying on it. We know from the part 1 that there's a policy for DLLs in the DLL.Applocker file. We might as well start with dumping the Security Descriptor from the file using the Format-AppLockerSecurityDescriptor function from part 3, to check it matches our expectations. The DACL is as follows: - Type : AllowedCallback - Name : Everyone - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains '%WINDIR%*' - Type : AllowedCallback - Name : Everyone - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains '%PROGRAMFILES%*' - Type : AllowedCallback - Name : BUILTINAdministrators - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains '*' - Type : Allowed - Name : APPLICATION PACKAGE AUTHORITYALL APPLICATION PACKAGES - Access: Execute|ReadAttributes|ReadControl|Synchronize - Type : Allowed - Name : APPLICATION PACKAGE AUTHORITYALL RESTRICTED APPLICATION PACKAGES - Access: Execute|ReadAttributes|ReadControl|Synchronize Nothing shocking here, just our rules written out in a security descriptor. However it gives us a hint that perhaps some of the enforcement is being done inside the kernel driver. Unsurprisingly if you look at the names in APPID you'll find a function called SrpVerifyDll. There's a good chance that's our target to investigate. By chasing references you'll find the SrpVerifyDll function being called via a Device IO control code to an device object exposed by the APPID driver (DeviceSrpDevice). I'll save you the effort of reverse engineering, as it's pretty routine. The control code and input/output structures are as follows: // 0x225804 #define IOCTL_SRP_VERIFY_DLL CTL_CODE(FILE_DEVICE_UNKNOWN, 1537, METHOD_BUFFERED, FILE_READ_DATA) struct SRP_VERIFY_DLL_INPUT { ULONGLONG FileHandle; USHORT FileNameLength; WCHAR FileName[ANYSIZE_ARRAY]; }; struct SRP_VERIFY_DLL_OUTPUT { NTSTATUS VerifyStatus; }; Looking at SrpVerifyDll itself there's not much to really note. It's basically very similar to the verification done for process creation I described in detail in part 2 and 3:
There is one big difference in step 1 where the token is captured over the one I documented in part 3. In process blocking if the current token was a non-elevated UAC token then the code would query for the full elevated token and use that to do the access check. This means that even if you were creating a process as the non-elevated user the access check was still performed as if you were an administrator. In DLL blocking this step does not take place, which can lead to a weird case of being able to create a process in any location, but not being able to load any DLLs in the same directory with the default policy. I don't know if this is intentional or Microsoft just don't care? Who calls the Device IO Control to verify the DLL? To save me some effort I just set a breakpoint on SrpVerifyDll in the kernel debugger and then dumped the stack to find out the caller:
Breakpoint 1 hit
fffff803`38cff100 48895c2410 mov qword ptr [rsp+10h],rbx
# Call Site
01 appid!AipDeviceIoControlDispatch
03 nt!IopSynchronousServiceTail
05 nt!NtDeviceIoControlFile
07 ntdll!NtDeviceIoControlFile
09 ADVAPI32!SaferiIsDllAllowed
0b ntdll!LdrpMapDllFullPath
0d ntdll!LdrpLoadDllInternal
Easy, it's being called from the function SaferiIsDllAllowed which is being invoked from LdrLoadDll. This of course makes perfect sense, however it's interesting that NTDLL is calling a function in ADVAPI32, has MS never heard of layering violations? Let's look into LdrpMapDllNtFileName which is the last function in NTLL before the transition to ADVAPI32. The code which calls SaferiIsDllAllowed looks like the following: NTSTATUS status; if ((LoadInfo->LoadFlags &0x100) 0 && LdrpAdvapi32DllHandle) { status = LdrpSaferIsDllAllowedRoutine( LoadInfo->FileHandle, LoadInfo->FileName); } The call to SaferiIsDllAllowed is actually made from a global function pointer. This makes sense as NTDLL can't realistically link directly to ADVAPI32. Something must be initializing these values, and that something is LdrpCodeAuthzInitialize. This initialization function is called during the loader initialization process before any non-system code runs in the new process. It first checks some registry keys, mostly importantly whether 'RegistryMachineSystemCurrentControlSetControlSrpGPDLL' has any sub-keys, and if so it proceeds to load the ADVAPI32 library using LdrLoadDll and query for the exported SaferiIsDllAllowed function. It stores the DLL handle in LdrpAdvapi32DllHandle and the function pointer 'XOR' encrypted in LdrpSaferIsDllAllowedRoutine. Once SaferiIsDllAllowed is called the status is checked. If it's not STATUS_SUCCESS then the loader backs out and refuses to continue loading the DLL. It's worth reiterating how different this is from WDAC, where the security checks are done inside the kernel image mapping process. You shouldn't be able to even create a mapped image section which isn't allowed by policy when WDAC is enforced. However with AL loading a DLL is just a case of bypassing the check inside a user mode component. If we look back at the calling code in LdrpMapDllNtFileName we notice there are two conditions which must be met before the check is made, the LoadFlags must not have the flag 0x100 set and LdrpAdvapi32DllHandle must be non-zero. The most obvious condition to modify is LdrpAdvapi32DllHandle. If you already have code running (say VBA) you could use WriteProcessMemory to modify the memory location of LdrpAdvapi32DllHandle to be 0. Now any calls to LoadLibrary will not get verified and you can load any DLL you like outside of policy. In theory you might also be able to get the load of ADVAPI32 to fail. However unless LdrLoadDll returns STATUS_NOT_FOUND for the DLL load then the error causes the process to fail during initialization. As ADVAPI32 is in the known DLLs I can't see an easy way around this (I tried by renaming the main executable trick from the AMSI bypass). The other condition, the LoadFlags is more interesting. There still exists a documented LOAD_IGNORE_CODE_AUTHZ_LEVEL flag you can pass to LoadLibraryEx which used to be able to bypass AppLocker DLL verification. However, as with SANDBOX_INERT this in theory was limited to only System and TrustedInstaller with KB2532445, although according to Stefan Kanthak it might not be blocked. That said I can't get this flag to do anything on Windows 10 1909 and tracing through LdrLoadDll it doesn't look like it's ever used. Where does this 0x100 flag come from then? Seems it's set by the LDrpDllCharacteristicsToLoadFlags function at the start of LdrLoadDll. Which looks like the following: int LdrpDllCharacteristicsToLoadFlags(int DllCharacteristics) { int load_flags = 0; // .. if (DllCharacteristics & 0x1000) load_flags |= 0x100; return load_flags; } If we pass in 0x1000 as a DllCharacteristics flag (this doesn't seem to work by putting it in the DLL PE headers as far as I can tell) which is the second parameter to LdrLoadDll A simple PowerShell script to test this flag is below: If you run Start-Dll 'PathToAny.DLL' where the DLL is not in an allowed location you should find it fails. However if you run Start-Dll 'PathToAny.DLL' 0x1000 you'll find the DLL now loads. Of course realistically the DLL blocking is really more about bypassing the process blocking by using the DLL loader instead. Without being able to call LdrLoadDll or writing to process memory it won't be easy to bypass the DLL verification (but of course it will not impossible). This is the last part on AL for a while, I've got to do other things. I might revisit this topic later to discuss AppX support, SmartLocker and some other fun tricks. This is part 2 in a short series on the internals of AppLocker (AL). Part 1 is here, part 3 here and part 4 here. In the previous blog post I briefly discussed the architecture of AppLocker (AL) and how to setup a really basic test system based on Windows 10 1909 Enterprise. This time I'm going to start going into more depth about how AL blocks the creation of processes which are not permitted by policy. I'll reiterate in case you've forgotten that what I'm describing is the internals on Windows 10 1909, the details can and also certainly are different on other operating systems. How Can You Block Process Creation?When the APPID driver starts it registers a process notification callback with the PsSetCreateProcessNotifyRoutineEx API. A process notification callback can return an error code by assigning to the CreationStatus field of the PS_CREATE_NOTIFY_INFO structure to block process creation. If the kernel detects a callback setting an error code then the process is immediately terminated by calling PsTerminateProcess.An interesting observation is that the process notification callback is NOT called when the process object is created. It's actually called when the first thread is inserted into the process. The callback is made in the context of the thread creating the new thread, which is usually the thread creating the process, but it doesn't have to be. If you look in the PspInsertThread function in the kernel you'll find code which looks like the following: if (++Process->ActiveThreads 1) CurrentFlags |= FLAG_FIRST_THREAD; // .. if (CurrentFlags & FLAG_FIRST_THREAD) { if (!Process->Flags3.Minimal || Process->PicoContext) PspCallProcessNotifyRoutines(Process); } This code first increments the active thread count for the process. If the current count is 1 then a flag is set for use later in the function. Further on the call is made to PspCallProcessNotifyRoutines to invoke the registered callbacks, which is where the APPID callback will be invoked. The behavior of blocking process creation after the process has been created is the key difference between WDAC and AL. WDAC prevents the creation of any executable code which doesn't meet the defined policy, therefore if you try and create a process with an executable file which doesn't match the policy it'll fail very early in process creation. However AL will allow you to create a process, doing many of the initialization tasks, and only once a thread is inserted into the process will the rug be pulled away. The use of the process notification callback does have one current weakness, it doesn't work on Windows Subsystem for Linux processes. And when I say it doesn't work the APPID callback never gets invoked, and as process creation is blocked by invoking the callback this means any WSL process will run unmolested. It isn't anything to do with the the checks for Minimal/PicoContext in the code above (or seemingly due to image formats as Alex Ionescu mentioned in his talk on WSL although that might be why AL doesn;t even try), but it's due to the way the APPID driver has enabled its notification callback. Specifically APPID calls the PsSetCreateProcessNotifyRoutineEx method, however this will not generate callbacks for WSL processes. Instead APPID needs to use PsSetCreateProcessNotifyRoutineEx2 to get callbacks for WSL processes. While it's probably not worth MS implementing actual AL support for WSL processes I'm surprised they don't give an option to block outright rather than just allowing anything to run. Why Does AppLocker Decide to Block a Process?We now know how process creation is blocked, but we don't know why AL decides a process should be blocked. Of course we have our configured rules which much be enforced somehow. Each rule consists of three parts:
void AiProcessNotifyRoutine(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) { PUNICODE_STRING ImageFileName; if (CreateInfo->FileOpenNameAvailable) ImageFileName = CreateInfo->ImageFileName; else SeLocateProcessImageName(Process, &ImageFileName); CreateInfo->CreationStatus = AipCreateProcessNotifyRoutine( ProcessId, ImageFileName, CreateInfo->FileObject, Process, CreateInfo); } The first thing the callback does is extract the path to the executable image for the process being checked. The PS_CREATE_NOTIFY_INFO structure passed to the callback can contain the image file path if the FileOpenNameAvailable flag is set. However there are situations where this flag is not set (such as in WSL) in which case the code gets the path using SeLocateProcessImageName. We know that having the full image path is important as that's one of the main selection criteria in the AL rule sets. The next call is to the inner function, AipCreateProcessNotifyRoutine. The returned status code from this function is assigned to CreationStatus so if this function fails then the process will be terminated. There's a lot going on in this function, I'm going to simplify it as much as I can to get the basic gist of what's going on while glossing over some features such as AppX support and Smart Locker (though they might come back in a later blog post). For now it looks like the following: NTSTATUS AipCreateProcessNotifyRoutine( HANDLE ProcessId, PUNICODE_STRING ImageFileName, PFILE_OBJECT ImageFileObject, PVOID Process, PPS_CREATE_NOTIFY_INFO CreateInfo) { POLICY* policy = SrpGetPolicy(); if (!policy) return STATUS_ACCESS_DISABLED_BY_POLICY_OTHER; HANDLE ProcessToken; HANDLE AccessCheckToken; AiGetTokens(ProcessId, &ProcessToken, &AccessCheckToken); if (AiIsTokenSandBoxed(ProcessToken)) return STATUS_SUCCESS; BOOLEAN ServiceToken = SrpIsTokenService(ProcessToken); if (SrpServiceBypass(Policy, ServiceToken, 0, TRUE)) return STATUS_SUCCESS; HANDLE FileHandle; AiOpenImageFile(ImageFileName, ImageFileObject, &FileHandle); AiSetAttributesExe(Policy, FileHandle, ProcessToken, AccessCheckToken); NTSTATUS result = SrppAccessCheck( Applocker 2 7 0 42AccessCheckToken,Policy); if (!NT_SUCCESS(result)) { AiLogFileAndStatusEvent(..); if (Policy->AuditOnly) result = STATUS_SUCCESS; } return result; } A lot to unpack here, be we can start at the beginning. The first thing the code does is request the current global policy object. If there doesn't exist a configured policy then the status code STATUS_ACCESS_DISABLED_BY_POLICY_OTHER is returned. You'll see this status code come up a lot when the process is blocked. Normally even if AL isn't enabled there's still a policy object, it'll just be configured to not block anything. I could imagine if somehow there was no global policy then every process creation would fail, which would not be good. Next we get into the core of the check, first with a call to the function AiGetTokens. This functions opens a handle to the target process' access token based on its PID (why it doesn't just use the Process object from the PS_CREATE_NOTIFY_INFO structure escapes me, but this is probably just legacy code). It also returns a second token handle, the access check token, we'll see how this is important later. The code then checks two things based on the process token. First it checks if the token is AiIsTokenSandBoxed. Unfortunately this is badly named, at least in a modern context as it doesn't refer to whether the token is a restricted token such as used in web browser sandboxes. What this is actually checking is whether the token has the Sandbox Inert flag set. One way of setting this flag is by calling CreateRestrictedToken passing the SANDBOX_INERT flag. Since Windows 8, or Windows with KB2532445 installed the 'caller must be running as LocalSystem or TrustedInstaller or the system ignores this flag' according to the documentation. The documentation isn't entirely correct on this point, if you go and look at the implementation in NtFilterToken you'll find you can also set the flag if you're have the SERVICE SID, which is basically all services regardless of type. The result of this check is if the process token has the Sandbox Inert flag set then a success code is returned and AL is bypassed for this new process. The second check determines if the token is a service token, first calling SrpIsTokenService to get a true or false value, then calls SrpServiceBypass to determine if the current policy allows service tokens to bypass the policy as well. If SrpServiceBypass returns true then the callback also returns a success code bypassing AL. However it seems it is possible to configure AL to enforce process checks on service processes, however I can't for the life of me find the documentation for this setting. It's probably far too dangerous a setting to allow the average sysadmin to use. What's considered a service context is very similar to setting the Sandbox Inert flag with CreateRestrictedToken. If you have one of the following groups in the process token it's considered a service: NT AUTHORITYSYSTEM NT AUTHORITYSERVICE NT AUTHORITYRESTRICTED NT AUTHORITYWRITE RESTRICTED
The last two groups are only used to allow for services running as restricted or write restricted. Without them access would not be granted in the service check and AL might end being enforced when it shouldn't.
With that out of the way, we now get on to the meat of the checking process. First the code opens a handle to the main executable's file object. Access to the file will be needed if the rules such as hash or publisher certificate are used. It'll open the file even if those rules are being used, just in case. Next a call is made to AiSetAttributesExe which takes the access token handles, the policy and the file handle. This must do something magical, but being the tease I am we'll leave this for now. Finally in this section a call is made to SrppAccessCheck which as its name suggests is doing the access check again the policy for whether this process is allowed to be created. Note that only the access check token is passed, not the process token. The use of an access check, verifying a Security Descriptor against an Access Token makes perfect sense when you think of how rules are structured. The allow and deny rules correspond well to allow or deny ACEs for specific group SIDs. How the rule specification such as path restrictions are enforced is less clear but we'll leave the details of this for next time. The result of the access check is the status code returned from AipCreateProcessNotifyRoutine which ends up being set to the CreationStatus field in the notification structure which can terminate the process. We can assume that this result will either be a success or an error code such as STATUS_ACCESS_DISABLED_BY_POLICY_OTHER. One final step is necessary, logging an event if the access check failed. If the result of the access check is an error, but the policy is currently configured in Audit Only mode, i.e. not enforcing AL process creation then the log entry will be made but the status code is reset back to a success so that the kernel will not terminate the process. To do the test we'll need to install my NtObjectManager PowerShell module. We'll use the module more going forward so might as well install it now. To do that follow this procedure on the VM we setup last time:
Now run the following three commands in the PowerShell windows. You might need to adjust the executable path as appropriate for the file you copied (and don't forget the ?? prefix). $path = '??C:Users$env:USERNAMEDesktopnotepad.exe' $sect = New-NtSectionImage -Path $path $p = [NtApiDotNet.NtProcess]::CreateProcessEx($sect) Get-NtStatus $p.ExitStatus After the call to Get-NtStatus it should print that the current exit code for the process is STATUS_PENDING. This is an indication that the process is alive, although at the moment we don't have any code running in it. Now create a new thread in the process using the following: [NtApiDotNet.NtThread]::Create($p, 0, 0, 'Suspended', 4096) Get-NtStatus $p.ExitStatus Applocker 2 7 0 4 +After calling NtThread::Create you should receive an big red exception error and the call to Get-NtStatus should now show that the process returned error. To make it more clear I've reproduced the example in the following screenshot:
That's all for this post. Of course there's still a few big mysteries to solve, why does AiGetTokens return two token handles, what is AiSetAttributesExe doing and how does SrppAccessCheck verify the policy through an access check? Find out next time.
Comments are closed.
|
Details
AuthorWrite something about yourself. No need to be fancy, just an overview. Archives
December 2021
Categories |