[总结型]C++stl中常见数据结构逆向分析

前言

更新时间2021.12.1
以一道题说明重要性。defcon2019q-election_coin,是2019年defcon的资格赛。election_coin一题,标签为reverse&pwn,当年有8支队伍做出来,分数最后为250+,属于中等偏难的题目。虽然是pwn,但妥妥的是一道reverse题。作者是用c++编写的一个网络应用,并且最后给出的二进制是stripped掉的。
题目资料: github
别人的wp: github

题目资料中有题目的源码,分析源码很容易看出其中的bug,有一个任意读的bug,有一个任意写的bug。所以最后exp脚本非常的简单,这题的难点还是在如何逆向上。下面截取题目的源码:

auto debug = headers.tryGet<ElectionDebugHeader>();
if (!debug) {
    auto addr = reinterpret_cast<const uint64_t*>(auth_token->token_.data() + auth_token->token_.size() - 8);
    std::stringstream buffer;
    
    //任意地址读 addr可控
    buffer << *reinterpret_cast<const uint64_t*>(addr);
    response.send(Http::Code::Multiple_Choices, buffer.str());
    return;
}
std::stringstream buffer;
buffer << "converted " << amount << " dogecoin (" << std::hex << target_value << ")";

//任意地址写 target_addr target_value 可控
*reinterpret_cast<uint64_t*>(target_addr) = target_value;
return buffer.str();

题目是reverse,非常困难,IDA分析的反C代码的main函数,极其复杂,这就要求选手(黑客)需要非常熟悉c++编译后的binary,熟悉c++常见的库与模板函数。

Ubuntu中 c++头文件目录/usr/include/c++/...,实现一般是在.../bits/stl_xxx
例如我本机的vector实现目录在/usr/include/c++/7.5.0/bits/stl_vector.h
以下是gcc的实现,全部是-O0编译。

std::vector

/usr/include/c++/7.5.0/bits/stl_vector.h

内存布局

class vector {
protected:
	//三个成员变量由 _Vector_base 继承而来
	// sizeof(std::vector<A>) == 24;
	pointer _M_start;//使用的空间头部
	pointer _M_finish;//使用的空间尾部
	pointer _M_end_of_storage; //可用空间尾部
}

下图来自《stl源码剖析》
在这里插入图片描述

构造与析构

//构造函数
void __cdecl std::vector<A>::vector(std::vector<A> *const this)
{
  std::_Vector_base<A>::_Vector_base(this);
}
void __cdecl std::_Vector_base<A>::_Vector_base(std::_Vector_base<A> *const this)
{
  std::_Vector_base<A>::_Vector_impl::_Vector_impl(&this->_M_impl);
}
void __cdecl std::_Vector_base<A>::_Vector_impl::_Vector_impl(std::_Vector_base<A>::_Vector_impl *const this)
{
  std::allocator<A>::allocator((std::allocator<A> *const)this);
  this->_M_start = 0LL;
  this->_M_finish = 0LL;
  this->_M_end_of_storage = 0LL;
}

//析构函数
void __cdecl std::vector<A>::~vector(std::vector<A> *const this)
{
  std::allocator<A> *v1; // rax

  v1 = std::_Vector_base<A>::_M_get_Tp_allocator(this);
  std::_Destroy<A *,A>(this->_M_impl._M_start, this->_M_impl._M_finish, v1);//依次调用各个对象的析构函数
  std::_Vector_base<A>::~_Vector_base(this);//回收所有对象占用的内存
}
void __cdecl std::_Vector_base<A>::~_Vector_base(std::_Vector_base<A> *const this)
{
  std::_Vector_base<A>::_M_deallocate(
    this,
    this->_M_impl._M_start,
    this->_M_impl._M_end_of_storage - this->_M_impl._M_start);
  std::_Vector_base<A>::_Vector_impl::~_Vector_impl(&this->_M_impl);
}
//构造函数
__int64 __fastcall sub_AC2(__int64 a1)
{
  return sub_B3E(a1);
}
_QWORD *__fastcall sub_B3E(__int64 a1)
{
  return sub_BDE((_QWORD *)a1);
}
_QWORD *__fastcall sub_BDE(_QWORD *a1)
{
  _QWORD *result; // rax

  sub_C94(a1);
  *a1 = 0LL;
  a1[1] = 0LL;
  result = a1;
  a1[2] = 0LL;
  return result;
}

