timg.jpg

还记得2017年Instagram 600万名人账号信息泄漏的事件吗?

2017年8月底,小天后赛琳娜(Selena Gomez)的IG帐号(Instagram)被黑客入侵,导致其前男友贾斯汀比伯的裸照被曝光。当大家以为事件已经结束的时候,紧接着Instagram被证实出现了严重漏洞问题,导致600万账号信息泄露,其中就有很多加V认证过的名人用户账号。

超4900万条Instagram名人账户数据再一次被泄漏!

根据最新的报道,Instagram位于AWS存储桶上的一个大型数据库由于保护不当,任何没有访问权限的人都可以访问它。目前,由该漏洞造成的损失和危害还在评估当中。另外,该数据库漏洞是由安全研究人员Anurag Sen首先发现的。

该大型数据库拥有4900多万条Instagram账户的联系信息,其中大部分都是网红和网络大V的个人信息,如个人经历、资料图片、粉丝数量、所在城市的位置、私人联系方式,电子邮件地址以及电话号码等信息。另外,数据库中还包含了计算每个账户价值的具体字段,如果你感兴趣,还可以算一下他们当前的商业价值。即使他们帐户中的相关个人信息之前被删除了,利用该漏洞还是能够将其恢复。

据TechCrunch网站(Anurag Sen首先将该消息透露给了TechCrunch)调查,该数据库属于社交媒体营销公司Chtrbox,其总部位于印度,主要业务就是让网红和网络大V发布广告。为了验证漏洞的可靠性,TechCrunch网站拿出了两个账号向Chtrbox验证其真伪,果不其然,这两个账号确实属于Chtrbox公司的。

当TechCrunch向Chtrbox公司表示该数据库已经泄漏时,他们却说事情还有待核实,目前还不清楚发生了什么。

目前Facebook已经宣布正在调查这一事件:

我们正在调查这个漏洞,以了解包含电子邮件地址及电话号码的数据是否来自Instagram。我们也正在和Chtrbox公司联系,以查明具体过程。

为何名人的信息频繁在Instagram上大规模泄露?

说到底,这和Instagram的商业模式有关,2018年年底,Instagram还准备推出只为名人提供服务的功能。Instagram计划通过Creator帐户的形式来为用户提供一个能深刻洞察他们粉丝数据的工具。不过这些账户仅提供给明星或者其他有影响力的用户使用。这些创建者帐户的功能类似于业务的重点简介,明星或者其他有影响力的用户可以通过这个功能看到他们的粉丝每天或者每周的数据变化,来让他们了解到底是什么导致他们粉丝数目增长或者减少。

此外,Instagram用户还可以启用消息的直接传递工具来过滤来自品牌合作伙伴或者朋友的笔记,同时,普通用户也可以通过标签来找到他们想要找的明星或者其他有影响力的人。

说到底,用户的信息在这里都是一种商品而已。

用户的隐私对Facebook来说只是一种商品而已

Instagram数据泄露进一步证明Facebook是多么不关心用户的隐私。Facebook泄露其用户私人信息已经是家常便饭了,特别是去年闹得沸沸扬扬的泄漏事件。

2018年,Facebook被曝多达8700万用户的数据落入政治数据公司Cambridge Analytica的手中,该公司为特朗普2016年的总统竞选工作。

这桩丑闻点燃了全球Facebook用户对于数据隐私储存的担忧。有知情人士透露,Facebook曾对一些公司开出白名单,与他们签署了定制的数据分享协议,以让他们获得有关用户好友的额外信息。这些信息包括手机号码和被称作是好友关联(friend link)的指标,后者被用于衡量用户和其Facebook好友之间的亲密程度。而目前,已经披露的数据显示,至少有60家设备制造商与Facebook达成了数据分享协议。

此次事件的爆发,或将促使Instagram和WhatsApp业务从Facebook中剥离

由于Facebook的用户隐私的泄露,已经触发公众对隐私保护的极限。因此,5月12日下午,Facebook的联合创始人发文呼吁监管机构拆分Facebook,将Instagram和WhatsApp从Facebook中剥离,以防止用户数据集中在一家巨头手里。扎克伯格对此回应称,该提议对解决问题毫无益处。不过此次事件的爆发,直接打脸扎克伯格,或将促使Instagram和WhatsApp业务从Facebook中剥离。

另外,经过长达一年的调查,去年Facebook侵犯隐私案又有了新进展。近日,外媒报道称,Facebook即将与美国政府就隐私监管达成和解协议,这份协议将使Facebook在未来20年内受到严格的条款监督。CEO马克•扎克伯格本人可能将扮演执行公司隐私政策的“指定合规官”角色。

与之伴随的是一张或将高达50亿美元的天价罚款单,配合监管和调查的同时,Facebook也在进行新的业务调整,不再宣扬“开放式社交平台”和“连接世界的愿景”,而是“做一个更简单的平台”,专注私密通讯和小群体聊天。

Stack Overflow是一个与程序相关的IT技术问答网站。用户可以在网站免费提交问题,浏览问题,索引相关内容,在创建主页的时候使用简单的HTML。在问题页面,不会弹出任何广告,销售信息,JavaScript窗口等。

不过,上周有报道指出有黑客访问了Stack Overflow所属公司(Stack Overflow隶属Stack Exchange Network)的内部网络。Stack Overflow在公告中写道:

我们已经确认黑客于5月11日获得了一定程度的生产访问,在发现入侵之后,我们就立即调查了黑客访问的范围并解决了所有已知的漏洞。

直至2018年9月,Stack Overflow有超过9,400,000名注册用户和超过16,000,000个问题,其中最常见的主题有JavaScript、Java、C#、PHP、Android、Python、jQuery和HTML。

根据最新的追踪,本次攻击实际早在5月5日就发生,而Stack Overflow直到5月11日才发现,这意味着黑客潜入系统并自由自在进行了至少6天的攻击。根据目前的分析,攻击者利用的是部署在 stackoverflow.com 开发层的一个构建漏洞,该漏洞允许攻击者直接登录到开发层,并直接升级他们的访问权限。

目前官方的公布结果是

攻击者对我们的系统进行了更改,以便为自己提供访问特权。不过这一变化很快就被发现,目前我们已经撤销了他们在整个网络的访问权限,开始调查入侵原因,并采取修复措施。

调查显示整体用户数据库没有受到损害,但部分用户私人信息被窃。之所以被窃,是因为攻击者提出的特权Web请求已经被部分确定,这些请求返回了约250位Stack Exchange用户的IP地址、名称或电子邮件。

针对此次安全事件,Stack Overflow 团队目前采取了以下措施:

1.终止对系统的未授权访问;

2.对所有日志和数据库进行广泛而详细的审查;

3.主动发表公开声明并联系那些受到攻击的用户;

4.及时进行补救(修复导致未经授权访问和升级的漏洞),并提高自身网站的安全几笔,比如重置公司密码等;

5.调查其他潜在问题;

目前此次事件调查还在继续中,我们会持续关注。

Nuki智能锁,可以将手机变成智能钥匙。Nuki只要接入网络之后,在任何时间、任何地点都可以通过远程控制的方式打开大门。比如可以在家里没人的情况下,为快递员开门,让它将包裹放到家里之后再离开,还可以随时了解家人孩子回家的动态。同时还可以设置临时开锁密码,为客人提供临时访问权限。

由于Nuki智能锁是把设备中Nuki Lock通过桥接装置(Bridge)集成到智能模块中的,所以这款智能门锁主要是通过蓝牙和无接WIF进行远程操作的。为此在本文中,我们将把Nuki Lock (version 1) 和Bridge (version 4)分开进行介绍,深入研究这两个设备内部的功能。

Nuki Lock

在2018年11月,Nuki发布了他们的智能锁2.0版本。这款智能门锁支持蓝牙Bluetooth 5 LE,ZigBee协议(一种低速短距离传输的无线网上协议,底层是采用IEEE 802.15.4标准规范的媒体访问层与物理层)和智能门锁传感器。 智能锁2.0版本保留了之前版本的关键属性。随着新版本的发布,原来的锁已经可以被公开破解了,因为它已没有安全保密性可言了。

1.jpg

通过上图,我们可以很清晰地看到,要把那么多不同的模块集成到Nuki Lock是件多么难办的事情。我们把后面的螺母螺钉拆除,发现里面的按钮,PCB和数据总线形成了一个单独的部件模块,首先必须将这些小部件断开,在钥匙槽的底部发现有两个螺钉将这些小部件固定在了一起,于是拆下小螺钉,拆开后,在Nuki门锁中发现如下部件。

1.塑料外壳,盖子和电池座;

2.钥匙槽是带齿轮齿的单独圆形塑料件,带有LED和四个按钮的PCB连接到它上面。这四个按钮由塑料件组合成一个,由两个软弹簧固定。因此,需要牢固地按下锁定按钮,因为它仅在PCB上的四个按钮被激活时才会记录。该PCB具有到主板的小数据总线,必须断开。

3.在机箱的主体中,在拆下几个螺钉后可拆开,我们发现:

3.1主电机,沿橡胶圈缠绕;

3.2一种齿轮机构,用于将电机的快速运动转换为缓慢但强大的动力,从而转动钥匙旋钮。两个齿轮通过两个小金属杆固定到位,,这两个金属棒与塑料外壳的整体相吻合;

3.3伺服电动机连接到杠杆和齿轮,齿轮根据需要将齿轮啮合到旋转键钮。通常情况下,机构未啮合,旋钮可以用手自由转动。在弹簧的帮助下,齿轮脱离到其静止位置;

3.4主板PCB和它下面的电源转换器;

3.5用于固定和连接电池的部件,以及安装在门上的旋钮;

2.jpg

大多数齿轮在第一次拆卸锁时就会拆开,需要反复试验才能把它们重新组装起来,最棘手的部分是连接到控制旋钮的伺服器上的杠杆系统。

我们可以从主PCB上观察到智能锁的核心是Cypress CY8C4248LQI-BL483,这是一个蓝牙4 BLE模块。我们还观察到SSD内存芯片(2MB Adesto AT25SD161)和一个5引脚端口,假设它们连接到芯片的串行引脚并用于调试。在测试中,我们观察到它们没有在我们的装置中使用,即使一个引脚对应3.3v输出而另一个接地,也没有被使用的迹象。

3.jpg

在重新组装期间,我们不得不重新焊接其中一根电机线,因为它们很薄并且容易断裂。在所有部件中,电机的寿命可能是最短的,但是更换应该很简单。

网桥(Bridge)装置

Nuki Internet Bridge通过WIFI为Smart Lock提供连接和远程访问,通过连接到本地网络,它为应用程序提供了一个HTTP API来集成(比蓝牙API更简单),并且还可以通过Nuki应用程序或Nuki Web界面控制来自任何地方的锁。

该设备是一个简单得多的硬件,下图就是一个连接到功率转换器的PCB:

4.jpg

该WIFI由Espressif ESP-WROOM 02提供,这是流行的ESP8266的一个版本,带有集成的2MB闪存。

蓝牙部分由与锁相同的Cypress蓝牙芯片提供,以下是一个附带的2MB的Spansion FL116KIF4内存模块(可选链接)。

5.jpg

希望通过这篇文章能够使大家了解Nuki智能锁的内部构造,以及它是否安全可靠。

本文介绍了苹果将在10.14.5上新引入的App Notarization机制,届时,苹果将要求开发人员上传应用程序之前,将它们提交给苹果,以扫描恶意内容,并查找可能存在的代码签名问题,没有经过苹果检测的应用程序以后可能将不被允许运行。

随着macOS 10.14.5的正式发布,苹果首次要求所有开发人员创建一个属于开发者自己的ID证书,以Notarization机制他们的应用程序,并且所有新的和更新的内核扩展都要经过Notarization机制。

什么是Notarization机制?

代码签名机制是一种对抗恶意软件的重要武器,它能够帮助用户识别已签名App的真实身份,并验证目标应用是否被非法篡改过。代码签名机制基于密码学方法来判断代码的真实性,并防止攻击者将恶意代码伪装成合法代码。

在Notarization机制采用之前,macOS使用Gatekeeper来阻止从互联网上下载的应用程序启动,Gatekeeper是山狮中引入的一项新安全技术,它可保证用户安装来自Mac App Store或者拥有开发者签名的应用。具体来说,它可以作为Mac App Store的应用鉴别工具,也可识别来自Mac App Store以外应用的开发者身份, 从而防止一些恶意软件的进入。使用Gatekeeper时, macOS会记录那些有问题的已知应用程序列表,并防止其被执行。但是,在应用程序通过Gatekeeper并得到用户批准后,Gatekeeper就会失效,很难检测到现有的二进制文件是否被感染,并且没有好的方法可以撤销应用程序的批准。因为一旦开发人员上传的证书被撤销后,Mac App Store就会撤销所有开发人员上传的应用程序。为了出现这种歌情况,苹果引入了Notarization机制,来强化对开发者及其上传应用的管理。

简而言之,Notarization机制是建立在当前Gatekeeper安全检查之上的一个新验证层,是Gatekeeper技术的补充。

在Mojave(10.14)之前,苹果只需要一个注册的Apple ID代码签名,就会完全信任上传的应用程序。而实行Notarization机制后,苹果现在还会检查提交的代码中是否存在“已知恶意应用程序”和“可能阻止你的应用程序正确安装的常见代码签名问题。” 通过这些(目前可选)额外检查的应用程序,就被认为是“经过了Notarization机制”,即它们是安全的。

这可能是苹果为更好地防范今年早些时候发现的安全问题而做出的努力,今年早些时候,macOS发现,不正确签名的程序仍将受到macOS的信任。

2018年6月,来自安全公司Okta Rex的研究专家Josh Pitts 在macOS的代码签名机制中发现了一个可以利用的安全漏洞。这个漏洞允许攻击者将恶意的不受信任的代码伪装成受信任的合法代码,并绕过多款macOS安全产品的检测,其中包括Little Snitch、F-Secure xFence、VirusTotal、Google Santa和Facebook OSQuery。实际上,代码签名攻击并不是一种新型的攻击技术,根据Pitts发布的漏洞披露信息,这种技术跟之前的代码攻击方式有所不同,此次的漏洞并不需要管理员访问权、JITíing代码和内存崩溃便能够绕过代码签名检测。攻击者只需要利用一种专门制作的Fat/Universal文件就能够让macOS的代码签名功能返回有效的值,自2005年之后(OS X Leopard)发布的苹果操作系统都将受到该漏洞的影响。这意味着,该漏洞已经存在了13年之久。

