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

news2024/11/17 6:01:13

汇编一维数组

之前的文章学习过了四种基本类型:char short int long的汇编表示形式

因为它们的数据宽度都小于等于32位,所以都可以只用一个通用寄存器来存储

接下来的数组显然就无法只用一个通用寄存器就可以存储了

在学习数组之前,再学习一个数据类型:long long(__int64),因为它也无法只用一个通用寄存器来存储

long long(__int64)

其数据宽度为64位,所以也无法只用一个通用寄存器来存储,通过汇编来看看其存储形式

老规矩,先上代码:

#include "stdafx.h"
unsigned __int64 function(){
        unsigned __int64 i=0x1234567812345678;
        return i;
}
int main(int argc, char* argv[])
{        
        unsigned __int64 i=function();
        printf( "%I64x\n", i );
        return 0;
}

简单解释一下:

写了一个函数,函数直接返回一个unsigned __int64,无符号64位的数据

接收函数返回的数据以后输出

为什么要特地写一个函数来返回数据?

之前的返回数据默认都是保存在eax中的,此时64位的数据一个eax肯定无法存储下,观察此时的数据存储

运行结果:

在这里插入图片描述

可以看到,能够正常地输出长度为64位的十六进制数

用汇编看看64位的数据是如何存储的

函数外部

16:       unsigned __int64 i=function();
0040D708   call        @ILT+5(function) (0040100a)
0040D70D   mov         dword ptr [ebp-8],eax
0040D710   mov         dword ptr [ebp-4],edx
17:       printf( "%I64x\n", i );

函数内部

7
:    unsigned __int64 function(){
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,48h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-48h]
0040101C   mov         ecx,12h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
8:        unsigned __int64 i=0x1234567812345678;
00401028   mov         dword ptr [ebp-8],12345678h
0040102F   mov         dword ptr [ebp-4],12345678h
9:        return i;
00401036   mov         eax,dword ptr [ebp-8]
00401039   mov         edx,dword ptr [ebp-4]
10:   }
0040103C   pop         edi
0040103D   pop         esi
0040103E   pop         ebx
0040103F   mov         esp,ebp
00401041   pop         ebp
00401042   ret

分析

16:       unsigned __int64 i=function();
0040D708   call        @ILT+5(function) (0040100a)

直接调用函数

函数内部省略保护现场等代码,截取出核心代码:

8:        unsigned __int64 i=0x1234567812345678;
00401028   mov         dword ptr [ebp-8],12345678h
0040102F   mov         dword ptr [ebp-4],12345678h
9:        return i;
00401036   mov         eax,dword ptr [ebp-8]
00401039   mov         edx,dword ptr [ebp-4]
10:   }

可以发现,i 这个变量存储在了ebp-8 和 ebp-4 中

在这里插入图片描述
也就是从ebp-8开始连续存储了64位,这里对应的内存地址为12FF1C~12FF24

接着看返回值部分

不难发现返回值分别放在eax和edx两个寄存器中,而不再是原本的eax寄存器

0040D70D   mov         dword ptr [ebp-8],eax
0040D710   mov         dword ptr [ebp-4],edx
17:       printf( "%I64x\n", i );

返回后可以看到,就是通过eax和edx来作为参数传递的,验证完毕

数组

数组的空间占用

那么如果是在数组中,char是否还会转变成int呢?

首先查看一个空函数默认分配的空间:

#include "stdafx.h"
void function(){

}
int main(int argc, char* argv[])
{
        function();
        return 0;
}
13:   {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,40h
00401056   push        ebx
00401057   push        esi
00401058   push        edi
00401059   lea         edi,[ebp-40h]
0040105C   mov         ecx,10h
00401061   mov         eax,0CCCCCCCCh
00401066   rep stos    dword ptr [edi]
14:       function();
00401068   call        @ILT+5(function) (0040100a)
15:       return 0;
0040106D   xor         eax,eax
16:   }
0040106F   pop         edi
00401070   pop         esi
00401071   pop         ebx
00401072   add         esp,40h
00401075   cmp         ebp,esp
00401077   call        __chkesp (00401090)
0040107C   mov         esp,ebp
0040107E   pop         ebp
0040107F   ret

注意看第三行为:sub esp,40h

这里默认提升的堆栈空间为40h,暂且记下

接下来,查看char数组分配的空间

void function(){
        char arr[4]={0};
}
00401023   sub         esp,44h

可以计算一下:44-40=4,也就是为arr数组分配了4个字节,每个char对应1个字节,并没有按4个字节来占用空间

