Dear viewer, these subtitles were generated by a machine via the service Trint and therefore are (very) buggy. If you are capable, please help us to create good quality subtitles: https://c3subtitles.de/talk/485 Thanks!
Right. So, yes, I'll be talking
about Windows drivers service, some
new insights.
So my name is Ilya.
I work for a company called Bioactive.
I am the director of penetration testing.
I do Pentax, Waterview basically break
stuff for fun and profit.
And I'm here to talk today about Windows
drivers attack surface.
It's sort of divided up into, you know, I
have a little intro and I've got two
pieces and piece one talks about
gives you some some background and talks
about the where.
And in part two was sort of the guts of
the talk. And it talks about what
I got a lot of ground to cover today.
So I'm going to try and move
my way through part one really fast.
I hope you'll bear with me.
If I don't, I'll run over.
So I kind of have to.
See, I was talking about basically I'm
going to be talking about attack surface
of Windows WDM, which is
Windows driver model drivers,
and specifically about the implementation
of security, I guess the
audience is sort of the you know, if
you're looking for bugs, those drivers
just talk. Might be interesting if
you're a driver, developer, just talk.
Might be interesting.
If you're just curious about this kind of
security stuff, then this talk might be
interesting, too, in terms
of knowledge.
It'd be nice if you have some kind of
general background on, you know,
what an operation is going to look like.
And if you have some specific knowledge
about Windows Kernel and Windows drivers,
that's even better, but not necessarily
required.
Before I talk
about memorization,
it's clear to say that, you know,
standing on the shoulders of giants,
there are have been a number of people
that have done research in these things.
And I would do a disservice if I didn't
at least put their names up there.
If you think I missed someone, please let
me know. I'd I'd like to add your name to
it, but I think this is a fairly
good list.
This this talk is slightly different than
most of those, though.
So if you look at most of the previous
research on Windows called Windows
drivers, it's mostly been focused
on exploitation and
it's usually been focused on the Windows
world, specifically enough so much on the
drivers.
And it's almost always been focused
on like one particular issue.
And so my presentation is different in
the sense that the focus isn't
so much on exploitation,
but it is more on the finding,
identifying of bugs and then giving some
kind of guidance on how to how would
you mitigate or fix these issues.
Also, it isn't so much about we just call
itself as this about specific
drivers, of course, to go ahead and
you can't have one without the other.
So much of this stuff applies
to them just as well and vice versa.
But it's a slight difference, though.
And the other thing is that I'm not
talking about one particular issue.
What I did is I sort of sat down and said
looked at all of the common bugs
you have or you find in most windows
drivers and sort of try to find
the common theme around it in a thread.
And that's basically what I'll be talking
about today.
Right, so the intro is basically
basically the statistics in show
you could easily spend days talking
about all sorts of intricate little
details about windows, driver bugs.
And so I tried to
get it down in the one hour thing
and basically comes down to all of these
little.
Obscure things that very few people seem
to know about and what the consequences
are if you if you if
you know or don't know about certain
things or if you do them wrong or
and whether or not that's documented or
not. And it turns out some of those
things are kind of like
most of it you find anonymous at the end.
But a lot of it is like you have to read
between the lines.
You'll have these subtle hints as to,
you know, what is problematic and what
isn't. But quite often it's not
explicitly mentioned.
And so it makes it very hard for people
to to know these things.
And that includes, unfortunately, most
driver developers.
So with that, let's dove into Part
one series.
The section is by no means to be
exhaustive. It's a quick reminder and
I'm going to try to make my way through
it really fast.
So basically, yeah, start
off with a little bit of architecture.
Basically at a high
level, doing the scroll is divided up
into all of these managers that
have their own tasks to do.
And the idea is that you as a user, if
you want to get to them, you basically
call system cause and you end up in one
of these managers, for example, if you do
at the device your control, you end up in
your manager from
fifty thousand feet view.
This is kind of what it looks like.
So you have I don't know if you know, you
know, OK, basically
from New Zealand, you call
it system calls and then you
go into, you know, your skull table, then
it'll go into one of these managers.
Quite often you end up in jail, a manager
inside, inside
this thing, there's a whole bunch of
frameworks working together.
There's there's over a dozen.
And all of these are worthy of their own
presentation. And I wish I could talk
about all of them, but
due to time constraints,
I'm going to limit myself to
the Windows driver model WDM
and a tiny, tiny little bit about
CDF.
So, yeah, what is WDM?
It's basically the Windows driver model.
It's been around since around Windows
2000.
It sort of extends and updates
the old and T driver model.
It is the standard model for how drivers
are written.
I mean, in recent years there's been a
shift towards Kamdesh, but there's still
a fair amount of WDM.
And so that's why I'll
basic focus on WDM.
So let me now say a few things about
the manager, which is sort of it's
it's this proxy that sits in between the
user and going to drivers.
And depending on what kind of request
you do, it may or may not do some
kind of validation
wants it to. And so it takes your request
for you to land.
And that sort of packages it up
in this thing called an IO
request packet and URP.
And we'll talk about that in a little
bit.
And that basically it finds your
drivers, especially for whatever thing
you want to do and sort of hands it off
there.
And let me try and illustrate that with
some code.
So basically, if you have like a simple a
very, very simple driver basically has
this thing called a driver entry, which
is basically the main entry point.
And then essentially any driver that
wants to have interaction would usually
goes general manager and says, hey,
create this device.
They call your device.
And then they say, OK, now that device
created.
I want usually to be able to talk to it.
And so they go to the manager and say,
I'll create a symbolic link.
And basically what it does, it creates a
symbolic link for you to talk to
that device.
And so basically
just means as you read a bunch of
dispatch calls to our manager and then
you basically
can do axles or fist controls
or open around sofort in code,
just kind of looks like this where
basically, you know, as I mentioned, for
you to create device which will link
and then you do all of these dispatch
functions and then for all of those
functions you basically have
and there's more of these.
But basically for all of these functions,
it kind of looks like this where
there's two arguments
that keep as long as the device object
and the actual urp.
And so the purpose is interesting because
this contains all the data
that's packaged up from user land that
your driver
gets access to.
And it includes things like, you know,
data being passed around your
IO status, what kind of request,
what you are.
And basically it sort of just kind of
nicely completes that where it says, OK,
this is the pointers that are as the
system for a pointer memory
descriptor lists your urp stack.
And so let's talk about your stack.
Basically, for every group that comes
in it associated, there's a stack
associated which is with it, which is
this URP stack, and it contains
very specific information that.
Your driver needs for that particular
operation, and so
it'll say, OK, you
recalled with this major minor function,
no, it's this device object
is this file object.
And then based upon
which major minor function?
No, there's a specific parameter union
and it'll have different sets of
elements inside union based
upon whether it's OK to read or write and
so forth.
But essentially all the specific data you
need, you tend to grab out of Europe
stack.
And then, of course, from New Zealand,
how you get to there? Well, you call
this, you know, a.
device control file
system call.
And then if you look at the last
five arguments sort of ones where there's
there's, you know, potential for four
attack surface and and where the
the entry into driver
happens, which is your
control code input buffer,
buffer like Apple buffer up and buffer
links.
And so, OK,
once that sort of goes through the
manager and it goes to your driver,
driver goes to base, goes to
your device control dispatch
and sort of packs up the surf and hands
it over.
So how depending on
your IO control code, the
way does etiquettes back up
differs.
And so let's look at what the IO control
it looks like, even though
it may look like just a no, it's actually
split up into several different bits and
pieces.
From a security perspective,
we really only care about two pieces of
the required access and the transfer
type.
And so I'll say a little bit about about
transfer type and requite axis.
Right axis basically one of three.
It says file any file data.
And if I write data and what that means
is if you open the handle to a device
and you say, you know, I open it as
Read-Only and then you, you and I are
equal, that requires file.
Right data. For example, Deyo
manager sees all your handles.
Only areto is only Fareed, but
your actual court says file right.
Data that that doesn't match and
so you imagine will kick you out.
And so this, this restricts you to
the access that you need.
And so the other one is the transfer
type, and there's basically four
different kinds of transfer type Bufford
neater, indirect, not direct.
And so let me let me sort of run
through those really quickly.
The first one is Method Bufford.
I met Bufford is sort of the preferred
safe ish way of
it, passing Arktos from from
out to drivers.
What that means is the manager basically
looks at the data that usually
passes in and it says, OK, your input
buffer is, I don't know, let's say 10
bytes big. What I'll do is I'll meet it
all, create a kernel buffer that's 10
bytes, picking up your data over,
and then I'll pass that on to the driver.
And what that means is the driver doesn't
have to worry about, you know,
race conditions or making sure that
the address is within user and
doesn't point a kernel.
And all these tricks, you don't have to
worry about it if using buffer Bufford.
Additionally, when the kernel does all
that copulatory to charge charges,
quarter for your memory. So if you hit
your quota, then that fails and sort
of returns and fails.
So a buffer to sort of the safe
ish way of passing data around.
Midnighters is the exact opposite
it, the manager does nothing, the manager
basically takes the data
given to it as is, and
sort of passed it on
to your driver. So your input buffer,
input buffer, you have a buffer
there, just raw pointers or raw lengths
that have not been validated and
pass along to your driver.
This is the endless source of windows,
driver bugs.
It's in my
view, as a driver writer, you should
avoid them at all costs.
Unfortunately, that doesn't
appear I mean, there's plenty people
pulling drivers to due to stuff.
And I mean, there's a perf benefit,
but it's it's a huge drain on
resources for security because you have
to manually check everything yourself.
So in order to just to sort of
describe an indirect bit abstract, I
first have to say something
about these things called models
and models. Basically, it's called a
memory descriptivist and it's
a data structure in the windows
kernel that represents a buffer by its
physical address.
And I'm not going to discuss that because
the terms are kind of opaque.
But basically when scroll has APIs
to create, modify, delete
and consume these models
so that you can go and say, OK, well, I
have to this model that describes
physical buffer. Now give
me a kernel of virtual address for it,
for example. And
the I think it's
the memory manager will do that for you
and basically hands you back a pointer.
Why is this important? It's important
because in this particular case, where I
go manager, pass the stuff back and
forth, what usually happens is you have
to do a double mapping.
So what happens is the user goes to
kernel to your manager and says, oh,
I have this, this and this
and this length.
And no manager goes to it and says, OK,
I'm going to create a model that
sees what this Juland Pointer is and
figures out what the physical
memory by it is.
And then you know, if it's one page
it finds all the physical memory pages
and sort of packets is up in the MDL
and pass that off to your driver and then
when a driver looks at it it'll come
and say OK, now
get me the kernel for traders
for it. So you get to sort of double
mapping in user and kernel
that boat map to the same physical page.
And the reason why you would do that is
so you get these situations where
you have to copy large amounts of data
around because you're using models.
You now have zero copy as you
get a huge, huge perf increase.
Essentially, that's if you draw them as
paint, that's what it looks like.
Yeah, and so this is done
for for Perth and you get zero copy.
Additionally, you know, you have no pain
of probing and try except
and so there are definitely upsides
stem there's also downsides to it.
And I'll get to that later.
But essentially, now that I've you have
an idea when ideals are basically about
indirect metal objects or these things
where that indirect sort of the
the input is going to be Namgyal.
And so when
your driver takes input and starts
reading from it, it'll come from MDL.
And when you spit out, it's going to be
the exact opposite, where it's like once
you drive, it generates output.
You just put it to an MDL and then
user automatically basically gets it.
In short, that's what these things are.
OK, so this sort of my last slide
four part one. So I hope I
wasn't too confusing. You know, I ran
through it really fast, but it's because
the next section is really where all the
cool stuff is.
But so most of the stuff
I talked about or we'll talk about is
WDM model, which is older
and so came the F is this new COL old
driver framework.
And it's part of what's called a WDEF,
which is something
drive a framework.
And basically this thing was made
sort of it was designed based
upon learning mistakes from
the WDM world.
A lot of that has to do with power
management and things like that, but
also for security.
A few things became apparent
and that sort of made easier to
do and using CDV
and so in general can make it easier to
write, write drivers and less
likely to introduce bugs.
But it is layered over the
same infrastructure that's WDM Soyuzes.
That is to say, if you don't understand
the old model, you're still
going to have sort of these implicit CDV
bugs.
But in general, it does make it harder to
have bugs.
And so what example, security wise is a
great one is where
it strongly discourages the use of
passing, usually pointed directly to
kernel. And in fact, so they have this
set of APIs to sort of extract data
out of the requests.
So one of them is IDF
request, retrieve input buffer.
And if you have met with Bufford or
Indirect Directorate out direct,
that API will just go grab the pointers
for you.
But if using neither, that API will
totally fail.
And if you must use Midnighter, then you
have to call this new API called
Davydov request retrieve unsafe
user input buffer.
And that, I mean pretty explicitly tells
you that it's unsafe
and and so
that's what these things were. You know,
they really knew that using these
methods, Niederer things are horrible.
And so they made it really hard for you
to use it.
And so if you do use it, you know, the
names very explicitly tell you that
things are unsafe.
So and generally as a private developer,
you're encouraged to use KDAF over WDM.
And the cool thing about Kim, you have
said, is actually open source.
Earlier this year, Microsoft slapped an
MIT license on it and put it on GitHub
and about 90 percent of code
is there.
There's tiny bits and pieces still
missing. The reader isn't there yet,
but by and large, the guts
of Kamdar for totally available
under a
free software license, I guess.
And if you want, you can totally take a
look at them. OK, so we get
out of the way. Let's dove
into these lots of
windows. Drivers attack surface.
So basically, pilots,
when you look at the
sort of bugs you have in a
driver, there's sort of three things that
come up right.
They can be elevation of privilege, bugs,
you know, like a buffer overflow or
something like it.
They can be the service right, where you
get a kernel panic
or a deadlock or,
you know, resource starvation type of
attacks.
And the third one is where you get
information leaks. Right. By and large,
those are the ones you would see when you
look for security, implementation, bugs
in drivers.
And so that's sort of the bylaws,
the kind of things that I'll talk about
specifically things that I'm not covering
are exploitation or bypassing
of mitigations.
For the most part, there's bits and
pieces where I'll sort of mentioned some
of that. But by and large, I'm
working from the assumption that bugs are
going to be exportable,
if that's what we're looking for.
I mean, this is an exploitation talk,
which is what I pretty much start off
with saying.
So I won't be focusing on
exploitation. I will instead be focusing
on sort of finding identifying
these type of bugs and sort
of developer guidance on how to fix
those kind of issues.
The other thing is that I. I'm assuming
that you guys have a basic understanding
of, you know, native code security,
so I, I won't discuss, you
know, the standard buffer overflow
or integer overflow or, you know,
what it means if you haven't unveiled it.
Lynnfield, I'm assuming those things are
understood.
Same thing for, you know, C++ type of
bugs or those kinds of things.
So just because I said I was going to
cover intro flows, I lied.
I am actually going to say something
about them specifically because
in the Windows kernel, you actually have
these APIs to help you
not have of those, they'll they'll have a
set of APIs you can use.
And if you are driver developers,
you are encouraged to use them, even
though I haven't seen them all that often
and you should in fact use them.
But when you use these APIs,
you should sort of there is
still potential for misuse.
And basically when you use the APIs,
there's sort of the three things you
should always or never do
is always take your term value, always
passing the exact type and
always you never
do it when path parameters, because when
you do any of those three things, you're
basically sort of reintroducing your
initial issues when you're trying
to use these new APIs and have examples
of this stuff.
But for in the interest of time, I'm sort
of just going to return really fast
basically to stuff on the left is wrong
and the stuff on the right is correct.
So there's nothing left to sort of see.
Here's how to use the API is wrong and
here's how to use the correct use of the
APIs.
OK, so basically now I come down to sort
of what it it is, I sort of
sat down and sort of, you
know, looked over, you know, what most
common screwy bugs are in drivers
and sort of try to figure out
if, like, what's the common
theme around all this? And they came up
with this idea where there's five
things that almost that most drivers
will do if they're
talking to use land where there's
potential for attack surface and
we have some kind of entry point.
And basically it comes down to
when you create a device, when
you talk to your manager, when you handle
data, when you
interact with the memory manager, and
when you interact with the object
manager. By and large, those
five areas,
a driver will if they talk to
user, those are the five big areas
where you have attack surface.
I mean, there's it's not you know, I
mean, there can be other ways to.
But by large, it's my
view it's it's those five.
And so basically I sort of
structured my next set of slides around
these five areas. And for each one of
these five, basically,
I'll discuss a few things that I've seen
gone wrong and do.
And so all of the first set of slides and
a cute little icon on the left top
that indicates this is a bug and then a
cute little icon with some tools that
says this is a fix, but
biological by these five.
And if three twenty five, there's a
couple of cases that I cover.
So with that, let's start with device
creation. Right.
As I showed earlier in the.
And the sample driver
is when you create a device, you
basically have to API calls,
which is IO create device or you create a
device secure and your driver basically
goes to jail and says, please create
a device for me.
And when you do that,
you can pass.
You can say, OK, these are the access
controls that I want on it.
And there's two ways of doing that.
Right? One is you call, I'll create
device secure and you
can passerelle what's called
Eskdale String, which is
a string representation of a security
descriptor, which basically says who
can and can't access the device under
what conditions?
If you don't call, you create device the
other way, do it is the you
specify the SDL string
and design file that comes with your
driver and
then that will be applied
by your manager when when
somebody tries to connect your device.
Commonly, though, these things are
missing, you'll see a lot of drivers
that they'll call
just AOK device and they won't
specify.
That's the also, you know, if you fall
back to ETRE, you know, default
or sort of
an actual that's too wide.
This is just a common sort of simple
design problem that you see with drivers
that are thrown together too fast.
Basically, the IDs that you just sit
down to sort of try to figure out who
needs to device and under what
circumstances.
And I mean, it it has value
to sit down and really think about this,
because what you're doing is you're you
can try to reduce cruel tax surface.
Right.
And so one good, you know,
proper engineering example would be to
sort of sit down and say, OK, well,
we have to set up high schools that
really an admin should only ever need to
do. So we're going to do is we will
create it will create a driver that is
only accessible by admin.
And then what we'll do is we'll create a
service on top of that that you can and
talk to through UDP
or whatever.
And then they have the service sort of
reroute things to tell
you what it needs to.
So in that example, you know, the
the downside of that is that, you know,
you have to write more code and you may
get some performance degradation.
But the upside is you get, you
know, extra security and probably,
you know, better reliability as well.
So that that that's one solution.
But the idea is basically that
this is this is usually a design
question was like, OK, who really needs
access to our device and then and
why write?
OK, so with that said, let's move
on to some more important stuff for
device creation.
When you create this device, you call
this AOK device API, and
basically you're allowed to pass these
device characteristics into it.
And it basically there's one bit in
there, which is this thing called Phyll
Device, Secure, Open and
has very special meaning.
So basically, when you create a device in
the windows call by default, your
manager will always assume that it is
filesystem device.
And what that means is that, you know,
when you open the device, the
manager basically assumes that it has,
you know, directories and files.
And so if you do, instead of you open the
files, you say device file,
I imagine looks at it and says, oh, I
don't have to apply.
The ackles here to filesystem has to do
it. I'll just go to the custom device and
let the first device handle it.
And that works really well if you're a
filesystem driver.
But if you're not a fast driver, which
most drivers are,
so if you're not a fast driver, all
of a sudden you have this problem where
your manager thinks you are and it
doesn't it won't apply your ackles
and the hands it off to you, but you're
not checking your meter.
And so all of a sudden, if you
don't set this particular flag and you're
not a filesystem driver,
you have to sort of bydesign automatic
echo acall, bypass problem.
And so that's why pretty much
every time you create a driver
and you're not a fast and driver, you
should really, really set this flag.
And it's bizarre because it's actually
documented and there's a lot of a lot
of drivers that are not doing this.
And what's really funny is that this bug
is incredibly, incredibly easy to find.
There's a tool out there called Device
Tree, and it enumerates all of your
devices. And you click on it and
you basically it allows you it's kind of
hard to see there, but you can basically
see who see what the Ackles are.
And then it also says, what are this?
Files of obscure flag is set.
And so if the flag isn't set
and Ackles basically say, oh, this all
the admin, then,
you know, you most likely
have a security bug right there.
And it's like it's one click away.
It's easy to find.
So what's the fix for this one?
Basically always used the flag.
Unless you are very specifically
building a some driver,
you should always, always set this flag.
That's the general rule.
Now, some people go, well, you know, you
don't have this flag.
You can sort of make your own
create dispatch, call back and
sort of do it in there. And and, yes, you
absolutely can.
You probably shouldn't because, A,
you're now adding more attacks surface,
even though this functionality is already
there. And Windows Squirtle just
set that one bit and you get it for free
too is if you do
do it yourself there there's
a few intricate sort of little things
that you want to try and mimic
exactly what your manager does in that
case.
Otherwise, you know, you get these sort
of, you know, sort of discrepancies.
So, yes, you can't do that.
But unless you're doing a fast driver,
probably shouldn't.
Yes, that's one of five, so the second
second of the five I had is basically
so working with your
manager and the first case I had is sort
of a simple one. But this is this
is a pretty common one.
I've seen in a bunch of drivers where
basically you'll have some like
I will call back and it will be
out direct and
it'll say, OK, well, we'll use the
MDL address.
So we'll sort of get our
due to get systematize from the safe
and we get our kernel pointer out of it
all without ever checking if that pointer
can be no, it can be null
by, you know, by sheer
the fact that the way it works is that
that pointer can absolutely be null.
And so before you ever touch and address,
you should check it for being an opener
at this one in a case where it could
happen as if it's a zero sized buffer.
If you're not doing that,
then you may very well have
a bug check or or worse.
And I'll talk about that later.
But the idea is that if
you're doing Adric,
check your embryologists before you use
it, which is to
fix great sort of second
sort of problem.
Yeshe in the manager
class is
using that Bufford.
I discussed Method Bufford before
and I said it is the sort of safer option
and it is.
But there's still something you should
know is a is similar with the
email address. Is that the pointer
you are given from the driver
spectrum in Europe
for Bufford is this
social system buffer
and the problem is similar to the email
address where that point can also
be null.
So the first thing you should do is
always, always check, make sure it's not
know.
The other thing, Whitmont, a Bufford, is
that even though it is the safer
or I guess the safest method
of 04 is that
because it's seen that way, you get the
sort of false sense of security
in that while, you know, Proby
and capturing of the whole buffer happens
for you.
Any and all of the content inside of that
buffer still needs, depending on what it
is, still needs any kind of validation.
Right. And so there's a set of developers
that'll say, oh, I'm at a Bufford, cool,
we're good. We don't need to end security
checking.
And then, you know, they'll have some
kind of a better Lynnfield that are never
validated and used to copy stuff around.
And, you know, all of sudden you get
memory. Corruption's left and right.
So, yeah, the idea would
buffer is that a validate for null
and B, all the embedded content
still has to be validated.
One of my one of my all time sort of pet
peeves or criddle bugs, is this
basically once let's
say you get octal.
And you do all the work and everything
and you're successful in the actual was
completed correctly and you're about to
sort of return and you you
basically there's a field in the in
Europe that says your status and
you basically say, OK, I had status
success.
And then there's a single I use that
information and information.
It's basically an integer.
And in the case of success, what that
means is I have completed successfully
and these are the amount of bytes that I
give to U.S. output.
And basically
the quite often what people will do
is they'll go to
I old status, that information, they'll
say, OK, whatever
output buffer linked that the user gave
me, that's that that's
the number I give you.
And sure enough, you work, you do that
and everything works. Nothing crashes.
You get old data you need and everything
works perfectly.
But there's this very subtle thing where
basically all of a sudden you have this
information leak.
So basically what happens is when you do
when I octal and you go to your manager
and you have better Bufford, the manager
looks at your input length
and you output length and it says, OK,
which one of these two is the maximum?
And it says, OK, let's say the
output length is is the bigger one.
It goes and says, OK,
mallock of kernel buffer of
that amount of data. And then it sees
what is the length of the input buffer
and it says, oh it was X, great.
We'll take them with Bufferin, we'll use
that length and we'll copy it into this
buffer. I just locate it.
So if you have an input buffer that is
smaller than the output buffer that the
delta between input and output is
initialized memory.
And if you now have this bug where at
the end of the day you're done all the
way to the end and what you've copied
into the output buffer is smaller
than the total allocated
space that is available,
then that that Delta all the way at the
end is just like memory.
And so if you then go and say,
you know, IO status,
that information is, you know, the full
buffer length, then that includes that
piece of uninsulated all the way at the
end. And that gets copied back to
usually. And and then when you it gets
the buffer, they can just read that
stuff.
It's kind of subtle because stuff
doesn't crash, stuff doesn't break.
You have to really look for it.
But quite often when you have these kind
of bugs is you can you can rig this so
you get really large buffers.
You can say, I want a one or two gig
buffer and all of a sudden you get just
piles and piles and piles of unrealized
data that comes out of Kerlin contains
all sorts of interesting things.
And so this is I reverse out of
a driver, but it's it's a fairly common
bug to see because you can test
all you want. This thing will never
crash. It's it's kind of subtle.
So unless you know about this explicitly,
you people don't find it either in
testing or for looking
for security bugs.
But it's basically the common case where
you you start your your
dispatcher and you say, OK, this
is the flight user gave me, and then you
do all the work and then all the way at
the end when you're done, you say, OK, I
was that information is we start off
with the apple before linked and bouffe
off we go.
I have an old son, Distin just leaked
information.
What's the other thing that's really
interesting about this one is.
Even though this is a WDM thing,
and even though, Kim, you have to sort of
build on top of this in
Cambodia, if you still
you can still have this particular
mistake, you don't actually
manually fill out the members of the
group. But Kim
gives you a set of opacity where you
basically just pass parameters.
So it's WDEF request, complete with
information, request information.
Both of you fill out just IO status
field.
And, you know, if it's too
large, you have the same problem.
And because Kim, you have is open
source, you can just go look at source
and see what these functions do.
And indeed, if you look it up and you see
what these functions do, you'll see that
internally they still look up to Europe
and they go and say biostatistician
information and the links you give it.
The fix here is is simple, I
mean, it's a little bit
hard to figure out initially because
the bug itself is so subtle that, you
know, because nothing crashes, nothing
ever misbehaves.
It's kind of sold that way.
But once you're aware of it, the bugs are
really easy and it's incredibly easy to
fix. Basically, you know, any
time you get the information, you
should just be exact.
If you copy five bytes there, then
say that, right.
Always recorded about a batch put in
there and then just make that your ISIS
on information and you never have that
bug again.
The thing is, though, you
have to be consistent about it.
And that can be pretty hard,
especially if you're reviewing old code.
And so it's really easy to miss one or
two cases.
That is, you know, that is still lying
around code that was written 20 years
ago.
OK, so
the last part about your manager that I
want to talk about, and this is sort of
in broad strokes, is about her
cancelation and the reason why
it is I
talk about it broadly, not specifically
because it's incredibly complex stuff.
And I could easily feel, you know, a one
hour lecture, just about Earth
cancelation stuff.
So basically what happens is
when, you know, usually it doesn't.
I actually think it comes to the driver
and the driver says, OK, well, this is
a told that I can't complete right
away. And so what I'm going to do is I'm
going to go take this URP and spend it
and I'm going to go back a year later and
say, hey, come back later.
I'll give you a signal later.
Once I'm done, you can go do something
else now.
And then once the driver is done that
says, OK, well, this is now completed
and then, you know, signals user lands
and says, hey, I'm done.
That time in between is undefined.
Depending on what the driver does
supposed to do can possibly
be a long time, for example, something
that's disc based.
And so basically, at any point
after the to
suspended and usually knows that the
kernel sort of pended it and is working
on it, usually it can go back to the
kernel and say, oh, hey,
you know that driver that's holding my
URP, I'm done
waiting. You've taken way too long.
Just cancel the damn urp.
And then basically the driver sort of
gets woken up again, finds Europe and
cancels it.
And the problem with this is that there's
this potential for all these, like, race
conditions, right. Where you have one
thread that's still trying to work on the
European. You have another thread that
has been instructed to cancel it.
And so you have one guy working on one
guy canceling it.
And if they're not in perfect sync,
you have all sort of these weird race
conditions.
And so that stuff gets incredibly
complicated, and so
there are literally dozens and dozens of
examples of what this stuff looks like,
and I wish I could get into it, but I
can't. But I will say, though,
that reconciliation routines
that have segregation issues often lead
to deadlocks, memory leaks, race
conditions, double freeze, muzaffer
freeze. So all the stuff that, you know,
if you're a security guy looking for
bugs, that you start drooling when
you hear those things, they're all in
there.
And I really wish I could get into
detail.
And so
fix wise, it's kind of there's
no easy advice to give.
The stuff is just extremely complex,
and especially when you do or
cancelation.
Be very careful, be extremely
conservative.
Implement would care,
you know, double check, triple check,
have it. Peer review, check again.
You know, try to use
Cassol safe urp cuz
that's pretty much it.
Unfortunately there's no better
advice.
OK, so now we've got those two down.
Let's move on to use
land and data pointers.
And so I guess we should first talk about
the elephant in the room,
which is basically, you know what if
a driver takes, you know,
direct pointers from user lands,
what you're supposed to do these things
called, you know, probing, which is
basically a fancy word for
is this pointer within the range that
usually is allowed to be
and including the length and
then the end pointer does not overflow at
all those things. And that's basically
what the pro free and appropriate
functions do.
Mm hmm.
OK, there you go.
So basically, you know
it if you take someone's
land, you're supposed to do this kind of
probing if you don't probe.
So let's say you take a pointer from New
Zealand and you forget the probe
and you read from it.
Basically, that's potentially information
leak.
You could be reading from anywhere and
you could definitely crash.
And depending if that information gets
sent back to you land, you could
potentially info leak.
So that's bad.
But if you get a point of Friesland and
you don't probe and you write from it,
you basically get this sort of right.
Anything, anywhere primitive and colonel.
And that is extremely bad.
You basically just want to curl.
You do that.
And so basically to try to prevent these
cases, you do
probing and use appropriate approach for
IDP's.
And so this is this is one example, I
think this is some Intel driver
where basically, you know, that
you take an input buffer and then
basically you
there's no probing anywhere in between
and you just sort of reading right from
it.
But suffice to say, these bugs,
unfortunately, are extremely, extremely
common.
Another interesting sort of nugget
with this stuff is that and
this I think this was noted a few years
ago, is that so these is basically
the way they work is you say, OK, here's
why I use land pointer.
Here's the thing they want to throw for
and here's the alignment.
And you can have these very subtle bugs
where people do probe.
But for whatever reason, the links
given to the probing is zero.
And when the APIs basically go,
those processes basically say, OK,
we'll have the link to zero short circuit
logic return success.
And that is perfectly valid.
You know, if I say a probe of a
probe of zero, then I mean, you know,
clearly that zero.
The thing is that there's a little bit of
risk here when you when that's being
done. Basically, the idea that if
you probe with a link to zero and
then later on you basically
use that address, but then you read or
write one byte.
Obviously the bug there is that you have
a link discrepancy between zero and one.
But the difference is that because
the way the probes work, this discrepancy
becomes incredibly exploitable.
Right. If the API is at work,
worked away where they said, OK, well,
the link is zero. What we're going to do
is we're going to make the link one and
we'll just probe that particular address.
It would make these bugs far less
exploitable.
And so that's why that's why there's
risk here.
The common case for where these kind of
bugs could happen, where you do have a
probe would link to zero is a
you'll you'll see this code where
they'll take, they'll take they'll
take like a buffer length
and it put buffer and they'll just assume
it's of a certain length. And they never
actually double check what the link
really is. But they still passed the
original input buffer linked to the pro
functions.
And so if you forget that kind of link
check, you end up with these exact
kind of issues.
The other one is where if you do link
checks, is that you may still have like
little intro flows where the probing link
just happens, overflow right back to
zero.
So so that that could happen to you.
So the fix basically is
performed, correct? Consistent, probing.
Always, always, always probe.
It's easier than it looks.
You always forget one somewhere.
It's where these things were.
Once you start like if you if it's
all in one simple function, then that
tends to be easy. But you'll have these
things where you have these complex
drivers, where it calls a function that
calls function, that calls a function.
And somewhere down the road you're not
quite sure if it's user pointer or a
kernel pointer.
And then, you know, you end up
missing a probe.
So the second sort of variation of this
is, OK, let's say you take a kernel,
you take use of water in your driver
and you've not probed it and then you're
going to start using it,
you can still do things that are horribly
wrong. So every time you use a
pointer, a usually a splintering kernel,
even if it's properties that you must
always wrapped up in a tree, except
the reason being is that if you don't do
any try, except is that
after you after it's been probed, you
could have a second use land thread that
takes, you know, your maps address and
basically changes the page projections or
unmap stem and then the
somewhere in critelli after the probing,
they go, oh, well, we'll just use these
pointers and all of a sudden they're
reading or writing to unmap pages,
which will throw an exception.
And so that's why you must always do a
try, except.
So the obvious key is to forget
exception handling,
but in the less obvious case, as
is, maybe you do have to accept case.
But the thing is, the you know, except
cases rarely exercise because,
you know, it's the exception, right.
It almost never happens.
And as such, these bugs
in accepted handlers
often don't show up in testing simply
because it's never been exercised.
So even if you
do accept what usually pointers,
it's still quite often to notice things
like memory leaks and reference
leaks
that sort of fixed here basically is
a use try except when using
using Pointer and B is.
Make sure your exceptional logic is same,
except your logic can be really tricky,
and so you want to design implement that
stuff with care.
Sort of related to
you, that point is what they're called
double features, so the idea basically
is I'm usually
and I go call to a driver and say, OK,
here's usually the pointer and the driver
takes the pointer and it does the probing
and it does try, except it does all that
stuff. Right.
But we'll take a pointer and he'll say
let's say it contains an embedded
Lynnfield and well, to lose, they'll
dereference usually the pointer and
they'll make sure that Leitchfield is
Valot. It's got to be, let's say, I don't
know, bigger than 10 and smaller than one
hundred or something.
And then sure enough, that's true.
And then right after that they'll drop
that pointer again. They'll take the
Lynnfield and they'll use it to do
something else.
And the difference is that there's a race
condition here
where basically
between the first check and the
second check, you know,
because I could have a second Suzlon
thread that really overrides that memory
so that when you check it, it's OK.
And when you start using it, it's some
totally different value.
And this is just a simple example of what
that could look like, where basically,
you know, you have some some some
functioning kernel where you pass it,
you know, some
some user pointer
and use a pointer will
contain some and better Lynnfield.
So in this case, it's got to be, you
know, smaller than hundred.
And then, you know, they use that
Lynnfield to copy into a local stock for
that 100 bytes.
And so between that check and read the
first time and the use in the
in the in the copy memory,
you can have a second. Usually try to
overnight's overwrites Stillings.
And basically, you end up with memory
corruption and
this is an example of a bug
that is a double and triple fach that I
found in the security program a couple
of months ago.
And I have a link to the advisory below,
if you will. Look at it, the fix for
this kind of stuff is basically capture
data, validate and only then do use,
never fetch again, keep the cap,
keep the data you captured and validate
that and use that.
Similarly, for four years and that stuff
is basically
sort of new, the references I
considered the same class.
It's not specific to Windows
drivers. I actually spoke about this
particular bug NCC conference
here in 2006.
I was talking about Lisker all the time,
but it applies just as well to
the Windows kernel.
And basically the only difference in sort
of the in trying to trigger a mapping
to the new page is in
Linux and Unix to basically do a map.
And you say, you know, you say fix
and you just give it an A.
and it'll go map it in windows.
It's slightly more tricky where basically
if you try to map the attritional,
it basically sort of goes like, oh, no
means you don't mean an address while
it's looking for you.
And so the way you're supposed to do it,
as you say, no, the address is one.
And then what it does is it roots down to
one to a zero.
And then it goes to maps, no page,
but it's essentially the same thing.
This used to be a much bigger deal as
of Windows eight mappings
of the new page or disallowed,
which isn't.
So that's what that's what they'll tell
you. And it isn't entirely accurate.
The 64 bit Windows mappings of no page or
disallowed on thirty bit Windows mappings
of the new page or disallowed by default.
But there's this thing called the a.D.A,
which is the virtual DOS machine.
So if you want to run all doors cames,
for example, you have to turn this stuff
on and all of a sudden you can have no
pointer references in your old DOS games.
And those would then allow you to explore
new point references in Windows
drivers, essentially.
But even if in this if this
sort of little Corta case
doesn't work and then we take
the premise that no map, no mappings
can't aren't allowed in Windows as
Windows eight, there are still some
exceptions where no pointers in kernel
might be exploitable.
So well as if you have null plus
a large offset, what you offset basically
becomes your pointer.
A common case of this might be where?
You have a copy of a reverse them
copy or where you pass to a function
as special meaning.
Yeah, the fix is basically defensive
programing, right? I mean, we all know
about pointed references there, nothing
special.
OK, let me I'm just going to skip this
because I'm running out of time.
OK, so memory related bugs.
This is number four out of the five I
had.
Basically, if you're
a Windows driver, there's two
sets of APIs you do to look at memory.
It's the locate pool and locate
pool with cuota.
And basically there's a discrepancy
between the two where one of the APIs,
when it fails, returns null and the other
API when it fails, throws an exception.
So if you mixed up one with the other,
all of a sudden you're checking for
value. But you might get an exception
or you're you have an exception handler.
But what really happens is you get a no
pointer back, so
you have to get those straight.
And I believe one
of the the pull would, quote, actually
allows you it has an option where you
basically go to it and say, hey, instead
of throwing give me a null,
I would recommend using the API that way
because then you have consistency.
But that's one of the ways you could say
you can go wrong using these APIs.
There are two
other things. One is basically so
the allocate pool with Cuota is
basically done so that any time you do an
allocation in kernel
based, driven by user
land, you're supposed to do allocations
based on the quarter after you land
caller. Right.
So in my view, any time you do an
analysis based upon
driven by user land, you should always do
the quarter ALAC.
If you don't, I think that's a bug.
And then, of course, you know, the, ah,
simple case, right, where obviously, yes,
you know, if you don't check in for
value, yes, you may have no references.
If you don't handle exceptions, yes, you
may have unhandled exceptions.
And even if you are, you could have
faulty exception, logic.
You know, these are pretty simple.
You know, fixed wises like.
Yes, check values.
Yes.
Charge quartet when you have to.
Yes. Cap Buffer's.
Please don't have unbelted Alaric's.
So the second sort of related issue, and
this is again, this is new as of Windows
eight, and this is mostly a mitigation
type thing.
So by default in Windows, when you used
to do a memory allocation,
the pages you would get back were
executable as of Windows
eight. That is no longer true.
So there's a possible pool
for the pool
by default.
They are now not suitable for
an office pool. They still have to be.
And so they came up with this new thing
called a nonpaid pool, not executable.
And the idea is that anytime you do
nonpublic Alok nowadays
is that you specify this particular
pulled it out page an ex, unless
you really need
executable, not painful memory,
which is not impossible, but it's
pretty rare that you actually need it
also be executable.
And in that case, sure, go ahead and use
the non-paper one.
But in any other case where you eat not
painful memory and you don't need to be a
shootable, you should really use
this flag.
It's not a bug per say by itself.
It's really sort of, you know,
this this does not
to let people is really mitigation
reputation. And so the idea is that if
you use this incorrectly, you're kind of
killing in mitigation.
Um.
And so basically
the idea is that, you know.
Welcome back.
The idea is that you
use the numbers baseball, and
if you're going to do things, if
you're going to do not be specific
allocations.
So the next memory bug
story, there's a API call that
gets us at us from Gizelle, which is
basically if you get Namgyal, how do you
get it mapped in your refrigerator space
to curdle this old API?
Basically,
if you call it, there's a risk of if
if it can't map it, what it does is
it causes a kernel panic.
And so there's a new API called Get
Systematized for MDL safe, and
that one doesn't cause a
kernel panic.
And so obviously you use a new one.
If you see the old one, that's a bug.
And while this seems easy, there's still
a fair amount of drivers that use the old
API.
And I start thinking about it.
And I think I mean, there's there's two
reasons why you would still have that if
you have an old code base.
But I think B is if you're
developing based upon
driver books and document basically
books that basically
tell you to use the old API because
the book was written before the API
existed. And there's actually there's
plenty of most of these kernel driver
books are relatively old.
And so they advise using that API.
And I think that's why these bugs are
still around.
Um.
Yeah, so this is basically what I was
talking about earlier, right, when you
have these embryos that are used to
create a mapping between user and kernel,
all of a sudden you end up with these
double features again, right where
let's say you have like an apple that
comes in with a method indirects
and you you take it, you get
your kernel point out EnerDel and you
just sort of using like embedded
Lynnfield
then.
Not realizing that that memory can be
changed any given time by yourself.
So you end up with all of these sort of
double bucks again.
The thing is, they're not quite as
obvious as the ones that directly from
the ones I showed earlier, because you're
dealing with a coral pointer.
So if you just look at the point, you
would assume that
usually doesn't really have
control over it.
And so that's why they're not as obvious.
This is a very simple sort of example of
what that would look like. Right.
So you have Namgyal you say, OK, it's
better safe, and then you get your data
back and then you say in but at length
and then you're going to go, OK, we'll
use we'll use that
a bit at length.
But because it comes from Namgyal, you
can have a second use that thread that
just changes your embedded length after
validation, but before you use.
Oh, did I?
OK.
I'm going to skip over this because I'm
really close to running out of time.
So I think I have
two more minutes. Sweet,
let me get to the first part
of your calendar, basically.
So when you talk to
Colonel and basically says, OK,
here's some handle to some file, go
do something with it, the way
the colonel goes, says, OK, well, I'm
supposed to how do I how do
I translate this handle to a colonel
point I can use?
You call it a function called a reference
object by handle, and that basically
translates one to the other.
And the API looks like this
and it has distinct object type and
access mode and basically object
type says.
And for that it is that the object
that the handlers of this particular type
and windows has about 40 something
different types of objects.
And so you can you if you
take a handle from user and kernel and
use this API and you say
object type is no, there's no type
and force and you get all these classic
type confusion bugs.
So that's one to the second
bug you can have here is basically where
if you access mode, you're supposed to
specify user.
If you specify, Colonel, that all of a
sudden use land gets to give you
handles that are really kernel handles.
And so because handle numbers are really
not secrets, they're easy to brute force.
And all of a sudden, if you say XML as
kernel mode, user olsun gets
to tinker with your kernel handles.
Oh, OK.
Another thing with this kind of object
stuff is that once you dereference and
you're done, you're supposed to
dereference and then you know, you're
done with handle. And so quite often
you'll have these bugs where
basically like let's say you
have some kind of exception that gets
thrown and you forget the leak, you
forget to do you have a handle
and also just handle leaks so
that this is a no sort of accounting
county bug except a problem with this.
If this is repeatable
most like you, let's say you
through two bits of this, if you can
repeat, is about four billion times, you
can overflow to zero.
And all of a sudden you have a.
I've got.
Awesome, perfect.
So if if you're in all
of a sudden it goes zero, you object,
it's freed, but you may still have
reference to it. And also when you get
these these use after free cases,
as of, I think, Windows eight,
there has been called attitude
of reference calls where basically
they the your flow, when an interval
happens, they cause
a kernel panic.
And so all of a sudden these bugs are no
longer exploitable beyond the leak,
which is great, but up to one seven.
That was a little problem.
Related issues are a double Refco
increase use after
reference at a passing of no
and the fixed basically always
BALSHAW reference calls.
Uh.
And I'm done
with.
Well, unfortunately, we do
not have time for questions.