默小白 发表于 2019-4-29 09:28

【转帖】C++逆向学习(一) string

转自:https://xz.aliyun.com/t/4890

CTF比赛中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`

## 小总结

逆向水深...动态调试确实很容易发现程序逻辑,但是有反调试的存在

多练习纯静态分析也有助于解题,看得多了也就能分辨库函数代码和作者的代码了

JuncoJet 发表于 2019-4-29 11:24

IDA的F5应该还是有限的吧,VC编译可能比较容易被IDA
但是MinGW编译…… F5的可能性就很低,所以从基础的结构看,看汇编代码



给个程序大家练练手

沐雨红尘 发表于 2019-4-29 09:43

onebei 发表于 2019-4-29 09:46

可以的,不错

caballarri 发表于 2019-4-29 09:49

学习了。谢谢大佬

piliyang 发表于 2019-4-29 09:58

膜拜大佬

宁逍遥 发表于 2019-4-29 10:05

学习了感谢大佬

tangjun 发表于 2019-4-29 10:06

学习了,感谢

hui00000 发表于 2019-4-29 10:09

学习了。谢谢大佬

Devin001100 发表于 2019-4-29 10:27

不太懂!大佬的世界果然不一样

1312513126 发表于 2019-4-29 10:55

感谢分享
页: [1] 2
查看完整版本: 【转帖】C++逆向学习(一) string