//析构函数
__int64 __fastcall sub_ADE(_QWORD *a1)
{
  sub_BA6(a1);
  sub_BB4(*a1, a1[1]);
  return sub_B5A(a1);
}
__int64 __fastcall sub_B5A(_QWORD *a1)
{
  sub_C38((__int64)a1, *a1, a1[2] - *a1);
  return sub_B22((__int64)a1);
}

push_back

//source code
void push_back(const value_type &__x)
{
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    {
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
        ++this->_M_impl._M_finish;
    }
    else
        _M_realloc_insert(end(), __x);
}
void __cdecl std::vector<A>::push_back(std::vector<A> *const this, const std::vector<A>::value_type *__x)
{
  A *v2; // rcx

  if ( this->_M_impl._M_finish == this->_M_impl._M_end_of_storage )
  {
    v2 = std::vector<A>::end(this)._M_current;
    
    //此扩容函数内有一个特性字符串 "vector::_M_realloc_insert"
    std::vector<A>::_M_realloc_insert<A const&>(this, (std::vector<A>::iterator)v2, __x); 
  }
  else
  {
    std::allocator_traits<std::allocator<A>>::construct<A,A const&>(
      (std::allocator_traits<std::allocator<A> >::allocator_type *)this, this->_M_impl._M_finish, __x);
    ++this->_M_impl._M_finish;
  }
}
__int64 __fastcall sub_E6A(__int64 a1, __int64 a2)
{
  __int64 result; // rax
  __int64 v3; // rax

  if ( *(_QWORD *)(a1 + 8) == *(_QWORD *)(a1 + 16) )
  {
    v3 = sub_FEE(a1);
    result = sub_103A(a1, v3, a2);
  }
  else
  {
    sub_FB4(a1, *(_QWORD *)(a1 + 8), a2);
    result = a1;
    *(_QWORD *)(a1 + 8) += 8LL; //+=8 是因为vector内部对象的大小为8。不同对象此处不同
  }
  return result;
}

pop_back

//source code
void pop_back() _GLIBCXX_NOEXCEPT
{
      __glibcxx_requires_nonempty();
      --this->_M_impl._M_finish;
      _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);//调用_M_finish位置对象的析构函数
}
void __cdecl std::vector<A>::pop_back(std::vector<A> *const this)
{
  std::allocator_traits<std::allocator<A>>::destroy<A>(
    (std::allocator_traits<std::allocator<A> >::allocator_type *)this, --this->_M_impl._M_finish);
}
__int64 __fastcall sub_104C(__int64 a1)
{
  *(_QWORD *)(a1 + 8) -= 8LL; //-=8 是与对象大小有关
  return sub_1985(a1, *(_QWORD *)(a1 + 8));
}

emplace_back

//source code
template <typename _Tp, typename _Alloc>
template <typename... _Args>
void vector<_Tp, _Alloc>::
    emplace_back(_Args &&...__args)
{
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    {
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish;
    }
    else
        _M_emplace_back_aux(std::forward<_Args>(__args)...);
}
void __cdecl std::vector<A>::emplace_back<>(std::vector<A> *const this)
{
  A *v1; // rdx

  if ( this->_M_impl._M_finish == this->_M_impl._M_end_of_storage )
  {
    v1 = std::vector<A>::end(this)._M_current;
    std::vector<A>::_M_realloc_insert<>(this, (std::vector<A>::iterator)v1);
  }
  else
  {
    std::allocator_traits<std::allocator<A>>::construct<A>(
      (std::allocator_traits<std::allocator<A> >::allocator_type *)this, this->_M_impl._M_finish);
    ++this->_M_impl._M_finish;
  }
}
__int64 __fastcall sub_F44(__int64 a1)
{
  __int64 result; // rax
  __int64 v2; // rax

  if ( *(_QWORD *)(a1 + 8) == *(_QWORD *)(a1 + 16) )
  {
    v2 = sub_1148(a1);
    result = sub_1436(a1, v2);
  }
  else
  {
    sub_1410(a1, *(_QWORD *)(a1 + 8));
    result = a1;
    *(_QWORD *)(a1 + 8) += 8LL;
  }
  return result;
}

