Bento是一个用于构建模块化Android用户界面的开源框架,由Yelp研发。在过去的一年中,研发人员已经看到了在最关键的模块化UI开发上使用Bento可以提高开发人员的工作效率和产品设计的灵活性。在这篇文章中,研发人员将详细解释Bento的工作原理,以及Yelp开源Bento Android框架,用于模块化UI开发的内幕。

什么是Bento?

Bento桌面的信息以卡片的形式呈现给用户,从未接来电到截图提醒,从音乐播放到日历行程。获得授权后,Bento会嵌入联系人、日历、Email、浏览搜索记录和音乐播放记录等,记住用户的一些基本信息和个人喜好,在下一次用户进行某些动作的时候,直接向云端服务器发送数据然后予以相应反馈,用户甚至不需要专门下载相应的 App 。Bento的另一大亮点是,这款第三方 Launcher 还带有推荐功能。从用户浏览喜好或潜在需求出发,提供推荐类服务的应用有很多,比如Google Now(它是谷歌在I/O开发者大会上随安卓4.1系统同时推出的一款应用,它会全面了解你的各种习惯和正在进行的动作,并利用它所了解的来为你提供相关信息)。但与它们不同的是,Bento所有服务都是在定制化的手机桌面完成的,这种桌面式的功能触发器直接决定了用户使用手机的方式。

所以,Yelp的开发人员用Bento形象地命令了这个框架。 Bento的字面意思是便当的意思,顾名思义,Bento框架就是要随时方便用户。Bento盒顾名思义就是一个装有各种分隔装置的容器,用于将不同的“美味”彼此分开。

许多Android应用程序都有一个基于列表的用户界面,使用RecyclerView来显示程序的视图。RecyclerView是support:recyclerview-v7中提供的控件,最低兼容到android 3.0版本。RecyclerView是一种类似于ListView的一个滑动列表,但是RecyclerView和ListView相比,RecyclerView比ListView更好,RecyclerView支持横向滑动,RecyclerView没有点击事件,需要自己加入,还可以做出各种炫酷的效果动画,更符合高内聚低耦合。

在底层级别上,RecyclerView通过引用有序的数据列表并在屏幕上为该列表中的每个数据项创建视图来工作。如果你的列表由同质数据类组成,那么这种方法非常有效。但是当你需要在列表中管理无限数量的数据和视图类时,这种方法的弱点就暴露出来了。如果你需要在除RecyclerView之外的用户界面中使用相同的视图类(例如ViewPager或ListView),这种方法也会不好用。

Bento被设计出来的目的,就是旨在通过提供一个框架来管理处理不同视图类的复杂性以及列表中每个视图的动态位置来解决以上出现的问题。 Bento还可用于管理其他父视图类(例如ViewPagers和ListViews)中的视图,但同时Bento也保留了RecyclerView的优势(如ViewHolder和视图回收)。

Bento如何运行?

Bento将不同的视图类和与显示这些视图类并与之交互的逻辑分组到“组件”中,组件可以是从简单文本视图到由其他组件组成复杂组件。

组件的核心是提供数据项的独立元素,关联的ComponentViewHolder类将使视图不断inflate化并将提供的数据项绑定到复杂的视图中。ViewHolder通常还会将组件或某些presenter绑定到视图,以处理任何用户交互。

为了演示组件的工作原理,我会在下面列出+要显示在屏幕上的组件的数据流图表。

1.jpg

1.首先,底层页面视图需要显示内容,因此它要求ComponentController呈现一个视图。

2.ComponentController需要将更新的视图返回到底层页面视图,因此,基于控制器维护的内部组件列表,它通过在页面视图需要的位置对列表中的组件调用getHolderType来创建一个新的ComponentViewHolder。此方法会返回ComponentViewHolder类,然后通过反射实例化该类。

3.由于这是组件第一次创建视图,因此需要对布局进行inflate化处理。 为此,ComponentController需要在新创建的ComponentViewHolder上调用inflate方法来创建视图。

4.接下来,研发人员需要使用数据填充视图,并确保它将对用户输入作出响应。在创建的ComponentViewHolder实例上调用bind方法,该方法提供了一个数据项和一个presenter。它们是通过调用相应组件的getPresenter和getItem方法的ComponentController生成的。presenter是执行某些业务逻辑或处理用户交互的任何对象。在许多情况下,它是Component类本身。数据项通常是一个数据类,其中包含要显示给用户的视图属性和字符串。

5.视图将使用数据项进行更新,并且事件侦听器将绑定到presenter,然后将视图传递回要呈现的底层页面视图。

组件在其父视图中的顺序由ComponentController决定,而这正是Bento盒中的神奇之处,它允许研发人员动态地向订单添加、删除和插入组件,这就像在一个简单的列表数据结构中操作一样。它还提供了一个抽象化操作,研发人员可以使用它来将此功能应用于不同的视图类,例如RecyclerView,ListView,ViewPager和其他许多其他类。例如,recoverclerviewcomponentcontroller处理与recoverclerview类和适配器通信的复杂编排,以确定跨度和位置,使得在列表中管理不同的组件集变得非常简单。研发人员还可以使用ComponentGroup创建不同组件的分组,以将组件的逻辑分组保存在列表中。

Bento框架可以轻松地将复杂的界面分解为一组易于理解、模块化、动态和可测试的组件。

具体示例

让研发人员看一个如何构建一个只呈现一些文本的底层组件的示例,下面是一个非常简单的Component类的示例:

class ExampleComponent(private val text: String): Component() {
    override fun getCount() = 1
    override fun getPresenter(position: Int) = this
    override fun getItem(position: Int) = text
    override fun getHolderType(position: Int) = ExampleViewHolder::class.java
}

在上图中,你可以看到其中已经覆盖了抽象组件类的一些方法。让研发人员来解读一下其中的每个方法:

1.getCount:组件可以在内部由一系列项组成,在以上的简单示例中,其中就只有一个项目。如果需要,每个位置的组件中的每个项目都可以拥有自己的presenter、数据项和ViewHolder类,但通常最好将其分解为不同的组件,除非所有项目都具有相同的ViewHolder和presenter。

2.getPresenter:presenter是组件的大脑,它知道如何响应用户交互并执行其他复杂的活动。在某种程度上,每个Bento组件都是它自己的MVP生态系统,其中组件是Presenter,ComponentViewHolder是视图,数据项是模型。

3.getItem:该项是与指定位置的组件关联的数据,在本文的示例中,数据就是研发人员想要显示的文本。

4.getHolderType:持有者类是由Bento框架通过反射实例化的类,它负责扩展组件的布局并将数据项绑定到视图。来看看本示例中的ExampleViewHolder类:

class ExampleViewHolder: ComponentViewHolder<ExampleComponent, String>() {
    private lateinit var textView: TextView
    override fun inflate(parent: ViewGroup) =
            parent.inflate<TextView>(R.layout.example_component_layout)
            .also { textView = it }
    override fun bind(presenter: ExampleComponent, element: String) {
        textView.text = element
    }
}

就像研发人员在使用RecyclerViews时看到的ViewHolder模式一样,Bento的ViewHolder可以按着inflate和bind方法进行不同的划分。

inflate :它的作用就是扩展已有的布局文件,它的根目录只包含一个简单的TextView元素。然后研发人员返回经过扩展的视图,并在textView中存储对它的引用,以便稍后在绑定数据时使用它。

Bind:只要组件中的项目准备好显示在屏幕上,就会调用此方法。对于组件类中的getCount定义的每个项,它都会调用一次。 bind方法提供对presenter的引用,以及此ViewHolder所代表的组件中位置的相应数据项。换句话说,presenter参数是从某个位置i调用getPresenter(i)获得的,元素参数也是从调用getItem(i)得到的相同位置i获得的。

注意:绑定方法通常是在视图在列表中循环使用时调用,因此性能应该是此方法的高优先级。

现在研发人员就有一个组件和一个ComponentViewHolder,它将获取一些字符串并将其绑定到TextView以显示用户。那么研发人员如何实际使用该组件呢?只需创建一个ComponentController来组织所有组件即可。对于此示例,研发人员将使用简单的RecyclerViewComponentController。以下是一个示例活动:

class ExampleActivity: AppCompatActivity() {
    private val componentController by lazy {
        RecyclerViewComponentController(recyclerView)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)
        componentController.addComponent(ExampleComponent("Hello World!"))
    }
}

在示例中,研发人员创建了一个常规的活动,其内容视图布局只是一个reclerView,其id为recyclerView。在第一次引用ComponentController时,研发人员只是按着一般的情况初始化它,并通过传递recoverclerview的实例来创建它。之后,研发人员调用addComponent并引入一个ExampleComponent的新实例。通过以上的一番操作,一个Hello World文本字符串便会显示出来。以下是应用程序最后呈现出的样子:

5.png

这证明了, Bento也有帮助类来避免样板代码的功能。本示例组件类实际上非常简单,因此研发人员可以将它编写为SimpleComponent:

class SimpleExampleComponent(
    private val text: String
): SimpleComponent<Nothing>(ExampleViewHolder::class.java) {
    override fun getItem(position: Int) = text
}

它仍然使用研发人员之前的ExampleViewHolder,但现在研发人员不需要担心计数或presenter,并且在超级构造函数中指定了ViewHolder类。 Bento还包含许多组件的变体,包括用于重复视图的ListComponent,用于延迟加载的PaginatingListComponent,甚至用于可以水平滚动的组件集合的CarouselComponent。

具体表现特征

模块化

从上面的示例可以看出,组件是非常模块化的。该组件作为一个内聚度很高的整体存在,不依赖于它所插入的环境。这样做有很多好处,下面我会具体介绍。

可测试性

由于组件不依赖于Android框架,因此很容易对其逻辑进行单元测试。使用ComponentViewHolderTestCase测试ComponentViewHolder的逻辑也很容易,ComponentViewHolderTestCase会扩展视图并将其注入到绑定数据的测试活动中。然后,Espresso可以检查所有内容是否正确显示,并在用户交互期间在presenter上调用正确的方法。从集成测试的角度来看,bento-testing模块提供了一些BentoInteraction来测试用户所看到的整个组件屏幕。

可重复使用性

为一个环境创建的组件可以在使用任何类的ComponentController的许多不同屏幕上重用,这意味着可以轻松地将相同的组件放入RecyclerView,ListView或ViewPager中。

集成性

Bento会被逐步嵌入到现有的应用程序中,因此以后你无需从头开始重写应用程序或重新考虑整个应用程序架构。早在一年前,研发人员就已经将它集成到Yelp消费者和企业所有者应用程序中了。例如,在消费者应用程序的附近屏幕上,标题下方的所有内容(下面以红色标出)都是Bento组件。

7.png

研发人员还集成了一些工具,使现有应用程序的转换更加轻松。例如,对于那些仍然使用ListViews的用户,可以使用ListAdapterComponent将现有列表包装到其自己的组件中,并开始将列表中的项目转换为各自独立的组件。

可扩展性

从技术和组织的角度来看,Bento都是可扩展的。使用Bento的项目可以拥有的组件数量没有限制。由于可以很容易地将组件彼此隔离,因此也可以轻松将它们跨组件隔离,从而缩短构建所需的时间。由于研发人员可以使用RecyclerViews作为Bento的支持视图,因此在使用大量异构组件时也非常高效。更多模块化用户界面组件也意味着研发人员可以将组件的所有权分配给特定团队进行维护。由于许多不同团队可以在同一屏幕上同时拥有组件,这意味着软件维护的任务可以更均匀地分布,并且更容易对出现的漏洞进行分类。

低耗能性

Bento不使用类注释,因为不需要进行注释处理,这意味着编译时的耗能非常低。此外,由于Bento只包含少数几个类,因此它的存储占用空间非常小,aar库文件只有105.6 KB。

总结

Yelp开源Bento Android框架,用于模块化UI开发后,可以帮助Android研发人员高效地进行开发,并使开发后的产品高效可靠地执行新功能。但Bento仍然需要持续改进,主要集中在性能方面。研发人员目前没有异步布局扩展,其中每个布局都是从主UI线程扩张出来的。在通知ComponentGroup中的更改时,研发人员也没有进行优化。Bento Android的一部分仍然用Java编写,而其他部分用Kotlin编写。

RISC-V

RISC-V(读作“RISC-FIVE”)是一个基于精简指令集(RISC)原则的开源指令集架构(ISA)。与大多数指令集相比,RISC-V指令集可以自由地用于任何目的,允许任何人设计、制造和销售RISC-V芯片和软件。虽然这不是第一个开源指令集,但它具有重要意义,因为其设计使其适用于现代计算设备(如仓库规模云计算机、高端移动电话和微小嵌入式系统)。设计者考虑到了这些用途中的性能与功率效率。该指令集还具有众多支持的软件,这解决了新指令集通常的弱点。

RISC-V 的特点有:

1.完全开源

对指令集使用,RISC-V基金会不收取高额的授权费。开源采用宽松的BSD协议,企业完全自有免费使用,同时也容许企业添加自有指令集拓展而不必开放共享以实现差异化发展。

2.架构简单

RISC-V架构秉承简单的设计哲学。体现为:

