CTF入门——从零开始的栈迁移

什么是栈迁移

所谓栈迁移,一般而言,是在栈溢出量很小(一般只能溢出到返回地址处),无法打一个完整的ROP链的情况下,利用两次leave ret,将栈帧位置改变到我可控的地址上,在这个地址上我预先已经写好了一段payload,于是在栈迁移过后程序就会继续执行我预先在别处布置好的payload,那么接下来我们就好好地来解释一下栈迁移的原理是什么

栈迁移的原理

首先,我们编写一个demo函数,来展示一个程序栈帧的建立与消亡过成

void demo()
{
	char buf[0x20];
	return 0;
}
int main()
{
	demo();
	return 0;
}

IDA打开看一下

进入demo的指令是call指令

这条指令相当于干了两件事:把当前指令(call)的下一条指令(mov)的地址压栈,跳转到demo

进入demo函数后,我们重点关注push rbp;mov rbp,rsp;sub rsp 30h这三条指令,在这里我讲详细讲解栈帧的构建与消亡过程

执行call指令后

执行push rbp后

此时rsp会向下移动,同时将当前的rbp压栈

执行mov rbp,rsp后

此时rbp,rsp的值相同

执行sub rsp,30h之后

从而为buf开辟了函数栈帧

在函数消亡时,会执行leave;ret;而leave指令,就是我们今天的主角,这条指令相当于两条指令:mov rsp,rbp;pop rbp

那我们来看一下正常函数调用结束的流程:

执行mov rsp,rbp之后

执行pop rbp之后

执行完ret之后

由此我们实现了栈帧的消亡

好了,现在如果程序存在一个栈溢出,但是溢出量很少,只能够溢出到ret的位置,那么此时我们该如何下手呢?于是,栈迁移应运而生

假设我们预先在一个可写地址(比如bss段或者栈上)构造好了rop链,那么我们可以如何让程序执行到我们的rop呢?

这里我们跳过函数栈帧的构建过程,直接来看栈溢出后程序将要返回的情况

我们利用栈溢出,把原先的old rbp伪造成了fake rbp(0x602020),返回地址伪造成了leave ret的地址(注意,栈帧我画的是上面是高地址,下面是低地址,而bss上面是低地址,下面是高地址)

那么在程序返回时,执行第一个leave(程序原本的leave),会变成如下状态

执行第一个leave的mov rsp,rbp

执行完第一个leave的pop rbp

由于返回地址也被填成了leave ret,所以接下来会执行第二个leave

执行第二个leave的mov rsp,rbp

执行第二个leave的pop rbp

此时rbp就变成0了,当然具体变成什么要看你0x602020位置的填充是多少

于是执行完这个leave之后,程序接着会执行ret指令,从而顺利的调用到rop链

以上就是栈迁移的全部原理

评论

  1. Not
    1 年前
    2025-3-08 20:07:30

    666,强强强

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