前言

这里不对C++进行详细分析,侧重于C++标准模板库和string的逆向。
C++中STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干个值。

  • STL容器是同质的,即存储的值的类型相同。
  • 算法是完成特定任务(如对数组进行排序或在链表中查找特定值)的处方。
  • 迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针。
  • 函数对象是类似于函数的对象,可以是类对象或函数模板。

STL使得能够构造各种容器和执行各种操作。

STL不是面向对象的编程,而是一种不同的编程模式——泛型编程(generic programming)。

模板类vector

构造函数与析构函数

要创建vector模板对象,可使用通常的<type>表示法来指出要使用的类型。另外vector模板使用动态内存分配,因此可以用初始化参数来指出需要多少矢量。

vector():                    创建一个空vector
vector(int nSize):           创建一个vector,元素个数为nSize
vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
vector(const vector&):       复制构造函数
vector(begin,end):           复制[begin,end)区间内另一个数组的元素到vector中

写一个demo,利于下一步使用IDA进行分析

#include <iostream>
#include <vector>

using namespace std;
int main(){
    //创建一个int型空vector容器
    vector<int> test1();
    
    //创建一个vector,元素个数为5
    vector<int> test2(5);

    //创建一个vector,元素个数为5,且值均为3
    vector<int> test3(5,3);
    
    //赋值构造函数
    vector<int> test4(test3);

    //复制[begin,end)区间内另一个数组的元素道vector中
    vector<int> test5(test4.begin(),test4.end());

    return 0;
}

下面是ida f5反编译出现的代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbx
  __int64 v4; // rax
  char test5; // [rsp+0h] [rbp-A0h]
  char test4; // [rsp+20h] [rbp-80h]
  char test3; // [rsp+40h] [rbp-60h]
  char test2; // [rsp+60h] [rbp-40h]
  char v10; // [rsp+86h] [rbp-1Ah]
  char v11; // [rsp+87h] [rbp-19h]
  int v12; // [rsp+88h] [rbp-18h]
  char v13; // [rsp+8Fh] [rbp-11h]

    //创建的空容器test1在ida中并没有显示
    ////创建vector test2,元素个数为5
  std::allocator<int>::allocator(&v10, argv);
  std::vector<int,std::allocator<int>>::vector(&test2, 5LL, &v10);//这里可以看到直接对test2进行了size
  std::allocator<int>::~allocator(&v10);

    //创建vector test3,元素个数为5,值为3
  std::allocator<int>::allocator(&v11, 5LL);
  v12 = 3;利用变量进行传值
  std::vector<int,std::allocator<int>>::vector(&test3, 5LL, &v12, &v11);
  std::allocator<int>::~allocator(&v11);
    //test4复制test3,与源码略有不同
  std::vector<int,std::allocator<int>>::vector(&test4, &test3);
    //利用区间进行传值,
  std::allocator<int>::allocator(&v13, &test3);
  v3 = std::vector<int,std::allocator<int>>::end(&test4);
  v4 = std::vector<int,std::allocator<int>>::begin(&test4);
  std::vector<int,std::allocator<int>>::vector<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,void>(
    &test5,
    v4,
    v3,
    &v13);
  std::allocator<int>::~allocator(&v13);

  std::vector<int,std::allocator<int>>::~vector(&test5);//对所有容器进行了析构
  std::vector<int,std::allocator<int>>::~vector(&test4);
  std::vector<int,std::allocator<int>>::~vector(&test3);
  std::vector<int,std::allocator<int>>::~vector(&test2);
  return 0;
}
总结
学会简化C++代码,括号里可以不看,只需要看他具体是什么函数即可
不同的vector构造函数的参数不同,第一个参数为容器名,第二个参数为size,第三个参数为初始值的地址,第四个参数为allocator用于分配内存
v3 = std::vector<int,std::allocator<int>>::end(&v14);
v4 = std::vector<int,std::allocator<int>>::begin(&v14);
这两句要会识别,这是常用的,他是取容器的begin和end,相当于C++的v14.begin();v14.end();

迭代器iterator

理解迭代器是理解STL的关键所在。模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。
每种容器类型都定义了自己的迭代器类型,如vector。

下面使用迭代器读取vector中的每一个元素

#include <iostream>
#include <vector>

