全盘加密分析技术的简单介绍,只针对未开启预启动认证的情况

xiaohui 逆向破解 2019年6月23日发布
Favorite收藏

导语:关于启动加载器(Boot Loader)和全盘加密(full disk encryption,FDE)的主题,虽然我已经写了几篇文章,但还没有对它进行更详细的探讨。所以在本文中,我希望更深入地了解如何真正开始执行这些类型的分析,以及为什么执行这些分析是有用的。

关于启动加载器(Boot Loader)和全盘加密(full disk encryption,FDE)的主题,虽然我已经写了几篇文章,但还没有对它进行更详细的探讨。所以在本文中,我希望更深入地了解如何真正开始执行这些类型的分析,以及为什么执行这些分析是有用的。我将首先介绍它的实用性,然后介绍如何实现这一点,但不会完全逆向盘加密启动加载程序。我不会做很多核心的逆向分析,比如查找密码操作中的漏洞或逆分析向自定义文件系统实现,但希望提供足够的信息,以便开始对未知的启动加载程序进行逆向分析。

注意:本文中描述的方法和技术只适用于未开启预启动认证机制的全盘加密的情况,因为只有这样,你才可以获得加密盘的解密密钥。如果碰到需要开启预启动身份验证的情况,则可以获取的信息可能只有元数据或“已删除”文件。

让我们看看在具体研究时,可能遇到的信息类型:

1. (加密的)隐藏文件系统;

2. (经过模糊处理的)加密密钥;

3.用户名;

4. 哈希/加密的密码;

5.Windows域凭据;

6.分析FDE时的配置信息;

7.标记删除的文件;

8.查找零日漏洞并绕过加密机制;

根据上面列出的项目,我们可以得出结论,从攻击性和防御性角度分析FDE都是可以的。它既可以帮助我们破解目标网络,也可以帮助我们获取敏感信息,还可以收集取证证据,或者帮助我们理解特定的加密实现,使我们能够解密全盘加密并对其进行分析。我在这篇文章中使用的工具,你可以在这里找到。如果你想知道所有细节和流程,请继续阅读。总的步骤分三步:

1.创建磁盘副本;

2.分析磁盘;

3.静态和动态启动分析

由于我无法轻松访问具有我想分析的确切功能的磁盘加密软件,因此本文,我将使用DiskCryptor 为分析对象,DiskCryptor是一种开放式加密解决方案,可对所有磁盘分区(包括系统分区)进行加密。

使用DiskCryptor的另一个原因是,它是开源的,使用它可以很容易理解某些难以理解的片段。我个人在进行逆向分析时,通常是先找到一个类似的开源变体,或者找到专有解决方案中使用的开源组件(如果适用的话)。

首先从观察启动过程开始,在这一步我主要是要确定是否有可识别的内容出现在屏幕上。你会感到惊讶的是,有时会开发一个* nix shell来将实际的加密和解密(COTS)组件连接在一起。

在观察完启动过程之后,你可能已经获得了一些字符串,理想情况下能够上网查到这些字符串的信息。

磁盘映像

我通常喜欢做的第一件事是创建一个我想要分析的磁盘映像。原因有两个:

1.不会破坏原始数据;

2.在进行逆向分析时,能很快知道它是否与硬件绑定;

有几种方法可以创建磁盘映像,最知名的方法当然是使用dd命令。dd是Linux/UNIX下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。如果你是在谷歌上查到的字符串信息,就用以下的dd命令:

dd if=/dev/sda of=disk-sda.img bs=1M conv=noerror

上面的内容虽然完整,但有一个缺点,你最终会得到一个与原始磁盘大小相同的文件。处理大型磁盘时,这可能非常不方便。理想情况下,我只想获得只包含数据的映像。为此,我调整了现有的FUSE示例,以保存扇区并在需要时播放它们。简单的理解,fuse实现了一个对文件系统访问的回调。fuse分为内核态的模块和用户态的库两部分。其中用户态的库为程序开发提供接口,也是我们实际开发时用的接口,我们通过这些接口将请求处理功能注册到fuse中。内核态模块是具体的数据流程的功能实现,它截获文件的访问请求,然后调用用户态注册的函数进行处理。

如果我们想在Windows启动之前创建所有扇区的“映像”,请使用以下命令。

python emulate_partial.py ~/disk-images/ mountpoint/

这将创建包含' /tmp/datadir_storage '中读取扇区内容的文件,可以使用' -datadir '选项更改这些内容。如果我们想用保存的数据模拟启动过程,请使用以下命令。

python emulate_partial.py --emulate ~/disk-images/ mountpoint/

此时,你将读取保存扇区的内容,从而模拟启动过程。如果你已经在考虑其他用例,那么,你可以使用以上方法,对所有读取和操作盘数据的代码进行混淆处理。

