你以为服务器关了这事就结束了? – XcodeGhost截胡攻击和服务端的复现,以及UnityGhost预警

0×00 序


截胡,麻将术语,指的是某一位玩家打出一张牌后,此时如果多人要胡这张牌,那么按照逆时针顺序只有最近的人算胡,其他的不能算胡。现也引申意为断别人财路,在别人快成功的时候抢走了别人的胜利果实。

虽然XcodeGhost作者的服务器关闭了,但是受感染的app的行为还在,这些app依然孜孜不倦的向服务器(比如init.icloud-analysis.com,init.icloud-diagnostics.com等)发送着请求。这时候黑客只要使用DNS劫持或者污染技术,声称自己的服务器就是”init.icloud-analysis.com”,就可以成功的控制这些受感染的app。具体能干什么能,请看我们的详细分析。

另外,有证据表明unity 4.6.4 – unity 5.1.1的开发工具也受到了污染,并且行为与XcodeGhost一致,更恐怖的是,还有证据证明XcodeGhost作者依然逍遥法外,具体内容请查看第三节。

PS:虽然涅槃团队已经发出过攻击的demo了2,但很多细节并没有公布。所以我们打算在这篇文章中给出更加详细的分析过程供大家参考。

0×01通信协议分析


在受感染的客户端App代码中,有个Response方法用于接收和处理远程服务器指令。

Response方法中根据服务器下发的不同数据,解析成不同的命令执行,根据我们分析,此样本大致支持4种远程命令,分别是:设置sleep时长、窗口消息、url scheme、appStore窗口。

通过4种远程命令的单独或组合使用可以产生多种攻击方式:比如下载安装企业证书的App;弹AppStore的应用进行应用推广;弹钓鱼页面进一步窃取用户信息;如果用户手机中存在某url scheme漏洞,还可以进行url scheme攻击等。

其通信协议是基于http协议的,在传输前用DES算法加密http body。Response方法拿到服务器下发送的数据后,调用Decrypt方法进行解密:

如果解密成功,将解密后的数据转换成JSON格式数据:

然后判断服务器端下发的数据,执行不同的操作。如下面截图是设置客户端请求服务端器sleep时长的操作:

0×2恶意行为分析及还原


在逆向了该样本的远程控制代码后,我们还原了其服务端代码,进一步分析其潜在的危害。

首先我们在服务端可以打印出Request的数据,如下图:

红色框标记的协议的头部部分,前4字节为报文长度,第二个2字节为命令长度,最后一个2字节为版本信息,紧跟着头部的为DES的加密数据。我们在服务端将数据解密后显示为:

这里有收集客户端信息上传到控制服务器。

同样我们返回加密数据给客户端:

明文信息为:

客户端根据App的运行状态向服务端提供用户信息,然后控制服务器根据不同的状态返回控制数据:

恶意行为一 定向在客户端弹(诈骗)消息

该样本先判断服务端下发的数据,如果同时在在“alertHeader”、“alertBody”、“appID”、“cancelTitle”、“confirmTitle”、“scheme”字段,则调用UIAlertView在客户端弹框显示消息窗口:

消息的标题、内容由服务端控制

客户端启动受感染的App后,弹出如下页面:

恶意行为二 下载企业证书签名的App

当服务端下发的数据同时包含“configUrl”、“scheme”字段时,客户端调用Show()方法,Show()方法中调用UIApplication.openURL()方法访问configUrl:

通过在服务端配置configUrl,达到下载安装企业证书App的目的:

客户端启动受感染的App后,目标App将被安装:

demo地址:http://v.youku.com/v_show/id_XMTM0MTQyMzM1Ng==.html

恶意行为三 推送钓鱼页面

通过在服务端配置configUrl,达到推送钓鱼页面的目的:

客户端启动受感染的App后,钓鱼页面被显示:

demo地址:http://v.youku.com/v_show/id_XMTM0MTQyMjkyOA==.html

恶意行为四 推广AppStore中的应用

通过在服务端配置configUrl,达到推广AppStore中的某些应用的目的:

phishing1.html页面内容:

客户端启动受感染的App后,自动启动AppStore,并显示目标App的下载页面:

demo地址:http://v.youku.com/v_show/id_XMTM0MTQyNTk0MA==.html

0×03 UnityGhost?


在大家以为一切都完结的时候,百度安全实验室称已经确认”Unity-4.X的感染样本”。并且逻辑行为和XcodeGhost一致,只是上线域名变成了init.icloud-diagnostics.com。这意味,凡是用过被感染的Unity的app都有窃取隐私和推送广告等恶意行为。

Unity是由Unity Technologies开发的一个让玩家创建诸如三维视频游戏、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。很多有名的手机游戏比如神庙逃亡,纪念碑谷,炉石传说都是用unity进行开发的。

更令人恐怖的是,在百度安全实验室确认后没多久,大家就开始在网上寻找被感染的Unity工具,结果在我搜到一个Unity3D下载帖子的时候发现”codeFun与2015-09-22 01:18编辑了帖子”!?要知道codeFun就是那个自称XcodeGhost作者的人啊。他竟然也一直没睡,大半夜里一直在看大家发微博观察动静?随后发现大家知道了Unity也中毒的事情,赶紧去把自己曾经投毒的帖子删了?

现在再去看那个帖子已经被作者删的没有任何内容了。。。 http://game.ceeger.com/forum/read.php?tid=21630&fid=8

但根据XcodeGhost作者没删之前的截图表明,从unity 4.6.4 – unity 5.1.1的开发工具都有可能被投毒了!

0×04 总结


虽然病毒作者声称并没有进行任何广告或者欺诈行为,但不代表别人不会代替病毒作者进行这些恶意行为。并且作者依然还在逍遥法外!所以立刻!马上!删掉那些中毒的app吧!

0×05 参考资料


  1. 涅槃团队:Xcode幽灵病毒存在恶意下发木马行为 http://drops.wooyun.org/papers/8973
  2. XcodeGhost 源码 https://github.com/XcodeGhostSource/XcodeGhost

你以为服务器关了这事就结束了? – XcodeGhost截胡攻击和服务端的复现,以及UnityGhost预警,首发于博客 – 伯乐在线

让微信等知名 APP 都中招的 XcodeGhost 事件全面详细回顾

9月18日,我们在「iOS大全」(iOShub)微信已做过首次汇总。19日晚,「程序员的那些事」主页君根据当前网上相关信息,按照时间线,再次做一次更全面汇总。

 

2015-09-14

国家互联网应急中心 发布预警

不过这个公告,绝大多数开发者,也是昨天开始才知道的。

2015-09-16

腾讯安全响应中心称,「发现 App Store上的 TOP5000 应用有 76 款被感染,于是我们向苹果官方及大部分受影响的厂商同步了这一情况。」

 

2015-09-17

XcodeGhost 事件在网上升温发酵

【9:45】唐巧 发了一条微博:

随后很多留言的小伙伴们纷纷表示中招,@谁敢乱说话表示:”还是不能相信迅雷,我是把官网上的下载URL复制到迅雷里下载的,还是中招了。我说一下:有问题的Xcode6.4.dmg的sha1是:a836d8fa0fce198e061b7b38b826178b44c053a8,官方正确的是:672e3dcb7727fc6db071e5a8528b70aa03900bb0,大家一定要校验。”另外还有一位小伙伴表示他是在百度网盘上下载的,也中招了。

 

【16:00】国外安全公司 paloalto 发现了 XcodeGhost 问题,并发布第一版分析报告

【17:43】阿里的蒸米、迅迪发布了他们文章:《 XCode 编译器里有鬼,XCodeGhost 样本分析

@程序员的那些事 主页君注:内容为节选,有删减)

虽然 XCodeGhost 并没有非常严重的恶意行为,但是这种病毒传播方式在iOS上还是首次。也许这只是病毒作者试试水而已,可能随后还会有更大的动作,请开发者务必要小心。

这个病毒让我想到了UNIX 之父 Ken Thompson 的图灵奖演讲 “Reflections of Trusting Trust”。他曾经假设可以实现了一个修改的 tcc,用它编译 su login 能产生后门,用修改的tcc编译“正版”的 tcc 代码也能够产生有着同样后门的 tcc。也就是不论 bootstrap (用 tcc 编译 tcc) 多少次,不论如何查看源码都无法发现后门,真是细思恐极啊。

