Approximately two years ago I found a memory corruption bug in one of the kernel drivers shipped with avast! 4.7. After I reported the bug to ALWIL Software they only took about two weeks to provide a fixed version. That's quite fast for a commercial software vendor. To be honest, I never checked if and how the bug was fixed with that new version. Anyway, a few weeks ago I decided to have a look at the avast! drivers again. While reversing the drivers I quickly realized that the measures taken by ALWIL Software to fix my 2 year old bug weren't sufficient.
► A Short Recap of the "old" BugDisassembly of the vulnerable aavmker4.sys driver (avast! 4.7, file version 4.7.1098.0):
[..] .text:00010E06 loc_10E06: .text:00010E06 xor edx, edx .text:00010E08 mov eax, [ebp+v38_uc] .text:00010E0B mov [eax], edx .text:00010E0D mov [eax+4], edx .text:00010E10 add esi, 4 ; src .text:00010E13 mov ecx, 21Ah ; len .text:00010E18 mov edi, [eax+18h] ; dst .text:00010E1B rep movsd ; memcpy [..]The memcpy() function at .text:00010E1B gets called with the following parameters:
memcpy (EDI, ESI, ECX); EDI (dst): the value is extracted from user controlled IOCTL input data ESI (src): points to user controlled IOCTL input data ECX (len): 0x21AAs both the destination address as well as the source data of that memcpy() call could be controlled by the requesting user it was possible to overwrite arbitrary memory addresses with arbitrary values. This could be exploited to control the kernel execution flow and to execute arbitrary code at the kernel level. For more details see TKADV2008-002.
► The FixThe "new" version of the kernel driver (avast! 4.8 <= 4.8.1368.0) implements the following check to remediate the described bug.
Disassembly of the vulnerable aavmker4.sys driver (avast! 4.8, file version 4.8.1356.0):
[..] .text:00010F21 loc_10F21: .text:00010F21 and [ebp+var_4], 0 .text:00010F25 cmp dword ptr [edi], 0 .text:00010F28 jz loc_10FC5 .text:00010F2E  mov esi, [edi+870h] .text:00010F34 mov [ebp+v34_uc], esi .text:00010F37 mov eax, ds:MmUserProbeAddress .text:00010F3C  cmp esi, [eax] ; user space or kernel space? .text:00010F3E jnb short loc_10F46 [..]Before the described memcpy() function is called a pointer value gets extracted from the user supplied IOCTL input data. That value is then stored in ESI (see ). Then it is checked if ESI points into user space or kernel space (see ). This is done by comparing the pointer with MmUserProbeAddress. If ESI points into kernel space, the memcpy() function gets called as before. If it points into user space memcpy() won't be called.
If ESI points into kernel space the following code gets executed:
[..] .text:00010F99 loc_10F99: .text:00010F99 xor edx, edx .text:00010F9B mov eax, [ebp+v34_uc] .text:00010F9E mov [eax], edx .text:00010FA0 mov [eax+4], edx .text:00010FA3 lea esi, [edi+4] ; src .text:00010FA6 mov ecx, 21Ah ; len .text:00010FAB mov edi, [eax+18h] ; dst .text:00010FAE rep movsd ; memcpy [..]The memcpy() function at .text:00010FAE gets called with the following parameters:
memcpy (EDI, ESI, ECX); EDI (dst): the value is extracted from a user defined kernel space address ESI (src): points to user controlled IOCTL input data ECX (len): 0x21AAssumption of the "new" check: As the destination address of memcpy() is now extracted from kernel space memory it isn't possible to directly control that value from user space anymore.
This only holds true if we weren't able to (temporarily) store user controlled data at a user defined address in kernel space. Unfortunately, the aavmker4.sys driver supports at least one IOCTL that allows an unprivileged user to temporarily store arbitrary data at a known kernel space address.
Well, the fix that never was ;)
► ExploitationExploiting this bug is quite easy:
STEP 1: Use one of the IOCTLs supported by aavmker4.sys to temporarily store arbitrary data at a known kernel space address (e.g. the IOCTL 0xb2d6001c).
STEP 2: Send a request to the vulnerable IOCTL. Store a pointer at offset 0x870 of the IOCTL data that points to the kernel space address of STEP 1.
STEP 3: Manipulate a function pointer.
STEP 4: Fun and profit.
Working out the rest of the attack is left as an exercise for the reader ;)
Affected are avast! 4.8 <= 4.8.1368.0 and avast! 5.0 < 5.0.418.0. See also my security advisory (TKADV2010-003) for further information.
► EIP ControlI wrote a proof of concept (poc) that gains control of the kernel execution flow (EIP control). Output of the kernel debugger when executing the poc (avast! 5.0 under Windows XP SP3):
#################### AAVMKER: WRONG RQ ######################! Access violation - code c0000005 (!!! second chance !!!) 41414141 ?? ??? kd> kb *** ERROR: Symbol file could not be found. Defaulted to export symbols for Aavmker4.SYS - ChildEBP RetAddr Args to Child WARNING: Frame IP not in any known module. Following frames may be wrong. edc99bdc f7955cae 8621ada8 e21f0030 00000001 0x41414141 edc99c34 804ee129 861f8030 866c0d98 806d22d0 Aavmker4+0xcae edc99c44 80574dde 866c0e08 865833c0 866c0d98 nt!IopfCallDriver+0x31 edc99c58 80575c7f 861f8030 866c0d98 865833c0 nt!IopSynchronousServiceTail+0x70 edc99d00 8056e4ec 000002f8 00000000 00000000 nt!IopXxxControlFile+0x5e7 edc99d34 8053d648 000002f8 00000000 00000000 nt!NtDeviceIoControlFile+0x2a edc99d34 7c91e514 000002f8 00000000 00000000 nt!KiFastCallEntry+0xf8 039cb1b0 65006e90 000002f8 b2d60034 039cb1fc 0x7c91e514 039cb24c 00000000 00000000 00000000 00000000 0x65006e90 kd> r eax=8621ada8 ebx=866c0d98 ecx=00000000 edx=00000000 esi=867d2bc0 edi=80527660 eip=41414141 esp=edc99be0 ebp=edc99c34 iopl=0 nv up ei pl nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202 41414141 ?? ???If no kernel debugger is attached the poc gives the following BSoD: