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

栏目: C++ · 发布时间: 6年前

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

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

前言:说实话,我自己也不会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(2 v.size)或v.resize(2 v.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为第一个元素,以后不在赘述
  • 4 num + 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 #[1], __int64 #[n], __int64 p, __int64 #[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 = #[1];                                // 这里说明下,都是地址
  v9 = #[n];
  v8 = p;
  v7 = #[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让你看下我下的断点,具体也可以自己进行调试,这样会让你更加理解这部分代码

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

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

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

可以得到如下图

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

这里便可以获得具体地址,然后调试部分就不讲了,有时间在写篇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,

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

运行截图

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

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

总结下:

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

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Python

Programming Python

Mark Lutz / O'Reilly Media / 2006-8-30 / USD 59.99

Already the industry standard for Python users, "Programming Python" from O'Reilly just got even better. This third edition has been updated to reflect current best practices and the abundance of chan......一起来看看 《Programming Python》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具