根据热心网友举报,投毒者网名为『coderfun』。他在各种iOS开发者论坛或者weibo后留言引诱iOS开发者下载有毒版本的Xcode。并且中毒的版本不止Xcode 6.4,还有6.1,6.2和6.3等等。

 

2015-09-18

第一批受感染的 APP 陆续被曝光

【14:14 】开始,@图拉鼎  在微博持续发布了由他测试验证受感染的 APP,至少包括如下:

  • 网易云音乐
  • 滴滴出行
  • 12306
  • 中国联通手机营业厅
  • 高德地图
  • 豌豆荚的开眼
  • 网易公开课
  • 下厨房
  • 51卡保险箱(金融应用)
  • 同花顺
  • 中信银行动卡空间

这个列表出来后,令人震惊。

【15:43】网易云音乐发公告

摘几个网友对该公告的评论:

@吴明prfc :

这公告,用被小偷改造的工具,导致被偷。给你说小偷走了,没威胁了,大家放心

@祝佳音:

翻译:虽然我们愚蠢又无能,也不知如何收拾残局,但不知道为什么,好像敌人暂时没动静了。这件事就当没发生,就当没发生!

【19:17】@Saic 童鞋发布《XcodeGhost 实际用途猜测分析》,给出了 XG  木马的逻辑:

在用户安装了目标应用后,木马会向服务器发送用户数据。

服务器根据需要返回模拟弹窗。

弹窗可以是提示支付失败,请到目标地址付款,也可以是某个软件的企业安装包。

用户被诱导安装未经审核的安装包后,程序可以调用系统的私有 API,实现进一步的攻击目的。

 

木马中还有一些调用应用内购的攻击逻辑,就不展开说明了。

如果之前有遇到任何程序弹出非系统需要输入 Apple ID 或密码的网站,并输入过密码的,还请尽早修改。

全文:http://weibo.com/p/1001603888503866975286

 

【21:02】微博上曝光了第二份受感染的 APP 列表:


(右侧是版本信息)

 

【21:43】中招的@腾讯微信团队 在微博发声明,里面提到:

1.该问题仅存在iOS 6.2.5版本中,最新版本微信已经解决此问题,用户可升级微信自行修复,此问题不会给用户造成直接影响。

全文:http://weibo.com/p/1001603888540667758451

 

2015-09-19

【凌晨 4:39】自称 XcodeGhost 作者的网友在微博发声明,称只是实验项目,无任何威胁

以下引用部分微博上针对该声明的评论:

@唐巧_boy:我刚刚看了一下作者放出的源码,和逆向出来的代码行为一致,应该是真的。

@南非蜘蛛:你有点小调皮,想进监狱了是吧?

@Daniel_K4:作者是实验么?持续传播了半年,我不太相信。。。现在才出来发布说明,应该是被定位到吓坏了吧。

@Livid:一个程序员出于试验目的做的修改版 Xcode.dmg 能够通过自然分发的方式装到那么多重要 App 的开发环境里,也是一件挺不可思议的事情。

@Easy:翻一下就是「我就配了把你家钥匙。还没开始偷东西呢,当然是无害了」。

@tombkeeper 事闹大了,就会变成公安部督办案件,就几乎一定能破案,几乎一定能抓到人。这时候无论自首还是跑路都比发“澄清”有意义得多。

@onevcat:算个账,微信用户总数 5 亿日活70%。每天每人就算5个POST请求,每个请求300Byte,日流入流量就接近500G,以及17.5亿次请求。据说服务器扔在亚马逊,那么资费算一下每个月应该是存储$450,请求$260K。这还只是单单一个微信,再算上网易云音乐等等,每月四五十万刀仅仅是苦逼iOS开发者的个人实验?

2015-09-19  上午

为避免给更多用户造成可能是伤害,苹果公司开始下架受感染的 APP,并给相应开发商发邮件

图来自@小小小小_灿,可能看不清。在邮件中,苹果也说明了重新上架的条件:

  1. 从官方下载 Xcode;
  2. 重新编译打包;
  3. 提交等待审核;

 


【开发圈和安全圈的事后讨论】

(不分时间顺序)

@Saic:

苹果以审核严格著称,为何会允许应用上架???

 

恶意代码加载到程序中后,将收集到的用户信息加密,发送到远程服务器。

收集的信息包括系统版本,程序名称,用户的唯一识别 ID,语言等非敏感信息。

对苹果来说,这段代码与普通的第三方统计代码并没有区别,甚至你在使用一些程序内的微博登陆或微信分享功能时,微博和微信都可能会收集这些信息到自己的服务器。

因为没有涉及到苹果禁止开发者使用的接口,一切看起来都很正常,所以带有恶意代码的应用可以正常发布到 App Store。

 

对我有什么影响???

根据目前的研究进展以及自称是开发者公布的恶意代码源码,代码主要做了以下事情:

在用户安装了目标应用后,木马会向服务器发送用户数据。

服务器会返回一些可以让程序弹出提示的控制代码,例如:

  • 用户名密码错误,请到以下地址修改,用户确认后跳转到一个伪造的钓鱼网站
  • 弹出 App Store 官方的应用下载页面,诱导用户下载
  • 程序有升级,用户确认后可以利用非官方渠道、修改过的应用替换掉当前应用
  • 其他非官方应用程序的推广和下载

因为弹窗是从用户信任的应用里弹出,很多时候不会多做怀疑就会授权或确认下载。

另外根据相关研究,代码可能存在多种变种 [ 3 ],可能存在直接窃取用户 Apple ID 的版本,模拟系统登陆框在技术上是可行的。

Via: http://weibo.com/p/1001603888803550000430

 

@图拉鼎:

对于 iOS 开发者的建议,立即删除从不明来源下载的 Xcode,即使你是用官方地址然后在迅雷上下载的,最后从 App Store 安装最新版本的 Xcode。有条件的公司应该在今天开始专门设置一台有专人管理的 Build Server,所有发布至 App Store 的 App 只能从该台电脑 Build 并发布,以防止未来此类事件的再现。

 

@sunny_THU:

2. 这件事情是不是很严重

可以说很严重,也可以说不严重。说这件事情很严重,是因为我们把程序的控制权分享分享给了第三个人,他可以做任何程序内部的操作,包括监控输入,支付,跳转,用户的内容等行为(但也仅限于单个中毒的app内部,系统层的内容和别的app的内容是拿不到的,这里就不得不感谢苹果的沙盒机制,在机制上保护了一定的安全性),并且这次攻击证明根本上苹果软件的开发和提交机制是有漏洞的,更严重的问题是会不会已经有了一些别的病毒在软件中,只是没有被发现。 不严重是因为这次的病毒如果按照木马作者披露的源码,并没有做严重损害用户隐私的事情(一些安全机构逆向了源码,基本和作者公开的代码符合 http://security.tencent.com/index.php/blog/msg/96 )。

3. 怎么避免这种事情

“程序最大的bug不是程序自身,而是人”。这次事件是各种因素一起影响的结果,开发者根本没有意识到Xcode都会被攻击,国内网络环境太差,公司不配备VPN等等。而开发者作为软件的开发者和安全的负责人以及直接责任人更需要去规范和争取,因为你比其他人知道的更多,也更清楚问题有多严重。

 

@TK 教主:

2015 年 3 月 10 日的这篇文章透露:美国圣地亚国家实验室在 2012 年 CIA 的秘密会议“Jamboree”上提出被称作“Strawhorse”的攻击方式:通过修改 Xcode 使开发商不知情地发布带有后门的 APP。“codefun” 开始散布修改后的 Xcode 是在 3 月中旬,不知道是不是受了这个启发。

 

@月光博客:

迅雷的回应如下:对于Xcode被植入恶意代码一事,有猜测称迅雷服务器受到感染,导致使用迅雷会下载到含有恶意代码的Xcode。迅雷第一时间安排工程师进行检测,并对比了离线服务器上的文件,结果都与苹果官方下载地址的文件信息一致。也就是说,官方链接的Xcode经迅雷下载不会被植入恶意代码。

XcodeGhost事件并不表明苹果iOS的安全性相比安卓有什么问题,实际上,就开发环境来说,安卓实际上也是一样的,苹果的开发软件下载速度慢,而安卓的开发软件不翻墙根本无法从官网下载,大量安卓开发者都不是从官网下载的开发环境,很多开发工具来路不明,因此安卓很可能也存在类似问题。

 

@唐巧:

XcodeGhost 这件事情,苹果自己也有责任,Mac App Store下载速度慢得要死,每次下Xcode花个几十分钟非常正常,这才造成大家都用迅雷和百度网盘这种非官方渠道。就说现在吧,我的Mac系统更新了一上午,还是处于卡顿无进度状态。[泪]

 

@腾讯玄武实验室:

虽然目前 init.icloud-analysis.com 等 XcodeGhost 相关域名已经不可访问,XcodeGhost 的始作俑者可能也不会再利用其干坏事,但这并不代表其他人不会。

有心干坏事的人,可以通过 DNS 投毒、伪造 WiFi 接入点等手段,控制或大或小的一个地区对 init.icloud-analysis.com 等域名的解析,从而变成 XcodeGhost 的控制者。所以受影响的 APP 开发商还是应该尽快发布干净的版本让用户升级。

 

 


【主页君留言】:今天这篇耗时 2+ 小时。如果各位觉得不错,请分享给其他朋友。

微信号:iProgrammer

(长按上图,可自动识别二维码)

其他媒体和公号若转载

必须保留全文完整内容,包括本段声明和二维码图片

 

让微信等知名 APP 都中招的 XcodeGhost 事件全面详细回顾,首发于博客 – 伯乐在线

内存寻址原理

在做网络安全事件分析的时候,都会遇到内存寻址的知识,例如上次跟大家分享的《 空指针漏洞防护技术》,就涉及到非法访问内存地址的问题。如果这个坎儿迈不过去,你就会迷失在代码中,更无从分析了。今天绿盟科技的安全技术专家就讲讲这个内存寻址的原理,文章分为上下两篇《内存寻址原理》及《内存寻址方式》。

随着信息化发展和数据处理能力需求的提高,对计算机硬件产品的性能和容量也提出了新的挑战,要求计算机处理能力也要能随实际情况需求的变动而提升、改变。

当下,一台普通的电脑硬盘容量也要200多G,内存也有4G;如此大容量的硬盘和内存,在处理大量数据或是大型游戏面前还是显得力不从心,需要通过扩容来满足需求,比如将内存由4G提升到8G或是16G不等。扩容后对个人体验确实提升不少。对于内存容量的提升需要有相应的硬件基础支撑,需要有能消化掉这么多内存的寻址地址。比如说如果一8位单片机如果要装载16G的内存,那就是暴殄天物。

哪里有需求哪里就有市场 ;计算机从8位的51单片机,20位8086寻址,发展到32位 win2003,64位win10,都是由于信息化需求的膨胀推动着计算机一代又一代的改革创新。

对于内存的扩容,很多人都不是很清楚应用程序如何使用的物理内存地址。远了不说,单说现在常用计算机中的32位、64系统;系统是怎么样将虚拟地址转化成线性地址,线性地址又是怎样转换成物理地址的,其中又用到了哪些寄存器或是数据结构,相信很多人对此也是一知半解;也像我一样,想结合实例从地址转换的本质来掌握其精髓之处。接下来就一起学习从逻辑地址到物理地址的整个转换过程。

1.实模式与保护模式简介

CPU常见三种工作模式:实模式与保护模式,虚拟8086模式。

实模式 :CPU复位(reset)或加电(power on)的时候以实模式启动,处理器以实模式工作。在实模式下,内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB。在实模式下,所有的段都是可以读、写和可执行的。实模式下没有分段或是分页机制,逻辑地址和物理地址相等。

由此得知:

  1. 在实模式下最大寻址空间时1M,1M以上的内存空间在实模式下不会被使用。
  2. 在实模式所有的内存数据都可以被访问。不存在用户态、内核态之分。
  3. 在BIOS加载、MBR、ntdlr启动阶段都处在实模式下。

保护模式 :对于保护模式大家并不陌生;是目前操作系统的运行模式,利用内存管理机制来实现线性地址到物理地址的转换,具有完善的任务保护机制。

保护模式常识:

  1. 现在应用程序运行的模式均处于保护模式。
  2. 横向保护,又叫任务间保护,多任务操作系统中,一个任务不能破坏另一个任务的代码,这是通过内存分页以及不同任务的内存页映射到不同物理内存上来实现的。
  3. 纵向保护,又叫任务内保护,系统代码与应用程序代码虽处于同一地址空间,但系统代码具有高优先级,应用程序代码处于低优先级,规定只能高优先级代码访问低优先级代码,这样杜绝用户代码破坏系统代码。

虚拟8086 模式: 简称V86模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。可以把8086程序当做保护模式的一项任务来执行。虚拟8086允许在不退出保护模式的情况下执行8086程序。

虚拟8086常识:

  1. 寻址的地址空间是1M字节.
  2. 可以在虚拟8086模式下运行16位DOS程序。
  3. 在V86模式下,代码段总是可写的;这与实模式相同,同理,数据段也是可执行的。
  4. 32系统编写V86模式的程序:

2. 保护模式寻址基础知识

接下来就以32位系统为例,介绍保护模式下,内存中一些地址转换相关的寄存机和数据结构。

2.1 内存地址概念

逻辑地址 :在进行C语言编程中,能读取变量地址值(&操作),实际上这个值就是逻辑地址,也可以是通过malloc或是new调用返回的地址。该地址是相对于当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,CPU不进行自动地址转换)。应用程序员仅需和逻辑地址打交道,而分段和分页机制对一般程序员来说是完全透明的,仅由系统编程人员涉及。应用程序员虽然自己能直接操作内存,那也只能在操作系统给你分配的内存段操作。一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。

线性地址 :是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。

物理地址(Physical Address) 是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了,比如在实模式下。

2.2 虚拟地址,线性地址,物理地址关系

对于保护模式下地址之间的转换,对程序员来说是透明的。那么物理内存通过内存管理机制是如何将虚拟地址转换为物理地址的呢?当程序中的指令访问某一个逻辑地址时,CPU首先会根据段寄存器的内容将虚拟地址转化为线性地址。如果CPU发现包含该线性地址的内存页不在物理内存中就会产生缺页异常,该异常的处理程序通过是操作系统的内存管理器例程。内存管理器得到异常报告后会根据异常的状态信息。特别是CR2寄存器中包含的线性地址,将需要的内存页加载到物理内存中。然后异常处理程序返回使处理器重新执行导致页错误异常的指令,这时所需要的内存页已经在物理内存中,所以便不会再导致页错误异常。

2.3 段式机制及实例分析

前面说到在线性地址转换为物理地址之前,要先由逻辑地址转换为线性地址。系统采用段式管理机制来实现逻辑地址到线性地址的转换。保护模式下,通过”段选择符+段内偏移”寻址最终的线性地址。

CPU的段机制提供一种手段可以将系统的内存空间划分为一个个较小的受保护的区域,每个区域为一个段。相对32位系统,也就是把4G的逻辑地址空间换分成不同的段。每个段都有自己的起始地址(基地址),边界和访问权限等属性。实现段机制的一个重要数据结构就是段描述符。

下面是个程序实例中显示除了各个段的值:

图中给出了代码段CS,堆栈段SS,数据段DS等段寄存器的值;从得到的值可知,SS=DS=ES是相等的,至于为什么有些段的值相等,后面会说到。 以实例中给出的地址0x83e84110 为例,哪里是段描述符,哪里是段内偏移, 又是如何将该逻辑地址转换为线性地址的呢?相信很多人都迫不及待的想知道整个转换过程,接下来就要看看逻辑地址到线性地址详细转换过程。

上面说到段式管理模式下有段选择符+段内偏移寻址定位线性地址,其实际转换过程如下图所示

从图中可知,逻辑地址到线性地址的转换,先是通过段选择符从描述符表中找到段描述符,把段描述符和偏移地址相加得到线性地址。也就是说要想得到段描述符需要三个条件:

  1. 得到段选择符。
  2. 得到段描述符表
  3. 从段描述符表中找到段描述符的索引定位段描述符。

前面我们提到了段描述符 + 偏移地址,并没有提段选择符和段描述符表。所以我们要弄清楚这几个观念段选择符,段描述符表,段描述符,以及如何才能得到这几个描述符?

2.3.1 段描述符基础知识

从上图可知,通过段选择符要通过段描述符表找到段描述符,那么段描述符表是什么,又是怎么得到段描述符表呢?

在保护模式下,每个内存段就是一个段描述符。其结构如下图所示:

图中看出,一个段描述符是一个8字节长的数据结构,用来描述一个段的位置、大小、访问控制和状态等信息。段描述符最基本内容是段基址和边界。段基址以4字节表示(图中可看出3,4,5,8字节)。4字节刚好表示4G线性地址的任意地址(0×00000000-0xffffffff)。段边界20位表示(1,2字节及7字节的低四位)。

2.3.2 段描述符表实例解析

在现在多任务系统中,通常会同时存在多个任务,每个任务会有多个段,每个段需要一个段描述符,段描述符在上面一小节已经介绍,因此系统中会有很多段描述符。为了便于管理,需要将描述符保存于段描述符表中,也就是上图画出的段描述符表。IA-32处理器有3中描述符表:GDT,LDT和IDT。

GDT是全局描述符表。一个系统通常只有一个GDT表。GDT表也即是上图中的段描述符表,供系统中所以程序和任务使用。至于LDT和IDT今天不是重点。

那么如何找到GDT表的存放位置呢?系统中提供了GDTR寄存器用了表示GDT表的位置和边界,也就是系统是通过GDTR寄存器找到GDT表的;在32位模式下,其长度是48位,高32位是基地址,低16位是边界;在IA-32e模式下,长度是80位,高64位基地址,低16位边界。

位于GDT表中的第一个表项(0号)的描述符保留不用,成为空描述符。如何查看系统的GDT表位置呢?通过查看GDTR寄存器,如下图所示

从上图看出GDT表位置地址是0×8095000,gdtl值看出GDT边界1023,总长度1024字节。前面知道每一项段描述符占8字节。所以总共128个表项。图中第一表项是空描述符。

2.3.3 段选择符结构

前面我们介绍了段描述符表和段描述符的格式结构。那么如何通过段选择符找到段描述符呢,段选择符又是什么呢?

段选择符又叫段选择子,是用来在段描述符表中定位需要的段描述符。段选择子格式如下:

7

段选择子占有16位两个字节,其中高13位是段描述在段描述表中的索引。低3位是一些其他的属性,这里不多介绍。使用13位地址,意味着最多可以索引8k=8192个描述符。但是我们知道了上节GDT最多128个表项。

在保护模式下所有的段寄存器(CS,DS,ES,FS,GS)中存放的都是段选择子。

2.3.4 逻辑地址到线性地址转换实例解析

已经了解了逻辑地址到虚拟地址到线性地址的转换流程,那就看看在前面图中逻辑地址0x83e84110对应的线性地址是多少?

首先,地址0x83e84110对应的是代码段的一个逻辑地址,地址偏移已经知道,也就是段内偏移知道,通过寄存器EIP得到是0x83e34110。段选择符是CS寄存器CS=0008,其高13位对应的GDT表的索引是1,也就是第二项段描述符(第一项是空描述符)。GDT表的第二项为标红的8个字节

通过段描述的3,4,5,8个字节得到段基址。

如上图所示第二项段描述符的3,4,5,8字节对应的值为0×00000000。由此我们得到了段机制和段内偏移。最后的线性地址为段基址+段内偏移=0×0+0x83e34110=0x83e34110。

由此我们知道在32系统中逻辑地址就是线性地址。

其实通过观察其他的段选择子会发现,所有段选择子对应的基地址都是0×0,这是因为在32系统保护模式下,使用了平坦内存模型,所用的基地址和边界值都一样。既然基地址都是0,那么也就是线性地址就等于段内偏移=逻辑地址。

总之:

  1. 段描述符8字节
  2. GDTR是48位
  3. 段选择子2个字节。

2.4 页式机制及实例分析

前面介绍了由逻辑地址到线性地址的转换过程,那么接下来就要说说地址是如何将线性地址转为物理地址。需要先了解一些相关的数据结构。

前面说到如果CPU发现包含该线性地址的内存页不在物理内存中就会产生缺页异常,该异常的处理程序通过是操作系统的内存管理器例程。内存管理器得到异常报告后会根据异常的状态信息。特别是CR2寄存器中包含的线性地址,将需要的内存页加载到物理内存中。然后异常处理处理返回使处理器重新执行导致页错误异常的指令,这时所需要的内存页已经在物理内存中,所以便不会再导致也错误异常。

32位系统中通过页式管理机制实现线性地址到物理地址的转换,如下图:

2.4.1 PDE结构及如何查找内存页目录

从上图中我们知道通过寄存器CR3可以找到页目录表。那么CR3又是什么呢?在32系统中CR3存放的页目录的起始地址。CR3寄存器又称为页目录基址寄存器。32位系统中不同应用程序中4G线性地址对物理地址的映射不同,每个应用程序中CR3寄存器也不同。也就是说每个应用程序中页目录基址也是不同的。

从上图知道页目录表用来存放页目录表项(PDE),页目录占一个4kb内存页,每个PDE长度为4字节,所以页目录最多包含1KB。没启用PAE时,有两种PDE,这里我们只讨论使用常见的指向4KB页表的PDE。

页目录表项的高20位表示该PDE所指向的起始物理地址的高20位,该起始地址的低12位为0,也就是通过PDE高20位找到页表。由于页表低12位0,所以页表一定是4KB边界对齐。 也就是通过页目录表中的页目录表项来定位使用哪个页表(每一个应用程序有很多页表)。

以启动的calc程序为例,CR3寄存器是DirBase中的值,如下图

Calc.exe程序对应的CR3寄存器值为0x2960a000,下面是对应PDT结构

2.4.2页表结构解析

页表是用来存放页表表项(PTE)。每一个页表占4KB的内存页,每个PTE占4个字节。所以每个页表最多1024个PTE。其中高20位代表要使用的最终页面的起始物理地址的高20位。所以4KB的内存页也都是4KB边界对齐。

内存寻址原理,首发于博客 – 伯乐在线

空指针漏洞防护技术(提高篇)

在《空指针漏洞防护技术-初级篇》中我们介绍了空指针及空指针漏洞的概念,在这次高级篇中介绍空指针利用及相应的防护机制。

1 提高篇之:空指针的利用

前面主要介绍了空指针的一些概念和相关的知识,了解了什么是空指针,对于由野指针导致的空指针漏洞不是今天的重点。接下来主要就针对指向零页内存的空指针漏洞做详细的介绍。

此类漏洞利用主要集中在两种方式上:

  1. 利用NULL指针。
  2. 利用零页内存分配可用内存空间

对于第一种情况可以利用NULL指针来绕过条件判断或是安全认证。比如X.0rg空指针引用拒绝访问漏洞(CVE-2008-0153 ),如下图对比修改补丁前后的对比:

从代码补丁可以看出该漏洞利用NULL指针改变程序流程来触发漏洞。

针对第二种情况,在某些情况下零页内存也是可以被使用,比如下面两种情况:

  1. 在windows16系统或是windows16虚拟系统中,零页内存是可以使用的;在windows 32位系统上运行DOS程序就会启动NTVDM进程,该进程就会使用到零页内存。
  2. 通过ZwAllocateVirtualMemory等系统调用在进程中分配零页内存(win7系统之前)。

接下来结合ZwAllocateVirtualMemory API函数的调用直观感受在win7与win8系统中零页内存分配的差异。

1.1 ZwAllocateVirtualMemory基本介绍

zwAllocateVirtualMemory函数在指定进程的虚拟空间中申请一块内存,那是不是只要在内存申请空间就会调用zwAllocateVirtualMemory函数呢?调用zwAllocateVirtualMemory需要根据实际的情况。

