深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

admin 2025年5月11日23:22:58评论4 views字数 7042阅读23分28秒阅读模式

我在过去写过一篇文章(https://crackme.net/articles/resln/ ),介绍了这个反序列化代码执行漏洞,但是懒没有深入研究,也不知道为什么不是所有项目类型都生效

今天有人来问我这个问题,只能静下心深入研究一下

正文

随便试几下就能发现,从最近项目中打开是能稳定利用的,但是从sln打开就分项目类型,部分项目类型不生效,部分项目类型需要用户二次操作(例如打开项目属性)

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

已知漏洞存在于Microsoft.VisualStudio.dll中,搜索可知该dll被devenv.exe加载

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

使用dnspy打开Microsoft.VisualStudio.dll,在有漏洞的位置加个断点,附加到devenv.exe开始调试

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现
// Token: 0x06000289 RID: 649 RVA: 0x0000B23C File Offset: 0x0000943Cinternal 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: 0x000527E8protected 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, thisout 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();}

可以推测出,从最近项目中打开项目时自动设置了_optionKeysVsToolboxService,原理明白了接下来跟调用栈看看哪里设置了_optionKeys就行

_optionKeys的setter方法上打个断点,继续跟调用栈看哪个设置了VsToolboxService

// Token: 0x0600219E RID: 8606 RVA: 0x00053A8C File Offset: 0x00051C8CprotectedvoidAddOptionKey(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);    }}

最终跟到VSCorePackageVSCorePackage中没有判断就直接执行了base.AddOptionKey(typeof(VsToolboxService).Name),很明显,该漏洞能否零点击利用的关键就是VSCorePackage是否会被零点击调用

// Token: 0x06000106 RID: 262 RVA: 0x00003B10 File Offset: 0x00001D10publicVSCorePackage(){    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的业务组件

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

从调用栈的lazy load async等关键词可以猜出来,大概是开发者为了优化程序性能,使用了延迟加载机制,只有在需要时才加载对应组件,这也就解释了为什么部分项目需要用户二次操作才能利用(所以并不是微软修复了这个漏洞,仅仅是优化机制阻碍了零点击利用

接下来重点就看看有没有什么方法能够绕过这个延迟加载机制

已知.net窗体应用可以零点击利用,调试发现是窗体设计组件System.Design.dll自动调用了VSCorePackage(前提是窗体设计窗口要放到前台再保存关闭项目,这样用户打开项目时就会自动进入窗体设计窗口并加载System.Design.dll

问题差不多解决了,然后找零点击利用链就行

很明显,一条目前已知的利用链

.net窗体设计器

-> System.Design.dll

-> VSCorePackage

但是用户不一定安装了.net开发环境,最好再找一条更通用的利用链

不需要审计代码,代码量太大审计起来太头疼,直接创建一个项目,把可能存在利用链的功能用上,判断是否能零点击利用成功最终调试定位到利用链

这里作为演示,我猜测vs的「类视图解析」功能可能存在利用链,所以创建一个C++项目,编写一个简单的代码

#include<iostream>intmain(){    std::cout << "hello world";}
深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

保持「类视图解析」在前台,保存退出项目,在suo文件插入payload,打开,发现零点击利用成功

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

调试分析代码可知,发现漏洞并不是出现在「类视图解析」功能上,而是「窗口聚焦」功能,因为我把「类视图解析」聚焦到了前台,用户打开就会自动聚焦到「类视图解析」上导致漏洞被零点击激活

// Token: 0x060006BF RID: 1727 RVA: 0x0001DFF0 File Offset: 0x0001C1F0internaloverrideboolOnQuerySwitchPane(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, nullnullnulluint.MaxValue);                windowFrame.DocumentSite.InitializeDocumentObject(vsWindowPane);                windowFrame.SetProperty(-3004base.Caption);                base.AddFrame(windowFrame);                return true;            }        }        return false;    }    return false;}

所以利用链就是

窗口聚焦 

-> Microsoft.VisualStudio.Platform.WindowManagement.dll

-> VSCorePackage

创建一个空项目,随便选一个窗口聚焦(这里用git更改窗口),保存退出项目,在suo文件插入payload,打开,零点击利用成功,更加证实了这个利用链

深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

同理,这里必然还存在非常多的利用链,但是我懒得挖了,这两条零点击已经非常厉害了(够用就行,笑)

原文始发于微信公众号(crackme安全实验室):深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月11日23:22:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   深入解析Visual Studio反序列化零点击代码执行漏洞,实现 100% 稳定复现https://cn-sec.com/archives/4052064.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息