在处理器领域,主流的架构为x86与ARM架构。x86与ARM架构的发展的过程也伴随了现代处理器架构技术的不断发展成熟,但作为商用的架构,为了能够保持架构的向后兼容性,其不得不保留许多过时的定义,导致其指令数目多,指令冗余严重,文档数量庞大,所以要在这些架构上开发新的操作系统或者直接开发应用门槛很高。而RISC-V架构则能完全抛弃包袱,借助计算机体系结构经过多年的发展已经成为比较成熟的技术的优势,从轻上路。RISC-V基础指令集则只有40多条,加上其他的模块化扩展指令总共几十条指令。 RISC-V的规范文档仅有145页,而“特权架构文档”的篇幅也仅为91页。

3.易于移植*nix

现代操作系统都做了特权级指令和用户级指令的分离,特权指令只能操作系统调用,而用户级指令才能在用户模式调用,保障操作系统的稳定。RISC-V提供了特权级指令和用户级指令,同时提供了详细的RISC-V特权级指令规范和RISC-V用户级指令规范的详细信息,使开发者能非常方便的移植linux和unix系统到RISC-V平台。

4.模块化设计

RISC-V架构不仅短小精悍,而且其不同的部分还能以模块化的方式组织在一起,从而试图通过一套统一的架构满足各种不同的应用场景。用户能够灵活选择不同的模块组合,来实现自己定制化设备的需要,比如针对于小面积低功耗嵌入式场景,用户可以选择RV32IC组合的指令集,仅使用Machine Mode(计算机模式);而高性能应用操作系统场景则可以选择譬如RV32IMFDC的指令集,使用Machine Mode(计算机模式)与User Mode(用户模式)两种模式。

5.完整的工具链

对于设计CPU来说,工具链是软件开发人员和cpu交互的窗口,没有工具链,对软件开发人员开发软件要求很高,甚至软件开发者无法让cpu工作起来。在cpu设计中,工具链的开发是一个需要巨大工作的。如果用RISC-V来设计芯片,芯片设计公司不再担心工具链问题,只需专注于芯片设计,RISC-V社区已经提供了完整的工具链,并且RISC-V基金会持续维护该工具链。当前RISC-V的支持已经合并到主要的工具中,比如编译工具链gcc, 仿真工具qemu等。

6.开源实现

加入RISC-V阵营更多是因为商业因素

目前,伯克利研究团队已经完成了基于RISC-V指令集的顺序执行的64位处理器核心(代号为Rocket),并前后基于45nm与28nm工艺进行了12次流片。Rocket芯片主频1GHz,与ARM Cortex-A5相比,实测性能较之高10%,面积效率高49%,单位频率动态功耗仅为Cortex-A5的43%。在嵌入式领域,Rocket已经可以和ARM争市场了。目前全球上百家科技公司加入RISC-V阵营,更多的还是因为商业原因。虽然在很多报道中,都将ARM的成功归咎于开放,但实际上,ARM只是在商业模式上开放,在技术上是卡的很死的。

迄今为止,获得ARM32授权的公司一只手都能数的过来,ARM64授权虽然多一些,但授权费却异常昂贵,法国创业公司Greenwave表示,他们如果使用ARM架构,要花掉1500万美元的授权费。而且授权到期后,是否继续授权和授权费用都要重新谈判。

虽然对高通、苹果、华为这些大公司而言,上亿元人民币的授权费不算什么,但对创业公司来说,这是不可承受之重。

为了深入挖掘RISC-V,我们应该研究一些不同类型的指令集架构。

目前的指令集架构有哪些类型?

最常见的是通过它们的复杂性来描述和分类:

精简指令集(RISC)和复杂指令系统计算机(CISC)

这仅实现了经常使用的指令,不常见的操作则会以子程序实现。通过使用子程序,可以权衡性能,但它仅适用于最不常见的操作。

RISC使用加载或存储架构,这意味着它将指令划分为访问存储器的指令和执行算术逻辑单元(ALU)操作的指令。

RISC的英文全称是Reduced Instruction Set Computer,中文是精简指令集。特点是所有指令的格式都是一致的,所有指令的指令周期也是相同的,并且采用流水线技术。在中高档服务器中采用RISC指令的CPU主要有Compaq(康柏,即新惠普)公司的Alpha、HP公司的PA-RISC、IBM公司的PowerPC、MIPS公司的MIPS和SUN公司的Sparc。

在计算机指令系统的优化发展过程中,出现过两个截然不同的优化方向:CISC技术和RISC技术。CISC是指复杂指令系统计算机(Complex Instruction Set Computer);RISC是指精简指令系统计算机(Reduced Instruction Set Computer)。这里的计算机指令系统指的是计算机的最低层的计算机指令,也就是CPU能够直接识别的指令。随着计算机系统的复杂,要求计算机指令系统的构造能使计算机的整体性能更快更稳定。最初,人们采用的优化方法是通过设置一些功能复杂的指令,把一些原来由软件实现的、常用的功能改用硬件的指令系统实现,以此来提高计算机的执行速度,这种计算机系统就被称为复杂指令系统计算机,即Complex Instruction Set Computer,简称CISC。另一种优化方法是在20世纪80年代才发展起来的,其基本思想是尽量简化计算机指令功能,只保留那些功能简单、能在一个节拍内执行完成的指令,而把较复杂的功能用一段子程序来实现,这种计算机系统就被称为精简指令系统计算机.即Reduced Instruction Set Computer,简称RISC。RISC技术的精华就是通过简化计算机指令功能,使指令的平均执行周期减少,从而提高计算机的工作主频,同时大量使用通用寄存器来提高子程序执行的速度。

RISC和CISC是设计制造微处理器的两种典型技术,虽然它们都是试图在体系结构、操作运行、软件硬件、编译时间和运行时间等诸多因素中做出某种平衡,以求达到高效的目的,但采用的方法不同,因此,在很多方面差异很大,它们主要有:

1.指令系统:RISC设计者把主要精力放在那些经常使用的指令上,尽量使它们具有简单高效。对不常用的功能,常通过组合指令来完成。因此,在RISC计算机上实现特殊功能时,效率可能较低。但可以利用流水技术和超标量技术加以改进和弥补。而CISC计算机的指令系统比较丰富,有专用指令来完成特定的功能。因此,处理特殊任务效率较高。

2.存储器操作:RISC对存储器操作有限制,使控制简单化;而CISC计算机的存储器操作指令多,操作直接。

3.程序:CISC汇编语言程序一般需要较大的内存空间,实现特殊功能时程序复杂,不易设计;而RISC汇编语言程序编程相对简单,科学计算及复杂操作的程序设计相对容易,效率较高。

4.中断:RISC计算机在一条指令执行的适当地方可以响应中断,但是相比CISC指令执行的时间短,所以中断响应及时;而CISC计算机是在一条指令执行结束后响应中断。

5.CPU:RISC CPU包含有较少的单元电路,因而面积小、功耗低;而CISCCPU包含有丰富的电路单元,因而功能强、面积大、功耗大。

6.设计周期:RISC微处理器结构简单,布局紧凑,设计周期短,且易于采用最新技术;CISC微处理器结构复杂,设计周期长。

7.用户使用:RISC微处理器结构简单,指令规整,性能容易把握,易学易用;CISC微处理器结构复杂,功能强大,实现特殊功能容易。

8.应用范围:由于CISC指令系统的确定与特定的应用领域有关,故CISC计算机更适合于专用机;而RISC计算机则更适合于通用机。

超长指令字(VLIW)和显式并行指令计算(EPIC)

VLIW是一种非常长的指令组合,它把许多条指令连在一起,增加了运算的速度。 超长指令字(VLIW)是指令级并行,超线程(Hyper-Threading)是线程级并行,而多内核则是芯片级并行。这三种方式都是提高并行计算性能的有效途径。其中,VLIW(超长指令字)体系结构是美国Multiflow和Cydrome公司于20世纪80年代设计的体系结构,EPIC体系结构就是从VLIW中衍生出来的。

上述方法通过要求硬件执行所有这些逻辑来使硬件复杂化。相比之下,VLIW将这种复杂性留给了程序。作为权衡,编译器变得更加复杂,同时硬件被简化并且仍然在计算上表现良好。

VLIW最常见于嵌入式媒体处理器和图形处理单元(GPU)中。但是,Nvidia和AMD已经转向RISC架构,以提高非图形工作负载的性能。

为了开拓64位处理器的高端应用市场,1994年6月Intel和HP公司签署合作协议,共同开发以服务器和T作站为主要应用目标的全新64位架构高性能微处理器。1997年11月,Intel和HP公司发布基于EPIC(Explicitly Parallel Instruction Computing,显式并行指令计算)的Itanium系统结构。

超标量和VLIW是开发指令级并行的两种极端结构,前者完全依赖流水线硬件动态识别出可并行的指令,并将它们分发给相应的功能单元执行,后者则将指令级并行的开发工作全部交给编译器完成,在编译时静态确定每条指令的流出时刻和执行延迟,仅依赖简单的流水线硬件确保在指令实际执行延迟与编译器假定的延迟不一致时(如访问Cache不命中就会增加访存操作的延迟),程序的执行结果依然正确。在这两种结构中,单一的指令级并行开发机制使得它们都存在着严重的固有缺陷:超标量结构硬件复杂度太高,学术界和工业界一致认为,同时流出并执行8条指令将达到这种结构的极限;VLIW则面临着严重的代码兼容问题,而且目前VLIW编译器的智能程度远远无法满足人们的要求。显式并行指令计算(Explicitly Parallel Instruction Computing,EPIC)技术正是为了解决这两种结构的本质缺陷而提出的,它是在VLIW的基础上融合了超标量结构的一些优点而设计得到的,以期用有限的硬件开销为代价开发出更多的指令级并行。

最小指令集(MISC)

MISC比RISC更小,它包括非常少量的基本操作和相应的操作码。如果它们是基于堆栈而不是基于寄存器,则这些被归类为MISC,但是也可以由指令的数量(少于32条但大于1条)来定义。

单一指令集(OISC)

这描述了一个只使用一条指令的抽象计算机,它消除了对计算机语言操作码的需求。例如,“mov”是图灵完成,这意味着它能够成为一个OISC,以及其他使用减法的指令。据我所知,OISC还没有商业化。

在很多媒体的报道中,定义一套指令集被描述为黑科技,但事实上,实现一款高性能的CPU和围绕指令集构建软件生态才是真正具有挑战性的工作,定义一套指令集的难度并不大,伯克利的研究团队4名成员仅用了3个月就完成了RISC-V的指令集开发。由于伯克利研究团队缺人缺钱,于是决定将RISC-V开源,成为CPU中的Linux,在获得各方支援后,又相继开发了一些产品。

(接上文)

缓冲区覆盖的程序序列

在没有枚举MD_CLEAR功能的处理器上,某些指令序列可用于覆盖受MDS影响的缓冲区。你可以点此,详细查看这些序列。

不同的处理器可能需要不同的序列来覆盖受MDS影响的缓冲区,程序序列的一些要求如下:

1.在支持同步多线程(SMT)的处理器上,同一物理内核上的其他线程应在序列期间暂停,这样它们就不会分配填充缓冲区。这允许当前线程覆盖所有填充缓冲区,特别是,这些暂停的线程不应执行可能错过L1D缓存的任何加载或存储。暂停线程应该对PAUSE指令进行循环,以限制序列期间的交叉线程干扰。

2.对于依赖REP字符串指令的序列,MSR位IA32_MISC_ENABLES [0]必须设置为1,以便启用快速字符串。

缓冲区何时会被覆盖

无论何时切换到以前不信任的程序,都应覆盖存储缓冲区、填充缓冲区和加载端口。如果程序确保任何这些缓冲区中都不存在受保护的数据,则可以避免缓冲区被覆盖。

OS

当从ring0转换到ring3时,OS可以执行VERW指令来覆盖受影响缓冲区中的任何受保护数据。这将覆盖可能属于内核或其他应用程序的缓冲区中的受保护数据,当SMT处于活动状态时,也应在进入C状态之前以及退出C状态和转换为不可信代码之间执行此指令。

Intel ®程序保护扩展(Intel ®SGX)

当进入或退出Intel ®程序保护扩展(Intel ®SGX)安全区(enclave)时,枚举对MD_CLEAR支持的处理器将自动覆盖受影响的数据缓冲区。

虚拟机管理器(VMM)

VMM可以在进入用户VM之前执行VERW指令或L1D_FLUSH命令,这将覆盖可能属于VMM或其他vm的缓冲区中的受保护数据。除了加载枚举MD_CLEAR的微代码更新,在进入用户虚拟机以缓解L1TF之前已经使用L1D_FLUSH命令的VMM可能不需要进行更多更新。

虽然VMM可能只在一个线程上发出L1D_FLUSH来刷新L1D中的数据、填充缓冲区和内核中的所有线程加载端口,但只清除当前线程的存储缓冲区。当另一个线程接下来进入用户端时,可能需要VERW来覆盖属于另一个线程的存储缓冲区。

系统管理模式(SMM)

将系统管理模式(SMM)数据暴露给随后在相同逻辑处理器上进入的程序,可以通过在退出SMM时覆盖缓冲区来缓解这种情况。在枚举MD_CLEAR2的处理器上,处理器将在执行RSM指令时自动覆盖受影响的缓冲区。

进程内的安全域

使用基于语言的安全性的程序可以在不同的信任域之间进行转换,在信任域之间进行转换时,可以使用VERW指令清除缓冲区。