size

std::vector<A>::size_type __cdecl std::vector<A>::size(const std::vector<A> *const this)
{
  return this->_M_impl._M_finish - this->_M_impl._M_start;
}
__int64 __fastcall sub_F1E(_QWORD *a1)
{
  return (__int64)(a1[1] - *a1) >> 3;
}

resize

//source code
void resize(size_type __new_size)
{
      if (__new_size > size())
            _M_default_append(__new_size - size());
      else if (__new_size < size())
            _M_erase_at_end(this->_M_impl._M_start + __new_size);
}
void __cdecl std::vector<A>::resize(std::vector<A> *const this, std::vector<A>::size_type __new_size)
{
  std::vector<A>::size_type v2; // rdx

  if ( __new_size <= std::vector<A>::size(this) )
  {
    if ( __new_size < std::vector<A>::size(this) )
      std::vector<A>::_M_erase_at_end(this, &this->_M_impl._M_start[__new_size]);
  }
  else
  {
    v2 = __new_size - std::vector<A>::size(this);
    std::vector<A>::_M_default_append(this, v2);
  }
}
char __fastcall sub_FB4(_QWORD *a1, unsigned __int64 a2)
{
  __int64 v2; // rax
  char result; // al

  if ( a2 <= sub_F1E(a1) )
  {
    result = a2 < sub_F1E(a1);
    if ( result )
      result = sub_18FA(a1, *a1 + 8 * a2);
  }
  else
  {
    v2 = sub_F1E(a1);
    result = sub_169C(a1, a2 - v2);
  }
  return result;
}

erase

std::vector<long int>::iterator __cdecl std::vector<long>::_M_erase(
        std::vector<long int> *const this,
        std::vector<long int>::iterator __position){
          thisa = this;
  __positiona._M_current = __position._M_current;
  __rhs._M_current = std::vector<long>::end(this)._M_current;
  __lhs._M_current = __gnu_cxx::__normal_iterator<long *,std::vector<long>>::operator+(&__positiona, 1LL)._M_current;

// 如果不是删除最后一个的话,要把后面的元素都向前移动一格。
  if ( __gnu_cxx::operator!=<long *,std::vector<long>>(&__lhs, &__rhs) )
  {
    M_current = std::vector<long>::end(thisa)._M_current;
    v3._M_current = __gnu_cxx::__normal_iterator<long *,std::vector<long>>::operator+(&__positiona, 1LL)._M_current;
    std::move<__gnu_cxx::__normal_iterator<long *,std::vector<long>>,__gnu_cxx::__normal_iterator<long *,std::vector<long>>>(
      v3,
      (__gnu_cxx::__normal_iterator<long int*,std::vector<long int> >)M_current,
      __positiona);
  }
  std::allocator_traits<std::allocator<long>>::destroy<long>(
    (std::allocator_traits<std::allocator<long int> >::allocator_type *)thisa,
    --thisa->_M_impl._M_finish);
  return __positiona;
}
__int64 __fastcall std::vector<long>::_M_erase(__int64 a1, __int64 a2)
{
  __int64 v2; // rbx
  __int64 v3; // rax
  __int64 v5; // [rsp+0h] [rbp-40h] BYREF
  __int64 v6; // [rsp+8h] [rbp-38h]
  __int64 v7; // [rsp+18h] [rbp-28h] BYREF
  __int64 v8[4]; // [rsp+20h] [rbp-20h] BYREF

  v6 = a1;
  v5 = a2;
  v8[1] = __readfsqword(0x28u);
  v8[0] = std::vector<long>::end(a1);
  v7 = __gnu_cxx::__normal_iterator<long *,std::vector<long>>::operator+(&v5, 1LL);
  if ( (unsigned __int8)__gnu_cxx::operator!=<long *,std::vector<long>>(&v7, v8) )
  {
    v2 = std::vector<long>::end(v6);
    v3 = __gnu_cxx::__normal_iterator<long *,std::vector<long>>::operator+(&v5, 1LL);
    std::move<__gnu_cxx::__normal_iterator<long *,std::vector<long>>,__gnu_cxx::__normal_iterator<long *,std::vector<long>>>(
      v3,
      v2,
      v5);
  }
  *(_QWORD *)(v6 + 8) -= 8LL;
  std::allocator_traits<std::allocator<long>>::destroy<long>(v6, *(_QWORD *)(v6 + 8));
  return v5;
}
__int64 __fastcall sub_16BE(__int64 a1, __int64 a2)
{
  __int64 v2; // rbx
  __int64 v3; // rax
  __int64 v5; // [rsp+0h] [rbp-40h] BYREF
  __int64 v6; // [rsp+8h] [rbp-38h]
  __int64 v7; // [rsp+18h] [rbp-28h] BYREF
  __int64 v8[4]; // [rsp+20h] [rbp-20h] BYREF

  v6 = a1;
  v5 = a2;
  v8[1] = __readfsqword(0x28u);
  v8[0] = sub_183E(a1);
  v7 = sub_1658(&v5, 1LL);
  if ( (unsigned __int8)sub_188E(&v7, v8) )
  {
    v2 = sub_183E(v6);
    v3 = sub_1658(&v5, 1LL);
    sub_18CE(v3, v2, v5);
  }
  *(_QWORD *)(v6 + 8) -= 8LL;
  sub_191E(v6, *(_QWORD *)(v6 + 8));
  return v5;
}

