栈溢出攻击4——绕过ASLR的jmp esp技巧实战

1. 从限制长度到绕过限制:理解漏洞的根源

在上一篇文章里,我们聊了一个看起来“安全”的程序,它用 read 函数读取数据时,明明限制了长度,为什么最后还是被我们搞出了栈溢出?核心在于一个“声东击西”的漏洞:getLens 函数里,用来存储用户输入长度的变量 v2,和存储用户回答“是否修改”的缓冲区 buf,在栈上的位置离得太近了。

我画个简单的图帮你回忆一下:

栈顶 (低地址)
+------------------+
| buf (12字节)     | <-- 我们输入“y/n”的地方,但程序允许读16字节
+------------------+
| ... (填充)       |
+------------------+
| v2 (4字节)       | <-- 存储“允许读取长度”的关键变量!
+------------------+
栈底 (高地址)

你看,buf 只有12字节的空间,但 read 函数却允许我们写入16字节。多出来的那4个字节,不偏不倚,正好会覆盖掉下面的 v2。这样一来,我们就能把程序原本规定的“最多读32字节”,偷偷改成我们想要的任意长度(比如77字节)。这就是典型的“二次溢出”或“相邻变量覆盖”漏洞,程序检查了单次输入的边界,却没料到数据在不同函数、不同变量间流动时产生的副作用。

踩过这个坑之后,我得到一个很重要的经验:看漏洞不能只看一个函数、一行代码。必须像侦探一样,跟踪数据在整个程序里的生命周期,看看它从哪儿来,到哪儿去,中间有没有机会“变形”或者“越界”。很多看似坚固的防御,就是这样被一环一环撬开的。

2. ASLR:让地址“捉迷藏”的安全卫士

好了,现在我们能控制读取的长度了,可以往栈上写一大堆数据,覆盖掉函数的返回地址。按照经典的栈溢出思路,接下来是不是该把一段能打开shell的代码(Shellcode)塞进去,然后让程序跳过去执行就完事了?

如果你这么想,并且在现代的Linux系统(比如Ubuntu)上试过,大概率会失败。因为系统默认开启了一个叫 ASLR 的安全机制。ASLR,全称是“地址空间布局随机化”,这个名字听起来有点唬人,其实原理很简单。

你可以把程序运行时的内存想象成一座城市,代码区、堆区、栈区就像城市里的不同功能区。在没有ASLR的时候,每次程序启动,栈区(就是我们搞溢出的地方)都固定在同一个“街区”。我们知道门牌号(地址),直接“送货”(Shellcode)上门就行。

但ASLR开启后,每次程序重启,操作系统都会给栈区、堆区、库文件加载的地址进行一次“大搬家”。这次栈可能在0xfff83000,下次就跑到了0xfff75000。我们的Shellcode虽然成功“寄”到了栈上,但我们根本不知道它这次的具体“门牌号”是多少,自然没法让程序准确跳过去执行。

这就好比你想让朋友去你家拿东西,却只告诉他“东西在我家客厅”,而不说你家在哪个小区几栋几楼,他当然找不到。ASLR就是为了增加攻击者猜测“门牌号”的难度,让依赖固定地址的攻击手段失效。

3. 绝地求生:发现“jmp esp”这颗救命稻草

面对ASLR带来的地址随机化,我们传统的“硬编码Shellcode地址”的方法行不通了。难道就这样放弃了吗?当然不。在漏洞利用的世界里,有一条黄金法则:利用程序自身已有的东西

我们重新审视一下栈溢出发生时,CPU和内存的状态:

  1. 我们覆盖了返回地址,CPU拿着这个被我们篡改的地址去执行。
  2. 函数返回时,会执行 ret 指令,这个指令相当于 pop eip,即把当前栈顶的值弹出来,放到指令指针 EIP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值