0%

C++_vector_reverse

C++逆向之容器vector篇入门(本文首发于安全客)

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

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

vector的构造以及析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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出现的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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>::end(&v14);
v4 = std::vector<int,std::allocator>::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::iterator p=v1.begin( ); p初始值指向v1的第一个元素。*p取所指向元素的值。
    对于const vector只能用vector::const_iterator类型的指针访问。
  11. p=v1.end( ); p指向v1的最后一个元素的下一位置。
  12. v.clear() 删除容器中的所有元素。
  13. v.resize(2v.size)或v.resize(2v.size, 99)   将v的容量翻倍(并把新元素的值初始化为99)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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进行分割,方便你们看懂,你们自己调试的时候也可以这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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附近的进行分析

1
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的第一个元素,如果这里看不懂的话,可以跟进去看看,双击这行

1
2
3
4
5
6
7
8
9
10
11
__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部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__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;
}

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

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

accumulate

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
__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里面

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__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都可以在这里观察整个过程,

1
2
3
4
5
6
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个元素减去第一个元素就完美了,贴上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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>::end(&v24);
  2. 在ida的f5插件识别出来的代码下,不清楚的部分可以跟进去,看看具体是什么操作
  3. 需要了解常见的vector容器的基本操作,在自己遇到的时候可以快速识别,不需要步进了解具体过程
  4. 在不了解具体过程的情况下,可以进行动态调试,方便自己理解

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

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/6e2e265a/
版权声明:转载请注明出处!