From 3310f63b43313da9d9a5e7407645d767fa316464 Mon Sep 17 00:00:00 2001 From: Bryan Berns Date: Sat, 30 Jul 2016 12:25:56 -0400 Subject: [PATCH] Project Rebase --- .gitattributes | 63 ++++ .gitignore | 245 ++++++++++++++++ ConcurrentQueue.h | 50 ++++ DriverKitPartial.h | 35 +++ Functions.h | 16 ++ Helpers.cpp | 364 ++++++++++++++++++++++++ InputOutput.h | 102 +++++++ Main.cpp | 473 +++++++++++++++++++++++++++++++ Notes.txt | 18 ++ Operation.cpp | 186 ++++++++++++ Operation.h | 92 ++++++ OperationAddAccountIfMissing.cpp | 86 ++++++ OperationAddAccountIfMissing.h | 24 ++ OperationCheckCanonical.cpp | 51 ++++ OperationCheckCanonical.h | 21 ++ OperationCompact.cpp | 84 ++++++ OperationCompact.h | 21 ++ OperationExportDescriptor.cpp | 58 ++++ OperationExportDescriptor.h | 24 ++ OperationFactory.h | 72 +++++ OperationFindAccount.cpp | 46 +++ OperationFindAccount.h | 24 ++ OperationFindDomain.cpp | 51 ++++ OperationFindDomain.h | 24 ++ OperationFindNullAcl.cpp | 24 ++ OperationFindNullAcl.h | 21 ++ OperationHelp.cpp | 230 +++++++++++++++ OperationHelp.h | 21 ++ OperationInheritChildren.cpp | 23 ++ OperationInheritChildren.h | 21 ++ OperationMigrateDomain.cpp | 73 +++++ OperationMigrateDomain.h | 26 ++ OperationNoHiddenSystem.cpp | 10 + OperationNoHiddenSystem.h | 17 ++ OperationPath.cpp | 22 ++ OperationPath.h | 17 ++ OperationPrintDescriptor.cpp | 35 +++ OperationPrintDescriptor.h | 21 ++ OperationQuiet.cpp | 10 + OperationQuiet.h | 17 ++ OperationRemoveAccount.cpp | 49 ++++ OperationRemoveAccount.h | 24 ++ OperationRemoveOrphan.cpp | 61 ++++ OperationRemoveOrphan.h | 24 ++ OperationRemoveRedundant.cpp | 78 +++++ OperationRemoveRedundant.h | 21 ++ OperationReplaceAccount.cpp | 58 ++++ OperationReplaceAccount.h | 26 ++ OperationReport.cpp | 120 ++++++++ OperationReport.h | 27 ++ OperationResetChildren.cpp | 32 +++ OperationResetChildren.h | 24 ++ OperationSetOwner.cpp | 46 +++ OperationSetOwner.h | 24 ++ OperationSidHistory.cpp | 37 +++ OperationSidHistory.h | 20 ++ OperationThreads.cpp | 21 ++ OperationThreads.h | 17 ++ OperationWhatIf.cpp | 10 + OperationWhatIf.h | 17 ++ Resource.rc | Bin 0 -> 4648 bytes Version.h | 3 + repacls.sln | 28 ++ repacls.vcxproj | 247 ++++++++++++++++ repacls.vcxproj.filters | 211 ++++++++++++++ resource.h | 14 + 66 files changed, 4057 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 ConcurrentQueue.h create mode 100644 DriverKitPartial.h create mode 100644 Functions.h create mode 100644 Helpers.cpp create mode 100644 InputOutput.h create mode 100644 Main.cpp create mode 100644 Notes.txt create mode 100644 Operation.cpp create mode 100644 Operation.h create mode 100644 OperationAddAccountIfMissing.cpp create mode 100644 OperationAddAccountIfMissing.h create mode 100644 OperationCheckCanonical.cpp create mode 100644 OperationCheckCanonical.h create mode 100644 OperationCompact.cpp create mode 100644 OperationCompact.h create mode 100644 OperationExportDescriptor.cpp create mode 100644 OperationExportDescriptor.h create mode 100644 OperationFactory.h create mode 100644 OperationFindAccount.cpp create mode 100644 OperationFindAccount.h create mode 100644 OperationFindDomain.cpp create mode 100644 OperationFindDomain.h create mode 100644 OperationFindNullAcl.cpp create mode 100644 OperationFindNullAcl.h create mode 100644 OperationHelp.cpp create mode 100644 OperationHelp.h create mode 100644 OperationInheritChildren.cpp create mode 100644 OperationInheritChildren.h create mode 100644 OperationMigrateDomain.cpp create mode 100644 OperationMigrateDomain.h create mode 100644 OperationNoHiddenSystem.cpp create mode 100644 OperationNoHiddenSystem.h create mode 100644 OperationPath.cpp create mode 100644 OperationPath.h create mode 100644 OperationPrintDescriptor.cpp create mode 100644 OperationPrintDescriptor.h create mode 100644 OperationQuiet.cpp create mode 100644 OperationQuiet.h create mode 100644 OperationRemoveAccount.cpp create mode 100644 OperationRemoveAccount.h create mode 100644 OperationRemoveOrphan.cpp create mode 100644 OperationRemoveOrphan.h create mode 100644 OperationRemoveRedundant.cpp create mode 100644 OperationRemoveRedundant.h create mode 100644 OperationReplaceAccount.cpp create mode 100644 OperationReplaceAccount.h create mode 100644 OperationReport.cpp create mode 100644 OperationReport.h create mode 100644 OperationResetChildren.cpp create mode 100644 OperationResetChildren.h create mode 100644 OperationSetOwner.cpp create mode 100644 OperationSetOwner.h create mode 100644 OperationSidHistory.cpp create mode 100644 OperationSidHistory.h create mode 100644 OperationThreads.cpp create mode 100644 OperationThreads.h create mode 100644 OperationWhatIf.cpp create mode 100644 OperationWhatIf.h create mode 100644 Resource.rc create mode 100644 Version.h create mode 100644 repacls.sln create mode 100644 repacls.vcxproj create mode 100644 repacls.vcxproj.filters create mode 100644 resource.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a2238d --- /dev/null +++ b/.gitignore @@ -0,0 +1,245 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ \ No newline at end of file diff --git a/ConcurrentQueue.h b/ConcurrentQueue.h new file mode 100644 index 0000000..3ee6d19 --- /dev/null +++ b/ConcurrentQueue.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +template +class ConcurrentQueue +{ +public: + + ConcurrentQueue() = default; + + T pop() + { + std::unique_lock oMutexLock(oMutex); + while (oQueue.empty()) + { + oCondition.wait(oMutexLock); + } + auto oVar = oQueue.front(); + oQueue.pop(); + return oVar; + } + + void push(const T& oItem) + { + std::unique_lock oMutuxLock(oMutex); + oQueue.push(oItem); + oMutuxLock.unlock(); + oCondition.notify_one(); + } + + void pop(T& oItem) + { + std::unique_lock mlock(oMutex); + while (oQueue.empty()) + { + oCondition.wait(mlock); + } + oItem = oQueue.front(); + oQueue.pop(); + } + +private: + std::condition_variable oCondition; + std::queue oQueue; + std::mutex oMutex; +}; \ No newline at end of file diff --git a/DriverKitPartial.h b/DriverKitPartial.h new file mode 100644 index 0000000..5e040e7 --- /dev/null +++ b/DriverKitPartial.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#define FileDirectoryInformation 1 + +#pragma comment(lib,"ntdll.lib") + +typedef struct _PRELATIVE_NAME +{ + UNICODE_STRING Name; + HANDLE CurrentDir; +} PRELATIVE_NAME, *PPRELATIVE_NAME; + +typedef struct _FILE_DIRECTORY_INFORMATION { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; + +typedef NTSTATUS(WINAPI *RTLDOSPATHNAMETONTPATHNAME_U)(PCWSTR DosPathName, PUNICODE_STRING NtPathName, PWSTR* FilePathInNtPathName, PRELATIVE_NAME* RelativeName); +typedef NTSTATUS(WINAPI *NTQUERYDIRECTORYFILE)(HANDLE FileHandle, HANDLE Event, PVOID ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, PUNICODE_STRING FileName, BOOLEAN RestartScan); + +static HMODULE hRtlLoadLibrary = LoadLibrary(L"ntdll.dll"); +static RTLDOSPATHNAMETONTPATHNAME_U RtlDosPathNameToNtPathName_U = (RTLDOSPATHNAMETONTPATHNAME_U)GetProcAddress(hRtlLoadLibrary, "RtlDosPathNameToNtPathName_U"); +static NTQUERYDIRECTORYFILE NtQueryDirectoryFile = (NTQUERYDIRECTORYFILE)GetProcAddress(hRtlLoadLibrary, "NtQueryDirectoryFile"); \ No newline at end of file diff --git a/Functions.h b/Functions.h new file mode 100644 index 0000000..55abb94 --- /dev/null +++ b/Functions.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +// helper functions +VOID EnablePrivs(); +const PSID GetSidFromName(std::wstring & sAccountName); +std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan); +std::wstring GetNameFromSidEx(const PSID tSid); +std::wstring GenerateAccessMask(DWORD iCurrentMask); +std::wstring GenerateInheritanceFlags(DWORD iCurrentFlags); +HANDLE RegisterFileHandle(HANDLE hFile, std::wstring sOperation); + + diff --git a/Helpers.cpp b/Helpers.cpp new file mode 100644 index 0000000..791c7df --- /dev/null +++ b/Helpers.cpp @@ -0,0 +1,364 @@ +#define UMDF_USING_NTSTATUS +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Operation.h" +#include "Functions.h" + +typedef struct SidCompare +{ + inline bool operator()(PSID p1, PSID p2) const + { + const DWORD iLength1 = GetLengthSid(p1); + const DWORD iLength2 = GetLengthSid(p2); + if (iLength1 != iLength2) return iLength1 < iLength2; + return memcmp(p1, p2, iLength1) > 0; + } +} +SidCompare; + +const PSID GetSidFromName(std::wstring & sAccountName) +{ + // for caching + static std::shared_mutex oMutex; + static std::map oNameToSidLookup; + + // scope lock for thread safety + { + std::shared_lock oLock(oMutex); + std::map::iterator oInteractor = oNameToSidLookup.find(sAccountName); + if (oInteractor != oNameToSidLookup.end()) + { + return oInteractor->second; + } + } + + // first see if the name look like a sid + PSID tSidFromSid; + if (ConvertStringSidToSid(sAccountName.c_str(), &tSidFromSid) != 0) + { + return tSidFromSid; + } + + // assume the sid is as large as it possible can be + BYTE tSidFromName[SECURITY_MAX_SID_SIZE]; + WCHAR sDomainName[UNLEN + 1]; + DWORD iDomainName = _countof(sDomainName); + DWORD iSidSize = sizeof(tSidFromName); + SID_NAME_USE tNameUse; + + // do lookup + if (LookupAccountName(NULL, sAccountName.c_str(), (PSID)tSidFromName, + &iSidSize, sDomainName, &iDomainName, &tNameUse) == 0) + { + std::unique_lock oLock(oMutex); + oNameToSidLookup[sAccountName] = nullptr; + return nullptr; + } + + // reallocate memory and copy sid to a smaller part of memory and + // then add the sid to the cache map + PSID tSid = (PSID)memcpy(malloc(iSidSize), tSidFromName, iSidSize); + + // scope lock for thread safety + { + std::unique_lock oLock(oMutex); + oNameToSidLookup[sAccountName] = tSid; + } + + // return the sid pointer to the caller + return tSid; +} + +std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan) +{ + // return immediately if sid is null + if (tSid == NULL) return L""; + + // for caching + static std::shared_mutex oMutex; + static std::map oSidToNameLookup; + + // scope lock for thread safety + { + std::shared_lock oLock(oMutex); + std::map::iterator oInteractor = oSidToNameLookup.find(tSid); + if (oInteractor != oSidToNameLookup.end()) + { + // if blank that means the account has no associated same + // and likely is an orphan + if (oInteractor->second == L"" && + bMarkAsOrphan != NULL) *bMarkAsOrphan = true; + + // return the found full name + return oInteractor->second; + } + } + + // lookup the name for this sid + SID_NAME_USE tNameUse; + WCHAR sAccountName[UNLEN + 1]; + DWORD iAccountNameSize = _countof(sAccountName); + WCHAR sDomainName[UNLEN + 1]; + DWORD iDomainName = _countof(sDomainName); + std::wstring sFullName = L""; + if (LookupAccountSid(NULL, tSid, sAccountName, + &iAccountNameSize, sDomainName, &iDomainName, &tNameUse) == 0) + { + DWORD iError = GetLastError(); + if (iError == ERROR_NONE_MAPPED) + { + if (bMarkAsOrphan != NULL) *bMarkAsOrphan = true; + } + else + { + wprintf(L"ERROR: Unexpected error returned from account lookup (%lu).", iError); + return L""; + } + } + else + { + // generate full name in domain\name format + sFullName = std::wstring(sDomainName) + + L"\\" + std::wstring(sAccountName); + } + + // copy the sid for storage in our cache table + const DWORD iSidLength = GetLengthSid(tSid); + PSID tSidCopy = (PSID)memcpy(malloc(iSidLength), tSid, iSidLength); + + // scope lock for thread safety + { + std::unique_lock oLock(oMutex); + oSidToNameLookup[tSidCopy] = sFullName; + } + + // return name + return sFullName; +} + +std::wstring GetNameFromSidEx(const PSID tSid) +{ + // if sid is resolvable then return the account name + std::wstring sName = GetNameFromSid(tSid, NULL); + if (sName != L"") return sName; + + // if sid is unresolvable then return sid in string form + WCHAR * sSidBuf; + ConvertSidToStringSid(tSid, &sSidBuf); + std::wstring sSid(sSidBuf); + LocalFree(sSidBuf); + return sSid; +} + +std::wstring GenerateInheritanceFlags(DWORD iCurrentFlags) +{ + std::wstring sFlags; + if (CONTAINER_INHERIT_ACE & iCurrentFlags) sFlags += L"Container Inherit;"; + if (OBJECT_INHERIT_ACE & iCurrentFlags) sFlags += L"Object Inherit;"; + if (NO_PROPAGATE_INHERIT_ACE & iCurrentFlags) sFlags += L"Do No Propagate Inherit;"; + if (INHERIT_ONLY_ACE & iCurrentFlags) sFlags += L"Inherit Only;"; + + // handle the empty case or trim off the trailing semicolon + if (sFlags.size() == 0) sFlags = L"None"; + else sFlags.pop_back(); + + // return the calculated string + return sFlags; +} + + +std::wstring GenerateAccessMask(DWORD iCurrentMask) +{ + // define the aesthetic names of permission + static struct + { + const DWORD Mask; + const std::wstring Description; + } + MaskDefinitions[] = + { + { FILE_ALL_ACCESS, L"Full Control" }, + { FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, L"Modify" }, + { FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, L"Read & Execute" }, + { FILE_GENERIC_EXECUTE, L"Execute" }, + { FILE_GENERIC_READ, L"Read" }, + { FILE_GENERIC_WRITE, L"Write" }, + { FILE_EXECUTE, L"Traverse Folder/Execute File" }, + { FILE_READ_DATA, L"List Folder/Read Data" }, + { FILE_READ_ATTRIBUTES, L"Read Attributes" }, + { FILE_READ_EA, L"Read Extended Attributes" }, + { FILE_WRITE_DATA, L"Create Files/Write Data" }, + { FILE_APPEND_DATA, L"Create Folders/Append Data" }, + { FILE_WRITE_ATTRIBUTES, L"Write Attributes" }, + { FILE_WRITE_EA, L"Write Extended Attributes" }, + { FILE_DELETE_CHILD, L"Delete Children" }, + { DELETE, L"Delete" }, + { READ_CONTROL, L"Read Permissions" }, + { WRITE_DAC, L"Set Permissions" }, + { WRITE_OWNER, L"Take Ownership" }, + { SYNCHRONIZE, L"Synchronize" } + }; + + // loop through the mask and construct of string of the names + std::wstring sMaskList; + for (int iMaskEntry = 0; iMaskEntry < _countof(MaskDefinitions) && iCurrentMask > 0; iMaskEntry++) + { + if ((MaskDefinitions[iMaskEntry].Mask & iCurrentMask) == MaskDefinitions[iMaskEntry].Mask) + { + sMaskList += MaskDefinitions[iMaskEntry].Description + L";"; + iCurrentMask ^= MaskDefinitions[iMaskEntry].Mask; + } + } + + // handle the empty case or trim off the trailing semicolon + if (sMaskList.size() == 0) sMaskList = L"None"; + else sMaskList.pop_back(); + + // return the calculated string + return sMaskList; +} + +VOID EnablePrivs() +{ + // open the current token + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken) == 0) + { + // error + wprintf(L"%s\n", L"ERROR: Could not open process token for enabling privileges."); + return; + } + + // get the current user sid out of the token + BYTE aBuffer[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE]; + PTOKEN_USER tTokenUser = (PTOKEN_USER)(aBuffer); + DWORD iBytesFilled = 0; + if (GetTokenInformation(hToken, TokenUser, tTokenUser, sizeof(aBuffer), &iBytesFilled) == 0) + { + // error + wprintf(L"%s\n", L"ERROR: Could retrieve process token information."); + return; + } + + WCHAR * sPrivsToSet[] = { SE_RESTORE_NAME, SE_BACKUP_NAME, SE_TAKE_OWNERSHIP_NAME }; + for (int i = 0; i < sizeof(sPrivsToSet) / sizeof(WCHAR *); i++) + { + // populate the privilege adjustment structure + TOKEN_PRIVILEGES tPrivEntry; + tPrivEntry.PrivilegeCount = 1; + tPrivEntry.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // translate the privilege name into the binary representation + if (LookupPrivilegeValue(NULL, sPrivsToSet[i], + &tPrivEntry.Privileges[0].Luid) == 0) + { + wprintf(L"ERROR: Could not lookup privilege: %s\n", sPrivsToSet[i]); + continue; + } + + // adjust the process to change the privilege + if (AdjustTokenPrivileges(hToken, FALSE, &tPrivEntry, + sizeof(TOKEN_PRIVILEGES), NULL, NULL) != 0) + { + // enabling was successful + continue; + } + + // Object attributes are reserved, so initialize to zeros. + LSA_OBJECT_ATTRIBUTES ObjectAttributes; + ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes)); + + // Get a handle to the Policy object. + LSA_HANDLE hPolicyHandle; + NTSTATUS iResult = 0; + if ((iResult = LsaOpenPolicy(NULL, &ObjectAttributes, + POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, &hPolicyHandle)) != STATUS_SUCCESS) + { + wprintf(L"ERROR: Local security policy could not be opened with error '%lu'\n", + LsaNtStatusToWinError(iResult)); + continue; + } + + // convert the privilege name to a unicode string format + LSA_UNICODE_STRING sPrivilege; + sPrivilege.Buffer = sPrivsToSet[i]; + sPrivilege.Length = (USHORT)(wcslen(sPrivsToSet[i]) * sizeof(WCHAR)); + sPrivilege.MaximumLength = (USHORT)((wcslen(sPrivsToSet[i]) + 1) * sizeof(WCHAR)); + + // attempt to add the account to policy + if ((iResult = LsaAddAccountRights(hPolicyHandle, + tTokenUser->User.Sid, &sPrivilege, 1)) != STATUS_SUCCESS) + { + LsaClose(hPolicyHandle); + wprintf(L"ERROR: Privilege '%s' was not able to be added with error '%lu'\n", + sPrivsToSet[i], LsaNtStatusToWinError(iResult)); + continue; + } + + // cleanup + LsaClose(hPolicyHandle); + + if (AdjustTokenPrivileges(hToken, FALSE, &tPrivEntry, + sizeof(TOKEN_PRIVILEGES), NULL, NULL) == 0 || GetLastError() != ERROR_NOT_ALL_ASSIGNED) + { + wprintf(L"ERROR: Could not adjust privilege: %s\n", sPrivsToSet[i]); + continue; + } + + // error if not all items were assigned + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + wprintf(L"ERROR: Could not enable privilege: %s\n", sPrivsToSet[i]); + } + } + + CloseHandle(hToken); + return; +} + +HANDLE RegisterFileHandle(HANDLE hFile, std::wstring sOperation) +{ + // lookup do a reverse lookup on file name + static std::map> oFileLookup; + + // do a reverse lookup on the file name + DWORD iSize = GetFinalPathNameByHandle(hFile, NULL, 0, VOLUME_NAME_NT); + + // create a string that can accommodate that size + std::wstring sPath; + sPath.reserve(iSize); + + // get the full name + GetFinalPathNameByHandle(hFile, (LPWSTR) sPath.data(), (DWORD) sPath.capacity(), VOLUME_NAME_NT); + + // if the handle already exists, then use that one if the parameters match + std::map>::iterator oFile = oFileLookup.find(sPath); + if (oFile != oFileLookup.end()) + { + if (oFileLookup[sPath].second == sOperation) + { + CloseHandle(hFile); + return oFileLookup[sPath].first; + } + else + { + wprintf(L"ERROR: The same file was used in mismatching read/write operations.\n"); + exit(-1); + } + } + else + { + oFileLookup[sPath] = std::pair(hFile, sOperation); + return hFile; + } +} \ No newline at end of file diff --git a/InputOutput.h b/InputOutput.h new file mode 100644 index 0000000..4237d0b --- /dev/null +++ b/InputOutput.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +class InputOutput +{ +private: + + static std::wstring & GetFileName() + { + thread_local std::wstring sScreenBuffer; + return sScreenBuffer; + } + + static std::wstring & GetDetail() + { + thread_local std::wstring sScreenBuffer; + return sScreenBuffer; + } + +public: + + static bool & InQuietMode() + { + static bool bQuietMode = false; + return bQuietMode; + } + + static bool & InWhatIfMode() + { + static bool bWhatIfMode = false; + return bWhatIfMode; + } + + static bool & ExcludeHiddenSystem() + { + static bool bExcludeHiddenSystem = false; + return bExcludeHiddenSystem; + } + + static short & MaxThreads() + { + static short iMaxThreads = 5; + return iMaxThreads; + } + + static std::wstring & BasePath() + { + static std::wstring sBasePath = L""; + return sBasePath; + } + + static void AddFile(const std::wstring & sLine) + { + // discover the long file name prefix so we can subtract it from the display path + static std::wstring sPrefix = L"FILE: "; + static size_t iPrefix = (size_t) -1; + if (iPrefix == (size_t) -1) + { + const std::wstring sUnc = L"\\??\\UNC\\"; + const std::wstring sLocal = L"\\??\\"; + if (sLine.compare(0, sUnc.size(), sUnc.c_str()) == 0) { iPrefix = sUnc.size(); sPrefix += L"\\\\"; } + else if (sLine.compare(0, sLocal.size(), sLocal.c_str()) == 0) iPrefix = sLocal.size(); + else iPrefix = 0; + } + + GetFileName() = sPrefix + sLine.substr(iPrefix) + L"\n"; + GetDetail() = L""; + } + + static void AddInfo(const std::wstring & sLine, std::wstring sPart, bool bMandatory = false) + { + if (!InQuietMode() || bMandatory) + { + GetDetail() += L" INFO: " + sLine + ((sPart == L"") ? L"" : L" in " + sPart) + L"\n"; + } + } + + static void AddWarning(const std::wstring & sLine) + { + GetDetail() += L" WARNING: " + sLine + L"\n"; + } + + static void AddError(const std::wstring & sLine, const std::wstring & sExtended = L"") + { + GetDetail() += L" ERROR: " + sLine + L"\n"; + if (sExtended != L"") GetDetail() += L" ERROR DETAIL: " + sExtended + L"\n"; + } + + static void WriteToScreen() + { + // to to screen if there is anything to write + if (GetFileName().size() > 0 && GetDetail().size() > 0) + { + wprintf(L"%s", (GetFileName() + GetDetail()).c_str()); + } + + // clear out buffer now that it's printed + GetDetail() = L""; + } +}; \ No newline at end of file diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..aa8b83c --- /dev/null +++ b/Main.cpp @@ -0,0 +1,473 @@ +#define UMDF_USING_NTSTATUS +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Operation.h" +#include "OperationHelp.h" +#include "InputOutput.h" +#include "ConcurrentQueue.h" +#include "DriverKitPartial.h" +#include "Functions.h" +#include "Version.h" + +#define MAX_DIRECTORY_BUFFER 65536 + +std::vector oOperationList; + +// general statistics +std::atomic iFilesScanned = 0; +std::atomic iFilesUpdatedSuccess = 0; +std::atomic iFilesUpdatedFailure = 0; +std::atomic iFilesEnumerationFailures = 0; +std::atomic iFilesReadFailures = 0; + +// used for processing the queue +ConcurrentQueue oScanQueue; +std::condition_variable oSyncVar; +std::mutex oSyncVarMutex; +std::atomic iFilesToProcess; + +// populated upon argument processing to determine what data is needed +bool bFetchDacl = false; +bool bFetchSacl = false; +bool bFetchOwner = false; +bool bFetchGroup = false; + +void AnalyzeSecurity(ObjectEntry & oEntry) +{ + // update file counter + iFilesScanned++; + + // print out file name + InputOutput::AddFile(oEntry.Name); + + // compute information to lookup + DWORD iInformationToLookup = 0; + if (bFetchDacl) iInformationToLookup |= DACL_SECURITY_INFORMATION; + if (bFetchSacl) iInformationToLookup |= SACL_SECURITY_INFORMATION; + if (bFetchOwner) iInformationToLookup |= OWNER_SECURITY_INFORMATION; + if (bFetchGroup) iInformationToLookup |= GROUP_SECURITY_INFORMATION; + + // used to determine what we should update + bool bDaclIsDirty = false; + bool bSaclIsDirty = false; + bool bOwnerIsDirty = false; + bool bGroupIsDirty = false; + + // read security information from the file handle + PACL tAclDacl = NULL; + PACL tAclSacl = NULL; + PSID tOwnerSid = NULL; + PSID tGroupSid = NULL; + PSECURITY_DESCRIPTOR tDesc; + DWORD iError = 0; + if ((iError = GetNamedSecurityInfo(oEntry.Name.c_str(), SE_FILE_OBJECT, + iInformationToLookup, (bFetchOwner) ? &tOwnerSid : NULL, (bFetchGroup) ? &tGroupSid : NULL, + (bFetchDacl) ? &tAclDacl : NULL, (bFetchSacl) ? &tAclSacl : NULL, &tDesc)) != ERROR_SUCCESS) + { + // attempt to look up error message + LPWSTR sError = NULL; + size_t iSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, iError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&sError, 0, NULL); + InputOutput::AddError(L"Unable to read file security", (iSize == 0) ? L"" : sError); + if (iSize > 0) LocalFree(sError); + + // clear out any remaining data + InputOutput::WriteToScreen(); + return; + } + + // some functions will reallocate the area for the acl so we need + // to make sure we cleanup that memory distinctly from the security descriptor + bool bDaclCleanupRequired = false; + bool bSaclCleanupRequired = false; + bool bOwnerCleanupRequired = false; + bool bGroupCleanupRequired = false; + + // used for one-shot operations like reset children or inheritance + DWORD iSpecialCommitMergeFlags = 0; + + // loop through the instruction list + for (std::vector::iterator oOperation = oOperationList.begin(); + oOperation != oOperationList.end(); oOperation++) + { + // skip if this operation does not apply to the root/children based on the operation + if ((*oOperation)->AppliesToRootOnly && !oEntry.IsRoot || + (*oOperation)->AppliesToChildrenOnly && oEntry.IsRoot) + { + continue; + } + + // merge any special commit flags + iSpecialCommitMergeFlags |= (*oOperation)->SpecialCommitFlags; + + if ((*oOperation)->AppliesToDacl) + { + bDaclIsDirty |= (*oOperation)->ProcessAclAction(L"DACL", oEntry, tAclDacl, bDaclCleanupRequired); + } + if ((*oOperation)->AppliesToSacl) + { + bSaclIsDirty |= (*oOperation)->ProcessAclAction(L"SACL", oEntry, tAclSacl, bSaclCleanupRequired); + } + if ((*oOperation)->AppliesToOwner) + { + bOwnerIsDirty |= (*oOperation)->ProcessSidAction(L"OWNER", oEntry, tOwnerSid, bOwnerCleanupRequired); + } + if ((*oOperation)->AppliesToGroup) + { + bGroupIsDirty |= (*oOperation)->ProcessSidAction(L"GROUP", oEntry, tGroupSid, bGroupCleanupRequired); + } + if ((*oOperation)->AppliesToSd) + { + (*oOperation)->ProcessSdAction(oEntry.Name, oEntry, tDesc); + } + } + + // write any pending data to screen before we start setting security + // which can sometimes take awhile + InputOutput::WriteToScreen(); + + // compute data to write back + DWORD iInformationToCommit = iSpecialCommitMergeFlags; + if (bDaclIsDirty) iInformationToCommit |= DACL_SECURITY_INFORMATION; + if (bSaclIsDirty) iInformationToCommit |= SACL_SECURITY_INFORMATION; + if (bOwnerIsDirty) iInformationToCommit |= OWNER_SECURITY_INFORMATION; + if (bGroupIsDirty) iInformationToCommit |= GROUP_SECURITY_INFORMATION; + + // if data has changed, commit it + if (iInformationToCommit != 0) + { + // only commit changes if not in what-if scenario + if (!InputOutput::InWhatIfMode()) + { + if ((iError = SetNamedSecurityInfo((LPWSTR) oEntry.Name.c_str(), SE_FILE_OBJECT, iInformationToCommit, + (bOwnerIsDirty) ? tOwnerSid : NULL, (bGroupIsDirty) ? tGroupSid : NULL, + (bDaclIsDirty) ? tAclDacl : NULL, (bSaclIsDirty) ? tAclSacl : NULL)) != ERROR_SUCCESS) + { + // attempt to look up error message + LPWSTR sError = NULL; + size_t iSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, iError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR) &sError, 0, NULL); + InputOutput::AddError(L"Unable to update file security", (iSize == 0) ? L"" : sError); + if (iSize > 0) LocalFree(sError); + + // clear out any remaining data + InputOutput::WriteToScreen(); + + iFilesUpdatedFailure++; + } + else + { + iFilesUpdatedSuccess++; + } + } + } + + + // cleanup + if (bDaclCleanupRequired) LocalFree(tAclDacl); + if (bSaclCleanupRequired) LocalFree(tAclSacl); + if (bOwnerCleanupRequired) LocalFree(tOwnerSid); + if (bGroupCleanupRequired) LocalFree(tGroupSid); + LocalFree(tDesc); +} + + +void CompleteEntry(ObjectEntry & oEntry, bool bDecreaseCounter = true) +{ + if (bDecreaseCounter) + { + if ((iFilesToProcess.fetch_sub(1) - 1) == 0) + { + std::unique_lock oLock(oSyncVarMutex); + oSyncVar.notify_one(); + } + } + + // flush any pending data from the last operation + InputOutput::WriteToScreen(); +} + +void AnalzyingQueue() +{ + // total files processed + thread_local BYTE DirectoryInfo[MAX_DIRECTORY_BUFFER]; + + // run until the loop is manually broken + for (;;) + { + ObjectEntry oEntry = oScanQueue.pop(); + + // break out if entry flags a termination + if (oEntry.Name.size() == 0) break; + + // skip if hidden and system + if (!oEntry.IsRoot && IsHiddenSystem(oEntry.Attributes) + && InputOutput::ExcludeHiddenSystem()) + { + CompleteEntry(oEntry); + continue; + } + + // do security analysis + AnalyzeSecurity(oEntry); + + // stop processing if not a directory + if (!IsDirectory(oEntry.Attributes)) + { + CompleteEntry(oEntry); + continue; + }; + + // construct a string that can be used in the rtl apis + UNICODE_STRING tPathU = { (USHORT) oEntry.Name.size() * sizeof(WCHAR), + (USHORT) oEntry.Name.size() * sizeof(WCHAR), (PWSTR) oEntry.Name.c_str() }; + + // update object attributes object + OBJECT_ATTRIBUTES oAttributes; + InitializeObjectAttributes(&oAttributes, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL); + oAttributes.ObjectName = &tPathU; + + // get an open file handle + HANDLE hFindFile; + IO_STATUS_BLOCK IoStatusBlock; + NTSTATUS Status = NtOpenFile(&hFindFile, FILE_LIST_DIRECTORY | SYNCHRONIZE, + &oAttributes, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | + FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); + + if (Status == STATUS_ACCESS_DENIED) + { + InputOutput::AddError(L"Access denied error occurred while enumerating directory"); + CompleteEntry(oEntry); + iFilesEnumerationFailures++; + continue; + } + else if (Status == STATUS_OBJECT_PATH_NOT_FOUND || + Status == STATUS_OBJECT_NAME_NOT_FOUND) + { + InputOutput::AddError(L"Path not found error occurred while enumerating directory"); + CompleteEntry(oEntry); + iFilesEnumerationFailures++; + continue; + } + else if (Status != STATUS_SUCCESS) + { + InputOutput::AddError(L"Unknown error occurred while enumerating directory"); + CompleteEntry(oEntry); + iFilesEnumerationFailures++; + continue; + } + + // enumerate files in the directory + for (;;) + { + Status = NtQueryDirectoryFile(hFindFile, NULL, NULL, NULL, &IoStatusBlock, + DirectoryInfo, MAX_DIRECTORY_BUFFER, (FILE_INFORMATION_CLASS)FileDirectoryInformation, + FALSE, NULL, FALSE); + + // done processing + if (Status == STATUS_NO_MORE_FILES) break; + + if (Status != 0) + { + InputOutput::AddError(L"An error occurred while enumerating items in the directory"); + break; + } + + for (FILE_DIRECTORY_INFORMATION * oInfo = (FILE_DIRECTORY_INFORMATION *)DirectoryInfo; + oInfo != NULL; oInfo = (FILE_DIRECTORY_INFORMATION *)((BYTE *)oInfo + oInfo->NextEntryOffset)) + { + // continue immediately if we get the '.' or '..' entries + if (IsDirectory(oInfo->FileAttributes)) + { + if (((oInfo->FileNameLength == 2 * sizeof(WCHAR)) && memcmp(oInfo->FileName, L"..", 2 * sizeof(WCHAR)) == 0) || + ((oInfo->FileNameLength == 1 * sizeof(WCHAR)) && memcmp(oInfo->FileName, L".", 1 * sizeof(WCHAR)) == 0)) + { + if (oInfo->NextEntryOffset == 0) break; else continue; + } + } + + // construct the entry + ObjectEntry oSubEntry; + oSubEntry.IsRoot = false; + oSubEntry.Attributes = oInfo->FileAttributes; + oSubEntry.Name += oEntry.Name + ((oEntry.IsRoot && oEntry.Name.back() == '\\') ? L"" : L"\\") + + std::wstring(oInfo->FileName, oInfo->FileNameLength / sizeof(WCHAR)); + + // if a leaf object, just process immediately and don't worry about putting it on the queue + if (!IsDirectory(oSubEntry.Attributes) || IsReparsePoint(oSubEntry.Attributes)) + { + // do security analysis + AnalyzeSecurity(oSubEntry); + CompleteEntry(oSubEntry, false); + } + else + { + iFilesToProcess++; + oScanQueue.push(oSubEntry); + } + + // this loop is complete, exit + if (oInfo->NextEntryOffset == 0) break; + } + } + + // cleanup + NtClose(hFindFile); + CompleteEntry(oEntry); + } +} + +VOID BeginFileScan(std::wstring sScanPath) +{ + // startup some threads for processing + std::vector oThreads; + for (USHORT iNum = 0; iNum < InputOutput::MaxThreads(); iNum++) + oThreads.push_back(new std::thread(AnalzyingQueue)); + + // queue that is used to store the directory listing + std::queue oQueue; + + // to get the process started, we need to have one entry so + // we will set that to the passed argument + ObjectEntry oEntryFirst; + oEntryFirst.IsRoot = true; + + // convert the path to a long path that is compatible with the other call + UNICODE_STRING tPathU; + RtlDosPathNameToNtPathName_U(sScanPath.c_str(), &tPathU, NULL, NULL); + + // copy it to a null terminated string + oEntryFirst.Name = std::wstring(tPathU.Buffer, tPathU.Length / sizeof(WCHAR)); + oEntryFirst.Attributes = GetFileAttributes(sScanPath.c_str()); + + // free the buffer returned previously + RtlFreeUnicodeString(&tPathU); + + // add this entry to being processing + iFilesToProcess++; + oScanQueue.push(oEntryFirst); + + // wait until all threads complete + while (iFilesToProcess > 0) + { + std::unique_lock oLock(oSyncVarMutex); + oSyncVar.wait(oLock); + } + + // send in some empty entries to tell the thread to stop waiting + for (USHORT iNum = 0; iNum < InputOutput::MaxThreads(); iNum++) + { + ObjectEntry oEntry = { L"" }; + oScanQueue.push(oEntry); + } + + // wait for the threads to complete + for (std::vector::iterator oThread = oThreads.begin(); + oThread != oThreads.end(); oThread++) + { + (*oThread)->join(); + delete (*oThread); + } +} + +int wmain(int iArgs, WCHAR * aArgs[]) +{ + // translate + std::queue oArgList; + for (int iArg = 1; iArg < iArgs; iArg++) + { + oArgList.push(aArgs[iArg]); + } + + // if not parameter was especially the artificially add a help + // command to the list so the help will display + if (iArgs <= 1) oArgList.push(L"/?"); + + // flag to track if more than one exclusive operation was specified + bool bExclusiveOperation = false; + + // process each argument in the queue + while (!oArgList.empty()) + { + // derive the operation based on the current argument + Operation * oOperation = FactoryPlant::CreateInstance(oArgList); + + // validate the operation was found although this + // should never happen since the factory itself will complain + if (oOperation == nullptr) exit(0); + + // add to the processing list if there is an actionable security element + if (oOperation->AppliesToDacl || + oOperation->AppliesToSacl || + oOperation->AppliesToOwner || + oOperation->AppliesToGroup || + oOperation->AppliesToSd) + { + // do exclusivity check and error + if (bExclusiveOperation && oOperation->ExclusiveOperation) + { + wprintf(L"%s\n", L"ERROR: More than one exclusive operation was specified."); + exit(-1); + } + + // track exclusivity and data that we must fetch + bExclusiveOperation |= oOperation->ExclusiveOperation; + bFetchDacl |= oOperation->AppliesToDacl; + bFetchSacl |= oOperation->AppliesToSacl; + bFetchOwner |= oOperation->AppliesToOwner; + bFetchGroup |= oOperation->AppliesToGroup; + + // add to the list of operations + oOperationList.push_back(oOperation); + } + } + + // verify a path was specified + if (InputOutput::BasePath().size() == 0) + { + wprintf(L"%s\n", L"ERROR: No path was specified."); + exit(-1); + } + + // ensure we have permissions to all files + EnablePrivs(); + + // not general information + wprintf(L"===============================================================================\n"); + wprintf(L"= Repacls Version %hs by Bryan Berns\n", VERSION_STRING); + wprintf(L"===============================================================================\n"); + wprintf(L"= Scan Path: %s\n", InputOutput::BasePath().c_str()); + wprintf(L"= Maximum Threads: %d\n", (int)InputOutput::MaxThreads()); + wprintf(L"= What If Mode: %s\n", InputOutput::InWhatIfMode() ? L"Yes" : L"No"); + wprintf(L"===============================================================================\n"); + + // do the scan + ULONGLONG iTimeStart = GetTickCount64(); + BeginFileScan(InputOutput::BasePath()); + ULONGLONG iTimeStop = GetTickCount64(); + + // print out statistics + wprintf(L"===============================================================================\n"); + wprintf(L"= Total Scanned: %llu\n", (ULONGLONG)iFilesScanned); + wprintf(L"= Read Failures: %llu\n", (ULONGLONG)iFilesEnumerationFailures); + wprintf(L"= Enumeration Failures: %llu\n", (ULONGLONG)iFilesReadFailures); + wprintf(L"= Update Successes: %llu\n", (ULONGLONG)iFilesUpdatedSuccess); + wprintf(L"= Update Failures: %llu\n", (ULONGLONG)iFilesUpdatedFailure); + wprintf(L"= Time Elapsed: %.3f\n", ((double)(iTimeStop - iTimeStart)) / 1000.0); + wprintf(L"= Note: Update statistics do not include changes due to inherited rights.\n"); + wprintf(L"===============================================================================\n"); +} \ No newline at end of file diff --git a/Notes.txt b/Notes.txt new file mode 100644 index 0000000..9a8cc10 --- /dev/null +++ b/Notes.txt @@ -0,0 +1,18 @@ +This file contains various information that may be useful in understanding +various elements of ACL, ACE, or SID contructs. + +IO CI OI DIR DOB SUB SOB + 0 0 0 1 0 0 0 icacls Exact /grant Everyone:F + 0 0 1 1 1 0 1 icacls Exact /grant Everyone:(OI)F + 0 1 0 1 0 1 0 icacls Exact /grant Everyone:(CI)F + 0 1 1 1 1 1 1 icacls Exact /grant Everyone:(CI)(OI)F + 1 0 0 0 0 0 0 icacls Exact /grant Everyone:(IO)F + 1 0 1 0 1 0 1 icacls Exact /grant Everyone:(OI)(IO)F + 1 1 0 0 0 1 0 icacls Exact /grant Everyone:(CI)(IO)F + 1 1 1 0 1 1 1 icacls Exact /grant Everyone:(CI)(OI)(IO)F + +Bit Mapping +DIR = ~IO +DOB = OI +SUB = CI +SOB = OI \ No newline at end of file diff --git a/Operation.cpp b/Operation.cpp new file mode 100644 index 0000000..2c70b08 --- /dev/null +++ b/Operation.cpp @@ -0,0 +1,186 @@ +#include "Operation.h" +#include "InputOutput.h" +#include "Functions.h" + +#include + +bool Operation::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // return immediately if acl is null + if (tCurrentAcl == NULL) return false; + + // flag to note whether a change was actually made + bool bMadeChange = false; + bool bSkipIncrement = false; + + ACCESS_ACE * tAce = FirstAce(tCurrentAcl); + for (ULONG iEntry = 0; iEntry < tCurrentAcl->AceCount; + tAce = (bSkipIncrement) ? tAce : NextAce(tAce), iEntry += (bSkipIncrement) ? 0 : 1) + { + // reset skip increment variable + bSkipIncrement = false; + + // do not bother with inherited aces + if (IsInherited(tAce)) continue; + + // convenience variable for sid associated with ace + PSID const tCurrentSid = &tAce->Sid; + + PSID tResultantSid; + SidActionResult tResult = DetermineSid(sSdPart, tObjectEntry, tCurrentSid, tResultantSid); + + if (tResult == SidActionResult::Remove) + { + DeleteAce(tCurrentAcl, iEntry); + bMadeChange = true; + bSkipIncrement = true; + continue; + } + else if (tResult == SidActionResult::Replace) + { + PSID const tOldSid = &tAce->Sid; + PSID const tNewSid = tResultantSid; + const DWORD iOldLen = GetLengthSid(tOldSid); + const DWORD iNewLen = GetLengthSid(tNewSid); + + // if the old sid in the ace matches the new sid, just return immediately + if (SidMatch(&tAce->Sid, tNewSid)) return false; + + // at this point, we know we are going to make a change so set the flag + bMadeChange = true; + + // if lengths are equals just overwrite the old sid with the new sid + if (iOldLen == iNewLen) + { + memcpy(tOldSid, tNewSid, iNewLen); + continue; + } + + // casted convenience variables + PACL const tAcl = tCurrentAcl; + PBYTE const tAclLoc = (PBYTE)tAcl; + PBYTE const tAceLoc = (PBYTE)tAce; + PBYTE const tOldSidLoc = (PBYTE)tOldSid; + + // if the new length is less than the old length, then copy the new sid + // and then shift the remaining bytes in the acl down to collapse the gap + if (iNewLen < iOldLen) + { + memcpy(tOldSid, tNewSid, iNewLen); + memmove(tOldSidLoc + iNewLen, tOldSidLoc + iOldLen, tAcl->AclSize - ((tOldSidLoc - tAclLoc) + iOldLen)); + } + + // if the size is bigger than we should expand the acl to accommodate the + // new size and then copy the various parts into the new memory + else + { + PBYTE const tNewAcl = (PBYTE)LocalAlloc(LMEM_FIXED, tAcl->AclSize + (iNewLen - iOldLen)); + memcpy(tNewAcl, tAclLoc, tOldSidLoc - tAclLoc); + memcpy(tNewAcl + (tOldSidLoc - tAclLoc), tNewSid, iNewLen); + memcpy(tNewAcl + (tOldSidLoc - tAclLoc) + iNewLen, tOldSidLoc + iOldLen, tAcl->AclSize - ((tOldSidLoc - tAclLoc) + iOldLen)); + + // free the existing pointer and update the value that was passed + if (bAclReplacement) LocalFree(tAcl); + tAce = (PACCESS_ACE)(tNewAcl + (tAceLoc - tAclLoc)); + tCurrentAcl = (PACL)tNewAcl; + bAclReplacement = true; + } + + // update size in ace header + tAce->Header.AceSize += (WORD)(iNewLen - iOldLen); + + // update size in acl header and return size differential + tCurrentAcl->AclSize += (WORD)(iNewLen - iOldLen); + } + } + + // return flag to indicate something has actually changed + return bMadeChange; +} + +bool Operation::ProcessSidAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID & tCurrentSid, bool & bSidReplacement) +{ + PSID tResultantSid; + SidActionResult tResult = DetermineSid(sSdPart, tObjectEntry, tCurrentSid, tResultantSid); + bool bMadeChange = false; + + if (tResult == SidActionResult::Remove) + { + // populate the default sid value to be used if the sid is empty + tCurrentSid = DefaultSidWhenEmpty; + bMadeChange = true; + } + else if (tResult == SidActionResult::Replace) + { + // only process change if sid is actually different + if (SidNotMatch(tCurrentSid, tResultantSid)) + { + // substitute sid + tCurrentSid = tResultantSid; + bMadeChange = true; + } + } + + // return flag to indicate something has actually changed + return bMadeChange; +} + +Operation::Operation(std::queue & oArgList) +{ + SID_IDENTIFIER_AUTHORITY tAuthNt = SECURITY_NT_AUTHORITY; + AllocateAndInitializeSid(&tAuthNt, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &DefaultSidWhenEmpty); +}; + +std::vector Operation::ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, std::wstring sDelimiter) +{ + // check if around arguments exist yet + if (iArgsRequired > 0 && oArgList.size() == 0) + { + exit(-1); + } + + // parse the parameters, splitting on : + std::wstring sArg = oArgList.front(); oArgList.pop(); + std::wregex oRegex(sDelimiter); + std::wsregex_token_iterator oFirst{ sArg.begin(), sArg.end(), oRegex, -1 }, oLast; + std::vector oSubArgs = { oFirst, oLast }; + + // verify we have enough parameters + if (oSubArgs.size() < (size_t) iArgsRequired) + { + exit(-1); + } + + // return the parsed args + return oSubArgs; +} + +void Operation::ProcessGranularTargetting(std::wstring sScope) +{ + // parse the parameters, splitting on : + std::wregex oRegex(L","); + std::wsregex_token_iterator oFirst{ sScope.begin(), sScope.end(), oRegex, -1 }, oLast; + std::vector sScopeOpts = { oFirst, oLast }; + + // default all to false if calling this method + AppliesToDacl = false; + AppliesToSacl = false; + AppliesToOwner = false; + AppliesToGroup = false; + + for (std::vector::iterator oScope = sScopeOpts.begin(); + oScope != sScopeOpts.end(); oScope++) + { + if (*oScope == L"DACL") AppliesToDacl = true; + else if (*oScope == L"SACL") AppliesToSacl = true; + else if (*oScope == L"OWNER") AppliesToOwner = true; + else if (*oScope == L"GROUP") AppliesToGroup = true; + else + { + // complain + wprintf(L"ERROR: Unrecognized scope qualifier '%s'\n", (*oScope).c_str()); + exit(-1); + } + } +} diff --git a/Operation.h b/Operation.h new file mode 100644 index 0000000..0c28c32 --- /dev/null +++ b/Operation.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Functions.h" + +// generic header for allow, deny, and audit aces +typedef struct _ACCESS_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + SID Sid; +} ACCESS_ACE; +typedef ACCESS_ACE *PACCESS_ACE; + +// macros to iterate through access control entries +#define FirstAce(Acl) ((ACCESS_ACE *)((PUCHAR)(Acl) + sizeof(ACL))) +#define NextAce(Ace) ((ACCESS_ACE *)((PUCHAR)(Ace) + ((PACE_HEADER)(Ace))->AceSize)) +#define NextAceWithRestart(Acl,Ace,Restart) ((Restart) ? FirstAce(Acl) : NextAce(Ace)) + +// define our own version of sid length since its faster +#define GetLengthSid(x) (sizeof(SID) + (((SID *) x)->SubAuthorityCount - 1) * sizeof(((SID *) x)->SubAuthority)) +#define SidMatch(x,y) (memcmp(x,y,min(GetLengthSid(x),GetLengthSid(y))) == 0) +#define SidNotMatch(x,y) (!SidMatch(x,y)) + +// macros for checking file attributes +#define CheckBitSet(x,y) ((x & y) != 0) +#define IsDirectory(x) CheckBitSet(x,FILE_ATTRIBUTE_DIRECTORY) +#define IsHiddenSystem(x) (CheckBitSet(x,FILE_ATTRIBUTE_HIDDEN) && CheckBitSet(x,FILE_ATTRIBUTE_SYSTEM)) +#define IsReparsePoint(x) (CheckBitSet(x,FILE_ATTRIBUTE_REPARSE_POINT)) + +// a few simple defines for convenience +#define IsInherited(x) CheckBitSet(x->Header.AceFlags,INHERITED_ACE) +#define HasContainerInherit(x) CheckBitSet(x->Header.AceFlags,CONTAINER_INHERIT_ACE) +#define HasObjectInherit(x) CheckBitSet(x->Header.AceFlags,OBJECT_INHERIT_ACE) +#define HasInheritOnly(x) CheckBitSet(x->Header.AceFlags,INHERIT_ONLY_ACE) +#define HasNoPropogate(x) CheckBitSet(x->Header.AceFlags,NO_PROPAGATE_INHERIT_ACE) +#define GetNonOiCiIoBits(x) ((~(CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE)) & x->Header.AceFlags) + +typedef struct ObjectEntry +{ + std::wstring Name; + DWORD Attributes; + bool IsRoot; +} +ObjectEntry; + +typedef enum SidActionResult : char +{ + Nothing, + Replace, + Remove, + Add, +} +SidActionResult; + +class Operation +{ +protected: + + static std::vector ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, std::wstring sDelimiter = L":"); + void ProcessGranularTargetting(std::wstring sScope); + +public: + + bool AppliesToDacl = false; + bool AppliesToSacl = false; + bool AppliesToOwner = false; + bool AppliesToGroup = false; + bool AppliesToSd = false; + + bool AppliesToRootOnly = false; + bool AppliesToChildrenOnly = false; + bool ExclusiveOperation = false; + + DWORD SpecialCommitFlags = false; + PSID DefaultSidWhenEmpty = NULL; + + virtual bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) { return false; } + virtual bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement); + virtual bool ProcessSidAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID & tCurrentSid, bool & bSidReplacement); + virtual SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) { return SidActionResult::Nothing; } + + Operation(std::queue & oArgList); +}; + +#include "OperationFactory.h" \ No newline at end of file diff --git a/OperationAddAccountIfMissing.cpp b/OperationAddAccountIfMissing.cpp new file mode 100644 index 0000000..1127670 --- /dev/null +++ b/OperationAddAccountIfMissing.cpp @@ -0,0 +1,86 @@ +#include "OperationAddAccountIfMissing.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationAddAccountIfMissing::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationAddAccountIfMissing::OperationAddAccountIfMissing(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // fetch params + tAddSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tAddSid == NULL) + { + // complain + wprintf(L"ERROR: Invalid account '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // do a reverse lookup on the name for info messages + sAddSid = GetNameFromSidEx(tAddSid); + + // flag this as being an ace-level action + AppliesToDacl = true; +} + +bool OperationAddAccountIfMissing::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // check explicit effective rights from sid (no groups) + DWORD iPermissionMask = 0; + DWORD iInheritMask = 0; + if (tCurrentAcl != NULL) + { + ACCESS_ACE * tAceDacl = FirstAce(tCurrentAcl); + for (ULONG iEntry = 0; iEntry < tCurrentAcl->AceCount; tAceDacl = NextAce(tAceDacl), iEntry++) + { + // skip other ace types that are not allowing accessing + if (tAceDacl->Header.AceType != ACCESS_ALLOWED_ACE_TYPE) continue; + + // do not look at sids that are not the specified account + if (SidNotMatch(&tAceDacl->Sid, tAddSid)) continue; + + // merge if the effective permissions + // ignore the inherited ace flag since its not relevant + iPermissionMask |= tAceDacl->Mask; + iInheritMask |= (tAceDacl->Header.AceFlags & ~INHERITED_ACE); + } + } + + // define what constitutes 'full control' based on the object + const DWORD iDesiredInheritMask = IsDirectory(tObjectEntry.Attributes) + ? (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE) : 0; + + // only attempt to add permissions if our flags do not match + if (iPermissionMask != FILE_ALL_ACCESS || iInheritMask != iDesiredInheritMask) + { + EXPLICIT_ACCESS tEa; + tEa.grfAccessPermissions = FILE_ALL_ACCESS; + tEa.grfAccessMode = GRANT_ACCESS; + tEa.grfInheritance = iDesiredInheritMask; + tEa.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + tEa.Trustee.pMultipleTrustee = NULL; + tEa.Trustee.ptstrName = (LPWSTR) tAddSid; + tEa.Trustee.TrusteeForm = TRUSTEE_IS_SID; + tEa.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + + // merge the new trustee into the dacl + PACL tNewDacl; + SetEntriesInAcl(1, &tEa, tCurrentAcl, &tNewDacl); + + // cleanup the old dacl (if necessary) and assign our new active dacl + if (bAclReplacement) LocalFree(tCurrentAcl); + tCurrentAcl = tNewDacl; + + // flag to commit tag and cleanup dacl + InputOutput::AddInfo(L"Adding full control for '" + sAddSid + L"'", sSdPart); + bAclReplacement = true; + return true; + } + + return false; +} diff --git a/OperationAddAccountIfMissing.h b/OperationAddAccountIfMissing.h new file mode 100644 index 0000000..260c0da --- /dev/null +++ b/OperationAddAccountIfMissing.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationAddAccountIfMissing : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"AddAccountIfMissing"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tAddSid = nullptr; + std::wstring sAddSid = L""; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationAddAccountIfMissing(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationCheckCanonical.cpp b/OperationCheckCanonical.cpp new file mode 100644 index 0000000..11471e3 --- /dev/null +++ b/OperationCheckCanonical.cpp @@ -0,0 +1,51 @@ +#include "OperationCheckCanonical.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationCheckCanonical::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationCheckCanonical::OperationCheckCanonical(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; +} + +bool OperationCheckCanonical::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // sanity check (null acl is considered valid) + if (tCurrentAcl == NULL) return false; + + enum AceOrder : unsigned char + { + Unspecified = 0, + Explicit = 1 << 0, + Deny = 1 << 1, + Allow = 1 << 2, + Inherited = 1 << 3 + }; + + unsigned char oOrderOverall = Unspecified; + ACCESS_ACE * tAce = FirstAce(tCurrentAcl); + for (ULONG iEntry = 0; iEntry < tCurrentAcl->AceCount; tAce = NextAce(tAce), iEntry++) + { + // check inheritance bits + unsigned char oThisAceOrder = (IsInherited(tAce)) ? Inherited : Unspecified; + + // check allow/deny + oThisAceOrder |= (tAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) ? Allow : Unspecified; + oThisAceOrder |= (tAce->Header.AceType == ACCESS_DENIED_ACE_TYPE) ? Deny : Unspecified; + + // make sure this order is not less then the current order + if (oThisAceOrder < oOrderOverall) + { + InputOutput::AddInfo(L"Access control list is not canonical", sSdPart); + return false; + } + oOrderOverall = oThisAceOrder; + } + + // report the + return false; +} diff --git a/OperationCheckCanonical.h b/OperationCheckCanonical.h new file mode 100644 index 0000000..8813e09 --- /dev/null +++ b/OperationCheckCanonical.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationCheckCanonical : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"CheckCanonical"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationCheckCanonical(std::queue & oArgList); +}; + diff --git a/OperationCompact.cpp b/OperationCompact.cpp new file mode 100644 index 0000000..b7812ec --- /dev/null +++ b/OperationCompact.cpp @@ -0,0 +1,84 @@ +#include "OperationCompact.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" +#include "Functions.h" + +#include + +ClassFactory * OperationCompact::RegisteredFactory = +new ClassFactory(GetCommand()); +std::atomic iTestCount; + +OperationCompact::OperationCompact(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; +} + +bool OperationCompact::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // sanity check + if (tCurrentAcl == NULL) return false; + + // track whether the acl was actually change so the caller may decide + // that the change needs to be persisted + bool bMadeChange = false; + + ACCESS_ACE * tAceOuter = FirstAce(tCurrentAcl); + for (ULONG iEntryOuter = 0; iEntryOuter < tCurrentAcl->AceCount; tAceOuter = NextAce(tAceOuter), iEntryOuter++) + { + // only process standard ace types + if (tAceOuter->Header.AceType != ACCESS_ALLOWED_ACE_TYPE && + tAceOuter->Header.AceType != ACCESS_DENIED_ACE_TYPE && + tAceOuter->Header.AceType != SYSTEM_AUDIT_ACE_TYPE) continue; + + // only process explicit entires + if (IsInherited(tAceOuter)) continue; + + bool bSkipIncrement = false; + ACCESS_ACE * tAceInner = NextAce(tAceOuter); + for (ULONG iEntryInner = iEntryOuter + 1; iEntryInner < tCurrentAcl->AceCount; + tAceInner = (bSkipIncrement) ? tAceInner : NextAce(tAceInner), iEntryInner += (bSkipIncrement) ? 0 : 1) + { + // reset skip increment variable + bSkipIncrement = false; + + // stop processing completely if the flags are not identical or + // the flags aren't mergeable with identical masks + if (!(tAceInner->Header.AceFlags == tAceOuter->Header.AceFlags) && + !((tAceInner->Mask == tAceOuter->Mask) && + (GetNonOiCiIoBits(tAceInner) == GetNonOiCiIoBits(tAceOuter)))) continue; + + // stop processing completely if we have a mismatching type + if (tAceInner->Header.AceType != tAceOuter->Header.AceType) continue; + + // if sids are equal then delete this ace + if (SidMatch(&tAceInner->Sid, &tAceOuter->Sid)) + { + // the CI and OI flags of entries are mergable since they both add additional + // permissions. however, the IO flags effectively blocks access to the parent + // container so this is merged by setting the bit to zero if either one of the two + // entries has it unset. + tAceOuter->Header.AceFlags |= (tAceInner->Header.AceFlags & CONTAINER_INHERIT_ACE); + tAceOuter->Header.AceFlags |= (tAceInner->Header.AceFlags & OBJECT_INHERIT_ACE); + tAceOuter->Header.AceFlags &= (!HasInheritOnly(tAceInner) || !HasInheritOnly(tAceOuter)) ? ~INHERIT_ONLY_ACE : ~0; + + // per previous checks, the masks are either idential or mergable so we can + // unconditionally or them together + tAceOuter->Mask |= tAceInner->Mask; + + // cleanup the old entry and setup the next interaction to reach the + // check on the current index + InputOutput::AddInfo(L"Compacted entries for '" + + GetNameFromSidEx(&tAceInner->Sid) + L"'", sSdPart); + DeleteAce(tCurrentAcl, iEntryInner); + iEntryInner = iEntryOuter; + tAceInner = tAceOuter; + bMadeChange = true; + } + } + } + + return bMadeChange; +} diff --git a/OperationCompact.h b/OperationCompact.h new file mode 100644 index 0000000..bd6a931 --- /dev/null +++ b/OperationCompact.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationCompact : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Compact"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationCompact(std::queue & oArgList); +}; + diff --git a/OperationExportDescriptor.cpp b/OperationExportDescriptor.cpp new file mode 100644 index 0000000..9a5e81c --- /dev/null +++ b/OperationExportDescriptor.cpp @@ -0,0 +1,58 @@ +#include "OperationExportDescriptor.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationExportDescriptor::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationExportDescriptor::OperationExportDescriptor(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList, L"\\0"); + + // fetch params + hFile = CreateFile(sSubArgs[0].c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // see if names could be resolved + if (hFile == INVALID_HANDLE_VALUE) + { + // complain + wprintf(L"ERROR: Could not create file '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // flag this as being an ace-level action + AppliesToSd = true; + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToOwner = true; + AppliesToGroup = true; +} + +bool OperationExportDescriptor::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) +{ + // convert the current security descriptor to a string + WCHAR * sInfo = NULL; + if (ConvertSecurityDescriptorToStringSecurityDescriptor(tSecurityDescriptor, SDDL_REVISION_1, + DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + &sInfo, NULL) == 0) + { + InputOutput::AddError(L"ERROR: Unable to generate string security descriptor."); + return false; + } + + // write the string to a file + DWORD iBytes = 0; + std::wstring sToWrite = sFileName + L"|" + sInfo + L"\r\n"; + if (WriteFile(hFile, sToWrite.c_str(), (DWORD)sToWrite.size() * sizeof(WCHAR), &iBytes, NULL) == 0) + { + LocalFree(sInfo); + InputOutput::AddError(L"ERROR: Unable to write security descriptor to file."); + return false; + } + + // cleanup + LocalFree(sInfo); + return false; +} diff --git a/OperationExportDescriptor.h b/OperationExportDescriptor.h new file mode 100644 index 0000000..8024cc1 --- /dev/null +++ b/OperationExportDescriptor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationExportDescriptor : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"ExportDescriptor"; } + static ClassFactory * RegisteredFactory; + + HANDLE hFile = INVALID_HANDLE_VALUE; + std::wstring sFile = L""; + +public: + + // overrides + bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) override; + + // constructors + OperationExportDescriptor(std::queue & oArgList); +}; + diff --git a/OperationFactory.h b/OperationFactory.h new file mode 100644 index 0000000..7e6fafd --- /dev/null +++ b/OperationFactory.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Operation.h" + +class FactoryPlant +{ +protected: + + virtual Operation * CreateInstanceSub(std::queue & oArgList) = 0; + + static std::map & GetCommands() + { + static std::map vCommands; + return vCommands; + } + +public: + + static Operation * CreateInstance(std::queue & oArgList) + { + // get the first element off the list + std::wstring sCommand = oArgList.front(); oArgList.pop(); + + // error if the string is least one character long + if (sCommand.size() == 0) return nullptr; + + // error if the string does not start with "/" or "-" + if (sCommand.at(0) != '/' && sCommand.at(0) != '-') + { + wprintf(L"ERROR: Unrecognized parameter '%s'\n", sCommand.c_str()); + exit(-1); + } + + // convert to uppercase for map matching + std::transform(sCommand.begin(), sCommand.end(), sCommand.begin(), ::toupper); + + // remove the first character + sCommand.erase(0, 1); + + // see if there's a class that matches this + std::map::iterator + oCommand = GetCommands().find(sCommand); + + // error if there is no matching command + if (oCommand == GetCommands().end()) + { + wprintf(L"ERROR: Unrecognized parameter '%s'\n", sCommand.c_str()); + exit(-1); + } + + // create the the new class + return GetCommands()[sCommand]->CreateInstanceSub(oArgList); + } +}; + +template class ClassFactory : public FactoryPlant +{ +private: + + Operation * CreateInstanceSub(std::queue & oArgList) + { + return new SubType(oArgList); + } + +public: + + ClassFactory(std::wstring sCommand) + { + std::transform(sCommand.begin(), sCommand.end(), sCommand.begin(), ::toupper); + GetCommands()[sCommand] = this; + }; +}; diff --git a/OperationFindAccount.cpp b/OperationFindAccount.cpp new file mode 100644 index 0000000..747dbbe --- /dev/null +++ b/OperationFindAccount.cpp @@ -0,0 +1,46 @@ +#include "OperationFindAccount.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationFindAccount::RegisteredFactory = + new ClassFactory(GetCommand()); + +OperationFindAccount::OperationFindAccount(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // decode the passed parameter to an account name + tFindSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tFindSid == nullptr) + { + // complain + wprintf(L"ERROR: Invalid account '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(0); + } + + // reverse lookup the sid for reporting + sFindSid = GetNameFromSidEx(tFindSid); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 1) ProcessGranularTargetting(sSubArgs[1]); +} + +SidActionResult OperationFindAccount::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // check if the sid matches the ace + if (SidMatch(tCurrentSid, tFindSid)) + { + InputOutput::AddInfo(L"Found identifier '" + sFindSid + L"'", sSdPart, true); + }; + + return SidActionResult::Nothing; +} diff --git a/OperationFindAccount.h b/OperationFindAccount.h new file mode 100644 index 0000000..3ccd9bc --- /dev/null +++ b/OperationFindAccount.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationFindAccount : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"FindAccount"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tFindSid = nullptr; + std::wstring sFindSid = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationFindAccount(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationFindDomain.cpp b/OperationFindDomain.cpp new file mode 100644 index 0000000..3ad377b --- /dev/null +++ b/OperationFindDomain.cpp @@ -0,0 +1,51 @@ +#include "OperationFindDomain.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationFindDomain::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationFindDomain::OperationFindDomain(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // decode the passed parameter to an account name + tDomainSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tDomainSid == nullptr) + { + // complain + wprintf(L"ERROR: Invalid domain '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(0); + } + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 1) ProcessGranularTargetting(sSubArgs[1]); +} + +SidActionResult OperationFindDomain::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // see if this sid in the source domain + BOOL bDomainSidsEqual = FALSE; + if (EqualDomainSid(tCurrentSid, tDomainSid, &bDomainSidsEqual) == 0 || + bDomainSidsEqual == FALSE) + { + // no match - cease processing this instruction + return SidActionResult::Nothing; + } + + // resolve the sid for reporting + std::wstring sAccount = GetNameFromSidEx(tCurrentSid); + + // report the + InputOutput::AddInfo(L"Found domain identifier '" + sDomainSid + L"' on account '" + sAccount + L"'", sSdPart); + return SidActionResult::Nothing; +} diff --git a/OperationFindDomain.h b/OperationFindDomain.h new file mode 100644 index 0000000..3279637 --- /dev/null +++ b/OperationFindDomain.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationFindDomain : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"FindDomain"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tDomainSid = nullptr; + std::wstring sDomainSid = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationFindDomain(std::queue & oArgList); +}; diff --git a/OperationFindNullAcl.cpp b/OperationFindNullAcl.cpp new file mode 100644 index 0000000..155c7de --- /dev/null +++ b/OperationFindNullAcl.cpp @@ -0,0 +1,24 @@ +#include "OperationFindNullAcl.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" + +ClassFactory * OperationFindNullAcl::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationFindNullAcl::OperationFindNullAcl(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; +} + +bool OperationFindNullAcl::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // sanity check (null acl is considered valid) + if (tCurrentAcl == NULL) + { + InputOutput::AddInfo(L"Access control list is null", sSdPart); + } + + // report the + return false; +} diff --git a/OperationFindNullAcl.h b/OperationFindNullAcl.h new file mode 100644 index 0000000..5ad7d68 --- /dev/null +++ b/OperationFindNullAcl.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationFindNullAcl : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"FindNullAcl"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationFindNullAcl(std::queue & oArgList); +}; + diff --git a/OperationHelp.cpp b/OperationHelp.cpp new file mode 100644 index 0000000..4ba04c3 --- /dev/null +++ b/OperationHelp.cpp @@ -0,0 +1,230 @@ +#include "OperationHelp.h" + +#include + +ClassFactory * OperationHelp::RegisteredFactory = + new ClassFactory(GetCommand()); +ClassFactory * OperationHelp::RegisteredFactoryAltOne = + new ClassFactory(GetCommandAltOne()); +ClassFactory * OperationHelp::RegisteredFactoryAltTwo = + new ClassFactory(GetCommandAltTwo()); + +OperationHelp::OperationHelp(std::queue & oArgList) : Operation(oArgList) +{ + std::wcout << +LR"( +repacls.exe /Path ... other options .... + +Repacls was developed to address large scale migrations, transitions, health +checks, and access control optimizations. Repacls is multi-threaded and +employs account name caching to accelerate operation on large file servers +with millions of files. It was developed to address a variety of platform +limitations, resource consumption concerns, and bugs within xcacls, icacls, +setacl, and subinacl. + +Important: Unless otherwise specified, all repacls commands are recursive. + +Repacls must be executed with administrator permissions and will attempt to +acquire the backup, restore, and take ownership privileges during its +execution. + +Any command line parameter that accepts an account or domain name can also use +a SID string instead of the name. This may be necessary if working with an +account or domain that is no longer resolvable. + +Global Options +============== +Global Options affect the entire command regardless of where they appear in the +passed command line. It is recommended to include them at the very beginning +or end of your command as to not confuse them with ordered parameters. + +/Path + Specifies the file or directory to process. If a directory, the directory + is processed recursively; all operations specified affect the directory + and all files and folders under the directory (unless otherwise specified). + This parameter is mandatory. + +/Quiet + Hides all non-error output. This option will greatly enhance performance if + a large number of changes are being processed. Alternatively, it is + advisable to redirect console output to a file (using the > redirector) if + /Quiet cannot be specified. + +/Threads + Specifies the number of threads to use while processing. The default value + of '5' is usually adequate, but can be increased if performing changes + over a higher-latency connection. Since changes to a parent directory + often affect the inherited security on children, the security of children + objects are always processed after the the security on their parent objects + are fully processed. + +/WhatIf + This option will analyze security and report on any potential changes + without actually committing the data. Use of /WhatIf is recommended for + those first using the tool. + +/NoHiddenSystem + Use this option to avoid processing any file marked as both 'hidden' and + 'system'. These are what Windows refers to 'operating system protected + files' in Windows Explorer. + +Ordered Options +=============== +Ordered Options are executed on each SID encountered in the security descriptor +in the order they are specified on the command line. Executing commands in +this way is preferable to multiple commands because the security descriptor is +only read and written once for the entire command which is especially helpful +for large volumes. + +Commands That Do Not Alter Security +----------------------------------- +/PrintDescriptor + Prints out the security descriptor to the screen. This is somewhat useful + for seeing the under-the-cover changes to the security descriptor before + and after a particular command. + +/CheckCanonical + This command inspects the DACL and SACL for canonical order compliance + (i.e., the rules in the ACL are ordered as explicitly deny, explicitly allow, + inherited deny, inherited allow). If non-canonical entries are detected, + it is recommended to inspect the ACL with icacls.exe or Windows Explorer + to ensure the ACL is not corrupted in a more significant way. + +/ExportDescriptor + Export the security descriptor to the file specified. The file is + outputted in the format of file|descriptor on each line. The security + descriptor is formated as specified in the documentation for + ConvertSecurityDescriptorToStringSecurityDescriptor(). + +/FindAccount + Reports any instance of an account specified. + +/FindDomain + Reports any instance of an account matching the specified domain. + +/FindNullAcl + Reports any instance of a null ACL. A null ACL, unlike an empty ACL, allows + all access (i.e., similar to an ACE with 'Everyone' with 'Full Control') + +Commands That Can Alter Security (When /WhatIf Is Not Present) +-------------------------------- +/AddAccountIfMissing + This command will ensure the account specified has full control access to + path and all directories/files under the path either via explicit or + inherited permissions. This command does not take into account any + permissions that would be granted to the specified user via group + membership. If the account does not have access, access is + granted. This command is useful to correct issues where a user or + administrator has mistakenly removed an administrative group from some + directories. + +/Compact + This command will look for mergeable entires in the security descriptor and + merge them. For example, running icacls.exe /grant Everyone:R + followed by icacls.exe /grant Everyone:(CI)(OI)(R) will produce + two entries even those the second command supersedes the first one. + Windows Explorer automatically merges these entries when display security + information so you have to use other utilities to detect these + inefficiencies. While these's nothing inherently wrong with these + entires, it possible for them to result file system is performance + degradation. + +/MigrateDomain : + This command will look to see whether any account in + has an identically-named account in . If so, any entires + are converted to use the new domain. For example, + 'OldDomain\Domain Admins' would become 'NewDomain\Domain Admins'. Since + this operation relies on the names being resolvable, specifying a SID + instead of domain name for this command does not work. + +/RemoveAccount + Will remove from the security descriptor. If the specified name + is found as the file owner, the owner is replaced by the builtin + Administrators group. If the specified name is found as the group owner + (a defunct attribute that has no function in terms of security), it is + also replace with the built-in Administrators group. + +/RemoveOrphans + Remove any account whose SID is derived from the specified + and can no longer be resolved to a valid name. + +/RemoveRedundant + This command will remove any explicit permission that is redundant of + of the permissions its already given through inheritance. This option + helps recovered from the many individual explicit permissions that may + have been littered from the old cacls.exe command that didn't understand + how to set up inheritance. + +/ReplaceAccount + Search for an account and replace it with another account. + +/Report + This command will write a comma separated value file with the fields of + filename, security descriptor part (e.g., DACL), account name, permissions, + and inheritance flags. The regular expression will perform a case + insensitive regular expression search against the account name in + DOMAIN\user format. To report all data, pass .* as the regular expression. + An optional qualifier after regular expression can be specified after the + regular expression to refine what part of the security descriptor to scan. + See Other Notes & Limitations section for more information. + +/SetOwner + Will set the owner of the file to the name specified. + +/UpdateHistoricalSids + Will convert any instances of old SIDs present in the security descriptor + to there to active SID currently associated with the account. This is + especially useful after a domain migration and prior to removing + excess SID history on accounts. +)"; + +std::wcout << +LR"( +Exclusive Options +================= +/Help or /? or /H + Shows this information. + +/ResetChildren + This will reset all children of path to the to inherit from the parent. It + will not affect the security of the parent. This command does not affect + the security the root directory as specified by the /Path argument. + +/InheritChildren + This will cause any parent that is currently set to block inheritance to + start allowing inheritance. Any explicit entries on the children are + preserved. This command does not will not affect the security the root + directory as specified by the /Path argument. + +Other Notes & Limitations +========================= +To only affect a particular part of a security descriptor, you can add on an +optional ':X' parameter after the end of the account name where X is a comma +separated list of DACL,SACL, OWNER, or GROUP. For example, +'/RemoveAccount "DOM\joe:DACL,OWNER"' will only cause the designated account +to be removed from the DACL and OWNER parts of the security descriptor. + +Since repacls is multi-threaded, any file output shown on the screen or +written to an output file may appear differently between executions. In this +is problematic for your needs, you can turn off multi-threading by setting +/Threads to '1' or, in the case of comparing files between runs, sort the +output before comparing with your favorite text editor. + +Examples +======== +- Replace all instances of DOM\jack to DOM\jill in C:\test: + repacls.exe /Path C:\Test /ReplaceAccount "DOM\jack:DOM\jill" + +- Migrate all permissions for all accounts with matching + names in DOMA with DOMB: + repacls.exe /Path C:\Test /MigrateDomain DOMA:DOMB + +- Update old SID references, remove any explicit permissions that are already + granted by inherited permissions, and compact all ACLs if not compacted: + repacls.exe /Path C:\Test /UpdateHistoricalSids /RemoveRedundant /Compact + +Type 'repacls.exe /? | more' to scroll this documentation. +)"; + + exit(0); +} \ No newline at end of file diff --git a/OperationHelp.h b/OperationHelp.h new file mode 100644 index 0000000..417cce8 --- /dev/null +++ b/OperationHelp.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationHelp : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Help"; } + static std::wstring GetCommandAltOne() { return L"?"; } + static std::wstring GetCommandAltTwo() { return L"H"; } + static ClassFactory * RegisteredFactory; + static ClassFactory * RegisteredFactoryAltOne; + static ClassFactory * RegisteredFactoryAltTwo; + +public: + + // constructors + OperationHelp(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationInheritChildren.cpp b/OperationInheritChildren.cpp new file mode 100644 index 0000000..8f21055 --- /dev/null +++ b/OperationInheritChildren.cpp @@ -0,0 +1,23 @@ +#include "OperationInheritChildren.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationInheritChildren::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationInheritChildren::OperationInheritChildren(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToChildrenOnly = true; + ExclusiveOperation = true; + SpecialCommitFlags = UNPROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION; +} + +bool OperationInheritChildren::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // nothing to do -- the special commit flags will take care of it + return true; +} diff --git a/OperationInheritChildren.h b/OperationInheritChildren.h new file mode 100644 index 0000000..3a04512 --- /dev/null +++ b/OperationInheritChildren.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationInheritChildren : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"InheritChildren"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationInheritChildren(std::queue & oArgList); +}; + diff --git a/OperationMigrateDomain.cpp b/OperationMigrateDomain.cpp new file mode 100644 index 0000000..2bf97c9 --- /dev/null +++ b/OperationMigrateDomain.cpp @@ -0,0 +1,73 @@ +#include "OperationMigrateDomain.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationMigrateDomain::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationMigrateDomain::OperationMigrateDomain(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(2, oArgList); + + // fetch params + tSourceDomain = GetSidFromName(sSubArgs[0]); + tTargetDomain = GetSidFromName(sSubArgs[1]); + + // see if names could be resolved + if (tSourceDomain == nullptr) + { + // complain + wprintf(L"ERROR: Invalid source domain '%s' specified for parameter '%s'.\n", sSourceDomain.c_str(), GetCommand().c_str()); + exit(0); + } + + // see if names could be resolved + if (tTargetDomain == nullptr) + { + // complain + wprintf(L"ERROR: Invalid target domain'%s' specified for parameter '%s'.\n", sTargetDomain.c_str(), GetCommand().c_str()); + exit(0); + } + + // store the domain strings + sSourceDomain = GetNameFromSidEx(tSourceDomain); + sTargetDomain = GetNameFromSidEx(tTargetDomain); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 2) ProcessGranularTargetting(sSubArgs[2]); +} + +SidActionResult OperationMigrateDomain::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // see if this sid in the source domain + BOOL bDomainSidsEqual = FALSE; + if (EqualDomainSid(tCurrentSid, tSourceDomain, &bDomainSidsEqual) == 0 || + bDomainSidsEqual == FALSE) + { + // no match - cease processing this instruction + return SidActionResult::Nothing; + } + + // translate the old sid to an account name + std::wstring sSourceAccountName = GetNameFromSid(tCurrentSid, NULL); + if (sSourceAccountName.size() == 0) return SidActionResult::Nothing; + + // check to see if an equivalent account exists in the target domain + std::wstring sTargetAccountName = sTargetDomain + (wcsstr(sSourceAccountName.c_str(), L"\\") + 1); + PSID tTargetAccountSid = GetSidFromName(sTargetAccountName); + + // stop processing if the account does not exist + if (tTargetAccountSid == nullptr) return SidActionResult::Nothing; + + // update the sid in the ace + InputOutput::AddInfo(L"Migrating '" + sSourceAccountName + L"' to '" + sTargetAccountName + L"'", sSdPart); + tResultantSid = tTargetAccountSid; + return SidActionResult::Replace; +} diff --git a/OperationMigrateDomain.h b/OperationMigrateDomain.h new file mode 100644 index 0000000..232e998 --- /dev/null +++ b/OperationMigrateDomain.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Operation.h" + +class OperationMigrateDomain : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"MigrateDomain"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tSourceDomain = nullptr; + std::wstring sSourceDomain = L""; + PSID tTargetDomain = nullptr; + std::wstring sTargetDomain = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationMigrateDomain(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationNoHiddenSystem.cpp b/OperationNoHiddenSystem.cpp new file mode 100644 index 0000000..fe896e6 --- /dev/null +++ b/OperationNoHiddenSystem.cpp @@ -0,0 +1,10 @@ +#include "OperationNoHiddenSystem.h" +#include "InputOutput.h" + +ClassFactory * OperationNoHiddenSystem::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationNoHiddenSystem::OperationNoHiddenSystem(std::queue & oArgList) : Operation(oArgList) +{ + InputOutput::ExcludeHiddenSystem() = true; +} \ No newline at end of file diff --git a/OperationNoHiddenSystem.h b/OperationNoHiddenSystem.h new file mode 100644 index 0000000..5b21b5e --- /dev/null +++ b/OperationNoHiddenSystem.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Operation.h" + +class OperationNoHiddenSystem : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"NoHiddenSystem"; } + static ClassFactory * RegisteredFactory; + +public: + + // constructors + OperationNoHiddenSystem(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationPath.cpp b/OperationPath.cpp new file mode 100644 index 0000000..ed50004 --- /dev/null +++ b/OperationPath.cpp @@ -0,0 +1,22 @@ +#include "OperationPath.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationPath::RegisteredFactory = + new ClassFactory(GetCommand()); + +OperationPath::OperationPath(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList, L"\\0"); + + // verify this parameter has only be specified once + if (InputOutput::BasePath().size() > 0) + { + wprintf(L"%s\n", L"ERROR: Path can only be specified once."); + exit(-1); + } + + // store off the argument + InputOutput::BasePath() = sSubArgs[0]; +}; \ No newline at end of file diff --git a/OperationPath.h b/OperationPath.h new file mode 100644 index 0000000..b1ddf09 --- /dev/null +++ b/OperationPath.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Operation.h" + +class OperationPath : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Path"; } + static ClassFactory * RegisteredFactory; + +public: + + // constructors + OperationPath(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationPrintDescriptor.cpp b/OperationPrintDescriptor.cpp new file mode 100644 index 0000000..90750d0 --- /dev/null +++ b/OperationPrintDescriptor.cpp @@ -0,0 +1,35 @@ +#include "OperationPrintDescriptor.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationPrintDescriptor::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationPrintDescriptor::OperationPrintDescriptor(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToSd = true; + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToOwner = true; + AppliesToGroup = true; +} + +bool OperationPrintDescriptor::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) +{ + // convert the current security descriptor to a string + WCHAR * sInfo = NULL; + if (ConvertSecurityDescriptorToStringSecurityDescriptor(tSecurityDescriptor, SDDL_REVISION_1, + DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + &sInfo, NULL) == 0) + { + InputOutput::AddError(L"Unable to generate string security descriptor."); + return false; + } + + // write to screen + InputOutput::AddInfo(L"SD: " + std::wstring(sInfo), L"", true); + LocalFree(sInfo); + return false; +} diff --git a/OperationPrintDescriptor.h b/OperationPrintDescriptor.h new file mode 100644 index 0000000..0c08155 --- /dev/null +++ b/OperationPrintDescriptor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationPrintDescriptor : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"PrintDescriptor"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) override; + + // constructors + OperationPrintDescriptor(std::queue & oArgList); +}; + diff --git a/OperationQuiet.cpp b/OperationQuiet.cpp new file mode 100644 index 0000000..11bc543 --- /dev/null +++ b/OperationQuiet.cpp @@ -0,0 +1,10 @@ +#include "OperationQuiet.h" +#include "InputOutput.h" + +ClassFactory * OperationQuiet::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationQuiet::OperationQuiet(std::queue & oArgList) : Operation(oArgList) +{ + InputOutput::InQuietMode() = true; +} \ No newline at end of file diff --git a/OperationQuiet.h b/OperationQuiet.h new file mode 100644 index 0000000..cd135d5 --- /dev/null +++ b/OperationQuiet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Operation.h" + +class OperationQuiet : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Quiet"; } + static ClassFactory * RegisteredFactory; + +public: + + // constructors + OperationQuiet(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationRemoveAccount.cpp b/OperationRemoveAccount.cpp new file mode 100644 index 0000000..e5be0ae --- /dev/null +++ b/OperationRemoveAccount.cpp @@ -0,0 +1,49 @@ +#include "OperationRemoveAccount.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationRemoveAccount::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationRemoveAccount::OperationRemoveAccount(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // fetch params + tRemoveSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tRemoveSid == nullptr) + { + // complain + wprintf(L"ERROR: Invalid account '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // do a reverse lookup on the name for info messages + sRemoveSid = GetNameFromSidEx(tRemoveSid); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 1) ProcessGranularTargetting(sSubArgs[1]); +} + +SidActionResult OperationRemoveAccount::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // only process if sid matches + if (SidNotMatch(tCurrentSid, tRemoveSid)) + { + return SidActionResult::Nothing; + } + + // update the sid in the ace + InputOutput::AddInfo(L"Removing account '" + sRemoveSid + L"'", sSdPart); + tResultantSid = NULL; + return SidActionResult::Remove; +} diff --git a/OperationRemoveAccount.h b/OperationRemoveAccount.h new file mode 100644 index 0000000..b06739b --- /dev/null +++ b/OperationRemoveAccount.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationRemoveAccount : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"RemoveAccount"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tRemoveSid = nullptr; + std::wstring sRemoveSid = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationRemoveAccount(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationRemoveOrphan.cpp b/OperationRemoveOrphan.cpp new file mode 100644 index 0000000..23f9511 --- /dev/null +++ b/OperationRemoveOrphan.cpp @@ -0,0 +1,61 @@ +#include "OperationRemoveOrphan.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationRemoveOrphan::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationRemoveOrphan::OperationRemoveOrphan(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // decode the passed parameter to an account name + tDomainSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tDomainSid == nullptr) + { + // complain + wprintf(L"ERROR: Invalid domain '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(0); + } + + // do a reverse lookup of the name for reporting + sDomainName = GetNameFromSidEx(tDomainSid); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 1) ProcessGranularTargetting(sSubArgs[1]); +} + +SidActionResult OperationRemoveOrphan::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // only bother doing a domain check if a domain was specified + if (tDomainSid != NULL) + { + // see if this sid in the source domain + BOOL bDomainSidsEqual = FALSE; + if (EqualDomainSid(tCurrentSid, tDomainSid, &bDomainSidsEqual) == 0 || + bDomainSidsEqual == FALSE) + { + // no match - cease processing this instruction + return SidActionResult::Nothing; + } + } + + // see if the sid is unresolvable; if it is then this is not an orphan + bool bIsOrphan = false; + GetNameFromSid(tCurrentSid, NULL); + if (!bIsOrphan) return SidActionResult::Nothing; + + // update the sid in the ace + InputOutput::AddInfo(L"Removing orphan of security identifier or domain '" + sDomainName + L"'", sSdPart); + tResultantSid = NULL; + return SidActionResult::Remove; +} diff --git a/OperationRemoveOrphan.h b/OperationRemoveOrphan.h new file mode 100644 index 0000000..e3a7c44 --- /dev/null +++ b/OperationRemoveOrphan.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationRemoveOrphan : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"RemoveOrphans"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tDomainSid = nullptr; + std::wstring sDomainName = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationRemoveOrphan(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationRemoveRedundant.cpp b/OperationRemoveRedundant.cpp new file mode 100644 index 0000000..d311ca1 --- /dev/null +++ b/OperationRemoveRedundant.cpp @@ -0,0 +1,78 @@ +#include "OperationRemoveRedundant.h" +#include "DriverKitPartial.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationRemoveRedundant::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationRemoveRedundant::OperationRemoveRedundant(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; +} + +bool OperationRemoveRedundant::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // sanity check + if (tCurrentAcl == NULL) return false; + + // track whether the acl was actually change so the caller may decide + // that the change needs to be persisted + bool bMadeChange = false; + bool bSkipIncrement = false; + + ACCESS_ACE * tAceExplicit = FirstAce(tCurrentAcl); + for (ULONG iEntryExplicit = 0; iEntryExplicit < tCurrentAcl->AceCount; + tAceExplicit = (bSkipIncrement) ? tAceExplicit : NextAce(tAceExplicit), iEntryExplicit += (bSkipIncrement) ? 0 : 1) + { + // reset skip increment variable + bSkipIncrement = false; + + // only process explicit items in the outer loop + if (IsInherited(tAceExplicit)) continue; + + // only process standard ace types + if (tAceExplicit->Header.AceType != ACCESS_ALLOWED_ACE_TYPE && + tAceExplicit->Header.AceType != ACCESS_DENIED_ACE_TYPE && + tAceExplicit->Header.AceType != SYSTEM_AUDIT_ACE_TYPE) continue; + + // assume we are increments on the next round + ACCESS_ACE * tAceInherited = FirstAce(tCurrentAcl); + for (ULONG iEntryInherited = 0; iEntryInherited < tCurrentAcl->AceCount; tAceInherited = NextAce(tAceInherited), iEntryInherited++) + { + // only process inherited items in the inner loop + if (!IsInherited(tAceInherited)) continue; + + // stop processing if we have a mismatching type + if (tAceInherited->Header.AceType != tAceExplicit->Header.AceType) continue; + + // stop processing if the explit mask is not a subset of the inherited mask + if ((tAceExplicit->Mask | tAceInherited->Mask) != tAceInherited->Mask) continue; + + // stop processing if the explcit mask has container or object inherit + // but the inherited entry does not + if (HasContainerInherit(tAceExplicit) && !HasContainerInherit(tAceInherited)) continue; + if (HasObjectInherit(tAceExplicit) && !HasObjectInherit(tAceInherited)) continue; + + // stop processing if the inherited ace has a inherit only limitation but + // the explcit entry does not + if (HasInheritOnly(tAceInherited) && !HasInheritOnly(tAceExplicit)) continue; + if (HasNoPropogate(tAceInherited) && !HasNoPropogate(tAceExplicit)) continue; + + // if sids are equal then delete this ace since it is redundant + if (SidMatch(&tAceInherited->Sid, &tAceExplicit->Sid)) + { + InputOutput::AddInfo(L"Removed redundant explicit entry for '" + + GetNameFromSidEx(&tAceExplicit->Sid) + L"'", sSdPart); + DeleteAce(tCurrentAcl, iEntryExplicit); + bMadeChange = true; + bSkipIncrement = true; + break; + } + } + } + + return bMadeChange; +} diff --git a/OperationRemoveRedundant.h b/OperationRemoveRedundant.h new file mode 100644 index 0000000..845ee0b --- /dev/null +++ b/OperationRemoveRedundant.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Operation.h" + +class OperationRemoveRedundant : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"RemoveRedundant"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationRemoveRedundant(std::queue & oArgList); +}; + diff --git a/OperationReplaceAccount.cpp b/OperationReplaceAccount.cpp new file mode 100644 index 0000000..be36f9f --- /dev/null +++ b/OperationReplaceAccount.cpp @@ -0,0 +1,58 @@ +#include "OperationReplaceAccount.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationReplaceAccount::RegisteredFactory = + new ClassFactory(GetCommand()); + +OperationReplaceAccount::OperationReplaceAccount(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(2, oArgList); + + // fetch params + tSearchAccount = GetSidFromName(sSubArgs[0]); + tReplaceAccount = GetSidFromName(sSubArgs[1]); + + // see if names could be resolved + if (tSearchAccount == nullptr) + { + // complain + wprintf(L"ERROR: Invalid search account '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(0); + } + + // see if names could be resolved + if (tReplaceAccount == nullptr) + { + // complain + wprintf(L"ERROR: Invalid replace account '%s' specified for parameter '%s'.\n", sSubArgs[1].c_str(), GetCommand().c_str()); + exit(0); + } + + // store off the names for those entries + sSearchAccount = GetNameFromSidEx(tSearchAccount); + sReplaceAccount = GetNameFromSidEx(tReplaceAccount); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 2) ProcessGranularTargetting(sSubArgs[2]); +} + +SidActionResult OperationReplaceAccount::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // check if the sid matches the ace + if (SidMatch(tCurrentSid, tSearchAccount)) + { + InputOutput::AddInfo(L"Replacing account '" + sSearchAccount + L"' with '" + sReplaceAccount + L"'", sSdPart); + tResultantSid = tReplaceAccount; + return SidActionResult::Replace; + }; + + return SidActionResult::Nothing; +} diff --git a/OperationReplaceAccount.h b/OperationReplaceAccount.h new file mode 100644 index 0000000..dc2cd28 --- /dev/null +++ b/OperationReplaceAccount.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Operation.h" + +class OperationReplaceAccount : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"ReplaceAccount"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tSearchAccount = nullptr; + std::wstring sSearchAccount = L""; + PSID tReplaceAccount = nullptr; + std::wstring sReplaceAccount = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationReplaceAccount(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationReport.cpp b/OperationReport.cpp new file mode 100644 index 0000000..d7dc088 --- /dev/null +++ b/OperationReport.cpp @@ -0,0 +1,120 @@ +#include "OperationReport.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationReport::RegisteredFactory = +new ClassFactory(GetCommand()); + +#define Q(x) L"\"" + x + L"\"" + +OperationReport::OperationReport(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sReportFile = ProcessAndCheckArgs(1, oArgList, L"\\0"); + std::vector sMatchAndArgs = ProcessAndCheckArgs(1, oArgList, L":"); + + // fetch params + HANDLE hFile = CreateFile(sReportFile[0].c_str(), GENERIC_WRITE, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // see if names could be resolved + if (hFile == INVALID_HANDLE_VALUE) + { + // complain + wprintf(L"ERROR: Could not create file '%s' specified for parameter '%s'.\n", sReportFile[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // register the file handle + hReportFile = RegisterFileHandle(hFile, GetCommand()); + + // if this is the first handle using this file, write out a header + if (hFile == hReportFile) + { + // write out the header + DWORD iBytes = 0; + std::wstring sToWrite = std::wstring(L"") + Q(L"Path") + L"," + Q(L"Descriptor Part") + L"," + + Q(L"Account Name") + L"," + Q(L"Permissions") + L"," + Q(L"Inheritance") + L"\r\n"; + if (WriteFile(hReportFile, sToWrite.c_str(), (DWORD)sToWrite.size() * sizeof(WCHAR), &iBytes, NULL) == 0) + { + wprintf(L"ERROR: Could not write header to report file for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } + } + + // compile the regular expression + try + { + tRegex = std::wregex(sMatchAndArgs[0], std::wregex::icase | std::wregex::optimize); + } + catch (const std::regex_error &) + { + wprintf(L"ERROR: Invalid regular expression '%s' specified for parameter '%s'.\n", sMatchAndArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // flag that all parts of security descriptor are necessary + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sMatchAndArgs.size() > 1) ProcessGranularTargetting(sMatchAndArgs[1]); +} + +SidActionResult OperationReport::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // do not report null sids + if (tCurrentSid == NULL) return SidActionResult::Nothing; + + // fetch the account from the sid + std::wstring sAccount = GetNameFromSidEx(tCurrentSid); + + // skip any accounts that do not match the regex + if (!std::regex_match(sAccount, tRegex)) return SidActionResult::Nothing; + + // write the string to a file + DWORD iBytes = 0; + std::wstring sToWrite = Q(tObjectEntry.Name) + L"," + Q(sSdPart) + L"," + Q(sAccount) + L"\r\n"; + if (WriteFile(hReportFile, sToWrite.c_str(), (DWORD)sToWrite.size() * sizeof(WCHAR), &iBytes, NULL) == 0) + { + InputOutput::AddError(L"ERROR: Unable to write security information to report file."); + } + + return SidActionResult::Nothing; +} + +bool OperationReport::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // do not report null acls + if (tCurrentAcl == NULL) return false; + + ACCESS_ACE * tAce = FirstAce(tCurrentAcl); + for (ULONG iEntry = 0; iEntry < tCurrentAcl->AceCount; tAce = NextAce(tAce), iEntry++) + { + // skip inherited ace + if (IsInherited(tAce)) continue; + + // fetch the account from the sid + std::wstring sAccount = GetNameFromSidEx(&tAce->Sid); + + // skip any accounts that do not match the regex + if (!std::regex_search(sAccount, tRegex)) continue; + + // get the string versions of the access mask and inheritance + std::wstring sMask = GenerateAccessMask(tAce->Mask); + std::wstring sFlags = GenerateInheritanceFlags(tAce->Header.AceFlags); + + // write the string to a file + DWORD iBytes = 0; + std::wstring sToWrite = Q(tObjectEntry.Name) + L"," + Q(sSdPart) + L"," + + Q(sAccount) + L"," + Q(sMask) + L"," + Q(sFlags) + L"\r\n"; + if (WriteFile(hReportFile, sToWrite.c_str(), (DWORD)sToWrite.size() * sizeof(WCHAR), &iBytes, NULL) == 0) + { + InputOutput::AddError(L"ERROR: Unable to write security information to report file."); + } + } + + return false; +} \ No newline at end of file diff --git a/OperationReport.h b/OperationReport.h new file mode 100644 index 0000000..45eef89 --- /dev/null +++ b/OperationReport.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "Operation.h" + +class OperationReport : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Report"; } + static ClassFactory * RegisteredFactory; + + // operation specific + HANDLE hReportFile = INVALID_HANDLE_VALUE; + std::wregex tRegex; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationReport(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationResetChildren.cpp b/OperationResetChildren.cpp new file mode 100644 index 0000000..3e1c3d5 --- /dev/null +++ b/OperationResetChildren.cpp @@ -0,0 +1,32 @@ +#include "OperationResetChildren.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationResetChildren::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationResetChildren::OperationResetChildren(std::queue & oArgList) : Operation(oArgList) +{ + // setup null ace for allowing inheritance + InitializeAcl(&tAclNull, sizeof(tAclNull), ACL_REVISION); + + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToChildrenOnly = true; + ExclusiveOperation = true; + SpecialCommitFlags = UNPROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION; +} + +bool OperationResetChildren::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) +{ + // cleanup existing if it had been reallocated + if (bAclReplacement) LocalFree(tCurrentAcl); + bAclReplacement = false; + + // setting the null acl will overwrite all explicit entires + tCurrentAcl = &tAclNull; + + // nothing to do -- the special commit flags will take care of it + return true; +} diff --git a/OperationResetChildren.h b/OperationResetChildren.h new file mode 100644 index 0000000..107a538 --- /dev/null +++ b/OperationResetChildren.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationResetChildren : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"ResetChildren"; } + static ClassFactory * RegisteredFactory; + + // used for clearing out explicit aces + ACL tAclNull = { 0 }; + +public: + + // overrides + bool ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PACL & tCurrentAcl, bool & bAclReplacement) override; + + // constructors + OperationResetChildren(std::queue & oArgList); +}; + diff --git a/OperationSetOwner.cpp b/OperationSetOwner.cpp new file mode 100644 index 0000000..badbd5f --- /dev/null +++ b/OperationSetOwner.cpp @@ -0,0 +1,46 @@ +#include "OperationSetOwner.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationSetOwner::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationSetOwner::OperationSetOwner(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // fetch params + tOwnerSid = GetSidFromName(sSubArgs[0]); + + // see if names could be resolved + if (tOwnerSid == nullptr) + { + // complain + wprintf(L"ERROR: Invalid account '%s' specified for parameter '%s'.\n", sSubArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // do a reverse lookup on the name for info messages + sOwnerSid = GetNameFromSidEx(tOwnerSid); + + // flag this as being an ace-level action + AppliesToOwner = true; + + // target certain parts of the security descriptor + if (sSubArgs.size() > 1) ProcessGranularTargetting(sSubArgs[1]); +} + +SidActionResult OperationSetOwner::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // only process if sid does not matches + if (SidMatch(tCurrentSid, tOwnerSid)) + { + return SidActionResult::Nothing; + } + + // update the sid in the ace + InputOutput::AddInfo(L"Set owner to account '" + sOwnerSid + L"'", sSdPart); + tResultantSid = tOwnerSid; + return SidActionResult::Replace; +} diff --git a/OperationSetOwner.h b/OperationSetOwner.h new file mode 100644 index 0000000..bccdd49 --- /dev/null +++ b/OperationSetOwner.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationSetOwner : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"SetOwner"; } + static ClassFactory * RegisteredFactory; + + // operation specific + PSID tOwnerSid = nullptr; + std::wstring sOwnerSid = L""; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationSetOwner(std::queue & oArgList); +}; diff --git a/OperationSidHistory.cpp b/OperationSidHistory.cpp new file mode 100644 index 0000000..af65362 --- /dev/null +++ b/OperationSidHistory.cpp @@ -0,0 +1,37 @@ +#include "OperationSidHistory.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationSidHistory::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationSidHistory::OperationSidHistory(std::queue & oArgList) : Operation(oArgList) +{ + // flag this as being an ace-level action + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToGroup = true; + AppliesToOwner = true; +} + +SidActionResult OperationSidHistory::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) +{ + // lookup the textual name for this account and + // return if it is not found + std::wstring sAccountName = GetNameFromSid(tCurrentSid, NULL); + if (sAccountName == L"") return SidActionResult::Nothing; + + // now do a forward lookup on that same account name to see what the + // primary sid for the account actually is + PSID tNewSid = GetSidFromName(sAccountName); + if (tNewSid == nullptr) return SidActionResult::Nothing; + + // if two sid are the same then there is no need to + // make an update to the access control entry + if (SidMatch(tCurrentSid, tNewSid)) return SidActionResult::Nothing; + + // update the sid in the ace + InputOutput::AddInfo(L"Updating SID history reference for '" + sAccountName + L"'", sSdPart); + tResultantSid = tNewSid; + return SidActionResult::Replace; +} diff --git a/OperationSidHistory.h b/OperationSidHistory.h new file mode 100644 index 0000000..c327d8b --- /dev/null +++ b/OperationSidHistory.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Operation.h" + +class OperationSidHistory : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"UpdateHistoricalSids"; } + static ClassFactory * RegisteredFactory; + +public: + + // overrides + SidActionResult DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) override; + + // constructors + OperationSidHistory(std::queue & oArgList); +}; diff --git a/OperationThreads.cpp b/OperationThreads.cpp new file mode 100644 index 0000000..d626b1e --- /dev/null +++ b/OperationThreads.cpp @@ -0,0 +1,21 @@ +#include "OperationThreads.h" +#include "InputOutput.h" +#include "Functions.h" + +ClassFactory * OperationThreads::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationThreads::OperationThreads(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); + + // store off the argument + InputOutput::MaxThreads() = (short)_wtoi(sSubArgs[0].c_str()); + if (InputOutput::MaxThreads() == 0 || InputOutput::MaxThreads() > 32) + { + // complain + wprintf(L"ERROR: Invalid number of threads specified for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } +}; diff --git a/OperationThreads.h b/OperationThreads.h new file mode 100644 index 0000000..eddd429 --- /dev/null +++ b/OperationThreads.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Operation.h" + +class OperationThreads : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Threads"; } + static ClassFactory * RegisteredFactory; + +public: + + // constructors + OperationThreads(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationWhatIf.cpp b/OperationWhatIf.cpp new file mode 100644 index 0000000..db8da13 --- /dev/null +++ b/OperationWhatIf.cpp @@ -0,0 +1,10 @@ +#include "OperationWhatIf.h" +#include "InputOutput.h" + +ClassFactory * OperationWhatIf::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationWhatIf::OperationWhatIf(std::queue & oArgList) : Operation(oArgList) +{ + InputOutput::InWhatIfMode() = true; +} \ No newline at end of file diff --git a/OperationWhatIf.h b/OperationWhatIf.h new file mode 100644 index 0000000..8c0fb27 --- /dev/null +++ b/OperationWhatIf.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Operation.h" + +class OperationWhatIf : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"WhatIf"; } + static ClassFactory * RegisteredFactory; + +public: + + // constructors + OperationWhatIf(std::queue & oArgList); +}; \ No newline at end of file diff --git a/Resource.rc b/Resource.rc new file mode 100644 index 0000000000000000000000000000000000000000..b70c99a5c86508bf4d8e3d14c94ea9f0f480c359 GIT binary patch literal 4648 zcmdUzS#J|D5Xa{gB)-FvzEDxMDIg>s+awJY>7nKTq)HKxq(my)DhG$eX9s?NyY8;D z8&Hpjpw-6qcs%!vXY>2lmTg&L2R61(Hn9tv@{Vi+a)-g_Q)Q>Wy_6gKvQ z6zTz2T-F@Tk>epbcI?>N*0HYDZQqWpZzt9OT4#P}Eqf27&F_Kj0%JAU`kx`v*zM+5jX-bL^I+h5;GLXD9`c`6B_kyW3zJ1 z#O?2*d(%}4VJBBld6ho3>Q$wd|GdR?N_FKnJj!l|iWTck#ZIYPr$n^BCo(CQN%Z`d z65W)~nybeHGEFtcKdKRB^`;nY!AGlARr1nELaoE7J|TY}2YAq=NA~r>X^`h_cLyJ` zmgalpnNH_-0$isPF!sso#sW@41aG*v%2;)hB;4~S(5sW}H6kyjT7Du>-*dI2iF60l zkT$uWK=r#l$Ac|LT~Rr@zBg3e8dRJvSrW^cQSubFug?#u#8d3_cl>G&U-y)_#wU)D z&(nT^jCx*Noyn}#p`STM!dQlE-`C3+D{wB)eO1>Z>?+GtkYGmcsf#;-?f@ zFBY<~oLb0=n5s0X+AF8gM}FhH&d!w8rm!2v9)47hBO6qZTfNXFw_R#Kh`SH0hQM`q zRPf^jsP+=(isTeem2)qrA8b(PbiVKP{b}Iy)>&+TEZ@MAH@xL^e+}t})jR8U{=r_C zQM-QaWqANReqt>Hy*L{^_c$BW@pi}-eo$6cZ$G6k zACg0|vyb3FGah3OE%47AZTajHTgWD7^e?k(e-(Gs+tggI@r=0HwwE{Jd341?@hq)^ z9kt5G$=$U(hSLREJ;y?`g7r=!>)pg^G2Q#{e|DD#ZxtbN_V%#W + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37} + Win32Proj + PermChange + 8.1 + repacls + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + false + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\km + $(SolutionDir)Build\Debug\x86\ + $(SolutionDir)Temp\Debug\x86\ + + + true + $(SolutionDir)Build\\Debug\x64\ + $(SolutionDir)Temp\Debug\x64\ + AllRules.ruleset + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\km + $(SolutionDir)Build\Release\x86\ + $(SolutionDir)Temp\Release\x86\ + + + false + $(SolutionDir)Build\Release\x64\ + $(SolutionDir)Temp\Release\x64\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Console + true + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + 4100 + + + Console + true + RequireAdministrator + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + AnySuitable + Speed + MultiThreaded + + + Console + true + true + true + true + + + + + Level4 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + AnySuitable + Speed + MultiThreaded + false + NotSet + 4100 + + + Console + true + true + true + RequireAdministrator + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repacls.vcxproj.filters b/repacls.vcxproj.filters new file mode 100644 index 0000000..b968b8d --- /dev/null +++ b/repacls.vcxproj.filters @@ -0,0 +1,211 @@ + + + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source\Operations + + + Source + + + Source + + + Source\Operations + + + Source\Operations + + + + + Includes + + + Includes + + + Includes + + + Resources + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes\Operations + + + Includes + + + Includes\Operations + + + Includes\Operations + + + Includes + + + + + {363895d8-9c91-487c-afe6-1fb64c043fa1} + + + {90408b4f-ce8c-435f-8ae8-67de6a5ce9d5} + + + {61b0c674-ffa6-42cb-92be-790b08cd5b5f} + + + {826c14c0-f65d-482a-8432-9b681a1575ba} + + + {f973d354-b21b-4e1f-8147-9a5ab4da43e9} + + + + + Resources + + + + + Includes + + + \ No newline at end of file diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..7ca31da --- /dev/null +++ b/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Resource.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif