BetaMao

LUKS全盘加密分析

字数统计: 7.4k阅读时长: 28 min
2019/10/27 Share

纯软件形式的LUKS全盘加密在自解密时能够轻易被获取master key以实现全盘解密。。。

姿势

先列出相关的知识,这些将有助于更深入理解原理,这会在之后用到。。。

linux启动流程

  1. 加电BIOS识别硬件设备并完成自检操作。
  2. 按照设置依次尝试从外部设备启动系统,如磁盘。
  3. 磁盘(MBR格式)第一个扇区是主引导扇区(MBR),存放着BootLoader与分区信息。
  4. BootLoader加载指定位置的内核或者将控制权交给其他分区的Bootloader。
  5. 内核识别硬件设备并加载相应的驱动程序。
  6. 内核初始化完成后创建用户层的第一个程序init
  7. init用于初始化环境,如启动服务,挂载硬盘与提供shell。

细节

  1. linux的启动文件被放置在/boot目录下,其中vmlinuz为内核文件,它是一个可自解压的压缩文件。
  2. 驱动(内核模块)文件未被放置在内核里而是使用其他文件系统与其他目录下(如etx2文件系统 /usr/lib/modules/目录下),此时内核是无法识别文件系统的,也就无法加载识别文件系统的驱动。因此/boot目录下还有一个重要文件initrd,它的作用就是初始化root文件系统并识别驱动文件所在文件系统,即内核能够找到并运行它,它包含了识别驱动文件所在分区的文件系统驱动,因此内核就能通过它加载驱动文件所在的文件系统了,接下来就可以加载所有需要的驱动了。
  3. 现在主流的init系统有sysvinit,upstart,systemd,openrc-init。它们会以不同的格式启动开机自启动程序,到用户层后需要根据各自的特性跟踪对应的配置文件,以跟踪启动过程。
  4. GRUB2是Linux下主流的启动程序(BootLoader),它像是一个小型的系统,能够用来选择并加载真正想要启动的系统,为其设置内核启动参数,或是加载其他的启动程序,甚至解密全盘加密的磁盘。它使用模块方式实现各种功能,目前仅支持Luks1.0全盘加密(包括/boot分区)。

文件系统

基本每种操作系统都用到了文件系统,它的作用是组织管理持久数据,也就是说没有文件系统磁盘上的文件依然可以被使用,但是无法直接使用文件系统的各种有用功能(如日志事务,访问控制,链接文件甚至查看文件名与文件修改日期等),Linux支持很多种文件系统,如etx3,fat32及btrfs等,用户层不必直接处理各种文件系统的差异因为有虚拟文件系统屏蔽底层区别,内核也不必将所有文件系统包含进去而是以驱动的形式加载,由于device mapper机制,可以轻易实现系统堆叠,如下图为在真实磁盘分区上上堆叠了LVM,RAID,dm-crypt与文件系统etx2:

1
2
3
4
5
6
7
8
9
10
11
Ext2 Filesystem     <- top
|
Encryption
|
RAID
|
LVM
|
Raw partitions
|
Raw disks <- bottom

全盘加密

对文件加密可简单分为文件系统加密与全盘加密,前者加密的对象是文件系统中单一的文件,将会把文件作为一个整体进行加密,该方式不同文件可使用不同的密钥,并实现按需解密,缺点是未隐藏文件元信息(如文件大小,所有者,文件名等),另外可能需要空间存储额外的数据(如IV等)以及无法做到随机访问(若要访问加密后的文件的某一部分也必须将文件完整解密)。对于全盘加密,或者分区加密,它将使用透明加密的方式,只需要输入一次密码解密分区或磁盘,之后活动状态中的所有文件操作对用户来说都是透明的:

1
2
用户写->内核加密数据->写入磁盘
用户读->内核读磁盘->解密数据

该方式的好处就是能够实现随机访问,透明操作且效率较高,能够隐藏所有数据(包括文件元数据等),而且能够轻易实现数据销毁(比如有核平技术将能轻易实现[9]),缺点就是无法只解密指定文件,整个系统只使用一个加密密钥等。在全盘加密中1.密文需要和明文占据同样大小空间,2.随机访问,所以加密的单元一般为扇区,加密用到的IV需要就地取材而不是随机生成。

