Compartilhar via


Leveraging Exploit Guard in Windows Insider Build to Easily Audit Your Code

If you are a software developer and are looking to improve upon the security compliance of your software, there is a feature in the current Windows 10 Enterprise Insider Preview (as of 10.0.16253 - I can't guarantee this will make it or make it unchanged into future builds) that could be very useful to you.  In Settings->Windows Defender->Windows Defender Security Center->App and Browser Control->Exploit Protection Settings, you can enable custom exploit settings for either the entire system or specific programs.  There's a lot of different protections that can be turned on there and many of them can be turned on in Audit Mode.  In Audit Mode, instead of terminating a process when the situation occurs, it will write an event to the event log instead.  For admins, that means allowing the software to continue to run while being made aware of when the situation occurs.  For someone looking to improve upon their product by stopping these situations from happening in their software, it provides the added benefits of enabling security features without having to recompile (in some cases) and telling you exactly where in the code in your processes the issue occurs when it is encountered at runtime.  In build 10.0.16253, the protections that can be audited are:

  • Arbitrary Code Guard - Prevents non-image backed execute code and code page modifications (e.g. VirtualAlloc/VirtualProtect created/modified code)
  • Block Low Integrity Images
  • Block Remote Images
  • Block Untrusted Fonts
  • Code Integrity Guard
  • Disable Win32k system calls
  • Do Not Allow Child Processes
  • Export Address Filtering- One step in one common methodology to patch a function to another function
  • Import Address Filtering - One step in one common methodology to patch a function to another function
  • Simulate Execution
  • Validate API Invocation (CallerCheck)
  • Validate Image Dependency Integrity
  • Validate Stack Integrity

To take full advantage of this, install the Windows Performance Toolkit.  It's one of the installation options with the Windows SDK installer.  After enabling the settings that you are interested in for your application, open up an admin command prompt and browse to the Windows Performance Toolkit directory (typically Program Files (x86)\Windows Kits\{Version}\Windows Performance Toolkit).  You can enable and start collecting tracing for the aforementioned exploit protection settings with the data that you'll need to resolve stack traces by running the following two commands (after substituting whatever paths you want for the filenames):

xperf -on "PROC_THREAD+LOADER" -f "wdeg_klogger.etl"

xperf -start "WDEG" -on "Microsoft-Windows-Security-Mitigations:0xFFFFFFFFFFFFFFFF:0xFF:'stack'"  -f "wdeg_unmerged.etl"

After you run through whatever scenario you want to collect, you can stop the tracing and merge the data together with the following (after again substituting whatever paths you want for the filenames):

xperf -stop -stop "WDEG" -d "wdeg_merged.etl"

You can then delete the first two files that were created since you'll have all the data that you need in the third at this point.  You can open up the resultant etl file in Windows Performance Analyzer (WPA) and have a look at the data.  If you're not familiar with doing this, here's a sample preset file for looking at just this sort of data:

 <?xml version="1.0" encoding="utf-8"?>
<WpaProfileContainer xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" Version="2" xmlns="https://tempuri.org/SerializableElement.xsd">
  <Content xsi:type="WpaProfile2">
    <Sessions />
    <Views />
    <ModifiedGraphs>
      <GraphSchema Guid="04f69f98-176e-4d1c-b44e-97f734996ab8">
        <ModifiedPresets>
          <Preset Name="EG Stacks" BarGraphIntervalCount="50" IsThreadActivityTable="false" LeftFrozenColumnCount="5" RightFrozenColumnCount="9" KeyColumnCount="4" GraphColumnCount="10" InitialFilterQuery="[Event Name]:~=&quot;Microsoft-Windows-Security-Miti&quot;" InitialFilterShouldKeep="true" GraphFilterTopValue="0" GraphFilterThresholdValue="0" HelpText="{}{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0 Times New Roman;}{\f2\fcharset0 Segoe UI;}}{\colortbl\red0\green0\blue0;\red255\green255\blue255;}\loch\hich\dbch\pard\plain\ltrpar\itap0{\lang1033\fs18\f2\cf0 \cf0\ql{\f2 {\ltrch Groups all the events by provider, task, and opcode.}\li0\ri0\sa0\sb0\fi0\ql\par}
}
}">
            <Columns>
              <Column Name="Provider Name" Guid="8b4c40f8-0d99-437d-86ab-56ec200137dc" Width="200" IsVisible="true" SortPriority="18" />
              <Column Name="Task Name" Guid="511777f7-30ef-4e86-bd0b-0facaf23a0d3" Width="80" IsVisible="true" SortPriority="19" />
              <Column Name="Event Name" Guid="90fe0b49-e3bb-440f-b829-5813c42108a1" Width="100" IsVisible="false" SortPriority="0" />
              <Column Name="Stack" Guid="26eed6bf-f07d-4bb0-a36f-43a7d3a68828" Width="247" IsVisible="true" SortPriority="0">
                <StackOptionsParameter />
              </Column>
              <Column Name="Message" Guid="0734f1a4-fbd9-4ff6-aec0-cf43875c8253" Width="100" IsVisible="true" SortPriority="0" />
              <Column Name="Process" Guid="7ee6a5ff-1faf-428a-a7c2-7d2cb2b5cf26" Width="150" IsVisible="true" SortPriority="28" />
              <Column Name="ThreadId" Guid="edf01e5d-3644-4dbc-ab9d-f8954e6db6ea" Width="50" IsVisible="true" SortPriority="27" />
              <Column Name="Time" Guid="bbfc990a-b6c9-4dcd-858b-f040ab4a1efe" Width="80" IsVisible="true" SortPriority="29" />
            </Columns>
            <MetadataEntries>
              <MetadataEntry Guid="edf01e5d-3644-4dbc-ab9d-f8954e6db6ea" Name="ThreadId" ColumnMetadata="EndThreadId" />
              <MetadataEntry Guid="bbfc990a-b6c9-4dcd-858b-f040ab4a1efe" Name="Time" ColumnMetadata="StartTime" />
              <MetadataEntry Guid="bbfc990a-b6c9-4dcd-858b-f040ab4a1efe" Name="Time" ColumnMetadata="EndTime" />
            </MetadataEntries>
            <HighlightEntries />
          </Preset>
        </ModifiedPresets>
        <PersistedPresets />
      </GraphSchema>
    </ModifiedGraphs>
  </Content>