如Deep Dive:Managed Runtime Speculative Execution Side Channel Mitigations 中所讨论的,站点隔离可能是一种更有效的技术,通常用于处理推测执行端通道。

使用同步多线程(SMT)的环境进行缓解

OS

操作系统必须使用两种不同的方法来防止线程使用MDS来推断兄弟线程使用的数据值,第一个(组调度)可防止用户对用户的攻击。当一个线程在另一个线程上以用户模式进入的攻击者执行内核代码时,第二个(同步条目)可以保护内核数据免受攻击。

组调度

当前线程跨越安全域时,操作系统可以防止兄弟线程进入恶意代码。 OS调度程序可以通过确保共享相同物理内核的程序工作载荷彼此相互信任(例如,如果它们位于相同的应用程序定义的安全域中)或确保其他线程处于空闲状态来缓解对兄弟线程的控制。

操作系统可以静态地(例如,通过任务关联或cpuset)在工作加载之间强制执行这种信任关系,或者通过OS中的组调度程序(有时称为内核调度程序)动态地强制执行。组调度程序应该优先选择兄弟内核上具有相同信任域的进程,但前提是没有其他空闲内核可用。这可能会影响内核之间的加载平衡决策。如果来自兼容信任域的进程不可用,则调度程序可能需要使兄弟线程空闲下来。

1.png

没有组调度的系统

2.png

具有组调度的系统

图1显示了一个三核系统,其中Core 2进入来自不同安全域的进程。这些进程将能够使用MDS推断出受保护的数据。图2显示了组调度程序如何通过确保没有内核同时进入来自不同安全域的进程,以缓解进程到进程攻击的可能性。

使用IPI同步ring 0的进入和退出

当当前硬件线程从用户代码(应用程序代码)转换到内核代码(ring0模式)时,OS需要采取行动。这可能是系统调用或中断异步事的一部分,因此可能不允许兄弟线程在用户模式下执行,因为内核代码可能不信任用户代码。在操作系统的简化视图中,我们可以认为每个线程处于以下三种状态之一:

1.闲置状态;

2.Ring 0(内核代码)状态;

3.用户(应用程序代码)状态;

下图显示了保持内核安全情况下,不受恶意应用程序攻击的状态转换。

3.png

线程交会图

上图中的每个节点都显示了共享一个物理内核的两个线程的可能执行状态,从状态1开始,两个线程都处于空闲状态。依据该状态,中断将内核转换到状态2a或2b,具体取决于哪个线程被中断。如果没有要进入的用户任务,则物理内核在完成中断后转换回状态1。如果空闲状态是使用处理器c状态实现的,那么应该在进入受MSBDS影响的处理器的c状态之前执行VERW。

从2a或2b开始,线程可以开始进入用户进程。只要内核上的其他线程保持空闲状态,当从2a转换到3a或2b转换到3b时,就不需要SMT特定的缓解,尽管OS需要通过在转换到3a或3b之前执行VERW来覆盖缓冲区。或者,从2a或2b开始,如果中断唤醒的兄弟线程,则物理内核可以转换到状态4。如果该中断不会导致内核进入用户进程,则物理内核可能会返回2a或2b。

从状态4开始,内核可以转换到状态5并开始在两个线程上执行用户代码。此时操作系统必须确保转换到状态5,以防止第一个输入用户代码的线程对另一个线程的微体系结构缓冲区中的受保护数据执行攻击。操作系统还应该在两个线程上执行VERW,由于内核和用户状态之间的两个线程转换都没有硬件支持,因此操作系统应该使用标准程序技术来同步线程。操作系统还应注意边界,以避免在一个或两个线程转换到用户模式时将受保护的数据加载到微体系结构缓冲区中。注意,内核只应该在进入来自相同安全域的两个用户线程时进入状态5。

内核可以从状态5进入状态6a或6b,因为其中一个线程离开用户模式或者通过状态3a或3b进入。此时,中断将线程从空闲状态唤醒。当处于状态6a或6b时,操作系统应该避免访问任何被认为与用户模式中的兄弟线程相关的受保护的数据。如果处于内核状态的线程需要访问受保护的数据,操作系统应该从状态6a或6b转换到状态4。处于内核状态的线程应该使用处理器间中断(interprocessor interrupt, IPI)来处理内核状态中的两个线程,以便将内核转换为状态4。当内核线程通过进入空闲状态或返回到用户状态,退出内核状态时,可以允许兄弟线程退出IPI服务例程,并在执行VERW之后返回到用户状态本身进入。

禁用同步多线程(SMT)

防止同级线程通过MDS推断数据值的另一种方法是通过BIOS禁用SMT,或者让OS只在其中一个线程上调度工作。

针对Atom和Knight系列处理器的SMT缓解措施

某些受MDS影响的处理器(MDS_NO为0)不需要缓解其他兄弟线程,具体而言,任何不支持SMT的处理器(例如,基于Silvermont和Airmont微体系结构的处理器)都不需要SMT缓解。

基于Knights Landing或Knights Mill微体系结构的处理器不需要组调度或同步退出/进入来缓解来自兄弟线程的MDS攻击,这是因为这些处理器仅受MSBDS的影响,并且存储数据缓冲区仅在进入/退出C状态时,在线程之间共享。在这样的处理器上,应该在进入时覆盖存储缓冲区,以及在退出c状态和过渡到不可信代码之间覆盖存储缓冲区。每个内核中只有四个线程的处理器受到MDS的影响(不要枚举MDS_NO),它们是Knights系列处理器。

虚拟机管理器(VMM)

MDS的缓解与缓解L1TF所需的缓解相似,枚举MDS_CLEAR的处理器增强了L1D_FLUSH命令4,也覆盖了受MDS影响的微体系结构结构。这可以允许VMM通过组调度和使用L1D_FLUSH命令缓解L1TF的影响,从而缓解MDS的影响。VMM缓解可能需要应用于不受L1TF(设置了RDCL_NO)影响但受MDS (MDS_NO是clear)影响的处理器。在这样的处理器上,VMM可以使用VERW而不是L1D_FLUSH命令,使用程序序列实现L1D刷新的VMM应该使用VERW指令覆盖受MDS影响的微体系结构结构。

请注意,即使VMM仅在一个线程上发出L1D_FLUSH来刷新内核中所有线程的数据,也会为当前线程清除存储缓冲区。当下一个线程进入用户端时,可能需要VERW来覆盖属于该线程的存储缓冲区。

Intel ®SGX

Intel SGX安全模型不信任操作系统调度程序,通过这种方式来确保在兄弟线程上进入的程序工作加载彼此信任。对于受跨线程MDS影响的处理器,Intel SGX远程认证反映了BIOS是否启用了SMT。Intel SGX远程认证验证程序可以评估在平台上启用SMT时潜在的跨线程攻击的风险,并决定是否信任平台上的安全区以保护特定的受保护数据。

SMM

SMM是BIOS使用的一种特殊的处理器模式,枚举MD_CLEAR并受MDS影响的处理器将在退出SMM的RSM指令期间自动刷新受影响的微体系结构结构。

SMM程序必须在进入和退出SMM时对所有逻辑处理器进行交合,以确保兄弟逻辑处理器在自动刷新后不会将数据重新加载到微体系结构结构中。目前,大多数SMM程序已经做到了这一点。这确保了在属于SMM的数据位于微体系结构结构中时,非SMM程序不会进入,这种SMM实现不需要对MDS进行任何程序更改。

CPUID枚举

CPUID.(EAX=7H,ECX=0):EDX[MD_CLEAR=10] 列举了对附加功能的支持,这些功能将刷新下面列出的微体系结构。

1.执行(现有的)VERW指令时,其参数是一个内存操作数值;

2.在IA32_FLUSH_CMD MSR中设置L1D_FLUSH命令位;

3.执行RSM指令;

4.进入或退出Intel SGX安全区。

用于覆盖缓冲区的程序序列

对于没有枚举MD_CLEAR功能的处理器,可以使用以下指令序列覆盖受MDS影响的缓冲区。在枚举MD_CLEAR的处理器上,应该使用VERW指令或L1D_FLUSH command4来代替这些程序序列。

程序序列在每个处理器模型上使用最流行的可用内存操作办法,以确保覆盖所有的高阶位。在这些序列中间发生的系统管理中断(SMI)、中断或异常可能导致执行较小的内存访问,而这些访问只覆盖缓冲区的较低位。在这种情况下,当序列完成时,一些缓冲区条目可能被覆盖两次,而只有其他缓冲区条目的低位被覆盖。其中一些序列使用%xmm0覆盖微体系结构缓冲区,可以安全地假设这个值不包含受保护的数据,因为Intel在返回用户模式(可以直接访问%xmm0)之前执行这个序列。虽然覆盖操作通过MDS漏洞使兄弟线程可以看到%xmm0值,但Intel假设组调度确保兄弟线程上的进程在返回用户模式的线程上被进程信任。

请注意,在虚拟化环境中,VMM可能无法为用户操作系统提供有关正在使用的实际物理处理器模型的真实信息。在这些环境中,Intel建议用户操作系统始终使用VERW。

Nehalem,Westmere,Sandy Bridge和Ivy Bridge

下面的序列可以覆盖处理器系列代码Nehalem、Westmere、Sandy Bridge或Ivy Bridge中受影响的数据缓冲区。

static inline void IVB_clear_buf(char *zero_ptr)
{
        __asm__ __volatile__ (
                "lfence                         \n\t"
                "orpd (%0), %%xmm0              \n\t"
                "orpd (%0), %%xmm1              \n\t"
                "mfence                         \n\t"
                "movl $40, %%ecx                \n\t"
                "addq $16, %0                   \n\t"
                "1: movntdq %%xmm0, (%0)        \n\t"
                "addq $16, %0                   \n\t"
                "decl %%ecx                     \n\t"
                "jnz 1b                         \n\t"
                "mfence                         \n\t"
                ::"r" (zero_ptr):"ecx","memory");
}

Haswell和Broadwell

以下序列可以基于Haswell或Broadwell微体系结构覆盖处理器的受影响数据缓冲区:

static inline void BDW_clear_buf(char *dst)
{
        __asm__ __volatile__ (
                "movq %0, %%rdi                 \n\t"
                "movq %0, %%rsi                 \n\t"
                "movl $40, %%ecx                \n\t"
                "1: movntdq %%xmm0, (%0)        \n\t"
                "addq $16, %0                   \n\t"
                "decl %%ecx                     \n\t"
                "jnz 1b                         \n\t"
                "mfence                         \n\t"
                "movl $1536, %%ecx              \n\t"
                "rep movsb                      \n\t"
                "lfence                         \n\t"
                ::"r" (dst):"eax", "ecx", "edi", "esi",
			"cc","memory");
}

Skylake,Kaby Lake和Coffee Lake

对于基于Skylake,Kaby Lake或Coffee Lake微体系结构的处理器,所需的序列取决于启用了哪些向量扩展。

void _do_skl_sse(char *dst, const __m128i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"orpd (%1), %%xmm0\n\t"
		"orpd (%1), %%xmm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory"
	);
}

如果处理器支持Intel AVX但不支持Intel AVX-512,则可以使用此Intel AVX序列。

void _do_skl_avx(char *dst, const __m256i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"vorpd (%1), %%ymm0, %%ymm0\n\t"
		"vorpd (%1), %%ymm0, %%ymm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory", "ymm0"
	);
}

如果处理器支持Intel AVX-512,则可以使用此序列。请注意,使用Intel AVX-512操作可能会影响处理器频率。使用VERW和MD_CLEAR支持不会影响处理器频率,因此建议使用。

void _do_skl_avx512(char *dst, const __m512i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"vorpd (%1), %%zmm0, %%zmm0\n\t"
		"vorpd (%1), %%zmm0, %%zmm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory", "zmm0"
	);
}

Atom(仅限Silvermont和Airmont)

以下序列可以基于Silvermont或Airmont微体系结构覆盖处理器的存储缓冲区,由于Silvermont和Airmont不支持SMT,因此在进入/退出C状态时可能不需要这些序列。

static inline void SLM_clear_sb(char *zero_ptr)
{
       __asm__ __volatile__ (
               "movl $16, %%ecx                \n\t"
               "1: movntdq %%xmm0, (%0)        \n\t"
               "addq $16, %0                   \n\t"
               "decl %%ecx                     \n\t"
               "jnz 1b                         \n\t"
               "mfence                         \n\t"
               ::"r" (zero_ptr):"ecx","memory");
}

Knights Landing和Knights Mill

以下程序序列可以覆盖基于Knights Landing和Knights Mill的处理器的存储缓冲区,当线程被唤醒或进入睡眠状态时,Knights系列处理器重新分区存储缓冲区。程序应该在线程进入休眠状态之前以及线程唤醒和执行不信任代码之间执行此序列。请注意,Knights系列处理器支持用户级MWAIT,当操作系统启用时,可以防止操作系统知道线程何时休眠/唤醒。

Knights程序序列只需要覆盖存储缓冲区,因此不需要线程集合。无论其他线程在做什么,它都可以运行。

void KNL_clear_sb(char *dst)
{
  __asm__ __volatile__ (
       "xorl	%%eax, %%eax\n\t"
       "movl	$16, %%ecx\n\t"
       "cld \n\t"
       "rep stosq\n\t"
       "movl	$128, %%ecx\n\t"
       "rep stosq\n\t"
       "mfence\n\t"
       : "+D" (dst)
       :: "eax", "ecx", "cc", "memory"
       );
}

