返回 2026-04-23
⚙️ 工程

卸载程序注入 Explorer 导致再次崩溃Another crash caused by uninstaller code injection into Explorer

Raymond Chen 在其博客中记录了一起由第三方卸载程序引发的 Windows Explorer 崩溃案例。该卸载程序错误地将自身代码注入到资源管理器进程中,干扰了其正常运行。此类问题凸显了 Windows 系统下安装/卸载软件时的安全风险,建议用户谨慎授权未知程序的进程注入权限。

Raymond Chen

很久以前,我就注意到一个现象:任何足够先进的卸载程序,都很难与恶意软件区分开来。¹

在我们一次例行的调试聊天中,一位同事提到他发现资源管理器崩溃次数异常激增。他给我看了一个转储文件,我一看寄存器转储,就说:“哦,我敢打赌是某个有问题的卸载程序。”

关键线索是:崩溃发生在64位系统上的32位资源管理器。

32位版本的资源管理器是为了与32位程序保持向后兼容而存在的。它不是处理任务栏、桌面或文件资源管理器窗口的那个资源管理器副本。因此,如果在64位系统上运行的是32位资源管理器,那一定是因为某个其他程序正在用它来执行一些“脏活”。

出于好奇,我决定看看这个有问题的卸载程序为什么会崩溃。

这个特定卸载程序的注入代码中包含一个循环,试图执行一些文件操作;如果操作失败,它会暂停一小会儿然后重试。然而,这段代码的作者没有为这些函数指定正确的调用约定,导致它们被以 __cdecl 调用约定调用,而不是 __stdcall。在 __stdcall 调用约定中,被调函数会从堆栈中弹出参数;而在 __cdecl 调用约定中,则由调用者负责弹出。

这种调用约定不匹配意味着每次代码调用Windows函数时,代码都会将参数压入堆栈,Windows函数会弹出这些参数,然后调用代码又会再次将它们弹出。因此,每次循环迭代时,代码都会消耗掉一部分自身的堆栈空间。

显然,这个循环执行了太多次,因为它已经耗尽了整个堆栈,堆栈指针一路递增到其注入的代码区域。每次循环迭代时,堆栈都会侵占一小部分注入代码,直到堆栈指针最终进入正在执行的代码内部。

随后,代码因遇到无效指令而崩溃,因为原来的代码已经被堆栈数据覆盖而不再存在。

这留下了大量难看的“尸体”,以至于Windows团队一度认为这是Windows本身的一个bug。

¹ 标题引用自克拉克第三定律:任何足够先进的技术都与魔法无异。

作者

Raymond 参与了Windows三十多年的演进历程。2003年,他创建了一个名为《旧新事物》(The Old New Thing)的网站,其受欢迎程度远远超出了他的想象,这一发展至今仍让他感到有些不安。该网站催生了一本同名书籍《旧新事物》(Addison Wesley, 2007)。他偶尔会在Windows Dev Docs的Twitter账号上出现,讲述一些毫无实用价值的故事。

需要完整排版与评论请前往来源站点阅读。