LUKS

运行过程

LUKS是Linux Unified Key Setup的简称,它其实就是一种磁盘(全盘)加密格式,底层使用的是dm-crypt进行加解密操作,前端使用cryptsetup进行命令行控制,即它本身未实现任何密码算法,而是调用的内核提供的密码算法实现,它只是自定义了加密后分区格式,这种格式能存储加密与校验的元信息,方便密钥管理与使用等,它的基本使用方法如下:

  1. 将分区格式化为luks格式:cryptsetup --cipher aes-xts-plain64 --keysize 512 --hash sha512 --iter-time 55000 luksFormat /dev/sda2。这个过程会提示用户输入密码(该密码将会用于解密分区),输入密码后将根据指定的参数对分区进行格式化,即添加luks头等信息以及加密分区的剩余部分(用于存储数据的部分)。
  2. 打开加密后的分区:cryptsetup luksOpen /dev/sda2 betamao。此时会要求用户输入密码以解密分区,只有解密分区后才能对分区进行操作,解密后的分区将由dm映射为/dev/mapper/betamao,之后的操作都将在该设备上进行,对其读操作会转换为都/dev/sda2对应的加密数据,并返回其解密后的形式,写操作同理,这个操作对用户来说是透明的。
  3. 格式化分区:mkfs.ext4 /dev/mapper/betamao。解密映射后的分区是没有格式化的,为了更好的使用,一般需要根据需要将其格式化为操作系统能够识别的文件系统,比如这里为ext4,格式化后将使用文件系统的功能了(比如软连接)。
  4. 挂载分区:mount /dev/mapper/betamao /home/beta/test。像正常的添加磁盘操作一样,将分区挂载到合适的目录,就能对其进行文件读写操作了。
  5. 锁定分区:umount /home/beta/test;cryptsetup close /dev/sda2或者直接关机,这样分区就被重新锁定了,只有拥有密码才能访问它里面的文件。

注:此处的分区不一定是磁盘上真实的分区,它可以是一个文件(虚拟分区),也可以是一个逻辑卷。

内部实现

AFsplitter

首先说下它,该技术为秘密共享技术,它将密钥分成任意多份,分别存储在不同的位置,只有拿到所有分割密钥才能恢复原始密钥并解密数据,最直观的方法如下图:

D为明文(或者此处的原始密钥),$S_1$到$S_{n-1}$为随机生成的数据,通过异或得到$S_n$,那么$S_1$到$S_n$的运算就能恢复出D,其中任意$S_x$受损都会影响D,这就是D的反取证分解(当然示例的分解无法扩散变化,实际使用的算法会用hash等方法来达到$S_n$某一位变化影响M的多位)。

该技术的特点是任意一份分割秘密受损将无法恢复明文,若要实现按比例恢复,可使用shamir门限方案等。

数据结构

