吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7579|回复: 17
收起左侧

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

[复制链接]
默小白 发表于 2019-4-29 09:28

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

CTF比赛中C++的题越来越多,题目中经常出现stringvector等,而实际上手时发现常常迷失在"库函数"中,比如跟进了空间配置器相关函数

最近研究一下关于这些的底层机制与逆向,应该会写成一个系列

string

内存布局

visual studio的调试实在是太好用了,因此用它举例

定义一个string类,字符串为abcd,内存布局如下

img

其中,size是当前字符串长度,capacity是最大的容量

可以发现,capacitysize大的多

allocator是空间配置器,可以看到单独的字符显示

原始视图中可以得知,字符串的首地址

img

可以看到,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

img

再次+='a'

img

原先的capacity已经从0x0f变成了0x1f,长度也变成了16

而原先存储字符串的一部分内存也已经被杂乱的字符覆盖了

新的字符串被连续存储在另一块地址

img

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的成员进行适合读代码的命名,只好自己改一下

img

第一块逻辑,当size>capacity时,调用Rellocate_xxx函数

否则,就直接在str_addr后追加一个97,也就是a

img

第二块逻辑,这次因为用的是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相关的

img

之前在做题时经常分不清作者写的代码、库函数代码,经常靠动态调试猜,多分析之后发现清晰了不少

测试程序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++吗...

img

调用了一次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打开后虽然没有友好的命名,需要自己改,但是逻辑很清晰

img

for(auto c:input2)这句是一个"语法糖",迭代地取出每一个字符,追加到input1

IDA中可以看到,迭代器begin和end,通过循环中的operator!=判断是否已经结束,再通过operator+=追加,最后通过operator++来改变迭代器input2_begin的值

这里命名应该把input2_begin改成iterator更好一些,因为它只是一开始是begin

小总结

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

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

免费评分

参与人数 2吾爱币 +1 热心值 +2 收起 理由
橘子子 + 1 + 1 谢谢@Thanks!
lsj.xiaojin + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

JuncoJet 发表于 2019-4-29 11:24
IDA的F5应该还是有限的吧,VC编译可能比较容易被IDA
但是MinGW编译…… F5的可能性就很低,所以从基础的结构看,看汇编代码

Image 365.jpg
Image 364.jpg
给个程序大家练练手

猜数字解算器.rar

87.55 KB, 下载次数: 6, 下载积分: 吾爱币 -1 CB

头像被屏蔽
沐雨红尘 发表于 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
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-11 11:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表