介绍:Llama.cpp 的堆溢出问题
Llama.cpp简介
Llama.cpp是一个用于处理大型语言模型(LLM)推理的开源项目,支持远程过程调用(RPC)功能,使用户能够通过网络进行分布式计算。然而,由于其实现复杂性以及内存管理的独特性,该项目也成为了研究人员关注的安全目标。
最近,研究者Patrick Peng在探索Llama.cpp的RPC组件时发现了一个堆溢出漏洞,并成功将其转化为远程代码执行(RCE)。这篇文章将详细介绍这一漏洞的发现过程、分析方法以及最终的利用方式。
堆溢出的起因
Llama.cpp使用了自己的内存管理机制,基于glibc的基本malloc
和经典的ptmalloc
管理方法,并在此基础上添加了一些优化措施以适配张量(Tensor)相关操作的需求。
核心问题
漏洞的核心在于ggml_backend_cpu_buffer_cpy_tensor
函数中对张量维度大小计算的不当处理。具体来说,ggml_nbytes
函数用于计算张量的数据大小,但当输入的张量维度被精心构造时,可能导致计算结果超出实际分配的缓冲区范围,从而引发堆溢出。
size_t ggml_nbytes(const struct ggml_tensor * tensor) {
size_t nbytes;
const size_t blck_size = ggml_blck_size(tensor->type);
if (blck_size == 1) {
nbytes = ggml_type_size(tensor->type);
for (int i = 0; i < GGML_MAX_DIMS; ++i) {
nbytes += (tensor->ne[i] - 1) * tensor->nb[i];
}
} else {
nbytes = tensor->ne[0] * tensor->nb[0] / blck_size;
for (int i = 1; i < GGML_MAX_DIMS; ++i) {
nbytes += (tensor->ne[i] - 1) * tensor->nb[i];
}
}
return nbytes;
}
通过对ne[]
和nb[]
数组的控制,攻击者可以调整返回值nbytes
,进而导致memcpy
操作溢出目标缓冲区。
利用过程解析
第一步:控制溢出位置
在发现堆溢出后,关键是如何利用这一漏洞。研究者注意到,buffer
结构体与dst->data
缓冲区在内存中相邻。通过精确计算溢出偏移量,可以覆盖buffer
结构体中的成员,例如iface
指针。
第二步:绕过边界检查
为了成功触发后续的执行流重定向,必须绕过Llama.cpp引入的严格边界检查。这些检查确保了所有对buffer->data
的操作都在合法范围内。然而,通过部分写入技术(partial-write),研究者成功修改了iface->get_base
指针,指向了一个可控的地址。
第三步:泄露基址
为了进一步利用,需要泄露动态链接库(如libggml-base.so
和libc.so.6
)的加载基址。通过构造虚假的buffer
结构体,并结合ggml_backend_cpu_buffer_get_tensor
函数的行为,研究者成功泄露了memcpy
的GOT表项地址,从而推导出libc
的基址。
第四步:远程代码执行
最后,研究者采用了一种称为“结构导向编程”(Structure-Oriented Programming, SOP)的技术,将原本要求为buffer
结构体的参数重新解释为其他类型的结构体。这种方法使得攻击者能够在不违反现有安全检查的情况下,调用任意函数指针。
以下是利用的关键步骤:
- 构造虚假的
backend
结构体,并设置其device->iface->get_buffer_type
指针为目标函数(如system()
)。 - 使用
ggml_backend_get_alignment
作为入口点,触发深层调用链,最终执行目标命令。
实际效果演示
最终,研究者编写了一个名为exp.py
的脚本,实现了完整的利用过程。该脚本通过以下步骤完成远程代码执行:
- 分配并初始化必要的缓冲区。
- 通过部分写入技术泄露
libggml-base.so
的基址。 - 利用GOT表泄露
libc.so.6
的基址。 - 构造虚假的
backend
结构体,调用system()
执行反向Shell命令。
运行结果如下:
$ python3 exp.py
[*] Leaked libggml-base.so base address: 0x7ffff7e7b000
[*] Leaked libc.so.6 base address: 0x7ffff7a00000
[*] Exploitation complete. Waiting for reverse shell...
reverse_shell@target:~$
总结
本次研究展示了如何通过深入分析和创造性思维,突破看似无法逾越的安全防护机制。尽管Llama.cpp实现了多种内存保护措施,但在复杂系统的实现中,仍然可能存在可被利用的漏洞。这次成功的利用不仅证明了二进制安全的重要性,也为未来的系统设计提供了宝贵的经验。
希望本文能够帮助读者更好地理解现代软件安全的挑战,并激发大家对逆向工程和漏洞挖掘的兴趣!