哈Ha!
在2019年ZeroNight会议之前的HackQuest,有一项有趣的任务。 我没有按时通过决定,但是我感到非常激动。 我想您很想知道组织者和r0.Crew团队为参加者做了哪些准备。
任务:获得秘密
Micosoft 1998操作系统的激活码。
在本文中,我将告诉您如何执行此操作。
目录内容
0.任务
1.工具2.检查图像3.字符设备和内核4.搜索register_chrdev4.1。 准备一个新的最小的Linux映像4.2。 更多准备4.3。 在lunix上禁用KASLR4.4。 我们搜索并找到签名5.从/ dev / activate和写入功能中搜索fops6.我们学习写6.1。 哈希函数6.2。 密钥生成算法6.3。 Keygen挑战赛
在QEMU中启动的映像需要邮件和激活密钥。 我们已经知道邮件了,让我们寻找其余的!
1.工具
在
~/.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
让我们深入了解:
因此,我们有:
- 发行版-最低Linux 5.0.11。
- 字符设备
/dev/activate
用于检查邮件(密钥),这意味着需要在内核的某个地方查找验证逻辑。 - 邮件,密钥以
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映像
- 安装必要的工具:
sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev
- 下载脚本:
git clone https://github.com/ivandavidov/minimal cd minimal/src
- 正确的
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
- 编译中
./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.xz
和
rootfs.xz
FS
rootfs.xz
。 将它们重命名为
kernel.minimal.xz
,
rootfs.minimal.xz
。
另外,您需要从图像中拉出核心。
extract-vmlinux脚本将对此有所帮助:
extract-vmlinux kernel.minimal.xz > vmlinux.minimal
现在,在minimal / src / iso / boot文件夹中,进行以下设置:
kernel.minimal.xz
,
rootfs.minimal.xz
,
vmlinux.minimal
。
但是从lunix.iso中,我们只需要内核。 因此,我们
vmlinux.lunix
所有相同的操作,我们将其称为
vmlinux.lunix
kernel.xz
,而
kernel.xz
,
rootfs.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=-0x49
,
var48=-0x48
,因此
$sp+0x50+var49 = $sp+0x7
,
$sp+0x50+var48 = $sp+0x8
。
看看吧。
我们启动qemu,gdb,在
0xffffffff811f0748 call sub_FFFFFFFF811F0413
和
0xffffffff811f074d 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 r13d
上
ffffffff811f0786 dec r13d
(这是循环的退出):
并与:
>>> get_email_hash('test@mail.ru') 2b902daf5cc483159b0a2f7ed6b593d1d56216a61eab53c8e4b9b9341fb14880
但是哈希本身对于密钥来说显然有点长。
6.2。 密钥生成算法
密钥负责此代码:
这是每个字节的最终计算:
0xFFFFFFFF811F0943 imul eax, r12d 0xFFFFFFFF811F0947 cdq 0xFFFFFFFF811F0948 idiv r10d
在
eax
和
r12d
哈希字节中,将它们相乘,然后采用除以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 :)谢谢您的关注!
在这里编码