Pages

Sunday, February 23, 2014

Change Windows kernel objects' permissions

 

 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.
The following technique can be used to:
    • 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 LiveKD
    LiveKD [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. Kaspersky
    Initially, Kaspersky doesn't allow viewing it's permissions:
    Access denied to permissions
    After we zero out its process ACEs, it seems that we still cannot view the permissions but we can change them:

    Access to change permissions
    This still didn't allow me to kill the avp.exe process, which was my initial goal (not even after removing SSDT hooks) but I'll describe some techniques to do that in the next post.

    If we try this technique on another important process, lsass.exe, we get the following:
    Security warning: no permissions assigned.
    Nice!

    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.

     

    Resources


    1. Windows Security Hardening Through Kernel Address Protection - Mateusz "j00ru" Jurczyk 
    2. Easy local Windows Kernel exploitation - Cesar Cerrudo
    3. Local Kernel-Mode Debugging - MSDN
    4. Windows Internals 6th Ed.

    No comments:

    Post a Comment