【逆向学习记录】x86-64栈帧及跳转

本文详细介绍了64位系统中x86-64架构的栈帧建立、函数跳转过程,通过gdb调试展示了从main函数开始执行,到函数调用、返回的堆栈变化。在函数调用时,RIP被压栈,RBP仍用作栈帧指针。64位下函数参数传递通过寄存器和堆栈结合的方式,与32位有所不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概述

32位寄存器的堆栈和64位的寄存器的函数堆栈,还是有较大的差别的,这里准备花两篇文章好好学习温习一下,防止在不做技术的路上越忘越远,因此尽量写的详细简单,本文主要是64位寄存器的堆栈图,调试系统为ubuntu 16.04(64位)
具体堆栈图的画法,我是偶尔在网易云课堂学习的一节课(堆栈图),使用excel表格画图,也挺形象的,
64位的作图,原则上要采用8个字节的,因为要与上一篇文章进行对比,所以依然采用的是4个字节(int型的数据)

2.源代码及其汇编

源代码

#include<stdio.h>
int sum(int a,int b) {
   
    return a+b;
}
int main(){
   
    int i = 1;
    int j 
本人使用易语言 也有10几年时间,至今留下来的也只是一些怀念和情节,还记得上一次在本论坛发帖是在10年前,期间也只是零星来论坛看看。平常使用也纯属兴趣爱好,并非科班出身,一些见解如若有误,忘大神包含。我们知道目前易语言是支持32编译,后期估计也不会有所改善了,这似乎已经成为一门被放弃的失败品。既然如此面对如今64系统的普及,易语言爱好者面对64程序的操作层面上就显得有些无奈和悲哀。好在有着一些执着的爱好者不想放弃,依然在鼓励解决这些问题,如本论坛的一个开源模块WOW64Ext,就为易语言操作64模块进程提供了一些基本的封装,本人也借此基础上封装了几个功能,作为进一步扩展,有兴许的朋友可以继续完善。 一:浅谈64进程远程hook技术 关于HOOK这个话题,网络上铺天盖地并无新鲜,故在此我就不讲述什么是HOOK这些无聊话题了,本文主要阐述一些64下远程HOOK与32的主要区别。首先我们来看看要实现一个远程HOOK的构成顺序:1:在目标进程申请内存空间,存放我们截断后的穿插代码与HOOK原代码2:修改HOOK目标置的指令跳转至1申请的内存空间中3:穿插代码中把我们需要的寄存器或其他通过通讯手段传达到我们程序的回调接口中去,在这个过程中如果只需要取值,穿插代码不需要等待,如果需要修改生效,穿插代码需要等待回调接口的返回,并把修改内容写回。4:穿插代码最后跳回到HOOK度的下一跳指令,或指定的置。5:完成整个HOOK过程了解了整个过程看上去似乎很简单,确实要做到这个过程是不难的,只是要做到相对完美要考虑的情况有很多。比如对跳转使用的选择情况:HOOK跳转代码肯定是越短越好,像32JMP跳转只需要5字节即可,但是在64进程中情况确截然不同。32进程寻址能力为4字节,而64进程寻址能力变成了8字节,然而64汇编中所有的跳转直接寻址只支持4字节,这在32中当然不是什么问题,因为32最大寻址本来就不会超越4字节,不存在超限的说法:但64中想要达到转移,必须借用寄存器或地址偏移,那么一般在64中HOOK的跳转代码在不影响寄存器的情况下一般使用如下办法FFFFFFFFFFFFFFFF作为跳转目标地址:为了不影响寄存器必须提前压入一个寄存器 --------------------------Push raxMov rax, FFFFFFFFFFFFFFFF JMP rax 或 call rax 在内部要取回rax ,这里注意JMP和call的区别,最后平 -------------------------- Push rax Mov rax,FFFFFFFFFFFFFFFF Push rax Ret 在内部要取回rax ,最后平 有的朋友看到这要问了 我不能直接 JMP FFFFFFFFFFFFFFFF或者 push FFFFFFFFFFFFFFFF 啊,您要这么问,我实在不知如何回答你,表示无语,您还是直接下个源码玩玩算了。 其他类似的列子我就不一一举例了,总结也是差不多形态,以上列子共占用13字节度,这还是堆栈放在了内部平,否则还要+1个字节度,如果放弃其中一个寄存器可以-1个字节度,所以一般网上现有的64hook一般都在12字节以上,但是一个好用的hook要占用13字节的度,对我而言无疑无法忍受,难道真的没有其他办法了吗,要保护寄存器且支持转移,是不是还有其他办法,那么其实是有办法的,就是通过JMP [rip] 机器码形态为 FF 25 00 00 00 00 这句代码占用6字节,那么这是什么意思呢 FF 25 = jmp ,00 00 00 00为偏移度 对一个支持2G的字节转移度,JMP [rip]在调试器中可以解释为 jmp qword ptr ds:[0x地址],对了,也就是读取这个偏移置中的8字节数值作为跳转地址转移过去,如果偏移为00 00 00 00 那么就代表 JMP [rip]的下一条指令处8字节数据。想到这你也许会问  那么这个意思不就是JMP [rip]6字节+8字节度吗,对如果是连起来确实如此,但是我们可以给他个偏移啊,不就可以分开了吗,我们只需要搜索同模块的其他置中00或CC等连续8字节无用代码置,把跳转的地址写入其中,那么JMP [rip]就可以通过偏移读取到跳转地址了。我们也就能实现6字节的HOOK,这个方式的亮点是改写度小,且不影响寄存器和rsp堆栈指针,也算是达到曲线救国的目的。 比如对穿插代码中数据传递的问题:我们要获得16个通用寄存器RAX—R15的每个值,这些值我们又如何传递过去。一般远程HOOK数据传递使用消息或者远线程,因为这两种方式汇编改写量小一点,相对容易实现,在这我们不讨论远线程,我们来看看消息传递,一般是两个函数的选
CTF(Capture The Flag)是一种信息安全竞赛,通常涉及一系列与安全相关的挑战和问题。在这些挑战中,函数调用及的深入理解对于解决许多问题至关重要。 在计算机程序中,函数调用涉及到(Stack Frame)的创建和销毁。函数调用过程中用于存储函数参数、局部变量以及返回地址等信息的内存区域。具体来说,的结构通常包含以下几个部分: 1. 参数区域:存放函数调用时传递给函数的参数值。 2. 返回地址:函数执行完毕后,程序需要返回到的地址,通常是调用函数之后的指令地址。 3. 保存的寄存器:保存调用者上下文中的寄存器值,以便函数返回后能够恢复。 4. 局部变量区域:存放函数内部定义的局部变量。 5. 保存的指针(可选):一些系统会使用指针来引用当前的,方便函数调用和变量访问。 6. 顶指针:指向当前顶部的指针。 函数调用过程中,一般步骤如下: - 参数准备:调用者将参数准备好,按照约定的方式压入中。 - 调用指令:执行调用指令(如x86架构的`CALL`指令),该指令将返回地址压,并跳转到被调用函数的入口地址。 - 建立:被调用函数开始执行后,首先建立自己的,可能包括设置指针和局部变量等。 - 执行函数体:函数体内执行需要的操作,使用上的局部变量进行计算。 - 函数返回:当函数执行完毕准备返回时,清理局部变量,恢复之前保存的寄存器状态和顶指针,然后执行返回指令(如`RET`指令)回到调用者。 在CTF竞赛中,理解结构和函数调用机制对于逆向工程、二进制分析和漏洞利用等安全相关挑战尤为关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值