这是一篇关于在Llama.cpp
中利用堆溢出实现远程代码执行(RCE)的详细研究文章,主要内容总结如下:
- 研究背景与目标:作者是开发爱好者,花费约 30 小时利用堆溢出实现 RCE,此前已花 2 周研究
Llama.cpp
的 RPC 和内存实现。Llama.cpp
有特殊的堆管理系统,其特性使传统的ptmalloc
利用方法失效,作者旨在探索新的利用向量。 - 前期工作与漏洞发现:
Llama.cpp
的 RPC 服务器在开发初期存在低级别内存安全漏洞,主要利用Tensor
内存相关操作。Llama.cpp
实现了自己的内存管理机制,基于 glibc 的基本 malloc 和经典的ptmalloc
管理方法,并添加了优化Tensor
相关处理操作的功能。通过alloc_buffer
命令分配内存,返回包含实际分配内存的buffer
结构。 - 过去的补丁与缓解措施:针对早期报告的漏洞,
Llama.cpp
实施了大量的glibc
级别的内存检查,包括在deserialize_tensor()
、RPC
方法调用包装器、内部实现以及buffer->iface
实现等四个阶段进行检查,以防止各种内存越界和数据损坏问题。 分析与利用过程:
- 维度到破坏:cpy_tensor 和 ggml_nbytes:发现
ggml_nbytes
方法可用于计算Tensor
对象的维度大小,其计算结果受Tensor
的形状和步长影响。在ggml_backend_cpu_buffer_cpy_tensor
方法中,memcpy
的大小由src Tensor
的维度大小计算得出,而此方法未对src
和dst Tensor
的ggml_nbytes
大小进行比较,导致可通过构造特定的Tensor
来实现堆溢出。 - 利用悖论的世界:溢出的悖论:虽然可以控制 RPC 服务器的执行流到任意地址,但由于缺乏可利用的地址,无法单纯依靠
buffer
进行利用。修改buffer
的context
或get_base
成员会导致其他成员指针损坏,而获取ggmlbase
基地址需要绕过边界缓解措施,陷入了“溢出悖论”。 - 部分写入:现实生活中的部分写入?:介绍了在经典
glibc
CTF 利用中的部分写入技术,即在不损坏映射基的情况下部分溢出指针,以访问动态链接库中的特定方法。但在当前的利用中,部分写入buffer->iface
指针非常困难,因为需要控制ggml_nbytes
的计算,且目标方法的范围有限。 - 解决悖论:当经典 ptmalloc 不起作用时:通过研究
ggml_backend_buffer_free
方法中的NullPointerNoException
检查,发现可以将iface->free_buffer
设置为已知地址NULL
以避免程序崩溃,从而能够部分写入其他buffer->iface
成员,为解决“溢出悖论”提供了可能。 - 构造泄漏:逐层构建:目标是构造一个泄漏,通过部分写入
buffer->iface->get_base
成员,找到合适的操纵地址和参数,成功泄漏了ggml_backend_buffer_get_type
的地址,进而计算出libggml-base.so
的基地址。然后利用类似方法泄漏了libc.so.6
的基地址。 - 远程代码执行:新的悖论,面向结构的编程?:尽管绕过了
write-what-wheres
和read-what-wheres
的缓解措施,但在当前的Llama.cpp
版本中实现远程代码执行并不容易。通过结构导向编程,利用ggml_backend_t
和ggml_backend_dev_t
结构中的嵌套调用链,伪造backend
和device
结构,最终实现了在 RPC 服务器中执行远程指定代码。
- 维度到破坏:cpy_tensor 和 ggml_nbytes:发现
- 最终的利用脚本 exp.py:详细描述了利用过程的 Python 脚本实现,包括内存分配、部分写入、伪造结构等步骤,最终通过构造特定的
buffer
和backend
结构,实现了在Llama.cpp
中的远程代码执行,并通过监听127.0.0.1:1337
接收反向 shell 连接。
总的来说,作者通过深入研究Llama.cpp
的内存管理和 RPC 机制,克服了重重困难,成功实现了堆溢出利用并达到远程代码执行的目的,展示了在复杂的内存管理系统中进行漏洞利用的技巧和挑战。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。