microsoft / DTrace-on-Windows

Code for the cross platform, single source, OpenDTrace implementation
Other
473 stars 42 forks source link

Driver out of date #8

Closed stevemk14ebr closed 1 year ago

stevemk14ebr commented 3 years ago

For whatever reason the driver in this project is incomplete. Key routines such as TraceRegisterEngine and TraceInitSystem are not implemented. Generally the kernel interface should be documented as this would help security researchers use these apis :)

ashedel commented 2 years ago

Driver code in this repository is a subset of what MSFT distributes in binary form. The reason for this besides that we want to keep kernel trace interface private is that in order to use this interface dtrace.sys needs to be production-signed as a part of the OS build. Provided code should be sufficient to aid the development of your own trace providers.

stevemk14ebr commented 2 years ago

Can you share what are the exact signing requirements of dtrace? I understand that it's loaded by winload/ntoskrnl via dynamic schema set extensions. I'm inspecting the certificate and I notice it's signed with a certificate with the two EKUs:

Windows System Component Verification (1.3.6.1.4.1.311.10.3.6) Code Signing (1.3.6.1.5.5.7.3.3)

I'm able to generate my own self signed certificates that mirror this (I own the UEFI secure boot platform key and enable custom signers), however, when signing my own driver, I do not have the Platform Manifest Binary ID (1.3.6.1.4.1.311.10.3.28) field present in my driver. My driver therefore fails during kernel initialization and boots the windows repair screen, it boots and initializes successfully with DSE off. For reference, here's a comparison, my driver left, dtrace right:

image

I am able to load other boot drivers that are self signed by myself, but not this dtrace driver. I suspect it's due to this missing platform manifest ID, am I correct? And if so, how does one get signtool to generate that field, does my cert need an addition EKU?

stevemk14ebr commented 2 years ago

Unless I am mistaken the signing requirements are as follows, can you please confirm?:

ValidationFlags=IMGP_LATEST_MS_ROOT_REQUIRED | IMGP_WINDOWS_ROOT_REQUIRED | IMGP_MS_SIGNATURE_REQUIRED Scenario=ImgSigningScenarioWindows

Within winload!ImgpValidateImageHash, after the api schema set (which also must be MS signed) is loaded. It's loaded as an import to ntoskrnl.

This prevents third parties who own their platform key from utilizing the kernel dtrace interfaces, unless DSE is turned off manually each and every boot. Please reconsider this requirement and allow dtrace to be loaded when signed by non microsoft roots.

ashedel commented 2 years ago

Confirming. Simply replacing a driver on top of your existing install will not let it load as an import of a NTOS kernel because of a strict signing requirement that are in place to let it work as a part of the NTOS. Workaround to test your build would be to break this link between dtrace.sys and NTOS kernel by deleting "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\ApiSetSchemaExtensions\a6811726-50f2-43b1-a328-27285ef45fca" and rebooting. This will disable tight integration with NTOS kernel that is required for fbt/syscall/etw, but the rest of it should still be functional.

stevemk14ebr commented 2 years ago

Thanks for confirming that! Thanks for that tip, I understand the schema set entries and requirements. I actually already have all the kernel interfaces documented and a re-implementation working. My actual issue is that it's not possible for others to use my implementation unless DSE is off, which is a bit unfortunate.

It would be really cool if windows would trust the roots set by custom kernel signers for components like this. (And also make this feature more accessible)

haripulapaka commented 2 years ago

hi @stevemk14ebr, are you able to share more info of what you are building, its use cases, etc.? These interfaces are internal to ntos and thats why the api set needs to be signed with windows code cert.

stevemk14ebr commented 2 years ago

I'd love to. My use case is extremely broad, as an RE/security researcher inspecting applications by tracing their system calls is very very powerful, and it's great to have a blessed way to do this without disabling patchguard and other kernel security systems. Malware analysis, general RE via tracing, troubleshooting, etc, things like that.

My use case is simple, I found the kernel capabilities of DTrace extremely powerful, but the D language itself extremely lacking. I was unable to express the logic I wanted in the provided scripting language, things like printing wide strings, looping, functions, etc are all either very difficult or entirely missing.