</WpaProfileContainer>

You can save it with a wpaPreset file extension, load your data in WPA, go to My Presets (File->Window->My Presents in the newer versions of WPA), select Import, browse to wherever you saved the preset, choose it, and double-click it to bring up the view.  You'll want to load symbols in order to make the most of this.  You set up symbols in WPA under File->Configure Symbols; you'll want to point it to the Microsoft symbol server at msdl.microsoft.com/download/symbols as well as your symbol server (or location where you have your symbols files if you don't have a symbol server setup).  You can configure WPA to load symbols automatically whenever you load a trace, but you can also always manually load symbols by going to File->Load Symbols.  Once that's done, you'll be able to look at your stack traces pretty easily.  It'll look something like this:

Here's the sample code that I used compiled as an x64 console application when creating the above:

 #include <Windows.h>
#include <iostream>
using namespace std;
void* CreateCodeInVirtualMemory(BOOL writable)
{
    BYTE code[3] = { 0x33, 0xc0, 0xc3 };
   LPVOID result = VirtualAlloc(NULL, sizeof(code), MEM_COMMIT | MEM_RESERVE, writable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
 if (result)
    {
       memcpy(result, code, sizeof(code));
    }
   else cout << "VirtualAllocEx failed with error " << GetLastError() << endl;
  return result;
}
void CreateCodeInVirtualMemoryAndExecute(BOOL useWritableMemory)
{
    LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)CreateCodeInVirtualMemory(useWritableMemory);
  if (addr)
  {
       DWORD result = addr(NULL);
        cout << "Code at 0x" << hex << (void*)addr << " returned " << result << endl;
  }
   else cout << "NULL address was not executed" << endl;
}
void ExecuteIllegalMemory()
{
 CreateCodeInVirtualMemoryAndExecute(FALSE);
}
 
void PrintOptions()
{
 cout << "Enter one of the following options:" << endl;
   cout << "1 - Execute Memory Not Marked As Executable" << endl;
   cout << "2 - Create Code in Virtual Memory" << endl;
 cout << "3 - Create Code in Virtual Memory and Execute" << endl;
 cout << "0 - Exit" << endl;
 
}
void DecisionLoop()
{
   while (true)
  {
       int selection;
     PrintOptions();
     cin >> selection;
        switch (selection)
     {
           case 0:
                return;
            case 1:
                ExecuteIllegalMemory();
             break;
         case 2:
                CreateCodeInVirtualMemory(TRUE);
               break;
         case 3:
                CreateCodeInVirtualMemoryAndExecute(TRUE);
             break;
         default:
               cout << "Invalid input" << endl;
     }
   }
}
int main()
{
  DecisionLoop();
 return 0;
}

There are certainly more use cases for this that aren't covered here; this just scratches the surface of the possibilities :)

This kind of thing can also be done in much the same way for most anything that you find under Applications and Services logs in Event Viewer; click on the properties for a specific log and it will have the name in the form of Provider-Name-Parts/LogName.  You just want to use the "Provider-Name-Parts" portion in the xperf command.