luks的数据结构如下[4]:

  1. LUKS phdr头部存储的磁盘加密的元信息,比如上面命令指定的加密算法aes,摘要算法sha512,迭代次数55000以及随机盐等信息。
  2. KMn为密钥槽信息,luks不会直接使用用户提供的密码加密数据(因为这种密码熵太小且无法修改),它会随机生成一个主密钥用于加密磁盘数据,该主密钥将会由用户输入的密码进行PBKDF2等算法转换,用转换后的密码进行加密后存储,这样就可以实现多密钥管理,luks1提供最多8个密钥槽,允许用户最多设置8个不同的密码。
  3. bulk data由主密钥加密,综上一种典型的模式(TKS1)如下:
    1
    enc−data = encrypt(cipher−name, cipher−mode, key, original, original−length)
    解密过程
    用户输入passphrase经过PBKDF2(LUKS2新增bcrypt等算法支持)生成解密密钥:
    1
    2
    3
    4
    5
    6
    7
    key = PBKDF2(
    hash-spec, # 使用的PRF,存储在头部
    passphrase, # 用户输入的解密磁盘的密码
    salt, # 盐,初次随机生成后存储在luks分区的头部
    iteration-count, # 迭代轮数,一般根据硬件性能自适应生成,从分区头部获得
    derived−key−length # 生成的key的长度,从分区头部获得
    )
    同时将分布在磁盘各处的分散密钥合并:
    1
    2
    3
    4
    5
    6
    split-material = AFsplit(
    unsplit-material, # 未分割的长度
    length, # 每份的长度
    stripes # 分割的份数
    )
    unsplit-material= AFmerge(split-material, length, stripes)

    D是原始数据(unsplit-material),H是hash函数(默认为sha1,由hash-spec指定),两者结果参与运算即可得到主密钥,主密钥是唯一的并且基本不会变化:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    read phdr from disk
    check for correct LUKS_MAGIC and compatible version number
    masterKeyLength = phdr.key−bytes
    pwd = read password from user input
    foreach active keysplot in phdr do as ks{
    pwd−PBKDF2ed = PBKDF2(pwd, ks.salt, ks.iteration-count, masterKeyLength )
    read from partion (encryptedKey , // destination
    ks.key−material−offset , // s e c t o r number
    masterKeyLength ∗ ks.stripes) // number of bytes
    splitKey = decrypt(phdr.cipherSpec, // c i p h e r spec .
    pwd−PBKDF2ed, // key
    encryptedKey , // content
    encrypted ) // content length
    masterKeyCandidate = AFmerge (splitKey, masterkeyLength, ks.stripes)
    MKCandidate−PBKDF2ed = PBKDF2( masterKeyCandidate ,
    phdr.mk−digest−salt,
    phdr.mk−digest−iter,
    LUKS_DIGEST_SIZE)
    if equal ( MKCandidate−PBKDF2ed, phdr.mk−digest) {
    break loop and return masterKeyCandidate as correct master key
    }
    }
    return error: password does not match any keysplot
对称加密模式

以AES为例,作为分组密码,它只能处理128比特大小的数据,实际使用时需要配合指定的操作模式,常见的操作模式有ECB,CBC,CTR,OFB,CFB等,而在磁盘加密上主要是使用的是CBC,LRW和XTS。其中大部分工作模式都需要额外的IV,随机IV必须被存储,这部适用于全盘加密,于是全盘加密上的IV将由数据位置等信息生成,分如下几种:

  1. plain:初始向量是扇区号的32位小端版本,必要时用零填充。
  2. plain64:初始向量是扇区号的64位小端版本,必要时用零填充。
  3. plain64be:初始向量是扇区号的64位大端版本,必要时用零填充。
  4. essiv:“加密扇区|盐初始向量”,使用salt作为密钥,使用批量密码对扇区号进行加密。盐通过散列来源于批量密码的密钥。请注意,虽然密码算法始终与用于数据加密的算法相同,但其密钥大小取决于使用的散列算法。换句话说,虽然数据加密可以使用AES-128,但使用SHA256的ESSIV计算将使用AES-256。ESSIV将哈希算法作为选项,因此格式为essiv:hash,例如essiv:sha256。
  5. null:初始向量始终为零。提供与过时的loop_fish2设备的兼容性。

此处重点说明XTS-AES工作模式[19],它是XOR-Encrypt-XOR Tweakable Block Cipher with Ciphertext Stealing Advanced Encryption Standard的简称,被设计用于使用固定长度的data units的存储设备加密[11],也是当前luks默认的(推荐的)工作方式。它支持两种密钥长度256/512bits,实际上它使用的是AES128/AES256,由于引入了tweak value才使密钥长度翻倍。

  1. disk block或叫做data unit(如一个扇区)是一个长度不小于128bts的数据,它在之后将会被分成多个cipher block(每个128bit,即AES的分组操作),对于最后一个分组若不满足大小,将使用密文窃取技术;
  2. 各cipher block之间独立(即它实际使用的类似EBC工作模式,没有CBC那样的dependency);
  3. cipher block的加密输入包含了index信息(扇区号和块号);
1
2
3
4
5
6
7
8
9
10
C← XTS-AES-blockEnc(Key, P, i, j)
"""对1个cipher block(128bits)进行加密

param: Key 是256或512比特的XTS-AES key,可以被分为key1|key2
param: P 为明文plaintext
param: i 为128比特的tweak值,它是连续的非负整数,如用扇区号
param: j 为disk block内对应cipher block的下标

returns: C 为密文ciphertext
"""