背景了解

5月15号有媒体曝出,安全研究人员在在一个月之前在Intel 芯片中发现了一种被称为“ZombieLoad”的新漏洞,此漏洞可让攻击者获取当前处理器正在处理的敏感数据。

攻击者可以利用这个漏洞发起对Intel 芯片的侧信道攻击,这也是继此前Meltdown、Spectre与Foreshadow之后,最为严重的安全漏洞,研究员们在一个月之前向Intel 报告了这些漏洞。

“ZombieLoad”直接的理解就是“僵尸载荷”,即处理器无法理解或正确处理的大量数据,迫使处理器向处理器的微码请求帮助以防止崩溃。应用程序通常只能看到自己的数据,但是这个漏洞允许数据流过这些边界墙。研究人员表示,ZombieLoad将泄漏处理器内核当前加载的所有数据。这意味着,黑客利用的实际上是设计缺陷,而不是注入的恶意代码。

攻击方式

与此前三个侧信道攻击(Meltdown、Spectre与Foreshadow)方式类似,新的攻击方式也是利用处理器的推测执行过程中的漏洞。

此漏洞由此前参与Meltdown、Spectre漏洞研究的部分安全人员以及Bitdefender安全人员联合发现,其实际为微架构数据采样(MDS)攻击,可利用微体系结构中的推测执行操作推断出其他应用在处理器中处理的数据。

Intel 表示,ZombieLoad 包括 4 个漏洞。分别是针对存储缓冲区的攻击(CVE-2018-12126/Fallout)、加载缓冲区(CVE-2018-12127)、行填充缓冲区(CVE-2018-12130/Zombieload/RIDL)以及内存区(CVE-2019-11091)。其中Zombieload是严重性最高的,能够获取最大量且隐秘的数据。

影响范围

自2011年以来发布的所有Intel处理器很可能都会受到影响,尤其是云主机服务可能会受到较大的冲击。同时Intel也指出MDS攻击方式实际上利用难度较高,其实际影响不会有那么大。

漏洞修复

目前Intel已经发布了微代码更新,且新处理器不会受到影响。这其中包括英特尔至强、Broadwell、Sandy Bridge、Skylake 和 Haswell 等芯片型号。Kaby Lake、Coffee Lake、Whiskey Lake 和 Cascade Lake,以及所有的凌动和 Knights 处理器也都受到影响。

目前,苹果、微软和谷歌都已经分别发布补丁

英特尔对微结构数据采样的分析

微架构数据采样(MDS)的工作原理

MDS可以允许可以在本地执行系统上的代码的恶意用户推断受架构机制保护的数据,虽然使用漏洞“ZombieLoad”在系统上定位特定数据可能很困难,但恶意攻击者可以通过收集和分析大量数据来查找受保护数据。具体过程请参阅《深潜中的MDS表:CPUID枚举和体系结构MSR》,通过这种办法以获取可能受MDS影响的处理器列表。MDS只涉及与一级数据缓存(L1D)以外的微体系结构结构相关的方法,因此不包括异常数据缓存加载(RDCL)或L1终端故障(L1TF)。

MDS推测执行侧通道方法可用来泄露以下微架构结构中的数据:

1.存储缓冲区:用于保存存储地址和数据的临时缓冲区;

2.填充缓冲区:CPU缓存之间的临时缓冲区;

3.加载端口:将数据加载到寄存器时使用的临时缓冲区;

这些结构比L1D小得多,因此可以保存更少的数据,并且更频繁地被覆盖。使用MDS方法来推断与特定内存地址相关的数据也更加困难,这可能需要恶意攻击者收集大量数据并进行分析来查找任何受保护的数据。

新的微代码更新(MCUs)正计划被发布,以帮助程序缓解这些漏洞。Intel建议在切换到以前的程序不可信的程序时更新微代码并清除微架构缓冲区。这些缓解措施将需要对操作系统、虚拟机管理程序和Intel ®程序保护扩展(Intel ®SGX)进行更改和更新。

本文档中的微体系结构详细信息只适用于受MDS技术影响的处理器,而不是所有Intel 处理器的通用处理器。有关受影响的处理器列表,请参阅CPUID枚举和体系结构MSR。

微架构存储缓冲区数据采样(MSBDS)CVE-2018-12126

执行存储操作时,处理器将数据写入称为存储缓冲区的临时微体系结构。这使得处理器能够在将数据写入高速缓存或主内存之前继续执行存储操作之后的指令。另外, I / O写入(例如,OUT)也保存在存储缓冲区中。

当加载操作从与早期存储操作相同的内存地址读取数据时,处理器可以直接从存储缓冲区将数据转发给加载操作,而不是等待从内存或缓存加载数据,这种优化过程就被称为存储库加载转发(store-to-load forwarding)。

在某些条件下,来自存储操作的数据可以从存储缓冲区推测性地转发到针对不同内存地址的故障或辅助加载操作。由于存储的大小小于存储缓冲区宽度,或者尚未执行存储的数据部分,因此存储可能不会覆盖存储缓冲区内的整个数据字段。这些情况可能导致转发的数据包含来自以前存储的数据。由于加载操作将导一个fault/assist1并且其结果也将被丢弃,因此转发的数据不会导致漏洞的程序执行或架构状态更改。但是,恶意攻击者可能能够将这种仅用于推测的数据转发给一个开源的gadget框架(disclosure gadget ),从而允许他们推断出这个值。

MSBDS的跨线程影响

对于受MSBDS影响的处理器,物理内核上的存储数据缓冲区将在该内核上的活动线程上进行静态分区。这意味着具有两个活动线程的内核将有一半的存储缓冲区条目仅用于线程1,一半仅用于另一个线程。当线程进入休眠状态时,其存储缓冲区条目可能会被其他活动线程使用。这会导致以前用于进入睡眠状态的线程(并且可能包含过期数据)的存储缓冲区条目由其他(活动)线程重用。当线程从休眠状态被唤醒时,存储缓冲区将重新被分区。这会导致存储缓冲区将存储缓冲区条目从已经处于活动状态的线程传输到刚刚被唤醒的线程。

微架构填充缓冲数据采样(MFBDS)CVE-2018-12130

填充缓冲区是一个内部结构,用于收集第一级数据缓存丢失的数据。当内存请求丢失L1数据缓存时,处理器会分配一个填充缓冲区来管理对数据高速缓存行的请求。另外,填充缓冲区还会临时管理响应内存或由I / O操作返回或发送的数据。填充缓冲区可以将数据转发到加载操作,也可以将数据写入数据缓存。一旦来自填充缓冲区的数据被写入高速缓存,处理器就会释放填充缓冲区,从而允许在未来的内存操作中重用该条目。

填充缓冲区可能会保留以前内存请求中的过期数据,直到新的内存请求覆盖填充缓冲区。在某些条件下,填充缓冲区可以推测性地将数据(包括过期数据)转发到将导致故障的加载操作。不过,这不会导致程序执行漏洞,因为故障加载永远不会停止,因此不会修改架构状态。然而,开源的 gadget框架可能能够通过侧信道时序分析来推断转发的填充缓冲器条目中的数据。

MFBDS的跨线程影响

填充缓冲区在同一物理内核上的线程之间共享,无需任何分区。因为填充缓冲区是在兄弟线程

线程之间动态分配的,所以填充缓冲区中的过期数据可能属于另一个线程所进行的内存访问。例如,在兄弟线程(sibling thread)上执行不同应用程序的情况下,如果其中一个应用程序处于恶意攻击者的控制之下,则可能在特定条件下使用MFBDS来推断受害者的某些数据通过填充缓冲区的值。

微架构加载端口数据采样(MLPDS)CVE-2018-12127

处理器使用称为加载端口的微架构结构来从内存或I / O执行加载操作,在加载操作期间,加载端口从内存或I / O系统接收数据,然后将该数据提供给寄存器文件和更新的相关操作。在一些实现中,每个加载端口内的回写数据总线可以保留来自以前加载操作的数据值,直到较新的加载操作覆盖该数据为止。

在这些情况下,微架构加载端口数据采样(MLPDS)可以向恶意攻击者显示过期的加载端口数据:

1. 一个大于64位的故障或辅助(faulting/assisting)向量(SSE/Intel®AVX/Intel®AVX-512);

2.一个跨64字节边界的故障或辅助加载;

在这些情况下,故障/辅助加载操作推测性地提供从内部数据结构到较新的依赖操作的过期数据值。故障/辅助加载操作永远不会停止,因此不会修改架构状态。然而,接收过期数据的新依赖操作可以是开源的 gadget框架的一部分,该开源的 gadget框架可以向恶意攻击者泄露过期的数据。

MLPDS的跨线程影响

加载端口在同一物理内核上的线程之间共享,因为加载端口是在线程之间动态分配的,所以加载端口中的过期数据可能属于另一个线程进行的内存访问。例如,在兄弟线程上执行不同应用程序的情况下,如果其中一个应用程序受恶意攻击者控制,则可能在特定条件下使用MLPDS推断受害者的某些数据通过装载端口的值。

微架构数据采样不可缓存内存(MDSUM)CVE-2019-11091

使用不可缓存(uncacheable ,UC)内存类型的数据访问不会将新行填充到处理器高速缓存中,在受微架构数据采样不可访问内存(MDSUM)影响的处理器上,对不可访问内存进行故障或辅助的加载操作仍然可能从这些内核或数据访问中推测出数据。因为不可缓存的内存访问仍然通过存储缓冲区、填充缓冲区和加载端口移动数据,并且这些数据值可能在故障或辅助加载时被推测性地返回。因此,恶意攻击者可以通过上面讨论的MSBDS,MFBDS和MLPDS机制推测出这些数据值。

微架构数据采样漏洞的缓解措施

硬件缓解

有关受影响的处理器的完整列表,请参阅《深潜中的MDS表:CPUID枚举和体系结构MSR》

以下MSR枚举使程序能够检查处理器是否受MDS方法的影响:

值为1表示处理器不受RDCL或L1TF的影响。此外,值为1表示处理器不受MFBDS的影响。

· IA32_ARCH_CAPABILTIES [0]:RDCL_NO

· IA32_ARCH_CAPABILITIES [5]:MDS_NO

值为1时表示处理器不受MFBDS / MSBDS / MLPDS / MDSUM的影响。

请注意,如果设置了RDCL_NO或MDS_NO位(或两者都设置了),则可以缓解MFBDS。一些现有处理器也可能仅在加载微代码更新后枚举RDCL_NO或MDS_NO。

受影响的处理器的缓解措施

对于微体系结构数据采样漏洞的缓解措施包括在转换到可能权限较少的执行实体之前,例如,在操作系统(OS)执行IRET或SYSRET指令返回应用程序之前,清除存储缓冲区、填充缓冲区和加载端口。

有两种方法可以覆盖受MDS影响的微体系结构缓冲区:MD_CLEAR功能和程序序列。

处理器支持缓冲区覆盖(MD_CLEAR)

Intel 将发布微码更新和枚举MD_CLEAR功能的新处理器。在枚举MD_CLEAR的处理器上,应使用VERW指令或L1D_FLUSH命令使处理器覆盖受MDS影响的缓冲区值,因为这些指令优先于程序序列。

VERW指令和L1D_FLUSH命令将覆盖受MSBDS影响的处理器上当前逻辑处理器的存储缓冲区值,对于受MFBDS影响的处理器,这些指令将覆盖物理内核上所有逻辑处理器的填充缓冲区。对于受MLPDS影响的处理器,这些指令将覆盖物理内核上所有逻辑处理器的加载端口写回总线。受MDSUM影响的处理器也受MFBDS,MSBDS或MLPDS中的一个或多个影响。因此,如上所述覆盖缓冲区也将覆盖包含不可缓存数据的任何缓冲区条目。

VERW缓冲区覆盖细节

VERW指令已经定义为返回一个段是否可以从当前权限级别写入,MD_CLEAR枚举VERW的内存操作数变体(例如,VERW m16)已扩展到覆盖受MDS影响的缓冲区。

VERW的寄存器操作数变体不保证具有这种缓冲区覆盖功能,无论VERW权限检查的结果如何,以及当选择器为空或导致描述符加载段违例时,都会发生缓冲区覆盖。但是,对于最低延迟,Intel建议使用指示有效可写数据段的选择器。

示例用法:

MDS_buff_overwrite():
sub $8, %rsp
mov %ds, (%rsp)
verw (%rsp)
add $8, %rsp
ret

请注意,VERW指令更新EFLAGS寄存器中的ZF位,因此在现有代码中使用上述序列时请务必谨慎。还要注意,VERW指令不能在实际模式或虚拟8086模式下执行。

不管在同一物理内核上的另一个逻辑处理器上执行的是什么,添加到VERW的微代码将正确地覆盖逻辑处理器的所有相关微体系结构缓冲区。

设置推测障碍

一些枚举MD_CLEAR支持的处理器可以在VERW之后立即推测性地执行指令,这种推测性执行可能在VERW缓冲区覆盖功能清除推测指令线程之前发生。

由于这种可能性,应该在调用VERW和执行不能通过MDS观察受保护数据的代码之间设置一种推测障碍。