我在测试期间,只需要34M的数据来模拟启动过程,而不是20G。有时甚至可能不到34M,但我想至少加载几个Windows文件。

磁盘分析

在对磁盘进行映像之后,我们至少已经获得了我们正在分析的产品的信息,并且已经阅读了它们,甚至还使用了我们在网上找到的一些工具。现在,我们要做的就是磁盘分析。它是否包含未加密的部分?它是否包含带有有趣文件的分区?想要回答这些问题,就需要查看磁盘,这是一项相对容易的任务。不幸的是,我手头没有使用隐藏分区的工具。不过,可以通过以下方法达到相同的目的:

1.运行映像上的fdisk,要特别注意偏移量和总大小之间的差异;

2. 运行映像上的binwalk:手动验证结果;

3. 运行映像上的字符串:MBR和VBR之后的所有输出内容都应该是无用的,可读字符串表示潜在的有趣扇区;

最后,我还将扇区读取偏移与我用fdisk命令看到的分区进行比较:

fdisk -l ~/disk-images/dc-bios.img
Disk /home/dev/disk-images/dc-bios.img: 20 GiB, 21474836480 bytes, 41943040 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x688409a1

Device Boot Start End Sectors Size Id Type

/home/dev/disk-images/dc-bios.img1 * 2048 206847 204800 100M 7 HPFS/NTFS/exFAT
/home/dev/disk-images/dc-bios.img2 206848 41940991 41734144 19.9G 7 HPFS/NTFS/exFAT

为此,我使用了一个FUSE包装器,其中添加了一些print语句,输出如下所示:

python wrap_directory.py ~/disk-images/ mountpoint/
r /dc-bios.img 0 16384
r /dc-bios.img 21474811904 4096
r /dc-bios.img 21474816000 16384
r /dc-bios.img 21474832384 4096

上面的输出内容显示,DiskCryptor显然在磁盘末尾存储了一些数据。因为如果将偏移量转换为扇区(21474811904 / 512 = 41942992),你将立即看到扇区偏移量位于最后一个分区之后,但位于此盘扇区总数之前。需要记住的一个重要信息是,QEMU大小可能与软件本身在启动过程中使用的读取大小不同。此时的输出结果,可能会让你感到困惑。

根据输出的结果,你可以决定继续进行启动加载程序的静态分析或整个启动过程的动态分析,这取决于你分析的目的是什么。

静态和动态启动分析

要进行静态启动分析时,我们就需要获得包含启动代码的MBR副本。我们可以通过执行以下命令获得代码:

dd if=dc-bios.img of=dc-bootsector.img bs=512 count=1

现在我们可以用我们最喜欢的工具来分析这个副本了,在本文中,我们将使用Ghidra工具, Ghidra是由美国国家安全局(NSA)研究部门开发的软件逆向工程(SRE)套件。如果你尚未进行设置,可以按照此处的说明进行操作。首先我们将创建一个项目,然后通过‘File->Import File’,看到以下内容:

1.png

我们将语言选项设置为“x86:LE:16:Real Mode:default”,然后点击“options”按钮,将基本地址更改为“00:00:7c00”即可。我们的启动加载程序现在已经导入,如果我们双击它,代码浏览器就会打开。不过,显示的却是一堆乱码。现在,我们把光标放在第一条指令上,点击“d”或单击右键并选择“Disassemble”,它将神奇地显示左侧的汇编程序和右侧的伪代码。

2.png

有了这些,我们就可以更好地理解代码的作用。在深入讨论之前,最好了解一下常规启动加载程序,在此,我推荐一篇文章,该文章在逐行解释代码方面做得非常好。这确保我们至少准备好识别一些代码结构,比如将MBR复制到不同内存地址的代码。以下三条内容引起了我的兴趣:

与固定值@ 00:00:7c2e进行比较;

电话:00:00:7c39;

代码块在00:00:7c43;

我通常处我通过谷歌搜索来查找这些信息背后的内容,这些信息之所以有趣,是因为在理想情况下,你会尝试命名所有调用,至少在逆向分析可执行程序时,这是我常用的方法。

代码块应该触发某种deja-vu,下面这段代码会将当前的MBR重定位到一个不同的内存地址,然后在下一条指令上继续执行。

3.png

我们可以通过将光标放在0000:7c5a并进行反汇编来确认,它应该产生有意义的代码:

4.png

如果你像我一样,你会发现前面的解释很难理解,也很难理解使用这种类型的静态分析编写的汇编/反汇编C代码。这通常是我开始怀疑的地方,如果只是看到代码在调试器中工作是否更容易?为了执行启动代码的调试,我们将使用QEMU和GDB。

