C++逆向之容器vector篇入门

阅读量296030

|

发布时间 : 2019-04-22 10:30:05

 

前言:说实话,我自己也不会c++的逆向。然后,现在太多的题目是c++的逆向了,一上来就是一堆容器,搞得我不得不去补补c++逆向部分的知识了,我这篇文章以西湖论剑的easyCpp为例,希望能给那些跟我一样是c++逆向的新手的朋友们一点启发。下面我就开始我的抛砖引玉篇幅吧,在这篇文章里,我会以题目中出现的逆向出来的代码以及C++的代码进行对比,让你们更好的知道,c++容器入门篇其实不难,开始正文:

我将先给你们介绍每个容器操作的代码以及ida反汇编出来的代码进行对比

 

vector的构造以及析构

#include <iostream>
#include <vector>

using namespace std;
int main()
{
    //声明一个int型向量
    vector<int> test1;
    getchar();

    //声明一个初始大小为5的int向量
    vector<int> test2(5);
    getchar();

    //声明一个初始大小为10且值都是1的向量
    vector<int> test3(10,1);
    getchar();

    //声明并用num向量初始化test4向量
    int num = 1;
    vector<int> test4(num);
    getchar();

    //用向量vec的第0个到第9个值初始化test3
    vector<int> test5(test3.begin(), test3.end());
    getchar();

    //将arr[1]~arr[4]范围内的元素作为vec的初始值
    int array[5] = {1, 2, 3, 4, 5};
    vector<int> test6(&array[1], &array[4]);
    getchar();


    return 0;
}

这是C++代码,接下来是ida F5出现的代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbx
  __int64 v4; // rax
  char v6; // [rsp+0h] [rbp-100h]
  int v7; // [rsp+20h] [rbp-E0h]
  int v8; // [rsp+24h] [rbp-DCh]
  int v9; // [rsp+28h] [rbp-D8h]
  int v10; // [rsp+2Ch] [rbp-D4h]
  int v11; // [rsp+30h] [rbp-D0h]
  char v12; // [rsp+40h] [rbp-C0h]
  char v13; // [rsp+60h] [rbp-A0h]
  char v14; // [rsp+80h] [rbp-80h]
  char v15; // [rsp+A0h] [rbp-60h]
  char v16; // [rsp+C0h] [rbp-40h]
  char v17; // [rsp+E2h] [rbp-1Eh]
  char v18; // [rsp+E3h] [rbp-1Dh]
  int v19; // [rsp+E4h] [rbp-1Ch]
  char v20; // [rsp+E9h] [rbp-17h]
  char v21; // [rsp+EAh] [rbp-16h]
  char v22; // [rsp+EBh] [rbp-15h]
  int v23; // [rsp+ECh] [rbp-14h]

//创建一个vector
  std::vector<int,std::allocator<int>>::vector(&v16, argv, envp);
  getchar();


//创建初始容量大小为5的vector
  std::allocator<int>::allocator(&v17);
  std::vector<int,std::allocator<int>>::vector(&v15, 5LL, &v17);
  std::allocator<int>::~allocator(&v17);
  getchar();

//创建初始容量大小为10并且将元素初始化为1的vector
  std::allocator<int>::allocator(&v18);
  v19 = 1;
  std::vector<int,std::allocator<int>>::vector(&v14, 10LL, &v19, &v18);
  std::allocator<int>::~allocator(&v18);
  getchar();