为了说明这种可能性,请考虑以下指令序列:

1.代码区域A;

2. VERW m16;

3.代码区域B;

4.推测障碍(例如,LFENCE);

5.代码区域C;

假设可以通过代码区域A中的指令访问受保护的数据,VERW指令覆盖代码区域A中的指令位于受MDS影响的缓冲区中的任何数据。然而,代码区域B中的指令可以在缓冲区覆盖发生之前推测性地执行。因为代码区域C中的加载是在推测障碍之后执行,所以它们将不会通过代码区域A观察位于缓冲区中的受保护数据。

当与VERW一起使用时,以下是在受影响的处理器上为VERW设置合适的推测障碍的示例:

1.LFENCE;

2.当前权限级别的任何更改(例如SYSRET从管理器返回到用户模式);

3.VM的进入或退出;

4.成功进入睡眠状态的MWAIT;

5.WRPKRU指令;

6.在体系结构上序列化指令或事件;

例如,如果OS在从 ring 0转换到 ring 3之前使用VERW,则ring 转换本身就是合适的推测障碍。如果在进程内的安全子域之间使用VERW,则合适的推测障碍可能是VERW; LFENCE序列。

由于篇幅过长,译者将本文差分为两个部分,第二部分敬请期待!

2019年5月14日,微软紧急发布了针对远程桌面服务(Remote Desktop Service,RDP,以前称为终端服务)的远程执行代码漏洞CVE-2019-0708的修复程序,该漏洞影响了某些旧版本的Windows系统,比如Windows XP、Windows Server 2003、Windows 7、Windows Server 2008等。

从理论上讲,远程桌面服务本身并不容易受到攻击,但一旦被攻击,其后果可是不堪设想的。如果你还不知道该漏洞的威力,就请想一想2017年5月爆发的WannaCry(又叫Wanna Decryptor)。

CVE-2019-0708漏洞只需通过预认证(pre-authentication)且无需用户交互就能实现攻击,这意味着它和WannaCry一样,都属于一种“蠕虫式”的攻击。这个漏洞可以通过网络蠕虫的方式被利用,利用此漏洞的任何恶意软件都可能从被感染的计算机传播到其他易受攻击的计算机,其方式与2017年WannaCry恶意软件的传播方式类似。

虽然微软的安全响应中心(MSRC)暂时没有发现有利用此漏洞的恶意攻击样本,但还是要做到充分的准备。最近研究发现,恶意攻击者极有可能为此漏洞编写一个利用程序,并将其嵌入到他们自己的恶意软件中。

影响范围

· Windows 7;

· Windows Server 2008 R2;

· Windows Server 2008;

· Windows 2003;

· Windows XP;

请注意:Windows 8和Windows 10及之后版本的用户不受此漏洞影响。

修复办法

可以在Microsoft安全更新指南中找到支持Windows版本的下载,使用支持版本的Windows并启用自动更新的客户将自动受到保护。

不支持的系统包括Windows 2003和Windows XP,如果你使用的是不支持的版本,则解决此漏洞的最佳方法是升级到最新版本的Windows。即便如此,微软还是在KB4500705中为这些不支持的Windows版本提供了修复程序。

在启用了网络级别身份验证(NLA)的受影响系统上可能可以部分缓解可能利用此漏洞的“可疑”恶意软件或高级恶意软件的威胁,因为NLA在触发漏洞之前需要进行身份验证。但是,如果攻击者具有可用于成功进行身份验证的有效凭据,则受影响的系统仍然容易受到远程执行代码执行(RCE)的攻击。

出于这些原因,微软强烈建议所有受影响的系统(无论NLA是否启用),都应尽快更新。

可能的攻击威力

成功利用此漏洞的攻击者可以在目标系统上执行任意代码,然后攻击者可以安装恶意程序,进而查看、更改或删除目标设备上的数据,甚至创建具有完全用户权限的新帐户。

利用此漏洞的方法

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

参考及来源:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708

https://blogs.technet.microsoft.com/msrc/2019/05/14/prevent-a-worm-by-updating-remote-desktop-services-cve-2019-0708/?from=groupmessage&isappinstalled=0

在讲解本文之前,先介绍一下域账户和DNS的几个基本概念。

域账户

域账户是域是网络对象的分组。例如:用户、组和计算机。域中所有的对象都存储在 Active Directory (AD)下。Active Directory 可以常驻在某个域中的一个或多个域控制器下。

什么是DNS?

DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将服务器名和域名转换为IP地址的工作,DNS就是这样的一位“翻译官”。

为什么需要DNS解析域名为IP地址?

网络通讯大部分是基于TCP/IP的,而TCP/IP是基于IP地址的,所以计算机在网络上进行通讯时只能识别如“202.96.134.133”之类的IP地址,而不能认识域名。我们无法记住10个以上IP地址的网站,所以我们访问网站时,更多的是在浏览器地址栏中输入域名,就能看到所需要的页面,这是因为有一个叫“DNS服务器”的计算机自动把我们的域名“翻译”成了相应的IP地址,然后调出IP地址所对应的网页。

DNS域传送(DNS zone transfer)

DNS域传送漏洞是黑客常用的一种漏洞攻击手段,黑客可以用该漏洞快速的判定出某个特定zone的所有服务器,收集域信息,选择攻击目标,找出未使用的IP地址,黑客可以绕过基于网络的访问控制。

DNS域传送漏洞原理

DNS域传送(DNS zone transfer)指的是一台备用服务器使用来自主服务器的数据刷新自己的域(zone)数据库。

DNS服务器分为:主服务器、备份服务器和缓存服务器。在主服务器和备份服务器之间同步数据库,需要使用“DNS域传送”。域传送是指后备服务器从主服务器拷贝数据,并用得到的数据更新自身数据库。

一般来说,DNS域传送操作只在网络里真的有备用域名DNS服务器时才有必要用到,但许多DNS服务器却被错误地配置成只要有client发出请求,就会向对方提供一个zone数据库的详细信息,所以说允许不受信任的网络用户执行DNS域传送(zone transfer)操作是后果最为严重的错误配置之一。

综上所述,要实现域传送漏洞,就需要一个不安全配置的DNS服务器,然后网络上的任何用户都可以获取所有传送记录并收集有关网络中服务器的信息。然而,目前还很少有人知道,如果使用Active Directory集成DNS,任何用户都可以默认查询所有DNS记录。

本文,我会给你介绍了一个默认查询所有DNS记录的工具——Adidnsdump ,即使你是一个没有读取传送记录权限的用户,也可以使用以下方法获得域环境中的所有DNS解析记录。

具体获取过程

就我个人而言,每当我接手一个新的渗透测试任务时,我都会想法设法了解测试环境的网络布局,测试对象使用的软件以及有趣数据的位置。如果测试对象有非描述性服务器名称或描述,像BloodHound或ldapdomaindump这样的工具不会有太大帮助,因为SRV00001.company.local仍然没有告诉你在这台服务器上运行的是什么。在大量IP地址上运行EyeWitness等发现工具通常会返回大量默认的Apache / IIS页面,因为大多数站点都配置为侦听DNS名称而不是IP地址。此时你如果知道DNS记录,可能就会发现SRV00001.company.local和gitlab.company.local指向同一个IP,这个IP上可能存放着大量源码。

因此,我认为访问AD的DNS记录非常有价值。为此我编写了一个可以转储这些DNS记录的Adidnsdump。你既可以直接在网络中的主机运行它,也可以通过SOCKS隧道利用。

该工具的设计思路,是在我研究Active Directory DNS时开始的,主要受到Kevin Robertson在ADIDNS 上工作的启发。当我作为普通用户提取了ADSI Edit并突然看到了域中所有DNS记录时,我试图找出AD如何在LDAP中使用域来存储DNS记录。令我惊讶的是,早在2013年,就有人开发出可以提取DNS记录的PowerShell脚本,但它并没有完全符合我的要求,所以我决定用Python编写一个版本,并添加一些选项来枚举比默认情况下更多的记录。

DNS记录到底隐藏在哪了?

在LDAP中查询DNS记录的主要方法是选择dnsNode类的所有对象,然后执行查询操作,此时,你会看到DNS域中的所有记录。当我使用filter (objectClass=dnsNode)执行查询时,返回的结果非常有限。即使我手动浏览DNS域,都可以获取更多的记录。

1.png

如上图所示,很多记录的objectClass都处于隐藏状态,我想是因为计算机DNS记录的默认权限所导致的。这让我联想到了,不是通过活动目录DNS页面创建的其他记录,也是不会允许所有用户查看其内容的。再加上IP地址实际作为这些对象的属性来存储,因此无法查看这些记录中的IP地址。

但是,默认情况下,任何用户都可以创建新的DNS记录,任何用户也可以默认列出DNS域的子对象。至此,我们就知道DNS解析记录藏在哪儿了,只是无法使用LDAP查询它们而已。

2.png

通过使用LDAP枚举知道记录所在的位置之后,我们就可以直接使用DNS查询它,因为执行常规DNS查询不需要什么特别权限,这样我们就可以解析域中的所有记录。

使用adidnsdump查询所有DNS解析记录

点此GitHub,下载adidnsdump,它可以枚举DNS域中的所有解析记录。首先,使用参数–print-zones显示当前域中的所有区域。注意,并非所有的区域都有实际意义,例如转发(forward )、缓存和存根域并不包含该域的所有记录。如果找到这些域,最好查询它们实际所属的域。在我构建的测试域中,使用参数–print-zones只会输出默认域。

3.jpg

如果我们为adidnsdump指定域或者将默认域设置为空,我们将获得一个包含所有解析记录的列表。可以列出但不能读取的记录(即上述所谓的“隐藏”DNS记录)只会显示一个问号,因为不知道其中会存在哪种类型的记录以及它们指向何处。另外,这些记录会全部被保存到名为records.csv的文件中。

4.png

要解析这些未知记录,可使用参数-r,该标志将对所有未知记录执行A查询(如果你在IPv6网络中,则可以在代码中轻松将其更改为AAAA),之前的?都会显示出具体的记录内容。

5.png

如果你没有直接连接但通过代理工作,则可以通过socks代理该工具,并使用–dns-tcp标志通过TCP执行DNS查询。

缓解措施

为了安全起见,我建议你首先要对DNS记录的安全性持有客观的认知态度。如果你确实要隐藏DNS记录,就请删除“Everyone”和“Pre-Windows 2000 Compatible Access”的“列出内容”权限,以阻止普通用户查询DNS记录。但这可能会产生负面影响,所以我不建议那样做。

所以最好的办法是及时检测DNS查询活动的出现,通过监控大量DNS查询或启用对DNS区域列表的审计可能是一种更好的缓解措施。

adidnsdump可以通过GitHub 和PyPI(pip install adidnsdump)安装使用,现在,该工具仅将获取的记录转储到CSV文件。不过,你可以自己把文件转换为其他格式。

何为全盘加密

全盘加密(Full-disk encryption)对取证取证专家来说是个非常大的挑战。Android 的安全性问题一直备受关注,Google在Android系统的安全方面也是一直没有停止过更新,努力做到更加安全的手机移动操作系统。在Android的安全加密方面,加密分全盘加密和文件级加密(Android 7.0 引入)全盘加密在 Android 4.4中引入,在Android 5.0中做了比较大的更新。本文论述的就是全盘加密,以及如何通过启动一个闪存(flash drive)来破解这种加密方式。

对于一般人来讲,全盘加密也许并不必要;然而,如果你需要处理像商业机密和不希望被其他人看见的隐私的话,那么全盘加密就显得很有必要了。

全盘加密的意义有以下两个:

1.淘汰旧计算机的时候,旧计算机硬盘上遗留的机密数据不会被有心人士挖掘出来并公之于众;

2.只要加密强度设置地足够高,无论是执法人员还是那些企图盗取数据的不法分子都无法获取他们想要的数据。

一旦被加密,如果没有被授权,就无法获取硬盘内的数据,即使是手机或计算机的生产者也不可以。

全盘加密在大部分的商业操作系统中都有应用,用户只需要选择打开这个功能,设置一个较强的密码或是词组就可以了。要访问设置全盘加密的手机或计算机,在打开设备后,启动时,会收到输入密码的用户提示。输入正确的密码后,系统中的加密程序就会被解锁,也就解锁了系统,就可以获得数据访问权限。

一些全盘加密程序可能会需要双重认证,在访问设备数据时,不仅需要输入密码,还需要插入一个智能读卡器,或者是输入一个由安全令牌产生的随机密码。

全盘加密程序与文件加密程序不同的是,后者只针对某个特定的文件进行加密,而全盘加密将会对系统内的所有数据都进行保护,包括操作系统。但全盘加密程序只会在设备关闭时开启保护功能。

当授权用户登录计算机时,就会解开加密程序,并将设备中的所有数据都暴漏在所有使用设备的人面前,除非用户对独立的文件夹都进行了加密。

但并不是所有的全盘加密工具都能确保数据百分之百的安全。全盘加密工具的安全性有多高还是要看开发者设计的程序是否可靠,一些使用弱加密系统的设备,或存在很大安全漏洞的设备也会给人一种“伪安全”的假象,很容易被破解。

破解的思路

