CTF高质量PWN题之二叉树的漏洞利用

本文详细分析了一道涉及二叉树数据结构的CTF安全题目,揭示了在删除具有左右子节点的二叉树节点时,memcpy函数中的size处理漏洞。通过逐步调试和逆向工程,解释了如何利用这个漏洞进行内存溢出,控制堆布局,并最终实现getshell。文章还介绍了如何泄露libc基址,以及如何通过精心布置二叉树节点来触发和利用漏洞。
摘要由CSDN通过智能技术生成

初步分析

【技术资料】

程序运行起来看起来似乎是一道常规的菜单堆题:
在这里插入图片描述

libc环境:
在这里插入图片描述

是Glibc 2.27-3ubuntu1.4,这个版本与2.31版本很像,都有key机制,一定程度上防止了double free的攻击。

回到程序,程序的功能有插入,展示和删除,我们具体用IDA打开来看看程序是个什么逻辑。
在这里插入图片描述
可以看到函数列表有非常多的函数(原题去除了符号表,笔者经过逆向重命名了一些函数符号),并且使用c++编写,逆向起来难度更大,如果采取常规的静态分析手段,可能会花费很大的精力,由于题目名字是cxx_and_tree,我们猜测整个程序是用树这种数据结构来存储信息,最经典的莫过于二叉树,我们可以来写个demo来测试程序,如果申请以下堆块,那么堆结构如下面的图:

add(0, 0x60, 'a')
add(4, 0x60, 'a')
add(2, 0x60, 'a')
add(9, 0x60, 'a')
add(3, 0x60, 'a')
add(7, 0x60, 'a')

在这里插入图片描述
其中0x40大小的为node部分数据,其余大小的为其data数据,将其画为二叉树长成如下样子:
在这里插入图片描述

左右子树根据其index分如上图,并且通过观察每个node的节点可以确定程序是用二叉树来存储数据。

经过逐步调试和逆向加深对程序的理解后,笔者分析node结构体如下:

struct node
{
   
__int64 idx;  // 节点号
__int64 user_size; // 用户输入的size
__int64 *self_heap_buf; // 存储数据的buf
node *left; // 左孩子
node *right; // 右孩子
node *father; // 父节点
};

具体的漏洞和代码逆向请看下文。

漏洞分析与逻辑触发点

漏洞位于当我们删除某个二叉树节点的时候,如果该节点有左右子树,会调用一个memcpy的函数,这个函数的对于节点size的处理是有问题的。

在申请节点的时候,其size的算法是这样的:
在这里插入图片描述
做了一个类似于align的操作,这个操作是很安全的,人为扩展了一下chunk,使得我们能够申请的最大的size和其align之后最小的size一样大,但是下面的删除节点的操作就有bug了:
在这里插入图片描述

v2 = (unsigned __int64)tmp_target->user_size >> 3;

写个poc来看下我们能溢出的字节数量。

def poc():
for size in range(0x10, 0xff + 1):
biggerSize = ((size >> 3) + 1) * 8
smallerSize = (size >> 3) * 8
if biggerSize > smallerSize:
print("size:{}, biggerSize:{}, smallerSize:{}".format(hex(size), hex(biggerSize), hex(smallerSize)))
poc()

在这里插入图片描述
注意到我们在触发这个逻辑的时候,有部分size是比biggerSize要小的,最多可以溢出7字节。

整个删除节点的逻辑如下:

在这里插入图片描述
想要到达漏洞点所在的位置,则该节点必须同时拥有左右孩子节点才可以。

分析下如果该节点同时拥有左右孩子节点,那么删除该节点的时候发生的流程大致如下:

首先是获得该节点中右子树中最小的元素(按idx确定大小,因为下面一直走的是左子树的逻辑)
在这里插入图片描述
然后将其要替换的节点传入到带有bug的函数中,在此函数中,程序重新申请了一块buf,然后复制要替换节点的数据到新的buf中,值得注意的是,并没有像我们传统的数据结构中一通乱改指针,而是采用了一个复制的思想,但是新创建的buf的size给少了,控制得当能够溢出七个字节。
在这里插入图片描述
然后再往下的逻辑就是删掉刚才的右子树中的最小节点,因为其数据已经拷贝到原本要删除的节点当中。
在这里插入图片描述
在这里我有个疑问,既然之前选到了右子树的最小的节点,那么为什么还要判断其是否还有左子树呢?上面的分支应该永远不会进入,或许是出题人为了增加逆向难度,又或者是出题人面向ctrl+CV编程。

然后进入一个删除节点的函数:

unsigned __int64 __fastcall delete_leaf_node_or_right_children(struct node **father_node, struct node **to_delete_node, struct node **tmp_father_node)
{
   
struct node *v3; // rbx
struct node *v4; // rbx
struct node *v5; // rbx
struct node *v6; // rbx
unsigned __int64 v8; // [rsp+28h] [rbp-18h]
v8 = __readfsqword(0x28u);
if ( *to_delete_node == *father_node )        // only root node
{
   
if ( *((_DWORD *)father_node + 4) == 1 )    // only a node
{
   
v3 = *to_delete_node;
if ( *to_delete_node )
{
   
deleteNode0((__int64)*to_delete_node);
operator delete(v3);
}
*father_node = 0LL;
--*((_DWORD *)father_node + 4);
*to_delete_node = 0LL;
}
else                                        // has right children
{
   
*father_node = (*father_node)->right;
(*father_node)->father = 0LL
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值