Windows逆向安全(一)之基础知识(十二)

news2024/9/28 5:24:31

结构体

C语言中的结构体是一种自定义的数据类型,一个结构体里可由其它各种类型组合而成

声明结构体

举个简单的例子,自定义一个为player的类型,如下:

struct Player{
    float hp;                //人物血量
    float mp;                //人物魔量
    int money;                //人物金钱
    int atk;                //人物攻击力
    char name[20];        //人物昵称
    float x;                //人物x坐标
    float y;                //人物y坐标
};

结构体的初始化

初始化

使用自定义的结构体,然后初始化

#include "stdafx.h"
#include <string.h>
struct Player{
    float hp;                //人物血量
    float mp;                //人物魔力值
    int money;                //人物金钱
    int atk;                //人物攻击力
    char name[10];        //人物昵称
    float x;                //人物x坐标
    float y;                //人物y坐标
};
int main(int argc, char* argv[])
{
        Player player;

        player.hp=100;
        player.mp=50;
        player.money=1000;
        player.atk=10;        
        strcpy(player.name,"lyl610abc");
        player.x=600;
        player.y=100;
        return 0;
}

对应汇编代码

21:       Player player;
22:
23:       player.hp=100;
00401028   mov         dword ptr [ebp-24h],42C80000h
24:       player.mp=50;
0040102F   mov         dword ptr [ebp-20h],42480000h
25:       player.money=1000;
00401036   mov         dword ptr [ebp-1Ch],3E8h
26:       player.atk=10;
0040103D   mov         dword ptr [ebp-18h],0Ah
27:       strcpy(player.name,"lyl610abc");
00401044   push        offset string "lyl610abc" (0042601c)
00401049   lea         eax,[ebp-14h]
0040104C   push        eax
0040104D   call        strcpy (00401090)
00401052   add         esp,8
28:       player.x=600;
00401055   mov         dword ptr [ebp-8],44160000h
29:       player.y=100;
0040105C   mov         dword ptr [ebp-4],42C80000h

在这里插入图片描述不难看出结构体的成员的存储和数组并无差别,依旧是从低地址开始连续存储

其中要注意到,name成员实际占用空间为12字节,比声明的char name[10],多了2字节,为内存对齐的结果

结构体作为参数传递

参数传递

#include "stdafx.h"
#include <string.h>
struct Player{
    float hp;                //人物血量
    float mp;                //人物魔量
    int money;                //人物金钱
    int atk;                //人物攻击力
    char name[10];        //人物昵称
    float x;                //人物x坐标
    float y;                //人物y坐标
};

void getStruct(struct Player player){

}

int main(int argc, char* argv[])
{
        Player player;

        getStruct(player);

        return 0;
}

对应汇编代码

28:       getStruct(player);
004106D8   sub         esp,24h
004106DB   mov         ecx,9
004106E0   lea         esi,[ebp-24h]
004106E3   mov         edi,esp
004106E5   rep movs    dword ptr [edi],dword ptr [esi]
004106E7   call        @ILT+5(getStruct) (0040100a)
004106EC   add         esp,24h

分析流程

1.提升堆栈24h(为结构体的大小)

004106D8   sub         esp,24h

2.将9赋值给ecx(作为计数器使用,也就是要循环9次)

004106DB   mov         ecx,9

3.将结构体的首地址传址给esi

004106E0   lea         esi,[ebp-24h]

4.将esp赋值给edi,也就是将栈顶地址赋给edi

004106E3   mov         edi,esp

5.重复9次(重复直到ecx为0),将esi里的值赋值给edi里的值,每次ecx都会自减1,esi和edi自增4(增或减取决于DF标志位)

为什么是循环9次?

前面提升的堆栈为24h,对应十进制为36,这里每次循环都会让esi和edi自增4,36/4=9,所以要循环9次

004106E5   rep movs    dword ptr [edi],dword ptr [esi]

结合前面的esi=结构体首地址,edi为栈顶,这行代码就是将结构体复制到堆栈中

6.调用以结构体为参数的函数

004106E7   call        @ILT+5(getStruct) (0040100a)

7.函数调用结束后进行堆栈外平衡,将之前提升的堆栈恢复

004106EC   add         esp,24h

通过前面的分析可以知道,将结构体作为参数来传递是将整个结构体赋值到堆栈中来进行传递的

结构体作为返回值传递

返回值传递

#include "stdafx.h"
#include <string.h>
struct Player{
    float hp;                //人物血量
    float mp;                //人物魔量
    int money;                //人物金钱
    int atk;                //人物攻击力
    char name[10];        //人物昵称
    float x;                //人物x坐标
    float y;                //人物y坐标
};