在获取具有加密系统卷的计算机时,如果不首先对加密进行破解,则调查就无法进行。传统的操作是,直接删除硬盘驱动器,生成磁盘映像,然后通过映像来获取相关信息。不过,这种方法太过于粗暴,且效率低下。在本文中,我们会提供一种更快、更容易的方法来访问破解全盘系统加密所需的信息。大致的思路是这样的:通过启动闪存,然后暴力破解原始明文密码,将强行获取系统卷加密时所需的那些元数据。对于非系统卷,取证专家可以快速获取系统的休眠文件,以便稍后使用Elcomsoft取证磁盘解密器实时提取加密密钥。

这个取证流程的本质,就是访问存储在受全盘加密保护的计算机上的密码。一旦对系统分区进行了加密,除了破解加密之外,就别无他法了。与传统的获取密码的工作流程相比,Elcomsoft系统恢复工具(密码重设工具)有助于更快地启动密码恢复破解,并通过提取可能包含保护加密卷的动态加密密钥的系统休眠文件,在几分钟内完成加密卷的挂接。

这种新的取证流程,在分析ultrabook、笔记本计算机和二合一的Windows平板设备(如微软Surface range)时尤其方便。因为这些设备都具有不可移动、焊接存储或非标准介质的特性。通过这个思路,取证专家就可以提取对加密卷发起破解所需的所有信息。

Elcomsoft系统恢复工具提供了前所未有的安全性和兼容性,使用被授权的Windows PE环境可以确保完全的硬件兼容性和对系统的启动支持,这些系统均受到安全启动的保护。Elcomsoft系统恢复工具以严格的只读模式挂接用户的磁盘和存储介质,以确保取证取样。

使用Elcomsoft系统恢复工具制作一个可启动的Windows PE闪存

为了提取暴力破解原始密码所需的信息,你需要获取一小部分加密卷。你只需通过Windows PE闪存启动系统,然后运行Elcomsoft系统恢复工具一个(一个用于解锁Windows帐户和访问加密卷的工具)。你只需按着操作提示,轻点几下就可以制作一个可启动的Windows PE闪存。具体过程如下:

1.安装Elcomsoft系统恢复工具;

2.插入一个空的闪存并启动该工具;

1.png

3. 选择目标驱动器并指定文件系统。注意:确保选择正确的分区方案。虽然FAT 32 MBR,BIOS适用于旧PC,但大多数具有安全启动功能的新计算机都需要FAT32 MBR,UEFIx64。而某些配备了32位模式运行的64位处理器的设备(例如Lenovo ThinkPad 8)需要FAT32 MBR,UEFIx32分区。

2.png

4. 点击Format选项,Elcomsoft系统恢复工具将创建一个可启动的闪存,以及预装和预配置Windows PE和ESR实用程序。

3.png

目前,有两种截然不同的方法可用于访问存储在加密卷中的密码。

方法1:通过提取休眠文件来访问加密密钥

密码容器的设计旨在抵御对其密码的暴力破解,此外,一些全盘加密方法根本不使用密码,例如BitLocker设备加密,这是二合一设备和超薄笔记本计算机(如Microsoft Surface range)最常用的加密方法。

由于暴力破解密码可能非常耗时,因此我们开发了一种更高效的工具。

我们破解的对象是即时加密密钥(OTFE密钥),所有加密容器中都包含它们。这些密钥是系统在正常操作期间用于加密和解密信息的实际二进制密钥,密钥始终存储在系统的易失性内存中,同时挂接加密卷以便于对加密数据的读/写访问。你可以使用Elcomsoft Forensic Disk Decryptor(EFDD解密工具) 直接从设备的内存中提取这些密钥,但是这不是本文要讲的。本文,我们只谈如何提取休眠文件。因为全盘加密只在用户未进入操作环境时,发挥作用。

当用户让计算机进入睡眠状态(而不是关机)时,Windows此时的默认行为就被称为混合睡眠(hybrid sleep)状态。在混合睡眠期间,Windows会将设备易失性内存的副本保存在计算机的硬盘驱动器或SSD驱动器上,以便保存的状态可以在断电后继续存在。与此同时,计算机的RAM芯片仍处于开机状态,以保存信息。如果在睡眠期间不切断电源,计算机将立即恢复运行。但是,如果出现断电(或电量耗尽),Windows将从硬盘驱动器加载保存的RAM内容。存储计算机内存内容的文件称为休眠文件,Windows以“hiberfil.sys”的名称存储休眠文件。此时休眠文件已经被加密,我们要破解的就是这种被加密的休眠文件。

如果计算机在挂接加密分区时处于休眠状态,则OTFE密钥可以直接存储在系统的休眠文件中。如果通过闪存启动,我们可以获得休眠文件,并使用它来定位所有加密卷的OTFE密钥,这些卷在计算机进入休眠状态时仍然挂接。此时,你将需要Elcomsoft取证磁盘解密器来提取OTFE密钥,并使用它们来立即挂接或解密加密卷。

要提取系统的休眠文件,请执行以下操作:

1.安装Elcomsoft系统恢复工具 6.0工具或更新到最新版本;

2.创建一个可启动的闪存,确保指定目标系统的正确配置(BIOS或UEFI、32位或64位)。由于休眠文件可能非常大,我们建议使用至少32GB的闪存。

3.在你刚刚创建的闪存中启动目标系统;

4. 启动完成后, Elcomsoft系统恢复工具将启动。在以下窗口中,选择“磁盘工具”。

4.png

5.选择复制hiberfil.sys文件,整个休眠文件将被复制到已启动的系统的闪存上,所以要确保USB驱动器上有足够的空间。

5.png

6.指定文件应该存储的位置,默认情况下,ESR会建议使用启动它的驱动器。如果u盘上没有足够的空间,可以指定其他介质。

6.png

7.你现在可以将休眠文件传输到你的计算机,Elcomsoft取证磁盘解密器会提取OTFE密钥,并使用它们立即挂接或解密加密卷。这个过程可能需要几分钟,特别是当休眠文件的大小很大时,需要的时间会更长。

如果在休眠文件中没有找到加密密钥(如果将加密卷配置为在休眠或休眠时自动删除,可能会发生这种情况),则需要破解加密卷的密码。为了破解,你需要从加密卷中提取几千字节的加密元数据。

方法2:提取加密元数据并暴力破解密码

传统的获取方法是需要先将计算机进行拆卸,移除并映像所有存储设备。看似动静很大,其实最终的目的只是破解含有几千字节的加密元数据。现在的这个方法,就是让你在不拆卸硬盘驱动器的情况下,更高效地提取元数据。

Elcomsoft系统恢复工具允许取证者通过只读访问计算机存储设备的便携式闪存启动计算机,从而更快地开始取证。该工具会自动检测所有内置和可移动驱动器上的全加密盘,并允许提取位于加密卷中的已经暴力破解的加密元数据。由于密码容器的设计使得对密码的破解极其缓慢,在此,我们建议使Elcomsoft Distributed Password Recovery工具执行基于字典的分布式破解。ElcomSoft Distributed Password Recovery 是一款俄罗斯安全公司出品的分布式密码暴力破解工具,能够利用Nvidia显卡使WPA和WPA2无线密钥破解速度提高100倍,而且软件还允许数千台计算机联网进行分布式并行计算。

开启密码破解的正确姿势,是使用TrueCrypt还是VeraCrypt?

TrueCrypt,是一款免费开源的加密软件,同时支持Windows Vista,7/XP, Mac OS X, Linux 等操作系统。TrueCrypt不需要生成任何文件即可在硬盘上建立虚拟磁盘,用户可以按照盘符进行访问,所有虚拟磁盘上的文件都被自动加密,需要通过密码来进行访问。目前TrueCrypt的研发者已经停止了对此工具的开发工作。

VeraCrypt,是一款免费开源跨平台的实时磁盘文件加密工具,它是基于知名的开源加密工具 TrueCrypt 项目衍生而来。由于之前 TrueCrypt 已在官网上宣布其自身不安全并已停止开发了,因此现在比较活跃、而且同样是开源跨平台的 VeraCrypt 顺理成章成为大家公认的最佳文件加密工具新选择之一

因为TrueCrypt和VeraCrypt容器使用类似的加密格式,所以我们无法将它们区分开来。不过,这两种工具在破解加密时有所不同,因此你必须先选择正确的破解工具才能开启密码破解过程。

此外,TrueCrypt和VeraCrypt都为用户提供了多种加密算法的选择。每个算法都可以按着用户的选择配置不同的迭代次数(从密码生成OTFE密钥的哈希操作的次数)。如果用户指定了非标准的哈希迭代次数,那么除非你知道该次数或者尝试所有可能的组合,否则无法破解密码。就算破解成功,也是一个耗时耗力的过程。

要提取加密元数据,请执行以下操作。

1.安装Elcomsoft系统恢复工具 6.0或更新到最新版本;

2.创建一个可启动的闪存,确保指定目标系统的正确配置(BIOS或UEFI、32位或64位)。一般来说,我们建议使用至少32GB的高速闪存;

3.从你刚刚创建的闪存启动目标系统;

4.一旦启动开始, Elcomsoft系统恢复工具也要立即启动。在下面的窗口中,选择“磁盘工具”;

7.png

5.选择复制驱动器加密密钥;

8.png

6.Elcomsoft系统恢复工具将自动检测所有固定和可移动驱动器上的全盘加密卷;

9.png

7.你可以选择要处理的卷,例如,试试以下的TrueCrypt/VeraCrypt卷:

10.png

8.因为TrueCrypt和VeraCrypt卷使用相同的卷格式,所以不能自动区分它们,此时你将需要手动指定加密容器的类型;

11.png

9.TrueCrypt和VeraCrypt都允许使用不同的加密和哈希算法,如果你知道使用哪些加密和哈希算法来加密卷,请在下一步中指定它们。

12.png

10.但是,如果你对用户选择的加密算法和哈希算法有怀疑,请将这些值保留为“Unknown”。因为我们必须尝试多种组合,这会减慢破解密码的速度。但是,这仍然比指定错误的加密类型,并最终造成密码破解失败要好得多。

13.png

11. 完成转储加密元数据后,将文件传输到Elcomsoft分布式密码恢复工具以恢复原始明文密码。注意,即使使用功能强大的硬件,密码破解也可能会花费大量时间。

如果找到密码,则可以挂接加密卷,或者使用Elcomsoft取证磁盘解密器对其解密,以便进行脱机分析。

内存转储

如果你正在分析一个正在运行中的系统,并且用户已经登录,那么你还可以通过进行易失性内存转储来捕获OTFE密钥。为了捕获RAM映像,你必须在用户正在运行的系统上运行Elcomsoft取证磁盘解密器(不要考虑“只读”部分)。此时,用户必须已登录,并且该帐户必须具有管理权限。

注意:实时系统分析是危险的, 因为Elcomsoft系统恢复工具只提供了只读操作。

要捕获内存转储,请将Elcomsoft取证磁盘解密器安装到闪存上,将该闪存连接到目标系统并运行小型捕获工具。

14.png

内存转储被保存后,确保在闪存上指定正确的路径。

15.png

现在,你可以将内存转储转移到自己的计算机上,并运行Elcomsoft取证磁盘解密器来搜索OTFE密钥。

总结

Elcomsoft系统恢复工具虽然不会让你直接进入加密卷,但是该工具提供了一个比其他方式更快的方式,允许你高效地从中提取信息,这比使用传统方法能更快地提取加密密钥或破解密码。

前两篇文章(12),我们已经对CARBANAK的源代码本身进行了详细的分析,现在就让我们分析一下CARBANAK源代码的构建工具吧。

构建工具

让我们先来看看我们对CARBANAK构建工具的初始猜想和推断:

该工具会允许攻击者配置诸如C2地址、C2加密密钥和活动代码等详细信息,这个构建工具使用每个构建的新密钥加密二进制文件的字符串。

之所以会有以上的推论,是因为:

大多数CARBANAK的字符串都经过加密,以便使分析更加困难。我们注意到,对于我们遇到的每个示例,所有加密字符串的密钥和密文都会发生变化,即使在具有相同编译时间的示例中也是如此。另外,我们还观察到用于HTTP协议的RC2密钥在具有相同编译时间的示例之间发生变化。这些观察结果表明,可能存在构建工具。

图1显示了用于解码CARBANAK中的字符串的三个密钥,每个密钥都来自不同的CARBANAK示例。

1.png

对于每个构建版本,CARBANAK中字符串的解密密钥都是惟一的

事实证明,我们的推论是正确的,并在CARBANAK源转储中发现了一个构建工具,如图2中的英文翻译所示。

2.png

CARBANAK构建工具

使用此构建工具,你可以指定一组配置选项以及一个模板CARBANAK二进制文件,并将配置数据置换成二进制文件,以生成用于发送的最终的构建文件。 “前缀”文本字段允许操作符指定活动代码。管理主机文本字段用于指定C2地址,而管理密码文本字段是用于派生RC2密钥以加密通过CARBANAK的伪HTTP协议进行通信的密钥。

现在就可以确定有一个构建工具确实存在,并用于配置构建的活动代码和RC2密钥以及其他项目。要想知道编码的字符串是什么样的,只在构建工具的GUI中是找不到什么有价值的信息的,因此我们必须查看后门和构建工具的源代码。

