我在过去写过一篇文章(https://crackme.net/articles/resln/ ),介绍了这个反序列化代码执行漏洞,但是懒没有深入研究,也不知道为什么不是所有项目类型都生效
今天有人来问我这个问题,只能静下心深入研究一下
正文
随便试几下就能发现,从最近项目中打开是能稳定利用的,但是从sln打开就分项目类型,部分项目类型不生效,部分项目类型需要用户二次操作(例如打开项目属性)
已知漏洞存在于Microsoft.VisualStudio.dll中,搜索可知该dll被devenv.exe加载
使用dnspy打开Microsoft.VisualStudio.dll,在有漏洞的位置加个断点,附加到devenv.exe开始调试
// Token: 0x06000289 RID: 649 RVA: 0x0000B23C File Offset: 0x0000943C
internal void LoadOptions(Stream stream)
{
// BinaryFormatter 反序列化漏洞点,在这里加个断点开始调试
BinaryReader binaryReader = new BinaryReader(stream);
BinaryFormatter binaryFormatter = new BinaryFormatter();
int num = binaryReader.ReadInt32();
for (int i = 0; i < num; i++)
{
string text = binaryReader.ReadString();
int num2 = binaryReader.ReadInt32();
for (int j = 0; j < num2; j++)
{
string text2 = this.Links.Read(stream);
VsToolboxService.ToolboxItemContainer toolboxItemContainer = (VsToolboxService.ToolboxItemContainer)binaryFormatter.Deserialize(stream);
if (text2 != null && File.Exists(text2))
{
toolboxItemContainer.LinkFile = text2;
this.Links.TrackLink(text2);
this.Items.GetFilteredList(text).Add(toolboxItemContainer);
}
}
}
}
跟随调用栈来到Microsoft.VisualStudio.Shell.15.0.dll!Initialize()
// Token: 0x060021AA RID: 8618 RVA: 0x000545E8 File Offset: 0x000527E8
protected virtual void Initialize()
{
object servicesLock = this._servicesLock;
lock (servicesLock)
{
if (this._services != null && this._services.Count > 0)
{
IProfferService profferService = null;
foreach (KeyValuePair<Type, object> keyValuePair in this._services)
{
Package.ProfferedService profferedService = keyValuePair.Value as Package.ProfferedService;
if (profferedService != null)
{
if (profferService == null)
{
profferService = (IProfferService)this.GetService(typeof(SProfferService));
}
if (profferService == null)
{
break;
}
Guid guid = keyValuePair.Key.GUID;
uint num;
NativeMethods.ThrowOnFailure(profferService.ProfferService(ref guid, this, out num));
profferedService.Cookie = num;
}
}
}
}
// 上面的代码不重要,从这里开始判断
object optionKeysLock = this._optionKeysLock;
string[] array;
lock (optionKeysLock)
{
List<string> optionKeys = this._optionKeys;
array = ((optionKeys != null) ? optionKeys.ToArray() : null);
}
if (array != null)
{
IVsSolutionPersistence vsSolutionPersistence = (IVsSolutionPersistence)this.GetService(typeof(SVsSolutionPersistence));
if (vsSolutionPersistence != null)
{
foreach (string text in array)
{
try
{
// text参数是否为"VsToolboxService"就是漏洞能否利用的关键
int num2 = vsSolutionPersistence.LoadPackageUserOpts(this, text);
if (num2 == -2147418113 || num2 == -2147287038 || num2 == -2147467260)
{
break;
}
ErrorHandler.ThrowOnFailure(num2);
}
catch (Exception ex)
{
this.LogSolutionOptionsLoadFailure(text, ex);
}
}
}
}
this.ScheduleToolboxItemDiscoveryFactoriesRegistrationIfNecessary();
}
可以推测出,从最近项目中打开项目时自动设置了_optionKeys为VsToolboxService,原理明白了接下来跟调用栈看看哪里设置了_optionKeys就行
在_optionKeys的setter方法上打个断点,继续跟调用栈看哪个设置了VsToolboxService
// Token: 0x0600219E RID: 8606 RVA: 0x00053A8C File Offset: 0x00051C8C
protectedvoidAddOptionKey(string name)
{
if (this.zombie)
{
Marshal.ThrowExceptionForHR(-2147418113);
}
if (name == null)
{
throw new ArgumentNullException("name");
}
if (name.Length > 31)
{
throw new ArgumentException(string.Format(Resources.Culture, Resources.Package_BadOptionName, name));
}
object optionKeysLock = this._optionKeysLock;
lock (optionKeysLock)
{
if (this._optionKeys == null)
{
this._optionKeys = new List<string>();
}
if (this._optionKeys.Contains(name))
{
throw new ArgumentException(string.Format(Resources.Culture, Resources.Package_OptionNameUsed, name));
}
// 在这里加断点
this._optionKeys.Add(name);
}
}
最终跟到VSCorePackage,VSCorePackage中没有判断就直接执行了base.AddOptionKey(typeof(VsToolboxService).Name),很明显,该漏洞能否零点击利用的关键就是VSCorePackage是否会被零点击调用
// Token: 0x06000106 RID: 262 RVA: 0x00003B10 File Offset: 0x00001D10
publicVSCorePackage()
{
ServiceCreatorCallback serviceCreatorCallback = new ServiceCreatorCallback(this.OnCreateService);
IServiceContainer serviceContainer = this.GetService(typeof(IServiceContainer)) as IServiceContainer;
if (serviceContainer != null)
{
serviceContainer.AddService(typeof(IVSMDPropertyBrowser), serviceCreatorCallback, true);
serviceContainer.AddService(typeof(IToolboxService), serviceCreatorCallback, true);
serviceContainer.AddService(typeof(IComponentDiscoveryService), serviceCreatorCallback, true);
serviceContainer.AddService(typeof(IUIService), serviceCreatorCallback, true);
serviceContainer.AddService(typeof(AssemblyEnumerationService), serviceCreatorCallback);
}
// 注意这里
base.AddOptionKey(typeof(VsToolboxService).Name);
VSCorePackage._instance = this;
}
继续跟调用栈,mscorlib.dll是.net运行时的内部组件不用看,主要看vs的业务组件
从调用栈的lazy load async等关键词可以猜出来,大概是开发者为了优化程序性能,使用了延迟加载机制,只有在需要时才加载对应组件,这也就解释了为什么部分项目需要用户二次操作才能利用(所以并不是微软修复了这个漏洞,仅仅是优化机制阻碍了零点击利用)
接下来重点就看看有没有什么方法能够绕过这个延迟加载机制
已知.net窗体应用可以零点击利用,调试发现是窗体设计组件System.Design.dll自动调用了VSCorePackage(前提是窗体设计窗口要放到前台再保存关闭项目,这样用户打开项目时就会自动进入窗体设计窗口并加载System.Design.dll)
问题差不多解决了,然后找零点击利用链就行
很明显,一条目前已知的利用链
.net窗体设计器
-> System.Design.dll
-> VSCorePackage
但是用户不一定安装了.net开发环境,最好再找一条更通用的利用链
不需要审计代码,代码量太大审计起来太头疼,直接创建一个项目,把可能存在利用链的功能用上,判断是否能零点击利用成功最终调试定位到利用链
这里作为演示,我猜测vs的「类视图解析」功能可能存在利用链,所以创建一个C++项目,编写一个简单的代码
intmain(){
std::cout << "hello world";
}
保持「类视图解析」在前台,保存退出项目,在suo文件插入payload,打开,发现零点击利用成功
调试分析代码可知,发现漏洞并不是出现在「类视图解析」功能上,而是「窗口聚焦」功能,因为我把「类视图解析」聚焦到了前台,用户打开就会自动聚焦到「类视图解析」上导致漏洞被零点击激活
// Token: 0x060006BF RID: 1727 RVA: 0x0001DFF0 File Offset: 0x0001C1F0
internaloverrideboolOnQuerySwitchPane(FrameMoniker frameMoniker)
{
WindowFrame windowFrame = base.FindFrame(frameMoniker);
if (windowFrame != null)
{
return true;
}
Guid toolWindowGuid = frameMoniker.ToolWindowGuid;
if (toolWindowGuid != Guid.Empty)
{
Guid guid = typeof(IVsWindowPane).GUID;
IntPtr zero = IntPtr.Zero;
ErrorHandler.ThrowOnFailure(GlobalServices.ServiceProvider.QueryService(ref toolWindowGuid, ref guid, out zero));
using (SafeIUnknown safeIUnknown = new SafeIUnknown(zero))
{
IVsWindowPane vsWindowPane = safeIUnknown.ToObject() as IVsWindowPane;
if (vsWindowPane != null)
{
windowFrame = WindowFrame.CreateInstance(frameMoniker);
windowFrame.DocumentSite = new DocumentObjectSite(windowFrame, null, null, null, uint.MaxValue);
windowFrame.DocumentSite.InitializeDocumentObject(vsWindowPane);
windowFrame.SetProperty(-3004, base.Caption);
base.AddFrame(windowFrame);
return true;
}
}
return false;
}
return false;
}
所以利用链就是
窗口聚焦
-> Microsoft.VisualStudio.Platform.WindowManagement.dll
-> VSCorePackage
创建一个空项目,随便选一个窗口聚焦(这里用git更改窗口),保存退出项目,在suo文件插入payload,打开,零点击利用成功,更加证实了这个利用链
同理,这里必然还存在非常多的利用链,但是我懒得挖了,这两条零点击已经非常厉害了(够用就行,笑)
原文始发于微信公众号(crackme安全实验室):深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论