现在,通过将代码签名提交给苹果的Notarization机制,使用户对开发者的应用程序更有安全归属感。该服务会自动扫描含有开发人员 ID签名的应用程序并执行安全检查。当程序被检查完并上传到APP商店时,苹果会在你的开发的程序上附上一个标识,让Gatekeeper知道它已经经过Notarization机制验证。

总的来说,Notarization机制是个高级别的安全机制:

1.Notarization机制是向苹果提交应用程序或内核扩展(kext)以获得批准的过程;

2.Notarization机制过程要求应用程序开发人员将其应用程序或内核扩展提交给Apple进行审核。在审核之后,苹果会根据情况判定是否给予批准,并在其服务器中记录app / kext。这是应用开发者在上传应用之前要做的事情,而不是Apple管理员要做的事情。

Stapling是Notarization机制过程的最后一步,它允许Notarization机制的应用程序在macOS上运行而无需检查Apple服务器。

一旦应用程序经过Notarization机制验证,Apple就会向开发人员提供可以“Stapling”到Notarization机制对象的标识。如果app或kext没有经过Stapling,当app / kext加载时,macOS将与Apple服务器连接以查看是否可以运行。

在未来的macOS版本中,默认情况下苹果会将所有应用程序进行Notarization机制验证。虽然目前Notarization机制还正在推广阶段,但苹果希望开发人员现在就开始对其上传的应用程序进行Notarization机制验证。

从macOS 10.14.5开始,苹果就必须对内核扩展进行Notarization机制验证,届时未经Notarization机制验证的程序将在加载时失败。

注意:在4月7日之前已经被签名的内核扩展将会被广泛使用并将继续运行(截至10.14.5 beta 4)。这意味着:

1.在4月7日以前,尚未更新的应用程序将按原来的方式在macOS 10.14.5 beta 4中运行。

2.如果具有内核扩展名(kext)的应用程序自4月7日以来已更新且未经Notarization机制,则无法成功安装。

经过Notarization机制验证的kext具有相关的安全时间戳,从macOS 10.14.5开始将需要该时间戳。在2019年4月7日之前签名的那些未经Notarization机制验证的kext将在macOS 10.14.5 beta 4中继续使用。kext文件是一个Mac OS X内核扩展,常见于Hackintosh(PC上的黑苹果)。它们通常用于设备驱动程序,运行于系统的核心基底。但是,在GAP环境中故意不加盖时间戳或签名的kext文件也可能不会运行。

译者注:GAP,源于英文的"air gap",gap技术是一种通过专用硬件使两个或者两个以上的网络在不连通的情况下,实现安全数据传输和资源共享的技术。gap中文名字叫做安全隔离网闸,它采用独特的硬件设计,能够显著地提高内部用户网络的安全强度。

总的来说,Notarization机制的特点如下:

1.Notarization机制是将其提交给苹果审核的过程,其目的就是要让苹果对商店里的应用程序进行安全控制。

2.Stapling是将Notarization机制附加到应用程序或kext上的过程,这样它就可以离线运行或在某种安全隔离的网络上运行。

3. Notarization机制只是针对开发人员的,目前只是在测试,等到 macOS 10.14.5,很可能会强制使用Notarization机制。

4.内核扩展Notarization机制是10.14.5的强制性要求,未经Notarization机制验证的内核扩展将在10.14.5加载时失败。

也有例外,如果kext是在2019年7月4日之前进行签名的,则kext在10.14.5 beta 4中加载时不需要进行Notarization机制验证。如果是在2019年7月4日之后进行签名的,就需要进行Notarization机制验证。

注意:此截止日期将在beta测试过程中随时进行调整。

· 10.14.5 beta 2的截止日期是2019年3月11日;

· 10.14.5 beta 3的截止日期是2019年4月7日;

Notarization验证机制对MacAdmin的影响

上述信息对于提高程序的安全性非常有用,但不幸的是,管理员可以做的事情并不多,甚至无法预防它们的攻击。

应用程序开发人员自己有责任对其应用程序和相应的kext进行适当的公证,如果做不到这一点,将会导致10.14.5中各种应用程序不受支持。从技术上讲,MacAdmin可以代表开发人员对应用程序进行Notarization机制验证,但经过测试,我们不推荐这么做。

MacAdmin最好的办法是评估他们所在的机群,在应用程序造成破坏之前对它们进行分类。具体操作过程如下:

1.列出MacAdmin要上传的所有应用程序及其更新方式(是否自动更新还是手动修补程序?)

2.如果MacAdmin管理的应用程序自4月7日以来没有更新,请注意下一次更新,并确保它经过了Notarization机制验证。

3.如果应用程序在4月7日之后有更新,请查看发行说明以查看应用程序及其内核扩展是否已经过Notarization机制验证。

4.检查Notarization机制内核扩展是否会更改机构ID和内核扩展白名单配置文件。 具体可参考MacAdmins #notarization

总的来说,MacAdmin的最大风险是10.14.5发布之前已经存在的应用程序。

作为MacAdmin,要做的就是在10.14.5中,将开发人员发布的那些经Notarization机制验证的程序部署到你的机群中!

另外,作为MacAdmin,自己的应用程序部署过程也值得注意!通过上述讲解,你已经知道经过Stapling的内容可能不是通过Stapling的内容递归的。这意味着,如果要解压缩并重新压缩.pkgs以进行上传,则应确保已经.pkg的内容已经经过Stapling。

一些故障排除技巧

虽然作为一个MacAdmin,如果你发现一个应用程序没有更新,你能做的事情并不多,但至少有了这些信息,当10.14.5发布时,你会知道为什么一个应用程序不能工作。

下面是验证给定kext的时间戳的简单方法,虽然这并不能直接告诉你kext是否经过了Notarization机制验证,但如果仔细观察还是能够找到线索的:

1.在kext上的stapler validate -v ,注意:使用该命令需要安装Xcode及其命令行工具;

2.检查secureTimestamp并查看它是否在截止之前;

3.如果secureTimestamp是在截止日期之前,那么将加载特定的kext;

4. 此后签署的任何带有较新的时间戳的kext,除非经过Notarization机制验证,否则不会加载;

要验证kext是否已签名,请使用以下命令:

kextutil -nt MyKext.kext

要检查应用是否经过Notarization机制验证,请使用以下命令:

spctl -v -a <app>

要获取经过Notarization机制验证的所有应用程序的列表,请在你的终端中运行以下命令(需要安装Xcode和命令行工具):

for i in /Applications/* ; do stapler validate "${i}" | grep -B 1 worked; done

Fleetsmith将如何处理Notarization验证机制所带来的影响?

Fleetsmith是一家提供Mac电脑群组管理软件服务的初创公司,Fleetsmith希望帮助中小企业提供和管理苹果设备,无论是电脑、手机、ipad还是苹果电视。通过手工管理这些设备是一个耗时的过程,因此较大的公司通过其他的商业服务或内部方案解决这个问题,但是Fleetsmith将这种高效的设备管理方式提供给了较小的公司,将其作为云服务提供。

Fleetsmith计划在几周内将Notarization机制纳入他们的代理构建过程,所以Fleetsmith代理肯定会与macOS 10.14.5发布的时间同步。

Fleetsmith目录中的应用程序如何管理?

Fleetsmith会继续自行监控这些版本,并将根据测试版本对每个版本进行完整性检查。如果有新的kext白名单机构ID或开发者ID出现,Fleetsmith会及时更新它们。

Fleetsmith计划在目录应用程序的信息框中,对没有经过Notarization验证机制的程序进行安全提示。

上一篇文章,我们对防火墙过滤规则和ICMP/ICMPv6数据包的传输过程做了充分的介绍,以剖析其中可能出现的攻击风险。本文我就详细解析恶意数据包如何被传送。

Nftables的实施和细节

Linux是在netfilter conntrack模块中实现的数据包的各种功能,Nftables在netfilter/nf_conntrack_core.c中以函数nf_conntrack_in开始启动,该函数处理在参数skb中发送的每个输入数据包。在nf_conntrack_handle_icmp中处理第4层协议和ICMP和ICMPv6的的提取。

unsigned int
nf_conntrack_in(struct sk_buff *skb, const struct nf_hook_state *state)
{
    // ..

    l4proto = __nf_ct_l4proto_find(protonum);

    if (protonum == IPPROTO_ICMP || protonum == IPPROTO_ICMPV6) {
        ret = nf_conntrack_handle_icmp(tmpl, skb, dataoff,
                           protonum, state);
        if (ret <= 0) {
            ret = -ret;
            goto out;
        }
        /* ICMP[v6] protocol trackers may assign one conntrack. */
        if (skb->_nfct)
            goto out;
    }
    // ...
}

然后nf_conntrack_handle_icmp根据ICMP的版本调用nf_conntrack_icmpv4_error()或nf_conntrack_icmpv6_error()。这些函数非常相似,所以先让我们理解一下ICMP。

如果类型为ICMP_DEST_UNREACH、ICMP_PARAMETERPROB、ICMP_REDIRECT、ICMP_SOURCE_QUENCH、icmp_time_overflow之一,则nf_conntrack_icmpv4_error会验证ICMP标头并调用icmp_error_message。

/* Small and modified version of icmp_rcv */
int nf_conntrack_icmpv4_error(struct nf_conn *tmpl,
                  struct sk_buff *skb, unsigned int dataoff,
                  const struct nf_hook_state *state)
{
    const struct icmphdr *icmph;
    struct icmphdr _ih;

    /* Not enough header? */
    icmph = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_ih), &_ih);
    if (icmph == NULL) {
        icmp_error_log(skb, state, "short packet");
        return -NF_ACCEPT;
    }

    // ...

    if (icmph->type > NR_ICMP_TYPES) {
        icmp_error_log(skb, state, "invalid icmp type");
        return -NF_ACCEPT;
    }

    /* Need to track icmp error message? */
    if (icmph->type != ICMP_DEST_UNREACH &&
        icmph->type != ICMP_SOURCE_QUENCH &&
        icmph->type != ICMP_TIME_EXCEEDED &&
        icmph->type != ICMP_PARAMETERPROB &&
        icmph->type != ICMP_REDIRECT)
        return NF_ACCEPT;

    return icmp_error_message(tmpl, skb, state);
}

然后icmp_error_message负责提取和识别匹配状态:

/* Returns conntrack if it dealt with ICMP, and filled in skb fields */
static int
icmp_error_message(struct nf_conn *tmpl, struct sk_buff *skb,
                   const struct nf_hook_state *state)
{
    // ...

    WARN_ON(skb_nfct(skb));
    zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);

    /* Are they talking about one of our connections? */
    if (!nf_ct_get_tuplepr(skb,
                   skb_network_offset(skb) + ip_hdrlen(skb)
                               + sizeof(struct icmphdr),
                   PF_INET, state->net, &origtuple)) {
        pr_debug("icmp_error_message: failed to get tuple\n");
        return -NF_ACCEPT;
    }

    /* rcu_read_lock()ed by nf_hook_thresh */
    innerproto = __nf_ct_l4proto_find(origtuple.dst.protonum);

    /* Ordinarily, we'd expect the inverted tupleproto, but it's
       been preserved inside the ICMP. */
    if (!nf_ct_invert_tuple(&innertuple, &origtuple, innerproto)) {
        pr_debug("icmp_error_message: no match\n");
        return -NF_ACCEPT;
    }

    ctinfo = IP_CT_RELATED;

    h = nf_conntrack_find_get(state->net, zone, &innertuple);
    if (!h) {
         pr_debug("icmp_error_message: no match\n");
        return -NF_ACCEPT;
    }

    if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY)
        ctinfo += IP_CT_IS_REPLY;

    /* Update skb to refer to this connection */
    nf_ct_set(skb, nf_ct_tuplehash_to_ctrack(h), ctinfo);
    return NF_ACCEPT;
}

1.首先,使用nf_ct_zone_tmpl计算数据包skb的网络区域。 nftables有网络连接conntrack区域的概念。这些区域允许虚拟化连接跟踪,以便在conntrack和NAT中处理具有相同身份的多个连接。除非有明确的规则要求,否则所有数据包都将进入0区域;

2. 然后使用nf_ct_get_tuplepr从ICMP层内的ip数据报中提取ip连接状态 origtuple;

3.nf_ct_invert_tuple执行状态的源或目标交换,因为它引用原始出站数据包,而防火墙却检查的是入站数据包;

4.nf_conntrack_find_get查找与提取的状态匹配的已知状态,此时我们看到外层IP层未被考虑用于查找状态;

5.如果找到状态,则nf_ct_set会标记具有相关状态(IP_CT_RELATED)的sbk数据包。

对于ICMPv6,类型小于128的反馈消息有类似的实现过程。

PacketFilter 的实现细节

在PacketFilter中,相关的概念实际上是隐含的,并且是在状态的概念下实现的。数据包过滤的总体设计思路是这样的:数据包可以与状态相关联吗?

如果是,则允许数据包通过;如果不是,则根据过滤规则测试数据包。如果匹配规则允许数据包通过,则可能会创建一个状态。

整个逻辑是在/sys/net/pf.c中的函数pf_test中实现的,下面这段代码显示了对ICMPv6的处理过程,为了方便讲解,部分代码已被删除。

pf_test(sa_family_t af, int fwdir, struct ifnet *ifp, struct mbuf **m0)
{
    // ...
    switch (pd.virtual_proto) {

    case IPPROTO_ICMP: {
        // look for a known state
        action = pf_test_state_icmp(&pd, &s, &reason); 
        s = pf_state_ref(s);

        if (action == PF_PASS || action == PF_AFRT) {
            // if a valid state is found the packet might go there
            // without being tested against the filtering rules
            r = s->rule.ptr;
            a = s->anchor.ptr;
            pd.pflog |= s->log;

        } else if (s == NULL) {
            // if no state is found the packet is tested
            action = pf_test_rule(&pd, &r, &s, &a, &ruleset, &reason);
            s = pf_state_ref(s);
        }
        break;
    }

    case IPPROTO_ICMPV6: {
        // look for a known state
        action = pf_test_state_icmp(&pd, &s, &reason);
        s = pf_state_ref(s);

        if (action == PF_PASS || action == PF_AFRT) {
            // if a valid state is found the packet might go there
            // without being tested against the filtering rules
            r = s->rule.ptr;
            a = s->anchor.ptr;
            pd.pflog |= s->log;
        } else if (s == NULL) {
            // if no state is found the packet is tested
            action = pf_test_rule(&pd, &r, &s, &a, &ruleset, &reason);
            s = pf_state_ref(s);
        }
        break;
    }

    // ...
}

pf_test_state_icmp()是尝试查找此数据包与已知连接之间关系的一个函数,它使用对pf_icmp_mapping()的调用来了解数据包是带内还是带外。在带外情况下,提取内部IP数据包及其第4层协议以找到状态。过程如下所示:

int pf_test_state_icmp(struct pf_pdesc *pd, struct pf_state **state, u_short *reason) {
    // ...

    if (pf_icmp_mapping(pd, icmptype, &icmp_dir, &virtual_id, &virtual_type) == 0) { // <-- 1
        /*
         * ICMP query/reply message not related to a TCP/UDP packet.
         * Search for an ICMP state.
         */

        // ...
    } else { // <-- 2
        /*
         * ICMP error message in response to a TCP/UDP packet.
         * Extract the inner TCP/UDP header and search for that state.
         */

        switch (pd->af) {
        case AF_INET: // <-- 3
            if (!pf_pull_hdr(pd2.m, ipoff2, &h2, sizeof(h2), NULL, reason, pd2.af)))
            { /* ... */ }

        case AF_INET6: // <-- 4
            if (!pf_pull_hdr(pd2.m, ipoff2, &h2_6, sizeof(h2_6), NULL, reason, pd2.af))
            { /* ... */ }
            // ...

        switch (pd2.proto) {
        case IPPROTO_TCP: {
            struct tcphdr *th = &pd2.hdr.tcp;
            // ...
            if (!pf_pull_hdr(pd2.m, pd2.off, th, 8, NULL, reason, pd2.af)) { // <-- 5
                // ...
            }
            key.af = pd2.af; // <-- 6 
            key.proto = IPPROTO_TCP;
            key.rdomain = pd2.rdomain;
            PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af);
            PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af);
            key.port[pd2.sidx] = th->th_sport;
            key.port[pd2.didx] = th->th_dport;

            action = pf_find_state(&pd2, &key, state); // <-- 7
            if (action != PF_MATCH)
                return (action);

            // ...

            break;
        }
        case IPPROTO_UDP: {
            struct udphdr *uh = &pd2.hdr.udp;
            int action;
            if (!pf_pull_hdr(pd2.m, pd2.off, uh, sizeof(*uh), NULL, reason, pd2.af)) { // <-- 8
                // ...
            }

            key.af = pd2.af; // <-- 9
            key.proto = IPPROTO_UDP;
            key.rdomain = pd2.rdomain;
            PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af);
            PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af);
            key.port[pd2.sidx] = uh->uh_sport;
            key.port[pd2.didx] = uh->uh_dport;

            action = pf_find_state(&pd2, &key, state); // <-- 10
            if (action != PF_MATCH)
                return (action);
            break;
        }
        case IPPROTO_ICMP: {
            // ...
            break;
        }
        case IPPROTO_ICMPV6: {
            // ...
            break;
        }

        default: { // <-- 11
            int action;
            key.af = pd2.af;
            key.proto = pd2.proto;
            key.rdomain = pd2.rdomain;
            PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af);
            PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af);
            key.port[0] = key.port[1] = 0;
            action = pf_find_state(&pd2, &key, state);
            // ...
            break;
        }
    }

1.pf_icmp_mapping()确定是否应提取内部数据包,如果是,则继续执行。

2.此时仅针对以下数据包继续执行:

· IPv4上的ICMP_UNREACH;

· IPv4上的ICMP_SOURCEQUENCH;

· IPv4上的ICMP_REDIRECT;

· IPv4上的ICMP_TIMXCEED;

· IPv4上的ICMP_PARAMPROB;

· IPv6的ICMP6_DST_UNREACH;

· IPv6上的ICMP6_PACKET_TOO_BIG;

· IPv6上的ICMP6_TIME_EXCEEDED;

· IPv6上的ICMP6_PARAM_PROB。

· 3和4.根据版本提取IP标头;

· 5和8.提取UDP或TCP的标头;

· 6和9.初始化查找密钥,而不考虑上层IP数据包;

· 7和10.执行状态查找,如果发现状态,则函数可以返回PF_PASS,允许数据包通过。

概念验证

本节,我们将演示一个具体的示例。配置是这样的:处于同一网络上的4个主机、两个子网、一个局域网和一个广域网以及用于防护它们的防火墙。我们将使用Linux nftables和OpenBSD Packet Filter作为防火墙来进行测试。

你可以使用虚拟机或真实的设备来设置测试环境,我们在测试中使用了真实的IP前缀。请注意:

1.0.0.0/8下的广域网是一个不受信任的网络;

2.0.0.0/24以下的局域网是一个受信任的网络,其访问必须由防火墙过滤;

· M,广域网IP 为1.0.0.10上的攻击者;

· A,广域网IP 为1.0.0.11上的主机;

· H,局域网IP 为2.0.0.10上的敏感服务器;

· B,局域网IP为2.0.0.11上的主机;

· F,局域网IP 为2.0.0.2与广域网IP 为1.0.0.2之间的防火墙;

12.png

我们将考虑在端口53和1234上从A到B建立一个会话UDP,攻击者必须知道这些会话参数,才能发起进攻。

防火墙配置应该能做到以下防护效果:

1.阻止所有从广域网到局域网上的ICMP;

2.允许ICMP从局域网到广域网;

3.允许A和B之间的UDP连接;

4.阻止其他一切连接;

在这些条件下,我们预计攻击者无法向H发送单个ICMPv6数据包。

对于Linux下的测试,防火墙配置如下(同样可以使用命令nft):

# iptables -P INPUT DROP
# iptables -P FORWARD DROP
# iptables -P OUTPUT DROP
# iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
# iptables -A FORWARD -i if-wan -o if-lan -p udp --dport 53 -j ACCEPT

对于OpenBSD测试,防火墙配置如下:

# em0 is on the WAN
# em1 is on the LAN

block all

# explicitly block icmp from the WAN to the LAN
block in on em0 proto icmp

# allow icmp from the lan to both the WAN and LAN
pass in  on em1 inet proto icmp from em1:network
pass out on em1 inet proto icmp from em1:network
pass out on em0 inet proto icmp from em1:network

# allow udp to B 
pass in  on em0 proto udp to b port 53
pass out on em1 proto udp to b port 53
pass in  on em1 proto udp from b port 53
pass out on em0 proto udp from b port 53

在B上模拟一个UDP服务:

(B) $ nc -n -u -l 53

通过A建立连接:

(A) $ nc -u -n -p 1234 2.0.0.11 53
TEST

我们可以检查从M到H的入站ICMP是否被过滤:

(M) $ ping -c 1 2.0.0.10 -W2

PING 2.0.0.10 (2.0.0.10) 56(84) bytes of data.
--- 2.0.0.10 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

现在我们将使用下面的python脚本,它使用了scapy库。

from scapy.all import *
M = "1.0.0.10" # attacker
H = "2.0.0.10" # protected server
A = "1.0.0.11" 
B = "2.0.0.11"
Pa = 1234
Pb = 53

icmp_reachable = IP(src=M, dst=H) / \
                 ICMP(type=3, code=3) / \
                 IP(src=B, dst=A) / \
                 UDP(sport=Pb, dport=Pa)
send(icmp_reachable)

在Linux和OpenBSD两种情况下,网络捕获都显示ICMP数据包由防火墙转发到H,并从一个接口发送到另一个接口。

19.png

通过Wireshark进行的捕获,其中显示了第二个ICMP消息是从一个接口发送到另一个接口的

因此,无论过滤规则如何设置,攻击者都能够将数据包发送到正常过滤的主机H。

实践中的攻击示例

通常情况下,以上我们所描述的攻击,都是假设攻击者知道现有连接的状态,即TCP或UDP情况下的源和目标IP和端口。这个假设听起来不靠谱,但实践证明,这个假设条件很容易实现。

第一种情况是,如果面向互联网的服务(如Web服务器)位于防火墙层之后,攻击者只需连接到此服务,并将其自身的连接用作有效状态,就可以通过ICMP访问防火墙后层的所有主机

第二种情况是,由于攻击者控制的服务器可能处于泄漏状态,所以局域网中的受害者很容易主动连接到攻击者控制的服务器。

第三种情况是,攻击者可以嗅探受害者的数据,由于IP和TCP标头未加密,因此TLS或SSH并不受保护。

还有一种可能就是,攻击者很可能早已经潜伏在了防火墙后层的局域网上,例如在办公室网络中,只是试图到达经过过滤的DMZ中的服务器。在这种情况下,攻击者可以简单地从局域网发起出站连接,然后从外部定位DMZ。

在大多数情况下,网络防火墙的配置看似合理,但却隐藏着极大的风险,比如下面几种情况。

猜测连接状态

如果攻击者无法轻易知道防火墙上活动连接的状态,他仍然可以尝试暴力破解的方法。

实际上,自动化环境更有可能暴露已知状态,因为系统可能经常通过安装/发现/设置步骤来生成DHCP,DNS或NTP的数据。这些协议是很好的暴力破解对象,因为它们涉及的秘密更少。

1.基于UDP的协议有DHCP,NTP,ISAKMP,所以猜测DHCP状态可能适用于不公开DHCP服务器但包含配置为尝试DHCP的客户端的网络。由于来源和目标是事先知道的,网关可以猜测或暴力破解。所以攻击者通过针对一小组已知的NTP服务器强制执行NTP客户端源地址,可能会成功猜测出NTP状态。这同样适用于固定源和目标端口的ISAKMP。

2.除非连接跟踪实现不检查seq/ack字段(RFC5927推荐使用这种检查),否则猜测出TCP状态会很困难。事实证明,nftables不检查seq / ack字段,因此基于TCP的持久连接协议是很好的猜测对象。这意味着SSH、HTTPS,后端服务、SIP等都可能受到攻击,RFC5961则记录了这种攻击。

3.当应用程序经常使用ICMP ECHO请求时,ICMP状态在一些罕见的情况下是已知的,比如IP连接检查、squid ping等;

4.AH/ESP:由于nftables和Packet Filter都会验证SPI字段,因此只需知道源地址和目标地址即可恢复现有状态。

安全影响

总而言之,能够识别出活动连接的攻击者会将ICMPv6错误发送到防火墙后层的任何主机,并将数据包视为相关。在大多数防火墙配置中,由于允许相关的数据通过,这都将导致数据包被转发到其目标,而不进行任何过滤,攻击者可以利用这个过程到达通常无法到达的主机。

以下就是我们发现的4种攻击情形,它们就是滥用了此过程:

1.攻击过滤主机的IP堆栈;

2.在过滤网络中与植入的恶意载荷进行通信的设置;

3.使用漏洞作为oracle来枚举现有的连接状态;

4.针对过滤的局域网内的网络拓扑结构更改发起攻击;

在第一种攻击情形中,对IP堆栈的成功攻击不太可能一次性完成,因为攻击者只有少量可用的ICMPv6消息类型。但是,如果老旧工业设备和不太成熟的堆栈接收到意外的ICMP数据包,则可能会受到影响。

而第二种攻击情形将有助于在受限网络中发送或接收来自恶意载荷的消息,由于发送的ICMP错误的内部数据报中的有效载荷可以是任意的,因此它可以用作隐蔽信道。

第三种攻击情形会要求攻击者位于防火墙的前后两层,即广域网上和局域网上,其攻击思想是使用ICMP数据包的遍历作为封装数据包与连接状态匹配的证据。实际上,如果内层可以与已知连接相关,则数据包才能够通过。这种技术可以在bruteforce环境中使用,例如,攻击者尝试猜测内部主机和合法服务器之间UDP会话的源端口。

从攻击者的角度来看,第四种攻击情形看起来很有趣,但在现代操作系统中确实行不通。由于两个防火墙都允许ICMP重定向使用所描述的技术进行发送,因此过滤的主机可能会接收可以更改其路由表的数据包。但是我们的测试表明,普通的操作系统(Linux,Windows macOS)可以适当地缓解这种情况。

以上所述的攻击思想都是将ICMP数据包发送到应该过滤的局域网上的主机。但是,值得一提的是,局域网通常使用NAT构建,并且受到保护。这是因为地址转换过程将修补内部和外部IP数据包。因此,只能转发现有连接中的数据包。

总结

综上所述,这些漏洞行为的根源是内部IP数据包与外部IP数据包之间缺乏相关性。

与从A到B的连接相关的合法ICMP错误,应始终使其外部和内部IP标头类似于以下几种情况:

1.外部IP源= B或A和B之间的任何中间设备;

2.外部IP目标= A;

3.内部IP源= A;

4.内部IP目标= B;

实际上,从A到B的路径上的任何主机都可能产生错误,并且事先不知道是哪个主机。这就是为什么检查源IP不可靠。但是,检查目标IP就可以防止这个漏洞。由于此带外ICMP错误会发送到A,因此它必须是对以A作为源的原始数据报的响应。

因此,可以实现的另一个可能的检查是验证内部源IP与外部目标IP是否匹配。

20.png

目前,我们已经将这个PacketFilter中的漏洞披露给了OpenBSD,FreeBSD,Oracle Solaris和OPNsense开发人员和维护人员。他们很快解决了这个问题并在接下来的几个小时内提供了修复, OpenBSD团队发布了相关补丁,而FreeBSD也使用了一个类似的补丁

Linux开发人员和维护人员也收到了我们的漏洞通知,不过在本文发布时,尚未提供任何修复程序。

背景知识介绍

目前的防火墙总共分四类:

包过滤防火墙:包过滤防火墙不检查数据区,包过滤防火墙不建立连接状态表,前后报文无关,应用层控制很弱。

应用网关防火墙:不检查IP、TCP标头,不建立连接状态表,网络层保护比较弱。

状态检测防火墙:不检查数据区,建立连接状态表,前后报文相关,应用层控制很弱。

复合型防火墙:可以检查整个数据包内容,根据需要建立连接状态表,网络层保护强,应用层控制细,会话控制较弱。

什么是PacketFilter(包过滤)?

包过滤是一种内置于Linux内核路由功能之上的防火墙类型,其防火墙工作在网络层。传统的包过滤功能在路由器上常可看到,而专门的防火墙系统一般在此之上加了功能的扩展,如状态检测等,它通过检查单个包的地址,协议,端口等信息来决定是否允许此数据包通过。

数据包过滤用在内部主机和外部主机之间, 过滤系统是一台路由器或是一台主机。过滤系统根据过滤规则来决定是否让数据包通过。用于过滤数据包的路由器被称为过滤路由器。

数据包信息的过滤

数据包过滤是通过对数据包的IP头和TCP头或UDP头的检查来实现的,主要信息有:

· IP源地址

· IP目标地址

· 协议(TCP包、UDP包和ICMP包)

· TCP或UDP包的源端口

· TCP或UDP包的目标端口

· ICMP消息类型

· TCP包头中的ACK位

· 数据包到达的端口

· 数据包出去的端口

在TCP/IP中,存在着一些标准的服务端口号,例如,HTTP的端口号为80。通过屏蔽特定的端口可以禁止特定的服务。包过滤系统可以阻塞内部主机和外部主机或另外一个网络之间的连接,例如,可以阻塞一些被视为是有敌意的或不可信的主机或网络连接到内部网络中。

过滤器的实现

数据包过滤一般使用过滤路由器来实现,这种路由器与普通的路由器有所不同。

普通的路由器只检查数据包的目标地址,并选择一个达到目的地址的最佳路径。它处理数据包是以目标地址为基础的,存在着两种可能性:若路由器可以找到一个路径到达目标地址则发送出去;若路由器不知道如何发送数据包则通知数据包的发送者“数据包不可达”。

过滤路由器会更加仔细地检查数据包,除了决定是否有到达目标地址的路径外,还要决定是否应该发送数据包。“应该与否”是由路由器的过滤策略决定并强行执行的。

什么是nftables?  

nftables 是新的数据包分类框架,新的linux防火墙管理程序,旨在替代现存的 {ip,ip6,arp,eb}_tables。简而言之:

1.它在 Linux 内核版本高于 3.13 时方可使用;

2.它有一个新的命令行工具 ntf,它的语法与 iptables 不同;

3.它也包含了一个兼容层,让你在新的 nftables 内核框架之上运行 iptables 命令;

4.它提供了通用的集合基础允许你建立映射和关联。你可以使用这个新特性把你的规则集分类到多维树中,这大大地减少了找到包最终的行为之前需要检查的规则的数量;

nftables 的特点

1.拥有一些高级的类似编程语言的能力,例如定义变量和包含外部文件,即拥有使用额外脚本的能力,nftables也可以用于多种地址簇的过滤和处理。

2.不同于iptables, nftables并不包含任何的内置表。由管理员决定需要哪些表并添加这些表的处理规则。

3.表包含规则链,规则链包含规则。

Synacktiv公司的网络安全专家在对防火墙实施的安全评估中,观察到一个奇怪的防火墙行为,它可能会影响多个IP堆栈的漏洞。

本文分析了Linux nftables和OpenBSD PacketFilter的潜在行为,是如何形成绕过 nftables/PacketFilter 防火墙过滤规则传输 ICMP/ICMPv6 数据包的漏洞的? 

起初, Synacktiv 公司的网络安全专家认为这种攻击方式是他们第一个发现的,但之后他们发现2004年安全专家Fernando Gont也发布了一篇《如何检测你的IPv6地址组件是否易受到攻击》的文章,在文章中他在本文中,探讨了IPv6的不同组件将如何受到基于ND的攻击。但是,这篇文章的局限之处在于它当时没有确定这种攻击是不是发生在Packet Filter和nftables中。

ICMP和ICMPv6

ICMP和ICMPv6是互联网的主要支持协议,这些协议是为连接测试和数据包没有到达目的地时的错误信号而设计的。接收ICMP消息可以让应用程序理解失败的原因,比如数据包太大,没有可用路由等等。

ICMP消息

从协议本身来说,ICMPv6和基于IPv4的ICMP协议发生了很大的变化,是两个不同的协议,所以为了区分两者,基于IPv6的ICMP协议我们用ICMPv6来命名,协议识别号58。而ICMP一般指代传统的IPv4下的协议。而ICMP协议我们最常用的功能莫过于Ping了,这是网络工程师判断故障或问题的第一步。

ICMPv6是IPv6非常重要的基础性协议,在IPv6中许多基础机制都是由ICMPv6定义及完成,例如地址冲突检测、地址解析、无状态自动获取等等。ICMPv6定义了多种消息类型和机制来实现这些功能。在IPv6包头中,NextHeader=58则表示IPv6包头后封装着一个ICMPv6消息。

1.jpg

ICMPv6消息有两大类:差错消息及信息消息。

2.jpg

和ICMP一样,ICMPv6仍然是用于传递错误信息和网络故障诊断的。ICMP V6除了基本的错误反馈和信息控制功能外,还包括一些使用ICMPV6的机制,如路径MTU发现(PDM)机制,PDM机制通过发送长度越来越大的数据包到达目的节点,当一个给定的数据包长度超过到达目的节点路径上最小的MTU时,该数据包将被丢弃,并发送一个数据包太大的消息给源点,从而源地址节点就知道了这条路径上最小的MTU为多少。

出于不同的目的,ICMPv6反馈的消息可以用类型和代码分别标识,每种标识都有不同的含义,例如,ICMP会反馈以下消息:

1.Echo回复和请求(类型1和8);

2.无法到达目的地(类型3);

3. source quench(类型4);

4.时间戳回复和请求(类型14和15);

……

ICMPv6则会反馈以下消息:

1. 无法到达目的地(类型1);

2.数据包太大(类型2);

3. 超时(类型3);

4.参数问题(类型4);

5. 路由器请求和路由器通告(133和134型);

6.邻居请求和邻居通告(类型135和136);

……

目前,其中的很多消息类型已经被弃用,我们可以根据不同目的,大致将ICMP消息分为三类:

1.请求:它们由主机生成以查询某些信息;

2.回复:它们是上述ICMP请求的ICMP响应;

3.错误:它们是由网络设备或主机在无法处理数据包时创建的;

本文重点介绍错误消息,这个类别非常有趣,因为它的消息会被作为带外数据(out-of-band,OOB)进行发送,以响应另一个协议的第4层通信消息。传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方。为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道。

例如,UDP数据包可能会生成ICMP错误。这些错误通常被封装在ICMP有效载荷、IP标头以及接下来的64字节的出错包中。图1显示了主机B拒绝关闭端口上的数据包的行为:

为:

3.png

带外ICMP错误的示例

已知的攻击和缓解措施

作为信号协议,ICMP消息可以改变接收系统的IP栈的行为。例如,ICMP Redirect和ICMPv6 Router通告可以改变主机的路由表。

恶意用户可以滥用ICMP来破坏网络操作,以下就是过去与ICMP有关的各种攻击方法:

1.ICMP孔http://samy.pl/pwnat/是利用ICMP消息遍历NAT的概念,它要求发起者在NAT之后;

2.ICMP隧道滥用ICMP协议将任意数据封装在ICMP消息之上;

3.ICMP ECHO放大使用广播执行DoS;

4.通过攻击MTU发现进程或数据包拥塞信号,降低网络数据,拥塞(Congestion)是评价一个后端设计是否能绕线绕的通的重要评价指标;

5.ICMPv6 NDP攻击,类似于IPv4中的ARP攻击;

6.ICMPv6 MLD发现外加DoS,类似于IGMP攻击;

通过正确配置操作系统的IP堆栈,可以缓解以上攻击。有趣的是,可以在不使用操作系统的防火墙特性下(例如:sysctl,netsh,…)启用各种ICMP保护。

在Linux上使用sysctl的示例:

# sysctl -a -r '^net\.ipv[46]\.(icmp|conf\.default\.accept)' | cut -d= -f1
net.ipv4.conf.default.accept_local 
net.ipv4.conf.default.accept_redirects 
net.ipv4.conf.default.accept_source_route 
net.ipv4.icmp_echo_ignore_all 
net.ipv4.icmp_echo_ignore_broadcasts 
net.ipv4.icmp_errors_use_inbound_ifaddr 
net.ipv4.icmp_ignore_bogus_error_responses 
net.ipv4.icmp_msgs_burst 
net.ipv4.icmp_msgs_per_sec 
net.ipv4.icmp_ratelimit 
net.ipv4.icmp_ratemask 
net.ipv6.conf.default.accept_dad 
net.ipv6.conf.default.accept_ra 
net.ipv6.conf.default.accept_ra_defrtr 
net.ipv6.conf.default.accept_ra_from_local
...
net.ipv6.conf.default.accept_redirects 
net.ipv6.conf.default.accept_source_route 
net.ipv6.icmp.ratelimit

在理想情况下,危险的ICMP消息应该被每个主机的IP堆栈阻止,而不需要防火墙。但在实践中,安全加固通常由广域网和受限局域网之间的防火墙来实现。这就会产生一个问题,即ICMP和ICMPv6应该如何过滤?

ICMP应该如何过滤?

RFC建议的内容

在过滤ICMP消息时,阻止所有消息类型并不是一个好的缓解方式。它会降低整体用户体验,例如,阻止“太大的数据包” 类型,实际上就会完全阻止IPv6工作,并可能严重降低IPv4的性能。

RFC4890[10](2007)在第4.3.1章是明确说明允许ICMPv6的错误消息的,它们是不能被阻止的:

Error messages that are essential to the establishment and
maintenance of communications:
-  Destination Unreachable (Type 1) - All codes
-  Packet Too Big (Type 2)
-  Time Exceeded (Type 3) - Code 0 only
-  Parameter Problem (Type 4) - Codes 1 and 2 only

草案“过滤ICMP消息的建议”(2013)曾建议;“当设备充当网关或防火墙时,哪些ICMP和ICMPv6消息应该被接受、限制速率或拒绝。”该草案建议允许接受或限制以下消息:

ICMPv4-unreach-(net|host|frag-needed|admin);
ICMPv4-timed-(ttl|reass);
ICMPv6-unreach-(no-route|admin-prohibited|addr|port|reject-route);
ICMPv6-too-big;
ICMPv6-timed-(hop-limit|reass);
ICMPv6-parameter-unrec-option;
ICMPv6-err-expansion.

似乎人们对什么是安全的ICMP数据有不同的看法,通常认为,防火墙应该阻止来自WAN的所有入站ICMP和ICMPv6数据包(NDP除外),除非它们与已知的现有连接相关,,而已知的现有连接可以由有状态检测防火墙跟踪。

注意:草案“过滤ICMP消息的建议”(2013)目前已被升级更新了。

状态检测防火墙和配套的信息反馈机制

状态检测是比包过滤更为有效的安全控制方法。对新建的应用连接,状态检测检查预先设置的安全规则,允许符合规则的连接通过,并在内存中记录下该连接的相关信息,生成状态表。对该连接的后续数据包,只要符合状态表,就可以通过。这种方式的好处在于:由于不需要对每个数据包进行规则检查,而是一个连接的后续数据包(通常是大量的数据包)通过散列算法,直接进行状态检查,从而使得性能得到了较大提高;而且,由于状态表是动态的,因而可以有选择地、动态地开通1024号以上的端口,使得安全性得到进一步地提高。

事实上,状态检测防火墙实现了相关数据包的概念。这些相关数据包是与附加到现有连接的带外数据匹配的数据包。与此相关的概念不仅用于ICMP,还用于其他协议,如可能使用辅助TCP数据传送的FTP。

关于ICMP、带内和带外数据之间的关联是通过从封装在ICMP错误消息中的IP分组中提取“状态标识符”来完成的,如果连接已知,则使用此标识符在表中查找。

为了说明这个概念,我会举以下一个例子。在一个简单的网络中,如果我们希望只允许LAN上的主机通过端口1234上的UDP与WAN上的任何主机连接,且仍然希望A接收带外错误。在这种情况下,将使用以下高级防火墙配置:

1.允许从LAN到WAN udp端口1234的输入;

2.如果数据包与现有的允许连接相关,则允许从WAN到LAN的输入;

3.阻止所有输入;

传出的带内UDP数据将匹配规则1,输入的带外ICMP错误消息将匹配规则2,如图2所示,规则3将拒绝任何其他数据包。

6.png

通过提取内层对ICMP错误进行状态过滤

实际上,根据防火墙配置的语义不同,规则2实现的方式也不同。

什么是连接状态?

到目前为止,我们知道有状态防火墙会根据ICMP(或ICMPv6)错误推断状态。但剩下的问题是,哪些信息实际上是从内部IP数据包中提取的?

由于第4层协议具有不同的语义,每个协议都有自己的提取器,但我们在Packet Filter和nftables衍生物中观察到以下内容:

TCP使用以下字段构造状态:

1.内部IP源和IP目的地;

2.内部源和目标端口;

3.SEQ和ACK字段仅用于包过滤,但不用于nftables;

而UDP使用以下字段构造状态:

1.内部IP源和IP目的地;

2.内部源和目标端口;

而ICMP使用以下字段构造状态:

1.内部IP源和目的地;

2.不同类型的ICMP字段;

对于其他协议:

1.内部IP源和IP目的地;

2.协议的id;

3. 如果防火墙支持协议提供的属性(如SCTP或UDP-Lite端口),则将使用协议提供的属性(nftables可以,而Packet Filter不可以);

总而言之,当防火墙收到带外ICMP反馈的错误消息时,它会执行以下操作:

1.解码IP/ICMP或IPv6/ICMPv6标头;

2.从封装的IP或IPv6数据包中提取状态;

3.尝试匹配现有状态列表中的“状态标识符”;

4.如果内部IP数据包状态与现有状态匹配,则将数据包标记为相关。

获取ICMP 

我们发现当提取内部包来寻找状态时,与外部数据包的相关性都将丢失。这意味着,只要封装的数据包可以与现有连接相关,整个数据包就会被标记为相关。然后,在大多数情况下这个数据包将被允许通过。

被恶意篡改的ICMPv6数据包经常滥用此行为,这种攻击会以使用过滤规则的主机为攻击目标。攻击时,该恶意数据包会被封装在一个具有合法状态的数据包中,如下所示。