//声明并用num向量初始化test4向量
  v23 = 1;
  std::allocator<int>::allocator(&v20);
  std::vector<int,std::allocator<int>>::vector(&v13, v23, &v20);
  std::allocator<int>::~allocator(&v20);
  getchar();


  std::allocator<int>::allocator(&v21);
  v3 = std::vector<int,std::allocator<int>>::end(&v14);
  v4 = std::vector<int,std::allocator<int>>::begin(&v14);
  //这两句在c++中相当于v14.begin()以及v14.end()

  //这句看着很长,其实也就是构造函数,将::提取出来就可以看出std::vector<...>::vector<...>(...)
  std::vector<int,std::allocator<int>>::vector<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,void>(
    &v12,
    v4,
    v3,
    &v21);
  std::allocator<int>::~allocator(&v21);
  getchar();


  v7 = 1;
  v8 = 2;
  v9 = 3;
  v10 = 4;
  v11 = 5;
  std::allocator<int>::allocator(&v22);
  //将arr[1]~arr[4]范围内的元素作为vec的初始值,v8为数组下标为1的元素,v11为最后一个
  std::vector<int,std::allocator<int>>::vector<int *,void>(&v6, &v8, &v11, &v22);
  std::allocator<int>::~allocator(&v22);
  getchar();


  std::vector<int,std::allocator<int>>::~vector(&v6);
  std::vector<int,std::allocator<int>>::~vector(&v12);
  std::vector<int,std::allocator<int>>::~vector(&v13);
  std::vector<int,std::allocator<int>>::~vector(&v14);
  std::vector<int,std::allocator<int>>::~vector(&v15);
  std::vector<int,std::allocator<int>>::~vector(&v16);
  return 0;
}

小结:

  • 从代码里可以看出,在ida的识别世界里,他会先创建一个临时变量,然后将他的地址传到vector的构造函数里
  • 而不同的vector构造函数,只是参数不同,第二个为初始容量,第三个为初始数值的地址,第四个为allocator用于分配内存
  • 可以看出构造函数和析构函数是同时存在的
  • 要学会简化所识别出来的C++代码,括号里的模板类可以不仔细看,只需要看他具体是什么函数就行

重点
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();

 

vector的常用操作识别

先进行vector操作知识的复习

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

using namespace std;
int main()
{
    vector<int> test;
    //创建一个vector,并且将5个数值压入容器
    for(int i=0; i<5; i++)
        test.push_back(i);
    getchar();

    //输出容器大小
    cout << test.size();
    getchar();

    //删除容器中最后的一个元素
    test.pop_back();
    getchar();

    //判断容器是否为空,若非空,删除一个元素
    if(!test.empty())
        test.pop_back();
    getchar();

    //重新设置容器大小并赋值
    test.resize(5, 2);
    getchar();

    //创建一个新容器,并判断新容器跟旧容器是否相等
    vector<int> test1(5,2);
    if(test1 == test)
        cout << "Right!" << endl;
    getchar();

    //将区间[first,last)的元素赋值到当前的vector容器中,或者赋n个值为x的元素到vector容器中,这个容器会清除掉vector容器中以前的内容
    test1.assign(test.begin(), test.end());
    getchar();
    test.clear();
    getchar();

    return 0;
}

