How I Became a Security Researcher

Now this is a story all about how

my title got flipped-turned upside down.

And I’d like to take a minute just sit right there

and I’ll tell you how I became a researcher.


In south-eastern Michigan born and raised

on Office online spending most of my days.

Chillin’ out maxin’ relaix’ all cool

trying to get access to the application pool.

When a window displayed that didn’t look good

And gave me access to something under the hood.

I found one little bug and with a tiny bit of fear

Wondered, can I call myself a security researcher?

I emailed Microsoft and it became clear

The XSS I found was definitely something to fear

They replied and confirmed this bug was theirs

And they ended the email calling me a researcher


A patch Tuesday had passed and then did another

Finally I saw my bug with its own CVE

I looked at the web page

My name was listed there

I am now considered a researcher!

To be clear, I don’t consider myself a security researcher, rather I am a programmer who happens to dabble in security research. Maybe this is a little bit of imposter syndrome kicking in, but I really think I just know enough to be dangerous.

A few months back I learned of the Online Services Bug Bounty program Microsoft was offering. I decided to give it a go. I am always looking for authorized hacking opportunities and really wasn’t expecting anything to come of it. I created my test tenants and started playing around. I decided to focus on XSS vulnerabilities, as that is something I am pretty comfortable with and is pretty easy to test client side. Whenever I have tested for XSS vulnerabilities, I have always found that dialog windows tend to be some of the biggest culprits, so I honed in on those in my test tenant.

Eventually I stumbled across an OWA dialog that wasn’t escaping strings. I was easily able to generate this (URLs hidden to protect the innocent):

And then with a little more tweaking, this:

It was clear I had found an XSS vulnerability. I followed the submission instructions on the bug bounty page and waited to hear back. Within a few days, I got an email from the Microsoft security team indicating they had forwarded the bug to the product team. About a week after that I received confirmation that the product team had reproduced the bug and were working on a fix. Here is the timeline for the fix:

Oct. 9 (Thursday)   - Reported Bug
Oct. 13 (Monday)    - Microsoft confirmed receipt of bug 
Oct. 21 (Tuesday)   - Product team confirms repro of bug
Oct. 29 (Wednesday) - Product team confirmed that bug exists in boxed products
Dec. 9 (Tuesday)    - Official security bulletin released.

Since this bug impacted user installed versions of exchange, it took a bit longer for the product team to fix. I didn’t continuously test the online URL to know exactly when it was fixed, but I know as of today the vulnerability is fixed.

The bulletin for the bug is MS14-075.

The Microsoft team has credited me for this fix in multiple locations:

Creating a Windows Keyboard Hook

Every so often I have an app where I want to listen for global keyboard events and respond to them. I have had this code sitting around for a while and finally got it up to github, so anyone can use it as they see fit. It uses a few Win32 APIs to listen for the keyboard events and then fires events to let the caller know something happened. The Win32 calls are:

[DllImport("user32.dll")]
static extern short GetAsyncKeyState(int vKey);

[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags,
   int dwExtraInfo);

Then I simply have a background worker continuously checking the GetAsyncKeyState of the keys that we are monitoring. When we hit a key we are monitoring, we fire the event, so the consumer knows the keys were pressed.

foreach (int iKey in watchCodes)
{
    short ret = GetAsyncKeyState(iKey);
    if (ret != 0)
    {
        keybd_event((byte)iKey, 0x45, KEYEVENTF_KEYUP, 0);
        this.KeyPressed((Keycode)iKey);
    }
}

It’s pretty basic now, but it does provide some useful functionality if you need to globally monitor keystrokes. The way it is setup now, key combinations are not exactly easy to do, but that functionality could easily be added to the API.

Creating a PowerShell Navigation Provider: #4 Tab Completion

In the third post in this series we covered the basics of navigation. Now we are going to implement tab completion in the provider. While doing this may seem simple and a small task, it makes your provider feel more integrated with PowerShell.

The final working code for this project can be found at the github depot under the TabCompletion tag.