Player retStruct(){

        Player player;
        return player;
}

int main(int argc, char* argv[])
{

        Player player;
        player=retStruct();

        return 0;
}

对应汇编代码

函数外部

30:       Player player;
31:       player=retStruct();
0040107E   lea         eax,[ebp-6Ch]
00401081   push        eax
00401082   call        @ILT+0(retStruct) (00401005)
00401087   add         esp,4
0040108A   mov         esi,eax
0040108C   mov         ecx,9
00401091   lea         edi,[ebp-48h]
00401094   rep movs    dword ptr [edi],dword ptr [esi]
00401096   mov         ecx,9
0040109B   lea         esi,[ebp-48h]
0040109E   lea         edi,[ebp-24h]
004010A1   rep movs    dword ptr [edi],dword ptr [esi]

可以看到,函数明明是个无参的函数,但是却在函数前push了eax,并且eax是ebp-6C的地址

为什么明明是无参函数,却仍然push了 eax?

这里的eax是作为返回值来使用的,要将整个结构体作为返回值来传递,只用一个eax肯定是不够存储的,数据应该存在堆栈中,而这里就是用eax来保存 要存储返回结构体的堆栈地址的

函数内部

前面所说可能有些抽象,来结合函数里面返回的内容分析:

19:       Player player;
20:       return player;
00401038   mov         ecx,9
0040103D   lea         esi,[ebp-24h]
00401040   mov         edi,dword ptr [ebp+8]
00401043   rep movs    dword ptr [edi],dword ptr [esi]
00401045   mov         eax,dword ptr [ebp+8]
21:   }
00401048   pop         edi
00401049   pop         esi
0040104A   pop         ebx
0040104B   mov         esp,ebp
0040104D   pop         ebp
0040104E   ret

先看前面几行代码

00401038   mov         ecx,9
0040103D   lea         esi,[ebp-24h]
00401040   mov         edi,dword ptr [ebp+8]
00401043   rep movs    dword ptr [edi],dword ptr [esi]

发现和前面将结构体作为参数传递的代码差不多,就是将结构体的数据复制到堆栈中,此时复制的堆栈的起始地址为ebp+8

再看关键语句

00401045   mov         eax,dword ptr [ebp+8]

这里就是将ebp+8也就是前面复制的堆栈的起始位置 赋值给 eax,eax作为返回值来传递数据

剩下的内容就是恢复现场和返回,这里就不再过多赘述

返回后

00401087   add         esp,4
0040108A   mov         esi,eax
0040108C   mov         ecx,9
00401091   lea         edi,[ebp-48h]
00401094   rep movs    dword ptr [edi],dword ptr [esi]
00401096   mov         ecx,9
0040109B   lea         esi,[ebp-48h]
0040109E   lea         edi,[ebp-24h]
004010A1   rep movs    dword ptr [edi],dword ptr [esi]

返回后首先进行堆栈外平衡,因为先前push了一个eax

00401087   add         esp,4

然后就是熟悉的操作

0040108A   mov         esi,eax
0040108C   mov         ecx,9
00401091   lea         edi,[ebp-48h]
00401094   rep movs    dword ptr [edi],dword ptr [esi]

00401096   mov         ecx,9
0040109B   lea         esi,[ebp-48h]
0040109E   lea         edi,[ebp-24h]
004010A1   rep movs    dword ptr [edi],dword ptr [esi]

先将eax这个返回值赋值给esi

然后就是把返回值复制到现在的堆栈中

再接着就是把堆栈中的数据复制给临时变量player,对应player=retStruct();

将结构体作为返回值,会将返回值eax压入堆栈中,说明了push 的内容不一定是参数,也可以是返回值

内存对齐

内存对齐也称作字节对齐

前面或多或少都有提到过内存对齐,但没有具体展开,现在来谈谈内存对齐

为什么要内存对齐

性能原因

寻址时提高效率,采用了以空间换时间的思想

当寻址的内存的单位和本机宽度一致时,寻址的效率最高

举个例子:

  • 在32位的机器上,一次读32位(4字节)的内存 效率最高
  • 在64位的机器上,一次读64位(8字节)的内存 效率最高

平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

内存对齐例子

前面其实已经有过不少内存对齐的例子,就比如上面的

char name[10];

实际占用的空间为12,12=4 × 3 ,这里的4就是本机宽度,单位为字节,实际占用的空间为本机宽度的整数倍

再看看结构体的内存对齐:

#include "stdafx.h"
#include <string.h>
struct S1{
        char a;
        int b;
        char c;
};

struct S2{
        int a;
        char b;
        char c;
};

int main(int argc, char* argv[])
{
        S1 s1;
        S2 s2;
        printf("%d\n",sizeof(s1));
        printf("%d\n",sizeof(s2));

        return 0;
}

运行结果

在这里插入图片描述
分析

可与看到,明明结构体里的参数类型是一样的,都是两个char和一个int,但其占用的空间却不一样,这就是内存对齐的结果

此时的对齐参数为默认值8

在这里插入图片描述

对齐参数

上面有提到对齐参数,什么是对齐参数?

对齐参数:n为字节对齐数,其取值为1、2、4、8,(默认值取决于编译器)VC++6.0中n 默认是8个字节

对齐数=编译器默认的一个对齐数与该成员大小的较小值

再看看下面的例子,对比上面对齐参数默认为8时的结果

修改对齐参数

可以通过

#pragma pack(n)//设置对齐参数
#pragma pack())//取消设置的默认对齐参数,还原为默认

来指定对齐参数

为上面的例子指定对齐参数:

#pragma pack(1)
struct S1{
        char a;
        int b;
        char c;
};
#pragma  pack()

#pragma pack(1)
struct S2{
        int a;
        char b;
        char c;
};
#pragma  pack()

对齐参数为1

在这里插入图片描述
可以看到,分配的空间都变成了6字节,2个char 各占用1字节,int占用4字节
结构体

在这里插入图片描述

对齐参数为2

将对齐参数改为2,结果如下:

在这里插入图片描述

此时空间占用情况如下:

在这里插入图片描述

内存对齐的规则

  1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset(偏移)为0的地方,以后每个数据成员的对齐按照#pragma
    pack指定的数值(或默认值)和)数据成员类型长度中,比较小的那个进行,也就是min{对齐参数,sizeof(数据成员)},在上一个对齐后的地方开始寻找能被当前对齐数值整除的地址
  2. 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐.主要体现在,最后一个元素对齐后,后面是否填补空字节,如果填补,填补多少.对齐将按照#pragma
    pack指定的数值(或默认值)和结构(或联合)最大数据成员类型长度中,比较小的那个进行,也就是min{对齐参数,最大数据成员类型长度}
  3. 结合1、2推断:当#pragma pack的n值等于或超过所有数据成员类型长度的时候,这个n值的大小将不产生任何效果

用规则分析结果

只看规则描述可能还是有些抽象,拿上面对齐参数为2的例子进行分析

先上完整的代码:

#include "stdafx.h"
#include <string.h>

#pragma pack(2)
struct S1{
        char a;
        int b;
        char c;
};
#pragma  pack()

#pragma pack(2)
struct S2{
        int a;
        char b;
        char c;
};
#pragma  pack()

S1 s1;
S2 s2;
int main(int argc, char* argv[])
{
        s1.a=1;
        s1.b=2;
        s1.c=3;

        s2.a=4;
        s2.b=5;
        s2.c=6;

        printf("%d\n",sizeof(s1));
        printf("%d\n",sizeof(s2));

        return 0;
}

这里将s1和s2声明为全局变量,方便查看偏移

分析结果

29:       s1.a=1;
0040D7C8   mov         byte ptr [s1 (00427e48)],1
30:       s1.b=2;
0040D7CF   mov         dword ptr [s1+2 (00427e4a)],2
31:       s1.c=3;
0040D7D9   mov         byte ptr [s1+6 (00427e4e)],3
32:
33:       s2.a=4;
0040D7E0   mov         dword ptr [s2 (00427e40)],4
34:       s2.b=5;
0040D7EA   mov         byte ptr [s2+4 (00427e44)],5
35:       s2.c=6;
0040D7F1   mov         byte ptr [s2+5 (00427e45)],6

通过反汇编得到各个成员所占用的内存空间(之前的数据也是这样得来的)

在这里插入图片描述
数据成员的对齐

对于s1:

  1. 首先存储的是char类型的a,分配在00427e48这个地址(这个地址也是对齐参数的整数倍)

  2. 接下来要在a后面分配的是int类型的b,由于int为4字节,取min{sizeof(b),对齐参数}=min{4,2}=2;于是从上一个地址00427e48+1(a占用了1个字节)向后找能够被2整除的地址来存储b,也就是对应的s1+2
    (00427e4a)

  3. 最后要在b后面分配的是char类型的c,取min{sizeof©,对齐参数}=min{1,2}=1;于是从上一个地址00427e4a+4(b占用了4个字节)开始向后找能够被1整除的地址来存储c,也就是对应s1+6 (00427e4e)

