C语言程序的机器表示(逆向+函数调用栈详解版)

C语言程序的机器表示

1 基本数据类型

image-20241019165821803

在Windows系统中,通常一个字等于两个字节,在32位程序和64位程序,在处理数据时,通常对8,4,2和1字节数据进行处理

x86使用的是浮点寄存器,Intel提供了8个128位的寄存器,xmm0~xmm7,每一个寄存器可以存放4个 (32位)单精度的浮点数

image-20241019170230993

大端:Motorola的PowerPC系列CPU采用的大端序

  • 字节数据的高位字节存放于内存的低地址端(内存的起始地址),低位字节存放于内存的高地址端, 这是人们正常读写数值的方法

小端: Intel的x86系列CPU使用的小端序

  • 字节数据的低位字节存放于内存的低地址端(内存的起始地址),高位字节存放于内存的高地址端

    image-20241019171427207

2 变量表现形式

存在于堆栈中的变量为动态变量,考虑到其动态的性质,不适合存储全局变量,因此,动态变量均为局部变量

存在于数据段中的变量为静态变量,其作用域可能为函数内部(局部静态变量)、文件内部(全局静态变量),以及进程内部(全局变量)

image-20241020095752095

PE(Windows下可执行程序)文件包括以下节区:

  1. textbss/BSS BSS段通常是用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配

  2. text/code :代码段通常是指用来存放程序执行代码的一块内存区域。 这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读在代码段中,也有可能包含一些只读的常量

  3. rdata :只读数据段

  4. **data:**数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域,属于静态内存分配

  5. idata 导入段,包含程序需要的所有DLL文件信息

  6. edata 导出段,包含所有提供给其他程序使用的函数和数据

  7. rsrc 资源数据段,程序需要用到的资源数据

  8. reloc 重定位段,如果加载PE文件失败,将基于此段进行重新调整

栈中的局部变量:

int main(int argc, char *argv[]){ // argc:表示命令行参数的数量,即用户在命令行中输入的参数个数(包括程序的名称)// argv[]:是一个指向字符串数组的指针数组,包含了命令行输入的所有参数,其中 argv[0] 通常是程序的名称。char str1[20]={0,};// 通过 {0,} 初始化,str1 中的所有位置都填充了 0char *p1 = str1;   // str1的第一个位置int intValue=80;printf("%d\n", argc);// 使用 printf 函数将命令行参数的个数 argc 输出到控制台。return 0; 
}
image-20241020102911042

[ebp+var_14](ebp-20)[ebp+var_1](ebp-1)之间的栈空间均被初始化。

xor eax, eax 是一个非常高效的指令,因为它只需要 一个机器周期 来执行,同时它的指令编码更短,占用的字节也少(只需要 2 个字节的指令长度)。

mov eax, 0 需要加载立即数 0,通常需要 5 个字节 的指令长度,并且执行速度也可能稍慢

现代 CPU 中,xor eax, eax 是一个完全数据无关的操作,它不会对缓存、数据依赖等有任何影响。可以提高指令流水线的效率。

mov eax, 0 需要访问立即数,并可能导致一些数据相关性

char *p1 = str1

[ebp+var_18]EBP - 24)存储的是一个指针(即地址),而 [ebp+var_14]EBP - 20)可能直接存储数组的数据。地址和数据在栈上的存储是有区别的,地址指向的是一个数组的首地址,而数据是该数组内的元素。

int intValue=80 程序的整型局部变量则存储在了[ebp+var_1C](ebp- 28)

而[ebp+arg_0]则是参数argc的值

全局变量:

image-20241020103556895 image-20241020103646305

**静态变量:**静态变量是程序执行前系统就为之静态分配存储空间的一类变量,如存储于数据段中的一个固定位置, 存在于进程的整个生命周期

全局静态变量

  • 只是全局静态变量只能在本文件内使用,全局静态变量的作用域和全局变量略有区别

  • 全局静态变量等价于编译器限制外部源码文件访问的全局变量

局部静态变量:

C语言中局部静态变量的赋值只进行一次,而且它不会随作用域的结束而消失,并且在未进入作用域之前就已经存在,局部静态变量存储在.data段

image-20241020104447136

image-20241020105415258

byte_4255D8最低位为零,and后为零,test后为0,jnz则不转移,通过or c1,1,将 byte_4255D8最低位置为1.

