Windows 10 Egghunter(WoW64)及更多内容
引言
好吧,我承认,我一直对Egghunter有些着迷。但这并不意味着我喜欢仅仅因为其功能而使用(或滥用)Egghunter。事实上,我认为如果能避免使用Egghunter,那是一种好的做法,因为它们往往会降低运行速度。
我的意思是,我一直对在不导致进程崩溃的情况下搜索内存的技术很着迷。这只是个人喜好,不太重要。真正重要的是Corelan Team回来了。嗯,是我回来了。这是我近三年来的第一篇(技术性)文章,也是自那时起Corelan Team“逐渐淡出”后的第一篇文章。(实际上,我很好奇(一些)最初的Corelan Team成员是否能够再次抽出时间来联手,开始进行/发表一些研究。我当然希望如此,但让我们拭目以待。)
正如你们中的一些人所知,我最近离开了我的日常工作。(说来话长,篇幅太长,不适合在这篇文章中讲述。很乐意边喝一杯边分享细节。)我创办了一家名为“Corelan Consulting”的新公司,并试图通过漏洞开发培训和网络安全咨询谋生。培训进展顺利,2019年的课程几乎已经排满,并且已经在计划2020年的课程了。你可以在这里找到培训时间表。如果你有兴趣在你的公司或会议上设立Corelan Bootcamp或Corelan高级课程,请先阅读感言,然后联系我。我仍然需要提高与锁定咨询项目相关的销售技巧,但我相信最终一切都会好起来的。(是的,如果你想与我合作,请联系我,我可以兼职做治理/风险管理与评估工作;-))
无论如何,在构建2019年版Corelan Bootcamp课程、更新Windows 10材料时,我意识到Lincoln编写的Windows 7的wow64 Egghunter在Windows 10上不再有效。事实上,我某种程度上预料到它会失败,因为我们知道微软在每个主要的Windows版本中都会更改系统调用号。由于最常用的Egghunter机制是基于系统调用的,很明显,更改号码会破坏Egghunter。
顺便说一下:系统调用(及其号码)记录在:https://j00ru.vexillium.org/syscalls/nt/64/ (感谢Mateusz “j00ru” Jurczyk)。你可以在上述网站的表格中找到“NtAccessCheckAndAuditAlarm”系统调用号的演变。
无论如何,更改系统调用号听起来并不太令人兴奋或困难,但我们也清楚,Windows 10版本的系统调用的参数和堆栈布局、行为也与Windows 7版本不同。
我们发现一些Windows 10 Egghunter的PoC流传,但发现它们在真正的漏洞利用中并不总是可靠。Lincoln研究了一会儿,进行了一些调试,并制作了一个适用于Windows 10的版本。:)
所以,这意味着我们很自豪能够宣布一个适用于Windows 10的(wow64)Egghunter。下面的版本已在真实的漏洞利用和目标中测试过。
Windows 10的wow64 Egghunter
如前所述,挑战在于弄清楚新的系统调用期望其参数在何处以及如何传递,它如何更改寄存器和堆栈以确保参数始终处于正确位置,并提供预期的功能:测试给定页面是否可访问,并且在此过程中不导致进程死亡。
以下是更新后的例程:
|
|
这个Egghunter在Windows 10上运行良好,但它假设你在wow64环境下运行(64位操作系统上的32位进程)。当然,正如Lincoln在他的博客文章中所解释的,你可以简单地添加一个检查来确定架构,并使Egghunter也能在原生32位操作系统上运行。
你可以用mona.py生成这个Egghunter——只需运行!mona egg -wow64 -winver 10。
在调试这个Egghunter(或任何使用系统调用的wow64 Egghunter)时,你会注意到在执行系统调用期间会出现访问违规。这些访问违规可以安全地传递,并由操作系统处理……但调试器每次看到访问违规时都会中断。(实际上,当代码尝试测试不可读的页面时,调试器就会中断。换句话说,你会遇到大量访问违规,需要你手动干预。)
如果你使用Immunity Debugger,你可以简单地告诉调试器忽略访问违规。为此,点击’debugging options’,然后打开’exceptions’选项卡。在"Add range"下添加以下十六进制值:
|
|
当然,调试完Egghunter后,别忘了再次移除这两个异常。:)
未来展望
当然,微软有权在其操作系统中更改他们想要的任何内容。我认为开发人员不应该自己发出系统调用,我相信他们应该使用ntdll.dll中的包装函数。换句话说,对于微软来说,更改系统调用号应该是“安全”的。我不知道每个Windows版本增加系统调用号背后有什么原因,我也不知道系统调用号是否会永远保持不变,因为Windows 10被标记为“最后一个Windows版本”。从Egghunter的角度来看,那将是很棒的。随着越来越多的人采用Windows 10,Egghunter的成功率也会越来越高。但实际上,我不知道这是否是一个有效的假设。
无论如何,这让我思考:有没有办法使用不同的技术使Egghunter工作,而不使用系统调用?如果存在这样的技术,它是否也能在旧版本的Windows上工作?如果我们不使用系统调用,它是否能在原生x86和wow64环境下直接工作?
让我们看看。
异常处理
skape撰写的关于Egghunter的原始论文(“Safely Searching Process Virtual Address Space”,2004年!)已经介绍了使用自定义异常处理程序来处理尝试从不可访问的页面读取时发生的访问违规。通过使处理程序指回Egghunter,Egghunter将能够继续运行。不幸的是,原始的实现似乎不再有效。在进行一些测试时(许多年前,以及最近在Windows 10上),看起来操作系统并不真正允许你使异常处理程序直接指向堆栈(尚未尝试堆,但我预计存在相同的限制)。换句话说,如果Egghunter从堆栈或堆中运行,你将无法使Egghunter使用自身作为异常处理程序并继续运行。
在寻找可能的解决方案之前,让我们回顾一下异常处理机制是如何工作的。当操作系统看到异常并决定将其传递给进程中的相应线程时,它将指示ntdll.dll中的一个函数在该线程内启动异常处理机制。这个例程将检查TEB的偏移量0(通过FS:[0]访问),并检索堆栈上异常处理链中最顶层的记录的地址。每个记录由2个字段组成:
|
|
最顶层的记录包含将被首先调用的例程的地址,以检查应用程序是否可以处理异常。如果该例程失败,将尝试链中的下一条记录(直到其中一个例程能够处理异常,或者直到使用默认处理程序,将进程送入天堂)。所以,换句话说,ntdll.dll中的例程将找到记录,并调用“handler”地址(即记录第二个字段中放置的任何内容)。
因此,将其转化为Egghunter世界:如果我们想在对异常发生时保持控制,我们将不得不创建一个自定义的“最顶层”SEH记录,确保在Egghunter执行期间它始终是最顶层的记录,并且我们必须使记录的处理程序指向一个允许我们的Egghunter继续运行并处理下一页的例程。同样,如果我们的“自定义”记录是最顶层的记录,我们将确信它将是第一个被使用的记录。
当然,我们应该小心,并考虑到运行异常处理机制的后果和影响:
- 异常处理机制将改变ESP的值。该功能将在新的ESP位置创建一个“异常分发器堆栈”帧,并在ESP+8处有一个指向原始SEH帧的指针。我们必须“撤销”对ESP的这一更改,以确保它指向堆栈上Egghunter存储其数据的区域。
- 接下来,我们还应该避免一直创建新记录。相反,我们应该尝试继续使用同一个记录,避免不断向堆栈推送数据,避免堆栈空间耗尽。此外,当然,Egghunter需要能够在内存中的任何位置运行。
- 最后,无论我们放什么作为“SE Handler”(记录的第二个字段),都必须是SAFESEH兼容的。不幸的是,这是我的“解决方案”的弱点。此外,如果SEHOP处于活动状态,我的例程将无法工作。(但据我所知,这在客户端系统上默认不活动。)
创建我们自己的自定义SEH记录意味着我们将向堆栈写入一些内容,覆盖/破坏已经存在的内容。因此,如果你的Egghunter/shellcode也在堆栈上的那个位置附近,你可能需要在运行Egghunter之前调整ESP。只是提醒一下。:)
这是我的基于SEH的Egghunter的样子(准备好用nasm编译):
|
|
让我们看看Egghunter的各个组成部分。
首先,Egghunter以一个“GetPC”例程开始(旨在找到其自身在内存中的绝对地址),然后是一条指令,该指令将0x1d字节添加到它使用该GetPC例程检索到的地址。添加此偏移量后,ECX将包含实际“handler”例程在内存中的绝对地址。(在上面的代码中用标签“handle”引用)。请记住,Egghunter需要能够在运行时动态确定此位置,因为Egghunter将使用异常处理机制返回到自身并继续运行Egghunter。这意味着我们需要知道(确定)它的位置,在堆栈上存储引用,以便我们稍后在异常处理机制中可以“检索/跳转”到它。
接下来,代码正在创建一个新的自定义SEH记录。虽然SEH记录只需要2个字段,但代码实际上将5个特制的值压入堆栈。只有最后2个将成为SEH记录,其他几个用于允许异常处理程序恢复ESP并继续执行Egghunter。让我们看看压入了什么以及为什么:
PUSH ECX:这是“handle”例程在内存中的地址,由之前的GetPC例程确定。异常处理程序最终需要返回到这个地址。PUSH ECX:我们再次压入地址,但这个不会被使用。我们将使用pop/pop/ret指针两次。第一次将用于异常处理程序将执行带回我们的代码,第二次它将用于返回到存储在堆栈上的“ECX”。第二个ECX只是为了补偿p/p/r中的第二个POP。你可以在堆栈上压入任何你喜欢的内容。PUSH 0x90c3585C:此代码将被执行。它是POP ESP, POP EAX, RET。这将把堆栈重置回我们存储SEH记录的原始堆栈位置。RET将执行转移回堆栈上的p/p/r指针(SEH记录的一部分)。换句话说,p/p/r指针将被使用两次。第二次,它最终将返回到存储在堆栈上的ECX地址。(见之前的PUSH ECX指令)- 接下来,通过将另外2个值压入堆栈来创建真正的SEH记录:
- 指向P/P/R的指针(必须是非safeseh保护的指针)。我们必须使用p/p/r,因为我们不能让这个handler字段直接指向堆栈(或堆)。由于我们不能让异常机制直接返回到我们的代码,我们将使用pop/pop/ret来保持对执行流的控制。在上面的代码中,你必须将
0x44444444值替换为非SafeSEH保护的pop/pop/ret的地址。然后,当发生异常时(即当Egghunter到达一个不可访问的页面时),pop/pop/ret将第一次被触发执行,返回到SEH记录第一个字段中的4个字节。 - 在SEH记录的第一个字段中,我放置了2个pop和一个短跳转序列。这将稍微调整堆栈,使指向SEH记录的指针位于堆栈顶部。接下来它将跳转到之前压入堆栈的指令序列(
0x90C3585C)。如前所述,该序列将再次触发POP/POP/RET,最终将返回到存储的ECX指针(即Egghunter所在的位置)。
- 指向P/P/R的指针(必须是非safeseh保护的指针)。我们必须使用p/p/r,因为我们不能让这个handler字段直接指向堆栈(或堆)。由于我们不能让异常机制直接返回到我们的代码,我们将使用pop/pop/ret来保持对执行流的控制。在上面的代码中,你必须将
为了完成SEH记录的创建并将其标记为最顶层的记录,我们只需将其位置写入TEB。由于我们的新自定义SEH记录当前位于ESP,我们可以简单地将ESP的值写入TEB的偏移量0(MOV DWORD [FS:EBX],ESP)。(这就是我们一开始清除EBX的原因。)
此时,Egghunter已准备好测试页面是否可读。代码将使用EDX作为读取位置的引用。例程首先转到页面的末尾(OR DX, 0x0FFF),然后转到下一页的开头(INC EDX),然后我们将EDX的值存储在堆栈上(在[ESP+24]),以便异常处理程序稍后可以拾取它。如果读取尝试(SCASD)失败,将触发访问违规。访问违规将使用我们的自定义SEH记录(因为它应该是最顶层的记录),并且该例程旨在恢复Egghunter的执行(通过运行“handle”例程,该例程最终将从堆栈恢复EDX指针并移动到下一页)。“handle”例程将:
- 再次调整堆栈,将其位置更正为运行Egghunter时所在/应该所在的位置。(
SUB ESP,0x14) - 接下来,它将确保我们的自定义记录再次成为最顶层的SEH记录(只是预见到其他代码可能添加了新的最顶层记录)。
- 最后,它将从堆栈中拾取一个引用(我们存储了上次尝试访问的地址)并继续(到下一页)。
如果页面可读,Egghunter将检查标签的存在,两次。如果找到标签,最终的JMP EDI将告诉CPU运行放置在双标签后面的代码。
调试Egghunter时,你会注意到它会抛出访问违规(当代码尝试访问不可访问的页面时)。当然,在这种情况下,这些访问违规是完全正常的,但你仍然必须将异常传递给应用程序(Shift F9)。你也可以配置Immunity Debugger以忽略(并传递)这些异常,通过配置Exceptions。为此,点击’debugging options’,然后打开’exceptions’选项卡。在"Add range"下添加以下十六进制值:
|
|
当然,调试完Egghunter后,别忘了再次移除这两个异常。
为了使用Egghunter,你需要先将asm指令转换为操作码。为此,你需要安装nasm。(我使用了Win32安装程序,来自https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/win32/)
将上面的asm代码片段保存到文本文件中(例如“c:\dev\win10_egghunter_seh.nasm”)。接下来,运行“nasm”将其转换为包含操作码的二进制文件:
|
|
接下来,将二进制文件的内容转储为可以在脚本和漏洞利用中使用的十六进制格式:
|
|
(你可以在Corelan的github仓库中找到bin2hex.py脚本的副本)
如果一切顺利,你将得到:
|
|
再次提醒,不要忘记将\x44\x44\x44\x44(第三行末尾)替换为pop/pop/ret的地址(并且,如果你正在编辑字节,请以小端字节序存储地址 :))
Python友好的复制/粘贴代码:
|
|
我还没有将这个例程添加到mona.py中(但我最终会在某个时候添加)。当然,如果你发现有改进的空间,并且/或者能够减小Egghunter的大小,请不要犹豫告诉我。(在将其添加到mona之前,我会等待你的反馈一段时间。)
当然,我很想知道Egghunter是否对你有用,以及它是否能在不同Windows版本和架构(32位系统、旧版Windows等)上运行。
到此结束
感谢阅读!我希望你喜欢这篇全新的文章,并且和我一样对未来充满期待。
如果你想聊天、讨论信息安全话题、提问(和回答问题),请注册加入我们的Slack工作区。要访问该工作区:
- 访问https://www.facebook.com/corelanconsulting(并顺便点赞该页面)。你不需要Facebook帐户,该页面是公开的。
- 浏览帖子,找到包含Slack邀请链接的那个。
- 注册,完成。
另外,请随时在Twitter上关注我们(@corelanconsult)以获取新文章和博客帖子的通知。
Corelan培训与Corelan咨询
本文只是你在我们的Corelan Bootcamp中学到内容的一个小例子。如果你想参加我们的Corelan课程之一,请在https://www.corelan-training.com/index.php/training-schedules查看我们的时间表。如果你更愿意在你的公司或会议上设立课程,请通过此表格联系我。
正如文章开头所解释的:培训和咨询项目现在是我的主要收入来源。只有在我能够谋生的前提下,我才能免费进行研究和发布信息。这个网站由Corelan Consulting支持、托管和资助。我能教的课程越多,能做的咨询越多,我就能在研究上和发布教程上投入更多时间。
谢谢!