对于s2:

  1. 首先存储的是int类型的a,分配在00427e40这个地址(这个地址也是对齐参数的整数倍)

  2. 接下来要在a后面分配的是char类型的b,取min{sizeof(b),对齐参数}=min{1,2}=1;于是从上一个地址00427e40+4(a占用了4个字节)向后找到能够被1整除的地址来存储b,也就是对应的s2+4 (00427e44)

  3. 最后要b后面分配的是char类型的c,取min{sizeof©,对齐参数}=min{1,2}=1;于是从上一个地址00427e44+1(b占用了1个字节)向后找到能够被1整除的地址来存储c,也就是对应的s2+5 (00427e45)

结构体整体的对齐

无论是对于结构体s1还是结构体s1,对应的min{对齐参数,最大数据成员类型长度}=min{2,4}=2

对于s1:

前面数据成员对齐后的总长度为7,因为:s1+6 (00427e4e)+1(加上c所占用的空间)=s1+7

7并不是2的整数倍,于是要在后面补空字节,补1个空字节,使得总长度为8

对于s2:

前面数据成员对齐后的总长度为6,因为:s2+5 (00427e45)+1(加上c所占用的空间)=s2+6

6正好是2的整数倍,于是无需在后面补空字节,总长度为6

无论是将结构体作为参数传递还是作为返回值传递,期间都有大量的内存复制操作,显然实际使用中并适合采用如此耗费性能的操作,一般是使用指针来进行传递的

对于结构体的对齐,不仅仅要考虑结构体成员的对齐,还要考虑结构体整体的对齐

结构体里面使用的static变量在用sizeof进行大小计算时是不会将其算进去的,因为静态变量存放在静态数据区,和结构体的存储位置不同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/438815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MongoDB 聚合管道中使用数组表达式运算符判断数组中是否包含元素($in)并获取元素索引($indexOfArray)

数组表达式运算符主要用于文档中数组的操作&#xff0c;之前我们介绍了如果获取文档数组中的元素&#xff1a; MongoDB 聚合管道中使用数组表达式运算符获取数组中指定位置的元素($arrayElemAt&#xff0c;$first&#xff0c;$last)https://blog.csdn.net/m1729339749/article…

k8s 1.20 二进制安装

安装前的准备 CentOS Linux release 7.3.1611 (Core) 初始化系统参数 集群规划 192.168.6.20 master1 - etcd、kube-apiserver、kube-controller-manager、kube-scheduler 192.168.6.21 master2 - etcd、kube-apiserver、kube-controller-manager、kube-scheduler 192.168.6…

手牵手SpringBoot2集成Redis7

Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 Redis是一个NoSQL数据库&#xff0c;常用缓存(cache) Re…

注册苹果开发者账号的方法

在2020年以前&#xff0c;注册苹果开发者账号后&#xff0c;就可以生成证书。 但2020年后&#xff0c;因为注册苹果开发者账号需要使用Apple Developer app注册开发者账号&#xff0c;所以需要缴费才能创建ios证书了。 所以新政策出来后&#xff0c;注册苹果开发者账号&#…

2023年2月安全更新补丁

一、2023年2月安全更新补丁 近日&#xff0c;微软发布了2023年2月安全更新补丁&#xff0c;共发布了75个漏洞的补丁程序&#xff0c;其中18个漏洞值得关注&#xff08;包括个7严重漏洞、11个重要漏洞&#xff09; Microsoft Windows是美国微软公司以图形用户界面为基础研发的…

SSM学习记录7:通过cdn引入vue进行使用

通过cdn引入vue进行使用 引入vue 和 vue-router&#xff0c;注&#xff1a;vue的版本要比router版本低一个版本&#xff0c;例vue2配router3 <!-- import Vue before Element --><script src"https://cdn.jsdelivr.net/npm/vue2.7.14"></script>&…

CloudEon云原生大数据平台

文章目录 [toc] 1. CloudEon是什么&#xff1f;2. gitHub地址和官网地址3. 官网教程4. 特性5. 架构6. 支持组件版本7. 安装部署7.1 部署前提7.1.1 Kubernetes环境准备&#xff08;必须&#xff09;7.1.2 SSH服务准备&#xff08;必须&#xff09;7.1.3 数据库环境准备&#xff…

【学习笔记】websocket数据帧

介绍 RFC官网给出的数据帧格式 字段解释 FIN&#xff08;1bit&#xff09;: 标记位&#xff0c;表示该数据帧是否为完整消息最后的数据帧。RSV1/2/3&#xff08;各1bit&#xff09;: 标记位&#xff0c;根据RFC的介绍&#xff0c;这三个bit位是用做扩展用途&#xff0c;没有…

rslidar_SDK二次开发

rslidar_SDK代码二开注意事项 文章目录 rslidar_SDK代码二开注意事项0.下载地址1.CMakeLists.txt1.1 Cmake里面&#xff0c;首先选择编译方式&#xff0c;原始ORIGINAL还是CATKIN&#xff0c;还是COLCON1.2 增加新增cpp文件位置1.3 如果选择ORIGINAL模式&#xff0c;需要额外增…

LabView中数组的使用2-1

在LabView中&#xff0c;数组用来管理相同类型的数据。 1 在前面板中创建数组 1.1 创建空数组 在前面板中创建数组时&#xff0c;首先在前面板中点击鼠标右键&#xff0c;弹出“控件”对话框&#xff0c;之后选择“新式->数组、矩阵与簇->数组”&#xff0c;在前面板中…

硬盘格式化工具,强烈推荐这个!

案例&#xff1a;硬盘格式化工具推荐 【我的电脑已经用了好几年了&#xff0c;硬盘存储容量严重不够了&#xff0c;最近想把它格式化&#xff0c;但却不知道怎么操作&#xff0c;大家有什么比较好的硬盘格式化工具可以推荐吗&#xff1f;】 硬盘作为存储设备&#xff0c;我们…

薄膜热电化学电池性能测试中的半导体制冷片高精度度温度控制解决方案

摘要&#xff1a;电化学热电池&#xff08;electrochemical thermcells&#xff09;作为用于低品质热源的热电转换技术&#xff0c;是目前可穿戴电子产品的研究热点之一&#xff0c;使用中要求具有一定的温差环境。电化学热电池相应的性能测试就对温度和温差形成提出很高要求&a…

11、HOOK原理上

一、HOOK 1.1 HOOK简介 HOOK,中文译为“挂钩”或“钩子”.在iOS逆向中是指改变程序运行流程的一种技术.通过hook可以让别人的程序执行自己所写的代码. 在逆向中经常使用这种技术重点要了解其原理,这样能够对恶意代码进行有效的防护. 1.2 Hook的应用场景 描述一个HOOK实用技…

由表及里的解读数据仓库

数据仓库作为商业智能BI系统中的一部分&#xff0c;已经成长为了企业信息化建设中必不可少的重要支撑&#xff0c;在可见的未来&#xff0c;数据仓库还会随着信息化、数字化技术、理念、应用的落地&#xff0c;继续成长。 数据仓库是一个面向主题的、集成的、随时间变化但信息…

jd侧边栏以及模态框样式设置

点击图像出现模态框&#xff0c;点击按钮叉叉模态框消失 html css样式&#xff1a; <style> * { padding: 0; margin: 0; } html, body { height: 100%; width: 100%; } li { list-style: none; } .box { position: fixed; bottom: 120px; right: -10px; width: 200px; }…

看完这篇 HTTPS,和面试官扯皮就没问题了

看完这篇 HTTPS&#xff0c;和面试官扯皮就没问题了 下面我们来一起学习一下 HTTPS &#xff0c;首先问你一个问题&#xff0c;为什么有了 HTTP 之后&#xff0c;还需要有 HTTPS &#xff1f;我突然有个想法&#xff0c;为什么我们面试的时候需要回答标准答案呢&#xff1f;为什…

零成本教你部署一个ChatGPT网站

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

BPMN绘制流程的项目推荐

项目简介 Vite Vue Bpmn流程编辑器&#xff0c;基于Bpmn.js&#xff0c; Vite&#xff0c; Vue.js 3.x, Naiveui 实现了 Bpmn.js 和 Diagram.js 的 typescript 类型声明&#xff0c;typescript 可以用来在编辑器中编写代码。 整合项目地址&#xff1a; https://github.com/m…

【JavaScript】4.JavaScript对象

JavaScript 对象 1. 对象 在 JavaScript 中&#xff0c;对象是一组无序的相关属性和方法的集合&#xff0c;所有的事物都是对象&#xff0c;例如字符串、数值、数组、函数等 对象是由属性和方法组成的。 属性&#xff1a;事物的特征&#xff0c;在对象中用属性来表示&#x…

浅谈兼容性测试

兼容性测试的概念 兼容性测试是一种软件测试&#xff0c;用于确保构建的系统/应用程序/网站与其他各种对象&#xff08;如其他网络浏览器、硬件平台、用户、操作系统等&#xff09;的兼容性。这种类型的测试有助于了解产品在特定环境中的表现。 为了方便理解&#xff0c;可以…