C++逆向学习(二) vector

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

内容简介:此文为原创文章作者:ret2nullptr@先知社区

C++逆向学习(二) vector

此文为原创文章

作者:ret2nullptr@先知社区

恭喜作者获得

价值100元的天猫超市享淘卡一张

欢迎更多优质原创、翻译作者加入

ASRC文章奖励计划

欢迎多多投稿到先知社区

每天一篇优质技术好文

点滴积累促成质的飞跃

今天也要进步一点点呀

现在的逆向C++题越来越多,经常上来就是一堆容器、标准模板库,这个系列主要记录这些方面的逆向学习心得

本文主要介绍 std::vector ,因为逆向题中的C++代码可能会故意写的很绕,比如输入一个数组,直接给 vector 赋值即可,但是也可以用稍微费解的方法 连续push_back() ,也算是一种混淆的手段,文章中的示例会逆向一些故意写的繁琐的程序

vector

内存布局

仍然用vs调试,观察内存布局

C++逆向学习(二) vector

vector a 的第一个字段是 size 大小 第二个字段是 capacity 容量

std::string 差不多

size>capacity 也就是空间不够用时

首先配置一块新空间,然后将元素从旧空间一一搬往新空间,再把旧空间归还给操作系统

内存增长机制

测试代码:

#include<iostream>
#include<vector>
using namespace std;

int main(int argc, char** argv) {
    std::vector<int> a;
    int num[16];
    for (int i = 0; i < 100; i++) {
        a.push_back(i);
        std::cout << "size : " << i+1 << "\t" << "capacity : " << a.capacity() << std::endl;
    }
    system("pause");
    return 0;
}
//visual studio 2019 x64

运行结果:

C++逆向学习(二) vector

可以看到,后面的增长速度和 std::string 一样是1.5倍扩容,一开始有点差别,分析一下源码

else if (max_size() - size() < _Count)
    //可以申请的最大容量也不够用,抛出异常_THROW(length_error, "vector<T> too long");
    _Xlen();
else if (_Capacity < size() + _Count){//空间不足,需要扩容   
    _Capacity = max_size() - _Capacity / 2 < _Capacity
        ? 0 : _Capacity + _Capacity / 2;    // 尝试扩容1.5倍
    if (_Capacity < size() + _Count)//扩容1.5倍后依然不够用,则容量等于当前数据个数加上新增数据个数
        _Capacity = size() + _Count;
    pointer _Newvec = this->_Alval.allocate(_Capacity);//申请新空间
    pointer _Ptr = _Newvec;
    _TRY_BEGIN
        _Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),
            _Newvec);   //move原先的数据
    _Ptr = _Ucopy(_First, _Last, _Ptr); //copy新增的数据到新内存之后
    _Umove(_VEC_ITER_BASE(_Where), _Mylast, _Ptr);  
    _CATCH_ALL
        _Destroy(_Newvec, _Ptr);
    this->_Alval.deallocate(_Newvec, _Capacity);//释放原来申请的内存
    _RERAISE;
    _CATCH_END
...

详见注释,注意这句 扩容1.5倍后依然不够用,则容量等于当前数据个数加上新增数据个数 ,也就解释了一开始的增长是 1 2 3 4 的原因

调试

具体调试一下,当 push_back (0)和(1)时:

C++逆向学习(二) vector

注意一开始的内存窗口,每次动态扩容时确实已经改变了存储空间的地址

再F5执行到断点,内存窗口的 红色 说明这块内存刚动过,已经被操作系统回收了, vector 中的元素也已经改变了存放地址

C++逆向学习(二) vector

accumulate

上次写西湖论剑 easyCpp 的探究时有朋友说再举一些 std::accumulate 的例子...

关于用 std::accumulate + lambda 反转 vector ,在上一篇文章已经写过了

西湖论剑初赛easyCpp探究

在这边就算是补个例子

#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>

using namespace std;

int main(int argc, char** argv) {
    std::vector<int> v(5);
    for (int i = 0; i < 5; i++) {
        std::cin >> v[i];
    }
    int sum = std::accumulate(v.begin(), v.end(), 0,
        [](int acc, int _) {return acc + _; });
    std::cout << sum;
    return 0;
}
//visual studio 2019 x64

std::accumulate 对一个容器进行 折叠 ,并且是 左折叠 ,对其进行 一元操作 ,实例中为 lambda +

因为 迭代器 可以看作是 容器算法 的中间层,这也是STL的设计哲学,因此传入的是 vectorbegin()end()

在"循环"的内部,通过判断 当前迭代器是否到达末尾 得到是否结束循环的信息,形如:

for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter){
    /*...*/ 
}

IDA视角

IDA中打开,因为是windows下vs编译的,看不出 vectoraccumulatelambda 的特征了

C++逆向学习(二) vector

分析一下,开了一块内存0x14字节,也就是对应我们的5个int

依次输入赋值,最后用一个指针++遍历这个地址

获得累加和并输出

transform

换个稍复杂的 std::transform 的例子,保留特征,用g++编译

#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>

using namespace std;

int main(int argc, char** argv) {
    std::vector<int> a = { 1,2,3,4,5};
    std::vector<int> b(5);
    std::vector<int> result;
    for (int i = 0; i < 5; i++) { std::cin >> b[i]; }
    std::transform(a.begin(), a.end(), b.begin(), std::back_inserter(result),
        [](int _1, int _2) { return _1 * _2; });

    for (int i = 0; i < 5; i++) {
        if (result[i] != 2 * (i + 1)) {
            std::cout << "You failed!" << std::endl;
            exit(0);
        }
    }
    std::cout << "You win!" << std::endl;
    return 0;
}
//g++ main.cpp -o test -std=c++14

std::transform 同时对两个列表进行操作,输入5个数存入 vector b 中,然后 vector result 分别是 a[i]*b[i] ,最后判断 result 中的每个数是否符合要求

注意, vector b 大小一定要超过 vector a ,从参数中也可以看出来, b 只传入了 begin()

如果 vector b 较小,后面的内存存放的是未知的数据

会造成未定义行为 UB

IDA视角

IDA打开可以看到 vector 相关代码,但是命名很乱,根据 std::transform 二元操作符 的特征我们可以更改一下变量名

C++逆向学习(二) vector

我们定义的 vector{1,2,3,4,5} 在内存中如下

C++逆向学习(二) vector

跟进 std::transform

C++逆向学习(二) vector

一眼注意到最关键的 lambda ,其他都是 operator* = ++ 等重载的迭代器相关的操作符

熟悉 transform 的话显然没有需要我们关注的东西

C++逆向学习(二) vector

lambda 中也只是我们实现的简单乘法运算

C++逆向学习(二) vector

算法很简单,只要输入5个2就会得到 win

vector存vector

这个程序写的有点...没事找事,用于再深入分析一下

比如输入10个数,分别放入size为1 2 3 4的四个vector,并且把4个vector一起放在一个vector中,再进行运算

虽然正常程序不会这么写,但是作为逆向的混淆感觉效果不错

#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>

using namespace std;

int main(int argc, char** argv) {
    std::vector<std::vector<int>> a;
    a.push_back(std::vector<int>{1, 2, 3});
    a.push_back(std::vector<int>{6, 7});
    for (auto v : a) {
        for (auto n : v) {
            std::cout << n << "\t";
        }
        std::cout << std::endl;
    }
    return 0;
}
//g++ main.cpp -std=c++14 -o test

内存结构

为了方便说明,仍然在vs下观察内存结构

C++逆向学习(二) vector

一开始纠结了很久,因为 vector 开的内存必定是连续的,也就是说 {1,2,3} 是连续的, {6,7} 也是连续的

那么外层 vector 如果把 {1,2,3},{6,7} 存在一起,那么当内层 vector 扩容时,一定会影响到外层 vector

最后才明白,外层 vector 只是存了内层 vector 的数据结构,而不是直接存了 {1,2,3},{6,7}

IDA视角

IDA打开g++编译过后的程序,便于学习演示

C++逆向学习(二) vector

结合注释和变量的重命名,逻辑比较清晰

vector_vector<vector<int> >.push_back(&vec1)

可以理解为外层 vector 存了内层 vector 的"指针"

输出部分:

C++逆向学习(二) vector

稍微有些不理解,看起来两个内层 vector 的迭代器之间有一些优化

vec1 = end(vec2_addr) ,这一句没怎么看懂,因为上传附件经常丢失...没有上传例程,通过源码编译比较简单,大佬们有兴趣可以试着逆一下逻辑

不过主线还是清晰的

  • 外层 vector 的迭代器 operator ++operator !=

  • 双层循环,内层循环分别得到每个内层 vector*iterator ,通过 ostream 输出

小总结

vector 中连续内存里存的是 类型的数据结构 ,比如 int 的数据结构, vector<int> 的数据结构

但无论如何,每个 vector 用于存数据的内存都是连续的

比如 {1,2,3} , vector<int>{1,2},vector<int>{3,4,5} 这两个 vector

C++逆向学习(二) vector

C++逆向学习(二) vector

C++逆向学习(二) vector

请猛戳右边二维码

Twitter:AsrcSecurity

公众号ID

阿里安全响应中心

C++逆向学习(二) vector


以上所述就是小编给大家介绍的《C++逆向学习(二) vector》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

JAVA核心技术卷2

JAVA核心技术卷2

Cay S. Horstmann、Gary Cornell / 陈昊鹏、王浩、姚建平 / 机械工业出版社 / 2008-12 / 118.00元

《JAVA核心技术卷2:高级特征》是Java技术权威指南,全面覆盖Java技术的高级主题,包括流与文件、XML、网络、数据库编程、高级Swing、高级 AWT、JavaBean构件、安全、分布式对象、脚本、编译与注解处理等,同时涉及本地化、国际化以及Java SE 6的内容。《JAVA核心技术卷Ⅱ:高级特征》对Java技术的阐述精确到位,叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Jav......一起来看看 《JAVA核心技术卷2》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具