[二进制漏洞]栈(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

#include <stdio.h>

/*自己的函数*/
void myfunction(int a,int b)
{
int c = a+b;
printf("%d\n",c);
} int main()
{
myfunction(1,2);
printf("函数调用完毕!");
return 0;
}

函数调用过程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)

[ebp+0]  -----> 栈帧底 ,也是当前的栈顶  【ebp】【esp】
[ebp+4] --> 调用完Call函数后下一条指令地址
[ebp+8] --> 1(参数1)
[ebp+0xC] --> 2(参数2)

在我们代码中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-0x10]  栈顶 [esp]
[ebp-0xC]
[ebp-8]
[ebp-4]
[ebp+0] -----> ebp 栈帧底 ,之前栈顶
[ebp+4] --> 调用完Call函数后下一条指令地址
[ebp+8] --> 1(参数1)
[ebp+0xC] --> 2(参数2) 【可以看到我们可以利用ebp这种方式来进行对临时变量的一个定位,因为ebp永远是栈底,所以可以用来寻找不同的数据,当ebp-代表的是临时变量,ebp+代表的是函数参数】

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

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

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

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

栈溢出漏洞实战

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

要求:不允许使用pwntools工具

#include <stdio.h>

void hack()
{
printf("Hack Success!!!!\n");
} int main()
{
printf("Hello,Please Start Hack!\n");
char buf[20];
scanf("%s",buf);
return 0;
}

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

pwndbg调试

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

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

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

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

开始Hack

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

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

思路:
char buf[20]; 是20个字节的空间,因为他是个临时变量,所以他应该是用ebp-xxx来定位。
假设 [ebp-xxx] = buf地址
那么我们需要覆盖到的是返回地址,一般是在[ebp+4]
而这里strcpy允许我们任意的输入任何长度的字符串(造成漏洞的原因)
我们这里只要把[ebp+4]给覆盖了就行,所以我们在输入20个字符串后,再继续输入4个字符串会把[ebp+0]覆盖掉,因为溢出。
接着继续输入4个字符串,(28个字符串),就会把[ebp+4]也给覆盖掉,就覆盖到返回地址了。
程序ret的时候,就能跳到我们28个字符串中最后4个字符串构造的地址中去了。

