Thursday, October 31, 2019

Hunting for filesystem bugs

Hunting for filesystem bugs

I. Introduction

Filesystem bugs have been fairly rare, until recently.
This is mainly because prior to James Forshaw's work we didn't have the tools to exploit these types of race conditions in the filesystem.

Forshaw has documented many ways to exploit filesystem bugs.
Well known examples are:

1. Directory junctions
2. Hardlinks
3. Object manager symlinks

There's a couple more, for a full overview I suggest reading all the blogposts by Forshaw and his published PoCs.

This video is particularly useful and I recommend watching it:

II. Finding filesystem bugs

This is just my way of finding bugs, and there is definitely better and more hardcore ways (i.e doing actual reverse engineering of attack surface where these bugs occur).

I mainly use one tool, process monitor:

Process monitor records all of the filesystem operations that occur in the filesystem (in usermode.. it doesn't work as well from kernel mode.. sometimes handles get opened through the system process.. but I think for tracking filesystem race conditions in the kernel it would be better to write your own hooks or something).

My two strategies:

1. Look at existing PoCs. If there is one bug in a piece of code, that is usually a good indicator that it's old code and that it's going to be prone to more bugs. Sometimes you can find new bugs or bypass a patch by just messing around with a PoC. One thing that I often do is to try and add more "complexity".
Examples of "adding more complexity" are deleting files and folder, because that might trigger a different code path when files and folders suddenly don't exist. Other things I do is modifying the ACL, such that the user doesn't have write access to it.. this could result in the higher privileged process to drop impersonation (I actually found bugs like this).

2. Apply a bunch of filters to process monitor and just go exploring. The trick here is to come up with ways to trigger code that people havn't looked at before... so you just start doing crazy stuff. Things I would try in the past is running all the tasks in the task scheduler, starting services, etc. Just do stuff! Try to think of obscure areas where people havn't looked before.. this can be hard, but just google things, look at msdn pages (i.e there is a looooot of stuff people havn't looked at yet. Also, you can find a lot of code samples in the windows sdk, just start running them and messing around with it!

Filters that I commonly use in process monitor when "exploring":

-Integrity is "Medium" then exclude (unless looking for appcontainer escapes)
-Intergrity is "Low" then exclude
-Detail contains "Impersonating: DESKTOP-L7TMINM\test" then exclude (modify the user to match yours)
-Path contains "c:\windows" then exclude (but you might want to include c:\windows\temp as it's user-writeable)
-Path contains "c:\program files" then exclude

These you will want to discard most of the time because they won't result in a bug.
Next I'll often start by including this:

-Operation is "CreateFile" then include

You can further filter things down by filtering out when it opens a handle for things like "Read control" etc. Once you see an interesting file handle being opened you deselect the "CreateFile" include-filter and look if anything interesting is done with the filehandle.

III. Types of bugs

I roughly explained how to set-up filters in procmon, but now you need to know what to look for.
Most of this comes by experience, so this is really the hardest part to master.
Some vulnerable cases are this (excluding hardlinks as those are mitigated):

Arbitrary read:

Contents of a file from a user write-able location are read and then written to another file.

You can exploit this by making it read an arbitrary file using junctions. So when looking at procmon, file reads can be useful when used to copy contents to a file and there is no impersonation on the read.

Arbitrary delete:

Tip: One way to quickly find deletes is by adding the filter "detail contains "delete: true" then include".

If a file is being deleted in a user writeable folder without impersonation then you can redirect this delete to arbitrary files using junctions.
There is a small detail here. In the past you would need to have either control over the filename of the file being deleted or have a case where it just removes all the files in a folder.

However, these guys came up with a way to delete specific files without having control over the filename:

So basically if you have a bug where it will delete c:\blah\bear.jpg, because even if you turn c:\blah into a junction pointing to c:\windows\system32 it will just try to delete c:\windows\system32\bear.jpg. What the guys from the article above discovered is that if you turn c:\blah into a junction to the object manager so that it becomes c:\blah -> \rpc control and then plant a symlink in \rpc control named "bear.jpg" pointing to for example c:\windows\system32\drivers\pci.sys you can have it delete arbitrary files. This trick can also be used with arbitrary writes/creates where you don't control the name. I think this will be patched soon.. as this is pretty bad.. I spent a lot of time looking at procmon and I can promise you that this trick can be used to abuse a shitload of bugs.

Arbitrary file writes/create:

If you control the location of a file create and/or write, you can turn this into a junction and have it create a file else where. Or.. write to a different file.

Things to consider here are:

-Is there some way to control the contents being written?
-Can I perhaps have partial control over the contents being written?
You need atleast some control over the contents being written. Just the ability to create a file in an arbitrary directory isn't going to be useful, unless that file is created with a permissive ACL or you can control its contents somehow. (You might want to reverse the function that does the filewrite, as it could reveal more about where it fetches the contents being written)

Arbitrary directory creation:

If a directory is created in a user-writeable location, it would be interesting to see if the ACL is written too. You may be able to create a directory in an arbitrary location and then have it given a permissive ACL that lets the user write to it.

IV. Conclusion

This bug type is super easy to find and exploit. The difficult thing is knowing where to look. But knowing where to look mostly comes down to persistence and just trying things until you find something.
This blogpost was written in a rush and I might change things later lol.