内容简介:关于启动加载器(注意:本文中描述的方法和技术只适用于未开启预启动认证机制的全盘加密的情况,因为只有这样,你才可以获得加密盘的解密密钥。如果碰到需要开启预启动身份验证的情况,则可以获取的信息可能只有元数据或“已删除”文件。让我们看看在具体研究时,可能遇到的信息类型:
关于启动加载器( 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’,看到以下内容:
我们将语言选项设置为“x86:LE:16:Real Mode:default”,然后点击“options”按钮,将基本地址更改为“00:00:7c00”即可。我们的启动加载程序现在已经导入,如果我们双击它,代码浏览器就会打开。不过,显示的却是一堆乱码。现在,我们把光标放在第一条指令上,点击“d”或单击右键并选择“Disassemble”,它将神奇地显示左侧的汇编程序和右侧的伪代码。
有了这些,我们就可以更好地理解代码的作用。在深入讨论之前,最好了解一下常规启动加载程序,在此,我推荐一篇文章, 该文章 在逐行解释代码方面做得非常好。这确保我们至少准备好识别一些代码结构,比如将MBR复制到不同内存地址的代码。以下三条内容引起了我的兴趣:
与固定值@ 00:00:7c2e进行比较;
电话:00:00:7c39;
代码块在00:00:7c43;
我通常处我通过谷歌搜索来查找这些信息背后的内容,这些信息之所以有趣,是因为在理想情况下,你会尝试命名所有调用,至少在逆向分析可执行程序时,这是我常用的方法。
代码块应该触发某种deja-vu,下面这段代码会将当前的MBR重定位到一个不同的内存地址,然后在下一条指令上继续执行。
我们可以通过将光标放在0000:7c5a并进行反汇编来确认,它应该产生有意义的代码:
如果你像我一样,你会发现前面的解释很难理解,也很难理解使用这种类型的静态分析编写的汇编/反汇编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加载第二阶段并在第一行进行反汇编时,就应该看到以下内容。
我们可以立即观察到代码将被复制到另一个地方并跳转过去,因为我们发现了相同的构造,即重复的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处进行反汇编,编译后的内容如下。
同样,我们看到了很多乱码,但其中也包含一个调用,所以让我们先来看看这个调用。
在进行逆向分析时,将动态分析与静态分析结合起来可以大大加快处理速度,并且可以帮助我更好地分析静态代码。
由于指令'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是开源的,建议多多进行尝试,同时密切关注源代码,掌握所有细节并熟悉代码结构。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。