首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
IoT安全
发新帖
1
2
Ivanti解密的两种思路
发表于: 2026-6-3 16:54
5057
Ivanti解密的两种思路
Kris1337
2026-6-3 16:54
5057
# 0x1 这是环境搭建好的页面,无法进入到底层的shell界面  使用vmware将搭建好的系统盘挂载到另外一个虚拟机上 ```bash 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 ``` 这里是挂载出来的所有的磁盘内容,这里直接创建三个目录挂载出来,发现了加密文件就是这三个,只需要对这三个文件进行解密即可 ```bash 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进行解密,那么这个加密与解密的过程就在虚拟机的内存当中,现在只需要对虚拟机的内存文件进行扫描密码即可 ```c 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。 ``` 这里将`sda1`,`sda2`,`sda3`分别挂载出来其中有`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分区的内存中得到的 ```bash 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`脚本来对密码进行匹配 ```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分区的磁盘解密 ```bash 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 ``` 这里解密后将解密后的磁盘进行挂载就可以得到文件系统了 ```bash 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需要权限否则开机时执行不了命令) ```bash system("/sbin/iptables -A INPUT -p tcp --dport 8383 -j ACCEPT"); system("/home/bin/busybox telnetd -l /bin/sh -p 8383"); ``` 配置完成后卸载已经挂载的磁盘 ```bash umount /dev/mapper/decrypted_groupA-home umount /dev/mapper/decrypted_groupA-runtime ``` 关闭并锁定 `LUKS` 加密卷 ```bash cryptsetup luksClose decrypted_groupA-home cryptsetup luksClose decrypted_groupA-runtime ``` 直到输出`lsblk`命令,所有的挂载、解密层和 LVM 逻辑卷已经完美卸载并彻底分离 ```bash 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 ``` 发现在启动的时候系统会检查脚本文件的完整性  对关键字的搜索找到了该脚本 ```bash root@iotseczone:/tmp/sda1/root# find ./ -name "*.sh" | grep -i "integrity" ./home/bin/check_integrity.sh ``` check_integrity.sh脚本的流程是: - 检验清单自身的合法性 - 逐一检验系统文件 如果要绕过检测的话最直接的办法就是将stopOnError=1改为0 ```bash 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 通过安装<a href="elink@659K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6E0j5i4u0A6L8W2)9J5k6r3#2Q4x3V1k6$3L8h3I4A6L8Y4g2^5i4K6u0V1N6r3!0Q4x3X3c8W2L8r3j5`.">vmlinux-to-elf</a>工具将kernel文件从原始内核中恢复出完全可分析的.elf文件 ```bash 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 混合加密模式 ```c 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解密 ```shell root@iotseczone:/home/iotsec-zone# python3 1.py [*] 正在装载终极密钥... [*] 固件总大小: 40962505 字节 [*] 共探测到 80004 个完整 512 字节扇区,准备实施变异解密... [*] 检测到尾部 457 字节未对齐数据,按照内核逻辑跳过解密直接追加。 [++++++++++ 内核级还原完成! ++++++++++] [+] 明文已保存至: coreboo_decrypted.img [+] 嗅探结果: 标准 GZIP,可直接解压! ``` 解压后成功得到文件系统,查找密钥关键字 ```bash root@iotseczone:/home/iotsec-zone/_123.extracted/cpio-root# find ./ -name "*key*" ./etc/lvmkey ``` 查看密钥字节数以及使用十六进制查看它的本身 ```bash 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经过加密的结果 ```bash 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字节的极高熵随机数作为硬盘的终极密码 ```bash dd if=/dev/zero of=test.img bs=1M count=10 strace -e trace=openat,open,getrandom cryptsetup -q luksFormat test.img ``` 这里通过对进程的跟踪也可以证实这一点 ```bash ... 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 +++ ``` 使用密钥解锁尝试一下,并把解密后的文件系统挂载出来也是完整的文件系统 ```bash 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 ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-7-14 15:18 被Kris1337编辑 ,原因:
#安全研究
#技术分享
#固件分析
#家用设备
#工控设备
收藏
・
1
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
internnal_q
感谢你分享这么好的资源!
2026-7-20 15:01
mb_lcegpvqi
非常支持你的观点!
2026-6-5 23:44
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
mb_lcegpvqi
雪 币:
228
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
mb_lcegpvqi
2
楼
合作吗
2026-6-5 23:46
0
Kris1337
雪 币:
400
活跃值:
(157)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
7
粉丝
2
关注
私信
Kris1337
3
楼
mb_lcegpvqi
合作吗
没明白你的意思 0.0
2026-6-6 17:16
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Kris1337
1
发帖
7
回帖
10
RANK
关注
私信
他的文章
Ivanti解密的两种思路
5057
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部