Adding Tab Support is actually pretty easy (as are most things in the PowerShell Provider world, once you know what to do). To start, we will modify our CmdletProvider attribute to include capabilities. We are going to add the ExpandWildcards capability to our provider:

[CmdletProvider("MyPowerShellProvider", ProviderCapabilities.ExpandWildcards)]

Next, we will modify our ItemExists method to properly validate the tag actually exists:

protected override bool ItemExists(string path)
{
    if (tags == null)
        return true;

    if (String.IsNullOrEmpty(path))
        return true;

    var itemFromTag = from tag in tags.Data.Items
                      where tag.Name.Equals(path, StringComparison.CurrentCultureIgnoreCase)
                      select tag;
    return itemFromTag.Any();
}

Now we will provide a helper method to convert a path from PowerShell into an array of valid tags. This method will handle the wildcards sent in from PowerShell, so it does some basic Regex parsing:

private string[] TagsFromPath(string path)
{
    if (tags == null)
        return null;

    var regexString = Regex.Escape(path).Replace("\\*", ".*");
    regexString = "^" + regexString + "$";
    var regex = new Regex(regexString);

    var itemFromTag = from tag in tags.Data.Items
                      where regex.IsMatch(tag.Name)
                      select tag.Name;

    if (itemFromTag.Any())
        return itemFromTag.ToArray();

    return null;
}

Finally, we will override the ExpandPath method in the base provider:

protected override string[] ExpandPath(string path)
{
    return TagsFromPath(path);
}

Simple enough, if you compile and run, tab completion should now work.

For example, if you go to your SO: drive and do a cd jTab, you will see that the ExpandPath method is called with a path parameter of j*. If you press Tab multiple times, you will see it iterate over the various options (.\java, .\javascript, .\jquery, …).

You could also use your own wildcard, so cd j*vTab and it will only iterate over the matches of java and javascript.

As you have seen implementing tab completion in PowerShell is just as easy as everything else we have done (if not easier), it is just knowing the correct methods to implement. At this point we have a rather functional sample provider. Sure, it doesn’t do much, but this should give you most of what you need to implement your own provider with whatever backend API you need.

For now, this will be the last of the navigation provider posts. If you find something that should be added, leave a comment below and maybe I will add it to the series.

Creating a PowerShell Navigation Provider: #3 Nested GetChildItems() Support

In the second post in this series, we covered the basics of acquiring children and outputting them to PowerShell. Next we are going to add some nested navigation and only allow users to navigate into tags that we know exist.

First some administrative tasks. I decided to remove my custom C# Stackoverflow API that was very minimal and use something more robust. I went with StacMan and added it to the project using the nuget package. I then modified the existing code and views to work as before. I also changed the drive name to SO, to make it easier to type and to make it more relevant. With that out of the way, we can now forge ahead.

The final working code for this post can be found the NestedNavigation tag in the github repo.

To start, I modified the tag acquisition to keep the tags around, so I will only load them once and so I can reuse this code where necessary:

private static StacManResponse<Tag> tags;
private void LoadTags(bool forceReload=false)
{
    if (tags == null || forceReload)
    {
        var stackOverflow = new StacManClient();
        tags = stackOverflow.Tags.GetAll("stackoverflow.com").Result;
    }
}

Next, in my IsItemContainer, instead of blindly returning true, I now check to see if the path sent to us is blank (i.e. at the root) or if the path is a tag in our cache. This allows us to navigate into a valid state:

protected override bool IsItemContainer(string path)
{
    // The path is empty, so we are at the root.  The root is always valid for us.
    if (String.IsNullOrEmpty(path))
        return true;

    LoadTags();

    if (tags.Data == null)
        return false;

    if (tags.Data.Items == null)
        return false;

    var itemFromTag = from tag in tags.Data.Items
        where tag.Name.Equals(path, StringComparison.CurrentCultureIgnoreCase)
        select tag;

    return itemFromTag.Any();   
}

At this point, if you ran the project and started navigating around, you would be able to navigate into first level folders that were valid tags that came back from the API.

Now that I have that working, I am going to modify the GetChildItems call to return questions for the tag folder you are in. To do this, I am going to simply check the path that is sent to the call and if it is non-empty, then query the questions API:

protected override void GetChildItems(string path, bool recurse)
{
    if (string.IsNullOrEmpty(path))
    {
        // Write the tags
        LoadTags();

        foreach (var tag in tags.Data.Items)
        {
            WriteItemObject(tag, tag.Name, false);
        }
    }
    else
    {
        // Write the questions for this tag
        var stackOverflow = new StacManClient();
        var questions = stackOverflow.Questions.GetAll("stackoverflow.com", tagged:path);

        foreach (var q in questions.Result.Data.Items)
        {
            WriteItemObject(q, q.Title, false);
        }
    }
}

At this point questions are returned, but again the output is hideous. Similarly what we did for tags in the last post, we will do for questions. We are going to add a custom view:

<View>
  <Name>QuestionView</Name>
  <ViewSelectedBy>
    <TypeName>StackExchange.StacMan.Question</TypeName>
  </ViewSelectedBy>
  <TableControl>
    <TableHeaders>
      <TableColumnHeader>
        <Label>Title</Label>
        <Width>70</Width>
        <Alignment>left</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>Creator</Label>
        <Width>20</Width>
        <Alignment>left</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>Reputation</Label>
        <Width>10</Width>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
    </TableHeaders>
    <TableRowEntries>
      <TableRowEntry>
        <TableColumnItems>
          <TableColumnItem>
            <PropertyName>Title</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>$_.Owner.DisplayName</ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>$_.Owner.Reputation</ScriptBlock>
          </TableColumnItem>
        </TableColumnItems>
      </TableRowEntry>
    </TableRowEntries>
  </TableControl>
</View>

One interesting thing to call out in this view is that we are using the ScriptBlock element to access nested properties (Owner Name and Reputation) on the question object. So now, if you dir your directory, you will see this:

Title                                         Creator              Reputation
-----                                         -------              ----------
C# Clearing a Table                           user3594691                   1
To list and without tolist                    myfinite                     27
EntitySpaces/C#: How to use a subquery in ... brad                         11
SQL query search for all the data by month... user3337393                   1
Unity control object via socket Udp c#        iboy15                        6
multicolumn combo box in asp.net              nomi ikon                     6
C# How can I start PhantomJS + Selenium wi... Tiago Castro                  1
Override Json Serialization RavenDB           Shea                        101
How to get &quot;Windows Phone Pivot Page&... n179911                    3017
How to write binary data to serial port in C# Stefan Vogel                  1
Can&#39;t get the last row of excel when u... Wen Ling Lo                   1
Position Window before created using shell... Mark Robbins               1113

Since we are in PowerShell, we get a lot of querying functionality for free, so you can try things like:

#questions from people with rep over 100
dir so:\c# | where {$_.Owner.Reputation -gt 100}

#questions with an answer
dir so:\xml | where {$_.AnswerCount -gt 0}

#questions that have a positive score
dir | where {$_.Score -gt 0}    

As you can see combining PowerShell semantics with data from an API can be an interesting way for your users to access your data. In our next post, we will look at tab completion, so we can behave similarly to other drives within a PowerShell environment.

Creating a PowerShell Navigation Provider: #2 Basic GetChildItems() Support

As you saw in the first post in this series, getting a PowerShell provider up and running is pretty easy. Next we are going to add some basic GetChildItems() support and provide a nice experience to your users.

A good starting point for this article is the GetStackOverflowTags tag in the GitHub repo for this project. I have added a basic StackOverflow API at this point that allows me to get a list of tags from StackOverflow ordered by the most popular tags.

After adding the API, I simply updated the GetChildItems() call to consume the API and return the object to the PowerShell window.

protected override void GetChildItems(string path, bool recurse)
{
    var stackOverflow = new StackOverflowAPI.StackOverflow();
    var tags = stackOverflow.GetTags().Result;

    foreach (var tag in tags)
    {
        WriteItemObject(tag, tag.name,true);
    }
}

Now, anytime you call dir, you get output that looks like this:

PSPath            : PowerShellProvider\MyPowerShellProvider::java
PSParentPath      : 
PSChildName       : java
PSDrive           : MyDrive
PSProvider        : PowerShellProvider\MyPowerShellProvider
PSIsContainer     : True
has_synonyms      : True
is_moderator_only : False
is_required       : False
count             : 669099
name              : java

Not very user friendly. You could use various PowerShell constructs to format the output like you want, for example, I could do dir | Format-Table -Property Name,Count to get the output:

name                                                                  count
----                                                                  -----
java                                                                  669099
c#                                                                    663666
javascript                                                            647161
php                                                                   603881
...

Instead of forcing the user to type that every time, we can modify our project to use a PowerShell manifest and a custom formatting file to get our desired output on a dir command. Create a new file called MyPowerShellProvider.psd1 and set its build action to Content and CopyToOutputDirectory to Copy if newer.

Copy the contents of the sample manifest file and update the following lines:

# Script module or binary module file associated with this manifest
RootModule = 'PowerShellProvider.dll'

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
GUID = 'AE0DA9B1-93DF-4959-9C97-BAA45D5F8EE0'

# Author of this module
Author = 'John Koerner'

# Company or vendor of this module
CompanyName = ''

# Copyright statement for this module
Copyright = 'See the license file for this repo'

Update your InstallProvider.ps1 file to point to the new psd1 file:

Import-Module .\MyPowerShellProvider.psd1

At this point, your provider should still work as before, but now it is loading the information based on the .psd1 file. Next we will create a custom formatting file. I am simply going to modify the table output to use the Name and Count fields. Create a new file named MyViewDefinition.ps1xml and set the Copy to Output Directory to Copy if newer. For the contents of this file, use the following:

<Configuration>
  <ViewDefinitions>
    <View>
      <Name>MyView</Name>
      <ViewSelectedBy>
        <TypeName>StackOverflowAPI.Tag</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>25</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Count</Label>
            <Width>10</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Count</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

Some of the important bits here are that we set the TypeName node in the ViewSelectedBy node to our type that we are using. This can give us a lot of flexibility if we want different formatting for different types of data. We then set table headers to our desired header information and then one TableColumnItem for each header we defined, telling it from which property it can obtain the value.

Finally we can update the .psd1 file to leverage this formatting, by updating the FormatsToProcess line to include our ps1xml file.

FormatsToProcess = @("MyViewDefinition.ps1xml")

Now when you run dir on MyDrive: you will get the following output:

Name                      Count
----                      -----
java                      671397
c#                        665365
javascript                649489
php                       606076
android                   534244
jquery                    503075
python                    318616
...

At this point you have a drive that can show a single level of depth and the dir output doesn’t look half bad. In the next article, we will really dig into navigation and work on navigating in and out of folders and have valid dir contents at every depth.

Creating a PowerShell Navigation Provider: #1 The Basics

This post will cover the basics of getting your environment setup to create a PowerShell navigation provider. All of the code is available on GitHub. I have created a v0.1 tag which represents the code that will be covered in this post.

  • Create a new Class Library project in Visual Studio.
  • Add a reference to System.Management.Automation from the C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0 directory.

  • Modify your project debug settings to start an external program and start C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

  • Add a CmdletProvider attribute to your class, have it inherit from NavigationCmdletProvider and implement the required members:

    [CmdletProvider("MyPowerShellProvider", ProviderCapabilities.None)]
    public class MyProvider : NavigationCmdletProvider
    {
        protected override bool IsValidPath(string path)
        {
            return true;  // All paths are valid for now
        }
    }
    

The next thing we will do is create a script to install the provider when we start debugging.

  • Add a new file to the project and call it InstallProvider.ps1. This installs our PowerShell module into the current PowerShell session. The contents of the file are:

    Import-Module .\PowerShellProvider.dll
    
  • Set the Copy to Output Directory property to Copy if newer on the file.

  • You can manually run this file every time you start PowerShell or you can update your debug settings and add the following to the command line arguments:

    -NoExit  -File .\InstallProvider.ps1
    