用GDB来调试用户态程序是一个方便快捷的定位问题的方法,极大的缩短了调试程序和定位问题的时间。而对于内核或者驱动ko的调试或者我们想了解内核运行的某些过程,我们也可以借助于gdb工具, GDB+QEMU的方式是一种比较常见的调试内核和驱动的方法。

设置QEMU和GDB

建议你下载并汇编最新版本的QEMU,在运行QEMU之前,首先启动FUSE脚本的'wrap_directory.py'以打印所有读取和写入,然后使用下面的命令开始模拟(请注意由于wordpress主题导致的换行):

sudo ./qemu-system-i386 -snapshot -m 1024 -drive 
file=~/github/public/boot_loader_reversing/mountpoint/dc-bios.img 
-monitor stdio -s -S -object memory-backend-file,id=mem,size=1024M,
share=on,mem-path=/dev/shm/panda-mem -numa node,memdev=mem

以上的命令会确保我们通过fuse包装器访问映像,并在需要运行volatility、rekall或类似软件时,将内存作为可访问文件启用。此外,它还支持远程GDB调试并在启动时暂停虚拟机,直到你连接GDB并指示它继续执行为止。

至于GDB,我通常会创建一个' dot_gdbinit '文件,然后链接到真正的'.gdbinit'符号。通常我首先在我的'.gdbinit'文件中使用以下命令:

set architecture i8086
target remote :1234
set disassembly-flavor intel
hbreak *0x7c00
continue

第一行命令会尝试强制GDB进入16位模式,由于某些原因,强制进入效果并不好。这会导致逆向分析出现混乱。第二行是将GDB连接到QEMU,第三行设置查看组装内容的首选方式。第4行最重要,因为它可以确保硬件断点设置在将用于开始执行MBR代码的地址上。最后一行将让QEMU继续执行,直到命中断点为止。

使用GDB调试MBR

现在,我们可以通过使用“ni”或“si”遍历代码并广泛使用“hbreak”来检查GDB代码。但是,当我们遇到具有自己的参数和调用约定的中断调用时,这就变成了一个单调乏味的过程。另外,我们不希望手工完成所有装配过程。因此,我们四处搜索并找到了一些很好的示例代码。如果我们将文件'debug_cmds.py'放在与我们启动GDB的目录相同的目录中,并使用'source debug_cmds.py'更新'dot_gdbinit'文件,现在就可以访问以下命令了。

brm-ci <mnemonic>
brm-disassemble [count]
brm-pexi

第一个命令允许我们中断特定的指令,例如:

brm-ci int

该指令将运行MBR代码,直到遇到中断指令。brm-disassemble命令允许我们反汇编代码。为什么要建立我们自己的反汇编命令呢?因为在实际模式中,你需要考虑内存段。这意味着你要用disassemble $cs*16+$pc,+100代替disassemble $pc,+100命令。

为了保险起见,我使用了另一个已实现过的命令,它还让我对GDB和python脚本有了更多的了解。最后一个命令“brm-pexi”是最有趣的一个,因为这是我实现一些中断参数解析并使其更容易理解所发生的事情的尝试。你可以随意提交一个pull请求来扩展它,支持更多的中断调用。下面是一个使用这些新命令调试会话的示例:

(gdb) brm-disassemble 
0x00007c00    xor    eax,eax
0x00007c02    nop
0x00007c03    nop
0x00007c04    jmp    0x7c16  
(gdb) brm-ci int
0x00007c02 in ?? ()
0x00007c03 in ?? ()
[..]
0x00007d03 in ?? ()
0x00007d06 in ?? ()
int    0x13
(gdb) brm-pexi 
called 0x13 - low level disk services
Function 0x41 - Test Whether Extensions Are Available
Function params: 
    DL (drive index) 0x80
    BX (signature) 0x55aa
0x0000d445 in ?? ()
Return values
    CF (clear if present) 0
    AH (error|version) 0x41
    BX (signature) 0x55aa
    CX (supported iface) 0

好多了吧?我们以更简单的方式跳过代码,并自动解码中断调用。

brm-pexi命令包含一个错误,它不会真正执行中断调用。由于我在整个GDB / QEMU设置中遇到了一些问题,所以我就这样设置了,它提醒我正确的寄存器及其值应该是什么。

在这个过程中,我还遇到了一个关于GDB的有趣技巧,你可以在没有源代码的情况下将结构定义加载到GDB中。

set confirm off
add-symbol-file dap-main.o 0
set confirm on

当我们遇到从磁盘实际读取扇区的中断调用时,我们可以看到它的用处:

(gdb) brm-pexi 
called 0x13 - low level disk services
Function 0x42 - Extended Read Sectors From Drive
Function params: 
    DL (drive index) 0x80
    DS:SI (DAP) 0x0:0x7c06
Print DAP structure using: 
    set $dapstruct = *(struct dap *) ($ds*0x10+$si)
    p/x $dapstruct
