在C++代码中std::vector是很常见的容器。上篇文章中,我们讨论了std::string的逆向技巧,这篇中我们接着讨论std::vector。
一 内存布局
找到std::vector源码中定义的成员变量,如下:
template<class _Alloc_types>
class _Vector_alloc
{ // base class for vector to hold allocator
public:
/* 省略函数相关代码*/
private:
_Compressed_pair<_Alty, _Vector_val<_Val_types> > _Mypair;
};
template<class _Val_types>
class _Vector_val
: public _Container_base
{ // base class for vector to hold data
public:
/* 省略函数相关代码*/
pointer _Myfirst; // pointer to beginning of array
pointer _Mylast; // pointer to current end of sequence
pointer _Myend; // pointer to end of array
};
可以看出,vector有三个成员变量:
_Myfirst:指向数组起始位置的指针
_Mylast:指向有效元素结束位置的指针
_Myend:指向数组结束位置的指针
这三个成员变量都是指针类型,共占用4+4+4=12个字节(32位系统下)。vector中没有记录数组大小的成员变量。数组大小是通过_Mylast- _Myfirst计算得出。
二 逆向分析
写个测试代码:
//编译环境:VS2015(v140) + X86 + Release
int main()
{
vector<int> lst = { 1 ,2, 3 };
for (auto it = lst.begin(); it != lst.end(); it++)
{
printf("%d", *it);
}
return 0;
}
还是编译一下,拖进IDA中F5一下。
上图中,变量Memory就是vector的指针。Memory[0]、Memory[1]和Memory[2]分别是_Myfirst,_Mylast和_Myend。sub_401150是vector的构造函数,函数传入vector首地址指针(Memory),元素数组的首地址(Src)和尾地址(Memoy,这里是最后一个元素的后一个地址,所以是Memory)。进入该函数,如下:
上图给出了代码的简要分析。这里有一个疑问,第14行代码中的0x3FFFFFFF是怎么来的?其实它是由下面第9行代码中max_size编译得来的。
bool _Buy(size_type _Capacity)
{ // allocate array with _Capacity elements
this->_Myfirst() = pointer();
this->_Mylast() = pointer();
this->_Myend() = pointer();
if (_Capacity == 0)
return (false);
else if (max_size() < _Capacity)
_Xlen(); // result too long
else
{ // nonempty array, allocate storage
this->_Myfirst() = this->_Getal().allocate(_Capacity);
this->_Mylast() = this->_Myfirst();
this->_Myend() = this->_Myfirst() + _Capacity;
}
return (true);
}
看一下max_size函数的内容:
size_t max_size() const _NOEXCEPT
{ // estimate maximum array size
return ((size_t)(-1) / sizeof (_Ty));
}
显然,0x3FFFFFFF是由 0xFFFFFFFF / sizeof (_Ty)得来的。这里sizeof (_Ty)等于4,因为测试代码中的vector是int数组。
三 总结
我们知道,vector还有其他的重载构造和众多成员函数,虽然他们编译出的代码不尽相同,但是只要我们掌握了vector的内存布局,还是可以在逆向分析中轻松地识别出他们。另外,在vector的元素类型不同的情况下,编译出的代码形式看起来也不太一样,尤其是vector是结构体数组,或者嵌套了其他容器,编译后的代码会看起来比较复杂,但是基本的逻辑是一致的。总的来说,只要把握vector的内存布局,逆向分析还是比较简单的。