堆的分配、使用、回收都是通过微软的API来管理的,最常见的API是malloc和new。在底层调用是HeapAlloc,同时通过HeapCreate来创建堆。对zwAllocateVirtualMemory函数的调用情况是:

. HeapCreate->RtlCreateHeap->ZwAllocateVirualMemory,这里会直接申请一大块内存,至于申请多大的内存,由进程PEB结构中的字段决定,HeapSegmentReserve字段指出要申请多大的虚拟内存,HeapSegmentCommit指明要提交多大内存。

图中看出默认申请大小是0×100000,默认提交大小是0×2000。下图是利用Heapcreate函数调用zwAllocatevirtualMemory时的函数调用栈关系。

图中展示了函数的调用过程。

  1. HeapAlloc->RtlAllocateHeap-> ZwAllocateVirualMemory,这里的内存申请是由Heapcreate已经提交的内存中申请。堆管理器从这片内存中划分一块出来以满足申请的需要,仅当申请的内存不够的时候,才会再次调用ZwAllocateVirualMemory函数。 **也就是说,我们平时使用**** malloc ****或是**** new *\*在堆上申请一块小的内存是不会调用该函数的。** 这点大家要注意。下图中展示了利用HeapAlloc调用ZwAllocateVirualMemory的过程:

c. 直接利用zwAllocateVirtualMemory分配内存,对于zwAllocateVirtualMemory函数,微软没有给出公开的文档,但是可以通过相关资料或是逆向来了解该函数的使用方式。直接利用zwAllocateVirtualMemory函数分配内存首先加载函数所在模块,同时获取该函数的符号地址。由于该函数提供了比较全面的参数,利用此函数来分配内存空间更加灵活多变。

1.2 ZwAllocateVirtualMemory函数知识

zwAllocateVirtualMemory该函数在指定进程的虚拟空间中申请一块内存,该块内存默认将以64kb大小对齐。

NTSYSAPI NTSTATUS NTAPI **ZwAllocateVirtualMemory** (
IN HANDLE *ProcessHandle*,
IN OUT PVOID **BaseAddress*,
IN ULONG *ZeroBits*,
IN OUT PULONG *RegionSize*,
IN ULONG *AllocationType*,
IN ULONG *Protect*
);

两个主要参数:

返回值

如果内存空间申请成功会返回0,失败会返回各种NTSTATUS码。

从zwAllocateVirtualMemory说明来看,本想利用BaseAddress参数在零页内存中分配空间,但是当BaseAdress指定为0时,系统会寻找第一个未使用的内存块来分配,而不是在零页内存中分配。那么如何才能分配到零页内存呢?

1.3 零页内存分配之实例win7 vs win8

了解了zwallocatevirtualmemory的用法,就结合实例来看看如何利用该函数进行零页内存分配。前面介绍将BaseAdress设置为0时,并不能在零页内存中分配空间,就需要利用其它方式在零页内存分配空间。在AllocateType参数中有一个分配类型是MEM_TOP_DOWN,该类型表示内存分配从上向下分配内存。那么此时指定 BaseAddress为一个 低地址,例如 1,同时指定分配内存的大小 大于这个值 ,例如8192(一个内存页),这样分配成功后 地址范围就是 0xFFFFE001(-8191) 到 1把0地址包含在内了,此时再去尝试向 NULL指针执行的地址写数据,会发现程序不会异常了 。通过这种方式我们发现在0地址分配内存的同时,也会在高地址(内核空间)分配内存(当然此时使用高地址肯定会出错,因为这样在用户空间)。

下面就举个例子看如何在零页分配内存

了解windows编程的情况下,对上面的代码不难理解,获取模块句柄,获取zwAllocateVirtualMemory函数地址,并传递参数调用该函数,其中baseaddress的值为4,参数类型中包含了MEM_TOP_DOWN,即内存由上向下分配。所以如果成功的话,将把零页内存中的地址4-0都会分配出去;函数返回0表示内存分配成功。然后再给0地址赋值打印,对0地址重新赋值后再次打印,查看结果。

为了能对比win7与win8系统运行结果的差异,需要准备两个干净的系统win7和win8,这里采用的都是32位系统。

在win7系统中直接编译运行该程序,运行结果如下

从显示结果来看,利用zwAllocateVirtualMemory函数,确实在零内存页分配了4-0地址的空间,也就是说我们可以利用zwAllocateVirtualMemory在零页内存中成功分配空间。

同时在win8系统中同时编译该程序,如果F5调试运行结果如下图:

或是CTRL+F5非调试运行,其结果如下图:

在win8系统中在调用zwallocatevirtualmemory后,函数返回了非0,返回值为0Xc00000f0。最终没能在零页内存分配空间。也就是说在win8系统中对零页内存做了安全防护,导致在零页地址分配内存时失败。

接下来看看win8系统中到底对NULL Pointer也就是零页内存做了哪些防护。

2 提高篇:windows零页内存防护机制

win8系统对零页内存防护机制通过搜索引擎可以查到: **在**** WIN8 ****系统中利用了内核进程结构**** EPROCESS ****中的**** flags ****字段的**** Vdmallowed *\*标志来判断是否允许访问零页内存。** 但是知其然而不知其所以然不是我们追求的目标。我们要做的就是在不依靠其他文献的条件下通过逆向和动态调试技术来剖析win8对零页内存的防护机制。

2.1 搭建内核调试环境

探索Win8的零页内存保护机制在内核中的实现,首先需要搭建一个能调试内核的环境。利用上面给出的例子结合内核调试来分析安全机制。关于如何搭建内核环境在之前的文章中已经介绍过,这里再累赘一下。

5.1.1 虚拟机及调试环境

搭建内核调试环境,采用双机调试模式,在虚拟机中安装win8系统同时配置win8调试模式。

Vmware安装win8系统就不在详解。系统启动后:

管理员权限打开CMD

按照上面的步骤配置好后,关闭虚拟机,打开win8虚拟机配置选项,删除并行端口,添加串行端口,同时配置串行端口,如下图:

配置好vmware后启动虚拟机进入调试模式,如下图:

5.1.2 配置启动windbg

双机模式调试内核需要配置windbg工具,现在主机中安装windbg;创建一个windbg的快捷方式,并在属性->目标中填入如下内容:

"C:\program file\WinDbg(x64)\windbg.exe" -b -k com:pipe,port=\.\pipe\com_1,baud=115200,resets=0,pipe

Windbg路径根据安装路径不同而调整。

启动windbg,由于我的主机是win7系统如果直接双击windbg快捷方式启动

这是缺少必要的权限,所以在启动windbg快捷方式时,需要以管理员的权限启动,启动完成后就可以和虚拟机中的win8系统进行内核级调试,启动之后的结果如下图:

到此内核调试的基本环境创建好了,为了能够更加直观的查看windows系统函数及调用关系需要为windbg配置符号表,这样windbg可以识别出windows标准的导出符号,同时为了能够更方便的调试nullpointer,也需要加载该程序的符号表。

到此内核调试环境搭建完成。

2.2 用户态及内核态跨栈调试

该部分是动态调试的关键部分,也是注意事项最多的一节。

5.2.1 内核调试用户态程序

在上面配置好环境后,在windbg中运行G命令,启动调试。启动程序后,同时在windbg中按住CRTL+BREAK断到调试器中。此时win8系统处于中断状态,不会再运行任何指令。

调试器断下来

我们知道在win7与win8中调用zwallocatevirtualmemory时由于零页内存保护机制的原因,win8系统不能在零页内存分配空间。那么就从调试nullpointer入手,通过调用zwallocatevirtualmemory调试内核态程序来剖解安全机制。

但是现在windbg处于内核调试状态,查看所有启动的进程

需要切换到nullpointer进程中。加载了nullpointer符号表。查看main函数的反汇编如下图所示:

5.2.2 用户态进入内核态

跟进该函数,最后进入

进入了sysenter指令之前,对windows系统有所了解的话,就知道该函数利用该调用进入快速系统调用也就是说此时将由用户态进入内核态。