以下是ida f5识别出来的代码,我相信很多新手看到这么多代码,已经开始晕了,不要紧,一步步给你分析下,我将每一步用getchar进行分割,方便你们看懂,你们自己调试的时候也可以这么做

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rbx
  __int64 v6; // rax
  int i; // [rsp+Ch] [rbp-64h]
  char v9; // [rsp+10h] [rbp-60h]
  char v10; // [rsp+30h] [rbp-40h]
  int v11; // [rsp+54h] [rbp-1Ch]
  char v12; // [rsp+5Bh] [rbp-15h]
  int v13; // [rsp+5Ch] [rbp-14h]

  //用上一节的知识,这里创建了一个vector
  std::vector<int,std::allocator<int>>::vector(&v10, argv, envp);
  for ( i = 0; i <= 4; ++i )
  //这里相当于v10.push_back(i)
    std::vector<int,std::allocator<int>>::push_back(&v10, &i);
  getchar();

  //求v10的大小,相当于v3 = v10.size();
  v3 = std::vector<int,std::allocator<int>>::size(&v10);
  //相当于cout<<v3;
  std::ostream::operator<<(&std::cout, v3);
  getchar();

  //相当于v10.pop_back();
  std::vector<int,std::allocator<int>>::pop_back(&v10);
  getchar();

  //相当于if(!v10.empty())
  //        v10.pop_back();
  if ( (unsigned __int8)std::vector<int,std::allocator<int>>::empty(&v10) ^ 1 )
    std::vector<int,std::allocator<int>>::pop_back(&v10);
  getchar();

  //这个resize有没有发觉很像上一节的构造函数,第一个参数为一个char变量的地址,第二个为容器初始大小,第三个为初始数据的地址
  //相当于v10.resize(5,2);
  v11 = 2;
  std::vector<int,std::allocator<int>>::resize(&v10, 5LL, &v11);
  getchar();

  //这里就是上一节的那个构造函数了,相当于vector<int>v9(5,2);
  std::allocator<int>::allocator(&v12);
  v13 = 2;
  std::vector<int,std::allocator<int>>::vector(&v9, 5LL, &v13, &v12);
  std::allocator<int>::~allocator(&v12);
  //这里判断两个容器是否相等,相当于v9 == v10
  if ( (unsigned __int8)std::operator==<int,std::allocator<int>>(&v9, &v10) )
  {
    v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Right!");
    std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  }
  getchar();

  //这两句又出现了吧,end跟begin,这几句很常用,能识别就行
  v5 = std::vector<int,std::allocator<int>>::end(&v10);
  v6 = std::vector<int,std::allocator<int>>::begin(&v10);

  //将下面句子简化可以看出他就是v9.assign(v6,v5);
  //其实就是v9.assign(v10.begin(), v10.end());
  //具体怎么简化的话,你就看::,不要看模板,那只是类型的问题
  std::vector<int,std::allocator<int>>::assign<__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>,void>(
    &v9,
    v6,
    v5);
  getchar();

  //清空容器元素,相当于v10.clear();
  std::vector<int,std::allocator<int>>::clear(&v10);
  getchar();

  //析构函数
  std::vector<int,std::allocator<int>>::~vector(&v9);
  std::vector<int,std::allocator<int>>::~vector(&v10);
  return 0;
}

小结:

  • c++ vector的逆向其实不难,最主要你要耐心去看,如果你看多几次,你会发觉这个不难,也就是基本操作而已
  • 具体的重要步骤详解,我都在上面注释写的很清楚,一一对应,你可以根据getchar一个个对应去看,看多几遍就知道了
  • 要学会简化ida识别的代码,不要盯着模板一直在那看

好了,vector的基本操作完了,接下来拿一道题来实战吧。我相信各位的技术,接下来直接上代码你们也是可以看懂了,看不懂就往上面翻一翻,查下基本操作

 