ICMP-Reachable packet:
[ IP [email protected] [email protected] type=ICMP ]
[ ICMP [email protected] [email protected] ]
[ IP [email protected] [email protected] ]
[ UDP [email protected] dport=Pa ]
M: the attacker IP
H: the destination IP on which ICMP should be filtered
A: host IP from which the attacker knows an existing session with B
B: host IP from which the attacker knows an existing session with A
Pa: the port used by A its UDP session with B
Pb: the port used by B its UDP session with A
Type: the ICMP type of an out-of-band error packet (example 3)
Code: the ICMP code of an out-of-band error packet (example 3)

在这种情况下,恶意ICMPv6数据包就会运行被通过, nftables和Packet Filter在实现时都受此攻击的影响。

8.png

状态ICMP过滤被攻击的过程

接下来,我将介绍Linux和OpenBSD的实现细节,以剖析这种恶意数据包为何会通过以及这种行为如何被预防。

2018年1月3日,Google Project Zero(GPZ)团队安全研究员Jann Horn在其团队博客中爆出CPU芯片的两组漏洞,分别是Meltdown与Spectre。

它们之所以广受重视,是因为它们根据的是体系结构的设计漏洞,而非针对某个系统或者某个程序,因此它几乎可以遍及大多数近代的CPU。

这里主要有三个漏洞: 

1.CVE-2017-5753(边界检查绕过);

2.CVE-2017-5715(分支目标注入);

3.CVE-2017-5754(恶意数据缓存载入);

Spectre主要利用前两个漏洞进行攻击,而meltdown则主要利用第三个漏洞进行攻击。

Meltdown漏洞影响几乎所有的Intel CPU和部分ARM CPU,而Spectre则影响所有的Intel CPU和AMD CPU,以及主流的ARM CPU。

这两个漏洞允许黑客窃取计算机的全部内存内容,包括移动设备、个人计算机、以及在所谓的云计算机网络中运行的服务器。

漏洞原理

这两组漏洞来源于芯片厂商为了提高CPU性能而引入的两种特性:乱序执行(Out-of-Order Execution)和推测执行(Speculative Execution)。

Meltdown漏洞

Meltdown漏洞只发生在Intel处理器上,利用了处理器对乱序(out-of-order)执行处理不当的缺陷。现代处理器为了提高各个运算单元的利用率,不是一次执行一条指令,而是综合考虑当前指令和后续几条指令,一次批量调度多条指令到空闲的运算单元并行执行,所以代码里指令的先后关系并不一定是指令执行的先后关系。

如果用户空间一条指令读取了内核的内存地址,正常情况下该指令会导致陷入(trap),从而终止该指令的执行。但由于乱序原因,该指令后续指令可能会在trap发生之前就得到执行。如果后续是精细策划的恶意代码,就能够抓住上面提到的处理器漏洞,把“一闪而过”的内核数据“藏匿”在缓存中,偷运出去。

Spectre漏洞

和Meltdown不同,Intel,AMD和ARM都有Spectre漏洞。该漏洞利用了处理器在跳转预测(branch prediction)失效处理不当的缺陷。

简而言之,就是攻击者利用CPU的推测执行机制,暂时绕过代码中的隐式和显式安全检查,以防止程序读取内存中的未授权数据。虽然处理器的推测被设计为微架构的细节,在架构层面是不可见的,但精心设计的程序可以在推测中读取未经授权的信息,并通过诸如程序片段的执行时间(黑客利用短短的时间窗口)之类的侧通道将其公开。

当JavaScript被证明可以用来安装Spectre攻击时,V8团队就开始从 V8 角度分析 Spectre 漏洞。V8使用C++开发,并在谷歌浏览器中使用。在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生设备码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。

对Spectre漏洞的攻击由两部分组成:

1.将无法访问的数据泄漏到隐藏的CPU状态,所有已知的Spectre攻击都使用推测将无法访问的数据位泄漏到CPU缓存中。

2.提取隐藏状态以恢复无法访问的数据,为此,攻击者需要一个足够精确的计时器。令人惊讶的是,低精确度时钟就足够了,尤其是使用边缘阈值等技术。

由于V8的开发者不知道如何彻底解决这两个进程,因此他们只能设计并部署一些缓解措施,这些措施可以极大的减少泄漏到CPU缓存中的信息量,缓解措施使恢复隐藏状态变得非常困难。

处理器在性能增强的同时,行为越来越智能,充分挖掘潜力,优化计算资源,这本来是件好事。Intel是这方面的先行者,包括超线程在内都做得很成功。然而如今Intel面临的局面,罪魁祸首也是这种激进的优化策略。因为对于这些推测执行的指令,由于不知道最终是否会采纳,处理器是在一种“试探”的方式下运行,现在问题就出在这个“试探”机制上。

执行这些指令时,处理器没有做严格的权限验证。例如入侵者从用户空间读取内核地址。推测执行时攻击者首先把数据从内核里读出来,然后再检查这个读操作是否合法的危险方式。这就好比你发现有人闯入密室,慌忙打开灯,结果强盗把你的宝藏看得清清楚楚,你发现有强盗马上又把灯关上。在这些推测执行的指令里,攻击者竟然能够在处理器察觉到越权之前的短暂一刻获得这些内核数据。这个时间窗口很短暂,只有纳秒数量级(大约十亿分之一秒),但足够执行数十条指令,发起一次攻击。

缓解措施

降低计时器的精度

为了精准地抓住这个时差,攻击者程序需要高精度计时器。 糟糕的是,CPU就提供了这样的计时器,但Web平台不会公开它们。Web平台最精确的计时器performance.now(),这个时间戳实际上并不是高精度的。为了降低像Spectre这样的安全威胁,各类浏览器对该类型的值做了不同程度上的四舍五入处理。(Firefox从Firefox 59开始四舍五入到2毫秒精度)一些浏览器还可能对这个值作稍微的随机化处理,这个值的精度在未来的版本中可能会再次改善。

和JavaScript中其他可用的时间类函数(比如Date.now)不同的是:1.window.performance.now()返回的时间戳没有被限制在一毫秒的精确度内,而它使用了一个浮点数来达到微秒级别的精确度。

2.另外一个不同点是,window.performance.now()是以一个恒定的速率慢慢增加的,它不会受到系统时间的影响(可能被其他程序调整)。另外,performance.timing.navigationStart + performance.now() 约等于 Date.now()。

另外在Chrome中,计时器的精度从5微秒范围降低到100微秒范围,并引入随机均匀抖动来防止恢复精确度。在与V8的所有供应商协商之后,他们决定一起采取前所未有的措施,立即在所有浏览器上禁用SharedArrayBuffer API,以防止任何人构建一个纳秒级计时器,用于Spectre攻击。

提高计时器的精度

与上面的方法正好相反,那就是处理器的计时器精度比捕获漏洞的时间还短。

最初提高计时器的精度这样的方法被认为是无法用于缓解漏洞的,然而两年前,一个专门研究微架构攻击的学术研究团队发表了一篇论文,研究了网络平台中计时器在缓解中的可用性。他们得出结论,并发可变共享内存和各种精确度恢复技术可以构建甚至更高精确度的计时器,精确度可达纳秒级。这样的计时器足够精确,可以检测单个L1高速缓存命中和未命中的情况,Spectre gadget通常就是这样泄漏信息的。

比如V1攻击可以用于绕过内存访问的边界检查,核心是利用了推测执行可以执行条件分支语句之后的指令这一性质。攻击者可以利用V1攻击来执行特定的代码片段(gadget),获取其无权限获取的内存空间的内容。

译者注:L1高速缓存也就是V8团队经常说的一级缓存。在CPU里面内置了高速缓存可以提高CPU的运行效率,这也正是Pentium II比Celeron快的原因。内置的L1高速缓存的容量和结构对CPU的性能影响较大,容量越大,性能也相对会提高不少,所以这也正是一些公司力争加大L1级高速缓冲存储器容量的原因。

放大(Amplification)技术

在V8团队的进攻性研究中,他们清楚地发现,仅仅依靠以上所说的计时器的缓解措施是远远不够的。一个原因是攻击者可能只是重复执行他们的gadget,这样累积的时间差异就远大于单个缓存命中或未命中。目前V8团队能够设计出可靠的gadget,一次使用多个缓存行,以便进行缓存容量,产生的时间差异高达600微秒。后来,他们又发现了不受缓存容量限制的任意放大技术,这种放大技术依赖于多次读取机密数据的尝试。

JIT缓解措施

为了使用Spectre读取无法访问的数据,攻击者欺骗CPU以推测性方式执行代码,该代码通常读取无法访问的数据并将其编码到缓存中。这种攻击可以通过以下两种方式得到缓解:

1.防止推测执行代码的出现;

2.防止推测执行读取无法访问的数据;

V8团队通过在每个关键条件分支上插入推荐的推测障碍指令(例如英特尔的LFENCE)并使用retpolines作为间接分支来对上述的第一种措施进行试验。所谓的“retpolines” ,就是用一个不触发间接分支预测器的指令序列替换间接分支。显然,Linux社区建议所有编译器和汇编编写者避免在用户空间中的所有间接分支。这意味着,例如,V8团队应该更新rr的手写汇编以避免间接分支。不幸的是,这种缓解措施的条件太过苛刻,以至于大大降低了设备的运行性能(通过Octane基准测试,发现减速2-3倍)。所以最后,V8团队选择了上述的第二种缓解方法,插入缓解序列,防止因错误推测而读取秘密数据。让V8团队用以下代码片段来说明这个缓解技术:

if (condition) {
  return a[i];
}

为了方便讲解,V8团队假设条件是0或1。如果CPU在i超出界限(访问通常不可访问的数据)时从[i]推测性地读取数据,那么上面的代码很容易受到攻击。V8团队发现,在这种情况下,当条件为0时,推测会尝试读取[i]。此时,第二种缓解会重写此程序,使其行为与原始程序完全相同,但不会泄漏任何推测加载的数据。

在测试时,V8团队预留了一个CPU寄存器,V8团队称之为poison,以跟踪代码是否在错误预测的分支中执行。poison寄存器在所有分支中维护并调用生成的代码,因此任何错误预测的分支都会导致poison寄存器变为0。然后V8团队会检测所有内存访问,以便它们无条件地用poison寄存器的当前值屏蔽所有加载的结果。不过这么做的目的并不是妨碍处理器预测(或错误预测)分支,而是用错误预测的分支破坏(潜在的越界)加载值的信息。测试代码如下所示(假设a是数字数组):

let poison = 1;
// …
if (condition) {
poison *= condition;
return a[i] * poison;
}

附加代码对程序的正常(架构定义)行为没有任何影响,它只影响在推测cpu上运行时的微架构状态。如果程序是在源级别进行检测的,那么现代编译器中的高级优化可能会删除此类检测。在V8中,V8团队阻止编译器通过在编译的最后阶段插入缓冲区来删除缓解。

另外,V8团队还使用poison技术来防止在解释器的字节码分派循环和JavaScript函数调用序列中错误推测的间接分支造成的泄漏。在解释器中,如果字节码处理程序(即解释单个字节码的设备代码序列)与当前字节码不匹配,V8团队就将poison设置为0。对于JavaScript调用,V8团队将目标函数作为参数传递(在寄存器中),如果传入的目标函数与当前函数不匹配,V8团队会在每个函数的开头将poison设置为0。随着poison缓解措施的不断改善,V8团队发现在进行Octane基准测试时,设备的性能减速不到20%。

而WebAssembly的缓解措施更简单,因为主要的安全检查是确保内存访问在一定的范围内进行。对于32位平台,除了正常的边界检查之外,V8团队将所有内存填充到下一个次方,并无条件地屏蔽用户提供的内存索引的任何高位。而64位平台则不需要这样的缓解,因为它已经实现了使用虚拟内存保护进行边界检查。现在,V8团队正在尝试将switch/case语句编译为二进制搜索代码,而不是使用潜在易受攻击的间接分支,但这在某些场景中就不好用了,因为有的间接调用受到retpolines的保护。

缓解措施终究不是一个长久之计

事实上,V8团队所进行的攻击性研究比防御性研究进行地更加顺利,这意味着,再好的缓解措施都是权宜之计,是不可能从根子上缓解Spectre攻击的。让缓解措施失效的原因很多,首先,比Spectre攻击厉害的攻击多得是,V8团队面临许多其他更严重的安全威胁,包括由于常规错误导致的直接越界读取(比Spectre更快更直接),越界写入和潜在的远程代码执行。

其次,由于V8本身的日益复杂的缓解措施,实际上可能会增加攻击面并降低设备的性能。第三,测试和维护微架构泄漏的缓解甚至比设计gadget更棘手,因为很难确定缓解措施是否能按着设计进行工作。如上所述,重要的缓解就被后来的编译器优化功能给删除了。

第四,V8团队发现, Spectre的变体出现的速度非常快,这让既定的缓解方法疲于应付。

终极解决办法:网站隔离

最终V8团队得出的结论是,本质上,是不受信任的代码可以使用Spectre和边信道读取进程的整个地址空间。虽然程序缓解降低了许多潜在gadget的有效性,但效率不高或不全面。唯一有效的缓解措施是将敏感数据移出进程的地址空间。值得庆幸的是,Chrome多年来一直在努力将网站分成不同的进程,以减少传统漏洞造成的攻击面。目前,这项付出终究得到了回报,V8团队在2018年5月之前已尽可能多的为平台部署了网站隔离。

对于Apple用户来说,忘记Apple账户密码并不是什么大问题。如果你把密码忘了,有很多选项可以让你恢复对账户的访问权限。如果你的帐户未开启双因素身份验证模式,则可以通过回答安全问题以快速重置密码,或使用iForgot(苹果的Apple ID密码重设服务)恢复对你帐户的访问权限。如果你已经设置了双因素身份验证来保护你的Apple帐户,你可以在几秒钟轻松更改密码。

以上,我们所讲的是双因素身份验证(2FA)中的第一个验证因素,也就是密码验证缺失的情况下,所有的应对策略。综上所述,重设它们是个很简单的事情。但如何应对第二个身份验证因素缺失的情况呢?如果你出国旅行时,手机被盗,则就无法收到发到你手机上的第二个身份验证因素,比如PIN,签名或ID。还有就是,黑客通过中间人(MitM)攻击、终端人(Man-in-the-endpoint)攻击、2FA 暴力攻击或SIM卡交换伪装成用户,则会出现更糟糕的情况,比如你的网络支付账号被盗等。