它的内部过程如下,其中乘法为模$ GF(2^{128})=x^{128} + x^{7} + x^{2} + x + 1$上的乘法,$\alpha$是它的一个生成元:

可扩展的,对于一个disk block的加密如下图:

可见其实XTS-AES不算一种全新的加密算法而是一种加密方案,在最下层它就是普通的AES加密,所以256比特或512比特长的密钥只是两个密钥的连接,在实际使用时对半切割就好了。

注:Ciphertext stealing(密文窃取)即加密时把倒数第二个分块的密文部分内容移动到最后一个块的明文部分,使最后一个块填充后再对最后一个块加密,这样密文和明文大小就不会有变化,解密时先解密最后一个分块,再将解密后的属于倒数第二块的密文和倒数第二块组合成一个完整块,这样就能解密倒数第二块了。(密文窃取只会影响最后两个块,其他块该咋的还咋的)

思路

流行的方法为冷启动攻击[10]与类似的邪恶女仆攻击:

  1. 硬核方法:柯克霍夫准则说啦安全性应取决于密钥保密,那么有弱密码的话再好的设计也不管用,搜了下网上有两类针对LUKS的密码猜解方案,第一种是使用如john等工具来生成密码并使用脚本自动测试密码是否正确,明显的这是一种极其低效的手段;第二种就是使用hashcat,其已经实现了luks解密模式,配合上宇宙第一快的hashcat能够以较快的速度破解,然而密码猜解这种天命行为毕竟不那么靠谱,只能说是最后的办法了,不过编写该解密模式的作者其中使用熵来判断是否猜解成功的思路挺帅的。
  2. 取巧方法:由于系统可正常启动,磁盘又是全盘加密,被加密的分区必定在加载系统前先被解密再挂载,使用cryptsetup luksDump等命令可以确认被加密的磁盘使用了luks的aes256加密,分析mbr发现使用了grub,搜索网络资料,有以下思路:
  • 分析grub,既然系统都被加密了还能正常启动,肯定是在启动grub之后,将控制权转交给被加密的内核前完成的解密操作,密钥一定在grub相关的文件里(生成),这就被限定在很小的范围了,通过逆向分析即可获取密钥。
  • 向系统注入shell,该操作可以在系统启动时文件解密前的各个阶段进行,比如直接hook grub,当拿到shell后就可以直接取消锁或者拿文件了。
  • 当系统运行时,dump虚拟机内存,从内存中查找密钥,这种方式的可行性是假设密钥是被普通的保存在内存中而不是存放在加密硬件中的,既然使用了透明加密,密钥在磁盘被使用时一定会被存储在计算机的某个位置。
  • 从虚拟机启动后提供的服务或shell登入,再进行必要的提权操作,简单的测试了弱密码发现登陆shell失败(事实上在后来的分析中也发现这种方式基本不可能成功)。
    明显的第三种方法最简单,于是选用它。

操作

内存AES抓取

原理

device-mapper实现了透明加密,在启用(解密)设备后,用户对文件读时会将加密后的文件解密后提交给用户层,写操作相反,整个过程都会涉及到加解密,所以密钥一定存放在某个地方,在此处为内存,那么dump内存后将能够得到密钥相关的数据了。但是转储的内存如此大如何找到密钥文件是最难的事,最直接的方法是顺序遍历内存的每一字节,并读取密钥长度的数据作为密钥去解密数据或者验证hmac,但是这是一个低效的事,直接的提高效率的办法是先通过信息熵找到疑似密码的区域(熵高出阈值)再去验证,不过有另一种更精确也能容错与自动更正内存错误(如密钥发生1bit错误时试解密的方法将会失效而下面的方式将能够成功找到密钥并修正错误)的方式,那就是根据轮密钥生成算法的属性。
以AES为例,标准的AES加密有一种分组长度(128位)及三种密钥长度(128,192,256位),三种密钥长度在实际加密过程中会对一个分组分别执行10,12,14轮加密,例如AES128会将16字节的密钥扩展为11组共176字节的轮密钥,除了第一轮其他每一轮加密用对应的那一个轮密钥,其中轮密钥只与16字节的输入密钥相关,与明文或密文无关,那么知道扩展密钥中任何连续的Nk个字(Nk代表密钥长度,如AES128的Nk为4,此处4字节为一个字)能够重新产生整个扩展密钥(比如AES128 扩展密钥444个字,只要知道其中连续的4个字就可以恢复输入的16字节的密钥),如下图,得到足够长度后能够推出整个扩展密钥。
AES key schedule
另一方面,在AES的实现中,轮密钥的位置一般是连续的,所以只需要遍历内存,并计算一个完整的AES key schedule,那么他们应该满足轮密钥的生成关系,换句话说就是一片内存区域的数据如果能够满足轮密钥生成算法的约束那么就可以猜测该区域存储的是AES的扩展密钥,也就可以计算出该区域的AES主密钥,而且若设定容错阈值,那么可以实现小部分不匹配依然能识别的情况。
*
注:尽管AES需要使用特定的操作模式才能进行实际的对称加密操作,但是显然的磁盘加密时不会将一个磁盘(或分区等)作为一个整体加密,否则无法做到随机存取,因此操作模式不会对密钥恢复造成任何影响。另一方面,由于AES用了轮常数所以可以很容易定位当前轮数。当然,当前有工具已经实现了AES Key查找**

