https://download.csdn.net/download/larry_zeng1/11122054
前言:说实话,我自己也不会c++的逆向。然后,现在太多的题目是c++的逆向了,一上来就是一堆容器,搞得我不得不去补补c++逆向部分的知识了,我这篇文章以西湖论剑的easyCpp为例,希望能给那些跟我一样是c++逆向的新手的朋友们一点启发。下面我就开始我的抛砖引玉篇幅吧,在这篇文章里,我会以题目中出现的逆向出来的代码以及C++的代码进行对比,让你们更好的知道,c++容器入门篇其实不难,开始正文:
我将先给你们介绍每个容器操作的代码以及ida反汇编出来的代码进行对比
#include
#include
using namespace std;
int main()
{
//声明一个int型向量
vector test1;
getchar();
//声明一个初始大小为5的int向量
vector test2(5);
getchar();
//声明一个初始大小为10且值都是1的向量
vector test3(10,1);
getchar();
//声明并用num向量初始化test4向量
int num = 1;
vector test4(num);
getchar();
//用向量vec的第0个到第9个值初始化test3
vector test5(test3.begin(), test3.end());
getchar();
//将arr[1]~arr[4]范围内的元素作为vec的初始值
int array[5] = {1, 2, 3, 4, 5};
vector 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>::vector(&v16, argv, envp);
getchar();
//创建初始容量大小为5的vector
std::allocator::allocator(&v17);
std::vector>::vector(&v15, 5LL, &v17);
std::allocator::~allocator(&v17);
getchar();
//创建初始容量大小为10并且将元素初始化为1的vector
std::allocator::allocator(&v18);
v19 = 1;
std::vector>::vector(&v14, 10LL, &v19, &v18);
std::allocator::~allocator(&v18);
getchar();
//声明并用num向量初始化test4向量
v23 = 1;
std::allocator::allocator(&v20);
std::vector>::vector(&v13, v23, &v20);
std::allocator::~allocator(&v20);
getchar();
std::allocator::allocator(&v21);
v3 = std::vector>::end(&v14);
v4 = std::vector>::begin(&v14);
//这两句在c++中相当于v14.begin()以及v14.end()
//这句看着很长,其实也就是构造函数,将::提取出来就可以看出std::vector<...>::vector<...>(...)
std::vector>::vector<__gnu_cxx::__normal_iterator>>,void>(
&v12,
v4,
v3,
&v21);
std::allocator::~allocator(&v21);
getchar();
v7 = 1;
v8 = 2;
v9 = 3;
v10 = 4;
v11 = 5;
std::allocator::allocator(&v22);
//将arr[1]~arr[4]范围内的元素作为vec的初始值,v8为数组下标为1的元素,v11为最后一个
std::vector>::vector(&v6, &v8, &v11, &v22);
std::allocator::~allocator(&v22);
getchar();
std::vector>::~vector(&v6);
std::vector>::~vector(&v12);
std::vector>::~vector(&v13);
std::vector>::~vector(&v14);
std::vector>::~vector(&v15);
std::vector>::~vector(&v16);
return 0;
}
重点:
v3 = std::vector
v4 = std::vector
这两句要会识别,这是常用的,他是取容器的begin和end,相当于C++的v14.begin();v14.end();
先进行vector操作知识的复习
vector对象最重要的几种操作
#include
#include
using namespace std;
int main()
{
vector 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 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>::vector(&v10, argv, envp);
for ( i = 0; i <= 4; ++i )
//这里相当于v10.push_back(i)
std::vector>::push_back(&v10, &i);
getchar();
//求v10的大小,相当于v3 = v10.size();
v3 = std::vector>::size(&v10);
//相当于cout<>::pop_back(&v10);
getchar();
//相当于if(!v10.empty())
// v10.pop_back();
if ( (unsigned __int8)std::vector>::empty(&v10) ^ 1 )
std::vector>::pop_back(&v10);
getchar();
//这个resize有没有发觉很像上一节的构造函数,第一个参数为一个char变量的地址,第二个为容器初始大小,第三个为初始数据的地址
//相当于v10.resize(5,2);
v11 = 2;
std::vector>::resize(&v10, 5LL, &v11);
getchar();
//这里就是上一节的那个构造函数了,相当于vectorv9(5,2);
std::allocator::allocator(&v12);
v13 = 2;
std::vector>::vector(&v9, 5LL, &v13, &v12);
std::allocator::~allocator(&v12);
//这里判断两个容器是否相等,相当于v9 == v10
if ( (unsigned __int8)std::operator==>(&v9, &v10) )
{
v4 = std::operator<<>(&std::cout, "Right!");
std::ostream::operator<<(v4, &std::endl>);
}
getchar();
//这两句又出现了吧,end跟begin,这几句很常用,能识别就行
v5 = std::vector>::end(&v10);
v6 = std::vector>::begin(&v10);
//将下面句子简化可以看出他就是v9.assign(v6,v5);
//其实就是v9.assign(v10.begin(), v10.end());
//具体怎么简化的话,你就看::,不要看模板,那只是类型的问题
std::vector>::assign<__gnu_cxx::__normal_iterator>>,void>(
&v9,
v6,
v5);
getchar();
//清空容器元素,相当于v10.clear();
std::vector>::clear(&v10);
getchar();
//析构函数
std::vector>::~vector(&v9);
std::vector>::~vector(&v10);
return 0;
}
小结:
好了,vector的基本操作完了,接下来拿一道题来实战吧。我相信各位的技术,接下来直接上代码你们也是可以看懂了,看不懂就往上面翻一翻,查下基本操作
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>::vector(&v23, argv, envp);// #定义五个容器,相当于vector v23,v24,v25,v26,v27
std::vector>::vector(&v24, argv, v4);
std::vector>::vector(&v25, argv, v5);
std::vector>::vector(&v26, argv, v6);
std::vector>::vector(&v27, argv, v7);
for ( i = 0; i <= 15; ++i )
{
scanf("%d", &v30[i], v20);
std::vector>::push_back(&v24, &v30[i]);// 相当于v24.push_back(v30[i]);
}
for ( j = 0; j <= 15; ++j )
{
LODWORD(v29) = fib(j);
std::vector>::push_back(&v23, &v29);// 相当于v23.push_back(fib(j)) ; j从0-15
}
std::vector>::push_back(&v25, v30);// 相当于v25.push_back(v30[0]);
r12_7 = std::back_inserter>>(&v25);// back_inserter创建一个容器指针,指向v25
v8 = std::vector>::end(&v24);// v8 = v24.end()
v29 = std::vector>::begin(&v24);// v29 = v24.begin();
v9 = __gnu_cxx::__normal_iterator>>::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>::vector(&v28, v8, v11);// 创建一个新容器 vector v28
v12 = std::vector>::end(&v25);// v12 = v25.end();
v13 = std::vector>::begin(&v25);// v13 = v25.begin();
std::accumulate<__gnu_cxx::__normal_iterator>>,std::vector>,main::{lambda(std::vector>,int)#2}>(
&v29,
v13,
v12,
&v28,
v14,
v15,
v3); // //倒置函数
std::vector>::operator=(&v26, &v29);//将容器v29赋值给v26
std::vector>::~vector(&v29);
std::vector>::~vector(&v28);
if ( std::operator!=>(&v26, &v23) )
{
puts("You failed!");
exit(0);
}
std::back_inserter>>(&v27);
v16 = std::vector>::end(&v24);
v17 = std::vector>::begin(&v24);
std::copy_if<__gnu_cxx::__normal_iterator>>,std::back_insert_iterator>>,main::{lambda(int)#3}>(v17);
puts("You win!");
printf("Your flag is:flag{", v16, v20);
v28 = std::vector>::begin(&v27);
v29 = std::vector>::end(&v27);
while ( __gnu_cxx::operator!=>>(&v28, &v29) )
{
v18 = __gnu_cxx::__normal_iterator>>::operator*(&v28);
std::ostream::operator<<(&std::cout, *v18);
__gnu_cxx::__normal_iterator>>::operator++(&v28);
}
putchar(125);
std::vector>::~vector(&v27);
std::vector>::~vector(&v26);
std::vector>::~vector(&v25);
std::vector>::~vector(&v24);
std::vector>::~vector(&v23);
return 0;
}
我的注释部分只写到了获得正确flag的过程部分,也就是前半部分,后面部分其实也不难,你们可以作为练习分析下,
下面根据这里面的难点和重点进行具体分析,整个过程最难的部分就是Add和accumulate,这两部分是重点,如果不理解这两部分是无法得到正确的flag的
我先对Add附近的进行分析
v9 = __gnu_cxx::__normal_iterator>>::operator+(&v29, 1LL);// 传v29的地址,在里面在用指针,相当于传了v24.begin(),这个操作过后就是相当于v9 = v24.begin() + 1;
这句我原来以为是将v29+1,后面才发觉这是取容器v29的第一个元素,如果这里看不懂的话,可以跟进去看看,双击这行
__int64 __fastcall __gnu_cxx::__normal_iterator>>::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>>::__normal_iterator(&v4, &v3);// 相当于v4 = v3
return v4;
}
__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!=>>(&v10, &v9) )
{
v4 = __gnu_cxx::__normal_iterator>>::operator*(&v10);// v4 = v10
v11 = main::{lambda(int)#1}::operator() const(&v7, *v4);// 点进去后发现就是num[0] + *v10
v5 = std::back_insert_iterator>>::operator*(&v8);// v5 = &v8
std::back_insert_iterator>>::operator=(v5, &v11);// 将结果存到v5里去,v5指向result容器
__gnu_cxx::__normal_iterator>>::operator++(&v10);// 指针自加,相当于数组下标+1
std::back_insert_iterator>>::operator++(&v8);// 指针++
}
return v8;
}
具体注释我也写好了,匿名函数你在外部看不出什么,然后你双击进去后就能看出他是干什么了,这里就相当于
for(int i=1 ; i< num.Length; i++)
num[i] = num[0] + num[i];
这部分对我来说可能最难理解的吧,他有好多层,我一层层进去后,最后才理解他是如何将容器进行倒置的
__int64 __fastcall std::accumulate<__gnu_cxx::__normal_iterator>>,std::vector>,main::{lambda(std::vector>,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!=>>(&v11, &v10) )
{
v7 = *__gnu_cxx::__normal_iterator>>::operator*(&v11);//v11就是我们输入的元素,这里你可以对照我上面部分的注释,看传入的参数是什么
std::vector>::vector(&v13, v9);
main::{lambda(std::vector>,int)#2}::operator() const(&v14, &a7, &v13, v7);//这里是重点,倒置就在这里面
std::vector>::operator=(v9, &v14);//赋值语句没什么好说的
std::vector>::~vector(&v14);
std::vector>::~vector(&v13);
__gnu_cxx::__normal_iterator>>::operator++(&v11);//自增语句
}
std::vector>::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::__copy_m>>>(__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>>::operator*(&v5);
std::back_insert_iterator>>::operator=(v3, v7);// 这里创建新容器,将数据压入栈
v7 += 4LL;
std::back_insert_iterator>>::operator++(&v5);
}
return v5;
}
在上一部分我标注的重点里,一直点进去能看到这里的代码,在这里下断,随你用gdb还是ida都可以在这里观察整个过程,
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040133f > std::accumulate<__gnu_cxx::__normal_iterator > >, std::vector >, main::{lambda(std::vector >, int)#2}>(__gnu_cxx::__normal_iterator > >, std::vector >, main::{lambda(std::vector >, int)#2}, main::{lambda(std::vector >, int)#2})+113>
breakpoint already hit 8 times
2 breakpoint keep y 0x0000000000400fd0
3 breakpoint keep y 0x00000000004020b3 > >::operator=(int const&)+33>
breakpoint already hit 6 times
我这里用info b让你看下我下的断点,具体也可以自己进行调试,这样会让你更加理解这部分代码
我这里截了部分图,这是第一次循环的时候得到的结果,他只push了8进去,具体调试地址可以从ida里看,在代码界面右键Copy to assembly,在右键
可以得到如下图
这里便可以获得具体地址,然后调试部分就不讲了,有时间在写篇gdb如何调试的吧,在这题目里需要用的指令有
具体的话:
这道题就是输入的第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的反汇编代码,具体多余的文字赘述我也就没写了
好了,就说这么多了,我这篇图贴的不多,大部分都是代码,似乎都是代码,希望大佬们不要见怪
转载出处: https://www.anquanke.com/post/id/176870