因此,如果你无法访问第二个身份验证因素,我会在下面详细介绍应对之策,并比较在Apple和Google生态系统中重新获得对帐户的控制权的过程。

Apple帐户的双因素身份验证主要应用在哪些地方?

双因素认证是一种采用时间同步技术的系统,采用了基于时间、事件和密钥三变量而产生的一次性密码来代替传统的静态密码。

Apple团队引入了双因素身份验证(2FA)功能,该功能为Apple ID提供了最大程度的保护。双因素身份验证的想法是为人们的Apple ID或iCloud帐户添加额外的安全层,即使有人发现了其密码。启用后,该功能仅允许从受信任设备访问帐户。启用2FA后,Apple ID所有者将首次收到有关尝试使用相同Apple ID登录新设备的通知。要批准登录新设备,将要求用户输入密码和验证码(六位验证码),该验证码将发送到可信设备。

1.登录Apple帐户;

1.1登录Apple帐户不止需要使用登录名和密码,还需要第二个身份验证因素;

1.2只有使用你的第二个身份验证因素,你才可以更改或重置登录的密码;

2.出厂重置iPhone,关闭iCloud Lock;

2.1使用登录名和密码后,你可以使用Apple ID密码禁用iCloud Lock;

2.2使用第二个身份验证因素,你可以更改或重置Apple ID密码,然后重置手机并禁用iCloud Lock;

3.从iCloud备份恢复新设备;

3.1使用登录名和密码后,你仍然需要第二个身份验证因素,才能从iCloud备份恢复新设备;

3.2使用你的第二个身份验证因素后,你才可以更改或重置密码,然后设置新设备。

4.如果你丢失了密码,可以有多种选项来恢复?

4.1 如果你至少有一个受信任的设备作为你的第二个身份验证因素,那么你可以更改密码。

4.2 你可以使用iforgot.apple.com重置密码,如果你的某个设备仍然可以通过2FA机制接收推送通知,则重置密码的时间不会超过一分钟。

1.png

4.3如果你仍然可以访问第二个身份验证因素(无论是可信设备还是具有可信电话号码的SIM卡),还有许多其他选项可让你重置Apple ID密码。

2.png

丢失密码的安全后果

如果你还没有丢失第二个身份验证因素,只是丢失Apple ID密码,则你的个人信息不会产生什么严重的后果。

目前,没有第二个身份验证因素的唯一Apple服务是“查找我的电话”。最糟糕的情况是,恶意的人可能会远程锁定在Apple ID上注册的所有设备(你可以解锁并更改Apple ID密码)或远程擦除你的设备(在这种情况下,你会丢失数据,但通过更改Apple ID密码即可解决此问题)。

第二个身份验证因素是什么?

以下项目都需要用到你的第二个身份验证因素:

1.你的iPhone,iPad,iPod Touch或Mac计算机已登录Apple ID;

1.1只有当你可以解锁时(使用Touch ID,面部识别码或密码);

1.2通过设置> Apple ID>密码和安全提供离线代码;

3.png

1.3将在线代码发送到你的设备上(如果有Internet连接);

1.4在iOS 11.3或更高版本的iPhone上,用户可能不需要输入验证码。在某些情况下,可信的电话号码可以在iPhone的后台自动验证;

2. 你信任的电话号码;

2.1SIM卡接收短信或电话;

2.2必须在Apple ID中注册至少一个可信电话号码才能使用2FA;

2.3你可以将多个电话号码注册为可信号码;

2.4任何可信赖的电话号码都可用于更改或重置Apple ID;

2.5每个可信设备都使用唯一的验证码;

不知道你有没有注意到,只有你的Apple设备或你信任的电话号码才能成为你的第二个身份验证因素。没有可以备份的有二维码,也没有Windows / Android设备可用作备份。

这意味着,如果你只有一台Apple设备(比如iPhone或iPad mini))和一个可信赖的电话号码,且突然失去两者的访问权限,则会发生以下很严重的情况。

有趣的是,失去第二个身份验证因素是一个真正无法解决的挑战。这意味着,即使你知道自己的帐户名和密码,也会失去对所有可信设备和可信电话号码的访问权限。

在Apple环境中,丢失第二个身份验证因素的安全后果

如果你丢失了第二个身份验证因素,可能会产生严重后果。

1.如果你丢失了那些装有SIM卡的iPhone、iPad或其他苹果设备,攻击者必须先将其解锁才能可能访问第二个身份验证因素。如果你使用安全锁定屏幕,则成功解锁的可能性很低。

2.如果你丢失了受信任的SIM卡(无论有没有iPhone),攻击者可能能够重置你的Apple帐户密码,远程锁定或擦除你的其他设备,下载你的iCloud备份和一些同步数据(例如日历,iCloud邮件,照片等)。

不过,攻击者无法访问以下任何内容:

1.存储在iCloud钥匙串中的密码;

2.你的健康数据(如果你使用的是iOS 12.0或更高版本);

3.你的消息(短信和iMessage历史);

4.一些应用数据(例如身份验证信息,Gmail消息,聊天日志等);

帐户恢复

对于苹果用户来说,一个非常常见的情况是,他们在旅途中丢失了唯一一部iPhone。如果没有双因素身份验证,替换设备就像购买一个新设备并从云备份中恢复一样简单。然而,如果用户设置了双因素身份验证,由于缺乏可信任的设备和丢失了唯一可信任的SIM卡,则情况就很糟糕了。

通过双因素认证,苹果引入了一种自动恢复账户访问的方法。在大多数情况下,需要很长时间的等待。提供一些额外的信息,比如注册的电话号码,理论上可以加快恢复速度。

建议用户访问iforgot.apple.com并按照提示操作。即使该过程是自动化的,恢复也可能需要很长时间。根据Apple的说法:

如果你使用双因素身份验证且无法登录或重置密码,则可以在帐户恢复等待期后重新获得访问权限。结束等待后,Apple会给你发送一条短信,提示你重新进入你的账户。此时你可以按照说明,立即重新获得对Apple ID的访问权限。

保护儿童帐户:家庭共享,屏幕时间和双因素身份验证

根据苹果官方网站的表述:

Apple家人共享可让最多六位家庭成员轻松共享iTunes、iBooks和App Store购买内容、Apple Music家庭会员资格和iCloud储存空间方案。您的家人还可以共享相簿、日历和提醒事项,甚至帮助定位彼此丢失的设备。

简单来说就是,启用“家人共享”可为你和最多5位家人提供以下服务:

1、绝大多数收费APP只需购买一次,在家人间实施共享即可;

2、只需付一个账号的费用,家人即可获得自己的Apple Music会员资格;

3、以实惠的价格共享iCloud云存储空间,当然,无需担心个人隐私问题;

4、家人间的位置共享;

5、支持为13周岁以下儿童设置独立儿童账户;

6、支持共享相簿、日历、提醒事项;

苹果建议所有人,包括未成年人,在他们的个人设备上使用自己的苹果id。将孩子添加到你的家庭共享帐户中,还可以通过启用屏幕时间来控制他们如何使用自己的设备。为了让你能够通过iCloud远程控制孩子的设备,你将被迫向孩子的帐户添加两个因素的身份验证。

把13岁以下的孩子加入你的家庭是一个单向的选择,出于未知的原因,苹果不允许将未成年儿童从家庭共享中移除(甚至不允许解散家庭)。因此,如果你的孩子无法同时使用他们唯一的设备和他们信任的电话号码,则你可能会被家里一个不活跃的苹果ID卡住。如果你没有为你的孩子注册一个非icloud的电子邮件,那么“[email protected]”不仅是他们的苹果ID,也是他们唯一的电子邮件地址。

谷歌如何处理二次认证

当涉及到双因素身份验证时,谷歌几乎与Apple完全相反。 Google不会将你绑定到任何特定的生态系统,允许你使用任何设备作为你的第二个身份验证因素。下面就是我列出在Google世界中可以充当2FA的项目:

4.png

1.你的Android手机或平板电脑已登录你的Google帐户并配置为接受2FA推送提示;

1.1只有你可以解锁它(生物识别,密码,模式等);

1.2默认情况下,设备上不会生成离线代码(你可以单独设置它们);

1.3将在线提示内容发送到你的设备上(如果有Internet连接),只需点击“是”即可通过2FA;

1.4始终在服务器端执行身份验证;

2. 你信任的电话号码;

2.1SIM卡接收短信或电话;

2.2谷歌并不认为SIM卡和短信是双因素身份验证的安全手段,试图让用户远离使用短信2FA

2.3你可以将多个电话号码注册为可信号码

3.身份验证应用程序;

3.1使用行业标准的TOTP协议;

3.2所有可用的认证应用程序;

3.3兼容不同的开发商,包括谷歌、微软、小米和独立开发商;

3.4所有这些都可以以二维码的方式提供;

3.5二维码可以保存并重复使用多次,以初始化新的身份验证应用程序;

3.6二维码可以存储在云端(不同的账户),允许远程访问;

3.7二维码可以随时从谷歌账户中被删除;

4.备份代码可打印,存放在安全的位置;

5.安全密钥(FIDO U2F或内置于手机中);

6.如果你要求不再在该计算机上提示输入2FA代码,则可以直接使用密码登录Google帐户

对于Google帐户来说,如果你丢失了第二个身份验证因素会发生什么情况?

谷歌已经认识到了用户的手机丢失或被盗后无法获得2FA代码的问题,并对这个问题进行了全面的阐述,具体请看《两步验证的常见问题》。谷歌通过建议你使用备份选项登录到你的谷歌帐户来解决此问题,由于谷歌提供了更广泛的二级身份验证因素选择,所以这个建议非常具有实操性。

如果没有任何备份选项可用,而你仍然可以使用计算机,则可以将该计算机的Web浏览器用作第二个身份验证因素。如果你已在计算机中登录自己的Google帐户(例如在Chrome浏览器中)并且要求Google不再提示该设备上的2FA代码,则只需打开Google两步验证页面,输入你的Google帐户密码并重置双因素身份验证选项即可。例如,你可以生成一组新的备用验证码,以便你在新的Android手机上立即登录Google帐户,这将成为你的新的第二个身份验证因素。如果你无备份选项可以用,并且仍无法访问具有可信Web浏览器的计算机,则必须执行自动帐户恢复过程。在我们的测试中,不仅恢复尝试不成功,而且谷歌暂时阻止了对我们试图恢复的测试帐户的访问,要求我们更改密码。

通过Family Link重新访问Google子帐户

就像Apple一样,Google还允许用户创建一个家庭共享账户。谷歌在2017年推出了Family Link,为13岁以下儿童提供自己的Google帐户,Family Link在技术上适用于拥有现有Google帐户的任何人。在Android设备上,Google Family Link提供与Apple Screen Time类似的功能,允许你控制孩子使用Android设备的方式。

不过Google采用了一种非常不同的方法来保护儿童帐户,虽然13岁以下的儿童无法设置帐户以使用双因素身份验证,但这并不意味着任何知道密码的人都可以登录。要登录孩子的帐户,必须先由一名授权的成人监督员批准登录。成人必须输入谷歌帐户密码(并通过2FA)才能授权儿童登录。

如果孩子丢失了他们唯一的Apple设备和他们唯一受信任的电话号码,那么在Apple的世界中,他们将被拒绝访问他们的Apple帐户,他们的父母也几乎无法(或根本无法)重新控制这些账户。

而Google为了权衡安全性和便利性之间的关系,为父母或法定监护人提供了一种重新控制孩子账户的简单方法:即通过Family Link应用程序立即重置孩子的密码。

总结

Apple和谷歌都有自己的双因素身份验证实现过程,在本文中,我们回顾了在这两种环境中,用户在知道第一个验证因素(密码)的情况下,失去第二个身份验证因素时所产生的后果及恢复方法的差异。此外,我们还回顾了苹果和谷歌生态系统在访问子帐户方面的差异。

前言

我最近正在研究一个使用加密Token(USB加密狗)的不知名Windows软件,其中可执行文件本身经过某种形式的预处理,并被封装和加密,以阻止在没有加密狗的情况下使用。目前,该软件不再适用于Windows 10(可能是由于Envelope保护逻辑中的某些漏洞)。

实际上,该软件使用这些Envelope是为了防止加密狗代码被删除,如下所示:

if(!dongle_present()){exit();}

即使被删除,也很容易被修复。

另外,使用这些Envelope还有作用。

1.隐藏硬编码的秘密;

2.隐藏未发布的功能;

3.阻止或使软件复制复杂化;

过去今年,我已经研究了几个辅助封装的硬件的二进制文件,其中并没有很多变体。考虑到封装代码已经成了一个不怎么被关注的技术,没有很多变体也是可以理解的。另外,添加至少需要通过HID进行通信的硬件组件,会使任何封装器模型复杂化,并显著增加攻击面。

然而,本文所讲的样本则非常有趣,因为它似乎不是普通的AKS/HASP SRMEnvelope或VMPEnvelope,而是来自深思洛克(SenseLock) 的更便宜的产品。

硬件

1.png

Senselock LC也被称为Clave 2,这个预算级别的硬件加密Token不像Elite EL型号那样华而不实。它没有强大的API,无法执行用户创建的自定义固件,它绝对是“入门级”的Token设备。

配置如下:

1.内置2KB闪存;

2.通过API和“更新包”远程更新此内存区域;

3. AES128位加密,带有不可配置的硬件密钥;

4.每次调用最多16个字节的输入数据;

5.HID(Driverless)支持Windows/Mac/Linux 32/64位;

另一个值得吹捧的特性(仅适用于Windows 32位)是一个Envelope实用程序,它可以用加密狗加密和封装现有的、预编译的可执行文件,并提供一些额外的安全选项,比如自定义漏洞消息或将特定的封装可执行文件绑定到特定的加密狗。

供应商提供的软件

幸运的是,这个加密狗的API可以在Senselock站点上免费获得,包括用于封装的实用程序。应该注意的是,虽然其他产品(如HASP)会混淆客户端API库和封装实用程序,但这并非总是如此。

你可以很容易地从API zip中轻松抓取一个静态库文件并解压缩它:

2.png

可以看出它们没有被删除,以后会有用的。

003.png

Envelope实用程序本身是一个相当基本的GUI,它会扩展到使用LC库静态编译的exe。