std::shared_ptr

template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp> {
...
}

template <typename _Tp, _Lock_policy _Lp>
class __shared_ptr {
private:
    _Tp *_M_ptr;                     // Contained pointer.
    __shared_count<_Lp> _M_refcount; // Reference counter.
}

template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count {
private:
	//_Sp_counted_base是基类
	//若是shared_ptr(new A()), 实际上放的对象是_Sp_counted_ptr的实例
	//class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>
	_Sp_counted_base<_Lp>*  _M_pi;
}

template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base : public _Mutex_base<_Lp> {
private:
	//此类为基类,成员函数有virtual
	//隐藏的成员变量(vtable ptr)
      _Atomic_word  _M_use_count;     // #shared
      _Atomic_word  _M_weak_count;    // #weak + (#shared != 0)
}

template<typename _Ptr, _Lock_policy _Lp>
class _Sp_counted_ptr final : public _Sp_counted_base<_Lp> {
private:
	_Ptr             _M_ptr;
}

内存布局

A动态调试发现,如果是make_shared<A>()的方式创建智能指针,则_M_pi中是没有_M_ptr的,未深究。

构造函数

void __cdecl std::shared_ptr<A>::shared_ptr<A,void>(std::shared_ptr<A> *const this, A *__p)
{
  std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2>::__shared_ptr<A,void>(this, __p);
}

void __cdecl std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2>::__shared_ptr<A,void>(std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2> *const this, A *__p)
{
  this->_M_ptr = __p;
  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<A *>(
    &this->_M_refcount,
    __p,
    (std::false_type)((_BYTE)this + 8));
  std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2>::_M_enable_shared_from_this_with<A,A>(this, __p);
}

void __cdecl std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<A *>(std::__shared_count<(__gnu_cxx::_Lock_policy)2> *const this, A *__p, std::false_type a3)
{
  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<A *>(this, __p);
}

