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

news2024/12/23 16:35:21

指针四

指针数组

什么是指针数组

首先回顾一下先前关于数组的知识:

所谓数组就是用于存储相同数据类型的集合

再结合先前关于指针的知识:指针的本质也是一种数据类型

于是当数组中存储的成员的数据类型为指针时,该数组就可以称为指针数组(本质是数组)

代码

#include "stdafx.h"
void function(){
        int** arr[5]={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

反汇编代码

9:        int** arr[5]={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
00401038   mov         dword ptr [ebp-14h],1
0040103F   mov         dword ptr [ebp-10h],2
00401046   mov         dword ptr [ebp-0Ch],3
0040104D   mov         dword ptr [ebp-8],4
00401054   mov         dword ptr [ebp-4],5

小总结

  • 可以看到指针数组其实并没有什么特别之处,只不过存储的数组成员的数据类型为指针而已
  • 指针数组的赋值也和先前对指针的赋值没有什么区别

结构体指针

什么是结构体指针

所谓结构体指针就是在结构体后加上若干个*使其称为一个指针类型

代码

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

struct S1{
        int a;
};

void function(){
        S1* s1=(S1*)0x12345678;
    printf("%x\n",s1);
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

在这里插入图片描述
结果分析

可以看到,这里关于结构体指针的使用貌似和普通的指针没有什么区别,但此时会发现这里还没有操作结构体内部的成员

所以结构体指针的实际使用也并不是这样,下面看一个错误的例子

错误代码

void function(){
        S1* s1=(S1*)0x12345678;
    int a=s1->a;
}

只是在上面代码的基础上添加了一个读取结构体成员的语句,查看运行结果

运行结果

在这里插入图片描述

运行结果不出所料出错了,开始分析错误的原因

反汇编代码

14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr [ebp-4],12345678h
15:       int a=s1->a;
0040103F   mov         eax,dword ptr [ebp-4]
00401042   mov         ecx,dword ptr [eax]
00401044   mov         dword ptr [ebp-8],ecx

反汇编分析

0.执行前s1和s1->a的状态

s1:

在这里插入图片描述
s1->a:

在这里插入图片描述
1.为结构体指针s1赋值

14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr [ebp-4],12345678h

在这里插入图片描述
此时再看看s1->a:

在这里插入图片描述
可以发现对s1的赋值操作,改变的不是s1->a的值,而是改变了s1->a的地址

其实从执行前s1和s1->a的状态就可以看出,s1存储的内容并不是直接存储结构体成员的内容,而是存储指向结构体成员的地址

所以这里对于先前对于s1的赋值操作改变的只是成员的地址,而没有改变成员的值

并且刚开始时,结构体成员并没有被分配对应的内存地址

2.访问s1->a

15:       int a=s1->a;
0040103F   mov         eax,dword ptr [ebp-4]
00401042   mov         ecx,dword ptr [eax]
00401044   mov         dword ptr [ebp-8],ecx

此时出错的原因已经显而易见了,先前对s1的赋值操作修改了s1->a的地址,使其指向了一个不可访问的地址而导致出错

正确代码

前面已经知道了出错的原因是访问了不可访问的地址导致出错,并且刚开始结构体成员没有被分配对应的内存地址

于是只要手动为结构体成员分配内存地址即可,这里将使用到malloc函数来进行分配内存地址

malloc函数

void *malloc(size_t size)

参数:size,内存块的大小,以字节为单位

返回值:返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL

相关头文件:malloc.h、alloc.h、stdlib.h

大致了解了malloc函数,现在来看代码:

#include "stdafx.h"
#include <malloc.h>                //这里使用了malloc.h
struct S1{
    int a;
        int b;
        int c;
};
void function(){
        S1* s1=(S1*) malloc(sizeof(S1));        //申请一块空间大小正好为S1大小的内存
        s1->a=610;
        s1->b=666;
        s1->c=52;
        printf("%d\n",s1->a);
        printf("%d\n",s1->b);
        printf("%d\n",s1->c);
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

在这里插入图片描述

可以看到结构体的成员能够正常地被改写和访问

反汇编代码

15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push        0Ch
0040D77A   call        malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr [ebp-4],eax
16:       s1->a=610;
0040D785   mov         eax,dword ptr [ebp-4]
0040D788   mov         dword ptr [eax],262h
17:       s1->b=666;
0040D78E   mov         ecx,dword ptr [ebp-4]
0040D791   mov         dword ptr [ecx+4],29Ah
18:       s1->c=52;
0040D798   mov         edx,dword ptr [ebp-4]
0040D79B   mov         dword ptr [edx+8],34h

反汇编分析

1.先看这个malloc函数

15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push        0Ch
0040D77A   call        malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr [ebp-4],eax
  1. 压入了参数0C,对应十进制为12,也就是S1的大小
  2. 调用malloc函数
  3. 堆栈外平衡
  4. 将返回值eax赋值给S1

看看返回值eax的内容:

在这里插入图片描述

可以看到eax就对应了结构体中的成员

eax=结构体成员首地址,里面的结构体成员连续存储

2.赋值,将610对应十六进制262赋值给[eax],对应前面的003807B8

16:       s1->a=610;
0040D785   mov         eax,dword ptr [ebp-4]
0040D788   mov         dword ptr [eax],262h

执行后:

在这里插入图片描述
3.赋值,将666对应十六进制29A赋值给[ecx+4],对应前面的003807BC

17:       s1->b=666;
0040D78E   mov         ecx,dword ptr [ebp-4]
0040D791   mov         dword ptr [ecx+4],29Ah

执行后:

在这里插入图片描述
4.赋值,将52对应十六进制34赋值给[edx+4],对应前面的003807C0

18:       s1->c=52;
0040D798   mov         edx,dword ptr [ebp-4]
0040D79B   mov         dword ptr [edx+8],34h

执行后:

在这里插入图片描述

小总结

  • 结构体指针和普通的指针实际上并没有什么不同

  • 在对结构体成员进行操作时,需要先对其进行初始化(为每个结构体成员分配内存地址)

  • 结构体指针并不直接存储结构体成员,而是存储了指向结构体成员的地址,该地址里存放着所有结构体成员

数组指针

前面学了指针数组,现在又来个数组指针,中间用结构体指针作了过渡,避免混淆

什么是数组指针

所谓数组指针,就是指向数组的指针(本质是指针)

既然是指针自然满足先前指针的一切特征:指针的赋值、指针的数据宽度、指针的加减、指针类型相减、指针之间比较

数组指针的声明

int (*px)[2];

声明如上,数组指针变量为px,类型为:int(*)[2];该数组指针指向的数组为int[2]

数组指针和指向数组的指针区别

代码

#include "stdafx.h"

void function(){
        int arr[6]={1,2,3,4,5,6};
        //声明一个数组指针,该指针指向数组为:int[2]
        int (*px)[2];        
        //给数组指针赋值,使该数组指针指向arr数组的首地址
        px=(int (*)[2]) &arr[0];
        //用一个临时变量parr2 存储数组指针
        int (*parr2)[2]=px;
    //*px为数组的首地址,也就是arr,这里就相当于int* arr2=arr;此时的arr2就是指向数组的指针
        int* arr2=*px;
    //初始化变量,准备循环
    int i;
    //循环遍历数组
        for(i=0;i<6;i++){
                printf("%x\t%d\n",arr2+i,arr2[i]);
        }
        printf("\n");        
        int a=(int) (parr2+1);
        int b=(int) (arr2+1);
        printf("%x\t%x\n",a,b);   
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

在这里插入图片描述
首先可以看到数组的正常遍历

然后分别输出了parr2+1和arr2+1的结果,注意这里的结果不同

反汇编代码

8:        int arr[6]={1,2,3,4,5,6};
00401038   mov         dword ptr [ebp-18h],1
0040103F   mov         dword ptr [ebp-14h],2
00401046   mov         dword ptr [ebp-10h],3
0040104D   mov         dword ptr [ebp-0Ch],4
00401054   mov         dword ptr [ebp-8],5
0040105B   mov         dword ptr [ebp-4],6
9:
10:       int (*px)[2];
11:
12:       px=(int (*)[2]) &arr[0];
00401062   lea         eax,[ebp-18h]
00401065   mov         dword ptr [ebp-1Ch],eax
13:
14:       int (*parr2)[2]=px;
00401068   mov         ecx,dword ptr [ebp-1Ch]
0040106B   mov         dword ptr [ebp-20h],ecx
15:       int* arr2=*px;
0040106E   mov         edx,dword ptr [ebp-1Ch]
00401071   mov         dword ptr [ebp-24h],edx
16:
17:       int i;
18:
19:       for(i=0;i<6;i++){
00401074   mov         dword ptr [ebp-28h],0
0040107B   jmp         function+66h (00401086)
0040107D   mov         eax,dword ptr [ebp-28h]
00401080   add         eax,1
00401083   mov         dword ptr [ebp-28h],eax
00401086   cmp         dword ptr [ebp-28h],6
0040108A   jge         function+8Fh (004010af)
20:           printf("%x\t%d\n",arr2+i,arr2[i]);
0040108C   mov         ecx,dword ptr [ebp-28h]
0040108F   mov         edx,dword ptr [ebp-24h]
00401092   mov         eax,dword ptr [edx+ecx*4]
00401095   push        eax
00401096   mov         ecx,dword ptr [ebp-28h]
00401099   mov         edx,dword ptr [ebp-24h]
0040109C   lea         eax,[edx+ecx*4]
0040109F   push        eax
004010A0   push        offset string "%x\t%d\n" (00422024)
004010A5   call        printf (00401160)
004010AA   add         esp,0Ch
21:       }
004010AD   jmp         function+5Dh (0040107d)
22:       printf("\n");
004010AF   push        offset string "\n" (00422020)
004010B4   call        printf (00401160)
004010B9   add         esp,4
23:
24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr [ebp-20h]
004010BF   add         ecx,8
004010C2   mov         dword ptr [ebp-2Ch],ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr [ebp-24h]
004010C8   add         edx,4
004010CB   mov         dword ptr [ebp-30h],edx
26:       printf("%x\t%x\n",a,b);
004010CE   mov         eax,dword ptr [ebp-30h]
004010D1   push        eax
004010D2   mov         ecx,dword ptr [ebp-2Ch]
004010D5   push        ecx
004010D6   push        offset string "%x\t%x\n" (00422fa4)
004010DB   call        printf (00401160)
004010E0   add         esp,0Ch

反汇编分析
1.数组的初始化

8:        int arr[6]={1,2,3,4,5,6};
00401038   mov         dword ptr [ebp-18h],1
0040103F   mov         dword ptr [ebp-14h],2
00401046   mov         dword ptr [ebp-10h],3
0040104D   mov         dword ptr [ebp-0Ch],4
00401054   mov         dword ptr [ebp-8],5
0040105B   mov         dword ptr [ebp-4],6

数组初始化后对应地址和内容为:

在这里插入图片描述
2.数组指针的赋值

12:       px=(int (*)[2]) &arr[0];
00401062   lea         eax,[ebp-18h]
00401065   mov         dword ptr [ebp-1Ch],eax

直接将arr数组的首地址也就是0012FF14传给了eax

然后再将eax赋值给数值指针px

数值指针赋值后:

在这里插入图片描述
可以看到数值指针里存储的内容为0012FF14即arr的地址

3.将数值指针px赋值给另一个数组指针parr2

14:       int (*parr2)[2]=px;
00401068   mov         ecx,dword ptr [ebp-1Ch]
0040106B   mov         dword ptr [ebp-20h],ecx

赋值后:

在这里插入图片描述
可以看到此时parr2里存储的内容=px里存储的内容=0012FF14=arr首地址

4.将数组的首地址赋值给arr2,即arr2=arr

14:       int* arr2=*px;
0040D82E   mov         edx,dword ptr [ebp-1Ch]
0040D831   mov         dword ptr [ebp-24h],edx

这里要注意到这里和前面一样都是赋值了[ebp-1Ch]

也就是明明赋值的是*px,但是赋值却和px一样

也可以得出结论px=px,那么为什么px和px是一样的**?

首先要明确无论是px还是*px 它们都是指针,一个为数组指针,而另一个则为普通指针

它们所指向的地址相同,都指向了arr的首地址0012FF14

区别px和*px的本质就在于其数据类型是两种不同的指针结构

指针在相加减时,加减的基本单位是指针去掉一个*后的数据宽度

  • px的数据类型为:int ()[2],去掉一个后变为int [2],数据宽度为int的数据宽度×数组的成员数=4*2=8
  • px的数据类型为:int,去掉一个*后变为int,数据宽度=4

赋值后:
在这里插入图片描述
5.循环遍历数组

就是普通的指针循环数组,在先前的文章中已经有详细介绍,这里不再赘述

6.第二种循环

24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr [ebp-20h]
004010BF   add         ecx,8
004010C2   mov         dword ptr [ebp-2Ch],ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr [ebp-24h]
004010C8   add         edx,4
004010CB   mov         dword ptr [ebp-30h],edx

通过前面可以得知*px=px,parr2=arr2,所以这里的[ebp-20h]=[ebp-24h]的:

在这里插入图片描述这里的不同之处就在于一个add了8,另一个add了4,和先前所分析的指针加减的单位相符合,于是产生了不同的结果

小总结

  • 在一个数组指针前加上*获得的就是指向数组的指针,如上例中的int arr2=px;
  • 数值指针和指向数组的指针中存储的内容都是数组的首地址,如上例中的px=*px=arr=0012FF14
  • 数组指针和指向数组的指针的主要区别在进行运算时的单位不同,前者为数据类型宽度×数组成员数,后者为数据类型宽度

数组指针的应用

可以利用数组指针进行加减时的单位不同来遍历数组的固定间隔的成员

下例为从数组的第二个成员开始,取出间隔为3的数组成员

代码

#include "stdafx.h"
void function(){
        int arr[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

        int (*px)[3];        
        //从数组的第二个成员开始
        px=(int (*)[3]) &arr[1];

        int i=0;
        for(i=0;i<15/3;i++){
                printf("%x\t%d\n",px+i,**(px+i));
        //注意这里取了两次*,第一次获得的是指向数组成员的指针,第二个获得的才是数组成员
        }
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

在这里插入图片描述

总结

在这里插入图片描述

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

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

相关文章

2023年的深度学习入门指南(7) - SIMD和通用GPU编程

2023年的深度学习入门指南(7) - SIMD和通用GPU编程 深度学习从一开始就跟GPU有不解之缘&#xff0c;因为算力是深度学习不可或缺的一部分。 时至今日&#xff0c;虽然多任务编程早已经深入人心&#xff0c;但是很多同学还没有接触过CPU上的SIMD指令&#xff0c;更不用说GPGPU…

成为黑客猎手:从零开始学习漏洞挖掘的完整指南

一.了解基础知识 学习计算机网络、操作系统、编程语言等相关基础知识&#xff0c;这些知识对于后续的漏洞挖掘和利用非常重要。具体建议如下&#xff1a; 学习计算机网络基础知识&#xff0c;例如 OSI 模型、TCP/IP 协议、HTTP 协议等。推荐书籍&#xff1a;《计算机网络》。…

qt 动态库/静态库的创建和使用教程(step by step)

一般大型项目中, 会将实现特定功能的函数或类, 封装成链接库, 供应用程序代码调用. 下面我将一步步教你如何在qt 中创建动态库/静态库, 并使用它. 目录 创建多子目录项目创建动态链接库编辑链接库内容创建应用工程并连接动态链接库 创建多子目录项目 首先创建一个多子目录项目…

PHP+python+nodejs+ springboot+vue 社区互助平台

项目介绍 社区互助平台的功能分为管理员和用户两个部分&#xff0c;系统的主要功能包括首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;租房信息管理&#xff0c;失物招领管理&#xff0c;宠物代遛管理&#xff0c;停车位出租管理&#xff0c;其他管理&#xff0c;趣…

哈希表(底层结构剖析--下)

文章目录 开散列哈希桶的模拟实现哈希桶的基本框架增加仿函数将数据类型转换为整型哈希桶的插入函数哈希桶的删除函数哈希桶的查找函数哈希桶的析构函数建议哈希表的大小为素数 开散列与闭散列比较哈希桶的时间复杂度及其测试开散列哈希桶的模拟实现完整代码 开散列哈希桶的模拟…

Java -- ELK之从nacos获取logback.xml配置信息

背景&#xff1a; 之前本地搭建好ELK后&#xff0c;随便起一个项目&#xff0c;可正常日志上送&#xff0c;但是后面我把elk部署到测试环境中发现&#xff0c;本地项目的日志就无法正常上送了&#xff0c;之前我是把上送地址配置到nacos上的&#xff0c;logback.xml中读取nacos…

项目管理中的必不可少的强大工具有哪些?

在项目管理中&#xff0c;我们总是想寻求一套功能强大的工具&#xff0c;来满足我们多样化的需求。但往往事与愿违&#xff0c;这样强大的工具总是费用高&#xff0c;操作复杂&#xff0c;需安装多个插件。下面&#xff0c;我就给大家推荐一款项目管理软件 ~Zoho Projects&…

Transformer 位置编码代码解析

Transformer 位置编码代码解析 Transformer 的 Multi-Head-Attention 无法判断各个编码的位置信息。因此 Attention is all you need 中加入三角函数位置编码&#xff08;sinusoidal position embedding&#xff09;&#xff0c;表达形式为&#xff1a; P E ( p o s , 2 i ) …

超强版干货投递!Milvus 的部署心得、运维秘籍都在这里了!

好奇 Milvus 读链路的演进&#xff1f;不知如何优化 Milvus&#xff1f;提到 Milvus 的业务场景只能靠想象&#xff1f;想获得其他人的部署经验&#xff1f;困惑于 Zilliz Cloud&#xff1f; 不藏了&#xff0c;摊牌了&#xff0c;对于上述的所有问题&#xff0c;你都可以在今天…

【Leetcode -206.反转链表 -876.链表的中间结点】

Leetcode Leetcode -206.反转链表Leetcode-876.链表的中间结点 Leetcode -206.反转链表 题目&#xff1a;给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1, 2, 3, 4, 5] 输出&#xff1a;[5…

移动端性能优化:GPU加速、图片优化与缓存策略

在移动端开发中&#xff0c;性能优化是一个至关重要的环节。本文将为您介绍如何通过 GPU 加速、图片优化和缓存策略来提高移动端性能。让我们开始吧&#xff01; 1. GPU 加速 在移动设备上&#xff0c;GPU 能够快速完成图形渲染任务。我们可以通过 CSS 属性来实现 GPU 加速&a…

单链表C语言实现 (不带头 带头两个版本)

链表就是许多节点在逻辑上串起来的数据存储方式 是通过结构体中的指针将后续的节点串联起来 typedef int SLTDataType;//数据类型 typedef struct SListNode//节点 {SLTDataType data;//存储的数据struct SListNode* next;//指向下一个节点地址的指针 }SLTNode;//结构体类型的…

机器学习材料性能预测与材料基因工程应用实战

一、背景: 传统的材料研发技术是通过实验合成表征对材料进行试错和验证&#xff0c;而过去的计算手段受限于算法效率&#xff0c;无法有效求解实际工业生产中面临的复杂问题。近几年随着大数据和人工智能介入&#xff0c;通过采用支持向量机、神经网络等机器学习算法训练数据集…

Vue核心 MVVM模型 数据代理

1.6.MVVM 模型 MVVM模型 M&#xff1a;模型 Model&#xff0c;data中的数据V&#xff1a;视图 View&#xff0c;模板代码VM&#xff1a;视图模型 ViewModel&#xff0c;Vue实例 观察发现 data中所有的属性&#xff0c;最后都出现在了vm身上vm身上所有的属性及Vue原型身上所有…

用友nc6 如果用户长时间没有任何操作,如何设置会话的失效时间?

1.web应用(新开的) NC中间件环境下的web profile和NC中间件没有关系&#xff0c;NC中间件只不过是个J2EE运行环境&#xff0c;是个Container&#xff0c;当你的web项目启动后&#xff0c;NC中间件创建web容器&#xff0c;其web应用的会话超时时间由你的web部署描述符&#xff…

电脑卡顿反应慢怎么办?这几招教给你!

电脑使用时间长了&#xff0c;电脑中的各种缓存文件也会就越来越多&#xff0c;这些文件的堆积会占用电脑内存从而导致电脑变得卡顿。还有在电脑中安装了许多软件&#xff0c;若这些软件都设置为开机自启动&#xff0c;这会占用大量的电脑内存&#xff0c;影响电脑的运行速度&a…

PMP项目管理备考资料都有哪些?

当今复杂多变的项目管理环境中&#xff0c;项目管理从业者在各种各样的项目环境中工作&#xff0c;一定会采用不同的项目方法。PMP认证试图覆盖业界所有有效的项目管理方法&#xff0c;PMP考试范围会覆盖预测型生命周期&#xff08;即瀑布式开发模式&#xff09;为代表的项目管…

什么是 MVVM?MVVM和 MVC 有什么区别?什么又是 MVP ?

目录标题 一、什么是MVVM&#xff1f;二、MVC是什么&#xff1f;三、MVVM和MVC的区别&#xff1f;四、什么是MVP&#xff1f; 一、什么是MVVM&#xff1f; MVVM是 Model-View-ViewModel的缩写&#xff0c;即模型-视图-视图模型。MVVM 是一种设计思想。 模型&#xff08;Model…

PerformanceTest, monitoring command

PerformanceTest, monitoring command 1、数据库 #查看最大连接数 show variables like max_connections; #例如:查看mysql连接数 show status like Threads%; 说明: threads_cached //查看线程缓存内的线程的数量 threads_connected //查看当前打开的连接的数量(打开的…

【Linux】6、在 Linux 操作系统中安装软件

目录 一、yum 命令二、安装 wget 一、yum 命令 类似 Linux 中的应用商店 &#x1f4c3;① yum 是 RPM 软件包管理器 ✏️ Red-Hat Package Manager &#x1f4c3;② yum 用于自动化安装、配置 Linux 软件&#xff08;可自动解决依赖问题&#xff09; &#x1f4c3;③ 语法&a…