测试代码
#include <iostream>
#include <afx.h>
int main()
{
char szHello[] = "char2 Hello GuiShou";
wchar_t wszHello[] = L"wchar_t Hello GuiShou";
std::string strHello = "string";
std::string strHello2 = "string Hello GuiShou";
CString csString = "CString";
CString csString2 = "CString Hello GuiShou";
return 0;
}
要把运行库修改为多线程MT,否则会编译不通过。这个代码因为引用了MFC的库,而且是MT
静态编译,所以分析会比较久,需要提前分析好。
这里分析字符串必须要用到动态调试,所以用x64dbg和ida同时打开程序
IDA中分析核心反汇编代码
字符串存储在data段,data段的数据是属于全局数据区,这个字符串的地址是固定不变的,也就是我们说的基地址,任何一个地方都能访问到
而char szHello[] 是局部变量,需要从全局变量拷贝到堆栈,每次复制4个字节
摁下A,可以把二进制数据转化为字符串
IDA中分析wchar数据核心代码
.text:00414885 mov ecx, 0Bh ; 用串操作指令一次性将字符串拷贝到栈区,字符串一共44字节,一个四个字节,需要复制11次
.text:0041488A mov esi, offset aWcharTHelloGui ; 将会把标号 aWcharTHelloGui 所代表的地址值赋给寄存器 esi。
.text:0041488F lea edi, [ebp+var_5C]
.text:00414892 rep movsd
这里用串操作指令的方式,一次拷贝四个字节,用串操作指令一次性将字符串拷贝到栈区
C风格的数组类型的字符串都是放在rdata段中,通过字符串拷贝的方式复制到堆栈内
.text:00414894 push offset Str ; "string"
.text:00414899 lea ecx, [ebp+var_80]
.text:0041489C call sub_4110EB
在x64dbg中观察内存结构,因为string实际上是C++的一个类,所以在初始化的时候,会将this指针传递给ecx,然后调用构造函数。
把字符串的地址压入堆栈
把这个string类的地址交给ecx
执行构造函数后,分析这个string类,这个在堆栈中
第一个字段,保存了一个指针,跳转两次以后就回到了原来的string内存结构,说明这个位置
是一个指向自己的指针。
第二个位置保存的是字符串
字符串长度小于16个字节,就保存字符串
+14的位置是字符串长度,+18的位置缓冲区的长度
00AA48A8 | 68 CCBDAA00 | push 字符串.AABDCC | ConsoleApplication1.cpp:8, AABDCC:"string Hello GuiShou"
00AA48AD | 8D8D 5CFFFFFF | lea ecx,dword ptr ss:[ebp-0xA4] |
00AA48B3 | E8 33C8FFFF | call 字符串.AA10EB |
第一个字段,保存了一个指针,跳转两次以后就回到了原来的string内存结构,说明这个位置
是一个指向自己的指针。
第二个位置保存的是一个地址
这个地址指向字符串的地址
字符串长度大于16个字节,就保存字符串的地址
+14的位置是字符串长度,+18的位置缓冲区的长度
string对象总结
struct my_string
{
my_string* self; // 指向自己的指针
union {
char str[0x10]; // 当字符个数少于 16
char* point; // 当字符个数大于 15
};
int length; // 当前的字符个数
int size; // 缓冲区的大小-1(空字符)
};
CString这里会调用两个构造函数,第一个构造函数将内存初始化
第二个构造函数给字符串赋值
查看第二个字符串
在+0的位置就保存了字符串的指针,不管字符串长度多少都保存了指 针,所以 CString只有一个字段,就是字符串的指针,没有任何结构。
CString类型的字符串本质一个指针,第一个call调用函数做初始化,第二个函数把字符串的地址给传进去。