jnz(Jump if Not Zero)是一个条件跳转指令,它的作用是根据零标志(ZF)的状态来决定是否跳转。

  • 如果 ZF = 0:这意味着上一次的操作结果非零,在这种情况下,程序将跳转到指定的标签。
  • 如果 ZF = 1:这意味着上一次的操作结果为零,程序将继续执行下一条指令,而不进行跳转。

当然并不是所有编译器的局部静态变量赋值算法都是这样,程序因为使用的是VC6.0++debug版本,如 果选择VS2015等更高版本或者是其他编译器,赋值 的算法则不相同

3 流程控制语句

int main(int argc, char *argv[]){if(argc<2) printf("Usage: %s [arbitrary string]\n",argv[0]);return 0;
}
image-20241020142857979

可以通过ida清楚地看到if语句控制块的模样,if argc< 2,在汇编中是一句jge short loc_401041代码, 如果大于等于2,则跳出,小于,执行printf函数,满足条件红色

switch case:

image-20241020143107026

将选择的序号值给了edx,根据edx,jmp到 off_4010D2这个table中的某个地址

image-20241020143409842

while:

Int main(){int i=100;while(i--){printf("Hello %d\n",i);}return0;
}

image-20241020143951808

for:

Int main(){int i=100;for(;i;i--){printf("Hello %d\n",i);}return0;
}

do while:

image-20241020144026036

4 函数调用及程序栈

这个视频解释函数栈,但是没有解释汇编语言

https://www.bilibili.com/video/BV1wh41117uB/?spm_id_from=333.337.search-card.all.click&vd_source=d76ad0aadca055336653cd966075f064

汇编语言函数调用栈:

https://www.bilibili.com/video/BV1iS4y1z7V5/?spm_id_from=333.337.search-card.all.click&vd_source=d76ad0aadca055336653cd966075f064

栈:每一个函数拥有自己的函数栈,函数栈中保存着函数的局部变量、流控制结构等等,每次一个函数调用后函数栈会被收回,并再次分配给其他被调用的函数

首先,什么是栈帧?C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

传递给func的参数被压到栈中,最后一个参数先进栈,所以第一个参数是位于栈顶的。所以说函数是从右往左进行参数的入栈的,这和变长参数有关。此外,func中声明的局部变量以及函数执行过程中需要用到的一些临时变量也都存在栈中。

程序栈是由高地址向低地址生长,在程序中函数站的栈顶地址是比栈底地址低的

假设栈的起始地址是 0x7FFFFFFF,并且从这里向低地址分配。

  • 程序开始时,栈顶和栈底都在 0x7FFFFFFF
  • 调用第一个函数时,栈顶移动到 0x7FFFFF00(降低),并且该函数的局部变量和状态被存储在这个范围内。
  • 调用另一个函数时,栈顶继续向低地址移动到 0x7FFFFE00

ESP是栈寄存器,指示当前栈顶的位置,EBP是栈基址寄存器,这个寄存器指向栈底的位置

push和pop是栈的基本操作,call、leave、ret可以用来构建函数栈和销毁函数栈

image-20241106205347405这里写图片描述

首先被调用的函数必须建立它自己的栈帧。EBP寄存器现在正指向main的栈帧中的某个位置,这个值必须被保留,因此,EBP进栈。然后ESP的内容赋值给了EBP。这使得函数的参数可以通过对EBP附加一个偏移量得到,而栈寄存器ESP便可以空出来做其他事情。第一个参数的地址是EBP加8,因为main的EBP和返回地址各在栈中占了4个字节。

栈的生成与销毁:

当函数调用发生时,新的栈帧被压入栈中,而当函数返回时,栈帧将从栈中弹出。

  1. push参数入栈 母函数调用子函数时,在母函数栈帧再往内存低地址方向的邻接区域,创建子函数的栈帧,首先把参数压入栈中;
  2. call 子函数地址 返回地址入栈,将指令寄存器eip中保存的下一条执行指令的地址做为返回母函数的返回地址压入栈,当子函数调用结束后返回时,程序应该按照返回地址跳转到母函数的下一条指令继续执行;代码区跳转,处理器从当前母函数的代码区跳转到子函数程序的入口,即程序的控制权转移到被调用的子函数;
  3. push ebp ; ebp中母函数栈帧基址指针入栈,子函数将基址寄存器ebp中保存的母函数帧栈的基地址指针压入栈中保存;
  4. mov ebp, esp ; esp值装入ebp,ebp更新为新栈帧基地址。把esp最新栈顶指针拷贝到基址寄存器ebp中,这时ebp中保存的就是正在被调用子函数的基地址,即子函数的栈帧底部地址;
  5. sub esp, xxx ; 给新栈帧分配空间,根据函数需要保存局部变量的空间大小,栈顶指针esp从子函数的基地址向内存低地址偏移, 为局部变量留出一定空间
  6. mov esp,ebp 栈顶指针esp恢复到栈底位置,也就是将分配的栈空间全部释放,将分配的栈空间全部释放
  7. pop ebp 将ESP会指向的栈帧中母函数栈帧基址指针弹出保存在 ebp
  8. ret 按照返回地址指向的位置,返回main函数下一步指令
#include<stdio.h>
Int foo(int a){printf("argv = %d",a);return 0;
}
Int main(){int a;printf("hello stack \n");a=1;foo(a);return 0;
}

image-20241020151736664image-20241020151828266

  1. 在.text 40109c处,执行了mov eax, [ebp+var_4]指 令,ebp+var_4位置的偏移是变量a在栈帧中的位置, 继续执行了push eax指令,导致a被压入栈中,ESP 向低地址增长4字节。

  2. 在.text 4010a0执行了call foo指令,call指令会将调用函数结束的返回地址,即函数中下一条指令add esp,4的地址0x4010a5压入栈中,程序进入foo函数中执行。

  3. 进入foo函数后,首先执行的是push ebp指令,该指令的目的是保存上一函数栈的栈基址位置,便于函数结束后恢复上一函数栈。指令执行后,EBP中的数据被压入栈中,ESP向低地址增长4字节。

  4. 在.text 401021执行了mov ebp,esp指令,该指令将EBP寄存器的值改写为当前ESP寄存器的值,导致栈底上移,新栈开始分配

  5. 在.text 401023执行了sub esp,40h的指令,该指令均导致ESP寄存器向低地址增长了64字节,也就是说栈的长度为64字节,新栈分配到此结束。

  6. 从.text 401023至.text 401053为函数调用了printf函数, 是函数正常的执行逻辑及编译器添加的检查函数。

  7. 在.text 401058处开始执行了mov esp,ebp和pop ebp 两条操作,在某些编译器中这两条也被整合成为leave 指令。

    首先mov esp,ebp指令将栈顶指针esp恢复到栈底位置,也就是将分配的栈空间全部释放

    再执行pop ebp指令,由之前的步骤可知,当前ESP寄存器指向空间的内容是main函数栈的栈基址位置,指令结束后,ESP会指向返回地址位置,EBP寄存器会恢复到main函数函数栈的栈底位置。

  8. 在.text 40105b处执行了ret指令,该指令内容可以理解为pop eip,执行后程序跳转回main函数,并且栈帧也恢复回到main函数执行call foo之前的状态

**函数调用约定:**对函数调用时如何传递参数的一种约定

image-20241020153651356

**Cdecl:**是C/C++默认的参数传递方 式,参数从右向左入栈

image-20241020154055975

此时相比未压栈时,ESP指针向减小了3*4=12(0x0c)函数参数由调用者清除,也称为手动清栈

当执行过callfoo_cdecl后,ESP指针是会恢复到压入参数后的位置,下一条执行add esp,0x0c就会把ESP指针增加 12字节,也就恢复到了压入参数之前的位置,从而实现了参数清除

image-20241020154336560

**Stdcll:**stdcall是windows API默认方式,参数从右向左入栈

调用foo_stdcall相比foo_cdecl,缺少add esp , 0ch指令,也就是说调用者未对传入参数进行清理,相应的,被调用函数负责对参数进行清理

image-20241020154538831

foo_stdcall采用的是retn 0ch的指令,而非retn

该指令的作用是retn + pop 0x0c字节,即返回后使ESP增加12个字节,这与foo_cdecl的指令执行是一致的

stdcall方式的优点在于,被调用者函数内部存在清理参数代码,与调用函数后再执行add esp,xxx相比,代码尺寸要小,是Win 32 API库使用的函数调用约定

