首页 > 编程语言 >C++逆向分析——this指针

C++逆向分析——this指针

时间:2023-08-02 18:32:51浏览次数:33  
标签:逆向 return int void C++ Plus Student 指针

this指针

概述

C++是对C的拓展,C原有的语法C++都支持,并在此基础上拓展了一些语法:封装、继承、多态、模板等等。C++拓展新的语法是为了让使用更加方便、高效,这样就需要编译器多做了很多事情,接下来我们就需要一一学习这些概念。

封装

之前我们学习过结构体这个概念,那么结构体可以做参数传递吗?我们来看一下如下代码:

struct Student {

int a;


int b;


int c;


int d;

};
 

int Plus(Student s) {


return s.a + s.b + s.c + s.d;

}
 

void main() {


 Student s = {1, 2, 3, 4};


int res = Plus(s);


return;

}

上面这段代码是定义一个结构体,然后将该结构体传入Plus函数(将结构体成员相加返回),那么问题来了,结构体它是否跟数组一样,传递的是指针呢?来看一下反汇编代码:

C++逆向分析——this指针_this指针

可以很清晰的看见,结构体作为参数传递时栈顶(ESP)提升了0x10(16个字节,也就是结构体的四个成员【int】的宽度),而后将ESP的值给了EAX,再通过EAX(ESP)将结构体的成员传入函数,结构体成员从左到右依次从栈顶向下复制进入堆栈。

也就是说当我们将结构体作为参数传递时与我们传整数什么的是没有本质区别的,唯一的区别就是传递结构体时不是使用的push来传递的,而是一次性的提升堆栈,然后mov赋值。

虽然我们可以使用结构体进行传参,但是这也存在一个问题,就是当我们使用结构体传参时,假设结构体有40个成员,那么就存在着大量的内存被复制,这样效率很低,是不推荐使用的

那如果非要这样使用该怎么办呢?我们可以使用指针传递的方式来,修改一下代码:

struct Student {

int a;


int b;


int c;


int d;

};
 

int Plus(Student* p) {


return p->a + p->b + p->c + p->d;

}
 

void main() {


 Student s = {1, 2, 3, 4};


int res = Plus(&s);


return;

}

C++逆向分析——this指针_逆向分析_02

这样我们就可以使用指针的方式来避免内存的重复使用,效率更高。

可能很多人看到这就很疑惑了,那这跟C++有什么关系呢?我们之前说过C++和C的本质区别,就是编译器替代我们做了很多事情;别着急,慢慢来看。

我们使用指针优化过的代码,实际上还是存在小缺陷的,当结构体成员很多的时候,我们在Plus函数体内就要用指针的调用方式,一堆成员相加...

那么是否可以让我们调用更加简单,更加方便呢?如下代码就可以:

struct Student {

int a;


int b;


int c;


int d;

 

int Plus() {


return a + b + c + d;

 }
};
 

void main() {


 Student s = {1, 2, 3, 4};


int res = s.Plus();


return;

}

将函数放在结构体内,就不需要我们再去写传参、再去使用指针的调用方式了,因为这些工作编译器帮我们完成了,而本质上这与指针调用没有区别:

C++逆向分析——this指针_成员函数_03

而这种写法就是C++的概念:封装;也就是说将函数写在结构体内的形式就称之为封装,其带来的好处就是我们可以更加方便的使用结构体的成员。