Print dap buffer (after executing interrupt) using: 
    x/10x $dapstruct.buffer_segment*0x10+$dapstruct.buffer_offset

通过将结构定义加载到GDB中,我们现在可以看到MBR代码将读取的内容:

(gdb) set $dapstruct = *(struct dap *) ($ds*0x10+$si)
(gdb) p/x $dapstruct
$1 = {size = 0x10, unused = 0x0, numsectors = 0x27, buffer_offset = 0x0, 
buffer_segment = 0x2000, startsectors = 0x27fffd1}

在上面的输出中,我们可以看到将读取扇区数据的缓冲区、扇区偏移量和将读取的扇区数量。如果我们继续执行中断,我们可以打印缓冲区数据:

(gdb) x/10x $dapstruct.buffer_segment*0x10+$dapstruct.buffer_offset
0x20000:    0x90909090  0x8ec88cfa  0x31e88ed8  0x8ec38edb
0x20010:    0xbcd38ee3  0x52fb4000  0x5d0000e8  0x8de5c583
0x20020:    0x6609ff9e  0x66085f8b

如果我们想复制由DiskCryptor执行的第二阶段代码,我们可以使用dd命令:

dd if=~/disk-images/dc-bios.img of=dc-bootsector2.img bs=512 skip=41942993 count=39

现在是用Ghidra的时候了,至少在我们再次进入调试模式之前。我们可以像原始MBR一样加载文件,主要的区别在于基本地址现在将设置为2000:0000。

第二阶段分析

当我们在Ghidra加载第二阶段并在第一行进行反汇编时,就应该看到以下内容。

5.png

我们可以立即观察到代码将被复制到另一个地方并跳转过去,因为我们发现了相同的构造,即重复的mov和段以及在返回之前的偏移(这应该是一个很大的跳转,因为整个段都在处理afaik之类的东西)。如果你查看Ghidra中的反汇编代码,就可以更清楚地看到偏移量。

然而,在将代码复制到另一个内存位置之前,还有很多事情要做。我通常更喜欢先了解全局,然后再深入细节。如果你想深入了解细节,你可以看到某种结构正在被使用。

为了查看在Ghidra中复制的代码,我建议你使用GDB。我放置了一个断点@ 2000:005b,理想情况下的输出结果如下:

(gdb) brm-disassemble 5
0x0002005b    rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
0x0002005d    pop    edx
0x0002005e    lea    eax,[esi+0x67]
0x00020061    push   eax
0x00020062    push   es
(gdb) i r
eax            0x2000   8192
ecx            0x931    2353
edx            0x9880   39040
ebx            0x1d 29
esp            0x3ffe   0x3ffe
ebp            0x0  0x0
esi            0xce 206
edi            0x0  0
eip            0x5b 0x5b
eflags         0x246    [ IOPL=0 IF ZF PF ]
cs             0x2000   8192
ss             0x0  0
ds             0x2000   8192
es             0x9880   39040
fs             0x0  0
gs             0x2000   8192

这意味着代码被复制的偏移量是2000:00ce,大小是0x931,因为这是CX寄存器被设置的值。但是跳转并不是简单地复制代码,如果你查看push并使用GDB验证它,它会跳转到0x69f。如果我们想在Ghidra中查看这段代码,我们必须去偏移0xce + 0x69f,因此需要在偏移量2000:076d处进行反汇编,编译后的内容如下。

6.png

同样,我们看到了很多乱码,但其中也包含一个调用,所以让我们先来看看这个调用。

在进行逆向分析时,将动态分析与静态分析结合起来可以大大加快处理速度,并且可以帮助我更好地分析静态代码。

7.png

由于指令'LQDT'和指令'JMPF',这个函数引起了我的注意。如果你已完成了模拟进程,就应该知道,如果要启动实际操作系统,就必须跳转到保护模式。在google上搜索以下命令:

MOV EAX,CR0 OR EAX,0x1 MOV CR0,EAX

第一页就包含了所有与跳转到受保护模式和跳出受保护模式相关的结果,如果我们扩大搜索范围,就会看到以下2个页面:

https://wiki.osdev.org/Real_mode

https://wiki.osdev.org/Protected_Mode

这两个页面包含了更多的示例代码,此时,你就可以识别跳转到受保护模式的代码,然后返回并开始跟踪代码。现在剩下要做的就是花大量时间逆向第二阶段启动加载程序的其余部分,以完全理解它是如何工作的。

由于DriveCrypt是开源的,建议多多进行尝试,同时密切关注源代码,掌握所有细节并熟悉代码结构。

本文翻译自:https://diablohorn.com/2019/05/21/introduction-to-analysing-full-disk-encryption-solutions/如若转载,请注明原文地址: https://www.4hou.com/reverse/18198.html
点赞 3
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论