**Fastcall:**与stdcall方式基本类似,参数的清理也由被调用函数来负责

但该方式通常会使用寄存器(而非栈内存)去传递参数。

若函数需要传递多个参数,参数会从右向左由栈传入,同时,最后两个参数使用寄存器ECX和EDX来传递

fastcall方式是很快的,寄存器的访问要快于对栈所在内存的访问速度, 但有时需要额外的系统开销来管理寄存器,如在调用函数前ECX、EDX中存有重要数据,那么需要先进行备份,此外,如果函数本身需要使用这两个寄存器,也需要将参数迁移到其他位置进行使用

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

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

相关文章

Linux 系统目录结构

1.ls /查看目录 &#xff08;1&#xff09;/bin&#xff1a;bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。 &#xff08;2&#xff09;/boot&#xff1a;存放启动 Linux 使用的一些核心文件&#xff0c;包括一些连接文件以及镜像文件。 &#xff0…

基于MPPT最大功率跟踪的光伏发电蓄电池控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于MPPT最大功率跟踪的光伏发电蓄电池控制系统simulink建模与仿真。本系统包括PV模块&#xff0c;电池模块&#xff0c;电池控制器模块&#xff0c;MPPT模块&#xff0c;PWM模…

matlab图像处理(1)

注意&#xff1a; 读取图像文件时需若图像不在工程目录文件下&#xff0c;需在代码中表明其其他路径的具体位置及名称

1.3 自然语言处理的应用

自然语言处理&#xff08;NLP&#xff09;在多个领域有广泛应用&#xff0c;如自动文摘、机器翻译、情感分析等。本实战将通过NLTK库&#xff0c;演示文本预处理的关键技术&#xff0c;包括小写转换、去噪、文本规范化、词干提取、词形还原、标记化以及删除停止词。这些技术为构…

更改lvgl图片的分辨率(减少像素)达到减小内存占用的目的

lvgl的内存占比过大&#xff0c;更改图片的分辨率&#xff08;减少像素&#xff09;达到减小内存占用的目的&#xff0c;可以用更多的空间去开发其他的功能 -- 由于lvgl中图片占的内存过大&#xff0c;所以需要更改图片的分辨率&#xff08;降低像素的方式&#xff09; --注意…

斗破QT编程入门系列之一:认识Qt:初步使用(四星斗师)

斗破Qt目录&#xff1a; 斗破Qt编程入门系列之前言&#xff1a;认识Qt&#xff1a;Qt的获取与安装&#xff08;四星斗师&#xff09; 斗破QT编程入门系列之一&#xff1a;认识Qt&#xff1a;初步使用&#xff08;四星斗师&#xff09; 参考书籍 《Qt5.9 C开发指南》 斗破观…

练习LabVIEW第四十二题

学习目标&#xff1a; 使用labview编写一个用户确认界面&#xff1a; 我们在程序中赋予5个人的账号密码&#xff0c;账号使用人名&#xff0c;密码随便&#xff0c;并规定相关权限。访问权限要在前面板显示&#xff0c;并且访问成功与否也要有显示。 开始编写&#xff1a; 前…

mqtt 传递和推送 温湿度计消息 js

mqtt 传递和推送 温湿度计消息 做了一个mqtt的小网站 包括设备管理&#xff0c;订阅管理&#xff0c;连接认证订阅授权 这里我新增了一个设备 订阅组温湿度里面有两个订阅 设备详情授权给设备使用 设备连接 和之前的wifi连接一样 温湿度也和之前的使用一样 require(u…

xinference 使用命令实践记录

1. qwen-chat 模型相关的参数组合&#xff0c;以决定它能够怎样跑在各种推理引擎上 命令 xinference engine -e http://0.0.0.0:9997 --model-name qwen-chat 结果 2. 将 qwen-chat 跑在 VLLM 推理引擎上&#xff0c;但是我不知道什么样的其他参数符合这个要求。 命令: xin…

【代码随想录day22】【C++复健】77. 组合;216.组合总和III; 17.电话号码的字母组合

