你們要的string字符串詳解!C++逆向學習(一) string

跟著小編學習的粉絲,你們要的string字符串詳解!C++逆向學習(一) string

最近研究一下關於這些的底層機制與逆向,應該會寫成一個系列

string

內存佈局

visual studio的調試實在是太好用了,因此用它舉例


你們要的string字符串詳解!C++逆向學習(一) string


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

可以發現,capacity比size大的多

而allocator是空間配置器,可以看到單獨的字符顯示

原始視圖中可以得知,字符串的首地址


你們要的string字符串詳解!C++逆向學習(一) string


可以看到,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;
}
/<windows.h>/<stdlib.h>/<string>/<iostream>

從輸出結果發現,capacity的變化為15 -> 31 -> 47 -> 70 -> 105

注意到15是二進制的1111,而31是二進制的11111,可能是設計成這樣的?...

只有第一次變化不是1.5倍擴容,後面都是乘以1.5

當長度為15時,如下,兩個0x0f表示長度,而第一行倒數第三個0f則表示的是當前的capacity


你們要的string字符串詳解!C++逆向學習(一) string


再次+='a'


你們要的string字符串詳解!C++逆向學習(一) string


原先的capacity已經從0x0f變成了0x1f,長度也變成了16

而原先存儲字符串的一部分內存也已經被雜亂的字符覆蓋了

新的字符串被連續存儲在另一塊地址


你們要的string字符串詳解!C++逆向學習(一) string


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
/<string>/<iostream>

我用的IDA7.0,打開以後發現IDA似乎並沒有對string的成員進行適合讀代碼的命名,只好自己改一下


你們要的string字符串詳解!C++逆向學習(一) string


第一塊邏輯,當size>capacity時,調用Rellocate_xxx函數

否則,就直接在str_addr後追加一個97,也就是a


你們要的string字符串詳解!C++逆向學習(一) string


第二塊邏輯,這次因為用的是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相關的


你們要的string字符串詳解!C++逆向學習(一) string


之前在做題時經常分不清作者寫的代碼、庫函數代碼,經常靠動態調試猜,多分析之後發現清晰了不少

測試程序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
/<string>/<iostream>

這次用g++編譯,發現邏輯很簡明,甚至讓我懷疑這是C++嗎...


你們要的string字符串詳解!C++逆向學習(一) string


調用了一次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
/<string>/<iostream>

仍然是g++編譯的,IDA打開後雖然沒有友好的命名,需要自己改,但是邏輯很清晰


你們要的string字符串詳解!C++逆向學習(一) string


for(auto c:input2)這句是一個"語法糖",迭代地取出每一個字符,追加到input1上

IDA中可以看到,迭代器begin和end,通過循環中的operator!=判斷是否已經結束,再通過operator+=追加,最後通過operator++來改變迭代器input2_begin的值

這裡命名應該把input2_begin改成iterator更好一些,因為它只是一開始是begin

小總結

逆向水深...動態調試確實很容易發現程序邏輯,但是有反調試的存在

多練習純靜態分析也有助於解題,看得多了也就能分辨庫函數代碼和作者的代碼了。

你必須很努力,然後看起來才毫不費力!

關注我每天都有乾貨,看下期哦。


分享到:


相關文章: