内容简介:此文为原创文章作者:ret2nullptr@先知社区
此文为原创文章
作者:ret2nullptr@先知社区
恭喜作者获得
价值100元的天猫超市享淘卡一张
欢迎更多优质原创、翻译作者加入
ASRC文章奖励计划
欢迎多多投稿到先知社区
每天一篇优质技术好文
点滴积累促成质的飞跃
今天也要进步一点点呀
CTF比赛中C++的题越来越多,题目中经常出现 string , vector 等,而实际上手时发现常常迷失在"库函数"中,比如跟进了空间配置器相关函数
最近研究一下关于这些的底层机制与逆向,应该会写成一个系列
string
内存布局
visual studio 的调试实在是太好用了,因此用它举例
其中, size 是当前字符串长度, capacity 是最大的容量
可以发现, capacity 比 size 大的多
而 allocator 是空间配置器,可以看到单独的字符显示
原始视图中可以得知,字符串的首地址
可以看到, abcd 字符串在内存中也是以 \x00 结尾的
扩容机制
正是由于 capacity 开辟了更多需要的空间,来具体研究一下它的策略
#include<iostream>
#include<string>
#include<stdlib.h>
#include<windows.h>
using namespace std;
int main(int argc, char** argv) {
string str;
for (int i = 0; i < 100; i++) {
str += 'a';
std::cout << "size : " << str.size() << " capacity : " << str.capacity() << std::endl;
}
system("pause");
return 0;
}
从输出结果发现, capacity 的变化为 15 -> 31 -> 47 -> 70 -> 105
注意到15是二进制的 1111 ,而31是二进制的 11111 ,可能是设计成这样的?...
只有第一次变化不是1.5倍扩容,后面都是乘以1.5
当长度为15时,如下,两个 0x0f 表示长度,而第一行倒数第三个 0f 则表示的是当前的 capacity
再次 +='a'
原先的 capacity 已经从0x0f变成了0x1f,长度也变成了16
而原先存储字符串的一部分内存也已经被杂乱的字符覆盖了
新的字符串被连续存储在另一块地址
vs的调试中,红色代表刚刚改变的值
不过原先使用的内存里还有一些 aaaa... ,可能是因为还没有被覆盖到
IDA视角
测试程序1
#include<iostream>
#include<string>
using namespace std;
int main(int argc, char** argv) {
string input;
cin >> input;
for (int i = 0; i < 3; i++) {
input += 'a';
}
for (int i = 0; i < 3; i++) {
input.append("12345abcde");
}
cout << input << endl;
return 0;
}
//visual studio 2019 x64 release
我用的IDA7.0,打开以后发现IDA似乎并没有对 string 的成员进行适合读代码的命名,只好自己改一下
第一块逻辑,当 size>capacity 时,调用 Rellocate_xxx 函数
否则,就直接在 str_addr 后追加一个97,也就是 a
第二块逻辑,这次因为用的是 append() ,每次追加10个字符,即使是一个 QWORD 也无法存放,所以看到的是 memmove_0 函数
最后是 v9[10] = 0 ,也是我们在vs中看到的,追加后,仍然会以 \x00 结尾
一开始我没想明白, +='a' 为什么没有设置 \x00结尾
后来才发现,*(_WORD*)&str_addr[_size] = 97;
这是一个 WORD ,2个byte,考虑小端序, \x00 已经被写入了
至于其中的 Reallocate_xxx 函数,有点复杂...而且感觉也没必要深入了,刚刚已经在vs里了解扩容机制了
最后还有一个 delete 相关的
之前在做题时经常分不清作者写的代码、库函数代码,经常靠动态调试猜,多分析之后发现清晰了不少
测试程序2
#include<iostream>
#include<string>
using namespace std;
int main(int argc, char** argv) {
string input1;
string input2;
string result;
std::cin >> input1;
std::cin >> input2;
result = input1 + input2;
std::cout << result;
return 0;
}
//g++-4.7 main.cpp
这次用g++编译,发现逻辑很简明,甚至让我怀疑这是C++吗...
调用了一次 operator+ ,然后 operator= 赋值,最后输出
但是用vs编译,IDA打开就很混乱...下次再仔细分析一下
测试程序3
#include<iostream>
#include<string>
using namespace std;
int main(int argc, char** argv) {
string input1;
string input2;
std::cin >> input1;
std::cin >> input2;
//语法糖
for(auto c:input2){
input1 += c;
}
std::cout << input1;
return 0;
}
//g++-4.7 main.cpp -std=c++11
仍然是g++编译的,IDA打开后虽然没有友好的命名,需要自己改,但是逻辑很清晰
for(auto c:input2) 这句是一个"语法糖",迭代地取出每一个字符,追加到 input1 上
IDA中可以看到,迭代器 begin和end ,通过循环中的 operator!= 判断是否已经结束,再通过 operator+= 追加,最后通过 operator++ 来改变迭代器 input2_begin 的值
这里命名应该把 input2_begin 改成 iterator 更好一些,因为它只是一开始是 begin
小总结
逆向水深...动态调试确实很容易发现程序逻辑,但是有反调试的存在
多练习纯静态分析也有助于解题,看得多了也就能分辨库函数代码和作者的代码了
更
多
精
彩
请猛戳右边二维码
Twitter:AsrcSecurity
公众号ID
阿里安全响应中心
以上所述就是小编给大家介绍的《C++逆向学习(一) string》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 一文读懂监督学习、无监督学习、半监督学习、强化学习这四种深度学习方式
- 学习:人工智能-机器学习-深度学习概念的区别
- 统计学习,机器学习与深度学习概念的关联与区别
- 混合学习环境下基于学习行为数据的学习预警系统设计与实现
- 学习如何学习
- 深度学习的学习历程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
孵化Twitter
[美]尼克·比尔顿(Nick Bilton) / 欧常智、张宇、单旖 / 浙江人民出版社 / 2014-1 / 49.90元
一个在挣扎中生存的博客平台Odeo,一小撮龙蛇混杂的无政府主义者员工,经历了怎样的涅槃,摇身一变,成为纽交所最闪耀的上市企业Twitter? 一个野心勃勃的农场小男孩,一个满身纹身的“无名氏“,一个爱开玩笑的外交家,一位害羞而又充满活力的极客,这四位各有特色的创始人如何从兢兢业业、每日劳作的工程师,成为了登上杂志封面、奥普拉秀和每日秀的富裕名人?而在Twitter日益茁壮成长的过程中,他们又......一起来看看 《孵化Twitter》 这本书的介绍吧!