https://github.com/microsoft/DTrace-on-Windows
DTrace for Windows is a port of the opensource release of DTrace originally developed by Sun for Solaris in 2005. DTrace allows for high performance function tracing with access to typed arguments and statistical event collection. DTrace utilizes several types of instrumentation or trace frameworks provided by Windows including ETW, a system call tracer, a kernel function tracer, and a userland function tracer.
- Install the DTrace for Windows binary package
https://download.microsoft.com/download/7/9/d/79d6b79a-5836-4118-a9b7-60bc77c97bf7/DTrace.amd64.msi
- Enable tracing boot flags in BCDEdit from Admin console:
bcdedit /set dtrace on
- Setup symbols (setx will add environment value permanently)
mkdir c:\symbols
setx _NT_SYMBOL_PATH srv*C:\symbols*https://msdl.microsoft.com/download/symbols
- Reboot and DTrace will be enabled
Basic setup and usage is provided in this Microsoft article
https://techcommunity.microsoft.com/t5/windows-kernel-internals/dtrace-on-windows/ba-p/362902
This book is a good guide to the language syntax
https://illumos.org/books/dtrace/chp-intro.html
Example One Liners:
**syscall counts **
dtrace -n "syscall:::entry { @num[probefunc] = count(); }"
dtrace: description 'syscall:::entry ' matched 470 probes
NtAlpcCreatePortSection 1
NtAlpcCreateSectionView 1
NtAlpcDeletePortSection 1
NtAlpcDeleteSectionView 1
...
NtOpenProcess 511
NtQueryValueKey 578
NtAlpcSendWaitReceivePort 617
NtSetInformationThread 634
NtDeviceIoControlFile 714
NtQueryInformationProcess 805
NtWaitForSingleObject 805
NtClose 1407
syscall count by process
dtrace -n "syscall:::entry { @num[pid,execname] = count(); }"
dtrace: description 'syscall:::entry ' matched 470 probes
132 Registry 1
6124 SynTPEnhServic 1
2084 svchost.exe 2
4596 RAVBg64.exe 3
8248 RAVBg64.exe 3
9368 Thunderbolt.ex 3
12696 RAVBg64.exe 3
12828 RAVBg64.exe 3
12892 RuntimeBroker. 4
5836 vmware-usbarbi 5
7132 svchost.exe 8
For more complex needs, you will want to write a program in the DTrace programming language. The language is C-like but lacks loops. The programs are compiled and then inserted into the DTrace instrumentation loop. The DTrace scripts run in a bytecode VM with protected memory access (faulty hooks should not crash the system) This is somewhat similar to how eBPF works on Linux.
Most public examples show how to collect statistics but we are more interested in hooking system calls and inspecting arguments, which can be done trivially. In the below example I am dumping all calls to NtDeviceIoControlFile system call with their IOCTL code and input/output buffer address and length. Arguments can be accessed as the args
array and will be typed, alternatively they can be accessed as arg0, arg1, arg2, etc and will be generically typed as unsigned longs. You can cast and dereference pointers, use copyin()
to copy buffers around. this
is a local function context, self
is a thread local storage context you can initialize arbitrary variables off of for access in other hooks. All hook functions are asynchronous.
syscall::NtDeviceIoControlFile:entry
{
if(execname != "conhost.exe")
{
this->ioctl = args[5];
this->inbuflen = args[7];
this->inbufptr = args[6];
this->outbuflen = args[9];
this->outbufptr = args[8];
printf("[%s:%d] IOCTL = %x in=%p %x out=%p %x\n", execname, pid, this->ioctl, this->inbufptr, this->inbuflen, this->outbufptr, this->outbuflen);
}
}
Example output:
C:\tools>\DTrace\dtrace.exe -s trace.d
dtrace: script 'trace.d' matched 13 probes
CPU ID FUNCTION:NAME
2 492 NtCreateFile:entry [Taskmgr.exe:11624]
2 493 NtCreateFile:return [Taskmgr.exe:11624] HANDLE = 0
2 180 NtDeviceIoControlFile:entry [MsMpEng.exe:5820] IOCTL = 8401f in=0 0 out=2261c776a08 400
2 181 NtDeviceIoControlFile:return [MsMpEng.exe:5820] RESULT = 103
1 180 NtDeviceIoControlFile:entry [MsMpEng.exe:5820] IOCTL = 8401f in=0 0 out=2261c776148 400
1 181 NtDeviceIoControlFile:return [MsMpEng.exe:5820] RESULT = 103
3 180 NtDeviceIoControlFile:entry [SocketHeciServ:9600] IOCTL = 12024 in=2ea62fed00 20 out=2ea62fed00 20
6 180 NtDeviceIoControlFile:entry [vmware-usbarbi:5760] IOCTL = 81012340 in=6e6b7ff560 177 out=6e6b7ff550 4
3 181 NtDeviceIoControlFile:return [SocketHeciServ:9600] RESULT = 103
6 181 NtDeviceIoControlFile:return [vmware-usbarbi:5760] RESULT = 0
6 492 NtCreateFile:entry [brave.exe:596]
We can inspect the IOCTL packet data using tracemem(buf, size, maxsize) (this is undocumented behavior) and a call stack with ustack():
if(args[7] > 0) // inbuf length
{
ustack(5);
this->inbuf = (uintptr_t)copyin((uintptr_t)self->IOCTL.inbufptr, self->IOCTL.inbuflen);
tracemem(this->inbuf, 256, self->IOCTL.inbuflen);
}
Example output
brave.exe [11012:624] HANDLE=584
ntdll`NtDeviceIoControlFile+0x14
mswsock`WSPSendTo+0x2b8
IOCTL=12023 INPUT=4d4d7fdca0 68 bytes OUTPUT=0 0 bytes
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
0: f8 dd 7f 4d 4d 00 00 00 01 00 00 00 00 00 00 00 ...MM...........
10: 40 dd 7f 4d 4d 00 00 00 4e 22 9e ea f9 7f 00 00 @..MM...N"......
20: a0 dd 7f 4d 4d 00 00 00 00 f1 38 62 fa 01 00 00 ...MM.....8b....
30: 10 23 38 62 fa 01 00 00 29 9c 73 6f ce 79 00 00 .#8b....).so.y..
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
50: 48 dd 7f 4d 4d 00 00 00 10 00 00 00 00 00 00 00 H..MM...........
60: 80 de 7f 4d 4d 00 00 00 ...MM...