std::list是一个双向链表,元素也存放在堆中。list的操作涉及到一些指针操作,这给逆向分析带来了一点困难。
一 内存布局
还是一样,先找到list源码中成员变量定义的地方:
template<class _Alloc_types>
class _List_alloc
{ // base class for list to hold allocator
public:
/*省略函数代码 */
private:
_Compressed_pair<_Alty, _List_val<_Val_types> > _Mypair;
};
template<class _Val_types>
class _List_val
: public _Container_base
{ // base class for list to hold data
public:
typedef _List_val<_Val_types> _Myt;
/*省略函数代码 */
_Nodeptr _Myhead; // pointer to head node
size_type _Mysize; // number of elements
};
list有两个成员变量:
_Myhead:指向头节点的指针
_Mysize:节点数量
在32位系统下,list占用4 + 4 = 8 个字节。
再看一下节点的定义:
template<class _Value_type,
class _Voidptr>
struct _List_node
{ // list node
_Voidptr _Next; // successor node, or first element if head
_Voidptr _Prev; // predecessor node, or last element if head
_Value_type _Myval; // the stored value, unused if head
private:
_List_node& operator=(const _List_node&);
};
作为一个双向链表,list的节点定义了_Next和_Prev分别指向后一个节点和后一个节点。还有一个_Myval用来存放当前节点的值。节点在内存中占用空间大小和list的元素类型有关。
二 逆向分析
测试代码:
// 编译环境:VS2015(v140) + X86 + Release
int main()
{
std::list<int> lst; //= { 0xAA,0xBB,0xCC };
lst.push_back(0xAA);
for (auto it = lst.begin(); it != lst.end(); it++)
{
printf("%X\n", *it);
}
return 0;
}
编译一下,拖进IDA中F5一下。
上面简要分析了代码,在了解了list的内存布局后,分析上面的代码还是比较简单的。值得一提的是测试代码编译后,list在增加一个节点后_Mysize变量应该增加1才对,但是在上面的代码中居然没有看到。其实这是因为编译器优化的原因,因为代码比较简单,在新增节点后并没有使用list的_Mysize变量,所以被优化掉了。
三 总结
前面的测试代码中是int类型的链表,然而实战中经常会遇到结构体类型的链表,分析起来会有些困难。这要求我们对list的内存布局比较熟悉,当然还需要一些耐心。