Now we can start implementing the overrides we need to get the provider working. The first method you want to implement is the InitializeDefaultDrives. This method is called when you install the provider and will pre-create drives so users don’t need to explicitly call New-Drive.

protected override Collection<PSDriveInfo> InitializeDefaultDrives()
{
    PSDriveInfo drive = new PSDriveInfo("MyDrive", this.ProviderInfo, "", "", null);
    Collection<PSDriveInfo> drives = new Collection<PSDriveInfo>() {drive};
    return drives;      
}

At this point you can start debugging and if you call Get-PSDrive, you will see the drive, but you will not be able to navigate to it. Let’s fill out a few more overrides, so we can navigate to our provider. To start, we will allow the user to navigate to any folder they type in. We will implement ItemExists and IsItemContainer:

protected override bool ItemExists(string path)
{
    return true;
}

protected override bool IsItemContainer(string path)
{
    return true;
}

Now when you debug, you can navigate into the drive by typing cd MyDrive: and you can change folders to your heart’s content. You’ll notice if you type dir you still get an error. Let’s fix that. Simply override the GetChildItems method:

protected override void GetChildItems(string path, bool recurse)
{
    WriteItemObject("Hello", "Hello", true);
} 

Now, you can navigate around and when you type dir, you will always get back your Hello object. Congratulations, you have a working navigation provider. In the next post in this series we will wire up the provider to a backend API and return custom objects instead of just strings.

The full code for the provider is:

[CmdletProvider("MyPowerShellProvider", ProviderCapabilities.None)]
public class MyPowerShellProvider : NavigationCmdletProvider
{

    protected override bool IsValidPath(string path)
    {
        return true;
    }

    protected override Collection<PSDriveInfo> InitializeDefaultDrives()
    {
        PSDriveInfo drive = new PSDriveInfo("MyDrive", this.ProviderInfo, "", "", null);
        Collection<PSDriveInfo> drives = new Collection<PSDriveInfo>() {drive};
        return drives;
    }

    protected override bool ItemExists(string path)
    {
        return true;
    }

    protected override bool IsItemContainer(string path)
    {
        return true;
    }

    protected override void GetChildItems(string path, bool recurse)
    {
        WriteItemObject("Hello", "Hello", true);
    }
}

You now have the most basic of basic providers available to you. In the next article, I cover the basics of getting GetChildItems() working to return something more real and more readable.

Creating a PowerShell Navigation Provider: Introduction

PowerShell has some really nice ways that it can be extended. One of those ways is via Providers. If you open up PowerShell and call Get-PSDrive you will see a list of the drives on your machine along with some other providers to access things like the registry, certificate stores, and aliases. Using these providers is very similar to navigating a normal drive, for example, I can navigate around the registry by simply typing cd HKCU:. Now I can see items in the registry by typing dir and I see items in my registry (I used dir | Format-Table -Property Name for brevity):

Name
----
HKEY_CURRENT_USER\AppEvents
HKEY_CURRENT_USER\Console
HKEY_CURRENT_USER\Control Panel
HKEY_CURRENT_USER\Environment
HKEY_CURRENT_USER\EUDC
HKEY_CURRENT_USER\Identities
HKEY_CURRENT_USER\Keyboard Layout
HKEY_CURRENT_USER\Network
HKEY_CURRENT_USER\Policies
HKEY_CURRENT_USER\Printers
HKEY_CURRENT_USER\Software
HKEY_CURRENT_USER\System
HKEY_CURRENT_USER\WXP
HKEY_CURRENT_USER\Volatile Environment

Navigating these items is now as easy as using cd to change directories. So cd Software\Microsoft\Windows\CurrentVersion takes me into that node within the registry. From here you can now use other commands to update the registry. As you can see, this is a convenient way to access data in the registry and since it is inside of PowerShell, it can all be scripted.

Creating a basic PowerShell provider is a relatively simple task. Over the next few blog posts, I will walk through the basics of creating a Navigation Cmdlet Provider, so you can add your own custom drive to PowerShell.

Updated Restart Explorer PowerShell Module to Restore Windows

Yesterday I posted a simple PowerShell to restart explorer. I have continued to tinker with it and gave it the ability to find the currently open explorer windows and re-open those after it restarts explorer.

