Skip to content

Instantly share code, notes, and snippets.

@richinseattle
Created December 22, 2021 00:09
Show Gist options
  • Save richinseattle/6bc4582f69d9e61a324e9888a1e43ed1 to your computer and use it in GitHub Desktop.
Save richinseattle/6bc4582f69d9e61a324e9888a1e43ed1 to your computer and use it in GitHub Desktop.

DTrace for Windows

https://github.com/microsoft/DTrace-on-Windows

Overview

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.

Installation / Build Notes

  • 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

Usage

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment