本文翻译自微软安全研究副总裁John Lambert在2015年9月26日的一篇自述性博客.......
七年前,一系列有针对性的攻击开始出现。
2008年,一群攻击者利用了一个零日漏洞,很快引起了全球的关注。他们耐心等待,并在亚洲的几个国家悄悄使用它。这个漏洞不仅非常好用,而且是攻击团队和漏洞猎人梦寐以求的那种漏洞。我们在业界称之为“可蠕虫型漏洞”,这个词让任何防御者都感到发抖。简而言之,攻击者拥有一个远程代码执行(RCE)漏洞,它影响了Windows的每一个版本,给他们完全的SYSTEM级别的权限,几乎不留下取证痕迹,而且可以在互联网上匿名使用。他们的攻击成功率高达95%,几乎是完美的。
漏洞利用缓解措施
这个故事开始于MS08-067出现的一年前。如果你遇见任何在微软的可信计算组工作的人,你会发现我们中许多人通过Microsoft安全性公告来衡量我们的职业里程碑。
“MS03-007?那是个灾难。”
“没错,但是MS03-026更疯狂。”
“你说的没错,那真的很疯狂。”
要理解MS08-067,你需要先了解MS07-029,那是Windows DNS的一个RCE漏洞。MS07-029是一系列远程过程调用(RPC)服务器漏洞之一,它们正在被微软、攻击者和安全研究人员不断地挖掘出来。但有一个不同点,MS07-029是我们在运用Visual Studio返回地址保护(/GS)和Windows数据防止执行(DEP)等防御措施的首个RCE漏洞。
我们把这些防御措施称为漏洞利用缓解措施,自从XP SP2后我们一直在不断地增加它们。这是我们用安全工程技术来对抗安全问题的一种方式。一旦攻击突破了进程的内存,就没有恢复的可能性,唯一的选择就是强制崩溃--这对用户来说是一种非常糟糕的体验,但它总比让机器被攻陷更好。
漏洞作者知道我们一直在增加缓解措施:SafeSEH、SEHOP、堆加固、ASLR、MemProtector、IsoHeap和Control Flow Guard。这些缓解措施不仅关闭了漏洞利用的途径,而且还为我们提供了非常重要的信息。
订阅漏洞利用遥测信息
崩溃对用户来说是一个可怕的经历,因此Windows有一个名为Windows错误报告(WER)的功能,如果用户同意,可以将崩溃数据发送给微软。WER从超过10亿台电脑上运行的不良应用程序和有缺陷的硬件中接收数亿次崩溃信号。
工程师们根据根本原因将它们以崩溃存储桶(bucket)形式进行分组并逐步排列优先级列表。我们首先修复影响最多客户的错误,记住这一点,下次出现Windows错误报告对话框时,你正在为自己提交的错误“投票”。
MS07-029的崩溃存储桶(bucket)非常有启示性。下图显示,在问题公开后(2007年4月初),Windows DNS的崩溃数量显著增加。一旦问题公开,安全研究人员和攻击者都会争相重新发现漏洞,并从概念验证转向实际利用。Windows DNS的崩溃存储桶(bucket)突然变得非常活跃。每个想法都有它的灵感时刻,对我来说,看到这个图表的意义:在我们得知这次攻击之前,每隔几天就会出现一次崩溃--这是WER宇宙中的一个小小的闪烁,我查看了每一个崩溃,并发现它们都是利用程序。
如果我们能够确定崩溃何时是漏洞利用,我们就可以在攻击生命周期的早期做出反应,更早地保护客户。要找到0day漏洞,你不会在每天生成数千次点击,充满大嘈杂的崩溃存储桶(bucket)中找到它们,你需要关注小数据的长尾。为了使安全崩溃“响亮”,我进入了位翻转、一击中招、不可重现的竞态条件和0day漏洞的光怪陆离世界。
在长尾中生存
到2008年9月,我们已经建立了一个能够对数百万次崩溃进行安全漏洞筛选的系统。在这个过程中,我感觉自己加入了全世界最小的行业--攻击失效工程师的行业。
9月25日,一个崩溃事件引起了我的注意--netapi32.dll中的一个漏洞。这本身并不罕见。在MS06-040中,我们修复了netapi32!NetpwPathCanonicalize 中的缓冲区溢出漏洞。每天会有来自僵尸网络的数百万次崩溃崩溃进来,企图利用这个漏洞(因为/GS的原因而失败)来感染易受攻击的PC。这个新的崩溃事件出现在非常相似的代码中,但在不同的WER桶(bucket)中。它既不是前100个问题中的问题,也不是前1000个问题中的问题。它是第45000个问题,历史上只出现过2次。这是在长尾上生存的典型数据。
深入 !pwnd指令
是什么让这个微小的崩溃存储桶脱颖而出?
首先,这里有一个漏洞利用程序,我们编写了一个自定义调试器扩展,调试信息色彩缤纷地显示!pwnd(Windows调试器扩展通过在其前面加上“!”符号调用),它在崩溃转储中发现了shellcode。我查看了shellcode,发现它使用了egghunt来寻找有效负载。
egghunt是一种漏洞利用工程技术,用于当缓冲区溢出在发送多少有效负载方面受限制时。解决方案是将攻击分为两个部分,在第一部分中,你以良性的方式与目标进行交互,发送包含真实有效负载前缀为唯一字节序列(“egg”)的数据,但不触发漏洞。如果你能以正确的方式做到这一点,你的有效负载将保留在某个地方的内存中。然后你触发漏洞,受限制的shellcode在内存中搜索egg并跳转到它,这是netapi32.dll的第一个使用egghunt的漏洞利用程序。这引起了人们的关注。
崩溃转储的第二个非寻常之处不仅是它崩溃的方式,而是在崩溃之前它的成功方式。我关注了进程中的其他线程,除了崩溃线程之外。其中一个线程揭示了攻击者已经利用了该进程,shellcode正处于使用URLDownloadToFileA下载有效负载的过程中!
0:066> kPn99
# ChildEBP RetAddr
00 0150f048 7c90e9ab ntdll!KiFastSystemCallRet(void)
01 0150f04c 7c8094e2 ntdll!ZwWaitForMultipleObjects(void)+0xc
...
08 0150f2b4 7813b13a urlmon!CUrlMon::StartBinding(
int fBindToObject = 0,
struct IBindCtx * pbc = 0x00185540,
struct IMoniker * pmkToLeft = 0x00000000,
struct _GUID * riid = 0x7813b394 {0000000c-0000-0000-c000-000000000046},
void ** ppvObj = 0x0150f2fc)+0x169
09 0150f2d8 7815bcd2 urlmon!CUrlMon::BindToStorage(
struct IBindCtx * pbc = 0x00185540,
struct IMoniker * pmkToLeft = 0x00000000,
struct _GUID * riid = 0x7813b394 {0000000c-0000-0000-c000-000000000046},
void ** ppvObj = 0x0150f2fc)+0x49
0a 0150f31c 781b73d2 urlmon!CBaseBSCB::KickOffDownload(
wchar_t * szURL = 0x0150f400 "hxxp://59.106.145.58/n2.exe")+0x193
0b 0150f330 781b7847 urlmon!CFileDownload::KickOffDownload(
wchar_t * szURL = 0x0150f400 "hxxp://59.106.145.58/n2.exe")+0x2d
0c 0150f348 781b7a4a urlmon!URLDownloadToFileW(
struct IUnknown * caller = 0x00000000,
wchar_t * szURL = 0x0150f400 "hxxp://59.106.145.58/n2.exe",
wchar_t * szFileName = 0x0150f380 "update.exe",
unsigned long dwReserved = 0,
struct IBindStatusCallback * callback = 0x00000000)+0x51
0d 0150f480 0150f5b8 urlmon!URLDownloadToFileA(
struct IUnknown * caller = 0x00000000,
char * szURL = 0x0150f6a1 "hxxp://59.106.145.58/n2.exe",
char * szFileName = 0x0150f6d1 "update.exe",
unsigned long dwReserved = 0,
struct IBindStatusCallback * callback = 0x00000000)+0x109
虽然egghunts不是什么新鲜事物,但这是netapi32漏洞利用程序的一种新的shellcode形式,也是成功利用的明确证据。但最后确认这是一个新0day漏洞假设的是转储文件中的版本信息,Netapi32.dll已经被完全打了补丁!只有一个解释:在野外存在一个新的0day漏洞。
0:088> lmvm netapi32
Image path: C:\WINDOWS\system32\netapi32.dll
Image name: netapi32.dll
Timestamp: Thu Aug 17 05:28:27 2006 (44E460EB)
File version: 5.1.2600.2976
进行逆向
大多数情况下,安全研究人员会发现一个漏洞然后编写一个攻击利用代码。而我则反其道而行之:审查攻击利用代码以确定漏洞,凭借着仅有的寻找不到复现且无法排除的解剖崩溃。攻击是否在缓冲区溢出本身中破坏了至关重要的线索?我反复研究崩溃信息。我研究了netapi32的源代码。漏洞常常在事后显而易见,但往往不容易被揭示。这是我的两难境地:如果我无法找到漏洞,即使有一个清晰的攻击利用代码,我们也无法采取行动。工程师首先需要理解缺陷才能修复它。如果我找不到缺陷,我们将错失这个机会。我被卡住了,时钟正在滴答作响,那么我做了什么?我寻求了帮助。
我把这个案子带给了MSRC安全工程师的经理Andrew Roths。安德鲁是一位聪明的安全工程师。唯一的问题是,安德鲁像我一样,之前查看了许多崩溃信息,并深知在数据尾部发现新问题的难度,这些数据充斥着许多死胡同、虚假信息和误导信息。我还清晰地记得当时的情况。由于我向公司最忙碌的团队寻求帮助,因为这是一个不太可能的事情,空气中有一些紧张的气氛。毕竟,这段代码已经被微软和安全研究人员大力审查过了。难道一个潜在的缓冲区溢出会逃过所有专业人员眼睛的追踪么?在崩溃中经常有令人困惑的因素。您可以自信地为一个崩溃调试几个小时,认为您已经找到了一个漏洞,直到发现该机器随机翻转内存位时才发现其实不是漏洞,在数据尾部分析是艰难的。
我记得安德鲁来到我的办公室的那一刻。我准备就是否应该将安全工程师从已确认的漏洞案例中撤出,从而可能会延误他们来处理这个难以捉摸的漏洞而进行辩论。但他脸上的表情告诉我,情况发生了改变。他说:“我发现了一个漏洞。”安德鲁所做的事情充分说明了他自己像任何一个好经理一样,安德鲁希望保护他的员工免受非议。他亲自接手了这个案子,他认为这可能是另一个死胡同,并想证明这一点以解决问题。MSRC的工程师们非常关心他们对漏洞的分析,他的坚定不移打破了案子。他向我展示了他的分析,一旦我们确定了,下一步就只有一件事可做。
蠕虫灾难
我们走过走廊来到危机经理菲利普的办公室。他正在与某个人开会。我们的脸上一定有些表情,因为他突然转向访问者说:“我稍后会和你谈”。我们进去后我说:“我们有一个零日”。我们解释了基本事实。我们有一个漏洞,可以远程进行匿名攻击,影响所有版本的Windows,它是蠕虫式攻击的,而且有人已经在利用它。
当你对一个危机经理说“蠕虫式攻击”这个词,会激活他隐含的应急反应。他以自己安静的方式从1数到11,立即开始动员所有人。在Code Red和Blaster带来的伤痛下,当一个安全问题是蠕虫式的时候,Microsoft的所有人都会认真对待,把这项工作作为最重要的工作。此时,我让漏洞响应来驱动工程修复,并回到我负责处理的崩溃问题上。
遥感数据能告诉我们更多有关活动和传播的信息吗?我是否可以判断攻击是否有针对性,或者是否已经在大规模地进行传染?我使用了漏洞修复工程技能中的一个"盯住崩溃现象"技术,也就是在每个Windows版本、每个SKU、每个可能失败的攻击方法上找到所有相关的崩溃问题,并对它们进行分析。在Windows Vista和Windows Server 2008上,漏洞一直无法得逞,Microsoft的安全开发周期(SDL)确保这些操作系统版本的svchost.exe进程和RPC接口都具有全面的ASLR和DEP,并已升级要求身份验证,这是一个深度防御的经典例子,现在这些都为我的雷达提供了推动力。
攻击者的急躁给防守者带来回报
为什么原始的攻击代码不能成功?简而言之,攻击者犯了一个错误。
漏洞位于文件名路径组件的规范化(例如,将 \foo..\bar\filename.ext 转换为 \bar\filename.ext)中。攻击者可以触发混淆代码索引字符串的方式,导致缓冲区被复制到不应该的堆栈位置。通常,这个无效的输入会导致错误的代码崩溃,但攻击者想出了一个绕过这个问题的方法。触发漏洞将导致代码进行扫描和搜索,寻找超出缓冲区范围的斜杠字符(‘\’或0x5c)。通常在缓冲区之外的是线程堆栈内容:本地变量、返回地址和指针。攻击者需要在附近放置一个0x5c字节,否则代码会在扫描堆栈时崩溃,第一时间就会触碰到毗邻每个线程堆栈的守卫页。他们的解决方案是首先使用不会触发漏洞的良性输入调用易受攻击的函数,并在下一次调用该函数时保留这些数据。这些数据就像幽灵一样潜藏在堆栈中。如果Windows在处理两个请求时使用相同的线程,这种技术将非常可靠。这几乎始终如此,由于一个巧合,Windows RPC线程池将包含漏洞的第二个请求分配给了另一个线程——这个线程没有精心放置的斜杠字符。netapi32代码继续搜索它,最终运行到线程堆栈的末端,触碰了守卫页,并产生了一个堆栈溢出错误(0xC00000fd),导致进程崩溃。
我的解释是,攻击已经成功并正在下载载荷,但攻击者变得不耐烦或粗心,第二次运行了攻击代码。毕竟,耐心是一种美德!
0:088> .ecxr eax=064c2ffe ebx=064f005c ecx=064ff49a edx=064ff544 esi=064ff464 edi=064ff464eip=5b878809 esp=064ff464 ebp=064ff474 iopl=0 nv up ei pl nz na po cycs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010203netapi32!ConvertPathMacros+0x101:5b878809 6683385c cmp word ptr [eax],5Ch ds:0023:064c2ffe=????0:088> .exr -1ExceptionAddress: 5b878809 (netapi32!ConvertPathMacros+0x00000101)ExceptionCode: c00000fd (Stack overflow)ExceptionFlags: 00000000NumberParameters: 2Parameter[0]: 00000000Parameter[1]: 064c2ffe
崩溃转化为态势感知
在"盯住崩溃现象"之后,我向响应过程提供了情况感知。通过检查崩溃数据中的区域信息,我们对攻击的地理传播情况有了一定的了解。从日本到马来西亚、菲律宾、越南等地,攻击范围广泛。以下是攻击活动的图表。几乎看不见的蓝条表示崩溃活动;而红条则表示攻击成功的活动。
一旦MSRC有了修复补丁,我们决定将其作为超出正常发布时间的更新来发布。每次发布补丁都会启动模仿攻击的时钟。这是MSRC业务中的其中一个困境。当然,您希望尽快发布更新。但是,当您发布超出正常发布时间的更新时,许多IT团队可能无法准备好,这会减慢系统更新的速度。攻击者不会有任何犹豫地下载补丁,进行差异比较并开始构建攻击,而未及时部署更新的防御者会处于不利地位。我们在思考:你是否可以等到补丁发布日期,这样全球各地的IT团队都准备好补丁并采取行动?还是提前发布并扰乱客户?答案很明显。我们有一个关键漏洞,我们发现攻击活动有所增加,我们的补丁已经准备好了,我们超出正常补丁发布时间发布了更新。
我们正在争分夺秒地扑灭一个火灾,以免火势让事态恶化,而Windows更新就像是我们的消防水带。构建Windows更新的工程师们建立了一个惊人的预防系统。虽然我们观察到的都是有针对性的攻击,但我们知道蠕虫随时可能被释放,使用数以亿计的易受攻击的系统作为氧气。MSRC利用了所有可以用来告知客户的方式,以便进行补丁更新。在一周内,Windows Update向4亿台个人电脑提供了补丁更新,并向公司防火墙后面的数百万更多系统进行了更新。我想不出还有哪个系统能以同样的速度更新4亿个任何东西。询问任何人对MS08-067的了解,大多数人都会提到Conficker。到10月份,Conficker甚至还不存在。虽然Conficker非常具有破坏力,但它只影响了未进行补丁更新的电脑。现在想象一下,如果Conficker还有数十亿个系统进行感染会发生什么。
漏洞利用的可靠性
一旦我们公开相关信息,攻击者就消失了,我再也没有见到过他们。不久之后,我看到人们重新发现漏洞,并从漏洞扫描程序中发现了易受攻击的系统,很快就会看到来自僵尸网络攻击的崩溃。如上面的图表所示,我展示了成功的攻击行为。WER只能看到崩溃数据,那我们是如何知道成功的攻击呢?当攻击成功时,它将向Web服务器发送一个响应。一旦攻击被公开,来自世界各地的安全研究人员都希望获得这些日志,不久之后,这些日志就在互联网上公开了,揭示了成功的攻击。通过将成功的攻击与崩溃数据相关联,我们得出结论:我们在最初使用的前6次中发现了攻击行为。攻击代码的可靠性达到了95%,因此我们的假设是正确的,我们看到了真实攻击行为的影子。超出正常发布时间发布更新的决定是正确的。
最终思考
对我而言,MS08-067永远是一个特别的记忆。安全工作通常是一个苦差事,攻击事件会吸引媒体的关注。但是这个故事展示了防御工作同样可以充满创意和激情,寻找漏洞的机智,发现秘密的惊喜,以及保护客户的渴望已经深深种在人们的血液里,这也是安全工作更多的是一种集体行动而非个体的工作。这就是“防御者的心态”。
虽然我将这些事件描述为个人趣闻,但如果没有许多了不起的人,包括:弗雷德·亚伦(Fred Aaron)、申震旭(Jinwook Shin)、达伦·卡纳沃(Darren Canavor)、安德鲁·罗斯(Andrew Roths)、艾伦·琼斯(Allen Jones)、马特·汤姆林森(Matt Thomlinson)、大卫·格兰特(David Grant)、尼古拉·考伊(Nicola Cowie)、马克·沃德里奇(Mark Wodrich)、达米安·哈斯(Damian Hasse)、罗伯托·班伯格(Roberto Bamberger)、金书满(Kinshuman Kinshumann)、史蒂文·沃特(Steven Wort)、文斯·奥尔戈万(Vince Orgovan)、马特·米勒(Matt Miller)、肯·约翰逊(Ken Johnson)、亚当·扎布罗克(Adam Zabrocki)、约翰·班斯(John Banes)、克里斯·贝茨(Chris Betz),被广泛认为是“Watson”之父的柯克·格鲁鲁姆(Kirk Glerum),这次开创性的漏洞崩溃分析是不可能的。还有其他许多人参与了响应工作,我为未全部列出表彰而道歉。对于我们所有的安全合作伙伴和客户的安全专业人员来说,你们保护了世界!