因为我们这里调试出来是[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函数。

#include <stdio.h>
int _a = 1;
int _b = 2;
int _c = 3;
int _d = 4;
int _e = 5;
int _f = 6;
int _g = 7;
int _h = 0x5655556d;
int _i = 8;
int _j = 9; void hack()
{
asm("mov esp,0xffffd57c\n");
printf("Hack Success!!!!\n");
asm("mov ebx,0\n");
asm("mov eax,1\n");
asm("int 0x80\n");
} int main()
{
printf("Hello,Please Start Hack!\n");
char buf[20];
scanf("%s",buf);
return 0;
}

解题思路:

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

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

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

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

Payload:

11111111111111111111(pUV

调试图:

Pwn菜鸡小分队

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

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

[二进制漏洞]栈(Stack)溢出漏洞 Linux篇的更多相关文章

  1. cve-2010-3333 Microsoft Office Open XML文件格式转换器栈缓冲区溢出漏洞 分析

    用的是泉哥的POC来调的这个漏洞 0x0 漏洞调试    Microsoft Office Open XML文件格式转换器栈缓冲区溢出漏洞 Microsoft Office 是微软发布的非常流行的办公 ...

  2. CVE-2010-2883Adobe Reader和Acrobat CoolType.dll栈缓冲区溢出漏洞分析

       Adobe Acrobat和Reader都是美国Adobe公司开发的非常流行的PDF文件阅读器. 基于Window和Mac OS X的Adobe Reader和Acrobat 9.4之前的9.x ...

  3. pwn之栈缓冲区溢出漏洞(入门)

    题目ret2text 题目信息确认 使用file命令查看文件类型 root@CTF:/home/# file ret2text ret2text: ELF 32-bit LSB executable, ...

  4. 简单尝试利用维控LeviStudioU的一栈缓冲区溢出漏洞

    这是别人给我发的,让我分析一下,看能否写出exp.只怪自己水平不够,最后没能写出exp,以下为自己的分析思路 环境为win10 pro x64 英文版(10.0.16299) 默认安全配置 一.漏洞分 ...

  5. [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇

    目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...

  6. Linux堆溢出漏洞利用之unlink

    Linux堆溢出漏洞利用之unlink 作者:走位@阿里聚安全 0 前言 之前我们深入了解了glibc malloc的运行机制(文章链接请看文末▼),下面就让我们开始真正的堆溢出漏洞利用学习吧.说实话 ...

  7. Nagios Core/Icinga 基于栈的缓冲区溢出漏洞

    漏洞名称: Nagios Core/Icinga 基于栈的缓冲区溢出漏洞 CNNVD编号: CNNVD-201402-484 发布时间: 2014-03-03 更新时间: 2014-03-03 危害等 ...

  8. Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞

    漏洞名称: Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-423 发布时间: 2013-11-29 更新时间: 201 ...

  9. Linux kernel ‘xfs_attrlist_by_handle()’函数缓冲区溢出漏洞

    漏洞名称: Linux kernel ‘xfs_attrlist_by_handle()’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-392 发布时间: 2013-11-29 更新 ...

随机推荐

  1. spring data es操作es

    https://www.freesion.com/article/59481222940/

  2. eBPF+Ftrace 合璧剑指:no space left on device?

    本文地址:https://www.ebpf.top/post/no_space_left_on_devices 最近在生产环境中遇到了几次创建容器报错 "no space left on d ...

  3. 算法题——Cantor表

    题目介绍 描述 现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的.他是用下面这一张表来证明这一命题的: 1/1, 1/2 , 1/3, 1/4, 1/5, - 2/1, 2/ ...

  4. 深入理解Kafka核心设计及原理(五):消息存储

    转载请注明出处:https://www.cnblogs.com/zjdxr-up/p/16127749.html 目录: 5.1文件目录布局 5.2消息压缩 5.3日志索引 5.4日志文件及索引文件分 ...

  5. Java学习day38

    Java内存:1.堆:存放new的对象和数组:可以被所有线程共享,不会存放别的对象引用 2.栈:存放基本变量类型(会包含这个基本类型的具体数值):存放对象的变量(会存放这个引用在堆里面的具体地址) 3 ...

  6. HbuilderX失焦时自动保存编辑器内容

    hbuilderX 有一个非常好用的功能:就是自动保存. 而且不需要安装什么插件,只需要在编辑器设置就可以了.接下来我们一起来设置吧: 1.打开我们的hbuilderX编辑器.在最上排选项栏里打开 & ...

  7. linux 手动挂载硬盘没有移到回收站解决方法

    linux 手动挂载硬盘没有移到回收站解决方法 修改挂载硬盘的文件夹权限为当前用户即可 或者 修改读写权限 chmod 777 mount-disk-path

  8. Invocation failed Unexpected end of file from server java.lang.RuntimeException: Invocation failed Unexpected end of file from server

    Android studio 提交 push的时候报错. Invocation failed Unexpected end of file from serverjava.lang.RuntimeEx ...

  9. Resource wordnet not found. Please use the NLTK Downloader to obtain the resource:

    第一次使用nltk的时候,出现了这样的错误: from nltk.stem.wordnet import WordNetLemmatizer lemmatizer = WordNetLemmatize ...

  10. AC自动机:Tire树+KMP

    简介 AC自动机是一个多模式匹配算法,在模式匹配领域被广泛应用,举一个经典的例子,违禁词查找并替换为***.AC自动机其实是Trie树和KMP 算法的结合,首先将多模式串建立一个Tire树,然后结合K ...