In many cases it's also useful to execute additional code during system call enter/exit. Think like writing to a logfile, copying files, writing to files, dumping processes, killing processes, etc, arbitrary logic. Some of this is kind of supported by DTrace but it's asynchronous from the system call, it appears things are queued by the driver then dispatched by the usermode component, this is not good for me as I often need to perform side effects right at the moment of the system call enter, blocking it until the side effect is complete.

So, I reversed the dtrace Stp syscall interfaces and built my own dtrace. It allows users to upload compiled free-standing C dlls to the kernel which maps them to memory and calls into them via function pointer on system call enter/exit. This allows users to comfortably program in C instead of the esoteric D language. It's also much faster as 'user scripts' are just assembly and do not have to be interpreted. Via my driver I pass a pointer to GetSystemRoutineAddress so they may resolve any kernel apis they like within their DLL, when they wish to perform 'side-effects' they may simply resolve what kernel apis they want, and invoke them. Because DTrace only triggers on the kernel <-> usermode boundary (for syscall probes) users are free to execute arbitrary system routines without re-entrancy concerns. EDIT: I forgot about Zw* apis called from drivers, these can re-enter the tracing probes. To fix this the TracingPrivate field of kthread can be used. Dtrace here uses this as a TLS slot to store vars like self->. I instead re-use this as a counter to track thread stack call depth and if depth > 1 then ignore the call on probe entry as it came from within my own probe handler.

I additionally lowered the IRQL requirements. DTrace executes at IRQL 15, which seems unnecessary as far as I can tell, at least for the syscall probe type. I only raise to IRQL 15 when calling the TraceAccessMemoryApi, then immediately lower it after, so that I may use the special don't BSOD case in the Mm handler. This basically gives users a proper C based system call hook where they make execute arbitrary side effects at IRQL = LOW_LEVEL. Obviously super powerful for analysis and research.

If you could expand on the IRQL 15 requirement I'd love to understand that, my best guess is that it's to prevent pre-emption of the interpreter or something of that sort (lock ordering)? Or perhaps something to do with either memory paging or the requirements of other probe types like FBT (which I did not research as the secure kernel and hypervisor interfaces are not documented)?

In summary: 1) Replaced D language with C code, kernel loads as a free standing DLL and maps it 2) Lowered IRQL reqs

If I may make a suggestion, consider replacing the D language with a kernel based web assembly JIT (or interpreter!). This would be much more performant, and web assembly is an excellent technology for this as it's sandboxed by design and allows users to program in higher level languages such as C or Rust. I attempted this path, but I could not bring myself to build a JIT, I did implement an interpreter system, but that was too slow to service each system call. This web assembly system would be prefferable to my C based plugin system, as that obviously opens up a security concern since code from usermode may be executed within the kernel. For my purposes this is to be run in an analyst's VM, and not on production systems or anything, so in my use case this concern is not relevant. Besides this plugin system, my re-implementation doesn't open any other sort of security holes, system call arguments can't be modified, and users can't 'cancel' system calls or fake return values. It's quite a nice kernel interface for tracing you've all added, big fan.

These interfaces are internal to ntos and thats why the api set needs to be signed with windows code cert

I understand this, but NTOS could trust the secure boot platform key root CA, by signing my driver with the root key used by the secure boot system I prove system ownership and therefore am secure boot compatible (if this change was implemented ofc, it's not as-is). It already trusts drivers signed from this root CA for boot and other drivers.

stevemk14ebr commented 2 years ago

Hi, I've released my project here: https://github.com/mandiant/STrace. I'd be interested in discussing the signing design of dtrace, as well as some other points of this system with the Microsoft team. If you'd be interested in that, please dm me on twitter at @stevemk14ebr or if you could provide an email here or another means of contact I would be grateful!

I am a big fan of this tracing system, I'd like to work on making it better with your help :)

CodeMaxx commented 1 year ago

Closing, since as @ashedel mentioned this repository is a subset of what Microsoft distributes as a binary.

stevemk14ebr commented 1 year ago

Even if the kernel interfaces are not to be documented, relaxing the signer requirements so that third parties other than microsoft may use the interface would be excellent (similar to say the antimalware interfaces). The only option to use these interface yourself as it stands requires:

1) Significant reverse engineering (done) 2) Disabling driver signature enforcement each and every boot

Issue 2 is an architectural design issue as it is incompatible with hypervisor security mechanisms, not to mention the boot now requires manual intervention to disable DSE - which disallows any custom dtrace implementation to be used in any automated system (think sandbox like or tracing technology)