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 0000000..b70c99a Binary files /dev/null and b/Resource.rc differ diff --git a/Version.h b/Version.h new file mode 100644 index 0000000..05e66d1 --- /dev/null +++ b/Version.h @@ -0,0 +1,3 @@ +#pragma once + +#define VERSION_STRING "1.5.0.0" diff --git a/repacls.sln b/repacls.sln new file mode 100644 index 0000000..de856de --- /dev/null +++ b/repacls.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "repacls", "repacls.vcxproj", "{0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Debug|x64.ActiveCfg = Debug|x64 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Debug|x64.Build.0 = Debug|x64 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Debug|x86.ActiveCfg = Debug|Win32 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Debug|x86.Build.0 = Debug|Win32 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Release|x64.ActiveCfg = Release|x64 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Release|x64.Build.0 = Release|x64 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Release|x86.ActiveCfg = Release|Win32 + {0E11E31A-BE5D-4B31-AA8E-DA9AEEC84F37}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/repacls.vcxproj b/repacls.vcxproj new file mode 100644 index 0000000..60e631c --- /dev/null +++ b/repacls.vcxproj @@ -0,0 +1,247 @@ + + + + + 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