How to BSOD Trend Micro Internet Security in 10 minutes

(Prequel: Hot to BSOD Norton Antivirus in 10 minutes)

Recently I have noticed that some of the well known brands like Norton, VmWare, Juniper, Trend Micro and some others have stability issues with their drivers.

It seems like these brands neglect to pass Driver Verifier tests, thus releasing quite unstable code. It is worth mentioning that even small mistake in kernel driver leads to BSOD (Blue Screen Of Death) error which makes your computer rebooting and you lose all your unsaved data .

Today I am going to show how one easily can cause BSOD on machine running Windows Vista and latest Trend Micro Internet Security. There are two key elements to achieve this:

1. Driver Verifier set to verify driver of Trend Micro

2. Custom made tool which is trying to make big amount of TCP connections to one IP (see sources here: stress.cpp)

That’s all. After you set verifier to verify tmtdi.sys (see here version of tmtdi.sys) and run the stress tool, the machine quickly bsods:

Now, let’s take a look at memory dump file generated by this assert:

0: kd> !analyze -v
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *

A device driver attempting to corrupt the system has been caught.  This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arg1: 00000010, caller is freeing a bad pool address
Arg2: 002316da, bad pool address
Arg3: 00000000
Arg4: 00000000

Debugging Details:

Page 15c5e not present in the dump file. Type ".hh dbgerr004" for details
Page 15b84 not present in the dump file. Type ".hh dbgerr004" for details
PEB is paged out (Peb.Ldr = 7ffd600c).  Type ".hh dbgerr001" for details
PEB is paged out (Peb.Ldr = 7ffd600c).  Type ".hh dbgerr001" for details

BUGCHECK_STR:  0xc4_10

POOL_ADDRESS:  002316da


PROCESS_NAME:  stress applicat


LAST_CONTROL_TRANSFER:  from 81b46960 to 8191cb8d

9dcdf608 81b46960 000000c4 00000010 002316da nt!KeBugCheckEx+0x1e
9dcdf628 81b331bd 002316da 9dcdf64c 928a8ac2 nt!ExFreePoolSanityChecks+0x2d
9dcdf634 928a8ac2 002316da 00000000 aa4a4b20 nt!VerifierExFreePoolWithTag+0x28
WARNING: Stack unwind information not available. Following frames may be wrong.
9dcdf64c 928a6c19 9dcdf688 65746d74 00000000 tmtdi+0x8ac2
9dcdf9f8 928ac53a 928b2ba0 a5039478 aa4a4b20 tmtdi+0x6c19
9dcdfae0 928ad42a ae98af00 87e5e2f0 ae98af00 tmtdi+0xc53a
9dcdfafc 81b316be 921c6970 921c6a28 ae98af00 tmtdi+0xd42a
9dcdfb20 8189397d ae98afb8 ad889aa8 921c6970 nt!IovCallDriver+0x23f
9dcdfb34 89dc431e 1416cbc5 00012007 89dc3a8c nt!IofCallDriver+0x1b
9dcdfbfc 89dbf040 a5080880 ae98af00 9dcdfc30 afd!AfdConnect+0x892
9dcdfc0c 81b316be 920e33e0 ae98af00 ad88ac98 afd!AfdDispatchDeviceControl+0x3b
9dcdfc30 8189397d ae98afdc ae98af00 920e33e0 nt!IovCallDriver+0x23f
9dcdfc44 81a957a3 ad88ac98 ae98af00 ae98afdc nt!IofCallDriver+0x1b
9dcdfc64 81a95f48 920e33e0 ad88ac98 02cdfb00 nt!IopSynchronousServiceTail+0x1d9
9dcdfd00 81a97012 920e33e0 ae98af00 00000000 nt!IopXxxControlFile+0x6b7
9dcdfd34 81899c7a 00003bc8 00000168 00000000 nt!NtDeviceIoControlFile+0x2a
9dcdfd34 775f5e74 00003bc8 00000168 00000000 nt!KiFastCallEntry+0x12a
02cdfc40 00000000 00000000 00000000 00000000 0x775f5e74


928a8ac2 83660400        and     dword ptr [esi+4],0


SYMBOL_NAME:  tmtdi+8ac2

FOLLOWUP_NAME:  MachineOwner


IMAGE_NAME:  tmtdi.sys


FAILURE_BUCKET_ID:  0xc4_10_VRF_tmtdi+8ac2

BUCKET_ID:  0xc4_10_VRF_tmtdi+8ac2

Followup: MachineOwner

As it seems from crash stack, the most interesting entries are:


If I take a look at the last one, just where the crash occures it seems like this is a function which is checking the validity of some magic address (see inline comments):