图3显示了CARBANAK后门源代码中定义的一个名为ON_CODE_STRING的预处理器标识符,当启用该标识符时,宏会被自动定义,另外这些宏封装了程序员希望用二进制编码的所有字符串。这些函数将要编码的字符串用字符串“BS”和“ES”串联起来。图4显示了构建工具源代码的标头文件中的一小段代码,其中将BEG_ENCODE_STRING定义为“BS”,END_ENCODE_STRING定义为“ES”。构建工具会在模板二进制文件中搜索这些“BS”和“ES”标记,提取它们之间的字符串,使用随机生成的密钥对它们进行编码,并用编码的字符串替换二进制文件中的字符串。另外,我们还发现了一个名为bot.dll的可执行文件,它恰好是构建工具使用的模板二进制文件之一。在这个二进制文件上运行的字符串表明,特定于CARBANAK后门工作的最有意义的字符串实际上是被加载在“BS”和“ES”之间的,如图5所示。

3.png

ON_CODE_STRING参数使简单的字符串包装宏能够通过构建工具为编码准备字符串

4.png

编码字符串标记的builder.h宏

5.png

模板CARBANAK二进制文件中的编码字符串标记

对源代码的访问

下面是我们起初的两个推论:

1.根据我们所观察到的信息,我们认为至少CARBANAK的一些运营商可以直接访问源代码,并了解如何修改源代码或与开发人员建立密切关系;

2.一些运营商可能会独立编译他们自己的后门;

第一个推论是根据下列证据作出的:

尽管有可能使用构建工具,但我们在示例集中发现了57个独特的编译时间,其中一些编译时间非常接近。例如,在2014年5月20日,有两个版本编译的时间相隔约4小时,并配置为使用相同的C2服务器。同样,在2015年7月30日,有两个版本编译的时间相隔约12小时。

为了进一步研究,我们执行了两个编译时间非常接近的CARBANAK示例,以查看代码中的更改(如果有的话)。图6显示了一个这样的差异。

6.png

两个编译时间非常接近的CARBANAK示例之间的微小差异

POSLogMonitorThread函数仅在示例A中执行,而blizkoThread函数仅在示例B中执行(Blizko是俄罗斯资金转账服务软件,类似于PayPal)。 POSLogMonitorThread函数监视对特定销售点软件的日志文件所做的更改,并将解析后的数据发送到C2服务器。blizkoThread函数通过在注册表中搜索特定值来确定计算机用户是否是Blizko客户。通过了解这些细微的差异,我们搜索了源代码,再次发现使用了预处理器参数。

7.png

预处理器参数会确定哪些函数将包含在模板二进制文件中

注意,这并不是运营商可以访问源代码的明确证据,只能作为参考。运营商不需要具备任何编程知识,只需简单指导如何在Visual Studio中添加和删除预处理器参数,即可对构建进行微调以满足他们对特定目标的访问需求。

第二个推论的证据是通过查看二进制C2协议的实现以及它随时间的推移而改变的过程:

多年来,这个协议经历了几次修改,每个版本都以某种方式建立在前一个版本的基础上。这些变化可能会使现有的网络签名失效,并使签名创建更加困难。

如图8所示,在我们的示例集中发现了二进制C2协议的五个版本。该图显示了在我们的示例集中找到每个协议版本的第一个注释编译时间,每个新版本都提高了协议的安全性和复杂性。

8.png

通过二进制编译时间显示的二进制C2协议的演变

如果CARBANAK项目位于中心位置,并且只将模板二进制文件交付给运营商,那么预期示例编译时间应该与二进制协议的发展保持一致。除了一个实现协议“版本3”的示例外,时间轴看起来是这样的。对于版本3没有列出日期的一个可能解释是,我们的样本集不够完整,无法包含该版本的第一个样本。这并不是我们发现的唯一一个在示例中实现过期协议的情况,图9显示了另一个示例。

9.png

使用过期版本的二进制协议的CARBANAK示例

在这个示例中,野外发现的CARBANAK示例使用的是协议版本4,而新版本至少已经发布了两个月。如果源代码保存在一个单独的中央位置,则不太可能发生这种情况。使用预处理器参数对模板二进制文件进行快速微调,再结合几个实现过时协议版本的CARBANAK示例,表明CARBANAK项目是分发给运营商的,而不是集中保存的。

以前未识别的命令的名称

源代码显示了一些命令的名称,这些命令的名称以前是未知的。实际上,它还显示了一些命令,这些命令在我们之前写的示例中完全没有出现过,因为该功能被禁用了。表1显示了在CARBANAK源代码中新发现的命令。

10.jpg

表1:以前未按名称标识的命令哈希值

msgbox命令在CARBANAK源代码中完全被注释掉了,并且严格用于调试,因此它从未出现在公共分析的样本中。同样,ifobs命令也没有出现在我们分析和公开记录的示例中,但可能出于不同的原因。图10中的源代码显示了CARBANAK能够理解的命令表,并且ifobs命令(0x6FD593)被#ifdef包围,除非启用了ON_IFOBS预处理器参数,否则ifobs代码无法编译到后门。

11.png

来自CARBANAK任务代码的命令表

然而,一个更有趣的命令是tinymet,因为它说明了源代码如何既有用又有误导性。

tinymet命令和相关载荷

在我们最初的CARBANAK分析中,我们指出命令0xB0603B4(当时名称未知)可以执行shellcode。源代码显示该命令(其实际名称为tinymet)旨在执行一个非常特定的shellcode。图12显示了用于处理tinymet命令的代码的简略列表,其中行号为黄色,隐藏的选定行(灰色)以更紧凑的格式显示代码。

12.png

缩写的tinymet代码清单

从第1672行开始的注释表明:

tinymet command
Command format: tinymet {ip:port | plugin_name} [plugin_name]
Retrieve meterpreter from specified address and launch in memory

在第1710行,tinymet命令处理程序使用单字节XOR密钥0x50来解码shellcode。值得注意的是,在第1734行,命令处理程序分配了五个额外字节,第1739行将5字节的mov指令硬编码到该空间中。它使用套接字句柄号填充mov指令的32位立即操作数,该套接字句柄号用于从服务器连接检索shellcode,这个mov指令的隐含目标操作数是edi寄存器。

我们对tinymet命令的分析到此就结束了,直到发现了名为met.plug的二进制文件。图12中的十六进制转储显示了此文件的结尾。

14.png

met.plug的十六进制转储

文件的末尾没有与五个缺失的字节对齐,这与任务源代码中动态组装的mov edi前导码相对应。但是,在源代码中找到的单字节XOR密钥0x50未能成功解码此文件。经过一些分析后,我们意识到这个文件的前27个字节是一个shellcode解码器,看起来非常类似于call4_dword_xor。图13显示了shellcode解码器和编码的metsrv.dll的开头。 shellcode使用的XOR密钥是0xEF47A2D0,它与5字节mov edi指令、解码器和相邻的metsr .dll在内存中的布局方式相匹配。

15.png

Shellcode解码器

解码产生了从偏移量0x1b开始的metsrv.dll副本,当shellcode执行退出解码器循环时,它会执行Metasploit的可执行DOS标头。

具有讽刺意味的是,拥有源代码会使我们的二进制分析偏向错误的方向,在实际存在使用四字节XOR密钥的27字节解码器前导码的情况下,建议使用单字节XOR密钥。此外,命令名称为tinymet,表明涉及TinyMet Meterpreter stager。这在某种程度上可能是这样的,但是源代码注释和二进制文件表明,开发人员和运营商已经可以直接下载Meterpreter而不用更改命令的名称了。

小结

访问CARBANAK的源代码和工具集,为我们提供了一个独特的机会来回顾前面的分析。以此来弥补原来分析的不足或加固对原来的分析推论。上述分析表明,即使没有访问源代码,只要有足够大的样本集和足够的分析,也可以达到同样的分析目的。例如在tinymet命令的情况下,有时,如果没有适当的上下文,我们根本就无法看到给定代码段的完整而清晰的目的。但是一些源代码也与附带的二进制文件不一致。

接下来,我们将介绍CARBANAK套件中一个有趣的工具:一个视频播放器,用于播放后门捕获的桌面录像。

视频播放器

CARBANAK后门可以录制受害者桌面的视频,据报道,攻击者通过观看录制的桌面视频,以了解在目标银行工作员工的操作流程,使他们能够成功插入银行验证过程未检测到的欺诈性交易。视频数据文件格式和用于观看视频的播放器似乎是自定义编写的,如下图所示的视频播放器和木马的C2服务器被设计成一对一工作模式。

1.png

CARBANAK桌面视频播放器

C2服务器以视频播放器理解的自定义视频文件格式封装从CARBANAK木马接收的视频流数据,并根据视频播放器提前设定的规则将这些视频文件写入磁盘上的某个位置。下图所示的StreamVideo构造函数就创建了一个新的视频文件,该文件将填充从CARBANAK 木马接收的视频捕获数据,其中包括签名标记、时间戳数据和受感染主机的IP地址。以下这段代码是C2服务器项目的一部分。

2.png

carbanak\server\ server\ Stream.cs来自C2服务器的代码片段,它将视频数据序列化到文件中

下图显示了LoadVideo函数,它是视频播放器项目的一部分。它通过查找TAG签名来验证文件类型,然后读取时间戳值和IP地址,就像它们由上图中的C2服务器代码写入一样。

3.png

carbanak\server\Player\ video .cs播放器代码,它加载C2服务器创建的视频文件

如下面两个图所示,视频文件的扩展名为.frm。第一个图所示的C2服务器的CreateStreamVideo函数按照MakeStreamFileName函数中定义的约定格式化文件路径,然后调用第二个图的StreamVideo构造函数。

4.png

carbanak\server\ server\ RecordFromBot.csC2服务器中的函数,用于格式化视频文件名并添加扩展名“frm”

下图中所示的视频播放器代码片段按着视频文件路径约定,在所有视频文件目录中搜索扩展名为.frm的文件,这些文件的开始和结束时间戳都在DateTime变量dt的范围内。

5.png

carbanak\server\Player\Video.cs:来自播放器代码的片段,用于搜索扩展名为“frm”的视频文件

我们遇到了几个视频文件的示例,但只有一些与这个视频播放器兼容。经过分析,我们发现至少有两种不同版本的视频文件格式,一种是压缩视频数据格式,另一种是原始格式。在对视频处理代码进行了一些轻微的调整之后,这两种格式现在都支持,样我们就可以播放所有视频示例了。

下图显示,攻击者似乎正在测试命令,并确保它们不会被某些安全监视工具检测到。

6.png

图中的命令列表都是关于持久性、屏幕截图创建和启动各种有效载荷的

总结

在这三篇文章中,我们详细分享了许多关于逆向一个大型、复杂的恶意软件家族的细节。

上一篇文章中,我们讨论了如何在CARBANAK中使用字符串哈希来管理整个代码库中的Windows API解析。但同时,CARBANAK的开发者在另一些任务中也使用了相同的字符串哈希算法。本文,我们将讨论CARBANAK是如何应对杀毒软件的检测并逃避它们的。

应对杀毒软件

使用源代码无疑会加快对字符串哈希的分析进程,例如,AV.cpp中的函数AVDetect通过进程名称哈希迭代进程来检测杀毒软件,如图1所示。

1.png

通过进程名称哈希检测杀毒软件

CARBANAK如何处理这些信息呢?它根据安装的内容来选择逃避杀毒软件的方法。图2显示了开发者通过注释禁用的AVG逃避的代码。

2.png

2017年11月,CARBANAK还学会了逃避流程注入检测。这种逃避使用的是Carberp变种木马中使用的一种技术,它用内存映射和通过QueueUserAPC异步过程调用的队列来替换远程堆分配和对CreateRemoteThread的调用。

分析幕后的开发构件

拥有源代码后,我们就有机会来了解幕后的开发构件和开发者的详细了。为此,我们在源代码转储中搜索了可能指向个人的构件,并进一步在Visual Studio解决方案文件中找到更多相关的信息。其中大多数引用驱动器O:作为源根(source root,),但我确实找到了以下主机路径:

C:\Users\hakurei reimu\AppData\Local\Temp

C:\Users\Igor\AppData\Local\Temp

E:\Projects\progs\Petrosjan\WndRec\…

E:\Projects\progs\sbu\WndRec\…

不幸的是,这些路径并不能给出很多有意义的答案。如果在以后的构件中观察到它们,那么可能会推断出它们之间的联系,但是在撰写本文时,我们对开发者还所知不多。

源代码的调查过程

CARBANAK源代码包含许多漏洞、以前的C2主机、密码和密钥信息。于是我们决定对这些信息进行全面的研究,并确定它们是否可以给研究带来新的信息,或证实之前的任何观察结果。

漏洞调查

首先,我想知道CARBANAK的开发者们是否使用了未公开披露的任何漏洞。因为我发现所有的漏洞都有详细记录,表1列出了我从CARBANAK源代码转储中查看到的升级权限代码。

3.jpg

CARBANAK源代码还包含大量复制于Mimikatz的代码,包括用于从lsass.exe转储密码的sekurlsa模块和用于允许多个远程桌面协议连接的终端服务修补代码。

关键材料的调查

我们的分析包括对源代码和附带的二进制文件中的密码和关键材料的调查,虽然其中许多都用于调试版本,但我们还是对它们进行了整理,以备将来猜测密码时用作参考。表2显示了用于rc2加密通信和其他目的的恢复密码,以及源代码中的相应名称及其遇到的状态(在源代码中激活,注释掉或编译成二进制文件)。

