IDA反汇编之栈帧例释

2023-11-08

目录

1.  例释环境和预备知识

1.1 运行环境

1.2  IDA版本

1.3  预备知识

2.  函数调用约定

3.  函数局部变量布局

4.  函数栈帧示例

5.  IDA栈视图


1.  例释环境和预备知识

1.1 运行环境

本示例运行环境为Windows 10平台,所示例的程序或动态库为VS2022平台下编写并编译的X64程序。

1.2  IDA版本

本示例使用IDA Pro版,版本号7.7.220118 Win x64。

1.3  预备知识

理解本示例需要具务如下知识:

关于栈桢的约定的相关知识,详情请参见Windows平台的相关约定:

X64汇编语言寄存器结构及其与X86架构编程区别_ComputerInBook的博客-CSDN博客_x64寄存器

而关于Linux或Unix平台的栈帧的约定稍有不同,请参见:

Linux/Unix平台X64函数调用约定_ComputerInBook的博客-CSDN博客_linux x64调用约定

以上两者虽然有一些差异,但它们又有共同之处,即保有影子内存,序言和结语,以方便对于调用异常的处理。

2.  函数调用约定

与X86调用约定不同,C/C++编译器在64位平台上仅支持一种调用约定。这种调用约定利用了64位平台上可获得的新增寄存器数量:

(1)前4个整数或者指针参数依次通过rcx,rdx,r8和r9传递。

(2)前4个浮点参数通过通过前4个SSE寄存器xmm0-xmm3传递。

(3)由调用者为寄存器中的参数传递保留栈上的空间(至少在运行栈上分配32字节的阴影空间(shadow space))。被调用函数可以访问这个栈空间来,将寄存器中的内容写回栈空间。

(4) 任何多余4个参数的其它参数都使用栈来传递,并按照从左到右的次序(即,从第5个参数开始,使用栈传递参数)。

(5) 任何调用返回的整数或者指针值都放在rax寄存器中(调用完成执行返回动作时放在rax寄存器中,例如,调用ret指令时),而浮点数的返回值放在寄存器xmmO中。

(6) rax,rcx,rdx,r8-r11寄存器是易失性的(volatile)。

(7) rbx,rbp,rdi,rsi,r12-r15 寄存器是非易失性的(nonvolatile)。

这些调用约定与C++非类似:指针默认作为第一个参数传递,其它三个参数利用余下的3个寄存器,多出4个的参数则使用栈传递。

(8) call指令从rsp(堆栈指针)寄存器中减去 8,表示空出8字节的栈空间用于存放返回值,因为地址是64位长(8字节)。

(9) 当调用一个子过程(subroutine)的时候,规定指令指针(rip)必须在一个16字节的边界对齐(也就是128位,即16的倍数,这可能是在设计CPU时综合性能考量)。call指令将一个8字节的返回地址压入堆栈中,因此,调用程序必须从堆栈指针中减去8,除了32,它已经减去了阴影空间。

以下示例中,我们在单独的dll文件中定义如下形式的原型函数,因为只展示函数参数传递,我们不写函数体:

void _cdecl demo(int v1, int v2, int v3, int v4,int v5);

实现为空函数体:

void demo(int v1, int v2, int v3, int v4, int v5)

{

}

调用函数:

void Test()

{

    demo(1, 2, 3, 4,5);

}

生成可执行文件后,用IDA打开,定位到Test函数的反汇编:

(1) 切换到Imports窗口,找到调用函数demo:

(2) 切换到Imports窗口,双击显示demo函数名的这一行,进入到IDA View-A窗口:

在图中高亮行显示extrn __imp_demo:qword这一行右键,再选择交叉引用列表,出现如下交叉引用界面:

我们选中第一行,双击第一行跳转到调用函数的汇编代码(图示模式):

右键选对【文本模式】进入文本模式(或按【空格键】)在图形模式或文本模式之间切换:

(3) 我们从调用函数开始分析,即下面的反汇编代码:

在这个调用函数前后,标有---SUBROUTINE----字样,以区分调用函数体的反汇编代码起止。

下面分别分析这一段反汇编代码:

var_18= dword ptr -18h

这是IDA为方便描述指针偏移而定义的局部变量,只起辅助分析作用,不是反汇编的一部分。其命名规则为var_offset的形式,其offset为相对于基址指针的偏移量;dword ptr表示这是一个双字整数,-18表示具体的值因为后面用加的形式,因为这里用负号;h后缀表示十六进制数

sub     rsp, 38h

这一行表示在调用函数的栈空间内分配38h(56字节的栈空间),因为栈空间的分配方向是由高地址向低地,栈顶指针指向低地址,分配空间即相减。为什么全本56字节呢?从前面的预备知识我们可以了解到,四个参数的影子内存占用8x4=32个字节(注意,一个X64影子内存的大小是8字节,不管这个参数是多大),参数数量小于等于4时,默认分配28h(40字节的影子内存参数量多于4个字节时,按每16字节递增,这里5个参数,本来再分配8字节就可以存下,由于最小分配16字节,因此分配56字节。

mov     [rsp+38h+var_18], 5

按从右向左的次序将参数入栈,因此先将5这个参数压入当前栈的偏移rsp-18h处,示意图如下:

rsp+56((=rsp+38h)

rsp+48

5

rsp+32(=rsp+38h-18h)

rsp+24

rsp+16

rsp+8

rsp

mov     r9d, 4

mov     r8d, 3

mov     edx, 2

mov     ecx, 1

按从右向左的次序将参数移入对应的寄存器参数(32位模式下是使用栈存储参数,注意与X64模式的区别),我们可以将这四个参数保存到影子内存,如果这样做了,则rsp栈指针恰好指向函数的第一个参数

call    cs:__imp_demo_cdecl

系统开始调用子函数,注意,在call命令还做了一件事,即在进入demo_cdec函数之前,将rsp的指针减8,分配8字节的空间用于存放返回地址,调用子函数完成后,从此处取出返回地址,并跳转到进入函数之前的地址,同时rsp指针加8(注意:X64模式会忽略前缀cs:)

add     rsp, 38h

这里与分配栈空间的地方对应,前面分配了38h字节内存,为里还原回去,从而使用得堆栈正确。

3.  函数局部变量布局

    与函数参数传递具有约定的规则不同的是,没有约定的规则指导局部变量的应该如何布局。当编译器在编译一个函数的时候,它必须面对的一个任务是计算出函数的局部变量所需要占用的空间数量。另一个任务是确定是否这些变量可以位于CPU的寄存器中,或者确定是否它们都应该位于程序栈帧中。决定将局部变量存于何处与调用函数或者任何被调函数都没有任何关系。基于检查函数源代码去确定局部变量的存储布局显然是不可能的。

4.  函数栈帧示例

定义两个函数,被调函数bar,为了演示,我们先置其实现为空;调用函数demo_stackframe,如下所示:

//callee

void bar(int j, int k)

{

}

//caller

void demo_stackframe(int a, int b, int c)

{

    int x = 0;

    char buffer[64];

    int y = 0;

    int z = 0;

    bar(z, y);

}

外层调用:

void Test()

{

    demo_stackframe(1,2,3);

}

生成可执行文件,然后执行后续操作。

(1) IDA加载可执行文件后,进步菜单【跳转】->【跳转到函数】,查找函数demo_stackframe:

(2) 反汇编代码分析

frame= byte ptr -1B0h

var_190= byte ptr -190h

var_18C= dword ptr -18Ch

k= dword ptr -10Ch

j= dword ptr -0ECh

arg_0= dword ptr  10h

arg_8= dword ptr  18h

arg_10= dword ptr  20h

下面分别说明:

frame= byte ptr -1B0h

var_190= byte ptr -190h

这两句是IDA定义的辅助变量,表示指针偏移值,byte ptr表示指针类型,后面的值表示具体偏移值。

var_18C= dword ptr -18Ch

这一句定义一个双字类型辅助变量var_18C,值为-18Ch

k= dword ptr -10Ch

j= dword ptr -0ECh

这两句定两个双字类型常量j,k。

arg_0= dword ptr  10h

arg_8= dword ptr  18h

arg_10= dword ptr  20h

这三句定义三个双字类型参数常量。

mov     [rsp-8+arg_10], r8d  ;将函数的第三个参数移入栈内存rsp-8+arg_10位置。

mov     [rsp-8+arg_8], edx ;将函数的第二个参数移入栈内存rsp-8+arg_8位置。

mov     [rsp-8+arg_0], ecx ;将函数的第一个参数移入栈内存rsp-8+arg_0位置。

push    rbp  ; rbp  指针入栈

push    rdi; rdi指针入栈

sub     rsp, 1A8h  ;分配1A8h大小的栈存储空间

lea     rbp, [rsp+20h]  ;将rsp+20h处的地址移入rbp寄存器作为栈基址

其它编译器生成的语句暂且跳过

mov     [rbp+190h+k], 0

mov     [rbp+190h+j], 0

mov     edx, [rbp+190h+k]               ; k

mov     ecx, [rbp+190h+j]               ; j

call    bar

以上语句将i,j局部变量赋值0后移入参数寄存器ecx,edx,再调用函数bar。

前面使用sub rsp, 1A8h分配了栈内存,我们看看后面释放栈内存的语句:

lea     rsp, [rbp+188h]

pop     rdi

pop     rbp

retn

本来与lea  rsp, [rbp+188h]等效的语句是add rsp, 188h,根前面的关系可以看出,

rsp = rbp-20h,因此,还原回去的需要加上1A8h ,即rsp= rbp-20h+1A8h=rbp+188h,这正是上面lea  rsp, [rbp+188h]的由来。

5.  IDA栈视图

很显然,栈帧是一个运行时概念;没有栈和没有一个正在运行的程序,栈帧是不能存在的。但是,这并不意味着你在执行诸如使用IDA等执行静态分析的时候,可以忽略栈帧的概念。为每个函数建立栈帧的所有代码都存在于二进制文件中。通过存细分析这些代码,即使程序没有运行,我们也可以获得任何函数栈帧的详细信息。事实上,IDA 的一些最复杂的分析功能是专门用于确定 IDA 反汇编的每个函数的栈帧布局。在初始分析阶段,IDA 通过记录每个push或pop操作以及任何可能改变堆栈指针的算术运算(例如添加或减去常量值),竭尽全力监控堆栈指针在函数过程中的行为。另外的目标包括确定是否一个给定函数使用了一个专用的栈帧指针(例如,通过识别一个push ebp/mov ebp,esp序列),以及识别所有引用函数栈帧局部变量的内存。

IDA栈帧视图有两种,一种是概要栈帧视图,另一种是详细栈帧视图。

查看概要栈帧视图,在引用的变量之上单击右键,比如:

mov     [rsp-8+arg_10], r8d

mov     [rsp-8+arg_8], edx

mov     [rsp-8+arg_0], ecx

arg_10上单击右键,出现上下文菜单,有诸多操作可以在这里选择。如下图:

要查看详细栈帧视图,同样,选中对应的变量,双击,可弹出如下栈帧视图:

上图中两个特别的值需要注意:“s”和“s”(每个变量以前导空格开始),这些伪变量是 IDA 对保存的返回地址(“r”)和保存的寄存器值(“s”在本例中仅表示 EBP)的特殊表示。 为了完整性,这些值包含在栈帧视图中,因为栈帧中的每个字节都被考虑在内

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IDA反汇编之栈帧例释 的相关文章

随机推荐

  • 4 个有用的 JavaScript 闭包技巧

    什么是闭包 根据 MDN 闭包是捆绑在一起 封闭 的函数及其周围状态 词法环境 的引用的组合 换句话说 闭包使您可以从内部函数访问外部函数的作用域 在 JavaScript 中 每次创建函数时都会创建闭包 例如 const getShowN
  • ioctl函数详解

    大部分驱动除了需要具备读写设备的能力之外 还需要具备对硬件控制的能力 一 在用户空间 使用ioctl系统调用来控制设备 原型如下 cpp view plain copy int ioctl int fd unsigned long cmd
  • IDEA+创建Maven多模块springboot项目

    第一步 创建顶级pom工程 一路next后 删掉顶级工程的src目录 父工程只需要管理好全局依赖和变量就好
  • 搭建nginx网站服务器

    1 检查网络环境 查看服务器是否可以上网 ifup ens33 打开ens33网卡 ping www baidu com 2 Centos 7配置阿里云yum源和安装EPEL源 1 对默认原文件进行备份 cd etc yum repos d
  • 计算机视觉的上游任务和下游任务

    这几天看CV论文和视频 经常提及什么上游任务 下游任务 简单来说下游任务是具体部署 上游任务是训练一个用于特征提取的预训练模型 比如这几年很火的CLIP 1 GPT 2 计算机视觉四大任务 分类 解决 what 定位 解决 where 检测
  • Unable to open debugger port

    今天在启动idea debug情形下报了如下错误 就是 Error running tomcat Unable to open debugger port 127 0 0 1 58946 java net SocketException s
  • java es score_elasticsearch系列(五)score

    概述 score在ES中有着很重要的作用 有了它才有了rank 是验证文档相关性的关键数据 score越大代表匹配到的文档相关性越大 官方解释 查询的时候可以用explain来展示score的计算过程 也可以增加format yaml来讲j
  • JAVA机试练习(2023华为od练手题PDF)

    目录 一 字符串操作 二 排序 三 栈 四 排列组合 五 双指针 六 深度搜索 七 二叉树 八 其他 1 HJ5进制转换 16变10 JAVA实现16进制转10进制 java十六进制转十进制 奥特曼下象棋的博客 CSDN博客 2 HJ3去除
  • Spring依赖注入的三种方式及其区别

    一 基于构造器的依赖注入 private final InventoryMapper inventoryMapper public InventoryController InventoryMapper inventoryMapper th
  • RocketMQ部署之动态设置JVM启动参数

    这里是weihubeats 觉得文章不错可以关注公众号小奏技术 文章首发 拒绝营销号 拒绝标题党 背景 线上的RocketMQ集群有运行一段时间了 比如测试环境和线上环境的RocketMQ集群部署的机器内存大小肯定不一样 所以可能要写多个部
  • PHP使用原生sql语句实现七天连续签到

    PHP原生使用原生sql语句实现七天连续签到 准备 一张放用户签到的数据表 字段包括id userid 用户id signtime 签到时间 时间戳 days 连续签到时间 七天连续签到 public function sign useri
  • MySQL 性能监控 4 大指标

    编者按 本文作者为 John Matson 主要介绍 mysql 性能监控应该关注的 4 大指标 文章系国内 ITOM 管理平台 OneAPM 编译呈现 MySQL 是什么 MySQL 是现而今最流行的开源关系型数据库服务器 由 Oracl
  • Temporary failure in name resolution 错误解决方法 运行sudo 命令 can not resovle host xxx

    Temporary failure in name resolution 错误解决方法 vim etc resolv conf nameserver 114 114 114 114 nameserver 8 8 8 8 运行sudo 命令
  • 你是否也无法在Thebrain 11中打开旧版数据?看看正确过渡新版方式

    TheBrain是一款与众不同的思维导图软件 其所有信息通过一个又一个的节点进行联系 最终形成一个杂而不乱的网状结构 一旦你搜索并点击一个想法后 与之相关的所有关联信息将一目了然 与传统的树形思维导图相比 TheBrain更有助于整合零散的
  • Robot Framework 学习(1)- 简单网站兼容性测试

    Robot Framework 简单网站兼容性测试 0 Robot Framework 简介 Robot Framework 是一个通用的自动化测试框架 主要用于 验收测试 和 验收测试驱动开发 ATDD 会其它文章中会详细介绍ATDD 它
  • SecureCRT发送AT指令

    1 首先安装驱动 MTK提供的驱动 会在设备管理器里面显示 2 打开secureCRT 选择连接类型为serial串口 3 设置secureCRT可以输入文本 4 然后就可以输入指令测试看看了 整个过程结束 但可能在第4步时输出没反应 这是
  • VS2017配置QT5.14.2

    一 QT下载 需要注意的是VS与QT的版本对应 VS2017对应的最新的QT版本是5 14 以后的版本适应的是VS2019 本文以VS2017 QT5 14为例 QT5 14 下载地址 Index of archive qt 5 14 5
  • 单片机——蜂鸣器(生日快乐歌)

    基础知识 改变单片机引脚输出波形的频率 就可以调整控制蜂鸣器音调 产生各种不同音色 音调的声音 改变输出电平的高低电平占空比 占空比是指一个周期内高电平所占的时间 则可以控制蜂鸣器的声音大小 单片机采用的是无源蜂鸣器 需要产生一定的脉冲才能
  • Acwing-873. 欧拉函数

    欧拉函数的证明使用了容斥原理 include
  • IDA反汇编之栈帧例释

    目录 1 例释环境和预备知识 1 1 运行环境 1 2 IDA版本 1 3 预备知识 2 函数调用约定 3 函数局部变量布局 4 函数栈帧示例 5 IDA栈视图 1 例释环境和预备知识 1 1 运行环境 本示例运行环境为Windows 10