.text:00018A8E ; int __stdcall sub_18A8E(PVOID VirtualAddress, int)
.text:00018A8E sub_18A8E       proc near
.text:00018A8E VirtualAddress  = dword ptr  8
.text:00018A8E                 mov     edi, edi
.text:00018A90                 push    ebp
.text:00018A91                 mov     ebp, esp
.text:00018A93                 push    esi
.text:00018A94                 mov     esi, [ebp+VirtualAddress]             <--- Store in ESI pointer to memory chunk
.text:00018A97                 test    esi, esi
.text:00018A99                 jz      short loc_18AD0
.text:00018A9B                 push    edi
.text:00018A9C                 mov     edi, ds:MmIsAddressValid
.text:00018AA2                 push    esi             ; VirtualAddress         <--- Check if this address is valid
.text:00018AA3                 call    edi ; MmIsAddressValid
.text:00018AA5                 test    al, al
.text:00018AA7                 jz      short loc_18ACF
.text:00018AA9                 mov     eax, [esi+4]                              <--- Storing in EAX address at offset of 4 bytes, i.e., ptr->Address
.text:00018AAC                 test    eax, eax
.text:00018AAE                 jz      short loc_18AC2
.text:00018AB0                 push    eax             ; VirtualAddress
.text:00018AB1                 call    edi ; MmIsAddressValid                 <--- Check if ptr-Address is valid
.text:00018AB3                 test    al, al
.text:00018AB5                 jz      short loc_18AC2
.text:00018AB7                 push    0               ; Tag
.text:00018AB9                 push    dword ptr [esi+4] ; P
.text:00018ABC                 call    ds:ExFreePoolWithTag                <--- Releasing ptr->Address (remember, ESI holds ptr) Here it BSODS!
.text:00018AC2 loc_18AC2:
.text:00018AC2                 and     dword ptr [esi+4], 0
.text:00018AC6                 and     word ptr [esi], 0
.text:00018ACA                 and     word ptr [esi+2], 0
.text:00018ACF loc_18ACF:
.text:00018ACF                 pop     edi
.text:00018AD0 loc_18AD0:
.text:00018AD0                 pop     esi
.text:00018AD1                 pop     ebp
.text:00018AD2                 retn    8
.text:00018AD2 sub_18A8E       endp

This address at offset of 4 bytes reminds me UNICODE_STRING structure, first two words are sizes of string and right after them at offset of 4 bytes you have an address to the actual string:

typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR  Buffer;

Now, let’s go back in one stack frame:

.text:00016BF2                 push    offset aTmtdi_modify_7 ; "[TMTDI_ModifyAddress] Modified:\t[%x:%x:"...
.text:00016BF7                 call    sub_10B34
.text:00016BFC                 add     esp, 50h
.text:00016BFF loc_16BFF:                              ; CODE XREF: sub_160BA
.text:00016BFF                                         ; sub_160BA
.text:00016BFF                 cmp     [ebp+var_35A], 0
.text:00016C06                 jnz     short loc_16C19
.text:00016C08 loc_16C08:                              ; CODE XREF
.text:00016C08                                         ; sub_160BA+128j ...
.text:00016C08                 push    65746D74h       ; int
.text:00016C0D                 lea     eax, [ebp+DestinationString]
.text:00016C13                 push    eax             ; VirtualAddress
.text:00016C14                 call    BSOD_Routine                             <--- It BSODS here

It seems like BSOD is called by the logging routine which is outputing to debugger console info about connection and process, etc:

.text:00016944                 push    offset aTmtdi_modify_6 ; "[TMTDI_ModifyAddress] Modified:\t%u.%u.%"...
.text:00016949                 call    DbgPrint

This is insane, what is the need to call always DbgPrint(…) even in release. Why not use logging levels? In 99% of the cases logs are not needed, so the overhead can be avoided just with using levels for logs.

At the same time, I don’t see any reason to keep logs like this in production code. Reason? If user has BSOD, and this has to be troubleshooted user has to have the ability to store logs in file. DbgPrint prints only to debugger output (or DbgViewer), but this is quite non-user-frendly to ask user to run WinDbg or DbgViewer, right? 🙂

Therefore, production code should have the possibility to log into file if it is needed. Otherwise logs are not needed. They also generate garbage when debugging machine having such a driver.



    • Hey,

      It is funny that the first comment to this post appears exactly after 3 years … Unfortunately, I do not seem to have the exe file anymore.

      Since there is a source file published in the article, you can easially compile it yourself. If this is a problem for you, just let me know and I will compile it.

      • After compiling and running on my workstation it executes fine. However, on other user workstations I receive the error “The program can’t start because MSVCR110D.dll is missing from your computer. Try reinstalling the program to fix this problem”. I’ve checked and I have the exact same Visual C++ redistributables on these machines as I do on my workstation. Any ideas?



        • Hello Joe,

          Yep. You are compiling your project with the option “Multi-threaded DLL” (/MD) but you should compile it with “Multi-threaded” (/MT) in the properties of your project.

          To solve this problem go to Properties -> C/C++ -> Code Generation -> RunTime Library and set it to “Multi-threaded” (/MT).

          The reason why it was working on your machine is because you installed Visual Studio and it installed CRT (C Run Time) libraries. But when you tried on another computer, most likely the Visual Studio was not installed, and there was no runtime dlls. By compiling your project with /MT switch you will get rid of dynamic dependencies and have it statically compiled.

Leave a Reply

Your email address will not be published. Required fields are marked *