C++反汇编 0x00

实验环境

不做特殊说明均为vs2022 x64 release版本 关闭优化,固定基址,关闭canary

image-20231025163548674

类特性

没有数据成员的类是占一个字节的,为了实例化后可以调用函数成员

类的静态数据成员与局部静态变量类似,存放的位置和全局变量一致。只是编译器增加了作用域的检查,在作用域之外不可见。

this指针

this指针中保存了所属对象的首地址,需要访问类数据成员时通过rcx传递this指针,默认调用约定称为thiscall

#include <stdio.h>

class Person {
public:
void setAge(int age) { //公有成员函数
this->age = age;
}
public:
int age; //公有数据成员
};

int main(int argc, char* argv[]) {
Person person;
person.setAge(5); //调用成员函数
printf("Person : %d\n", person.age); //获取数据成员
return 0;
}
.text:0000000140001450 ; int __fastcall main(int argc, char **argv)
.text:0000000140001450 main proc near ; CODE XREF: main_0↑j
.text:0000000140001450 ; DATA XREF: .pdata:000000014000900C↓o
.text:0000000140001450
.text:0000000140001450 var_18= Person ptr -18h
.text:0000000140001450 arg_0= dword ptr 8
.text:0000000140001450 arg_8= qword ptr 10h
.text:0000000140001450
.text:0000000140001450 mov [rsp+arg_8], rdx
.text:0000000140001455 mov [rsp+arg_0], ecx ; 保护参数
.text:0000000140001459 sub rsp, 38h
.text:000000014000145D mov edx, 5 ; 参数为5
.text:0000000140001462 lea rcx, [rsp+38h+var_18] ; this指针指向person的首地址
.text:0000000140001467 call j_?setAge@Person@@QEAAXH@Z ; Person::setAge(int)
.text:0000000140001467
.text:000000014000146C mov edx, [rsp+38h+var_18.age]
.text:0000000140001470 lea rcx, _Format ; "Person : %d\n"
.text:0000000140001477 call j_printf
.text:0000000140001477
.text:000000014000147C xor eax, eax
.text:000000014000147E add rsp, 38h
.text:0000000140001482 retn
.text:0000000140001482
.text:0000000140001482 main endp
.text:00000001400013C0 ; void __fastcall Person::setAge(Person *this, int age)
.text:00000001400013C0 ?setAge@Person@@QEAAXH@Z proc near ; CODE XREF: Person::setAge(int)↑j
.text:00000001400013C0
.text:00000001400013C0 arg_0= qword ptr 8
.text:00000001400013C0 arg_8= dword ptr 10h
.text:00000001400013C0
.text:00000001400013C0 mov [rsp+arg_8], edx ;参数:5
.text:00000001400013C4 mov [rsp+arg_0], rcx ; this指针指向person首地址
.text:00000001400013C9 mov rax, [rsp+arg_0] ; rax=this指针
.text:00000001400013CE mov ecx, [rsp+arg_8] ; rcx=5
.text:00000001400013D2 mov [rax], ecx ; this->age=5
.text:00000001400013D4 retn

对象作为函数参数

进入函数前预先保留对象使用的栈空间,类中没有定义拷贝构造函数,编译器会对原对象与临时对象中的各数据成员直接进行数据复制,形成一个临时对象,称为默认复制构造函数,这种复制方式属于浅拷贝,如果类带有指针变量,并有动态内存分配,它必须有一个拷贝构造函数否则会有问题

#include <stdio.h>
#include <string.h>

class Person {
public:
Person() {
name = new char[32]; //申请堆空间,只要不释放,进程退出前将一直存在
age = 18;
if (name != NULL) { //堆空间申请成功与否
strcpy(name, "tom");
}
}
~Person() {
if (name != NULL) { //检查资源
delete[] name; //释放堆空间
name = NULL;
}
}

const char* getName() {
return name; //获取数据成员
}
private:
char* name; //数据成员定义,保存堆的首地址
int age;
};

//参数为Person类对象的函数
void show(Person obj) {
printf(obj.getName());
}