内存dump

内存dump的工具非常多,windows下常见的有prodump,dumpit,process explorer或调试器等,Linux下可以直接复制内存的虚拟映射,但是此处我们需要dump的是虚拟机系统里的内存,virtualbox提供了VBoxManage管理工具可用于该操作,VMware无类似工具但是实际上挂起客户机即可拿到其内存文件,该文件位于目标虚拟机(一般也为虚拟磁盘)文件目录下。

解密步骤

使用virtualBox导入镜像,运行,待开始初始化时执行内存转储:

1
~ VBoxManage debugvm aurora-universal dumpvmcore --filename=lm.raw

之后使用findaes获取内存中的密钥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
~ findaes.exe lm.raw
Searching C:/Users/q/Desktop/lm.raw
Found AES-256 key schedule at offset 0x145f83ac:
4c de 03 ef 27 ff 12 9c 12 78 13 15 d8 73 48 35 40 fc 22 9c 06 78 44 ac 1d db 4c f0 39 75 1c 3b
Found AES-256 key schedule at offset 0x1472afac:
1f 2d 84 f0 64 a3 33 cd ba 45 17 f5 b1 52 37 5a ca 11 34 3c 97 e1 a2 8c 94 e9 66 fe 6a eb 0a 56
Found AES-256 key schedule at offset 0x1a45dbac:
36 6b 25 69 3f 1b 38 41 ef 6d d7 83 a8 30 97 ce 62 37 3d c6 8a 94 64 8f 85 1e 97 fd a0 6a d4 07
Found AES-256 key schedule at offset 0x1a45dfac:
41 40 eb 86 06 28 76 7f 40 73 24 40 70 43 da 0e cf 8c 99 d3 7b 23 ce fa bd eb d4 98 a6 7e 69 f6
Found AES-256 key schedule at offset 0x224557ac:
67 c7 0b be 3c 18 46 de ee f6 92 17 0c b8 bc 1c be 6e 18 a3 79 f3 0a f8 f6 56 15 0f fc eb 0a 10
Found AES-256 key schedule at offset 0x22455bac:
fd d9 17 22 db 16 08 6d ba e8 3c 62 f9 be 39 6d 57 6c ed 1c 22 d6 41 2f 49 31 90 3b dd 30 e1 cb
Found AES-256 key schedule at offset 0x22455fac:
81 39 48 7a 2f 57 01 13 d8 8a 4a d5 97 ac d2 0a 25 31 ca d3 e7 0b 61 d6 ae 59 5e 51 e8 39 88 4e
Found AES-256 key schedule at offset 0x26cb5fac:
5b f8 da d9 a1 02 71 a8 b5 0c de d5 b2 85 88 90 6c 53 d2 fa f1 a9 c5 5f 31 d1 4e 91 93 7c e8 1f
Found AES-256 key schedule at offset 0x2d5617ac:
ba 2f 76 d6 19 60 2a 2b ad 90 2e 21 40 74 df 84 cb fb 85 0a ea d7 08 95 ae b8 aa aa 71 49 86 d4
Found AES-256 key schedule at offset 0x358b4bac:
2c 7b c9 72 fa 44 d5 c7 a6 a4 8f 90 e0 a4 cc 49 1f f2 3d 48 5b 34 4d 0b b7 5e 70 cb 2d 80 89 31
Found AES-256 key schedule at offset 0x358b4fac:
ed 36 83 4d cf 90 69 9e 0a b2 6c 04 1c 77 fa bc c3 e7 6a 72 92 72 73 02 1a 03 b3 f7 44 68 52 c5
Found AES-256 key schedule at offset 0x35a123ac:
0d a2 0f 92 ee b5 bc e4 53 ee f3 c7 f1 55 6f 5a 27 b4 66 44 d7 65 06 a8 c0 f3 03 a5 38 2b 48 5e
Found AES-128 key schedule at offset 0x363bf3ac:
3c 4a 26 8f 0b 65 19 8c 5f ea 9d 9a 59 fc dd 99
``
如上可能会发现很多AES密钥先记录之后逐个尝试,接着在XXX虚拟机里添加含有kali系统的虚拟磁盘(若是反过来从kali里添加XXX的虚拟磁盘似乎会出错),设置为从kali启动,此时kali会自动挂载XX的磁盘,可使用fdisk列出磁盘:
```sh
~ fdisk -l

接着使用cryptsetup获取每个加密分区的luks加密元信息,主要关注注释部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
~ cryptsetup luksDump /dev/sdb2

LUKS header information for /dev/sdb2

Version: 1
Cipher name: aes # 数据部分的加密算法
Cipher mode: cbc-essiv:sha256 # 数据部分的加密模式
Hash spec: sha1 # PBKDF等使用的哈希算法
Payload offset: 4096
MK bits: 256 # 主密钥长度
MK digest: 86 61 8d 76 01 47 fa 65 91 62 fa f8 d6 67 aa 45 f3 96 7b 2f # 主密钥hmac摘要
MK salt: f0 70 79 ab 31 7b 58 c4 21 ed 87 53 6e ec 52 2c # 主密钥hmac盐
e9 82 c9 fb b8 65 33 55 2b 2b e4 dd a2 97 a9 58
MK iterations: 50875 # 主密钥迭代次数
UUID: ced1812d-df83-4f09-89b5-7db293ba80e0

Key Slot 0: ENABLED # 0号密钥槽已被使用
Iterations: 203708 # 用户密钥到加密主密钥加密蜜月的迭代次数
Salt: 05 b2 79 f9 70 b7 a1 c2 03 bb c9 18 64 b8 6e 09
cb 04 de eb b0 0c 30 1e df 6f 09 4e d1 e8 09 b8
Key material offset: 8
AF stripes: 4000 # 分割片数
Key Slot 1: DISABLED
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED

可见主密钥256位,通过pbkdf2函数验证内存中的aes密钥获得对应位置的主密钥:

1
2
3
4
5
6
7
8
mk-salt = ''   # 盐
aes-keys = [] # 可能的AES密钥
mk-digest = '' #
mk-itera = 50875 #
for aes-key in aes-keys:
x = hashlib.pbkdf2_hmac('sha1', aes_key.decode('hex'), mk_salt.decode('hex'), mk_itera, dklen=20)
if binascii.hexlify(x).decode() == mk-digest:
print(aes-key)

直接使用主密钥可以设置新的密码:

1
cryptsetup luksAddKey /dev/sdb2 --master-key-file <(echo '2c7bc972fa44d5c7a6a48f90e0a4cc491ff23d485b344d0bb75e70cb2d808931' | xxd -r -p)

再输入新设置的密码即可打开加密分区(当也可以直接使用主密钥解密分区,但是此处的IV太过复杂)。

注:类似的XTS-AES会需要组合两个密钥,直接将dump的密钥连接即可,可使用脚本枚举验证HMAC。

提升

我们当前的实现中,虽然密钥隐藏起来了,但是在启动后依然会被装载入内存,之后该密钥将能够被直接从内存dump,并使用公开资料解密数据,该过程难度较低,为此已经有人做了一些研究,软件层面上大体可分为三类:

  1. 不修改算法,将密钥存放在不易被dump的区域,例如始终存于寄存器,存于cache里等。这是在算法的实现上做文章,不会影响原数据加解密的结果,能兼容已有加密文件,缺点是需要特定的硬件,占用宝贵的高速存储资源,最关键的是在我们的虚拟机镜像上完全失效,直接pass。
  2. 自修改加密算法,以AES算法为例,我们可以自定义S盒,或者改变加密轮数,亦或者修改其他操作。这样即使能从内存中dump密钥(若改变密钥扩展算法将无法从dump的内存中找到密钥),也无法直接解密数据,当然这样的缺点是算法改变后所有数据都需要重新加密,而且这是密码学的安全性转换为软件保护的安全性。
  3. 另一种可选的方法是使用白盒密码,白盒密码就是应对这种情况产生的,它的思路是把密钥放置在算法里面,这样既不会改变加密解密的结果也不会在内存中出现真实密钥,可惜的是当前并没有兼顾效率与安全性的白盒AES密码实现,不过我们的对手不是密码学专家,他不一定能通过数学手段破译,所以牺牲掉一定的密码学安全性,把破解转移到了软件逆向之上是有用的。

想法

提出一种方案,在一定程度上提升全盘保护的强度,思路为对抗findaes等攻击dump密钥:

  1. 我们的启动引导程序是GRUB2,它当前支持luks1.0,相关代码是直接集成到项目之中的(在grub-core/diskgrub-core/lib/libgcrypt目录下),包括加解密部分,所以我们对luks算法的修改不会影响系统的正常功能。
  2. 修改工作模式部分,比如它的IV,以IV PLAIN64为例,它的原始值为扇区值:
    1
    2
    3
    4
    5
    6
    case GRUB_CRYPTODISK_MODE_IV_PLAIN64:
    iv[1] = grub_cpu_to_le32 (sector >> 32);
    /* FALLTHROUGH */
    case GRUB_CRYPTODISK_MODE_IV_PLAIN:
    iv[0] = grub_cpu_to_le32 (sector & 0xFFFFFFFF);
    break;
    我们可以将该值进行任何任何运算,当然这里修改后所有数据需要全部重新加密,所以操作是可选的。
  3. 修改算法部分,如上所述选择白盒密码,当前已有一些不成熟的WBDES,WBAES,WBSM4算法实现,我们可以选择一种性能符合要求的非主流算法进行实现,例如《面向智能终端的白盒密码技术研究与实现》中所描述的算法,这部分的修改不会改变加解密的结果也能防止密钥被dump,是修改的核心。更简单的,根据findaes原理,只要减少连续内存区的熵,增加重复数据,改变汉明距离即可绕过其搜索算法,所以只要修改其密钥排布就行啦~
  4. 转向软件保护,各种技巧都可以用上,比如当前的密钥隐藏技巧,尽管现在已经不需要外置密钥了,还是可以藏一个假的在那里误导攻击者,至于其他保护方式就太多了,各种混淆都可以上,不过由于分区需要被自动解密,决定了全盘保护的上限,所以为了效率不必多费功夫。

总结

在无专用加密设备,做纯全盘加密时,由于系统运行需要解密数据,在这种情况下只要能够物理访问磁盘就一定能够(如代码提升)获取加密密钥并解密磁盘,所以全盘加密不应该做为主要的版权数据保护手段,而应该在其他部分(如数据代码混淆,使用native库)下功夫,在全盘加密上,可以使用较小众的工具算法与模式防止现有工具直接破解,提高分析门槛。

参考

CATALOG
  1. 1. 姿势
    1. 1.1. linux启动流程
      1. 1.1.1. 细节
    2. 1.2. 文件系统
    3. 1.3. 全盘加密
    4. 1.4. LUKS
      1. 1.4.1. 运行过程
      2. 1.4.2. 内部实现
        1. 1.4.2.1. AFsplitter
        2. 1.4.2.2. 数据结构
        3. 1.4.2.3. 解密过程
        4. 1.4.2.4. 对称加密模式
  2. 2. 思路
  3. 3. 操作
    1. 3.1. 内存AES抓取
      1. 3.1.1. 原理
      2. 3.1.2. 内存dump
    2. 3.2. 解密步骤
  4. 4. 提升
    1. 4.1. 想法
  5. 5. 总结
  6. 6. 参考