diff --git a/Main.cpp b/Main.cpp index f1ca770..673372e 100644 --- a/Main.cpp +++ b/Main.cpp @@ -94,6 +94,7 @@ void AnalyzeSecurity(ObjectEntry & oEntry) bool bSaclCleanupRequired = false; bool bOwnerCleanupRequired = false; bool bGroupCleanupRequired = false; + bool bDescCleanupRequired = true; // used for one-shot operations like reset children or inheritance DWORD iSpecialCommitMergeFlags = 0; @@ -130,7 +131,40 @@ void AnalyzeSecurity(ObjectEntry & oEntry) } if ((*oOperation)->AppliesToSd) { - (*oOperation)->ProcessSdAction(oEntry.Name, oEntry, tDesc); + if ((*oOperation)->ProcessSdAction(oEntry.Name, oEntry, tDesc, bDescCleanupRequired)) + { + // cleanup previous operations if necessary + if (bDaclCleanupRequired) { LocalFree(tAclDacl); bDaclCleanupRequired = false; }; + if (bSaclCleanupRequired) { LocalFree(tAclDacl); bSaclCleanupRequired = false; }; + if (bOwnerCleanupRequired) { LocalFree(tAclDacl); bOwnerCleanupRequired = false; }; + if (bGroupCleanupRequired) { LocalFree(tAclDacl); bGroupCleanupRequired = false; }; + if (bDescCleanupRequired) { LocalFree(tDesc); bDescCleanupRequired = false; }; + + // extract the elements from the raw security descriptor + BOOL bItemPresent = FALSE; + BOOL bItemDefaulted = FALSE; + GetSecurityDescriptorDacl(tDesc, &bItemPresent, &tAclDacl, &bItemDefaulted); + GetSecurityDescriptorSacl(tDesc, &bItemPresent, &tAclSacl, &bItemDefaulted); + GetSecurityDescriptorOwner(tDesc, &tOwnerSid, &bItemDefaulted); + GetSecurityDescriptorGroup(tDesc, &tGroupSid, &bItemDefaulted); + + // extract relevant inheritance bits + DWORD tRevisionInfo; + SECURITY_DESCRIPTOR_CONTROL tControl; + GetSecurityDescriptorControl(tDesc, &tControl, &tRevisionInfo); + + // convert inheritance bits to the special flags that control inheritance + iSpecialCommitMergeFlags = CheckBitSet(SE_SACL_PROTECTED, tControl) ? + PROTECTED_SACL_SECURITY_INFORMATION : UNPROTECTED_SACL_SECURITY_INFORMATION; + iSpecialCommitMergeFlags = CheckBitSet(SE_DACL_PROTECTED, tControl) ? + PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION; + + // mark all elements as needing to be updated + bDaclIsDirty = true; + bSaclIsDirty = true; + bOwnerIsDirty = true; + bGroupIsDirty = true; + } } } @@ -181,7 +215,7 @@ void AnalyzeSecurity(ObjectEntry & oEntry) if (bSaclCleanupRequired) LocalFree(tAclSacl); if (bOwnerCleanupRequired) LocalFree(tOwnerSid); if (bGroupCleanupRequired) LocalFree(tGroupSid); - LocalFree(tDesc); + if (bDescCleanupRequired) LocalFree(tDesc); } @@ -346,13 +380,28 @@ VOID BeginFileScan() ObjectEntry oEntryFirst; oEntryFirst.IsRoot = true; + // make a local copy of the path since we may have to alter it + std::wstring sPath = *sScanPath; + + // handle special case where a drive root is specified + // we must ensure it takes the form x:\. to resolve correctly + size_t iSemiColon = sPath.rfind(L":"); + if (iSemiColon != std::wstring::npos) + { + std::wstring sEnd = sPath.substr(iSemiColon); + if (sEnd == L":" || sEnd == L":\\") + { + sPath = sPath.substr(0, iSemiColon) + L":\\."; + } + } + // 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); + RtlDosPathNameToNtPathName_U(sPath.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()); + oEntryFirst.Attributes = GetFileAttributes(oEntryFirst.Name.c_str()); // free the buffer returned previously RtlFreeUnicodeString(&tPathU); diff --git a/Operation.h b/Operation.h index 3cf3631..d0cabae 100644 --- a/Operation.h +++ b/Operation.h @@ -82,7 +82,7 @@ class Operation DWORD SpecialCommitFlags = false; PSID DefaultSidWhenEmpty = NULL; - virtual bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) { return false; } + virtual bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) { 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; } diff --git a/OperationExportDescriptor.h b/OperationExportDescriptor.h deleted file mode 100644 index 8024cc1..0000000 --- a/OperationExportDescriptor.h +++ /dev/null @@ -1,24 +0,0 @@ -#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/OperationHelp.cpp b/OperationHelp.cpp index b3dcb15..ae47796 100644 --- a/OperationHelp.cpp +++ b/OperationHelp.cpp @@ -108,11 +108,18 @@ Commands That Do Not Alter Security 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 +/SaveSecurity 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(). + descriptor is formatted as specified in the documentation for + ConvertDescriptorToStringSecurityDescriptor(). This command does + not print informational messages other than errors. + +/RestoreSecurity + The reverse operation of /SaveSecurity. Takes the file name and security + descriptors specified in the file specified and applies them to the file + system. This command does not print informational messages other than + errors. /FindAccount Reports any instance of an account specified. @@ -160,8 +167,11 @@ Commands That Can Alter Security (When /WhatIf Is Not Present) 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. + instead of domain name for this command does not work. +)"; +std::wcout << +LR"( /RemoveAccount Will remove from the security descriptor. If the specified name is found as the file owner, the owner is replaced by the builtin @@ -171,11 +181,8 @@ Commands That Can Alter Security (When /WhatIf Is Not Present) /RemoveOrphans Remove any account whose SID is derived from the specified - and can no longer be resolved to a valid name. -)"; + and can no longer be resolved to a valid name -std::wcout << -LR"( /RemoveRedundant This command will remove any explicit permission that is redundant of of the permissions its already given through inheritance. This option @@ -207,6 +214,8 @@ LR"( Exclusive Options ================= +Exclusive options cannot be combined with any other security operations. + /Help or /? or /H Shows this information. diff --git a/OperationPrintDescriptor.cpp b/OperationPrintDescriptor.cpp index 90750d0..acd230f 100644 --- a/OperationPrintDescriptor.cpp +++ b/OperationPrintDescriptor.cpp @@ -16,11 +16,11 @@ OperationPrintDescriptor::OperationPrintDescriptor(std::queue & oA AppliesToGroup = true; } -bool OperationPrintDescriptor::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) +bool OperationPrintDescriptor::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) { // convert the current security descriptor to a string WCHAR * sInfo = NULL; - if (ConvertSecurityDescriptorToStringSecurityDescriptor(tSecurityDescriptor, SDDL_REVISION_1, + if (ConvertSecurityDescriptorToStringSecurityDescriptor(tDescriptor, SDDL_REVISION_1, DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &sInfo, NULL) == 0) { diff --git a/OperationPrintDescriptor.h b/OperationPrintDescriptor.h index 0c08155..0eb2513 100644 --- a/OperationPrintDescriptor.h +++ b/OperationPrintDescriptor.h @@ -13,7 +13,7 @@ class OperationPrintDescriptor : public Operation public: // overrides - bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) override; + bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) override; // constructors OperationPrintDescriptor(std::queue & oArgList); diff --git a/OperationReport.cpp b/OperationReport.cpp index be6a6e4..12e1d7d 100644 --- a/OperationReport.cpp +++ b/OperationReport.cpp @@ -31,8 +31,16 @@ OperationReport::OperationReport(std::queue & oArgList) : Operatio // if this is the first handle using this file, write out a header if (hFile == hReportFile) { - // write out the header + // write out the file type marker + USHORT hHeader = 0xFEFF; DWORD iBytes = 0; + if (WriteFile(hFile, &hHeader, sizeof(USHORT), &iBytes, NULL) == 0) + { + wprintf(L"ERROR: Could not write out report file type marker '%s'.\n", GetCommand().c_str()); + exit(-1); + } + + // write out the header 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) diff --git a/OperationRestoreSecurity.cpp b/OperationRestoreSecurity.cpp new file mode 100644 index 0000000..1aef8fb --- /dev/null +++ b/OperationRestoreSecurity.cpp @@ -0,0 +1,74 @@ +#include "OperationRestoreSecurity.h" +#include "InputOutput.h" +#include "Functions.h" + +#include +#include +#include +#include + +ClassFactory * OperationRestoreSecurity::RegisteredFactory = +new ClassFactory(GetCommand()); + +OperationRestoreSecurity::OperationRestoreSecurity(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to part + std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList, L"\\0"); + + // open the file + std::wifstream fFile(sSubArgs[0].c_str()); + + // adapt the stream to read windows unicode files + fFile.imbue(std::locale(fFile.getloc(), new std::codecvt_utf16)); + + // read the file line-by-line + std::wstring sLine; + while (std::getline(fFile, sLine)) + { + // parse the file name and descriptor which are separated by a '|' character + std::vector oLineItems = SplitArgs(sLine, L"\\|"); + + // convert the long string descriptor its binary equivalent + PSECURITY_DESCRIPTOR tDesc; + if (ConvertStringSecurityDescriptorToSecurityDescriptor(oLineItems.at(1).c_str(), + SDDL_REVISION_1, &tDesc, NULL) == 0) + { + InputOutput::AddError(L"ERROR: Unable to parse string security descriptor."); + exit(-1); + } + + // update the map + oImportMap[oLineItems.at(0)] = tDesc; + } + + // cleanup + fFile.close(); + + // flag this as being an ace-level action + AppliesToSd = true; + AppliesToDacl = true; + AppliesToSacl = true; + AppliesToOwner = true; + AppliesToGroup = true; +} + +bool OperationRestoreSecurity::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) +{ + std::map::iterator oSecInfo = oImportMap.find(sFileName); + if (oSecInfo != oImportMap.end()) + { + // lookup the string in the map + if (bDescReplacement) LocalFree(tDescriptor); + tDescriptor = oSecInfo->second; + bDescReplacement = true; + } + else + { + // update the sid in the ace + InputOutput::AddError(L"Import File Did Not Contain Descriptor"); + } + + // cleanup + return true; +} diff --git a/OperationRestoreSecurity.h b/OperationRestoreSecurity.h new file mode 100644 index 0000000..a033479 --- /dev/null +++ b/OperationRestoreSecurity.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationRestoreSecurity : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"RestoreSecurity"; } + static ClassFactory * RegisteredFactory; + + std::map oImportMap; + std::wstring sFile = L""; + +public: + + // overrides + bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) override; + + // constructors + OperationRestoreSecurity(std::queue & oArgList); +}; + diff --git a/OperationExportDescriptor.cpp b/OperationSaveSecurity.cpp similarity index 64% rename from OperationExportDescriptor.cpp rename to OperationSaveSecurity.cpp index 7ea454a..7382a22 100644 --- a/OperationExportDescriptor.cpp +++ b/OperationSaveSecurity.cpp @@ -1,11 +1,11 @@ -#include "OperationExportDescriptor.h" +#include "OperationSaveSecurity.h" #include "InputOutput.h" #include "Functions.h" -ClassFactory * OperationExportDescriptor::RegisteredFactory = -new ClassFactory(GetCommand()); +ClassFactory * OperationSaveSecurity::RegisteredFactory = +new ClassFactory(GetCommand()); -OperationExportDescriptor::OperationExportDescriptor(std::queue & oArgList) : Operation(oArgList) +OperationSaveSecurity::OperationSaveSecurity(std::queue & oArgList) : Operation(oArgList) { // exit if there are not enough arguments to part std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList, L"\\0"); @@ -22,6 +22,15 @@ OperationExportDescriptor::OperationExportDescriptor(std::queue & exit(-1); } + // write out the file type marker + USHORT hHeader = 0xFEFF; + DWORD iBytes = 0; + if (WriteFile(hFile, &hHeader, sizeof(USHORT), &iBytes, NULL) == 0) + { + wprintf(L"ERROR: Could not write out report file type marker '%s'.\n", GetCommand().c_str()); + exit(-1); + } + // flag this as being an ace-level action AppliesToSd = true; AppliesToDacl = true; @@ -30,11 +39,11 @@ OperationExportDescriptor::OperationExportDescriptor(std::queue & AppliesToGroup = true; } -bool OperationExportDescriptor::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR const tSecurityDescriptor) +bool OperationSaveSecurity::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) { // convert the current security descriptor to a string WCHAR * sInfo = NULL; - if (ConvertSecurityDescriptorToStringSecurityDescriptor(tSecurityDescriptor, SDDL_REVISION_1, + if (ConvertSecurityDescriptorToStringSecurityDescriptor(tDescriptor, SDDL_REVISION_1, DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &sInfo, NULL) == 0) { diff --git a/OperationSaveSecurity.h b/OperationSaveSecurity.h new file mode 100644 index 0000000..83c1c31 --- /dev/null +++ b/OperationSaveSecurity.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Operation.h" + +class OperationSaveSecurity : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"SaveSecurity"; } + static ClassFactory * RegisteredFactory; + + HANDLE hFile = INVALID_HANDLE_VALUE; + std::wstring sFile = L""; + +public: + + // overrides + bool ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) override; + + // constructors + OperationSaveSecurity(std::queue & oArgList); +}; + diff --git a/Version.h b/Version.h index 94ed654..b797bc1 100644 --- a/Version.h +++ b/Version.h @@ -1,3 +1,3 @@ #pragma once -#define VERSION_STRING "1.7.0.3" +#define VERSION_STRING "1.8.0.0" diff --git a/repacls.vcxproj b/repacls.vcxproj index 437535d..267e87d 100644 --- a/repacls.vcxproj +++ b/repacls.vcxproj @@ -188,11 +188,12 @@ - + + @@ -218,11 +219,12 @@ + - + diff --git a/repacls.vcxproj.filters b/repacls.vcxproj.filters index 76341a1..e6796e8 100644 --- a/repacls.vcxproj.filters +++ b/repacls.vcxproj.filters @@ -25,9 +25,6 @@ Source\Operations - - Source\Operations - Source\Operations @@ -91,6 +88,12 @@ Source\Operations + + Source\Operations + + + Source\Operations + @@ -132,9 +135,6 @@ Includes\Operations - - Includes\Operations - Includes\Operations @@ -198,6 +198,12 @@ Includes\Operations + + Includes\Operations + + + Includes\Operations +