从逆向角度看C++ STL代码之std::map

std::map是基于红黑树实现的。红黑树对于很多朋友来说是一个比较难的数据结构,特别是逆向分析,更是让人头大。本文不打算讨论红黑树的相关技术细节,网上有很多资料,有兴趣的朋友可以自行搜索学习。本文主要讨论std::map逆向分析相关技术。

内存布局

根据前面文章介绍的经验,我们找到map成员变量定义的代码,如下:

//xtree 头文件 
template<class _Traits>
	class _Tree_comp_alloc
	{	// base class for tree to hold ordering predicate, allocator
public:
	typedef _Tree_comp_alloc<_Traits> _Myt;

	typedef typename _Traits::allocator_type allocator_type;
	typedef typename _Traits::key_compare key_compare;

	typedef _Tree_base_types<typename _Traits::value_type,
		allocator_type> _Alloc_types;
     /*省略部分代码 */
private:
	_Compressed_pair<key_compare,
		_Compressed_pair<_Alty, _Tree_val<_Val_types> > > _Mypair;
	};

template<class _Val_types>
	class _Tree_val
		: public _Container_base
	{	// base class for tree to hold data
public:
	typedef _Tree_val<_Val_types> _Myt;

	typedef typename _Val_types::_Nodeptr _Nodeptr;
	typedef _Nodeptr& _Nodepref;
	/*省略部分代码*/
	
	_Nodeptr _Myhead;	// pointer to head node
	size_type _Mysize;	// number of elements
	};


可以看出,map由两个变量构成:
_Myhead:指向红黑树head节点的指针
_Mysize: 红黑树节点数
在32位系统下,map在内存中占用8个字节。

接着我们看一下节点的结构:

template<class _Value_type,
	class _Voidptr>
	struct _Tree_node
		{	// tree node
		_Voidptr _Left;	//+0      left subtree, or smallest element if head
		_Voidptr _Parent;//+4  parent, or root of tree if head
		_Voidptr _Right;//+8  right subtree, or largest element if head
		char _Color;//+12  _Red or _Black, _Black if head  _Red = 0 ,_Black = 1
		char _Isnil;//+13  true only if head (also nil) node
		_Value_type _Myval;	//+16 为了内存对齐,在这个变量前面补齐了2个字节    the stored value, unused if head

	private:
		_Tree_node& operator=(const _Tree_node&);
		};

_Tree_node中_Color变量是节点颜色,_Red = 0 ,_Black = 1。_Myval是std::pair<key,value>类型的变量,std::pair<key,value>由_first和_second两个变量,分别是key和value。在32位系统的内存中,_Tree_node占用的空间取决于map的key和value的类型。_Tree_node在内存中实际占用的的内存比各个成员变量的和还要多两个字节。这是因为系统为了内存对齐,在_Myval前补了两个字节,所以_Myval的偏移实际上是16个字节。

实现map的红黑树,是从_Myhead节点开始,它实际上是红黑树的哨兵节点。_Myhead具有以下特点:

  • _Myhead的_Isnil = true
  • _Myhead的_Parent指向红黑树的根节点
  • _Myhead的_Left指向红黑树的最左边的节点,_Right指向最右边的节点(这是为了遍历时,方便找到begin())

下图体现了红黑树的结构特点,图中NIL节点就是_Myhead节点,除了root和_Myhead,其他节点的_Parent没有画出来,实际上它们的_Parent指针都指向它们各自的前驱节点。
在这里插入图片描述

逆向分析

测试代码如下:

// 编译环境:VS2015(v140) + X86 + Release
int main()
{
	map<int, int> lst = { { 0,1 },{ 1,2 },{ 2,3 },{3,4},{4,4},{ 5,5 } };
	for (auto it = lst.begin(); it != lst.end(); it++)
	{
		printf("%d", it->first);
	}
	return 0;
}

编译一下,用IDA打开,用强大F5反编译一下,如下:
在这里插入图片描述
先看一下,遍历红黑树的部分,这其实对树的中序遍历,由于_Myhead节点中存放了树的最左边节点,遍历直接从_Myhead->_Left开始。

我们进入map构造函数看一下:
在这里插入图片描述在这里插入图片描述
上图中,函数sub_401830是插入节点的代码,代码相对比较复杂,本文就不分析了,其实也没有比要。我们的目的通常是在逆向中识别到map的代码,只要认识就可以了。如果想要了解红黑树插入节点的代码原理,何不直接看源码呢?
函数sub_4016D0的功能是申请空间创建节点,这是map的典型特征代码。在逆向分析中,见到这段代码基本可以确定是map了。

总结

对于map的逆向相对来说还是比较困难的,尤其是key和value是比较复杂的数据结构时,需要更多的耐心,特别是要记住map的内存布局,和节点的内存布局。不过话说回来,正是因为map代码相对复杂,其特征也比较明显,有很多特征可以帮助我们识别是否是map。为了方便学习,我已经将测试代码的idb文件上传。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麦子zzy

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值