• [二进制漏洞]栈(Stack)溢出漏洞 Linux篇


    [二进制漏洞]栈(Stack)溢出漏洞 Linux篇

    前言

    我们在学习栈溢出漏洞之前,最好都要懂一些开发,还有一些汇编知识,因为不管是安全还是逆向,这些都是基于开发的,有了开发扎实的基础在后续中才会突破瓶颈。

    堆栈

    推荐大家可以先去看看《王爽汇编》,或者直接看Bilibili的堆栈是个啥?

    堆栈(Stack)概念

    首先来了解下什么是堆栈?我们得从CPU开始说起,CPU中有个模块叫ALU,专门用来处理数据运算。

    学过汇编的小伙伴们都知道,CPU中有多个寄存器,不过是固定的,比如eax、ebx、ecx、edx、ebp、esp、edi、esi、eip等,当处理的数据过多或者过大时候,寄存器都不够用了,这时候怎么办?

    增加CPU的寄存器吗?不行那样成本太大了,所以就需要找另外的地方存数据,那么硬件中读取速度除了CPU,也就内存条速度最快了。

    所以CPU招募了内存条,来用来存储数据,在内存条中还专门找了个区域用来存数据即:堆栈(stack),说白了堆栈就是一块内存

    堆栈数据存储方式

    我简单的画了一个堆栈示意图,堆栈是一个自高地址向下增长的内存空间,从图中可以看到我们的高地址,也就是栈底,而低地址大概4个空格的位置是栈顶。

    也就是记住地址越低是栈顶,而且堆栈中要添加数据,地址要往跟低的地址移动。

    接下来我们继续来看看,如何在堆栈中存取和读取数据,他既然是块内存,那么我们关注的肯定是存取和读取,首先堆栈中存入数据叫push,读取数据叫pop

    堆栈管理数据的方式是先进后出,即存取进去的数据,会在堆底,最后存取进去的数据会在栈顶,所以最先拿出来的数据也是最后放进去的即栈顶。

    这里有个需要注意的地方,就是很多人以为pop数据后,堆栈里面的数据就清空了,其实并不是。

    之前说过堆栈其实就是一块内存,当我们pop后,其实知识把栈顶往下移了而已,内存里面的数据还是在的,并没有被清除掉,只是对于堆栈而言,那数据被弹出。

    当要push新数据,push很多个数据,或者pop很多个数据,都按照图示以此类推。

    函数调用

    函数调用C语言代码

    学习堆栈最重要的应该就是函数调用了,当我们调用完一个函数后,代码都会往下继续执行下一句代码,那么这一步在底层是如何实现的呢?CPU怎么知道接下来要执行你函数调用完后的下一句代码?

    这一部分其实稍微学过汇编的都应该知道。

    当我们调用个函数的时候,在汇编层是叫Call myfunction,而调用函数的时候就会用到堆栈,传入的参数即:push

    函数调用过程GDB调试

    接着我们将上面代码编译出来,并且关闭stack保护,编译成32位,命令gcc test.c -m32 -fno-stack-protector -o test

    接下来用pwndbg进行调试,详细的看下,函数调用与堆栈中的关系。gdb test , b main ,r

    断点断到如上的位置,然后再单步n执行到call myfunction处,此时注意观察堆栈,可以看到堆栈中压入了数据2,1

    而我们代码是 myfunction(1,2);第一个参数是1,第而个参数是2,因为堆栈的先进后出的特性,所以先把最后的数据入栈。

    函数Call返回原理

    接着最重要的一步,需要注意!

    目前我们处在call myfunction函数上,我们先记一下call myfunction的下一句汇编地址是多少,我这里是0x565555ac,然后接着我们输入si,单步步入进行调试,跳转到myfunction函数的内部,然后此时注意观察你的堆栈有什么变化!

    此时我们观察堆栈发现,之前我们call的下一句地址0x565555ac被压栈了。

    当我们一直单步步过myfunction函数中的汇编代码,直到他的最后一句这里,发现汇编代码是一句ret,ret的汇编代码其实就是pop eip

    也就是将堆栈中的数据弹出到eip,eip我们都知道是汇编中的PC指针,修改eip,那么当前CPU就会指向那地方开始执行代码。

    而当前的堆栈数据就是我们调用myfunction函数时压入的下一条指令的地址,所以将其弹到eip,CPU就会指向那地方执行代码。

    所以底层利用这种call 函数时将下一条指令地址压栈的方式,然后执行完函数后再弹栈到eip的方式跳过到调用完函数后的下一条代码。

    函数栈帧

    函数栈帧描述

    栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构

    当一个函数在运行时,需要为它在堆栈中创建一个栈帧(stack frame)用来记录运行时产生的相关信息,因此每个函数在执行前都会创建一个栈帧,在它返回时会销毁该栈帧。

    所以说函数栈帧就是一种数据结构,也是块内存里的数据。

    函数栈帧调试

    我们继续用之前的代码做例子,然后用pwndbg调试来详细的分析函数栈帧。

    如上图,当我们准备调用call myfunction的时候,其实在C语言中是当我们执行myfunction(1,2)的时候就会生成一个栈帧,那么在汇编层具体是什么时候创建呢?

    然后当我们进入到myfunction函数内部,然后看到第一条汇编语句是push ebp,将ebp寄存器压入堆栈。

    EBP寄存器又被称为帧指针(Frame Pointer) 【指向当前栈帧的底部】
    ESP寄存器又被称为栈指针(Stack Pointer) 【永远指向栈帧的顶部】
    

    然后接着的一句汇编代码是mov ebp,esp这一句汇编指向完后,才开始真正的创建栈帧。

    此时栈帧的数据结构差不多是这样: (现在我们就可以用ebp来进行寻址了,当我们要用到第一个参数那么用ebp+8即可,第二个参数ebp+0xC)

    在我们代码中myfunction里面还有计算a+b的值赋值给c的代码,我们继续调试看汇编且关注栈帧中对数据的处理。

    当执行完栈帧创建后的汇编代码后,第一句的汇编代码是sub esp,0x10,我们之前讲过esp永远为栈顶,当esp-16代表的是,esp要向上移动16字节,用来存放数据。

    一般来说这种sub esp,xxx或者add esp,-xxx,都是用来创建临时变量 ,存放临时变量数据的。我们这里的临时变量就一个那就是int c,那么int占用4个字节,这里开辟了

    16字节空间,可能是gcc的优化为了对齐什么的吧,Windows的话多少个临时变量空间就开辟多少空间。

    那么此时的栈帧结构如下所示:

    当我们继续单步执行代码,执行到如下图所示的地方,可以看到果然是利用【ebp+偏移】进行函数参数的定位,然后利用【ebp-偏移】进行临时变量的定位。

    OK很好,到这里我们基本已经了解了函数调用栈帧的一个详细原理了,这里再考考大家,那我在这个myfunction函数里要怎么知道返回后下一条代码的地址呢?

    这个在之前说过了,当执行到ret汇编代码的时候,会把堆栈里面数据弹给eip。
    那么现在我们用了函数调用帧的概念,是不是就很好懂了,当我们执行到ret的时候,这时候栈帧也就全部结束了,所以此时堆栈中的数据就是返回地址了。
    也可以用[ebp+4]来代表返回地址。
    

    最后从其他文章里面偷来的图片,方便理解函数栈帧概念。

    栈溢出漏洞实战

    要求实现栈溢出来执行没有被调用的hack函数。

    要求:不允许使用pwntools工具

    首先我们执行程序,然后输入>=20字节,程序会崩溃(缓冲区溢出)!

    pwndbg调试

    接下来老规矩,pwndbg开始调试。

    首先来找到返回地址,正常情况下[ebp+4]就是ret的返回地址,但是main函数可能不太一样。

    调试下来发现,[ebp+20]才是返回地址,这个实际情况还是以ret语句时候堆栈里面的数据为准。

    在这里我们可以手动用命令set *地址=值来把return地址改成其他的,这里我们改成hack函数。

    开始Hack

    OK上面我没通过调试器修改数值,直接将堆栈的值改成了hack函数的地址,让他在return的时候直接返回到hack函数,从而成功输出Hack Success!!!!

    接下来我们用溢出来构造流程,让程序执行hack函数。

    因为我们这里调试出来是[ebp+20]才是返回地址,而且这里buf是[ebp-0x1c],0x1c=28,所以28字节刚好覆盖到ebp,那么再加20就覆盖到返回地址,所以长度是28+20=48

    覆盖前

    溢出覆盖后,溢出字符串1111111111111111111111111111111111111111111111112222

    哈哈哈,一开始我还以为开心的结束了能hack到了,结果狗日的...有坑啊这玩意。

    ;这里把[ebp=8]地址设为栈顶,调试发现[ebp-8],刚好是char [20]字节后的数据,也就是溢出后的第一个字节地址。
    0x565555e2 <main+74>                  lea    esp, [ebp - 8]
    ;然后这里把栈顶弹给ecx寄存器
    0x565555e5 <main+77>                  pop    ecx
    0x565555e6 <main+78>                  pop    ebx
    0x565555e7 <main+79>                  pop    ebp
    ;这里又把[ecx-4],也就是[ebp-8]栈顶-4位置堆栈里面的 值 ,设置为新的esp,然后ret返回。
    0x565555e8 <main+80>                  lea    esp, [ecx - 4]
    0x565555eb <main+83>                  ret
    
    所以这里的思路是,我们可以来控制ecx寄存器,因为ecx寄存器是由[ebp-8]地址的值赋值过去的,这里刚好是我们溢出覆盖到的最开始4个字节,所以我们可以控制这个地址,然后让这个地址指向偏移-4位置,然后这位置里面的值是hack函数地址,即可hack成功!
    

    哈哈,因为我自己出的题目,要求不能用pwntools工具,所以只能用ASCII码来构造,构造来构造去发现ecx的堆栈地址是0xFF这种开头的,这种ASCII码对不上,超过能显示正常字符的ASCII码了,所以最后放弃了,我重新把题目代码改了下,改成了下面的样子。

    题目要求:不能使用pwntools,让程序执行hack函数。

    解题思路:

    这题目不同电脑可能运行效果不一样,因为我把地址写死了,我这里把hack函数地址写到了全局变量,而且故意是第8个全局变量,因为这位置刚好是 .data段中地址是 可以用ASCII码来显示的,然后我在hack函数开头用了一个汇编设置了栈顶,因为不设置的话调用printf函数会失败,最后用汇编调用int 80(中断),功能号1 exit来强制退出程序,让其能显示出Hack Suucess字符串。
    

    因为构造中是要[ecx-4]才是返回地址,所以我们要填入的地址是0x56557028,字符串是VUp(

    因为内存中是大端存储,我们要反过来,改成(pUV

    最后加上20个字符串用来做溢出,payload如下。

    Payload:

    调试图:

    Pwn菜鸡小分队

    最后感谢大家的阅读,本菜鸡也是刚学,文章中如有错误请及时指出。

    大家也可以来群里骂我哈哈哈,群里有PWN、RE、WEB大佬,欢迎交流


    __EOF__

  • 本文作者: VxerLee
  • 本文链接: https://www.cnblogs.com/VxerLee/p/16391711.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    华为ensp创建 VLAN
    域名重定向工具 —— SwitchHosts 实用教程
    深度学习笔记: 最详尽解释预测系统的分类指标(精确率、召回率和 F1 值)
    LayUI使用(二)处理表格会出现下拉框的问题
    docker desktop 点击setting 一直转圈圈
    SQL Server Query Store Settings (查询存储设置)
    66.C++多态与虚函数
    激光测距用高精度时间测量(TDC)电路MS1005
    浅谈设计模式-备忘录模式
    4年用户数破亿,孙哥带领波场再创新高
  • 原文地址:https://www.cnblogs.com/VxerLee/p/16391711.html