API本身也是相当简单的,使用Clave 2,你可以进行如下操作:

1.打开具有给定索引的句柄;

2.使用密码登录,密码有三个 ,分别是admin (read – write)、general (read/encrypt/decrypt)和auth;

3.做你想做的;

应该注意的是,这种API流程的工作方式意味着在大多数情况下,通用密码至少可能是硬编码的。因此,如果有人知道所需的密码,那么使用这个公开可用的API并使用适用于其他软件的加密狗创建软件就很简单了。

注: 大多数加密TokenAPI都会进行一些初始化,以便将静态和动态库绑定到特定的加密狗或加密狗系列,这是为了防止人们通过公共可用库与特定加密狗交互。

漏洞寻找

1.一开始我们对目标应用程序了解得不够多,所以更好的方法是封装一个超级基础应用程序。这样,我们就将同时拥有代码和原始二进制文件,并将其用于我们的测试。

2.我们可能会使用API库来标记供应商代码,并尝试更快地确定整个过程的工作方式。

3.我们的目的是确定封装器是如何工作的,或者使用调试器生成一个运行转储。

4.如果我们真的想要额外的点,这是可能的,我们可以静态地重构原始的exe,而不需要实际运行程序。

4.1该封装器是否采用了额外的进程、nanomites……;

4.2如果封装器使用被盗字节,那么静态构建将更加困难;

测试应用程序

一个相当基础的C语言程序将非常适合测试:

#include <Windows.h>#include <stdio.h>int main() {
	HMODULE h = LoadLibraryA("user32.dll");	printf("This is a test program: %p\n", h);

	HANDLE hfile = CreateFileA("C:\\test.txt",
		GENERIC_READ,
		FILE_SHARE_READ,		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,		NULL);	CloseHandle(hfile);	return 0;
}

编译后的结果大约是9KB (JFC Visual Studio),用Envelope处理它会得到一个后缀为“_shell”的exe(大小> 100KB)

5.png

将它加载到IDA中分解IAT,这样我们的可执行文件中有一个新部分:

6.png

毫无疑问,这也是我们新的测试点。

首先要做的是打开我们的静态库,寻找常量和各种类似的函数,并检查xreferences,以尝试标记其中的一些内容。

结果如下:

7.png

现在,在原始入口点之后输入一些二级函数,这样我们将开始看到一些熟悉的内容,例如:

8.png

以及

9.png

现在,我们就获得了一些VM检测代码。

实际上,通过封装器入口点中的一些函数,你会看到很多来自以下链接的复制粘贴代码:https://www.cnblogs.com/zhangdongsheng/archive/2012/07/02/2573416.html

通常情况下,封装者会混淆这种逻辑,以使识别或修改变得更加困难。在这种情况下,如果你修改此部分中的任何字节,应用程序都将关闭(无论如何都不会正常运行)。

10.png

在最初的拆封过程中有一些校验和被用来阻止篡改,不过它们看起来更像是检查一个硬编码的值,该值不用于任何其他用途。因此,可以很容易地更改结果值或完全删除检查而不产生任何后果。

除此之外,它是寻找进程名称的非常典型的检查(例如ollydbg,idaq,softice等)。

需要注意的一件事是,因为我们标记了LC API函数,所以我们还可以看到这些API调用的执行位置。这样,我们可以看到调用LC_passwd的位置,这意味着,我们也知道了硬编码后的加密狗密码的位置。

11.png

现在,我们就可以开始解析封装器的流程了。

解析封装过程

在对大多数内容进行标记分析之后,解析封装器的过程就非常简单。所以我会简要的说一下,因为有很多示例文件,比如动态绑定解析、反调试检查和反vm或反篡改检查,它们在大多数封装的二进制文件和一些日常管理工作中都很常见。解析过程如下:

* Iterate each section of the exe that is marked as executable (e.g. a CODE section)
  -> Log into the Dongle,
  -> Send the hardcoded exe key (16 bytes) to the dongle's "encrypt" fuction.
  -> Retrieve the 16 byte key result.
  -> Use the key result in a Rijndael function to decrypt the given section in place.

* Go to a hardcoded offset in the .lcsh section

* Decrypt the a blob of data scraped from the original Import Address Table (in place)
  using the same method of getting a key and Rijndael and all that stuff from the code
  sections.

* Read the original IDT in plaintext from a hardcoded offset in the .lcsh section.

* Use the newly decrypted original IAT and IDT to write various offsets to
  relocation points and additional locations to reflect the IAT's new location.

* Jump to OEP (this address is also hardcoded into .lcsh).

1.可以看出,这个封装器做的任何事情都不会造成损失或破坏。因此,我们决定静态地重构一个可运行的非封装可执行文件。

2.我们应该能够复制所有这些步骤,值得注意的是,搞清楚整个过程不需要动态分析。因为没有垃圾代码、阻止反汇编的宏和混淆策略,所以静态地搞清楚这些步骤非常简单。

3.既然我们理解了这个过程,那么我们应该能够构建一个可以解压缩给定可执行文件的脚本。不过其中会受到一些限制:

3.1我们需要加密那个exe密钥,Envelope生成器会随机创建一个128位的值用于加密狗。因此,所有的exe都是不同的。因此,如果发布了更新的可执行文件,则还得依赖加密狗。

3.2我们可能希望选择性地指定一个预先录制的加密密钥,这样就不总是需要加密狗了,并在使用加密狗时,使用某种打印输出来查看生成的加密密钥。

3.3我们有一个dll ,甚至有一个64位dll,但由于API存在漏洞,所以供应商建议使用32位版本。因此,我们将使用带有Python Ctypes的32位供应商API dll,,并在我们的解封器中放入合法的加密狗交互。

除了加密狗的硬编码密钥和密码之外,它们所在的一般区域还有一些其他有趣的值。

13.png

在CFFExplorer中打开我们的原始exe,此时会出现一些值。

14.png

此时,我们就可以开始构建这个area中存在的内容了,并正确解压所涉及的重要可执行文件,比如:

1.OEP(即程序的入口点,可以用OD载入,不分析代码);

2.电子狗密码;

3.预加密的EXE密钥;

4.原始映像基底;

5.加密的IAT偏移和大小;

6.IDT偏移量和大小;

7. 初始重定位目录;

编写一个解封器

注意:其中包含了代码。

为了使整个编写过程更容易,我们将使用2个Py3模块:

1.pefile; 

2.py3rijndael; 

PEfile模块非常适合处理pe文件或自动执行这类任务,我们在示例中使用的是Rijndael的初始py3模块,因为这是封装器用来加密和解密代码块的。

第1步:加载Senselock数据块

首先是加载PE文件并从数据块中转储有趣的值,类似于:

15.png

注意,如果加密狗可用,我们可以使用针对“LC.dll”(pylc)创建的最小绑定派生出EXE密钥。密码和开发人员ID可以直接从可执行文件中读取。因此,不需要额外的配置。

第2步:解密所有代码段

接下来,我们必须迭代每个部分,并确定它是否可执行。我们可以通过检查“IMAGE_SCN_MEM_EXECUTE”标志的每个部分来实现这一点。如果是这样,我们使用Rijndael和EXE密钥解密该部分。

对比我们的封装和非封装版本,我们可以看到.text部分发生了变化:

16.png

事实上,与原版相比,我们似乎取得了进步!

016.png

即使在IDA中加载它并转到指定的OEP,我们也可以看到事情正在恢复正常。

18.png

第3步:解密IAT信息

接下来,我们需要解密该IAT块以查看我们正在使用的内容。内置结构是以下这样的:

· bytes – encrypted_iat_information

· bytes – original_idt

19.png

值得注意的是,这不仅仅是原始IAT的副本。Thunk条目已被删除并存储在自定义表中,在重构IAT之前需要对该表进行解析。

注意:以下步骤的难度将取决于PE内部结构和测试者导入方面的经验,在此,我建议你阅读一下该内容。http://www.reverse-engineering.info/SystemInformation/iat.html#sec3.2

第4步:解析IAT Blob

接下来的任务实际上是加载我们解密的IAT blob,并将其视为一个内置了几个字符串表的多链表。

20.png

特别值得注意的是,该表还考虑了按正常方式(IMAGE_ORDINAL_FLAG32)显示的导入序号。

在处理结束时,我们应该有一个看起来像这样的列表:

KERNEL32.dll
       CreateFileA
       LoadLibraryA
       CloseHandle
       IsDebuggerPresent
       InitializeSListHead
       GetSystemTimeAsFileTime
       GetCurrentThreadId
       GetCurrentProcessId
       QueryPerformanceCounter
       IsProcessorFeaturePresent
       TerminateProcess
       GetCurrentProcess
       SetUnhandledExceptionFilter
       UnhandledExceptionFilter
       GetModuleHandleW
VCRUNTIME140.dll
       memset
       _except_handler4_common
api-ms-win-crt-stdio-l1-1-0.dll
...

我们现在将“表”(LOL)列表,并在处理导入描述符表(IDT)之后将其返回。

第5步:恢复原始IDT

22.png

如前所述,原始IDT完好无损,并遵循加密的IAT信息blob。事实上,IDT仍然保留其最初位于IAT头部时的RVA(relative Virtual Address,相对虚拟地址偏移),它没有被修改并遵循典型的结构。

_fields_ = [('p_original_first_thunk', ctypes.c_int32),
            ('time_datestamp', ctypes.c_uint32),
            ('forwarder_chain', ctypes.c_int32),
            ('p_name', ctypes.c_int32),
            ('p_first_thunk', ctypes.c_int32)]

我们还知道,在这个示例中,包含8个条目(7个真实条目和1个空白)来表示表的结尾,每个长度为20字节。

这几步下来,重构就算完成了。如果你想尝试查找IDT过去的位置,可以通过以下步骤:

1.寻找最小的OFT值;

2.减去0x14 * number_of_idt_entries;

24.png

在大多数情况下,这应该是IDT的原始位置。由此我们知道,封装器不能在原始PE映像中准确地重建该表,它在lcsh中“in place”运行它,并更新所有重定位偏移量来进行匹配。

第6步:恢复DLL字符串位置!

因为知道IDT的位置和名称RVA,这意味着我们也知道这些dll名称字符串的位置。

作为奖励,让我们把它们放回原来的位置,以便让那些RVA值保持有效,而不是浪费额外的空间!

25.png

如上所述,编译过程将dll字符串内联到THUNK条目中,它们位于_IMAGE_IMPORT_BY_NAME结构的旁边,而且它们也是无序的。当然,他们中的一些也有多个尾随null( trailing null),至于是什么原因,我们也不清楚。

第7步:创建一个新的Thunk数组位置

为此,我们需要遍历前面创建的导入列表,找到函数名称,然后按名称结构创建导入。应该注意,由于序号不需要_IMAGE_IMPORT_BY_NAME结构,所以没有序号信息。

但是,对于每次导入,我们将添加偏移量,我们将把它写入列表供以后参考,因为这些偏移量将在以后修复IAT时需要用到。

26.png

第8步:调整重定位

现在要进入最后的调整阶段了,为此我们必须创建一个值表,通过RVA指向_IMAGE_IMPORT_BY_NAME结构,或者通过迭代导入列表将序号排列好。

通过IDT,我们可以在两个位置:FirstThunk和OriginalFirstThunk写入这些偏移量。

第9步

1.现在,我们必须将导入部分偏移量更改为前面编写IDT标头的值,同时更改导入表的总体大小。

2.我们必须将EntryPoint调整为真正的OEP;

3.我们应该重命名.lcsh部分让我们知道它已被修改;

27.png

从上图可以看出,构建结果非常完美。

28.png

由于名称表的导入偏移量是不同的,并且我们最后添加了一些额外的内容,所以一些PE标头偏移量会和原来的架构有所不同。除此之外,未封装的exe的大小为10074字节(原始值为9216字节,封装后为102912字节)。

现在,我们可以清理该架构,检查所封装的“代码”目录即可。但是,你需要从供应商SDK(Clave2 Basic_v2.2.2.2.zip)下载LC.dll文件。

总结

1.供应商将客户端库锁定到特定客户是有原因的;

2.有时候,一些混淆或反汇编策略可能会对分析封装工作产生巨大影响;

3. 如果可执行文件使用加密狗,如加密的应用程序文件或通过利用加密狗事务进行网络通信,那本文所讲的方法的效果就会差很多;

4.与封装器混合的硬件加密模块很少能构建出更复杂的安全模型;

前言

“Karta”(在俄语中的意思是“map”)是IDA(一个静态反编译程序)的源代码辅助二进制对比插件,该插件的开发是为了对比一个非常大的二进制文件(通常是固件文件)中的开源库的符号。对于那些每天都要处理固件文件的人来说,重复逆转net-snmp(一个免费的、开放源代码的SNMP实现,以前称为UCD-SNMP)非常浪费时间的。所以,人们急需一种对比插件工具,来识别使用过的开源码,并在IDA中自动对比它们对应的符号。

人们最初的开发重点是放在了快速对比的过程上,但在实践中,就发现即使他们试图逆向的二进制文件包含超过10万个函数,也仅仅会对比一个包含300个函数的库,而光这个过程就需要等待几个小时。

事实证明,出于性能原因而部署的启发式算法对对比结果也有很大影响。Karta的假阳性率非常低,而真阳性率很高。这使得它甚至可以用于对比中小型二进制文件。

因此,有人认为Karta可以成为开发人员工具箱中的一个重要工具,并应用于以下场景:

1.侦察阶段:确定二进制文件中使用的开放源代码(包括它们的版本);

2.优化Clutter:对比使用的开源的符号,从而为逆向工程节省时间;

3.查找用例:使用已用过的开放源代码列表,其中的符号已经在二进制文件中对比,以便在可执行或固件文件中轻松查找用例。

KARTA

如前所述,Karta是IDA*的源代码辅助二进制对比插件,该插件有两个重要的用途:

1.标识:对静态编译的开源代码的确切版本进行标识;

2.对比:对比已标识的开源代码的符号;

Karta现在是开源的,我可以在Github中找到。

由于在不同系统结构上编译开源库是非常复杂的事情,因此人们通常会通过让插件独立于系统运行来消除此复杂的过程。例如,如果我想要对比libpng开源的1.2.29版本(这是我在HP OfficeJet固件中使用的版本),我所要做的就是从Github复制它并在(本文使用的x86)设备上编译它。编译完成后,Karta可以生成描述库的规范.json配置文件。使用此配置,即使固件已编译为Big-Endian ARM Thumb模式,Karta插件现在也可以在固件中成功找到库。