那么是否在数组中,就是单独为每个char分配一个字节呢?

换个问法:char arr[3]={1,2,3}与char arr[4]={1,2,3,4}哪个更节省空间?

将上面的arr[4]改为arr[3],再观察对应反汇编

void function(){
        char arr[3]={0};
}
00401023   sub         esp,44h

可以发现,并不是期望中的43h,依旧是44h,实际上不论是数组还是非数组,存储数据时都要考虑内存对齐,在32位的系统中,以4个字节(32位)(本机宽度)为单位,因为在数据宽度和本机宽度一致时,运行效率最高,这也是为什么先前的char会占用4个字节的原因

问题的答案也浮出水面:arr[3]和arr[4]所占用的内存空间是一样的

数组的存储

前面了解了long long(__int64)的存储,再来看看数组是如何存储的,将数组作为返回值传递涉及指针,暂时先略过

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

查看其反汇编

8:        int arr[5]={1,2,3,4,5};
0040D498   mov         dword ptr [ebp-14h],1
0040D49F   mov         dword ptr [ebp-10h],2
0040D4A6   mov         dword ptr [ebp-0Ch],3
0040D4AD   mov         dword ptr [ebp-8],4
0040D4B4   mov         dword ptr [ebp-4],5
9:    }

可以看到存储的方式和前面的__int64相似,从某个地址开始连续存储

在这里插入图片描述

这里就是从ebp-14开始一直存储到ebp,对应内存地址为12FF18~12FF2C

数组的寻址

数组的存储并不复杂,接下来看看如何来找到数组的某个成员

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

查看反汇编代码:

8:        int x=1;
0040D498   mov         dword ptr [ebp-4],1
9:        int y=2;
0040D49F   mov         dword ptr [ebp-8],2
10:       int r=0;
0040D4A6   mov         dword ptr [ebp-0Ch],0
11:       int arr[5]={1,2,3,4,5};
0040D4AD   mov         dword ptr [ebp-20h],1
0040D4B4   mov         dword ptr [ebp-1Ch],2
0040D4BB   mov         dword ptr [ebp-18h],3
0040D4C2   mov         dword ptr [ebp-14h],4
0040D4C9   mov         dword ptr [ebp-10h],5
12:       r=arr[1];
0040D4D0   mov         eax,dword ptr [ebp-1Ch]
0040D4D3   mov         dword ptr [ebp-0Ch],eax
13:       r=arr[x];
0040D4D6   mov         ecx,dword ptr [ebp-4]
0040D4D9   mov         edx,dword ptr [ebp+ecx*4-20h]
0040D4DD   mov         dword ptr [ebp-0Ch],edx
14:       r=arr[x+y];
0040D4E0   mov         eax,dword ptr [ebp-4]
0040D4E3   add         eax,dword ptr [ebp-8]
0040D4E6   mov         ecx,dword ptr [ebp+eax*4-20h]
0040D4EA   mov         dword ptr [ebp-0Ch],ecx
15:       r=arr[x*2+y];
0040D4ED   mov         edx,dword ptr [ebp-4]
0040D4F0   mov         eax,dword ptr [ebp-8]
0040D4F3   lea         ecx,[eax+edx*2]
0040D4F6   mov         edx,dword ptr [ebp+ecx*4-20h]
0040D4FA   mov         dword ptr [ebp-0Ch],edx
16:
17:   }

变量

在这里插入图片描述
按顺序依次分析四种寻址方式

r=arr[1]

12:       r=arr[1];
0040D4D0   mov         eax,dword ptr [ebp-1Ch]
0040D4D3   mov         dword ptr [ebp-0Ch],eax

当给定了指定的数组下标时,编译器能够直接通过下标定位到数组成员的位置,获取到数据

r=arr[x]

13:       r=arr[x];
0040D4D6   mov         ecx,dword ptr [ebp-4]
0040D4D9   mov         edx,dword ptr [ebp+ecx*4-20h]
0040D4DD   mov         dword ptr [ebp-0Ch],edx

当给定的数组下标为变量时:

  1. 先将变量x赋值给ecx
  2. 然后通过ebp+ecx4-20h定位到对应的数组成员,并赋值给edx
    这里的4对应数组类型int的数据宽度,如果是short类型则为
    2
    先省去ecx不看,则为ebp-20h对应的是第一个数组成员
    然后+ecx*4,就是加上偏移得到对应的数组成员
  3. 最后再把edx赋值给r

r=arr[x+y]

