首页
社区
课程
招聘
某网络设备解密的两种思路
发表于: 3天前 1061

某网络设备解密的两种思路

3天前
1061

0x1

这是环境搭建好的页面,无法进入到底层的shell界面

使用vmware将搭建好的系统盘挂载到另外一个虚拟机上

Disk /dev/sda: 100 GiB, 107374182400 bytes, 209715200 sectors
Disk model: VMware Virtual S
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: gpt
Disk identifier: 7C0CDB37-340D-4226-A8F1-FC9EBAC6D294

Device       Start       End   Sectors  Size Type
/dev/sda1     2048      4095      2048    1M BIOS boot
/dev/sda2     4096   1054719   1050624  513M EFI System
/dev/sda3  1054720 209713151 208658432 99.5G Linux filesystem


Disk /dev/mapper/groupZ-home: 6.72 GiB, 7218397184 bytes, 14098432 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


Disk /dev/mapper/groupA-home: 4.87 GiB, 5226102784 bytes, 10207232 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


Disk /dev/mapper/groupA-runtime: 19.46 GiB, 20891828224 bytes, 40804352 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

这里是挂载出来的所有的磁盘内容,这里直接创建三个目录挂载出来,发现了加密文件就是这三个,只需要对这三个文件进行解密即可

root@eve:~# mount /dev/mapper/groupZ-home /tmp/sda2
mount: /tmp/sda2: unknown filesystem type 'crypto_LUKS'.

root@eve:~# mount /dev/mapper/groupA-home /tmp/sda2
mount: /tmp/sda2: unknown filesystem type 'crypto_LUKS'.

root@eve:~# mount /dev/mapper/groupA-runtime /tmp/sda2
mount: /tmp/sda2: unknown filesystem type 'crypto_LUKS'.

内核启动的时有调用crypto进行加密,其中也有调用api进行解密,那么这个加密与解密的过程就在虚拟机的内存当中,现在只需要对虚拟机的内存文件进行扫描密码即可

   1.boot loader把内核以及initrd文件加载到内存的特定位置。

   2.内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理。

   3.内核将initrd的内容保存在rootfs下的/initrd.image文件中。

   4.内核将/initrd.image的内容读入/dev/ram0设备中,也就是读入了一个内存盘中。

   5.接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。

   6..如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。

   7.执行initrd上的/linuxrc文件,linuxrc通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动,以及加载根文件系统。

   8./linuxrc执行完毕,常规根文件系统被挂载

   9.如果常规根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd。否则如果/initrd目录不存在,/dev/ram0将被卸载。

   10.在常规根文件系统上进行正常启动过程 ,执行/sbin/init。

这里将sda1sda2sda3分别挂载出来其中有grub.cfg,在grub.cfg中进入Current会进入到系统A分区,这里的内容已经告诉我们需要对GroupA组当中的磁盘进行解密,因为GrpupZ是恢复工厂设置

....
menuentry "Current" {

set root=(hd0,2) //将GRUB的根文件系统设置为第一块的第二个分区

linux /kernel system=A rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware
//采用了A/Z双分区

initrd /coreboot.img //初始内存盘的文件路径
}

menuentry "Factory Reset" {

set root=(hd0,1)

linux /kernel system=Z noconfirm rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware

initrd /coreboot.img

}

使用findaes工具对内存文件扫描获取的密钥,这里的密钥就是从系统A分区的内存中得到的

PS C:\Users\admin\Documents\Virtual Machines\ivanti> findaes.exe .\ivanti-ce6dee15.vmem
Searching .\ivanti-ce6dee15.vmem
Found AES-256 key schedule at offset 0xa511916c:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f

...

Found AES-256 key schedule at offset 0xf4586070:
f1 a8 aa 2b cc 4f d4 66 53 73 eb 56 81 d7 b7 9e 65 ec 1b 8e bf b9 2f 7d 71 c7 da 8e 80 95 91 72
Found AES-128 key schedule at offset 0xf4c6a040:
e1 fc 5e b7 d8 41 58 da ba d8 eb bc f6 cd 2a 18

构造一个python脚本来对密码进行匹配

import re
import subprocess
import binascii
import os

TARGET_DEVICES = [
    "/dev/mapper/groupA-home",
    "/dev/mapper/groupA-runtime",
    "/dev/mapper/groupZ-home"
]

def parse_keys_from_log(filepath):
    """从 findaes 日志中提取并清洗出有效的唯一 AES 密钥"""
    valid_keys = set()
    hex_pattern = re.compile(r'^([0-9a-f]{2}(?: [0-9a-f]{2})+)$', re.IGNORECASE)
    
    if not os.path.exists(filepath):
        print(f"[-] 找不到密钥文件: {filepath}")
        return []

    with open(filepath, 'r') as f:
        for line in f:
            line = line.strip()
            if hex_pattern.match(line):
                if '00 01 02 03' not in line:
                    clean_hex = line.replace(' ', '')
                    valid_keys.add(clean_hex)
                    
    return list(valid_keys)

def test_device(device, keys):
    """针对单个设备测试所有密钥"""
    if not os.path.exists(device):
        print(f"[-] 警告: 目标设备 {device} 不存在,跳过。")
        return False

    mapper_name = f"decrypted_{os.path.basename(device)}"
    tmp_key_file = f"/tmp/test_key_{os.path.basename(device)}.bin"

    print(f"\n[*] 开始测试设备: {device} (共 {len(keys)} 个候选密钥)")

    for hex_key in keys:
        try:
            with open(tmp_key_file, 'wb') as f:
                f.write(binascii.unhexlify(hex_key))

            cmd = [
                'cryptsetup', 'luksOpen', device, mapper_name,
                '--master-key-file', tmp_key_file
            ]
            
            result = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            
            if result.returncode == 0:
                print(f"[+] 破解成功! 设备 {device} 的 Master Key 是:")
                print(f"    {hex_key}")
                print(f"[+] 已成功挂载到: /dev/mapper/{mapper_name}")
                os.remove(tmp_key_file)
                return True
                
        except Exception as e:
            continue

    print(f"[-] 匹配失败: 没有任何提取的密钥可以解密 {device}。")
    if os.path.exists(tmp_key_file):
        os.remove(tmp_key_file)
    return False

def main():
    key_log_file = "keys.txt"  
    
    print("[*] 正在解析内存提取的密钥日志...")
    keys = parse_keys_from_log(key_log_file)
    
    if not keys:
        print("[-] 未能提取到有效的十六进制密钥,请检查 keys.txt 文件内容。")
        return

    print(f"[*] 成功清洗并提取了 {len(keys)} 个唯一的有效候选密钥。")
    
    for target in TARGET_DEVICES:
        test_device(target, keys)

if __name__ == "__main__":
    main()

执行脚本后成功把系统A分区的磁盘解密

root@eve:/tmp# python3 1.py

[+] 破解成功! 设备 /dev/mapper/groupA-home 的 Master Key 是:

ef6c94e31dd6093de0d3ab47beeda767a8f66285a4a46f872503462b2ae4a741

[+] 已成功挂载到: /dev/mapper/decrypted_groupA-home

[+] 破解成功! 设备 /dev/mapper/groupA-runtime 的 Master Key 是:
40b15427c2463521f5a972003b66ea905307868a4d72ff02196a31e5e7c0e489

[+] 已成功挂载到: /dev/mapper/decrypted_groupA-runtime

这里解密后将解密后的磁盘进行挂载就可以得到文件系统了

root@eve:/tmp# ls -l /tmp/sda1
total 24
drwxr-xr-x 2 675 511 4096 Oct 5 2024 boot
drwx------ 2 root root 16384 Apr 28 21:43 lost+found
drwxr-xr-x. 25 root root 4096 May 2 01:17 root
root@eve:/tmp# cd sda1/root/
root@eve:/tmp/sda1/root# ls
4.17.00-x86_64 bin boot cgroups conf data dev etc gzip home lib lib64
mnt2 modules pkg proc root run runtime sbin selinux sys tmp usr va
var webserver

拷贝一个支持相同架构的busybox,因为既要有pl脚本输出的内容,也需要后门权限
添加后门(busybox需要权限否则开机时执行不了命令)

system("/sbin/iptables -A INPUT -p tcp --dport 8383 -j ACCEPT");
system("/home/bin/busybox telnetd -l /bin/sh -p 8383");

配置完成后卸载已经挂载的磁盘

umount /dev/mapper/decrypted_groupA-home
umount /dev/mapper/decrypted_groupA-runtime

关闭并锁定 LUKS 加密卷

cryptsetup luksClose decrypted_groupA-home
cryptsetup luksClose decrypted_groupA-runtime

直到输出lsblk命令,所有的挂载、解密层和 LVM 逻辑卷已经完美卸载并彻底分离

root@iotseczone:/dev/mapper# lsblk

......

sda                  8:0    0   100G  0 disk
├─sda1               8:1    0     1M  0 part
├─sda2               8:2    0   513M  0 part /boot/efi
└─sda3               8:3    0  99.5G  0 part /
sdb                  8:16   0    80G  0 disk
├─sdb1               8:17   0   102M  0 part
├─sdb2               8:18   0   102M  0 part
├─sdb3               8:19   0   102M  0 part
├─sdb4               8:20   0     1K  0 part
├─sdb5               8:21   0   6.7G  0 part
│ └─groupZ-home    252:0    0   6.7G  0 lvm
├─sdb6               8:22   0   7.3G  0 part
│ ├─groupA-home    252:1    0   4.9G  0 lvm
│ └─groupA-runtime 252:2    0  19.5G  0 lvm
├─sdb7               8:23   0    17G  0 part
│ └─groupA-runtime 252:2    0  19.5G  0 lvm
├─sdb8               8:24   0   7.3G  0 part
├─sdb9               8:25   0    17G  0 part
├─sdb10              8:26   0   7.3G  0 part
└─sdb11              8:27   0    17G  0 part

发现在启动的时候系统会检查脚本文件的完整性

对关键字的搜索找到了该脚本

root@iotseczone:/tmp/sda1/root# find ./ -name "*.sh" | grep -i "integrity"
./home/bin/check_integrity.sh

check_integrity.sh脚本的流程是:

  • 检验清单自身的合法性
  • 逐一检验系统文件

如果要绕过检测的话最直接的办法就是将stopOnError=1改为0

sh-4.2# ps -aux | grep "telnetd"
root      4147  0.0  0.0   1248    32 ?        Ss   07:27   0:00 /home/bin/busybox telnetd -l /bin/sh -p 8383
root      4160  0.0  0.0   4516  3020 pts/0    S+   07:28   0:00 grep telnetd

0x2

通过安装vmlinux-to-elf工具将kernel文件从原始内核中恢复出完全可分析的.elf文件

root@iotseczone:/home/iotsec-zone# file ./Tools/qemu-images/powerpc/kernel.bin
./Tools/qemu-images/powerpc/kernel.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=61bc74a13bf25e025ff27033a10f7ae939c4c6f2, not stripped

在kernel文件的populate_rootfs函数中使用了基于 512 字节扇区的变异 CBC-MAC 混合加密模式

  if ( initrd_start )
  {
  // 如果内存中存在 initrd (初始 RAM 磁盘) 的起始物理/虚拟地址
    // 调用内核标准的打印函数
    printk((unsigned int)&unk_FFFFFFFF82055898, v7, v9, v10, v11, v12);
    
    //记录加密数据的起始地址
    v13 = initrd_start;
    
    //v14 计算出加密数据的总长度
    v14 = (unsigned int)(initrd_end - initrd_start);
    v19 = crypto_alloc_base((__int64)&aVaes[1], 0, 0);// 返回的 v19 是一个指向 crypto_tfm (Transform) 结构体的指针
    
    if ( v19 <= 18446744073709547520uLL )
    {
    
    // 异或还原逻辑 
    // 将分散在 .rodata 段的假密钥和硬编码的掩码进行 XOR,拼凑出真实的 16 字节 AES 密钥 
    // 结果存放在栈上的 v37 数组中 (v37 是一个 4 个元素的 32-bit 整型数组)
      v37[1] = HIDWORD(DSRAMFS_AES_KEY) ^ 0xAEEF41FE;
      v37[0] = DSRAMFS_AES_KEY ^ 0x99ED2BF2;
      v37[2] = qword_FFFFFFFF81E00168 ^ 0x141058C7;
      v37[3] = HIDWORD(qword_FFFFFFFF81E00168) ^ 0xD2ED180E;
      
      // v19 + 8 对应的是 crypto_tfm 结构体中的 setkey 函数指针! 
      // 翻译为 C 语言即:crypto_cipher_setkey(tfm=v19, key=v37, keylen=16)
      (*(void (__fastcall **)(unsigned __int64, _DWORD *, __int64))(v19 + 8))(v19, v37, 16);
      v20 = 0;
      while ( v14 > 511 )                       // v14大于511 512是一个标准磁盘扇区
      {
        v14 -= 512LL;
        LODWORD(v38) = v20;
        *(_QWORD *)((char *)&v38 + 4) = 0;
        
        // v21 指向当前要解密的 512 字节数据的内存起始位置 
        // v20 << 9 相当于 v20 * 512。v13 是基址。
        v21 = (_DWORD *)(v13 + (unsigned int)(v20 << 9));
        
        // 提前计算下一个扇区的编号
        v36 = v20 + 1;
        HIDWORD(v38) = 0;
        // v22 是当前扇区的结束指针。因为 v21 是 4 字节的 _DWORD 指针, 
        // 所以 128 * 4 = 512 字节。v22 指向扇区末尾。
        v22 = v21 + 128;
        
        // 【核心】间接调用加密函数 // v19 + 24 对应的是 crypto_tfm 中的 encrypt_one 函数指针。 
        // 将刚才构造的 v38 (扇区编号) 作为输入,用 AES 引擎加密,输出结果存入 v39。         // v39 就是这个 512 字节扇区的【全局静态掩码】。
        (*(void (__fastcall **)(unsigned __int64, _DWORD *, __int128 *))(v19 + 24))(v19, v39, &v38);
        do
        {
        
        // 1. Pre-XOR (前置异或) 
        // 将密文的前 16 个字节,与该扇区的【全局静态掩码 v39】进行异或
          *v21 ^= v39[0];
          v21[1] ^= v39[1];
          v21[2] ^= v39[2];
          v21[3] ^= v39[3];
          
          // 2. 状态保存 
          // 将前置异或后的密文原封不动地备份到 v35 (16 字节) 中
          v35 = *(_OWORD *)v21;
          
          // 3. 原地 AES 解密 
          // 再次调用 v19+24 (AES 引擎)。注意传参是 (v19, v21, v21) 
          // 意思是将 v21 指向的数据进行 AES 运算,结果再次覆盖写回 v21。
          (*(void (__fastcall **)(unsigned __int64, _DWORD *, _DWORD *))(v19 + 24))(v19, v21, v21);
          
          // 4. Post-XOR (后置异或 / CBC 链结) 
          // 将 AES 输出的结果,与 v38 进行异或。 
          // (注意:如果是扇区的第一个块,v38 是 [扇区编号,0,0,0];如果是后续块,v38 是上一个块的 v35 密文备份)
          *v21 ^= v38;
          v21[1] ^= DWORD1(v38);
          v21[2] ^= DWORD2(v38);
          v21[3] ^= HIDWORD(v38);
          
          // 5. 指针偏移与状态更新
          v21 += 4; // 指针前进 4 个 _DWORD (16 字节),准备处理下一个块
          v38 = v35;
        }
        while ( v22 != v21 ); // 如果还没有走到当前 512 字节扇区的末尾,就继续循环
        v20 = v36; // 扇区处理完毕,更新为下一个扇区编号
      }

通过对代码的分析和全局变量的提取,生成了一个解密脚本来解密加载到initrd当中的coreboot.img文件,这里的脚本作者就不提供了,希望大家可以根据密钥以及代码可以理解到该如何去解密这个coreboot.img,不是很难!

使用脚本将coreboot.img解密

root@iotseczone:/home/iotsec-zone# python3 1.py
[*] 正在装载终极密钥...
[*] 固件总大小: 40962505 字节
[*] 共探测到 80004 个完整 512 字节扇区,准备实施变异解密...
[*] 检测到尾部 457 字节未对齐数据,按照内核逻辑跳过解密直接追加。

[++++++++++ 内核级还原完成! ++++++++++]
[+] 明文已保存至: coreboo_decrypted.img
[+] 嗅探结果: 标准 GZIP,可直接解压!

解压后成功得到文件系统,查找密钥关键字

root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# find ./ -name "*key*"
./etc/lvmkey

查看密钥字节数以及使用十六进制查看它的本身

root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# wc -c ./etc/lvmkey
16 ./etc/lvmkey
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# hexdump -C ./etc/lvmkey
00000000  6e 1b d1 c3 48 70 bd 72  d2 31 bd 75 53 7e 6c c3  |n...Hp.r.1.uS~l.|
00000010

当这个密钥在内存当中的时候是32字节,这里却是16字节,
根据关键字对/etc/lvmkey进行搜索发现了在lvm-shlib文件当中luksFormat会生成32字节主卷密钥(这里也体现了cbc混合加密模式,它结合了 AES 加密算法、CBC 工作模式、ESSIV 初始化向量生成技术和 SHA256 哈希算法)

  • AES:一种对称加密算法
  • CBC:分组密码工作模式
  • ESSIV:通过扇区的比那好生成IV的方法 IV:是SN被hash经过加密的结果
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# grep -rnw ./ -e "/etc/lvmkey"
./bin/lvm-shlib:118:  echo "y" | $encrypt  -q --cipher aes-cbc-essiv:sha256 --key-file /etc/lvmkey luksFormat ${device}

它会在底层调用Linux内核的/dev/urandom,真随机数生成器抓取32字节的极高熵随机数作为硬盘的终极密码

dd if=/dev/zero of=test.img bs=1M count=10
strace -e trace=openat,open,getrandom cryptsetup -q luksFormat test.img

这里通过对进程的跟踪也可以证实这一点

...
Enter passphrase for test.img:
openat(AT_FDCWD, "/usr/lib/ssl/openssl.cnf", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/urandom", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/dev/random", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = 4
...
+++ exited with 0 +++

使用密钥解锁尝试一下,并把解密后的文件系统挂载出来也是完整的文件系统

root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root/etc# cryptsetup luksOpen /dev/mapper/groupA-home pulse_data --key-file /home/iotsec-zone/_123.extracted/cpio-root/etc/lvmkey
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# ls /dev/mapper/
control  groupA-home  groupA-runtime  groupZ-home  pulse_data
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# mount /dev/mapper/pulse_data /tmp/sda/
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# ls /tmp/sda/
boot  lost+found  root
root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# ls /tmp/sda/root/
4.17.00-x86_64  bin  boot  cgroups  conf  data  dev  etc  gzip  home  lib  lib64  mnt2  modules  pkg  proc  root  run  runtime  sbin  selinux  sys  tmp  usr  va  var  webserver

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2天前 被Kris1337编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (2)
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
合作吗
1天前
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
mb_lcegpvqi 合作吗
没明白你的意思 0.0
17小时前
0
游客
登录 | 注册 方可回帖
返回