Karta由模块组成,IDA反汇编模块可以被任何其他反汇编模块替换

寻找用例

虽然目前,我已经为介绍了几个插件用例,但在热门程序中查找用例可能是最有趣的用例。以下是我在研究过程中发现的两个真实用例。

HP OfficeJet固件

由于在对HP传真固件研究期间,我需要将用例用作调试漏洞( CVE-2017-9765 )。该漏洞允许攻击者远程破坏 SOAP Web 服务后台进程,并在受害者设备上执行任意代码。在完成Karta的开发之后,我返回固件并检查了Karta是如何帮助我进行漏洞研究的。

标识符插件会告诉我固件中使用的开源库是:

· libpng: 1.2.29

· zlib: 1.2.3

· OpenSSL: 1.0.1.j

· mDNSResponder: unknown

· gSOAP: 2.7

我可以看到确实使用了gSOAP(编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多),快速的CVE搜索显示它包含一个关键的漏洞:CVE-2017-9765

在快速编译了此版本的gSOAP的配置之后,我运行了对比器并导入了对比的符号。如下图所示,我可以看到易受攻击的代码函数soap_get_pi与Karta对比。

fig1.png

反编译的soap_get_pi函数,与Karta对比

这对Karta插件来说是一个非常好的消息,这意味着它可以在真实的场景中正常工作(可惜我也只是在调试漏洞完成后才知道这一点)。

普通的闭源程序:TeamViewer

虽然在固件中轻松找到用例很方便,但在Windows PC上使用的日常程序中,实现这一点就很难了,为什么?在阅读Project Zero在WebRTC上的博客文章时,我发现他们在一个名为libvpx的库中发现了一个漏洞:CVE-2018-6155,它可以攻击VP8视频编码技术源代码,从而影响了VP8库libvpx而不是WebRTC中的代码。因此,该漏洞有可能影响使用除WebRTC之外的所有其他库的程序。

这看起来很有趣,因为Project Zero特别指出这个漏洞“有可能影响使用除WebRTC之外的所有其他库的程序”。由于我在计算机上安装了TeamViewer,看起来它应该使用相同的开源库,让我来看看。

为此,我在IDA中打开了TeamViewer.exe,并在分析过程中开始运行它。为此,我还专门下载了最新版本的libvpx(1.7.0),为它编写了一个简单的标识符,并将其添加到Karta中。由于IDA完成这个分析的时间太长了,我直接将其停止,并运行Karta的标识符插件。从中确定的开放源代码包括:

· zlib: 1.2.5

· mDNSResponder: unknown

· libjpeg: 8b

· libvpx: 1.6.1

TeamViewer不仅使用libvpx(编解码器开发包),而且都是2017年1月的旧版本。

以下是Google发布的补丁,其中我感兴趣的函数是vp8_deblock,如下所示。

fig2.png

vp8_deblock函数的代码片段,易受CVE-2018-6155攻击

现在让IDA恢复分析进程,然后继续编译libvpx版本1.6.1的Karta配置。配置完成后,在IDA完成对二进制文件的分析之后,我运行了Karta的对比器插件。如下所示,对比器发现了易受攻击的函数:

fig3.png

Karta的对比结果显示它与易受攻击的函数相对比(用蓝色突出显示)

在我将结果导回IDA之后,我可以通过数字常量清楚地验证这是正确的对比。

fig4.png

如IDA Pro所示,易受攻击的函数与我的插件相对比

此时,我已经成功在TeamViewer程序中发现了一个漏洞,甚至还知道了在调试时确切地放置断点的位置。

整个漏洞发现过程大约需要2个小时,因为IDA的分析非常耗时,原因在于TeamViewer是一个非常大的程序,包含超过143000个函数。

Karta是如何运行的?

101个函数的二进制对比

简而言之,二进制对比可以归结为以下这个过程:如果我们想检查两个函数(一个来自已编译的开源,另一个来自二进制)是否表示同一个函数,为了能够比较这两个函数,我们需要将它们转换成用统一的方法表示的内容,通常称为“规范表示”。这种表示通常包括我们从函数中提取的一组特性:数字常量列表、字符串列表、汇编指令的数量等等。

当我们尝试对比一组相关函数时,例如,在一个已编译的开源项目中,我们将多余的信息存储在规范表示中,以便对函数之间的关系进行编码:被调用函数列表(被调用者),列表调用我的函数(调用者)等。使用此信息,我可以尝试根据控制流图(CFG)中的规则或位置对比两个函数。

此时,我们可以使用一些传统的二进制对比工具,如BinDiff或Diaphora。虽然每个对比工具都有自己独特的巧妙对比启发式算法,但它们都基于相同的规则,以比较两个规范表示并对结果进行评分,这意味着二进制对比工具首先将所有函数转换为它们的规范表示形式。

避免内存崩溃

当分析一个包含大约65000个函数的二进制文件时,比如我使用的OfficeJet打印机的固件,为所有函数构建规范表示的过程根本无法扩展。它需要很长时间(通常超过一个小时),并且可以在磁盘空间中消耗超过3GB的空间。这意味着,稍后将此数据集加载到内存中常常会导致对比程序崩溃。

如果我们想要对比巨大的二进制文件中的任何内容,就需要改变对比策略。由于开源库相对较小,我可以将问题描述为:

· M——我的开源函数数量;

· N——我的二进制文件中的函数数量;

由于我希望在大小为N的二进制中对比数量为M的函数,其中M << N,是使用依赖于M而不是N的内存。

5.png

二进制地址空间的图解,我试图在其中对比我的库

链接器位置的寻找

如果我将稍后讨论的特殊情况排除在外的话,我可以跳过编译过程直接使用以下步骤进行链接:

1.编译器独立编译每个源文件,并创建一个对比的二进制文件(.o用于gcc,.obj用于visual studio);

2.链接器将所有二进制文件组合成一个二进制大对象(一个可以存储二进制文件的“容器”);

3.在链接阶段,此大对象将按原样插入最终程序或固件;

这导致两个重要的结果:

1.编译后的源代码包含在固件或可执行文件中的一个连续的大对象中;

2.一旦我们找到了这个大对象的一个标志(被称为锚点),我们就可以根据这个大对象中应该包含的函数的数量来推测二进制中这个大对象位置的下限和上限。

其实,这也是Karta所依据的基本规则。

构建函数关系地图

Karta是一个源代码辅助工具,通过利用源代码中的信息,我可以构建一个映射:哪些函数包含在哪个文件中,以及该库包含哪些文件。

以下是对比二进制文件库的过程:

1. 从一个基本标识符脚本开始,该脚本检查二进制文件是否使用了该库,以及使用的版本——O(N)时间和O(1)内存消耗;

2.识别后,扫描二进制文件以搜索锚函数——O(N)时间和O(1)内存消耗;

3. 使用定位锚函数来放大二进制函数的推测范围,这些函数可能是库的一部分—O(1)时间和O(1)内存消耗;

4.此时,所有逻辑都将出现在聚焦范围内,其大小为O(M)。

以下是具体的示例:

1.我有一个含有322个函数的库,利用此方法,我找到了5个锚函数;

2.最低的锚点是二进制文件中的函数#2622;

3.最高的锚点是二进制文件中的函数#2842;

4.锚点之间包含的范围包括221(2842 – 2622 + 1 = 221)个函数;

5.我们还需要找到101(322 – 221 = 101)个函数;

6.为了安全起见,在我的方法中,我会在第一个锚点之前包含101个函数,在最后一个锚点之后也包含101个函数。

总的来说,函数的总数量: 423(101 + 221 + 101 = 423)个函数<< 65000个完整的二进制文件。

我现在要做的只是为聚焦功能构建规范表示,从而大大提高了我从这一点开始的性能。

注意:这个映射可以为构建函数关系地图提供进一步的帮助,因为来自文件a.c的foo()函数应该只与a.c中的函数对比,这样就不需要将它与我们已经标识为驻留在不同文件中的函数进行比较。

选择锚点

从本质上讲,这些锚点函数在规范表示之前都是对比的,这就限制了我在搜索时可以使用的特性。此外,我希望锚点成为库的唯一地标识,这样,我正在处理的二进制文件中的其他库就不会有任何误报。

在不知道所有开放源代码的情况下,为复杂的特性确定标准非常困难。尽管如此,我还是编写了一些在实践中似乎很有效的启发式算法。

fig6.png

IDA Pro中的OpenSSL函数SHA224_Init

我选择这个函数是因为它有惟一的数值常量,且可配置确切的规则,你可以在其中找到src/config/anchor_config.py文件。

对比步骤过程

现在我们已经知道了Karta对比引擎背后的主要逻辑是什么,下来就让我完整介绍对比的详细步骤。

标识符

每个受支持的库都有一个标识符,我试图在二进制文件中找到它。由于大多数开源代码都包括唯一字符串,且它们通常都具有完整版本详细信息,因此大多数标识符都是基于字符串的,并且是为他们试图识别的库配置的。一旦找到库,标识符就尝试提取版本信息,并对可执行或固件使用的确切版本进行指纹识别。

开源项目是不可能在编译后的二进制文件中试图隐藏的,这些库不仅通常包含一个简短的Google搜索,以识别字符串所在原始库的唯一线索。不过,它们通常包含不必要的信息。以下是一个来自libpng库的版权声明的示例,它是一个使用二进制文件编译的字符串。

fig7.png

编译后的二进制文件中包含libpng的版权字符串

如你所见,在大多数用例中,识别可执行程序中是否存在开源库相对比较容易。

锚点搜索

使用标识符中的信息,就可以加载特定库版本的配置(基于.json *)。第一步是扫描二进制文件,查找与库的锚函数对比的惟一数值常量和惟一字符串。如果没有锚点,我无法扩展库并继续对比过程。

对比开始后,整个配置都会加载到内存中。由于我们不需要对配置发出查询,这样就不需要使用更流行的sqlite数据库了,这种从sqlite到json的转换,会导致配置文件的大小大幅减小。

根据绘制的文件映射关系图,我可以精确定位包含对比锚函数的每个文件的位置,并估计其下限和上限(使用前面描述的相同逻辑)。

fig8.png

对比结果的示例图

文件提示

由于许多开源项目往往包含源文件名称的调试或跟踪字符串,通常,这些字符串位于上述源文件的函数中,这意味着我们可以使用它们作为文件提示。绘制的文件映射关系图后,我可以使用这些提示来确定其他文件的位置。

定位代理

由于代理是本地唯一的函数,它也可以称为本地锚。在它的文件中,它是一个锚,但它包含的常量弱于全局锚所需的常量,每个定位的文件都会尝试对比自己的代理。

优化链接器

在上面的讲解中,我假设了一种前提,就是开源将被编译为单个连续的大对象,并且内部文件不会彼此模糊处理。不幸的是,实际情况并非如此。

实际上,当我最初尝试在Adobe PDF的二进制文件(2d.x3d)中对比libtiff时,得到的结果并不理想:只有176/500个函数对比。经过调查,该链接器似乎与具有相同二进制结构的函数混淆了。例如,用不同的名称或不同的名称范围(来自不同文件的静态函数)实现了两次的函数。

9.png

来自libtiff的两个相同的函数,其实它们位于不同的文件中

10.png

来自IDA Pro的分析,显示使用了左边的函数而不是右边的函数

虽然这种优化减少了可执行文件的大小,但它由于会进行模糊处理,且改变了控制流程图。且稍后进行几次快速检查,我发现这种优化也会破坏其他二进制对比工具的对比结果。

我决定像处理链接器那样解决这个问题,在编译开源库的规范表示时,我对每个函数的链接器视图进行哈希,并将其存储为唯一的函数ID。 起初我自己对字节进行了哈希处理,但是当两个函数引用相同的全局变量并且该变量驻留在每个文件中的不同偏移量时,会引入一些错误。参见下图,我通过对大多数操作码的字节进行哈希,并在引用导出的全局变量时对指令的文本进行哈希,解决了这个问题。

11.png

文件tif_dirwrite.c中的函数TIFFClampDoubleToFloat()

12.png

文件tif_dir.c中的函数TIFFClampDoubleToFloat()

在对比过程中,对比器会查找任何可能合并的线索。当对比器在控制流图中发现两个函数是相同函数时,合并就会发生。此时,二进制函数就知道它所代表的源函数的数量,并且将保存它对比的合并源函数的列表。

使用此优化,我现在可以修复控制流图中的异常,因为每个检测到的合并都有效地将控制流图扩展到链接器优化之前的原始状态。在相同的二进制文件(2d.x3d)上再次测试时,结果明显优化:对比了248/500个函数,对比度提高了41%。

可以看到,Karta可以让链接器对函数_TIFFNoFixupTags的优化:

13.png

来自Karta的对比结果,成功地标识了链接合并函数

对比结果

现在是测试Karta如何处理原始的OfficeJet固件的时候了,我在计算机上的虚拟机(VM)中测试了这个插件,结果如下:

14.png

测试的OfficeJet固件上的对比结果

可以看到,不到30秒就可以对比一个包含300个函数(如libpng)的开源程序。此外,我还能够对比所有引用的库函数。

注意事项

1.当Karta处理函数的规范表示时,它与架构无关;

2.因为我的对比是从对比的开源库的角度进行的,所以我还可以推导出关于“外部”函数的信息,这些函数不是库的一部分,但是可以从库中调用。例如,libpng使用zlib。

15.png

在libpng对比过程中对比的外部zlib函数

此外,在大多数情况下,我能够对比标准库中的函数,例如:memcpy,memcmp,malloc等。

与已知的BinDiff工具的比较

由于我无法比较所有现有的二进制对比工具,所以我选择关注以下常用的工具:

BinDiff:BinDiff是二进制文件的比较二进制对比工具,可以帮助漏洞研究人员和工程师快速找到反汇编代码中的差异和相似之处。使用BinDiff,你可以识别并隔离供应商提供的修补程序中的漏洞修复程序。你还可以在同一二进制文件的多个版本的反汇编之间借用符号和注释,或使用BinDiff收集代码被盗或专利侵权的证据。

以下,我会对它们是否开源 、是否支持大型二进制文件、是否有源代码辅助、是否标识版本等方面进行比较。

16.png

对比结果表