零基础学会 BOF 攻击,从原理到实操全解析

admin 2025年2月8日13:13:41评论8 views字数 2564阅读8分32秒阅读模式

开年第一篇,奇安信啥时候开活动

事前准备

选择的调试工具是VS,先把编译器的安全检查关了(如果使用其他编译器,试试直接/GS-):

零基础学会 BOF 攻击,从原理到实操全解析

原理介绍

然后我们需要了解一下BOF攻击的原理。

已知我们的变量存储在栈上,以如下形式排列:

零基础学会 BOF 攻击,从原理到实操全解析

首先我们知道栈是一种经典的先进后出结构,当栈为一个函数压入数据时,参数值最先被压入,返回地址其次,最后被压入的则是函数中出现的局部变量和临时变量。根据先来后到的规则(意思是后面的变量才能覆盖前面的),如果局部变量中产生溢出,则有可能跨越其它帧溢出到返回地址,进而改变程序执行的位置。这就是BOF中栈溢出的原理。

溢出在什么情况下存在?

最简单的溢出方法就是使用危险的内存操作函数,比如我们使用memcpy函数复制100字节到一个10字节的指针空间里。

通常编译器(也许还有其他东西)会检查代码是否存在溢出,所以我们需要提前关闭溢出检查等。

Think & Write

代码规划与编写

决定写一个自己溢出自己的程序,作为例子还是绰绰有余哒。

我们以函数func作为存在栈溢出的被害者,以shellcode作为要执行的目标。当程序运行时,main函数调用func并将其溢出,然后使shellcode被执行,程序结束(我没有考虑栈平衡,实际上程序会发生一些异常然后退出)。

这里直接使用我上文中提到的memcpy溢出:

char* func(char* jump) {    //危险的 拷贝函数    char a[] = "augustST";    memcpy(a, jump,128); //拷贝输入值到a    return a;}

shellcode,理论上应该是一段内存,不过没关系我们这里用一个函数表示:

void shellcode() {    printf("当你看到这一段的时候,你已经被溢出了思密达");}

然后我们需要在main函数中通过一系列操作溢出func,然后执行shellcode。具体的操作就是使用一个非常长的指针空间作为参数传入,然后覆盖func中的局部变量a。

还记得我们看到过的栈图吗,实际上就是从局部和临时变量空间向栈顶溢出到return address。

零基础学会 BOF 攻击,从原理到实操全解析

我们又知道,变量在内存中是反向存放的:

参数向栈底存放

零基础学会 BOF 攻击,从原理到实操全解析

变量空间内则向顶堆叠

打个比方就是,如果一块内存的值是x12x34x56,则其在内存中的样子是x56x34x12。

按照这个原理呢,我们准备的用于覆盖被攻击临时变量的形式应该是(我们假设目标地址是xabxcdxefxgh):

x11x11x11x11(无意义的填充量)...xghxefxcdxab。

比如说这里的shellcode是0x00B813BB:

零基础学会 BOF 攻击,从原理到实操全解析

那我们最后要溢出到return address的量就是这样:

零基础学会 BOF 攻击,从原理到实操全解析

而填充量呢,填充多少实际上并不是固定值,它取决于编译时留给函数的局部变量内存空间有多大。

也就是说,我们需要先跑一次程序才知道编译器会预留多大的空间。

那么可以先简单写一个main,调用了func就行:

int main() {    char jump[] = "1";//这里预估一下溢出偏移    func(jump);    return 0;}

第一次调试

直接在func本体打个断点,然后开调:

零基础学会 BOF 攻击,从原理到实操全解析

运行到断点处,打开反汇编,先获取一次ESP地址,此时的地址指向为返回地址(因为所有临时变量未存储,栈指针未下移),获得ESP当前地址为0X003EFA30:

零基础学会 BOF 攻击,从原理到实操全解析

可以看到程序已经运行到func内部,在a赋值处下断点,查看a的地址:

零基础学会 BOF 攻击,从原理到实操全解析

这里获取到的地址为0x008FF8E4,因为此时A指向的是常量"auguatST"的位置,不在栈内。

进行memcpy之后,再次获取地址(此时指向在栈内):

零基础学会 BOF 攻击,从原理到实操全解析

为0x003EFA1C。

将这两个地址(除去中间的)相减可以获得一个偏移量0X00000014,也就是20个字节。那么我们需要构造的则是一个二十字节长的字符串。

验证溢出位置

随便构造一个20长度的字符串

"augustSTx11x11x11x11x11x11x11x11x01x01x01x01",然后重新调试。还是在func的入口处下断开调,先获取ESP位置:

零基础学会 BOF 攻击,从原理到实操全解析

然后调试到函数末尾,查看溢出是否刚好覆盖到原ESP处,可以看到覆盖成功:

零基础学会 BOF 攻击,从原理到实操全解析

获取shellcode位置

由于shellcode也是一段内存中的指令(可以这么说),所以它在每次加载时位置也会发生变化。

因此,我们需要在每次启动程序时获取shellcode的地址,然后将其与我们的payload拼接起来再进行溢出。

而shellcode的地址其实就是指向shellcode内存位置指针的值,也就是(ptr)shellcode。

我们可以用以下代码完成这些步骤:

uintptr_t a = (uintptr_t)shellcode; //转为指针memcpy(jump + 16, &a, 4); //memcpy到jump的最后四字节

改写之后的main函数如下所示:

int main() {    char jump[] = "augustSTx11x11x11x11x11x11x11x11x01x01x01x01";//这里预估一下溢出偏移    uintptr_t a = (uintptr_t)shellcode;    memcpy(jump + 16, &a, 4);    func(jump);    return 0;}

把它们拼起来再执行(遇到任何异常请继续),结果也是华丽丽的的溢出啦:

零基础学会 BOF 攻击,从原理到实操全解析

溢出报错解决

其实整个BOF攻击的内容到上面就已经结束了,不过笔者出于个人原因还需要编译一个不报错的版本,所以有了这一章。

debug版本中无论怎么修改编译器选项,exe运行时仍然会弹窗报错:

零基础学会 BOF 攻击,从原理到实操全解析

所以需要修改到release版本运行。而release版编译时默认是会对代码进行优化的(比如删去表面上从不执行的shellcode),因此我们需要在编译选项里关闭代码优化,同样的溢出检查也需要关掉:

零基础学会 BOF 攻击,从原理到实操全解析

然后继续生成,再执行就可以无报错溢出了。

. . . * . * 🌟 * . * . . .

由于很多人问我微信群的事情,所以我建了一个小微信群。现在可以在公众号菜单里选择合作交流->交流群获取交流群二维码,希望大家和谐交流,为更好更友善的行业环境贡献自己的力量。

如果喜欢我的文章,请点赞在看。网安技术文章、安卓逆向、渗透测试、吃瓜报道,尽在我的公众号:

原文始发于微信公众号(重生之成为赛博女保安):零基础学会 BOF 攻击,从原理到实操全解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月8日13:13:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   零基础学会 BOF 攻击,从原理到实操全解析https://cn-sec.com/archives/3703556.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息