西湖论剑之EasyCpp

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v3; // r15
  __int64 v4; // rdx
  __int64 v5; // rdx
  __int64 v6; // rdx
  __int64 v7; // rdx
  __int64 r12_7; // r12
  __int64 v8; // rbx
  __int64 v9; // rax
  __int64 v11; // rdx
  __int64 v12; // rbx
  __int64 v13; // rax
  __int64 v14; // r8
  __int64 v15; // r9
  __int64 v16; // rbx
  char v17; // al
  unsigned int *v18; // rax
  const char **v20; // [rsp+0h] [rbp-190h]
  signed int i; // [rsp+1Ch] [rbp-174h]
  signed int j; // [rsp+20h] [rbp-170h]
  char v23; // [rsp+30h] [rbp-160h]
  char v24; // [rsp+50h] [rbp-140h]
  char v25; // [rsp+70h] [rbp-120h]
  char v26; // [rsp+90h] [rbp-100h]
  char v27; // [rsp+B0h] [rbp-E0h]
  __int64 v28; // [rsp+D0h] [rbp-C0h]
  __int64 v29; // [rsp+F0h] [rbp-A0h]
  int v30[18]; // [rsp+110h] [rbp-80h]
  unsigned __int64 v31; // [rsp+158h] [rbp-38h]

  v20 = argv;
  v31 = __readfsqword(0x28u);
  std::vector<int,std::allocator<int>>::vector(&v23, argv, envp);// #定义五个容器,相当于vector<int> v23,v24,v25,v26,v27
  std::vector<int,std::allocator<int>>::vector(&v24, argv, v4);
  std::vector<int,std::allocator<int>>::vector(&v25, argv, v5);
  std::vector<int,std::allocator<int>>::vector(&v26, argv, v6);
  std::vector<int,std::allocator<int>>::vector(&v27, argv, v7);


  for ( i = 0; i <= 15; ++i )
  {
    scanf("%d", &v30[i], v20);
    std::vector<int,std::allocator<int>>::push_back(&v24, &v30[i]);// 相当于v24.push_back(v30[i]);
  }


  for ( j = 0; j <= 15; ++j )
  {
    LODWORD(v29) = fib(j);
    std::vector<int,std::allocator<int>>::push_back(&v23, &v29);// 相当于v23.push_back(fib(j)) ;   j从0-15
  }


  std::vector<int,std::allocator<int>>::push_back(&v25, v30);// 相当于v25.push_back(v30[0]);  

  r12_7 = std::back_inserter<std::vector<int,std::allocator<int>>>(&v25);// back_inserter创建一个容器指针,指向v25

  v8 = std::vector<int,std::allocator<int>>::end(&v24);// v8 = v24.end()
  v29 = std::vector<int,std::allocator<int>>::begin(&v24);// v29 = v24.begin();

  v9 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(&v29, 1LL);// 传v29的地址,在里面在用指针,相当于传了v24.begin(),这个操作过后就是相当于v9 = v24.begin() + 4;这里我所说的这种说法有语法错误,因为v24.begin()是迭代器,不能这么加,我说的是地址


  Add(
    v9,                                         // v24.begin()+4 相当于数组第二个数
    v8,                                         // v24.end() 相当于数组最后一个数
    r12_7,                                      // 只有输入的第一个元素的容器
    v30);                                       // 输入的第一个元素的元素的值
  std::vector<int,std::allocator<int>>::vector(&v28, v8, v11);// 创建一个新容器 vector<int> v28

  v12 = std::vector<int,std::allocator<int>>::end(&v25);// v12 = v25.end();
  v13 = std::vector<int,std::allocator<int>>::begin(&v25);// v13 = v25.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}>(
    &v29,
    v13,
    v12,
    &v28,
    v14,
    v15,
    v3);                                        // //倒置函数


  std::vector<int,std::allocator<int>>::operator=(&v26, &v29);//将容器v29赋值给v26
  std::vector<int,std::allocator<int>>::~vector(&v29);
  std::vector<int,std::allocator<int>>::~vector(&v28);


  if ( std::operator!=<int,std::allocator<int>>(&v26, &v23) )
  {
    puts("You failed!");
    exit(0);
  }

  std::back_inserter<std::vector<int,std::allocator<int>>>(&v27);
  v16 = std::vector<int,std::allocator<int>>::end(&v24);
  v17 = std::vector<int,std::allocator<int>>::begin(&v24);

  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}>(v17);
  puts("You win!");
  printf("Your flag is:flag{", v16, v20);
  v28 = std::vector<int,std::allocator<int>>::begin(&v27);
  v29 = std::vector<int,std::allocator<int>>::end(&v27);

  while ( __gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v28, &v29) )
  {
    v18 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v28);
    std::ostream::operator<<(&std::cout, *v18);
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v28);
  }

  putchar(125);
  std::vector<int,std::allocator<int>>::~vector(&v27);
  std::vector<int,std::allocator<int>>::~vector(&v26);
  std::vector<int,std::allocator<int>>::~vector(&v25);
  std::vector<int,std::allocator<int>>::~vector(&v24);
  std::vector<int,std::allocator<int>>::~vector(&v23);
  return 0;
}

我的注释部分只写到了获得正确flag的过程部分,也就是前半部分,后面部分其实也不难,你们可以作为练习分析下,
下面根据这里面的难点和重点进行具体分析,整个过程最难的部分就是Add和accumulate,这两部分是重点,如果不理解这两部分是无法得到正确的flag的

我先对Add附近的进行分析

v9 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(&v29, 1LL);// 传v29的地址,在里面在用指针,相当于传了v24.begin(),这个操作过后就是相当于v9 = v24.begin() + 1;

这句我原来以为是将v29+1,后面才发觉这是取容器v29的第一个元素,如果这里看不懂的话,可以跟进去看看,双击这行

__int64 __fastcall __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator+(_QWORD *v24, __int64 num)
{
  __int64 v3; // [rsp+18h] [rbp-18h]
  __int64 v4; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v3 = 4 * num + *v24;
  __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::__normal_iterator(&v4, &v3);// 相当于v4 = v3
  return v4;
}
  • 进行的是这个,他传入的是num,作为偏移,他取出来的是容器的第1个元素,我以下标为0为第一个元素,以后不在赘述
  • 4num + 24 这种写法很常见,在ida6.8尤其显著
  • 他将int数组识别为char数组,取值的时候通常也是这样取,假设有个int数组 int num[5] = {1,2,3,4,5}; 在ida6.8里他识别为char num[20]; 取值的时候就num[4*i],i是循环里的循环变量

接下来是Add部分

__int64 __fastcall Add(__int64 &num[1], __int64 &num[n], __int64 p, __int64 &num[0])
{
  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 = &num[1];                                // 这里说明下,都是地址
  v9 = &num[n];
  v8 = p;
  v7 = &num[0];
  v12 = __readfsqword(0x28u);
  while ( __gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v10, &v9) )
  {
    v4 = __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v10);// v4 = v10
    v11 = main::{lambda(int)#1}::operator() const(&v7, *v4);// 点进去后发现就是num[0] + *v10
    v5 = std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator*(&v8);// v5 = &v8
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator=(v5, &v11);// 将结果存到v5里去,v5指向result容器
    __gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator++(&v10);// 指针自加,相当于数组下标+1
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator++(&v8);// 指针++
  }
  return v8;
}

具体注释我也写好了,匿名函数你在外部看不出什么,然后你双击进去后就能看出他是干什么了,这里就相当于

for(int i=1 ; i< num.Length; i++)
    num[i] = num[0] + num[i];

accumulate

这部分对我来说可能最难理解的吧,他有好多层,我一层层进去后,最后才理解他是如何将容器进行倒置的

