内容简介:前言:说实话,我自己也不会c++的逆向。然后,现在太多的题目是c++的逆向了,一上来就是一堆容器,搞得我不得不去补补c++逆向部分的知识了,我这篇文章以西湖论剑的easyCpp为例,希望能给那些跟我一样是c++逆向的新手的朋友们一点启发。下面我就开始我的抛砖引玉篇幅吧,在这篇文章里,我会以题目中出现的逆向出来的代码以及C++的代码进行对比,让你们更好的知道,c++容器入门篇其实不难,开始正文:我将先给你们介绍每个容器操作的代码以及ida反汇编出来的代码进行对比
前言:说实话,我自己也不会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对象最重要的几种操作
- v.push_back(t) 在容器的最后添加一个值为t的数据,容器的size变大。
- v.size() 返回容器中数据的个数,size返回相应vector类定义的size_type的值。
- v.empty() 判断vector是否为空
- v[n] 或 v.at(n) 返回v中位置为n的元素,后者更加安全
- v.insert(pointer,number, content) 向v中pointer指向的位置插入number个content的内容。
还有v. insert(pointer, content),v.insert(pointer,a[2],a[4])将a[2]到a[4]三个元素插入。 - v.pop_back() 删除容器的末元素,并不返回该元素。
- v.erase(pointer1,pointer2) 删除pointer1到pointer2中间(包括pointer1所指)的元素。
vector中删除一个元素后,此位置以后的元素都需要往前移动一个位置,虽然当前迭代器位置没有自动加1,
但是由于后续元素的顺次前移,也就相当于迭代器的自动指向下一个位置一样。 - v1==v2 判断v1与v2是否相等。
- !=、<、<=、>、>= 保持这些操作符惯有含义。
- vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一个元素。*p取所指向元素的值。
对于const vector<typeName>只能用vector<typeName>::const_iterator类型的指针访问。 - p=v1.end( ); p指向v1的最后一个元素的下一位置。
- v.clear() 删除容器中的所有元素。
- 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让你看下我下的断点,具体也可以自己进行调试,这样会让你更加理解这部分代码
我这里截了部分图,这是第一次循环的时候得到的结果,他只push了8进去,具体调试地址可以从ida里看,在代码界面右键Copy to assembly,在右键
可以得到如下图
这里便可以获得具体地址,然后调试部分就不讲了,有时间在写篇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,
运行截图
把这段复制到 linux 上运行即可得到flag,或者直接逆向也得到了
我的这篇文章文字不多,大部分文字都在代码里写注释了,因为这篇文章针对的就是如何分析C++的vector的反汇编代码,具体多余的文字赘述我也就没写了
总结下:
- 在ida的f5插件识别出来的不会是你理想的c++代码,比如v24.begin(); 他会变成std::vector<int,std::allocator<int>>::end(&v24);
- 在ida的f5插件识别出来的代码下,不清楚的部分可以跟进去,看看具体是什么操作
- 需要了解常见的vector容器的基本操作,在自己遇到的时候可以快速识别,不需要步进了解具体过程
- 在不了解具体过程的情况下,可以进行动态调试,方便自己理解
好了,就说这么多了,我这篇图贴的不多,大部分都是代码,似乎都是代码,希望大佬们不要见怪
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 容器技术第一讲:容器入门篇
- 容器入门浅解
- docker入门之容器网络
- Docker 容器编排入门[Docker 系列-8]
- Docker入门实战-SSH连接Docker容器
- 又一篇 iOS Extension 入门(2/3)— 与容器沟通
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。