讲到了封装,我们就要知道另外两个概念:

  1. :带有函数的结构体,称为类;
  2. 成员函数:结构体里的函数,称为成员函数
  1. 函数本身不占用结构体的空间(函数不属于结构体
  2. 调用成员函数的方法与调用结构体成员的语法是一样的 → 结构体名称.函数名()

this指针

之前我们学过了封装,如下代码:

struct Student {

int a;


int b;


int c;


int d;

 

int Plus() {


return a + b + c + d;

 }
};
 

void main() {


 Student s = {1, 2, 3, 4};


int res = s.Plus();


return;

}

其对应的反汇编代码如下:

C++逆向分析——this指针_成员函数_03

可以看见我们使用s.Plus()的时候,传递的参数是一个指针,这个指针就是当前结构体的地址,这个指针就是this指针。(通常情况下编译器会使用ecx来传递当前结构体的指针)

 

我自己实验如下:

#include "StdAfx.h"


struct Student {
	int a;
	int b;
	int c;
	int d;
 
	int Plus() {
		return a + b + c + d;
	}
};
 
void main() {
	Student s = {1, 2, 3, 4};
	int res = s.Plus();
	return;
}

 

C++逆向分析——this指针_this指针_05

C++逆向分析——this指针_编译器_06

 

 

那么当我们将Plus函数修改成无返回值,不调用结构体成员后,这个指针还会传递过来么?

struct Student {

int a;


int b;


int c;


int d;

 

void Plus() {

 
 }
};
 

void main() {


 Student s = {1, 2, 3, 4};

 s.Plus();

return;

}

我们看下反汇编代码,发现指针依然会传递过来:

C++逆向分析——this指针_this指针_07

那也就是说this指针是编译器默认传入的,通常会通过ecx进行参数的传递,不管你用还是不用,它都存在着

既然this指针会作为参数传递,我们是否也可以直接使用这个指针呢?答案是可以的:

struct Student {

int a;


int b;

 

void Init(int a, int b) {


this->a = a;
this->b = b;
 }
 
};

我们在结构体的成员函数内使用this这个关键词就可以调用了,如上代码所示。

那么this指针有什么作用呢?我们可以看下如下代码:

struct Student {

int a;


int b;

 

void Init(int a, int b) {

 a = a;
 b = b;
 }
 
};
 

void main() {

 Student s;

 s.Init(1,2);


return;

}

这段代码我们要实现的就是,使用成员函数初始化成员的值,但是实际运行却不符合我们的预期:

C++逆向分析——this指针_this指针_08

跟进反汇编代码发现,这里就是将传入的参数赋值给了参数本身,并没有改变成员的值,这是因为编译器根本不知道你这里的a到底是谁,所以我们就需要借助this指针来实现:

#include <stdio.h>
 
struct Student {

int a;


int b;

 

void Init(int a, int b) {


this->a = a;


this->b = b;

 }
 

void Print() {


 printf("%d %d", this->a, this->b);

 }
 
};
 

void main() {

 Student s;

 s.Init(1,2);

 s.Print();

return;

}

为了方便,添加一个成员函数,用于打印输出成员的值:

C++逆向分析——this指针_编译器_09

可以看见,这里成功进行初始化了。

总结:

  1. this指针是编译器默认传入的,通常会使用ecx进行参数的传递
  2. 成员函数都有this指针,无论是否使用
  3. this指针不能做++ --等运算,也不可以被重新赋值
  4. this指针不占用结构体的宽度

 

 

this指针和函数都不占用struct的空间,我们验证下:

#include <cstdio>

struct A {
    char* hello() {
        return "hi";
    }
};

int main() {
    A a;
    printf("empty struct size=%d\n", sizeof(a));
}

 

输出为1。

所以可以知道,没有任何成员变量的struct大小为1.

 

恶意代码分析实战里20.1.1章节有一个例子:

#include "StdAfx.h"


class S {
public:
	int x; 
	
	void hello() {
		if (x == 10) printf("x is 10\n");
	}
};
 
void main() {
	S s1;
	s1.x = 9;
	s1.hello();

	S s2;
	s2.x = 10;
	s2.hello();
	return;
}

 我贴下其汇编结果(vc6下的)

13:   void main() {
0040D400   push        ebp
0040D401   mov         ebp,esp
0040D403   sub         esp,48h
0040D406   push        ebx
0040D407   push        esi
0040D408   push        edi
0040D409   lea         edi,[ebp-48h]
0040D40C   mov         ecx,12h
0040D411   mov         eax,0CCCCCCCCh
0040D416   rep stos    dword ptr [edi]
14:       S s1;
15:       s1.x = 9;
0040D418   mov         dword ptr [ebp-4],9
16:       s1.hello();
0040D41F   lea         ecx,[ebp-4]
0040D422   call        @ILT+10(S::hello) (0040100f)
17:
18:       S s2;
19:       s2.x = 10;
0040D427   mov         dword ptr [ebp-8],0Ah
20:       s2.hello();
0040D42E   lea         ecx,[ebp-8]
0040D431   call        @ILT+10(S::hello) (0040100f)
21:       return;
22:   }

 

8:        void hello() {
0040D4D0   push        ebp
0040D4D1   mov         ebp,esp
0040D4D3   sub         esp,44h
0040D4D6   push        ebx
0040D4D7   push        esi
0040D4D8   push        edi
0040D4D9   push        ecx
0040D4DA   lea         edi,[ebp-44h]
0040D4DD   mov         ecx,11h
0040D4E2   mov         eax,0CCCCCCCCh
0040D4E7   rep stos    dword ptr [edi]
0040D4E9   pop         ecx
0040D4EA   mov         dword ptr [ebp-4],ecx
9:            if (x == 10) printf("x is 10\n");
0040D4ED   mov         eax,dword ptr [ebp-4]
0040D4F0   cmp         dword ptr [eax],0Ah
0040D4F3   jne         S::hello+32h (0040d502)
0040D4F5   push        offset string "x is 10\n" (0042201c)
0040D4FA   call        printf (0040d740)
0040D4FF   add         esp,4
10:       }
0040D502   pop         edi
0040D503   pop         esi
0040D504   pop         ebx
0040D505   add         esp,44h
0040D508   cmp         ebp,esp
0040D50A   call        __chkesp (0040d490)
0040D50F   mov         esp,ebp
0040D511   pop         ebp
0040D512   ret

注意,和书里的汇编代码还不一样。

奇怪,为啥我的vc6出来的结果和书里不一样。。。

C++逆向分析——this指针_this指针_10

 

 

C++逆向分析——this指针_this指针_11

https://www.jaiminton.com/Tutorials/PracticalMalwareAnalysis/# 也说使用的vc6编译。

 

 

标签:逆向,return,int,void,C++,Plus,Student,指针
From: https://blog.51cto.com/u_11908275/6941593

相关文章

  • C语言逆向——预处理之宏定义、条件编译与文件包含
    预处理之宏定义、条件编译与文件包含预处理一般是指在程序源代码被转换为二进制代码之前,由预处理器对程序源代码文本进行处理,处理后的结果再由编译器进一步编译。预处理功能主要包括宏定义、文件包含、条件编译三部分。宏定义简单的宏:#define标识符字符序列#defineFALSE0#d......
  • C语言逆向分析——Switch语句,为何大多数情况较if语句更高效?就是因为查找表
    Switch语句Switch语句也是分支语句的一种,其语法如下:switch(表达式){case常量表达式1:语句;break;case常量表达式:语句;break;case常量表达式:语句;break;......default:语句;break;}需要注意如下几点:表达式结束不能是浮点数case后的常量......
  • C语言逆向——数组和结构体,数组多维只是一个编译构造的假象,本质会转成一维数组,结构体
    数组数组是C语言中非常重要的一个概念,学习C语言主要就是两个知识点:数组、指针,学好这两个,那么你的C语言一定也会很好。什么是数组?或者说什么情况下我们需要使用数组,比如说我们需要定义一个人的年龄,我们可以定义一个变量来表示,但是如果我们需要定义三个人的年龄呢?那就需要三个变量来......
  • 逆向——字符与字符串,中文字符GB2312编码由来
    字符与字符串在之前的课程中我们了解到变量的定义决定两个事情,第一是决定存储的数据宽度,第二是决定了存储的数据格式,那么我们来看下下面的代码:inta=123;//变量x,数据宽度为4个字节,里面存储的是补码(在计算机系统中,数值一律用补码来存储)intfloatb=123.4F;//IEEE编码(浮点)......
  • 【C++数据结构】启航,打开新世界的大门!
    @TOC一、学习数据结构的原因学习数据结构对于计算机科学和软件开发非常重要,它提供了处理和组织数据的有效方法和技术。以下是几个学习数据结构的重要原因:提高问题解决能力:数据结构教会了我们如何选择和使用适当的数据结构来解决问题。了解各种数据结构的特性和性能可以帮助我们分......
  • v_jstools js逆向分析工具
    1.工具地址:https://github.com/cilame/v_jstools2.下载文件文件下载下来以后,复制到桌面,然后解压到当前文件夹,会看到一个  v_jstools-main 的文件夹。3.插件安装谷歌浏览器地址栏输入如下url,打开扩展程序页面,并打开开发者模式:chrome://extensions/打开后,点击......
  • protobuf在c++中的使用
    一、安装sudoaptinstalllibprotobuf-devprotobuf-compiler二、编辑proto文件,生成代码文件proto语法分为“proto2”和”proto3“两个版本,指定方法是在proto文件中第一行写入:syntax="proto3";,这样指定使用proto3版本的语法,如果不指定,默认是使用proto2的语法。两个语法的......
  • C/C++ 数据结构五大核心算法之分治法
    分治法——见名思义,即分而治之,从而得到我们想要的最终结果。分治法的思想是将一个规模为N的问题分解为k个较小的子问题,这些子问题遵循的处理方式就是互相独立且与原问题相同。两部分组成:分(divide):递归解决较小的问题治(conquer):然后从子问题的解构建原问题的解三个步骤:1、......
  • 第三阶段C++提高编程(黑马程序员)——Day9
    2STL初识2.1STL的诞生长久以来,软件界一直希望建立一种可重复利用的东西C++的面向对象和泛型编程思想,目的就是复用性的提升大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作为了建立数据结构和算法的一套标准诞生了STL2.2STL基本概念STL(StandardTemplateLib......
  • 【八股文 02】C++ 进程内存布局及其相关知识
    1引言本文环境为Linux操作系统(x86)+C++。目的是了解进程内存布局,但是在了解的过程中发现需要前置一些知识,因此内容概览如下所示:1C/C++程序从源代码到可执行程序的构建过程1.1预处理,也叫预编译1.2编译1.3汇编1.4链接2各平台文件格式3ELF文件3.1ELF文......