Home WdNisDrv.sys - Windows Defender Network Stream Filter
Post
Cancel

WdNisDrv.sys - Windows Defender Network Stream Filter

Intro

Hello everyone (bee-keepers),

I hope you all had an awesome start to the week. Tonight we are going to take a quick moment and look at some generic modifications within the Windows Defender Network Stream Filter (WdNisDrv.sys) kernel driver. I don’t believe this is mapped to any CVE’s or even is a ‘security’ issue; however, it’s a interesting patch and understanding WHY it was changed is a fun little exercise, as the only way to get better is to practice right? :)

Tools

  • BinDiff - https://www.zynamics.com/bindiff.html
  • Ghidra - https://ghidra-sre.org/
  • Binary Ninja - https://binary.ninja/
  • BinExport - https://github.com/google/binexport
  • WinBinDex - https://winbindex.m417z.com/

File Information

  • Windows Version: 11 22h2
  • Patched Version: 4.18.2302.7
  • Non-Patched Version: 4.18.2201.11
  • Binaries: https://winbindex.m417z.com/?file=wdnisdrv.sys

Diffing

So, to save time explaining patching and the approach, please refer to the "Diffing" section within previous articles, CVE-2023-24869 - Remote Procedure Call Runtime Remote Code Execution Vulnerability - Brief Analysis & CVE-2023-23417- Windows Partition Management Driver - Brief Analysis.

One thing I will note is that the version that I have locally on my device of WdNisDrv.sys shows that is has been updated as of 03/27/2023 while the BASE version reported by Winbindex, shows as being released 09/20/2022

This is quite a large gap; however, luckily we only grew from 88.27KB -> 97.2KB in that time in size. However, this means that when we review within BinDiff, we will have a decent amount of noise to navigate and review, which happens to mean we get some good practice! So chin up haha.

BinDiff - Base: Primary - Latest: Secondary

Ah yes, right off the bat, I can tell that we are in for a fun treat; however, don’t worry, I will save you from having to go through every single one and instead we will focus on a single function.

Primary: sub_1C0014B40 - Secondary: sub_1C0016A74

Now, luckily when loading the primary version into binary ninja, symbols were able to be found and we get some PDB information (this helps the process greatly!); although, quite a few of the function names return nothing from my google-fu so while they help with understanding context, my google-fu was not strong enough to see what was publicly done already. If anyone finds anything that would be super neat!

BinDiff - Base: Primary - Latest: Secondary

Quick glance at this comparison and it is quite a short function; however, we can see that in the latest version, some code was added at the start of the yellow code block. Lets take a closer look at that code block.

BinDiff - Base: Primary - Latest: Secondary

Now that we can see what was added, it appears a function call has been executed where the first argument RCX is assigned the value from RDI which just so happens to also be used to set the second argument for the direct next function call to PTR_RtlAbsoluteToSelfRelativeSD().

Binary Ninja - Identifying Changes

After identifying a modification that seems straight forward, lets check it out within the mighty Binary Ninja, now since the function address is different between both, it’s no issue, we can just navigate directly to that address within Binary Ninja with no problem.

Base - SepSddlSecurityDescriptorFromSDDLString()

I have gone ahead and put a checkmark next to the interesting areas, the last checkmark within the condition statement rax_3 != 0 is the code block that has been modified that we witnessed within BinDiff earlier.

Latest - SepSddlSecurityDescriptorFromSDDLString()

Now, you may notice I have done ahead and renamed quite a few variables as I have gathered them from reading MSDN docs, and also only a single check mark. This check mark is the new function, now it was not originally named memset(); however, since we got PDB info loaded within the Base (older) version of WdNisDrv.sys I used that to determine what the function was doing by comparing Latest WdNisDrv.sys function and searching for constants within Latest inside of the Base version, then verifying similarities between the two functions to make sure they were the same, and in the Base version, the functions name is memset().

SepSddlSecurityDescriptorFromSDDLString()

So, what exactly is this function doing? Well, given the name, we know it is possibly related to Security Descriptors; however, this is just a guess.

In the simplest TL;DR, MSDN docs to the rescue:

The RtlAbsoluteToSelfRelativeSD routine creates a new security descriptor in self-relative format by using a security descriptor in absolute format as a template.

If this executes smoothly then, we get the return value from RtlAbsoluteToSelfRelativeSD() as the return value for SepSddlSecurityDescriptorFromSDDLString() function, or if we take the other code path after the very first conditional statement, which is based on the results of MmGetSystemRoutineAddress() trying to resolve if the function name string SeConvertStringSecurityDescriptorToSecurityDescriptor() is present, then SeConvertStringSecurityDescriptorToSecurityDescriptor() is called instead.

RtlAbsoluteToSelfRelativeSD() & Memset() - What is it all about?

Now that we have a generic understanding of what this function is doing, lets try to understand why the Memset() function has been utilized within this function before the call into RtlAbsoluteToSelfRelativeSD() function.

Memset MSDN Docs:

Sets a buffer to a specified character.

Now, in what context when it comes to security vulnerabilities is memset() related with? Off the top of my mind, possibly direct issues if not proper input validation of the count argument to make sure no buffer overruns occur. However, another class of vulnerabilities are also commonly present and that is the class of information leaks due to memory allocation routines not properly sanitizing the allocation memory region with null bytes.

But, what function here possibly leads to information leak during memory allocation?

ExAllocatePoolWithTag() and Information Leaks

Reviewing the arguments passed to memset() we can determine that the dest argument comes from the return value of ExAllocatePoolWithTag().

ExAllocatePoolWtihTag() MSDN Docs:

Memory that ExAllocatePoolWithTag allocates is uninitialized. A kernel-mode driver must first zero this memory if it is going to make it visible to user-mode software (to avoid leaking potentially privileged contents).

Is this a Security Issue or Not though?

At this time, I am not sure to be honest, the only way that I can think of this possibly being a security related issue would be if their is a size discrepancy between the allocation from ExAllocatePoolWithTag() & RtlAbsoluteToSelfRelativeSD(); where the second function does not properly utilize/fill all data within the buffer returned from the first function and also exposes this information eventually to usermode applications. However, at first glance, it appears that by using the same count/size argument bufLength between both functions, I don’t know how that is possible.

Also, for those who are reading into this on their own, quick little cool tip, they get the actual size of the Security Descriptor by utilizing the function RtlAbsoluteToSelfRelativeSD() to determine the size, by passing null as the second argument. The reason this is possible because of how the functions works according to MSDN.

RtlAbsoluteToSelfRelativeSD() MSDN Docs:

If the buffer is not large enough to hold the security descriptor, RtlAbsoluteToSelfRelativeSD returns STATUS_BUFFER_TOO_SMALL and sets this variable to the minimum required size.

So, if we pass a null byte as the second argument, the function will fail and the third argument BufferLength is then updated as the minimum size required to be successful.

Thanks for taking the time to read this, hopefully it helps others if they are looking at similar things. If you have any insight please share so it can help others :)

This post is licensed under CC BY 4.0 by the author.