function Restart-Explorer
{
    Param([switch] $SuppressReOpen)

    #Gather up the currently open windows, so we can re-spawn them.
    $x = New-Object -ComObject "Shell.Application"
    $count = $x.Windows().Count
    $windows = @();
    $explorerPath = [System.IO.Path]::Combine($env:windir, "explorer.exe");
    for ($i=0; $i -lt $count; $i++)
    {
        # The location URL contains the Path that the explorer window is currently open to
        $url = $x.Windows().Item($i).LocationURL;

        $fullName = $x.Windows().Item($i).FullName;

        # This also catches IE windows, so I only add items where the full name is %WINDIR%\explorer.exe 
        if ($fullName.Equals($explorerPath))
        {
            $windows += $url
        }
    }

    Stop-Process -ProcessName explorer

    if (!$SuppressReOpen)
    {
        foreach ($window In $windows){

            if ([string]::IsNullOrEmpty($window)){
                Start-Process $explorerPath
            }
            else
            {
                Start-Process $window
            }
        }
    }
}

Set-Alias re Restart-Explorer
Export-ModuleMember -function Restart-Explorer -Alias re

So now when I run re, explorer restarts and my explorer windows are restored. If for some reason I didn’t want to restore the windows I added a SuppressReOpen switch to prevent the script from re-opening explorer windows.

Creating a PowerShell Module to Restart Explorer.exe

I’ve been doing a fair bit of development lately with Shell Extensions and have to constantly restart my explorer.exe process. Quickly, I found that I could do this through powershell using the Stop-Process commandlet.

Stop-Process -ProcessName explorer

This was easy enough to type, but I found myself doing it quite a bit and wanted it to be quicker and easier to remember. I had wanted to learn more about PowerShell modules, so I figure this is as good of a time as any to try it out.

By default PowerShell will look in your %AppProfile%\Documents\WindowsPowerShell\Modules folder for any modules that should be loaded when you start a new shell. (If you want to see all the places it looks, you can look at the $env:PSModulePath variable) From there, each module will have its own folder. So I created a RestartExplorer folder and created a RestartExplorer.psm1 file which contains my module code:

function Restart-Explorer()
{
    Stop-Process -ProcessName explorer
}

Set-alias re Restart-Explorer
Export-ModuleMember -Function Restart-Explorer -Alias re

Now when I type re, explorer restarts for me. A few things to note in this script. The Export-ModuleMember commandlet tells PowerShell which components from this script should be available to the shell. If I did not include the -Alias re then re would not work from the shell window. If you have a lot of functions and aliases you want to export from a module, you could simply export them all using wildcards:

Export-ModuleMember -Function * -Alias *

While this is a simple example, it shows how easy it is to write modules in PowerShell and have them “just work” once you set them up.

Microsoft Reference Source is a Good Friend

With recent versions of .Net, Microsoft has provided reference source to allow you to navigate and debug issues with the .Net framework. The reference source website is a great tool to have in your back pocket when researching how something in the .Net framework works. You can download a full solution (minus some resource files) for use in Visual Studio or you can link directly to a line of code when sending emails or instant messages to colleagues.

Recently I was working with the Managed AddIn Framework, and I was trying to determine how to load settings for an AddIn. I did some reading and really didn’t find much on the subject. I tried creating a .config file for the AddIn and magically it was able to load the settings from there. I don’t like magic in my code, so I wanted to know how the settings were being loaded. I downloaded the reference source and was able to dig into the AddInActivator class and find the calls into the AddInServer. Inside that code, I found that they explicitly load the configuration file based on the AddIn assmebly name:

AppDomain domain;
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = Path.GetDirectoryName(token._addin.Location);
setup.ConfigurationFile = token._addin.Location + ".config";

I then took this information and was able to build up a nice email to my colleagues with links directly to the lines of code that we needed to understand to know how the MAF framework loaded configuration files.

I encourage every .Net developer to get a copy of the reference source on your machine and leverage it when you need to get a better understanding of how the .Net framework works.