SYSENTER用来快速调用一个0层的系统过程。SYSENTER是SYSEXIT的同伴指令。该指令经过了优化,它可以使将由用户代码(运行在3层)向操作系统或执行程序(运行在0层)发起的系统调用发挥最大的性能。

在调用SYSENTER指令前,软件必须通过下面的MSR寄存器,指定0层的代码段和代码指针,0层的堆栈段和堆栈指针:

  1. IA32_SYSENTER_CS:一个32位值。低16位是0层的代码段的选择子。该值同时用来计算0层的堆栈的选择子。

2.IA32_SYSENTER_EIP:包含一个32位的0层的代码指针,指向第一条指令。

3.IA32_SYSENTER_ESP:包含一个32位的0层的堆栈指针。

MSR寄存器可以通过指令RDMSR/WRMSR来进行读写。寄存器地址如下表。这些地址值在以后的intel 64和IA32处理器中是固定不变的。

为了能保证我们调试的**** NT!NtAllocatevirtualMemory ****函数刚好是**** nullpointer ****调用的,需要给**** NT!NtAllocatevirtualMemory ****函数下断点,意思是只在指定进程调用该函数时才断下来。** 待函数断下来后,同时查看函数调用栈如下图 **:

从图中调用栈可知,此时断下来的NT!NtAllocatevirtualMemory刚好是nullpointer引用的内核函数。此时已经进入内核调试状态。

5.3 逆向分析nt!NtAllocateVirtualMemory

进入win8内核调试,就要结合静态分析和动态调试来挖掘有用的信息。首先找到win8内核文件。 **需要注意的是此时分析的是虚拟机中**** win8 ****系统的内核文件,不是**** win7 *\*主机的内核文件。**

5.3.1 NtAllocatevirtualMemory参数确认

在前面的调试中,windbg断在了NT! NtAllocatevirtualMemory函数的入口处。对比NtAllocatevirtualMemory函数在windbg和IDA反编译的结果:

可知,在运行完指令call __SEH_prolog4_GS,后EBP+8为第一个参数,EBP+C为第二个参数,那就看看在调用call __SEH_prolog4_GS后EBP的值,如下图:

第一个参数是进程句柄,本进程句柄刚好是-1,第二个参数基地址指针,之前我们在程序中基地址是4,也就是0×00000004,查看基地址指针的值:

刚好是0×00000004,用户态传入的第三个参数是0,这里的第三个参数也刚好是0。其他参数不在一一列举;也就是是说内核函数NtAllocatevirtualMemory与用户态函数zwallocatevirtualmemory参数是一致的。

5.3.2 查找NtAllocatevirtualMemory 零页内存安全机制

到了最重要的时刻,接下来动态调试NtAllocatevirtualMemory, 一路单步执行,看到eax=0xc00000f0时停下:

从图中可知并EAX来自于ESI的赋值。那就继续往上看,看ESI的值是从哪里来的

从图中看出,在执行test[edi+0C4],1000000h指令后,如果相等就执行了mov esi,0xc00000f0。

5.3.3 确认NtAllocatevirtualMemory零页内存安全机制

前面已经找到了返回0xc00000f0的地方,接下来就要看看条件判断的地方, EDI=0x84b2ac80是EPROCESS进程的地址,查看EPROCESS结果可知:

在结构eprocess偏移0xc4的位置刚好是标志Vdmallowed,该值是0,并且[edi=0xc4] 与上1000000后刚好是零。导致ESI=0xc00000f0,进而导致EAX=0xc00000f0。

**到此基本上已经确认了**** NtAllocateVirtualMemory ****中对**** NULLPage ****的安全机制,就是检查**** EPROCESS ****中的**** VdmAllowed *\*的标志位。**

确定条件判断确定位置

从判断语句来看V76应该是参数中的基地址,V14应该是EPROCESS的地址。看一下这两个值的来源

从上图可知v76就是baseaddress; V14追溯到keGetCurrentThread,在内核模式下FS却指向KPCR(Kernel’s Processor Control Region)结构。即FS段的起点与KPCR结构对齐。看一下KPCR结构偏移124的位置

图中可知偏移124的位置刚好的当前线程的结构地址。通过查看函数KeGetCurrentThread的实现也能证实这一点,如下图:

线程结构地址+128(0×80)的位置刚好是当前进程的内核地址,如下图所示:

之后NtAllocateVirtualMemory函数在将进程结构地址偏移0xC4值与0×1000000做比较判断做安全检查。

到目前为止,NtAllocateVirtualMemory函数对零页内存的保护机制剖析完成。总结一下:

  1. 判断基地址是否小于**** 0×1000000,
  2. 判断内核结构**** EPROCESS ****的**** Vdmallowed ****标志是否为**** 0

5.3.4 查找内核中其他对零页内存保护的函数

通过对NtAllocateVirtualMemory逆向分析和动态跟踪了解了win8中对零页内存的保护机制;那么除了NtAllocateVirtualMemory函数,在内核中是否还有其他的函数也对零页内存进行了安全检查呢?

通过在整个NT内核文件中查找零页内存保护机制,又发现了几个包含零页内存包含的函数,搜索结果如下图:

也就是说在内核文件中除了NTAllocateVirtualMemory外,还有四个函数对零页内存做检测:

  1. MiIsVaRangeAvailable:
  2. MiMapViewOfPhySicalSection
  3. MiMapLockedPagesInUserSpace
  4. MiCreatePebOrTeb

这四个函数不都是导出函数,也就是说在内核编程中有可能我们使用的一些导出函数中内部调用了这四个函数中的某一个,其内部已经做了零页内存检测。

6 总结

本文先是介绍了什么是空指针漏洞,之后对windows系统的零页内存保护机制做了剖析。

空指针漏洞:

对零页内存的安全防护:

  1. 检测分配页是否在零页内存。
  2. **检测**** EPROCESS ****结构中的**** VdmAllowed *\*标志。**

Win8**** 以后的零页内存防护也只是保证了不会在零页内存分配空间,缓解分配零页内存空间来利用漏洞;从上图可知,利用零页内存分配导致的空指针漏洞也只是众多空指针漏洞类型中的一种。通过零页内存保护机制并不能缓解所有的空指针漏洞。

空指针漏洞防护技术(提高篇),首发于博客 – 伯乐在线

空指针漏洞防护技术(初级篇)

安全历史上由于空指针所带来的漏洞及攻击数不胜数,但由于其对利用者的编程能力有要求,对分析及防护者来说有更高的要求,所以国内对空指针漏洞及相关技术的讨论不是很多。今天这篇《空指针漏洞防护技术》,由绿盟科技威胁响应安全专家坐堂讲解,大家可以从中了解空指针漏洞的基础知识,并结合Windows 8的内存防护机制实例,动手实践空指针漏洞的防护技术。

1 背景

指针对于绝大部分的编程人员来说都不陌生,说起C/C++中指针的使用既带来了编程方面的方便;同时对编程人员来说,也是对个人编程能力的一种考验,不正确的使用指针会直接导致程序崩溃,而如果是内核代码中对指针的错误使用,会导致系统崩溃,后果也是相当严重。

一般情况下我们使用指针时,错误用法集中在三个方面:

  1. 由指针指向的一块动态内存,在利用完后,没有释放内存,导致内存泄露
  2. 野指针(悬浮指针)的使用,在指针指向的内存空间使用完释放后,指针指向的内存空间已经归还给了操作系统,此时的指针成为野指针,在没有对野指针做处理的情况下,有可能对该指针再次利用导致指针引用错误而程序崩溃。
  3. Null Pointer空指针的引用,对于空指针的错误引用往往是由于在引用之前没有对空指针做判断,就直接使用空指针,还有可能把空指针作为一个对象来使用,间接使用对象中的属性或是方法,而引起程序崩溃,空指针的错误使用常见于系统、服务、软件漏洞方面。

对于第一和第二种情况,我们可以通过一些代码审计工具在发布之前就能确定导致内存泄露或是野指针存在的地方。比如常见的工具有fority,valgrind等及时发现指针错误引用导致的问题。