14:       r=arr[x+y];
0040D4E0   mov         eax,dword ptr [ebp-4]
0040D4E3   add         eax,dword ptr [ebp-8]
0040D4E6   mov         ecx,dword ptr [ebp+eax*4-20h]
0040D4EA   mov         dword ptr [ebp-0Ch],ecx

当给定的数组下标为变量的加法算式时:

先计算出算式的结果

0040D4E0   mov         eax,dword ptr [ebp-4]
0040D4E3   add         eax,dword ptr [ebp-8]

然后和上面一样,通过ebp+eax*4-20h定位数组成员

0040D4E6   mov         ecx,dword ptr [ebp+eax*4-20h]

最后再把ecx赋值给r

0040D4EA   mov         dword ptr [ebp-0Ch],ecx

r=arr[x*2+y]

15:       r=arr[x*2+y];
0040D4ED   mov         edx,dword ptr [ebp-4]
0040D4F0   mov         eax,dword ptr [ebp-8]
0040D4F3   lea         ecx,[eax+edx*2]
0040D4F6   mov         edx,dword ptr [ebp+ecx*4-20h]
0040D4FA   mov         dword ptr [ebp-0Ch],edx

当给定的数组下标为变量的较复杂算式时:

依旧是先计算出算式的结果

0040D4ED   mov         edx,dword ptr [ebp-4]
0040D4F0   mov         eax,dword ptr [ebp-8]
0040D4F3   lea         ecx,[eax+edx*2]

然后和上面一样,通过ebp+ecx*4-20h定位数组成员

0040D4F6   mov         edx,dword ptr [ebp+ecx*4-20h]

最后再把edx赋值给r

0040D4FA   mov         dword ptr [ebp-0Ch],edx

数组越界的应用

先写一个普通的越界程序

#include "stdafx.h"
void  function(){
        int arr[5]={1,2,3,4,5};
        arr[6]=0x12345678;

}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果:

在这里插入图片描述

不出意料,程序报错了,同时可以发现,程序出错的原因是访问了不能访问的内存0x12345678,也就是我们给arr[6]赋值的内容,接下来从汇编的角度观察出错的原因:

函数外部

17:       function();
00401068   call        @ILT+5(function) (0040100a)
18:       return 0;
0040106D   xor         eax,eax
19:   }
0040106F   pop         edi
00401070   pop         esi
00401071   pop         ebx
00401072   add         esp,40h
00401075   cmp         ebp,esp
00401077   call        __chkesp (00401090)
0040107C   mov         esp,ebp
0040107E   pop         ebp
0040107F   ret

函数内部

7:    void  function(){
0040D480   push        ebp
0040D481   mov         ebp,esp
0040D483   sub         esp,54h
0040D486   push        ebx
0040D487   push        esi
0040D488   push        edi
0040D489   lea         edi,[ebp-54h]
0040D48C   mov         ecx,15h
0040D491   mov         eax,0CCCCCCCCh
0040D496   rep stos    dword ptr [edi]
8:        int arr[5]={1,2,3,4,5};
0040D498   mov         dword ptr [ebp-14h],1
0040D49F   mov         dword ptr [ebp-10h],2
0040D4A6   mov         dword ptr [ebp-0Ch],3
0040D4AD   mov         dword ptr [ebp-8],4
0040D4B4   mov         dword ptr [ebp-4],5
9:        arr[6]=0x12345678;
0040D4BB   mov         dword ptr [ebp+4],12345678h
10:
11:   }
0040D4C2   pop         edi
0040D4C3   pop         esi
0040D4C4   pop         ebx
0040D4C5   mov         esp,ebp
0040D4C7   pop         ebp
0040D4C8   ret

可以看到越界的那部分语句对应为:

9:        arr[6]=0x12345678;
0040D4BB   mov         dword ptr [ebp+4],12345678h

那么ebp+4存储的内容是什么?

在这里插入图片描述

ebp+4存储的内容为一个地址0040106D

这个地址对应为:

17:       function();
00401068   call        @ILT+5(function) (0040100a)
18:       return 0;
0040106D   xor         eax,eax
19:   }

就是call调用结束后的返回地址

分析可知,越界语句将函数的返回地址给覆盖成了0x12345678,导致无法正常返回,因此引发了错误

看到这里,发现通过数组越界可以覆盖返回地址后,便可以来搞搞事情了

通过数组越界向函数内插入其它函数

#include "stdafx.h"
int addr;
void HelloWorld(){
        printf("Hello World!\n");
        __asm{                
                mov eax,addr
                mov dword ptr [ebp+4],eax
        }
}
void  function(){
        int arr[5]={1,2,3,4,5};
        __asm{        
                mov  eax,dword ptr [ebp+4]
                mov  addr,eax                
        }
        arr[6]=(int)HelloWorld;        
}
int main(int argc, char* argv[])
{
        function();
        __asm{
                sub esp,4
        }
        return 0;
}

运行结果:

在这里插入图片描述
发现程序能够正常运行,并且输出了Hello World!

接下来解释一下代码的几处地方:

void  function(){
        int arr[5]={1,2,3,4,5};
        __asm{        
                mov  eax,dword ptr [ebp+4]
                mov  addr,eax                
        }
        arr[6]=(int)HelloWorld;        
}

首先是function函数,这个函数中,首先将ebp+4的地址保存到addr里,也就是将原本的返回地址备份

下面的arr[6]=(int)HelloWolrd则是将函数的返回地址修改为了自己写的HelloWorld函数

让代码去执行HelloWorld函数的内容

接着看HelloWorld函数

void HelloWorld(){
        printf("Hello World!\n");
        __asm{                
                mov eax,addr
                mov dword ptr [ebp+4],eax
        }
}

输出Hello Wolrd后,将先前备份的函数地址赋给ebp+4,让函数能够返回到原本的地址

最后是main函数

int main(int argc, char* argv[])
{
        function();
        __asm{
                sub esp,4
        }
        return 0;
}

main函数在调用完function函数后,要加上sub esp,4来自行平衡堆栈,因为先前的通过数组越界来调用其它函数使得堆栈不平衡,需要手动修正平衡,否则main函数里的__chkesp会报错

36:       function();
0040D4D8   call        @ILT+5(function) (0040100a)
37:       __asm{
38:           sub esp,4
0040D4DD   sub         esp,4
39:       }
40:       return 0;
0040D4E0   xor         eax,eax
41:   }
0040D4E2   pop         edi
0040D4E3   pop         esi
0040D4E4   pop         ebx
0040D4E5   add         esp,40h
0040D4E8   cmp         ebp,esp
0040D4EA   call        __chkesp (00401090)                        这里会检查堆栈是否平衡
0040D4EF   mov         esp,ebp
0040D4F1   pop         ebp
0040D4F2   ret

如不修正,会报错:

在这里插入图片描述

总结

数组的存储在内存中是连续存放的

无论是数组还是基本类型的存储都需要以内存对齐的方式来存储

数组的寻址方式大体可分为两种:

  1. 直接通过下标找到对应的数组成员

  2. 间接通过变量来找到数组成员:先找到数组的第一个成员,然后加上变量× 数据宽度得到数组成员

数组越界可以覆盖函数原本的返回地址,以此来向函数中插入其它函数,但注意要平衡堆栈

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

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

相关文章

云可见性和端口欺骗:已知的未知

与所有技术一样,新工具是建立在以前的基础上的迭代,经典的网络日志记录和指标也不例外。 网络流量的工具、检测和监控在私有云和内部部署中几乎没有变化。今天使用的许多日志和指标已有将近二十年的历史,最初是为了解决计费等问题而设计的。…

Node 08-express框架

express 介绍 express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址: https://www.expressjs.com.cn/ 简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)…

强训之【查找组成一个偶数最接近的两个素数和二进制插入】

目录 1.查找组成一个偶数最接近的两个素数1.1题目1.2思路1.2.1 暴力解法1.2.2 取中判断 1.3代码1.3.1暴力求解代码1.3.2 取中判断代码 2.二进制插入2.1题目2.2思路2.3代码 3.选择题 1.查找组成一个偶数最接近的两个素数 1.1题目 链接: link 描述 任意一个偶数(大于…

Android学习Day1

Android学习笔记 了解Android 的结构ActivityAndroid中的布局了解安卓的结构ActivityAndroid中的布局代码展示(登录界面的实现):实现效果:代码展示(注册界面的实现)实现效果一些之前学习的笔记end 了解Android 的结构 Activity …

服务(第八篇)location和rewrite

常用的Nginx正则表达式: 从功能看,rewrite和location似乎有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,还可以proxy_pass到其他机器。 rew…

企业如何挑选设备管理系统?

在当今高度竞争的市场环境中,企业需要不断提高生产效率、确保设备的可靠性和安全性以降低运营成本。设备管理系统(Equipment Management System,简称EMS)是一种帮助企业实现这些目标的有力工具。本文将为您解析企业如何挑选合适的…

提高亚马逊等其他跨境平台的曝光率方法有哪些?

很多卖家都会想尽办法提高自己店铺的曝光率,但有些新手卖家可能不知道曝光率意味着什么 简单来说,曝光率是指你自己的店铺和产品被呈现给顾客的次数,一般来说,曝光率通常决定了你店铺的总流量,交易总量,订…

PM866 3BSE050200R1高压变频器的四种控制方法

PM866 3BSE050200R1高压变频器的四种控制方法 高压变频器装置指驱动输入电源为6,000V或10KV的电机装置,高压变频器一般主要有下列几种方案选择: 一、直接高压控制(高成本) 目前以采用美国罗宾康类似的无谐波变频技术&a…

凌恩生物美文分享|3月客户文章盘点——累计IF=105,平均IF=8.08

凌恩生物以打造国内一流生物公司为目标,在科研测序领域深耕不辍,吸纳多名在生物信息高级技术人员的加盟,参与并完成多个高科技项目。现已在宏组学、基因组、表观遗传以及蛋白代谢等多组学及联合分析领域积累了深厚经验,打造出成熟…

ASP.NET Core MVC 从入门到精通之布局

随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用于初学者,在校毕业生&#xff0c…

核心业务4:标的管理

核心业务4:标的管理 1.标的管理流程图 2.数据库表设计 3.前端逻辑设计 4.后端逻辑设计 5.标的放款TODO 核心业务4:标的管理 1.标的管理流程图 ①上一个核心业务通过审核借款申请结束

mybatis-plus的代码生成器的应用

目录 1.工程引入mybatis-plus3-generator代码生成器2.只需要关注mybatis-plus-config.properties然后生成代码3.分别添加依赖解决报错4.加入其它配置然后测试效果 3.4版本 1.工程引入mybatis-plus3-generator代码生成器 mybatis-plus3-generator放入项目工程中,父工…

JeecgBoot 3.5.1 版本发布,开源的企业级低代码平台

项目介绍 JeecgBoot是一款企业级的低代码平台!前后端分离架构 SpringBoot2.x,SpringCloud,Ant Design&Vue3,Mybatis-plus,Shiro,JWT 支持微服务。强大的代码生成器让前后端代码一键生成! JeecgBoot引领…

kafka manager服务部署

1.配置一台centos7主机或者直接在kafka服务主机上部署也可以,关闭firewalld和selinux服务 2.安装java环境(需要jdk11以上) 参考:jdk1.8环境配置_Apex Predator的博客-CSDN博客 3.从github上下载编译好的cmak压缩包(下载最新版本的3.0.0.6) 下载地址&a…

Gradio 部署工具保姆级教程来了,以图生图,画你所想

2023 年以来国内各家大厂竞相发布大模型,AIGC 的热度来到了前所未有的高度,AI 绘画在国际艺术博览会上频频夺冠,数字艺术的新纪元正在逐渐展开。你是否也想与顶尖技术人员一起,参与到 AIGC 的浪潮中呢? 2023 PaddlePa…

source insight4.0使用技巧总结

一、技巧1:查看函数调用关系 步骤 1:在主菜单中点击下图中的按钮 图 1 打开relation界面 步骤 2:在弹出的relation界面点击“设置”按钮, 图2 点击“设置”按钮 步骤3: 在“设置”界面中,“Levels”选择…

一文详解一致性协议

目录 一致性协议 2PC二阶段提交 二阶段提交存在的问题: 3PC 三阶段提交 优点 Paxos算法 流程演变 Paxos优缺点 活锁问题 ZAB协议(Fast Paxos) 一致性协议 事务需要跨多个分布式节点时,为了保证事务的ACID特性,需要选举出一个协调者…

LeetCode 1105. Filling Bookcase Shelves【记忆化搜索,动态规划】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

甘特图控件DHTMLX Gantt入门使用教程【引入】:dhtmlxGantt与ASP.NET Core(下)

DHTMLX Gantt 是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求,具备完善的甘特图图表库,功能强大,价格便宜,提供丰富而灵活的JavaScript API接口,与各种服务器端技术&a…

从Pandas快速切换到Polars :数据的ETL和查询

对于我们日常的数据清理、预处理和分析方面的大多数任务,Pandas已经绰绰有余。但是当数据量变得非常大时,它的性能开始下降。 我们以前的两篇文章来测试Pandas 1.5.3、polar和Pandas 2.0.0之间的性能了,Polars 正好可以解决大数据量是处理的…