4.jpg

在CARBANAK源代码和二进制文件中找到的密码

我在调试目录中发现了一个加密的服务器证书,它似乎可以提供一个新的基于网络的指示,以准确地发现新的攻击趋势。我们通过在c#中修改一个公开可用的X509处理代码示例来遍历流行密码列表中的密码,从而强制使用这个容器。由于密码为单字符“1”,所以我们在不到1秒的时间内就找到了密码。该证书原来是用于测试的,因此密码很弱。证书如图3所示,详细信息见表3。

5.png

证书

6.1.jpg

6.2.jpg

6.3.jpg

证书详细信息

我还从源代码转储中解析了一个未受保护的私钥,图4和表4分别详细显示了私钥参数。

7.png

已解析的512位私钥

8.1.jpg

8.2.jpg

私钥参数

我在配置标头中找到了一个名为PUBLIC_KEY的值,其中的注释表明它是用于调试目的的。解析后的值如表5所示。

9.jpg

配置标头中定义的PUBLIC_KEY的私钥参数

调查基于网络的指示

源代码和二进制文件包含多个基于网络的指示符(NBI),它们与CARBANAK后门活动和FireEye之前观察到并记录的FIN7操作有明显的相似之处。表6显示了这些指示以及相关的FireEye公共文档。其中就包括遇到每个NBI时的状态(在源代码中活动、注释掉或编译成二进制文件)。域名被删除了,以防止浏览器、聊天客户端等意外解析或交互。

11.1.png

11.2.png

其中,四个TCP端点(80.84.49[.]50:443, 52.11.125[.]44:443, 85.25.84[.]223, and 37.1.212[.]100:700) 都是新发现的。

总结

经过我们对这个源代码转储的分析,可以证实它就是CARBANAK。另外,我们还发现了一些新的、有趣的信息。根据这些信息,我们能够将具体的漏洞信息通知给供应商,作出应对。有趣的是,这个项目本身并不叫CARBANAK,甚至也不叫Anunak,因为信息安全社区是根据恶意软件中发现的字符串构件来命名它的。开发者在Visual Studio项目中主要将恶意软件称为“bot”,包括文件名、源代码注释、输出二进制文件、用户界面和手册。

FLARE(FireEye Labs Advanced Reverse Engineering 的缩写)通常会分析一些被大量使用的、且危害极大的恶意软件的源代码,然后对其进行分析,比如通过逆向工程、事件响应、取证调查和渗透测试。

FIN7(也被称为Anunak或银行大盗Carbanak)是目前为止组织最为严密的复杂网络犯罪组织,自2013年起就开始活跃。

以下为该团伙犯罪时间线:

2013 年-2014 年 – 开发和使用 Anunak 恶意软件,主要针对金融机构和 ATM 网络;

2014 年-2016 年 – 开发并使用 Carbanak 恶意软件,相当于 Anunak 更新更复杂的版本;

2016 年-2017 年 – 使用合法渗透测试框架 Cobalt Strike 开发定制恶意软件;

2016 年初,Carbanak 团伙主要针对美国和中东地区的银行和金融机构。卡巴斯基实验室于 2015 年首次发现 Carbanak 团伙,该集团从 100 家金融机构窃取了 10 亿美元资金;

2016 年11月,Trustwave 的专家发现了该组织针对酒店业发起的一项新活动;

2017 年1月,Carbanak 团伙开始使用 Google 服务进行命令和控制(C&C)通信,监控被感染的电脑

2018年3月,欧洲刑警组织、FBI,以及西班牙、罗马尼亚、白俄罗斯以及中国台湾的网络安全公司、金融机构和执法机构联手行动,在西班牙阿利坎特市抓捕了利用 Carbanak 木马和 Cobalt 木马攻击银行谋取暴利的 Carbanak 犯罪团伙头目,对该团伙予以致命打击。

Carbanak 犯罪团伙至今共针对全球几十个多个国家和地区(俄罗斯、日本、瑞士、美国、荷兰、中国等)的 几百家家银行、电子支付系统和金融机构发起攻击,造成了至少几十亿欧元的重大损失。

2017年,FireEye 的威胁分析师Barry Vengerik和Tom Bennet发表了《CARBANAK后门背后的玄机》一文,文中强调:

Carbanak 犯罪团伙使用的许多技术,通常都是国家资助的攻击者使用的。其使用的技术的复杂程度,在经济动机趋势下的黑客身上并不多见。

在那篇文章发表之后,FireEye 的威胁分析师Nick Carr又发现了两个RAR压缩文件,其中包含CARBANAK源代码、构建器和其他工具(VirusTotal上目前有kb3r1papwmie两种版本)。

FLARE恶意软件分析请求通常最多只包含几十个文件,但是CARBANAK的源代码是20MB,其中了包含755个文件,由39个二进制文件和100000行代码组成。

我们本文的目标是找到之前分析中漏掉的威胁情报,在本系列文章中,我们将详细阐述并分享以前基于二进制代码逆向工程的公开分析中得出的推论。在本文的第一部分中,我将从源代码的角度讨论俄语语言问题,CARBANAK工具的翻译图形用户界面以及反分析策略。对源代码的分析与对二进制代码的分析一样困难,甚至更困难。

文件编码和语言注意事项

阅读源代码的过程需要两步:首先是正确的编码显示文件,其次是学习足够的俄语。图1显示了文本编辑器中,一开始还不知道正确编码的CARBANAK源代码:

1.png

没有正确解码的文件

根据猜测,这位文件的编码应该使用的是UTF-8和codepage1251(西里尔语或 古斯拉夫语)。以下这些文件的语言主要是codepage1251:

2.png

codepage1251(西里尔语)源代码

图2是一个C ++标头文件,定义了后门命令执行中涉及的错误值。其中大多数标识符都是英文的,但有些标识符俄语,需要学习俄语才能看出它描述的是什么。 

为了克服语言的障碍,我特意编写了一个脚本来仔细检查文件并创建优先级词汇表。该脚本位于FireEye vocab_scraper GitHub存储库中,它会遍历源目录,查找可打印的较低ASCII范围之外的所有字符序列:从十进制值32(空格字符)到126(波浪字符“~”)。该脚本将每个单词添加到Python defaultdict_并递增其计数。最后,脚本会按发生频率对此字典进行排序,并将其转储到文件中。

结果是一个3400多个单词的词汇表,部分结果如图3所示:

3.png

来自CARBANAK源代码的前19个西里尔字符序列

我花了几个小时在俄语学习网站上学习西里尔字母和俄语单词的发音。然后,我查找了使用最广泛的前600多个单词并创建了一个小字典。我将俄语语言输入添加到分析VM中,并使用Microsoft的屏幕键盘(osk.exe)来导航西里尔语键盘布局并查找定义。

译者注:2017年FireEye刚刚发布了一款名为 FLARE VM的软件,这是一款windows平台的发行软件,灵感来源于诸如Kali和REMnux之类的Linux发行版。它是由FireEye威胁研究员Peter Kacherginsky开发的,用来解决与维护定制VM相关的问题。

学习耳语除了对源代码注释之外,还可以了解如何阅读和输入西里尔字母对于翻译我在源代码转储中找到的CARBANAK图形用户界面非常有用。下图显示了我翻译的CARBANAK的命令和控制(C2)用户界面。

4.png

翻译后的C2图形用户界面

这些用户界面分别包括视频管理和回放应用程序,分别如图5和图6所示。

5.png

翻译后的视频管理应用程序用户界面

6.png

翻译后的视频回放应用程序用户界面

下图显示了操作员工具的RAR压缩中包含的后门构建器:

7.png

翻译后的后门构建器应用程序用户界面

操作员RAR文件还包含一个操作员手册,解释了所有后门命令的语义。下图显示了本手册中的前几个命令,包括俄语和英语。

8.png

操作员手册(左:原始俄语;右:翻译后的英语)

在更简单的后门中,函数会直接计算出从C2服务器接收的命令ID,并将控制分派给正确的函数以执行命令。例如,后门可能向其C2服务器请求一个命令,并接收一个带有命令ID 0x67的响应。后门中的dispatch函数将根据几个不同的值检查命令ID,包括0x67,比如,可以调用函数来向C2服务器执行反向shell。图9显示了在IDA Pro中查看的此类函数的控制流程图。每个代码块都检查命令ID,并将控制权传递给适当的命令处理代码,或者继续检查下一个命令ID。

9.png

一个简单命令处理函数的控制流程图

而在这方面,CARBANAK的做法完全不同。它使用一个名为命名通道的Windows机制,作为在后门控制下的所有线程、进程和插件之间进行通信和协调的手段。当CARBANAK任务组件接收到一条命令时,它将该命令转发到一个命名通道上,在该通道上它将通过几个不同的函数来处理消息,可能会将消息写入一个或多个额外的命名通道中,直到到达最终处理指定命令的目的地。命令处理程序甚至可以指定自己的命名通道,以便从C2服务器请求更多数据。当C2服务器返回数据时,CARBANAK将结果写入此辅助命名通道,并触发一个回调函数以异步处理响应数据。 CARBANAK命名的基于通道的任务组件非常灵活,可以控制固有的命令处理程序和插件。它还允许本地客户端在不使用网络的情况下向CARBANAK发送命令。实际上,我们不仅编写了这样的客户端来帮助进行分析和测试,而且还编写了一个名为botcmd.exe的客户端。

艰难的分析过程

从二进制角度分析CARBANAK中的这种命令处理机制无疑是具有挑战性的,它需要为反汇编中的许多不同视图维护选项卡,以及一种命令ID和命名通道名称的文本映射,以描述入站命令在到达目的地之前通过各种通道和函数的过程,图10显示了七个命名通道消息处理函数的控制流程图。通过二进制透视图,我就没有必要搜索多个源文件并处理模糊的函数名称。通过反汇编程序,我可以轻松地跟踪函数和全局变量的交叉引用,并在代码中打开多个相关的视图。

10.png

命名通道消息处理函数的控制流程图

通过源代码这个角度,我们可以很容易地对恶意软件做出准确的分析。这是因为,源代码包含了许多在编译和链接过程中丢失的信息。即便使用此方法,分析CARBANAK的任务组件(用于处理C2服务器发送的命令)也不是件容易的事。根据所使用的C2协议和正在处理的命令,控制流可能会在不同的函数中采用不同的路径,以便稍后再次使用相同流程并完成相同的命令。分析需要在5个文件中的近20个函数之间来回跳转,通常需要回溯以恢复关于函数指针和参数的信息,这些信息是从多达18个层传回来的。分析还涉及解决c++类继承、范围模糊、重载函数以及在使用命名通道使用时控制流终止等问题。简而言之,即使在源代码中,也很难对CARBANAK进行准确分析。

CARBANAK源代码中的反分析机制

CARBANAK的可执行代码充满了将十六进制数字推送到同一函数的逻辑,然后是对返回值的间接调用。这很容易被识别为模糊函数导入解析,其中CARBANAK使用一个名为PJW的简单字符串哈希(以其作者P.J. Weinberger的名字命名)来定义Windows API函数,而不公开它们的名称。PJW哈希的Python实现如图11所示。

def pjw_hash(s):
    ctr = 0
    for i in range(len(s)):
        ctr = 0xffffffff & ((ctr << 4) + ord(s[i]))
        if ctr & 0xf0000000:
            ctr = (((ctr & 0xf0000000) >> 24) ^ ctr) & 0x0fffffff
 
    return ctr

PJW哈

这种命名方法在CARBANAK样本中使用了数百次,阻碍了分析人员对恶意软件功能的分析。幸运的是,反汇编程序可以使用flare-ida脚本来注释混淆的导入,如图12所示。

12.png

使用FLARE的shellcode哈希搜索注释的模糊处理导入解决方案

CARBANAK的开发者通过使用C预处理器宏和预编译源代码扫描步骤来计算函数哈希,相对轻松地实现了整个后门的这种模糊的导入解析。图13显示了相关API宏和相关机制的定义。

13.png

用于导入解析的API宏

API宏允许恶意软件的开发者输入API(SHLWAPI, PathFindFileNameA)(…),并将其替换为GetApiAddrFunc(SHLWAPI, hashPathFindFileNameA)(…)。SHLWAPI是一个符号宏,定义为常量3,hashPathFindFileNameA是反汇编中观察到的字符串散列值0xE3685D1。但是如何定义哈希?

CARBANAK源代码中内嵌了一个程序,它负责扫描源代码,寻找API宏的调用,从而构建一个标头文件,为整个代码库中遇到的所有Windows API函数名定义字符串哈希。图14显示了这个实用程序的源代码及其输出文件api_funcs_hash.h。

14.png

字符串哈希实用程序的源代码和输出内容

当我对经过混淆处理的恶意软件进行逆向工程时,除了理解它本身的混淆技术如何实现外,我还发现了开发者使用了强大的C预处理器以及自定义代码扫描和代码生成工具进行混淆。

总结

通过CARBANAK的源代码,我们发现这些恶意软件的开发者是如何处理代码混淆的。无论是任务代码还是Windows API解析系统,恶意软件分析师都很难发现其中的机关。

在接下来的几个系列中,我们将逐步分析CARBANAK是如何规避安全监测,并进一步发起攻击的?