__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;
  v11 = a2;
  v10 = a3;
  v9 = a4;
  v15 = __readfsqword(0x28u);
  while ( __gnu_cxx::operator!=<int *,std::vector<int,std::allocator<int>>>(&v11, &v10) )
  {
    v7 = *__gnu_cxx::__normal_iterator<int *,std::vector<int,std::allocator<int>>>::operator*(&v11);//v11就是我们输入的元素,这里你可以对照我上面部分的注释,看传入的参数是什么
    std::vector<int,std::allocator<int>>::vector(&v13, v9);

    main::{lambda(std::vector<int,std::allocator<int>>,int)#2}::operator() const(&v14, &a7, &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;
}

先告诉你们我调试出来的结果吧,这部分让我自己看这ida代码,我看了好久,都没看懂他在干嘛,应该还是太菜了,所以我用gdb调试了一波,发觉他是每次取出一个元素,假设第一个元素,取出,第二个元素取出的时候,将他作为容器,将第一组的元素一个个push入栈达到逆置,然后保存这个容器,在取出一个元素,在创建一个只含这个元素的容器,将其作为主容器,将上次保存的容器的每个元素一个个进行push_back();然后循环一直下去就可以达到逆置的效果

举个例子说明吧: 假设元素为 1 2 3 4 5 6 7 8 9 10
第一次:创建一个只含1的容器(1),其余什么都不做
第二次:创建一个只含2的容器(2),将第一次创建的容器(1)里的元素,全部push到容器(2)里,保存容器(2)
第三次:创建一个只含3的容器(3),将容器2里的元素全部push到容器3里面

具体观察过程可以在循环里下断点进行观察,或者直接步过这部分,直接得到结果知道,由于我这里是分析文章,所以就进行了具体的分析

__int64 __fastcall std::__copy_move<false,false,std::random_access_iterator_tag>::__copy_m<int *,std::back_insert_iterator<std::vector<int,std::allocator<int>>>>(__int64 a1, __int64 a2, __int64 a3)
{
  _QWORD *v3; // rax
  __int64 v5; // [rsp+8h] [rbp-28h]
  __int64 v6; // [rsp+10h] [rbp-20h]
  __int64 v7; // [rsp+18h] [rbp-18h]
  __int64 i; // [rsp+28h] [rbp-8h]

  v7 = a1;
  v6 = a2;
  v5 = a3;
  for ( i = (a2 - a1) >> 2; i > 0; --i )
  {
    v3 = std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator*(&v5);
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator=(v3, v7);// 这里创建新容器,将数据压入栈
    v7 += 4LL;
    std::back_insert_iterator<std::vector<int,std::allocator<int>>>::operator++(&v5);
  }
  return v5;
}

在上一部分我标注的重点里,一直点进去能看到这里的代码,在这里下断,随你用gdb还是ida都可以在这里观察整个过程,

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040133f <std::vector<int, std::allocator<int> > 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}>(__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}, main::{lambda(std::vector<int, std::allocator<int> >, int)#2})+113>
    breakpoint already hit 8 times
2       breakpoint     keep y   0x0000000000400fd0 <main+511>
3       breakpoint     keep y   0x00000000004020b3 <std::back_insert_iterator<std::vector<int, std::allocator<int> > >::operator=(int const&)+33>
    breakpoint already hit 6 times

我这里用info b让你看下我下的断点,具体也可以自己进行调试,这样会让你更加理解这部分代码

result

我这里截了部分图,这是第一次循环的时候得到的结果,他只push了8进去,具体调试地址可以从ida里看,在代码界面右键Copy to assembly,在右键

picture1

可以得到如下图

picture2

这里便可以获得具体地址,然后调试部分就不讲了,有时间在写篇gdb如何调试的吧,在这题目里需要用的指令有

  • x/10wx 显示的如第一张图所显示的一样
  • n 下一步
  • s 步进,也就是步进函数内部
  • c 继续
  • start 在开始处下断点

具体的话:
这道题就是输入的第2-16个元素依次加上第一个元素,然后倒序排列,等于斐波那契数列就得出flag了,所以,反推之就是斐波那契数列倒序排列,在2-16个元素减去第一个元素就完美了,贴上代码

#! /usr/bin/python
# -*- coding: utf-8 -*-

def fib(n):
    if(n==1):
        return 1
    elif(n==2):
        return 1
    return fib(n-1) + fib(n-2)

if __name__ == '__main__':
    array= [fib(i+1) for i in range(16)][::-1]
    first = array[0]
    print first,
    for i in range(len(array)-1):
        print array[i+1] - first,

test

运行截图

把这段复制到linux上运行即可得到flag,或者直接逆向也得到了

我的这篇文章文字不多,大部分文字都在代码里写注释了,因为这篇文章针对的就是如何分析C++的vector的反汇编代码,具体多余的文字赘述我也就没写了

总结下:

  1. 在ida的f5插件识别出来的不会是你理想的c++代码,比如v24.begin(); 他会变成std::vector<int,std::allocator<int>>::end(&v24);
  2. 在ida的f5插件识别出来的代码下,不清楚的部分可以跟进去,看看具体是什么操作
  3. 需要了解常见的vector容器的基本操作,在自己遇到的时候可以快速识别,不需要步进了解具体过程
  4. 在不了解具体过程的情况下,可以进行动态调试,方便自己理解

好了,就说这么多了,我这篇图贴的不多,大部分都是代码,似乎都是代码,希望大佬们不要见怪

本文由NoOne原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/176870

安全客 - 有思想的安全新媒体

分享到:微信
+131赞
收藏
NoOne
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66