在HackQuest 2019上打破Micosoft Lunix


哈Ha!

在2019年ZeroNight会议之前的HackQuest,有一项有趣的任务。 我没有按时通过决定,但是我感到非常激动。 我想您很想知道组织者和r0.Crew团队为参加者做了哪些准备。

任务:获得秘密Micosoft 1998操作系统的激活码。

在本文中,我将告诉您如何执行此操作。

目录内容


0.任务
1.工具
2.检查图像
3.字符设备和内核
4.搜索register_chrdev
4.1。 准备一个新的最小的Linux映像
4.2。 更多准备
4.3。 在lunix上禁用KASLR
4.4。 我们搜索并找到签名
5.从/ dev / activate和写入功能中搜索fops
6.我们学习写
6.1。 哈希函数
6.2。 密钥生成算法
6.3。 Keygen

挑战赛


在QEMU中启动的映像需要邮件和激活密钥。 我们已经知道邮件了,让我们寻找其余的!

1.工具


  • Gdb
  • 量化宽松
  • Binwalk
  • 国际开发协会

~/.gdbinit您需要编写一个有用的函数:

 define xxd dump binary memory dump.bin $arg0 $arg0+$arg1 shell xxd dump.bin end 

2.检查图像


首先将jD74nd8_task2.iso重命名为lunix.iso。

使用binwalk,我们看到在偏移量0x413000处有一个脚本。 该脚本检查邮件和密钥:


我们直接使用图像中的十六进制编辑器来取消检查,并让脚本执行我们的命令。 现在看起来像什么:


请注意,必须修剪activated行才能activated ,以便图像大小保持不变。 幸运的是,没有哈希检查。 该映像称为lunix_broken_activation.iso。

通过QEMU运行它:

 sudo qemu-system-x86_64 lunix_broken_activation.iso -enable-kvm 

让我们深入了解:


因此,我们有:

  1. 发行版-最低Linux 5.0.11。
  2. 字符设备/dev/activate用于检查邮件(密钥),这意味着需要在内核的某个地方查找验证逻辑。
  3. 邮件,密钥以email|key格式传输。

不再需要target_broken_activation.iso图片。

3.字符设备和内核


诸如/dev/mem/dev/vcs/dev/activate等设备。 使用register_chrdev函数进行register_chrdev

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

name是名称,并且fops结构包含指向驱动程序函数的指针:

 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 

我们仅对此功能感兴趣:

 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 

在这里,第二个参数是已传输数据的缓冲区,下一个是缓冲区的大小。

4.搜索register_chrdev


默认情况下,“最小Linux”会使用禁用的调试信息进行编译,以减小映像的大小,但是最小。 因此,您不能只是启动调试器并按名称查找函数。 但是可以通过签名。

并且签名在包含调试信息的Minimal Linux映像中。 通常,您需要构建最小。

也就是说,方案如下:

  Minimal Linux ->   register_chrdev ->  ->   register_chrdev  Lunix 

4.1。 准备一个新的最小的Linux映像


  1. 安装必要的工具:
     sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev 
  2. 下载脚本:

     git clone https://github.com/ivandavidov/minimal cd minimal/src 
  3. 正确的02_build_kernel.sh
    删除它

     # Disable debug symbols in kernel => smaller kernel binary. sed -i "s/^CONFIG_DEBUG_KERNEL.*/\\# CONFIG_DEBUG_KERNEL is not set/" .config 

    添加它

     echo "CONFIG_GDB_SCRIPTS=y" >> .config 

  4. 编译中

     ./build_minimal_linux_live.sh 

该映像为minimal / src / minimal_linux_live.iso。

4.2。 更多准备


将minimal_linux_live.iso解压缩到minimum / src / iso文件夹。

最小的/ src / iso / boot文件rootfs.xz内核kernel.xzrootfs.xz FS rootfs.xz 。 将它们重命名为kernel.minimal.xzrootfs.minimal.xz

另外,您需要从图像中拉出核心。 extract-vmlinux脚本将对此有所帮助:

 extract-vmlinux kernel.minimal.xz > vmlinux.minimal 

现在,在minimal / src / iso / boot文件夹中,进行以下设置: kernel.minimal.xzrootfs.minimal.xzvmlinux.minimal

但是从lunix.iso中,我们只需要内核。 因此,我们vmlinux.lunix所有相同的操作,我们将其称为vmlinux.lunix kernel.xz ,而kernel.xzrootfs.xz ,现在我将告诉您原因。

4.3。 在lunix上禁用KASLR


在QEMU中全新组装的Minimal Linux的情况下,我设法禁用了KASLR。
但这与Lunix无关。 因此,您必须编辑图像本身。

为此,请在十六进制编辑器中将其打开,找到"APPEND vga=normal" ,并将其替换为"APPEND nokaslr\x20\x20\x20"

该图像称为lunix_nokaslr.iso。

4.4。 我们搜索并找到签名


我们在一个终端中启动新的Minimal Linux:

 sudo qemu-system-x86_64 -kernel kernel.minimal.xz -initrd rootfs.minimal.xz -append nokaslr -s 

在另一个调试器中:

 sudo gdb vmlinux.minimal (gdb) target remote localhost:1234 

现在在函数列表中查找register_chrdev


显然,我们的选择是__register_chrdev
我们对搜索register_chrdev并不感到困惑,但是发现了__register_chrdev