using namespace std;
int main(){
    vector<int> ivec(10,1);
    //定义一个iter变量,他的数据类型是由vector<int>定义的iterator类型
    vector<int>::iterator iter;
    for(iter=ivec.begin();iter!=ivec.end();++iter)
        *iter = 2;//使用*访问迭代器所指向的元素

    //counst_iterator 只能读取容器中的元素,而不能修改。
    vector<int>::const_iterator citer;
    for(citer=ivec.begin();citer!=ivec.end();++citer)
        cout << *citer;
    return 0;
}

伪代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int *v3; // rax
  __int64 j; // [rsp+0h] [rbp-60h]
  __int64 i; // [rsp+8h] [rbp-58h]
  char ivec; // [rsp+10h] [rbp-50h]
  char v8; // [rsp+2Bh] [rbp-35h]
  int v9; // [rsp+2Ch] [rbp-34h]
  __int64 v10; // [rsp+30h] [rbp-30h]
  __int64 v11; // [rsp+38h] [rbp-28h]
  __int64 v12; // [rsp+40h] [rbp-20h]
  __int64 v13; // [rsp+48h] [rbp-18h]

  std::allocator<int>::allocator(&v8, argv, envp);
  v9 = 1;
  std::vector<int,std::allocator<int>>::vector(&ivec, 10LL, &v9, &v8);
  std::allocator<int>::~allocator(&v8);
  for ( i = std::vector<int,std::allocator<int>>::begin(&ivec);
        ;
        __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&i) )//重载了运算符++
  {
    v10 = std::vector<int,std::allocator<int>>::end(&ivec);
    if ( !(unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&i, &v10) )
      break;
    *(_DWORD *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&i) = 2;
  }

  v12 = std::vector<int,std::allocator<int>>::begin(&ivec);
  __gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::__normal_iterator<int *>(&v11, &v12);
  for ( j = v11; ; __gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::operator++(&j) )
  {
    v13 = std::vector<int,std::allocator<int>>::end(&ivec);
    if ( !(unsigned __int8)__gnu_cxx::operator!=<int const*,int *,std::vector<int,std::allocator<int>>>(&j, &v13) )
      break;
    v3 = (unsigned int *)__gnu_cxx::__normal_iterator<int const*,std::vector<int,std::allocator<int>>>::operator*(&j);
    std::ostream::operator<<(&std::cout, *v3);
  }
  std::vector<int,std::allocator<int>>::~vector(&ivec);
  return 0;

for循环变化很大,但仔细一看还是有逻辑的。

插入迭代器front_insert与back_insert

定义在<iterator>头文件中

back_inserter:创建一个使用push_back的迭代器
inserter:此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
front_inserter:创建一个使用push_front的迭代器(元素总是插入到容器第一个元素之前)
由于list容器类型是双向链表,支持push_front和push_back操作,因此选择list类型来试验这三个迭代器。

list<int> lst = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> lst2 ={10}, lst3={10},lst4={10};
copy(lst.cbegin(), lst.cend(), back_inserter(lst2));
//lst2包含10,1,2,3,4,5,6,7,8,9
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
//lst3包含1,2,3,4,5,6,7,8,9,10
copy(lst.cbegin(), lst.cend(), front_inserter(lst4));
//lst4包含9,8,7,6,5,4,3,2,1,10

可以轻松看出三者区别,抓住关键函数即可,同上,不再展示伪代码

vector对象最重要的几种操作

   1.  v.push_back(t)     在容器的最后添加一个值为t的数据,容器的size变大。
   2. v.size()   返回容器中数据的个数,size返回相应vector类定义的size_type的值。
   3. v.empty()       判断vector是否为空
   4. v[n] 或 v.at(n) 返回v中位置为n的元素,后者更加安全
   5. v.insert(pointer,number, content) 向v中pointer指向的位置插入number个content的内容。
   6. 还有v. insert(pointer, content),v.insert(pointer,a[2],a[4])将a[2]到a[4]三个元素插入。
   7. v.pop_back() 删除容器的末元素,并不返回该元素。
   8. v.erase(pointer1,pointer2) 删除pointer1到pointer2中间(包括pointer1所指)的元素。
   9. vector中删除一个元素后,此位置以后的元素都需要往前移动一个位置,虽然当前迭代器位置没有自动加1,但是由于后续元素的顺次前移,也就相当于迭代器的自动指向下一个位置一样。
   10.v1==v2 判断v1与v2是否相等。
   11. !=、<、<=、>、>= 保持这些操作符惯有含义。
   12. vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一个元素。*p取所指向元素的值。
   13. 对于const vector<typeName>只能用vector<typeName>::const_iterator类型的指针访问。
   14.p=v1.end( ); p指向v1的最后一个元素的下一位置。
   15. v.clear() 删除容器中的所有元素。
   16. v.resize(2v.size)或v.resize(2v.size, 99)   将v的容量翻倍(并把新元素的值初始化为99)

西湖论剑EasyCPP

int __cdecl main(int argc, const char **argv, const char **envp)
{
    //由于代码太多,我删除了变量定义部分代码 
  v15 = argv;
  v26 = __readfsqword(0x28u);
  std::vector<int,std::allocator<int>>::vector((__int64)&v18);  //创建了五个vector容器
  std::vector<int,std::allocator<int>>::vector((__int64)&v19);
  std::vector<int,std::allocator<int>>::vector((__int64)&v20);
  std::vector<int,std::allocator<int>>::vector((__int64)&v21);
  std::vector<int,std::allocator<int>>::vector((__int64)&v22);
  for ( i = 0; i <= 15; ++i )
  {
    scanf("%d", &v25[4 * i], v15);
    std::vector<int,std::allocator<int>>::push_back(&v19, &v25[4 * i]);  //v19.push_back[i];
  }
  for ( j = 0; j <= 15; ++j )
  {
    LODWORD(v24) = fib(j);
    std::vector<int,std::allocator<int>>::push_back(&v18, &v24);  //一个递归函数对容器v18赋值
  }
  std::vector<int,std::allocator<int>>::push_back(&v20, v25);  //v20.push_back(v25[0]);
  v4 = std::back_inserter<std::vector<int,std::allocator<int>>>(&v20);  //创建一个使用push_back的迭代器
  v5 = std::vector<int,std::allocator<int>>::end(&v19);  //v5 = v19.end()
  v24 = std::vector<int,std::allocator<int>>::begin(&v19);  //v24 = v19.begin()
  v6 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(&v24, 1LL);  //v6 = v24.begin() + 1
  std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>(
    v6,                  //v19.begin()+1 容器的第二个元素
    v5,                  //v19.end()
    v4,                  //只有输入第一个元素的容器
    (__int64)v25         //输入第一个元素的值
    );                   //函数作用为将输入从第二个数开始 每个数加第一个数的值  
  std::vector<int,std::allocator<int>>::vector((__int64)&v23);  //创建一个新容器v23
  v7 = std::vector<int,std::allocator<int>>::end(&v20);  //v7 = v20.end()
  v8 = std::vector<int,std::allocator<int>>::begin(&v20);  //v8 = v20.begin()
  std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>(
    (__int64)&v24,          //v19.begin()
    v8,                     //v20.begin()
    v7,                     //v20.end()
    (__int64)&v23,          //新创建的容器
    v9,
    v10,
    v3);   //倒置函数
  std::vector<int,std::allocator<int>>::operator=(&v21, &v24);  //重载了运算符=,将容器v24的值赋给了容器v21
  std::vector<int,std::allocator<int>>::~vector(&v24);
  std::vector<int,std::allocator<int>>::~vector(&v23);
  if ( (unsigned __int8)std::operator!=<int,std::allocator<int>>(&v21, &v18) )  //v21 == v18 可得flag
  {
    puts("You failed!");
    exit(0);
  }
  std::back_inserter<std::vector<int,std::allocator<int>>>(&v22);
  v11 = std::vector<int,std::allocator<int>>::end(&v19);
  v12 = std::vector<int,std::allocator<int>>::begin(&v19);
  std::copy_if<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#3}>(v12);
  puts("You win!");
  printf("Your flag is:flag{", v11, v15);
  v23 = std::vector<int,std::allocator<int>>::begin(&v22);
  v24 = std::vector<int,std::allocator<int>>::end(&v22);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v23, &v24) )
  {
    v13 = (unsigned int *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v23);
    std::ostream::operator<<(&std::cout, *v13);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v23);
  }
  putchar(125);
  std::vector<int,std::allocator<int>>::~vector(&v22);
  std::vector<int,std::allocator<int>>::~vector(&v21);
  std::vector<int,std::allocator<int>>::~vector(&v20);
  std::vector<int,std::allocator<int>>::~vector(&v19);
  std::vector<int,std::allocator<int>>::~vector(&v18);
  return 0;

分析函数 std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>