int main(int argc, char* argv[]) {
Person person; //类对象定义
show(person);
return 0;
}
.text:0000000140001650 var_48= dword ptr -48h
.text:0000000140001650 var_40= Person ptr -40h
.text:0000000140001650 obj= Person ptr -28h
.text:0000000140001650 arg_0= dword ptr 8
.text:0000000140001650 arg_8= qword ptr 10h
.text:0000000140001650
.text:0000000140001650 ; FUNCTION CHUNK AT .text:0000000140005260 SIZE 00000018 BYTES
.text:0000000140001650
.text:0000000140001650 ; __unwind { // __CxxFrameHandler4_0
.text:0000000140001650 mov [rsp+arg_8], rdx
.text:0000000140001655 mov [rsp+arg_0], ecx
.text:0000000140001659 push rsi
.text:000000014000165A push rdi
.text:000000014000165B sub rsp, 58h
.text:000000014000165F lea rcx, [rsp+68h+var_40] ; this
.text:0000000140001664 call j_??0Person@@QEAA@XZ ; Person::Person(void)
.text:0000000140001664
.text:0000000140001669 nop
.text:000000014000166A lea rax, [rsp+68h+obj]
.text:000000014000166F lea rcx, [rsp+68h+var_40]
.text:0000000140001674 mov rdi, rax
.text:0000000140001677 mov rsi, rcx
.text:000000014000167A mov ecx, 10h
.text:000000014000167F rep movsb ; 采用默认拷贝构造函数在rsp+68+obj浅拷贝生成person复制品作为参数
.text:0000000140001681 lea rcx, [rsp+68h+obj] ; obj
.text:0000000140001686 call j_?show@@YAXVPerson@@@Z ; show(Person)
.text:0000000140001686
.text:000000014000168B mov [rsp+68h+var_48], 0
.text:0000000140001693 lea rcx, [rsp+68h+var_40] ; this
.text:0000000140001698 call j_??1Person@@QEAA@XZ ; Person::~Person(void)
.text:0000000140001698
.text:000000014000169D mov eax, [rsp+68h+var_48]
.text:00000001400016A1 add rsp, 58h
.text:00000001400016A5 pop rdi
.text:00000001400016A6 pop rsi
.text:00000001400016A7 retn
.text:00000001400015B0 ; void __fastcall show(Person obj)
.text:00000001400015B0 ?show@@YAXVPerson@@@Z proc near ; CODE XREF: show(Person)↑j
.text:00000001400015B0 ; DATA XREF: .pdata:000000014000B018↓o
.text:00000001400015B0
.text:00000001400015B0 arg_0= qword ptr 8
.text:00000001400015B0
.text:00000001400015B0 ; FUNCTION CHUNK AT .text:0000000140005240 SIZE 00000018 BYTES
.text:00000001400015B0
.text:00000001400015B0 ; __unwind { // __CxxFrameHandler4_0
.text:00000001400015B0 mov [rsp+arg_0], rcx ;临时对象地址
.text:00000001400015B5 sub rsp, 28h
.text:00000001400015B9 mov rcx, [rsp+28h+arg_0] ; 临时对象地址 this
.text:00000001400015BE call j_?getName@Person@@QEAAPEBDXZ ; Person::getName(void)
.text:00000001400015BE
.text:00000001400015C3 mov rcx, rax ; _Format
.text:00000001400015C6 call j_printf
.text:00000001400015C6
.text:00000001400015CB nop
.text:00000001400015CC mov rcx, [rsp+28h+arg_0] ; this
.text:00000001400015D1 call j_??1Person@@QEAA@XZ ; Person::~Person(void)
.text:00000001400015D1
.text:00000001400015D6 add rsp, 28h
.text:00000001400015DA retn

采用了浅拷贝将同一个堆地址赋值给name,show里调用了析构释放了堆,main里的也析构造成double free crash

第一种解决方法

设置引用计数,在进入复制构造函数时,记录类对象被复制引用的次数。当对象被销毁时,检查这个引用计数中保存的引用复制次数是否为0。如果是,则释放申请的资源,否则引用计数减1。

第二种解决方案

使用拷贝构造函数深拷贝数据

classname (const classname &obj) {
// 拷贝构造函数的主体
}
Person(const Person &obj) {
this->age = obj.age;
int len = strlen(obj.name);
this->name = new char[len + sizeof(char)];
strcpy(this->name, obj.name);
}
.text:00000001400016B0 ; __int64 __fastcall main(int argc, char **argv)
.text:00000001400016B0 main proc near ; CODE XREF: main_0↑j
.text:00000001400016B0 ; DATA XREF: .pdata:000000014000B018↓o
.text:00000001400016B0
.text:00000001400016B0 var_48= dword ptr -48h
.text:00000001400016B0 var_40= qword ptr -40h
.text:00000001400016B0 var_38= Person ptr -38h
.text:00000001400016B0 obj= Person ptr -30h
.text:00000001400016B0 var_20= qword ptr -20h
.text:00000001400016B0 arg_0= dword ptr 8
.text:00000001400016B0 arg_8= qword ptr 10h
.text:00000001400016B0
.text:00000001400016B0 ; FUNCTION CHUNK AT .text:0000000140005260 SIZE 00000018 BYTES
.text:00000001400016B0
.text:00000001400016B0 ; __unwind { // __CxxFrameHandler4_0
.text:00000001400016B0 mov [rsp+arg_8], rdx
.text:00000001400016B5 mov [rsp+arg_0], ecx
.text:00000001400016B9 sub rsp, 68h
.text:00000001400016BD lea rcx, [rsp+68h+obj] ; this
.text:00000001400016C2 call j_??0Person@@QEAA@XZ ; Person::Person(void)
.text:00000001400016C2
.text:00000001400016C7 nop
.text:00000001400016C8 lea rax, [rsp+68h+var_20]
.text:00000001400016CD mov [rsp+68h+var_40], rax
.text:00000001400016D2 lea rdx, [rsp+68h+obj] ; obj
.text:00000001400016D7 mov rcx, [rsp+68h+var_40] ; this
.text:00000001400016DC call j_??0Person@@QEAA@AEBV0@@Z ; Person::Person(Person const &)深拷贝,返回新对象
.text:00000001400016DC
.text:00000001400016E1 mov [rsp+68h+var_38.name], rax
.text:00000001400016E6 mov rcx, [rsp+68h+var_38.name] ; obj
.text:00000001400016EB call j_?show@@YAXVPerson@@@Z ; show(Person)
.text:00000001400016EB
.text:00000001400016F0 mov [rsp+68h+var_48], 0
.text:00000001400016F8 lea rcx, [rsp+68h+obj] ; this
.text:00000001400016FD call j_??1Person@@QEAA@XZ ; Person::~Person(void)
.text:00000001400016FD
.text:0000000140001702 mov eax, [rsp+68h+var_48]
.text:0000000140001706 add rsp, 68h
.text:000000014000170A retn
.text:0000000140003140 ; void __fastcall Person::Person(Person *this, const Person *obj)
.text:0000000140003140 ??0Person@@QEAA@AEBV0@@Z proc near ; CODE XREF: Person::Person(Person const &)↑j
.text:0000000140003140 ; DATA XREF: .pdata:000000014000B2AC↓o
.text:0000000140003140
.text:0000000140003140 var_48= byte ptr -48h
.text:0000000140003140 var_44= dword ptr -44h
.text:0000000140003140 var_40= qword ptr -40h
.text:0000000140003140 var_38= qword ptr -38h
.text:0000000140003140 var_30= qword ptr -30h
.text:0000000140003140 var_28= qword ptr -28h
.text:0000000140003140 var_20= qword ptr -20h
.text:0000000140003140 var_18= qword ptr -18h
.text:0000000140003140 arg_0= qword ptr 8
.text:0000000140003140 arg_8= qword ptr 10h
.text:0000000140003140
.text:0000000140003140 mov [rsp+arg_8], rdx
.text:0000000140003145 mov [rsp+arg_0], rcx ; this
.text:000000014000314A sub rsp, 68h
.text:000000014000314E mov rax, [rsp+68h+arg_0] ; this
.text:0000000140003153 mov rcx, [rsp+68h+arg_8] ; obj
.text:0000000140003158 mov ecx, [rcx+8] ; obj.age
.text:000000014000315B mov [rax+8], ecx ; this->age = obj.age;
.text:000000014000315E mov rax, [rsp+68h+arg_8] ; obj
.text:0000000140003163 mov rax, [rax] ; rax=obj.name
.text:0000000140003166 mov [rsp+68h+var_28], rax
.text:000000014000316B mov [rsp+68h+var_40], 0FFFFFFFFFFFFFFFFh
.text:000000014000316B
.text:0000000140003174
.text:0000000140003174 loc_140003174: ; CODE XREF: Person::Person(Person const &)+47↓j
.text:0000000140003174 inc [rsp+68h+var_40] ; rsp+68h+var_40是局部变量len
.text:0000000140003179 mov rax, [rsp+68h+var_28]
.text:000000014000317E mov rcx, [rsp+68h+var_40]
.text:0000000140003183 cmp byte ptr [rax+rcx], 0
.text:0000000140003187 jnz short loc_140003174 ; rsp+68h+var_40是局部变量len
.text:0000000140003187
.text:0000000140003189 mov rax, [rsp+68h+var_40]
.text:000000014000318E mov [rsp+68h+var_44], eax ; int len = strlen(obj.name);
.text:0000000140003192 movsxd rax, [rsp+68h+var_44]
.text:0000000140003197 inc rax
.text:000000014000319A mov rcx, rax ; size
.text:000000014000319D call j_??_U@YAPEAX_K@Z ; operator new[](unsigned __int64)
.text:000000014000319D
.text:00000001400031A2 mov [rsp+68h+var_20], rax
.text:00000001400031A7 mov rax, [rsp+68h+arg_0]
.text:00000001400031AC mov rcx, [rsp+68h+var_20]
.text:00000001400031B1 mov [rax], rcx ; this->name = new char[len + sizeof(char)];
.text:00000001400031B4 mov rax, [rsp+68h+arg_8]
.text:00000001400031B9 mov rax, [rax]
.text:00000001400031BC mov [rsp+68h+var_30], rax
.text:00000001400031C1 mov rax, [rsp+68h+arg_0]
.text:00000001400031C6 mov rax, [rax]
.text:00000001400031C9 mov [rsp+68h+var_38], rax
.text:00000001400031CE mov rax, [rsp+68h+var_38]
.text:00000001400031D3 mov [rsp+68h+var_18], rax
.text:00000001400031D3
.text:00000001400031D8
.text:00000001400031D8 loc_1400031D8: ; CODE XREF: Person::Person(Person const &)+CF↓j
.text:00000001400031D8 mov rax, [rsp+68h+var_30]
.text:00000001400031DD movzx eax, byte ptr [rax]
.text:00000001400031E0 mov [rsp+68h+var_48], al
.text:00000001400031E4 mov rax, [rsp+68h+var_38]
.text:00000001400031E9 movzx ecx, [rsp+68h+var_48]
.text:00000001400031EE mov [rax], cl
.text:00000001400031F0 mov rax, [rsp+68h+var_30]
.text:00000001400031F5 inc rax
.text:00000001400031F8 mov [rsp+68h+var_30], rax
.text:00000001400031FD mov rax, [rsp+68h+var_38]
.text:0000000140003202 inc rax
.text:0000000140003205 mov [rsp+68h+var_38], rax
.text:000000014000320A cmp [rsp+68h+var_48], 0
.text:000000014000320F jnz short loc_1400031D8 ; strcpy(this->name, obj.name);
.text:000000014000320F
.text:0000000140003211 mov rax, [rsp+68h+arg_0] ; 返回新对象
.text:0000000140003216 add rsp, 68h
.text:000000014000321A retn

对象作为返回值

不能用寄存器返回的采用在调用者函数里开辟局部空间,通过拷贝构造函数赋值,同样会存在作为函数参数时的一样的问题

#include <stdio.h>

class Person {
public:
int count;
int buffer[10]; //定义两个数据成员,该类的大小为 44 字节
};

Person getPerson() {
Person person;
person.count = 10;
person.buffer[0] = 'g';

return person; //返回局部对象
}

int main(int argc, char* argv[]) {
Person person;
person = getPerson();
printf("%d %d %d", person.count, person.buffer[0], person.buffer[9]);
return 0;
}

汇编太长了,不贴了,伪代码基本可以把汇编实现反应出来

main里开辟局部变量,作为getPerson参数,返回后再复制给v4再给v3

没搞懂为什么不直接用result,还要再给v4赋值一下

__int64 __fastcall main(int argc, char **argv)
{
int v3[11]; // [rsp+20h] [rbp-A8h] BYREF
char v4[44]; // [rsp+50h] [rbp-78h] BYREF
Person result; // [rsp+80h] [rbp-48h] BYREF

qmemcpy(v4, getPerson(&result), sizeof(v4));
qmemcpy(v3, v4, sizeof(v3));
j_printf("%d %d %d", (unsigned int)v3[0], (unsigned int)v3[1], (unsigned int)v3[10]);
return 0i64;
}
Person *__fastcall getPerson(Person *result)
{
LODWORD(result->name) = 10;
HIDWORD(result->name) = 'g';
return result;
}

构造函数调用时机

局部对象

隐式传入this指针,返回this指针

Person person; 

堆对象

#include <stdio.h>

class Person {
public:
Person() {
age = 20;
}
int age;
};

int main(int argc, char* argv[]) {
Person* p = new Person;
p->age = 21;
printf("%d\n", p->age);
return 0;
}

new完后会判断是否申请失败,决定是否调用构造函数,malloc不负责触发构造函数,它也不是运算符,无法进行运算符重载。

.text:0000000140003010                               main proc near                          ; CODE XREF: main_0↑j
.text:0000000140003010 ; DATA XREF: .pdata:000000014000B270↓o
.text:0000000140003010
.text:0000000140003010 var_28= qword ptr -28h
.text:0000000140003010 var_20= qword ptr -20h
.text:0000000140003010 var_18= qword ptr -18h
.text:0000000140003010 var_10= qword ptr -10h
.text:0000000140003010 arg_0= dword ptr 8
.text:0000000140003010 arg_8= qword ptr 10h
.text:0000000140003010
.text:0000000140003010 ; FUNCTION CHUNK AT .text:0000000140005240 SIZE 0000001D BYTES
.text:0000000140003010
.text:0000000140003010 ; __unwind { // __CxxFrameHandler4_0
.text:0000000140003010 48 89 54 24 10 mov [rsp+arg_8], rdx
.text:0000000140003015 89 4C 24 08 mov [rsp+arg_0], ecx
.text:0000000140003019 48 83 EC 48 sub rsp, 48h
.text:000000014000301D B9 04 00 00 00 mov ecx, 4 ; size
.text:0000000140003022 E8 F2 DF FF FF call j_??2@YAPEAX_K@Z ; operator new(unsigned __int64)
.text:0000000140003022
.text:0000000140003027 48 89 44 24 20 mov [rsp+48h+var_28], rax
.text:000000014000302C 48 83 7C 24 20 00 cmp [rsp+48h+var_28], 0
.text:0000000140003032 74 11 jz short loc_140003045 ;跳过构造函数
.text:0000000140003032
.text:0000000140003034 48 8B 4C 24 20 mov rcx, [rsp+48h+var_28] ; this
.text:0000000140003039 E8 3D E2 FF FF call j_??0Person@@QEAA@XZ ; Person::Person(void)
.text:0000000140003039
.text:000000014000303E 48 89 44 24 28 mov [rsp+48h+var_20], rax
.text:0000000140003043 EB 09 jmp short loc_14000304E
.text:0000000140003043
.text:0000000140003045 ; ---------------------------------------------------------------------------
.text:0000000140003045
.text:0000000140003045 loc_140003045: ; CODE XREF: main+22↑j
.text:0000000140003045 48 C7 44 24 28 00 00 00 00 mov [rsp+48h+var_20], 0
.text:0000000140003045
.text:000000014000304E
.text:000000014000304E loc_14000304E: ; CODE XREF: main+33↑j
.text:000000014000304E 48 8B 44 24 28 mov rax, [rsp+48h+var_20]

参数对象&&返回对象

全局对象&&静态对象

析构函数调用时机

局部对象

传递this指针

堆对象

#include <stdio.h>

class Person {
public:
Person() {
age = 20;
}
~Person() {
printf("~Person()\n");
}
int age;
};

int main(int argc, char* argv[]) {
Person* person = new Person();
person->age = 21; //为了便于讲解,这里没检查指针
printf("%d\n", person->age);
delete person;
return 0;
}

调用delete时如果判断为0直接跳过析构函数,调用代理析构函数时,传入了两参数 tish指针和1

.text:00000001400016E5 mov     [rsp+58h+var_20], rax           ; 对象地址
.text:00000001400016EA cmp [rsp+58h+var_20], 0
.text:00000001400016F0 jz short loc_140001708
.text:00000001400016F0
.text:00000001400016F2 mov edx, 1 ; unsigned int
.text:00000001400016F7 mov rcx, [rsp+58h+var_20] ; this
.text:00000001400016FC call j_??_GPerson@@QEAAPEAXI@Z ; Person::`scalar deleting destructor'(uint)

第二个参数1表示应该如何析构,例如下面的情况

int main(int argc, char* argv[]) {
Person *objs = new Person[3]; //申请对象数组
delete[] objs; //释放对象数组
return 0;
}

new申请时申请了20大小,比Person[3]多了堆空间的首地址处的8字节来内容保存对象数量

.text:000000014000169D mov     ecx, 14h                        ; size
.text:00000001400016A2 call j_??_U@YAPEAX_K@Z ; operator new[](unsigned __int64)
.text:00000001400016A2
.text:00000001400016A7 mov [rsp+68h+var_38], rax
.text:00000001400016AC cmp [rsp+68h+var_38], 0
.text:00000001400016B2 jz short loc_1400016FF
.text:00000001400016B2
.text:00000001400016B4 mov rax, [rsp+68h+var_38]
.text:00000001400016B9 mov qword ptr [rax], 3 ;首地址处的8字节保存对象数量

调用代理构造函数(第一个对象地址,对象大小,对象个数,构造函数地址),代理构造函数去一个一个构造对象

.text:00000001400016C0 mov     rax, [rsp+68h+var_38]
.text:00000001400016C5 add rax, 8
.........
.text:00000001400016D5 lea r9, j_??0Person@@QEAA@XZ ; constructor
.text:00000001400016DC mov r8d, 3 ; count
.text:00000001400016E2 mov edx, 4 ; size
.text:00000001400016E7 mov rcx, rax ; ptr
.text:00000001400016EA call j_??_L@YAXPEAX_K1P6AX0@Z2@Z ; `eh vector constructor iterator'(void *,unsigned __int64,unsigned __int64,void (*)(void *),void (*)(vo

调用代理析构函数(第一个对象地址,释放对象类型标志:1为释放单个对象,3为释放对象数组,0表示单个对象仅仅执行析构函数,不释放堆空间,2调用对象数组析构函数,不释放堆空间)

delete[]参数二为3,delete为1

.text:000000014000172E mov     edx, 3                          ; unsigned int
.text:0000000140001733 mov rcx, [rsp+68h+var_28] ; this
.text:0000000140001738 call j_??_EPerson@@QEAAPEAXI@Z ; Person::`vector deleting destructor'(uint)

参数对象&&返回对象

全局对&&静态对象

Reference

C++反汇编与逆向分析技术揭秘