拆卸:


采取什么签名? 我尝试了几种选择,并确定了以下内容:

  0xffffffff811c9785 <+101>: shl $0x14,%esi 0xffffffff811c9788 <+104>: or %r12d,%esi 


事实是,在lunix中只有一个函数包含0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6

现在我将展示,但是首先我们要找出要寻找的细分。


__register_chrdev函数__register_chrdev地址为0xffffffff811c9720 ,这是.text段。 我们在那里看。

与参考最小Linux断开连接。 现在连接到lunix。

在一个终端中:

 sudo qemu-system-x86_64 lunix_nokaslr.iso -s -enable-kvm 

在另一个:

 sudo gdb vmlinux.lunix (gdb) target remote localhost:1234 

我们看一下.text段的边界:


边框0xffffffff81000000 - 0xffffffff81600b91 ,寻找0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6


我们在地址0xffffffff810dc643找到了该作品。 但这只是功能的一部分,让我们看看上面的内容:


这是函数0xffffffff810dc5d0的开头(因为retq是相邻函数的出口)。

5.通过/ dev / activate搜索fops


register_chrdev函数的原型是这样的:

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

我们需要一个fops结构。

重新启动调试器和QEMU。 我们在0xffffffff810dc5d0休息一下。 它会工作几次。 这些是设备mem, vcs, cpu/msr, cpu/cpuid ,并在它们之后立即activate


指向名称的指针存储在rcx 。 指向fops的指针位于r8


我提醒结构跳板
 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 


因此, write功能的地址为0xffffffff811f068f

6.我们学习写


该功能包括几个有趣的块。 没有必要描述那里的每个断点,这是一个常规程序。 而且,计算块是肉眼可见的。

6.1。 哈希函数


vmlinux.lunix打开IDA,加载内核vmlinux.lunix然后查看其中的write函数。

首先要注意的是这个周期:


sub_FFFFFFFF811F0413一些sub_FFFFFFFF811F0413函数,其功能如下:


并且在地址0xffffffff81829ce0 ,检测到sha256的表:


也就是说, sub_FFFFFFFF811F0413 = sha256。 必须通过$sp+0x50+var49传输必须获得其哈希值的字节,结果存储在$sp+0x50+var48 。 顺便说一句, var49=-0x49var48=-0x48 ,因此$sp+0x50+var49 = $sp+0x7$sp+0x50+var48 = $sp+0x8

看看吧。

我们启动qemu,gdb,在0xffffffff811f0748 call sub_FFFFFFFF811F04130xffffffff811f074d xor ecx, ecx指令(该函数紧随其后)上设置中断。 test@mail.ru邮件test@mail.ru ,密码1234-5678-0912-3456

邮件的字节传递给该函数,结果是这样的:


 >>> import hashlib >>> hashlib.sha256(b"t").digest().hex() 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8' >>> 

也就是说,是的,它确实是sha256,只是它为邮件的所有字节计算哈希,而不是仅从邮件计算一个哈希。

然后,将哈希值按字节求和。 但是,如果总和大于0xEC ,则0xEC除以0xEC的余数:

 import hashlib def get_email_hash(email): h = [0]*32 for sym in email: sha256 = hashlib.sha256(sym.encode()).digest() for i in range(32): s = h[i] + sha256[i] if s <= 0xEC: h[i] = s else: h[i] = s % 0xEC return h 

金额保存在0xffffffff81c82f80 。 让我们看看test@mail.ru的哈希test@mail.ru

我们在ffffffff811f0786 dec r13dffffffff811f0786 dec r13d (这是循环的退出):


并与:

 >>> get_email_hash('test@mail.ru') 2b902daf5cc483159b0a2f7ed6b593d1d56216a61eab53c8e4b9b9341fb14880 

但是哈希本身对于密钥来说显然有点长。

6.2。 密钥生成算法


密钥负责此代码:


这是每个字节的最终计算:

 0xFFFFFFFF811F0943 imul eax, r12d 0xFFFFFFFF811F0947 cdq 0xFFFFFFFF811F0948 idiv r10d 

eaxr12d哈希字节中,将它​​们相乘,然后采用除以9的余数。

因为


并且字节以意外的顺序被获取。 我将在keygen中指出。

6.3。 Keygen


 def keygen(email): email_hash = get_email_hash(email) pairs = [(0x00, 0x1c), (0x1f, 0x03), (0x01, 0x1d), (0x1e, 0x02), (0x04, 0x18), (0x1b, 0x07), (0x05, 0x19), (0x1a, 0x06), (0x08, 0x14), (0x17, 0x0b), (0x09, 0x15), (0x16, 0x0a), (0x0c, 0x10), (0x13, 0x0f), (0x0d, 0x11), (0x12, 0x0e)] key = [] for pair in pairs: i = pair[0] j = pair[1] key.append((email_hash[i] * email_hash[j])%9) return [''.join(map(str, key[i:i+4])) for i in range(0, 16, 4)] 

因此,让我们生成一些密钥:

 >>> import lunix >>> lunix.keygen("m.gayanov@gmail.com") ['0456', '3530', '0401', '2703'] 


现在您可以放松并玩游戏2048 :)谢谢您的关注! 在这里编码

Source: https://habr.com/ru/post/zh-CN471778/


All Articles