void __cdecl std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<A *>(std::__shared_count<(__gnu_cxx::_Lock_policy)2> *const this, A *__p)
{
  std::_Sp_counted_ptr<A*,(__gnu_cxx::_Lock_policy)2> *v2; // rbx

  this->_M_pi = 0LL;
  v2 = (std::_Sp_counted_ptr<A*,(__gnu_cxx::_Lock_policy)2> *)operator new(0x18uLL);
  std::_Sp_counted_ptr<A *,(__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr(v2, __p);
  this->_M_pi = v2;
}

void __cdecl std::_Sp_counted_ptr<A *,(__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr(std::_Sp_counted_ptr<A*,(__gnu_cxx::_Lock_policy)2> *const this, A *__p)
{
  std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_Sp_counted_base(this);
  this->_vptr__Sp_counted_base = (int (**)(...))off_201C98;
  this->_M_ptr = __p;
}
unsigned __int64 __fastcall sub_1106(_QWORD *a1, __int64 a2)
{
  unsigned __int64 v3; // [rsp+20h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  *a1 = a2;
  sub_11F6(a1 + 1, a2);
  sub_121C(a1, a2);
  return __readfsqword(0x28u) ^ v3;
}

_QWORD *__fastcall sub_125A(_QWORD *a1, __int64 a2)
{
  __int64 v2; // rbx
  _QWORD *result; // rax

  *a1 = 0LL;
  v2 = operator new(0x18uLL);
  sub_1326(v2, a2);
  result = a1;
  *a1 = v2;
  return result;
}

析构函数

void __cdecl std::shared_ptr<A>::~shared_ptr(std::shared_ptr<A> *const this)
{
  std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2>::~__shared_ptr(this);
}

void __cdecl std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2>::~__shared_ptr(std::__shared_ptr<A,(__gnu_cxx::_Lock_policy)2> *const this)
{
  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count(&this->_M_refcount);
}

void __cdecl std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count(std::__shared_count<(__gnu_cxx::_Lock_policy)2> *const this)
{
  if ( this->_M_pi )
    std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release(this->_M_pi);
}

void __cdecl std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release(std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2> *const this)
{
  if ( __gnu_cxx::__exchange_and_add_dispatch(&this->_M_use_count, -1) == 1 )
  {
  	//调用_Sp_counted_base的析构函数,因为virtual,实则是_Sp_counted_ptr的析构,在其中释放管理的对象
    (*((void (__fastcall **)(std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2> *const))this->_vptr__Sp_counted_base + 2))(this);
    if ( __gnu_cxx::__exchange_and_add_dispatch(&this->_M_weak_count, -1) == 1 )
      (*((void (__fastcall **)(std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2> *const))this->_vptr__Sp_counted_base + 3))(this);
  }
}

多线程下shared_ptr自身的同步问题

当时逆向的时候发现shared_ptr在实现的可能的同步问题 ,觉得挺有意思的,就投入其中,然后这篇就鸽了。相关同步问题的讨论,注意这里说的同步问题不是shared_ptr管理的对象的同步问题,而是shared_ptr自身的,不当的使用可能会造成use-after-free或double free等严重的内存bug。贴一个我使用Fuzzing在知名开源项目发现的真实案例

//一个正确的在多线程的用法
//In thread 1
shared_ptr<myClass> private = atomic_load(&global);
...
//In thread 2
atomic_store(&global, make_shared<myClass>());

std::string

/usr/include/c++/9/bits/basic_string.h

内存布局

  typedef basic_string<char>    string;   
  
  template<typename _CharT, typename _Traits, typename _Alloc>
    class basic_string
    {
    protected:
        _Alloc_hider	_M_dataplus;        // pointer, 8 bytes
        size_type		_M_string_length;   // size_t, 8 bytes
				
        enum { _S_local_capacity = 15 / sizeof(_CharT) };
        union
        {
          _CharT           _M_local_buf[_S_local_capacity + 1];
          size_type        _M_allocated_capacity;
        }; // 16 bytes
    }
sizeof(std::string) == 32 // 2*8 + 16 bytes

关于_M_local_buf的相关优化
如果字符串的strlen长度小于等于15,那么std:string会把它放到自己的空间中,即_M_dataplus指向的这个空间往往是栈上。如果大于15,那么字符串会被放到堆上,即_M_dataplus指向的空间是malloc出来的。

string s = "aaaaaaaaaaaaaaa"; // 15个a

pwndbg> p &s
$1 = (std::string *) 0x7fffffffdba0

pwndbg> x/8xg 0x7fffffffdba0
0x7fffffffdba0:	0x00007fffffffdbb0	0x000000000000000f
0x7fffffffdbb0:	0x6161616161616161	0x0061616161616161
string s = "aaaaaaaaaaaaaaaa"; // 16个a

pwndbg> p &s
$1 = (std::string *) 0x7fffffffdba0

pwndbg> x/8xg 0x7fffffffdba0
0x7fffffffdba0:	0x000055555556aeb0	0x0000000000000010
0x7fffffffdbb0:	0x0000000000000010	0x0000555555555330

其中0x7fffffffdbb8里面的0x0000555555555330,是未定义的值,与std:string无关。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值