本文最后更新于 2025-05-11T15:46:08+08:00
我在过去写过一篇文章(https://crackme.net/articles/resln/ ),介绍了这个反序列化代码执行漏洞,但是懒没有深入研究,也不知道为什么不是所有项目类型都生效
今天有人来问我这个问题,只能静下心深入研究一下
正文
随便试几下就能发现,从最近项目中打开是能稳定利用的,但是从sln打开就分项目类型,部分项目类型不生效,部分项目类型需要用户二次操作(例如打开项目属性)

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| internal void LoadOptions(Stream stream) { 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()
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
| 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 { 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
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
| protected void AddOptionKey(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()
是否会被零点击调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public VSCorePackage() { 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++项目,编写一个简单的代码
1 2 3 4 5
| #include <iostream>
int main() { std::cout << "hello world"; }
|

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

调试分析代码可知,发现漏洞并不是出现在「类视图解析」功能上,而是窗口聚焦功能,因为我把「类视图解析」聚焦到了前台,用户打开就会自动聚焦到「类视图解析」上导致漏洞被零点击激活
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
| internal override bool OnQuerySwitchPane(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,打开,零点击利用成功,更加证实了这个利用链

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