77. 组合 这题做完之后还是有一种稀里糊涂的感觉。思考了半天什么范围合理&#xff0c;并且怎么设置才能让这个范围合理&#xff0c;然而一看答案&#xff0c;发现答案完全没考虑这些因素&#xff0c;直接暴力全遍历了。只能说确实这样能够放弃思考&#xff0c;比较省心一些.…

选择适合你的报表工具,山海鲸报表与Tableau深度对比

在数据分析和报表制作的领域&#xff0c;企业往往面临着选择合适工具的难题。尤其是当市场上有很多功能强大的工具时&#xff0c;如何从中挑选出最适合自己需求的报表软件成为了一个关键问题。今天&#xff0c;我们将对比两款报表工具——山海鲸报表和Tableau&#xff0c;看看它…

unity优化webgl下的textMeshPro字体大小

成果&#xff1a;优化前2.5M的字体文件优化后只有几百kb不到1m了 背景&#xff1a;unity微信小游戏要求字体文件在3m以内姑且我认为2.5m以内实际可以干到1M以内。微信小游戏要求尽可能的进游戏快&#xff0c;在这个背景下我们需要对字体进行优化&#xff0c;我采用的是3500字的…

Spark的学习-02

Spark Standalone集群的安装 架构&#xff1a;普通分布式主从架构 主&#xff1a;Master&#xff1a;管理节点&#xff1a;管理从节点、接客、资源管理和任务 调度&#xff0c;等同于YARN中的ResourceManager 从&#xff1a;Worker&#xff1a;计算节点&#xff1a;负责利用自己…

Vue前端框架

一.Vue概述 *Vue 是一套前端框架&#xff0c;用于免除原生JavaScript中的DOM 操作&#xff0c;简化书写。 *基于MVVM(Model-View-ViewModel)思想&#xff0c;实现数据的双 向绑定&#xff0c;将编程的关注点放在数据上。 *官网&#xff1a; https://cn.vuejs.org/ 二.Vue快速…

软件设计师 7日速成

数据流图和数据字典 数据流图 定义 数据流图是一种图形化的工具&#xff0c;用于描述系统中数据的流动情况。它可以帮助我们可视化数据在系统中的处理过程&#xff0c;包括数据的来源、去向、存储位置以及处理方式。 组成元素 数据流图通常包含以下四个基本元素&#xff1…

基于 Vue3、Vite 和 TypeScript 实现开发环境下解决跨域问题,实现前后端数据传递

引言 本文介绍如何在开发环境下解决 Vite 前端&#xff08;端口 3000&#xff09;和后端&#xff08;端口 80&#xff09;之间的跨域问题&#xff1a; 在开发环境中&#xff0c;前端使用的 Vite 端口与后端端口不一致&#xff0c;会产生跨域错误提示&#xff1a; Access to X…

【Allure】allure装饰器函数

**allure装饰器**​作用&#xff1a;用于将测试用例的数据展示到测试报告中 1.需要将这些装饰器函数添加**测试方法或测试类的开头**。2.同一个类或者一个方法可以添加多个装饰器函数 &#xff0c;这样此用例就具有了个作用属性 。 allure.epic() 敏捷中的概念 项目名称 allu…

python验证码滑块图像识别

文章目录 1、案例图片1、需求说明2、代码实现总结 1、案例图片 1、需求说明 python 3.10,写一个滑块验证码的自动化程序。需要一个opencv的函数&#xff0c;能准确的计算&#xff0c;在这同一张图片上&#xff0c;滑块形状和缺口形状的坐标位置及两个形状之间在X轴上的距离。请…

Linux基础-常用操作命令详讲

Linux基础-常用操作命令详讲 一、openssl加密简单介绍 1. 生成加密的密码散列&#xff08;password hash&#xff09;​编辑 1.1 常见的选项总结表 1.2 加密参数详解 2. 自签名证书 3. 证书转换 二、文件管理 1. 创建空文件 ​编辑 2. 删除文件 4. 新建目录 ​编辑…

【RAG系列】KG-RAG 用最简单的方式将知识图谱引入RAG

目录 前言 一、引入知识图谱的作用 二、引入知识图谱的挑战 三、KG-RAG的理论 query多跳有限性 知识局部密集性 四、KG-RAG的方法 向量入库 向量相似搜索 扩展子图 LLM Rerank LLM response 五、效果比对 六、源码 总结 前言 本文介绍一种比较新颖的RAG范式&am…