【转帖】C++逆向学习(一) string
转自:https://xz.aliyun.com/t/4890CTF比赛中C++的题越来越多,题目中经常出现`string`,`vector`等,而实际上手时发现常常迷失在"库函数"中,比如跟进了空间配置器相关函数
最近研究一下关于这些的底层机制与逆向,应该会写成一个系列
# string
## 内存布局
`visual studio`的调试实在是太好用了,因此用它举例
定义一个`string`类,字符串为`abcd`,内存布局如下
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181643-8fe5bdbe-641e-1.png)
其中,`size`是当前字符串长度,`capacity`是最大的容量
可以发现,`capacity`比`size`大的多
而`allocator`是空间配置器,可以看到单独的字符显示
原始视图中可以得知,字符串的首地址
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181652-94d454f2-641e-1.png)
可以看到,`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`
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181703-9bbf86a6-641e-1.png)
再次`+='a'`
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181709-9f81a5f8-641e-1.png)
原先的`capacity`已经从0x0f变成了0x1f,长度也变成了16
而原先存储字符串的一部分内存也已经被杂乱的字符覆盖了
新的字符串被连续存储在另一块地址
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181716-a3326958-641e-1.png)
> 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`的成员进行适合读代码的命名,只好自己改一下
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181727-a9eae91e-641e-1.png)
第一块逻辑,当`size>capacity`时,调用`Rellocate_xxx`函数
否则,就直接在`str_addr`后追加一个97,也就是`a`
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181733-ad531efa-641e-1.png)
第二块逻辑,这次因为用的是`append()`,每次追加10个字符,即使是一个`QWORD`也无法存放,所以看到的是`memmove_0`函数
最后是`v9 = 0`,也是我们在vs中看到的,追加后,仍然会以`\x00`结尾
> 一开始我没想明白,`+='a'`为什么没有设置`\x00结尾`
>
> 后来才发现,*(_WORD*)&str_addr = 97;
>
> 这是一个`WORD`,2个byte,考虑小端序,`\x00`已经被写入了
至于其中的`Reallocate_xxx`函数,有点复杂...而且感觉也没必要深入了,刚刚已经在vs里了解扩容机制了
最后还有一个`delete`相关的
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181742-b2d2146c-641e-1.png)
之前在做题时经常分不清作者写的代码、库函数代码,经常靠动态调试猜,多分析之后发现清晰了不少
### 测试程序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++吗...
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181750-b765cf32-641e-1.png)
调用了一次`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打开后虽然没有友好的命名,需要自己改,但是逻辑很清晰
!(https://xzfile.aliyuncs.com/media/upload/picture/20190421181756-bb1a78e4-641e-1.png)
`for(auto c:input2)`这句是一个"语法糖",迭代地取出每一个字符,追加到`input1`上
IDA中可以看到,迭代器`begin和end`,通过循环中的`operator!=`判断是否已经结束,再通过`operator+=`追加,最后通过`operator++`来改变迭代器`input2_begin`的值
> 这里命名应该把`input2_begin`改成`iterator`更好一些,因为它只是一开始是`begin`
## 小总结
逆向水深...动态调试确实很容易发现程序逻辑,但是有反调试的存在
多练习纯静态分析也有助于解题,看得多了也就能分辨库函数代码和作者的代码了 IDA的F5应该还是有限的吧,VC编译可能比较容易被IDA
但是MinGW编译…… F5的可能性就很低,所以从基础的结构看,看汇编代码
给个程序大家练练手 可以的,不错 学习了。谢谢大佬 膜拜大佬 学习了感谢大佬 学习了,感谢 学习了。谢谢大佬 不太懂!大佬的世界果然不一样 感谢分享
页:
[1]
2