对于第三种情况,空指针(Null Pointer)引用导致的错误,依靠代码审计工具很难发现其中的错误,因为空指针的引用一般不会发生在出现空指针然后直接使用空指针情况。往往是由于代码逻辑比较复杂空指针引用的位置会比较远,不容易发现;并且在正常情况下不会触发,只有在特定输入条件下才会引发空指针引用。对于排查此类错误也就更加困难。

本文不会重点讨论内存泄露和野指针的内容,而是通过一些现有的漏洞和实例来分析一下Null Pointer 空指针。从NULL Pointer概念、本质结合静态逆向及内核动态调试技术来了解在win7 32位和win8 32位下系统对Null Pointer处理情况有什么不同;Win8 32位针对Null Pointer添加了哪些防护机制。

本文从浅入深,循序渐进的讲述了NULL Pointer,结合静态逆向分析和内核级动态调试技术深入剖析win8系统对零页内存的保护机制,其中还涉及到了一些内核调试的技巧,对于对NULL Pointer的概念、使用比较模糊的人员值得一读,对于从事安全研究和学习的人员也是巩固、加深、拓展的好素材。

2 空指针由Null Pointer引发的漏洞

首先来看看近年来由于空指针的错误使用导致的系统、服务漏洞:

  • Microsoft windows kernel ‘win32k.sys’本地权限提升漏洞(CVE-2015-1721)(MS15-061)

该漏洞影响了windows Server 2003 SP2和R2 SP2,Windows Vista SP2, Windows Server 2008 SP2 和R2 SP1,Windows7 SP1,Windows8 ,Windows8.1,Windows Server2012 Gold和R2,Windows RT Gold and 8.1 。利用内核驱动程序 win32k.sys漏洞可以提升权限或是引起拒绝访问服务,主要原因是空指针的错误引用导致。

  • PHP空指针引用限制绕过漏洞(CVE-2015-3411)

PHP存在安全漏洞,由于程序多个扩展中缺少路径或某些函数的路径参数的空字节检查,允许远程攻击者利用漏洞可绕过目标文件系统访问限制,访问任意文件。

  • 0rg空指针引用拒绝访问漏洞(CVE-2008-0153 )

X.Org是X.Org基金会运作的一个对X Window系统的官方参考实现,是开源的自由软件。libXfont是一个用于服务器和实用程序的X字体处理库。 X.Org libXfont 1.4.9之前版本和1.5.1之前1.5.x版本的bitmap/bdfread.c文件中的’bdfReadCharacters’函数存在安全漏洞,该漏洞源于程序未能正确处理不能读取的字符位图。远程攻击者可借助特制的BDF字体文件利用该漏洞造成拒绝服务(空指针逆向引用和崩溃),执行任意代码。

  • Paragma TelnetServer空指针引用拒绝服务漏洞(BID-27143)

Pragma TelnetServer是一款远程访问和控制Telnet服务器。Pragma TelnetServer处理协议数据时存在漏洞,远程攻击者可能利用此漏洞导致服务器不可用。TelnetServer服务器对每个入站连接启动一个telnetd.exe进程,该进程在处理TELOPT PRAGMA LOGON telnet选项(138号)期间存在空指针引用,导致进程终止。尽管终止单个进程不会影响其他进程,但终止某些进程会导致拒绝访问服务器。

  • OpenSSL SSLv2客户端空指针引用拒绝服务漏洞(CVE-2006-4343)

OpenSSL是一种开放源码的SSL实现,用来实现网络通信的高强度加密,现在被广泛地用于各种网络应用程序中。OpenSSL的协议实现在处理连接请求时存在问题,远程攻击者可能利用此漏洞导致服务器拒绝服务。SSLv2客户端的get_server_hello()函数没有正确地检查空指针。使用OpenSSL的受影响客户端如果创建了到恶意服务器的SSLv2连接,就会导致崩溃。

  • Linux Kernel空指针间接引用本地拒绝服务漏洞(CVE-2014-2678)

Linux kernel 3.14版本内,net/rds/iw.c中的函数rds_iw_laddr_check在实现上存在本地拒绝服务漏洞,本地用户通过盲系统调用没有RDS传输的系统上的RDS套接字,利用此漏洞可造成空指针间接引用和系统崩溃。

  • ISC BIND named拒绝服务漏洞(CVE-2015-5477)

ISC BIND 9.9.7-P2之前版本、9.10.2-P3之前版本,named存在安全漏洞,远程攻击者通过TKEY查询,利用此漏洞可造成拒绝服务(REQUIRE断言失败及程序退出,指针未初始化)。

此类漏洞还有很多,从给出的几个漏洞来看,空指针漏洞主要是以拒绝服务访问漏洞为主,空指针错误引用自然会到底程序出现错误,严重者会崩溃,从而引起拒绝服务访问。

3 基础篇:空指针验证方式

由空指针的错误引用导致的漏洞,其原理本身很简单:错误引用空指针,导致非法访问内存地址。

那到底什么是空指针漏洞呢?在计算机编程过程中使用指针时有两个重要的概念:空指针和野指针。那么在前面我们提到的空指针漏洞是不是就是我们在编程时所说的空指针呢?要弄清这个问题,首先需要知道编程领域中空指针和野指针分别是什么,还要弄清楚什么是空指针漏洞。

3.1 概念性验证

假如 char p,那么p是一个指针变量,该变量还没有指向任何内存空间,如果p = 0; p = 0L; p = ”; p = 3 – 3; p = 0 * 5; 中的任何一种赋值操作之后(对于 C 来说还可以是 p = (void)0;), p 都成为一个空指针,由系统保证空指针不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针。(比如这里的(void)0就是一个空指针。当然除了上面的各种赋值方式之外,还可以用 p = NULL; 来使 p 成为一个空指针。因为在很多系统中#define NULL (void)0。

3.1.1 内存管理之空指针

空指针指向了内存的什么地方呢?标准中并没有对空指针指向内存中的什么地方这一个问题作出规定,也就是说用哪个具体的地址值(0×0 地址还是某一特定地址)表示空指针取决于系统的实现。我们常见的 空指针一般指向 0 地址,即空指针的内部用全 0来表示 (zero null pointer,零空指针)。

对于NULL能表示空指针,是否还有其他值来表示空指针呢?在windows核心编程第五版的windows内存结构一章中,表13-1有提到NULL指针分配的分区,其范围是从0×00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。

从图中看出,对NULL指针分配的区域有0×10000之多,为什么分配如此大的空间?在定义NULL的时候,只使用了 0×00000000这么一个值,而在表13-1有提到NULL指针分配的分区包含了0×00000000-0x0000FFFF,是不是有点浪费空间了;这是和操作系统地址空间的分配粒度相关的,windows X86的默认分配粒度是64KB,为了达到对齐,空间地址需要从0×00010000开始分配,故空指针的区间范围有那么大。

3.1.2知识拓展之野指针

“野指针”又叫”悬浮指针”不是NULL指针,是指向”垃圾”内存的指针。

“野指针”的成因主要有三种:

1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如: char *p = NULL; char *str = (char *) malloc(100);

2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。free以后其地址仍然不变(非NULL),只是该地址对应的内存变成垃圾内存,p成了”野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。如果程序比较长,我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。

用free或delete释放了内存之后,就应立即将指针设置为NULL,防止产生”野指针”。内存被释放了,并不表示指针会消亡或者成了NULL指针。

3)指针操作超越了变量的作用范围。例如不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放,这种情况让人防不胜防,示例程序如下:

另外需要注意的是如果程序定义了一个指针,就必须要立即让它指向一个我们设定的空间或者把它设为NULL,如果没有这么做,那么这个指针里的内容是不可预知的,即不知道它指向内存中的哪个空间(即野指针),它有可能指向的是一个空白的内存区域,可能指向的是已经受保护的区域,甚至可能指向系统的关键内存,如果是那样就糟了,也许我们后面不小心对指针进行操作就有可能让系统出现紊乱,死机了。所以必须设定一个空间让指针指向它,或者把指针设为NULL。

3.2源码级验证

接下来就结合两个小的实验例子看看空指针,及使用空指针的后果。

3.2.1指针使用前期对比

本例是要验证指针变量在初始化前后的状况,下图是一段代码:

代码很简单只是验证指针变量在赋值与未赋值分别指向的空间。代码中要打印的值:

  1. 指针变量初始化之前的地址
  2. 指针变量初始化之前的值
  3. 指针变量初始化之后的地址
  4. 指针变量初始化之后的值

编译之后,运行三次程序看运行的结果:

从三次运行结果来看:

  1. 每次打印指针变量的地址都不同,因为每次运行程序,P指针变量的地址都在变动。
  2. 同一次打印中指针变量在初始化之前与初始化之后的地址没有变化。因为指针变量也是变量,变量在其生命周期中值可以变动,但是地址不会改变。
  3. 每次打印中指针变量在初始化之前的值发生了变化。指针变量的值本身也是指向一块内存地址,在初始化之前该变量不指向任何内存地址,所以该变量的值就是一个随机值(也就是指向随机内存地址)。
  4. 每次打印中指针变量在初始化之后的值没有发生变化,在指针变量初始化之后P=0,所以在内存初始化的指针变量指向了内存地址都为0×00000000的位置。在每次打印中指针变量在初始化之后的值就不会发生变化。

3.2.2指针使用后期对比

针对指针变量在使用完毕后,内存释放之后的情况对比,其源码如下:

代码没什么复杂逻辑,只是想验证一下在指针变量释放后, P=NULL之前的野指针与P=NULL之后的指针状况,打印的内容:

  1. 指针变量P开始的地址与值
  2. 指针变量P在分配内存空间之后的地址与值
  3. 指针变量P在释放内存空间之后的地址与值
  4. 指针变量P在P=NULL之后的地址与值

下图是在编译之后运行的结果:

a. 指针变量在声明之后一直到最后的程序结束,指针变量的地址一直没有变动为0x35f778。 b. 定义指针变量时其值为0×00000000 c. 在申请新的内存之后,指针变量的值为申请内存的地址0x6e7a78 d. 在内存释放后,P变成野指针,但是此时P的值仍然是申请的内存地址0x6e7a78,但是此时P指针已经不能再使用。 e. 在P=NULL,p重新指向了地址0×00000000处。

由此可见,我们在释放指针变量后,指针变量会变成野指针,如果此时引用该指针会出现非法访问,因此需要在释放指针变量后将指针变量指向空。

3.3可视化内存验证

为了能够更加直观的查看指针变量在内存中的变化,下面就结合动态调试的技术看看指针变量在整个使用过程中的变化情况。下面是一段程序代码:

在这里,我们使用OD动态调试器,来看看调用printf函数的情况:

a. 第一条打印语句

printf("p address :%p, value :%08x\n", &p, p);

在调用printf前其内存情况:

可知指针变量P的值为EAX=[EBP-4],P的地址ECX=EBP-4,此时的寄存器值:

调用printf之后的值刚好是这两个值

b.第二条打印语句printf(“p intialized address :%p, value :%08x\n”, &p, p);在调用printf前其内存情况:

可知指针变量P的值为EDX=[EBP-4],P的地址EAX=EBP-4,此时的寄存器值:

image013

调用printf之后的值刚好是这两个值:

c. 第三条打印语句printf(“p free address :%p, value :%08x\n”, &p, p);在调用printf前其内存情况:

可知指针变量P的值为EAX=[EBP-4],P的地址ECX=EBP-4,此时的寄存器值:

image016

调用printf之后的值刚好是这两个值:

d. 第四条打印语句printf(“p ultimate address :%p, value :%08x\n”, &p, p);在调用printf前其内存情况:

可知指针变量P的值为EDX=[EBP-4],P的地址EAX=EBP-4,此时的寄存器值:

调用printf之后的值刚好是这两个值:

3.4 概念总结之空指针漏洞

前面对什么是空指针,什么是野指针做了讲解和验证。

在编程领域的空指针是指向NULL的指针,也就是说指向零页内存的指针叫空指针。对于未初始化的指针,释放内存而未将指针置为NULL和指针指向超出范围的情况称为野指针。那么在第二节中列举的空指针漏洞及未列举的空指针漏洞是不是都是由于引用零页内存导致的呢(比如CVE-2014-2678)?其实不然,有些漏洞是由于引用未初始化的指针或是引用超出范围的指针所导致,而这类漏洞应该说是由于错误的引用了野指针。比如最新的BIND漏洞(CVE-2015-5477):

但是到目前为止还没用听说过哪个漏洞命名为野指针漏洞,而更多的是空指针漏洞。也就是说在计算机安全领域中由空指针或是野指针导致的漏洞统一叫做空指针漏洞。

…未完待续 在下一次《空指针漏洞防护技术-高级篇》中,我们将介绍空指针利用及Windows 8中相应的防护机制。

空指针漏洞防护技术(初级篇),首发于博客 – 伯乐在线

一个缓冲区溢出漏洞的简易教程

这篇文章类似于“傻瓜系列之利用缓冲区溢出”。在这类漏洞中,我们的做法是利用网络,程序控制器,输入等等,发送超大的数据缓冲区给程序,覆盖程序内存的重要部分。在这些缓冲区覆盖程序内存之后,我们可以重定向程序的执行流并运行注入代码。

首先,我们需要做的是查明程序的哪一部分可以用来重写内存。处理这个任务的过程叫作“fuzzing”。我们可以为Metasploit框架中的各种协议找到若干个fuzzer(执行fuzzing任务的工具)。

接下来这个例子中,我们用metasploit对一个ftp服务器进行fuzz:

Fuzzer运行几分钟后,程序就崩溃了,见下图:

在Metasploit窗口中,我们可以看到崩溃缓冲区的长度:

在分析所有输出内容之后,我们可以得出:在ftp服务器通过用户命令发送了一个大于250的缓冲区后,程序崩溃了。

我们可以使用python来重现崩溃:

现在,我们重新实施这次攻击,但是首先要将FTP SERVER进程附加到一个调试器上,在这里我们用的调试器是OLLYDBG。

在实施攻击之后,我们可以很直观地看到ESP,EDI和EIP寄存器被覆盖。

稍微研究一下,大家可以发现:EIP控制程序的执行流,如果可以重写EIP,那么就可以手动重定向程序的执行流。EIP指向下一个待执行地址。

在这里,我们需要知道要重写的EIP缓冲区长度。我们可以在metasplpit中用pattern_create创建一种模式,并且作为一个缓冲区使用,来获取重写EIP的4个字节的位置。

把这些命令添加到我们的利用代码中,并再次运行:

现在,我们可以看到程序内存中的模式。

现在需要使用pattern_offset(偏移量模式)来找到那4个字节的准确位置(只要把4个字节作为一个脚本参数粘贴到EIP里面)。

由于在EIP之后ESP就被重写,我们可以写出这样一段利用代码如下:

并且,如果重新加载,在OLLY里面,可以看到它运行得很好。

在EIP之后,是这样改写ESP的:

那么在EIP里面我们需要做什么呢?将我们的恶意代码放到重写EIP的代码后面,然后需要做的只是简单的JMP ESP。

记住,EIP将包含下一条待执行指令的地址,所以此时需要做的是找到包含JMP ESP的地址。我们可以在OLLY(在E标签页)中进行查找。

一个简单的命令检索将会返回给我们一个地址。

现在,我们拷贝这个地址:

最后,我们需要做的是,在实行攻击之后,加入并执行我们的shell代码。我们可以用metasploit生成这些shellcode。

现在我们的利用代码如下。注意一下案例中CPU的ENDIAN,在EIP寄存器中我们会用到小端格式。

现在,如果我们再次实行攻击,将会运行我们的shellcode。

好的,现在我们可以生成另外的shellcode来执行不同的任务。

我们可以生成一段反向连接的shell代码,来访问我们的受害主机。

把这行代码我们的利用代码中,

最后运行利用代码:

我们的利用代码编写完成

提示:注意一些特殊字符。如果在缓冲区中间,利用代码被截断,可能是由于一些特殊字符导致的。特殊字符诸如“xa0″、 “|x00″等,会截断shellcode,你必须通过测试找到这些字符,并且在shellcode中避免用到,可别说我没提醒过你!

一个缓冲区溢出漏洞的简易教程,首发于博客 – 伯乐在线