__int64 __fastcall std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>,main::{lambda(int)#1}>(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
  int *v4; // rax
  __int64 v5; // rax
  __int64 v7; // [rsp+0h] [rbp-30h]
  __int64 v8; // [rsp+8h] [rbp-28h]
  __int64 v9; // [rsp+10h] [rbp-20h]
  __int64 v10; // [rsp+18h] [rbp-18h]
  int v11; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v12; // [rsp+28h] [rbp-8h]

  v10 = a1;
  v9 = a2;
  v8 = a3;
  v7 = a4;
  v12 = __readfsqword(0x28u);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v10, &v9) )
  {
    v4 = (int *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v10);
    v11 = main::{lambda(int)#1}::operator() const((_DWORD **)&v7, *v4);
    v5 = std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator*(&v8);
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator=(v5, &v11);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v10);
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator++(&v8);
  }
  return v8;

v10指向容器v19的第二个值,并且重载了运算符++,实现了遍历功能,v19中存储了输入的所有数值
v7 中为 输入的第一个数值
下面看一下 main::{lambda(int)#1}::operator() const((_DWORD *)&v7, v4)具体是如何实现的

__int64 __fastcall main::{lambda(int)#1}::operator() const(_DWORD **a1, int a2)
{
  return (unsigned int)(**a1 + a2);
}

v11 = v7 + v4;

std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>

__int64 __fastcall std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::vector<int,std::allocator<int>>,main::{lambda(std::vector<int,std::allocator<int>>,int)#2}>(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, char a7)
{
  int v7; // ebx
  __int64 v9; // [rsp+0h] [rbp-70h]
  __int64 v10; // [rsp+8h] [rbp-68h]
  __int64 v11; // [rsp+10h] [rbp-60h]
  __int64 v12; // [rsp+18h] [rbp-58h]
  char v13; // [rsp+20h] [rbp-50h]
  char v14; // [rsp+40h] [rbp-30h]
  unsigned __int64 v15; // [rsp+58h] [rbp-18h]

  v12 = a1;      //v19.begin() v19此时已经过运算
  v11 = a2;      //v20.begin() v20存储的是第一个数值
  v10 = a3;      //v20.end()
  v9 = a4;       //新创建的容器
  v15 = __readfsqword(0x28u);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v11, &v10) )
  {
    v7 = *(_DWORD *)__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v11);
    std::vector<int,std::allocator<int>>::vector(&v13, v9);
    main::{lambda(std::vector<int,std::allocator<int>>,int)#2}::operator() const(
      (__int64)&v14,
      (__int64)&a7,
      (__int64)&v13,
      v7);
    std::vector<int,std::allocator<int>>::operator=(v9, &v14);
    std::vector<int,std::allocator<int>>::~vector(&v14);
    std::vector<int,std::allocator<int>>::~vector(&v13);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v11);
  }
  std::vector<int,std::allocator<int>>::vector(v12, v9);
  return v12;

可以看到每次循环容器v13、v14都会被析构,只保留了在main中创建的新容器v9

__int64 __fastcall main::{lambda(std::vector<int,std::allocator<int>>,int)#2}::operator() const(__int64 a1, __int64 a2, __int64 a3, int a4)
{
  __int64 v4; // r12
  __int64 v5; // rbx
  __int64 v6; // rax
  int v8; // [rsp+4h] [rbp-3Ch]
  __int64 v9; // [rsp+8h] [rbp-38h]
  __int64 v10; // [rsp+10h] [rbp-30h]
  __int64 v11; // [rsp+18h] [rbp-28h]
  unsigned __int64 v12; // [rsp+28h] [rbp-18h]

  v11 = a1;
  v10 = a2;
  v9 = a3;
  v8 = a4;
  v12 = __readfsqword(0x28u);
  std::vector<int,std::allocator<int>>::vector(a1);
  std::vector<int,std::allocator<int>>::push_back(a1, &v8);
  v4 = std::back_inserter<std::vector<int,std::allocator<int>>>(v11); //定义了一个back_inserter插入迭代器
  v5 = std::vector<int,std::allocator<int>>::end(v9);
  v6 = std::vector<int,std::allocator<int>>::begin(v9);
  std::copy<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,std::back_insert_iterator<std::vector<int,std::allocator<int>>>>(
    v6,
    v5,
    v4);
  return v11;
}

结合以上迭代器back_inserter使用copy函数知识可得
容器v9并没有被析构,每次循环插入一个值,完成对v19容器的倒置。

总结

程序大概的加密思路为

  1. 对input[]从第二个值开始每个值加input[0]
  2. 对加密的input进行一个倒置
  3. 最后的input与递归的值相同

pumpkin9
10 声望3 粉丝

学习要看到核心,抓住本质。 --EX


下一篇 »
整数溢出