20155306 白皎 0day漏洞——漏洞利用原理之栈溢出利用

20155306 白皎 0day漏洞——漏洞利用原理之栈溢出利用

一、系统栈的工作原理

1.1内存的用途

根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行。但是不管什么样的操作系统、什么样的计算机架构,进程使用的内存都可以按照功能大致分为以下4个部分:

  •  代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行。
  •  数据区:用于存储全局变量等。
  •  堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点。
  •  栈区:用于动态地存储函数之间的关系,以保证被调用函数在返回时恢复到母函数中继续执行。

在Windows平台下,高级语言写出的程序经过编译链接,最终会变成PE文件。当PE文件被装载运行后,就成了所谓的进程。四个区有着各自的功能,在进程运行中缺一不可,大致过程如下:

PE文件代码段中包含的二进制级别的机器代码会被装入内存的**代码区**(.text),处理器将到内存的这个区域一条一条地取出指令和在**数据区**存放的全局变量等操作数,并送入运算逻辑单元进行运算;如果代码中请求开辟动态内存,则会在内存的**堆区**分配一块大小合适的区域返回给代码区的代码使用;当函数调用发生时,函数的调用关系等信息会动态地保存在内存的**栈区**,以供处理器在执行完被调用函数的代码时,返回母函数。

1.2系统栈

栈指的是一种数据结构,是一种先进后出的数据表。内存中的战区实际上指的就是系统栈

栈的最常见操作有两种:

压栈(PUSH)、弹栈(POP)。

用于标识栈的属性也有两个:

栈顶(TOP):push操作时,top增1;pop操作时,top减一。
栈底(BASE):与top正好相反,标识最下面的位置,一般不会变动的。

1.3寄存器与函数栈帧

每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
  • EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部,并非系统栈的底部。

除此之外,还有一个很重要的寄存器。

  • EIP:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。 可以说如果控制了EIP寄存器的内容,就控制了进程——我们让EIP指向哪里,CPU就会去执行哪里的指令。这里不多说EIP的作用,我个人认为王爽老是的汇编里面讲EIP讲的已经是挺好的了~这里不想多写关于EIP的事情。

1.4函数调用约定与相关指令

函数调用大概包括以下几个步骤:

(1)参数入栈:将参数从右向左依次压入系统栈中。

(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。

(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。

(4)栈帧调整:具体包括:   

            <1>保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。

      <2>将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。

      <3>给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶)。

      <4>对于_stdcall调用约定,函数调用时用到的指令序列大致如下:

      push 参数3      ;假设该函数有3个参数,将从右向做依次入栈

      push 参数2

      push 参数1

      call 函数地址   ;call指令将同时完成两项工作:a)向栈中压入当前指令地址的下一个指令地址,即保存返回地址。 b)跳转到所调用函数的入口处。

      push  ebp        ;保存旧栈帧的底部

      mov  ebp,esp     ;设置新栈帧的底部 (栈帧切换)

      sub   esp,xxx     ;设置新栈帧的顶部 (抬高栈顶,为新栈帧开辟空间)

函数返回的步骤如下:
<1>保存返回值,通常将函数的返回值保存在寄存器EAX中。

<2>弹出当前帧,恢复上一个栈帧。具体包括:   

(1)在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。

(2)将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。

(3)将函数返回地址弹给EIP寄存器。

<3>跳转:按照函数返回地址跳回母函数中继续执行。

add esp,xxx     ;降低栈顶,回收当前的栈帧

pop ebp      ;将上一个栈帧底部位置恢复到ebp

retn    ;该指令有两个功能:a)弹出当前栈顶元素,即弹出栈帧中的返回地址,至此,栈帧恢复到上一个栈帧工作完成。b)让处理器跳转到弹出的返回地址,恢复调用前代码区
   

二、栈溢出利用之修改邻接变量

-原理分析

本实验目的:是研究如何通过非法的超长密码去修改buffer的邻接变量authenticated来绕过密码验证。

原理:一般情况下函数的局部变量在栈中一个挨着一个排列,如果这些局部变量中有数组之类的缓冲区,并且程序中存在数组越界的缺陷,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏栈帧中所保存的EBP值、返回地址等重要数据。

实验代码:

#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"
int verify_password(char *password){
    int authenticated;
    char buffer[8];
    authenticated= strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return flag;
}
void main(){
    int valid_flag;
    char password[1024];
    while(1){
        printf("Please input password: ");
        scanf("%s",password);
        valid_flag = verify_password(password);
        if(valid_flag){
            printf("Incorrect password!\n");
        }
        else{
            printf("Congratulations!\n");
            break;
        }
    }
}

通过代码,我们可以想象出代码执行到verify_password时候的栈帧状态,如图所示:

我们分析一下:局部变量authenticated正好位于缓冲区buffer的下方,为int型,占用4字节。因此,如果buffer越界,则buffer[8]——buffer[11]正好写入相邻的authenticated中。同时,通过源码,我们可以发现当authenticated为0时,验证成功;反之则不成功。所以,我们只要做到让越界的ASCII码修改authenticated的值为0,则绕过了密码认证。

-实验步骤

2.1 首先验证程序运行结果,只有正确输入“1234567”才可以通过验证:

2.2假设我们输入的密码为7个“qqqqqqq”,按照字符串的关系大于1234567,strcmp返回1,因此authenticated值为1,通过ollydbg调试的实际内存如图:【0x71是"q"的ASCII码表示】

2.3下面我们试试输入超过7个字符,输入“qqqqqqqqrst”,如图所示,正好从第9个字符开始,开始写入authenticated中,因此authenticated的值为0x00747372:

2.4 我们知道,字符串数据最后都有座位结束标志的NULL(0),当我们尝试输入8个“q”,正好第九个字符0被写入authenticated中,我们看一下:

果然密码验证成功了:

最后,我们可以明白只要输入一个大于1234567的8个字符的字符串,那么隐藏的第九个截断符就能将authenticated覆盖为0,从而绕过验证。

三、栈溢出利用之修改函数返回地址

-原理分析

上一个实验介绍的改写邻接变量的方法似然很管用,但是并不太通用,本节介绍一个相对更通用的办法,修改栈帧最下方的EBP和函数返回地址等栈帧状态值。

下面,我们分析一下本实验的原理:如果继续增加输入的字符,那么超出buffer[8]边界的字符将依次淹没authenticated、前栈帧EBP、返回地址。也就是说,控制好字符串的长度就可以让字符串中相应位置字符的ASCII码覆盖掉这些栈帧状态值。

因此,本实验的目的是:我们通过溢出来覆盖返回地址从而控制程序的执行流程。

我们大致可以得出以下结论:
可以得出以下的结论:

  • 输入11个‘q‘,第9-11个字符连同NULL结束符将authenticated冲刷为0x00717171。
  • 输入15个‘q‘,第9-12个字符将authenticated冲刷为0x71717171;第13-15个字符连同NULL结束符将前栈帧EBP冲刷为0x00717171。
  • 输入19个‘q‘,第9-12字符将authenticated冲刷为0x71717171;第13-16个字符连同NULL结束符将前栈帧EBP冲刷为0x71717171;第17-19个字符连同NULL结束符将返回地址冲刷为0x00717171。

这里用19个字符作为输入,看看淹没返回地址会对程序产生什么影响。出于双字对齐的目的,我们输入的字符串按照"4321"为一个单元进行组织,最后输入的字符串为"4321432143214321432"进行测试,用OD分析如下图所示:

实际的内存状况和我们分析的结论一致,此时的栈状态见下表的内容:

由于键盘输入ASCII码范围有限,所以将代码稍作改动改为从文件读取字符串。源码如下:

#include <stdio.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[8];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);//over flowed here!
    return authenticated;
}
main()
{
    int valid_flag=0;
    char password[1024];
    FILE * fp;
    if(!(fp=fopen("password.txt","rw+")))
    {
        exit(0);
    }
    fscanf(fp,"%s",password);
    valid_flag = verify_password(password);
    if(valid_flag)
    {
        printf("incorrect password!\n");
    }
    else
    {
        printf("Congratulation! You have passed the verification!\n");
    }
    fclose(fp);
}

-实验步骤

3.1 用OD加载可执行文件,通过阅读反汇编代码,可以知道通过验证的程序分支的指令地址为:0x00401028

3.2正常的执行是调用verify_password函数,然后进行比较来决定跳转到错误或正确的分支。如果我们直接把返回地址覆盖为验证通过的地址,而不进入需要比较判断的分支,岂不是可以绕过密码验证了。首先创建一个password.txt的文件,写入5个“4321”后保存到与实验程序同名的目录下,如图:

【buffer[8]需要2个“4321”,authenticated需要一个,EBP需要一个,因此要覆盖返回地址,需要5个“4321”】

3.3保存后,用Ultra_32打开,切换到十六进制编辑模式:

3.4将最后4个字节改为新的返回地址【由于“大端机”的缘故,为了使最终数据为0x00401128,我们需要逆序输入】

3.5切换为文本格式,这时也就验证了为什么我们不再用键盘输入字符串。

3.6将psaaword.txt保存后,用OD重新加载程序并调试,首先可以看到成功绕过密码验证:

3.7我们再回头看一下最终的栈状态:authenticated和EBP被覆盖后均为0x31323334,返回地址被覆盖后为0x00401128(正好为验证成功的地址)

四、栈溢出利用之代码植入

-原理分析

本实验目的:在buffer中植入我们想让他做的代码,然后通过返回地址让程序跳转到系统栈中执行。这样我们就可以让进程去干本来干不了的事情啦!

为了在buffer中植入代码,我们扩充了buffer的容量,来承载我们即将要植入的代码!简单的对代码进行的修改,源码如下:

#include<stdio.h>
#include<windows.h>
#define PASSWORD "1234567"  

int verify_password(char * password)
{
    int authenticated;
    char buffer[44];
    authenticated = strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}  

int main()
{
    int valid_flag = 0;
    char password[1024];
    FILE * fp;
    LoadLibrary("user32.dll");//prepare for messagebox
    if(!(fp = fopen("password.txt", "rw+")))
    {
        exit(0);
    }
    fscanf(fp,"%s",password);
    valid_flag = verify_password(password);
    if(valid_flag)
    {
        printf("incorrect password!\n");
    }
    else
    {
        printf("Congratulation! You have passed the verification!\n");
    }
    fclose(fp);
}  

同样的,我们简单分析一下栈的布局:如果buffer中有44个字符,那么第45个字符null正好覆盖掉authenticated低字节中的1,从而可以突破密码的限制。

-实验步骤

4.1我们仍然以“4321”为一个单元,在password.txt中写入44个字符,如图:

4.2果然通过了验证。

4.3通过OD可以看到,authenticated低字节被覆盖。同时,我们可以知道buffer的起始地址为0x0012FB7C。因此password.txt中的第53-56个字符的ASCII码值将写入栈帧的返回地址中,成为函数返回后执行的指令。

4.4接下来,我们给password.txt植入机器代码。

用汇编语言调用MessageboxA需要3个步骤:


(1)装载动态链接库user32.dll。MessageBoxA是动态链接库user32.dll的导出函数。
(2)在汇编语言中调用这个函数需要获得这个函数的入口地址。【 MessageBoxA的入口参数可以通过user32.dll在系统中加载的基址和MessageBoxA在库中的偏移相加得到。(具体可以使用vc自带工具“Dependency  Walker“获得这些信息) 】
(3)在调用前需要向栈中按从右向左的顺序压入MessageBoxA。
 
  • 通过下图,我们可以得知user32.dll的基地址为0x77D10000,MessageBoxA的偏移地址为0x000407EA,基地址加上偏移地址得到入口地址为0x77D507EA

  • 开始编写函数调用的汇编代码,这里我们可以先把字符串“failwest”压入栈区,写出的汇编代码和指令对应的机器代码如图:

  • 将上述汇编代码一十六进制形式抄入password.txt,,但是要注意!第53~56字节为自己的buffer的起始地址。

4.5程序运行情况如下:

4.6我们可以对压入的字符串进行修改,哈哈,改成自己的学号。

4.7在单击弹框“ok”之后,程序会报错崩溃,因为MessageA调用的代码执行完成后,我们没有写安全退出的代码。

原文地址:https://www.cnblogs.com/0831j/p/9219081.html

时间: 2024-10-08 14:16:08

20155306 白皎 0day漏洞——漏洞利用原理之栈溢出利用的相关文章

20155306 白皎 0day漏洞——漏洞利用原理之GS

20155306 白皎 0day漏洞--漏洞利用原理之GS 一.GS安全编译选项的保护原理 1.1 GS的提出 在第二篇博客(栈溢出利用)中,我们可以通过覆盖函数的返回地址来进行攻击,面对这个重灾区,Windows在VS 7.0(Visual Studio 2003)及以后版本的Visual Studio中默认启动了一个安全编译选项--GS(针对缓冲区溢出时覆盖函数返回地址这一特征),来增加栈溢出的难度. 1.2 GS的工作原理 GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中

20155306 白皎 0day漏洞——漏洞的分析与复现

一.Ubuntu16.04 (CVE-2017-16995) 1.漏洞概述 Ubuntu最新版本16.04存在本地提权漏洞,该漏洞存在于Linux内核带有的eBPF bpf(2)系统调用中,当用户提供恶意BPF程序使eBPF验证器模块产生计算错误,导致任意内存读写问题. 攻击者(普通用户)可以利用该漏洞进行提权攻击,获取root权限,危害极大.该漏洞编号是CVE-2017-16995,在之前的一些老版本已经修复了,但是在最新的Ubuntu版本中,又出现了这个漏洞,并且Twitter爆出了漏洞利用

20155306 白皎 免考实践总结——0day漏洞

本次免考实践提纲及链接 第一部分 基础知识 1.1 0day漏洞概述 1.2二进制文件概述 1.3 必备工具 第二部分 漏洞利用 2.1栈溢出利用 2.1.1 系统栈工作原理 2.1.2 修改邻接变量 2.1.3 修改函数返回地址 2.1.4 代码植入 2.2 DEP 2.2.1 DEP机制的保护原理 2.2.2 linux下利用Ret2Lib绕过DEP 2.2.3 windows下利用Ret2Libc绕过DEP 2.3 GS 2.3.1 GS安全编译选项的保护原理 2.3.2 利用未被保护的内

20155306 白皎 《网络攻防》 EXP8 Web基础

20155306 白皎 <网络攻防> EXP8 Web基础 一.问题回答 - 什么是表单 表单:一般用来收集用户的信息和反馈意见 表单包括两个部分:一部分是HTML源代码用于描述表单(例如,域,标签和用户在页面上看见的按钮),另一部分是脚本或应用程序用于处理提交的信息(如CGI脚本).不使用处理脚本就不能搜集表单数据.表单由文本域.复选框.单选框.菜单.文件地址域.按钮等表单对象组成,所有的部分都包含在一个由标识符标志起来的表单结构中.表单的种类有注册表.留言薄.站点导航条.搜索引擎等. -

20155306 白皎 免考实践总结

本次免考实践提纲及链接 第一部分 基础知识 1.1 0day漏洞概述 1.2二进制文件概述 1.3 必备工具 第二部分 漏洞利用 2.1栈溢出利用 2.1.1 系统栈工作原理 2.1.2 修改邻接变量 2.1.3 修改函数返回地址 2.1.4 代码植入 2.2 DEP 2.2.1 DEP机制的保护原理 2.2.2 linux下利用Ret2Lib绕过DEP 2.2.3 windows下利用Ret2Libc绕过DEP 2.3 GS 2.3.1 GS安全编译选项的保护原理 2.3.2 利用未被保护的内

Struts2漏洞利用原理及OGNL机制

Struts2漏洞利用原理及OGNL机制研究   概述 在MVC开发框架中,数据会在MVC各个模块中进行流转.而这种流转,也就会面临一些困境,就是由于数据在不同MVC层次中表现出不同的形式和状态而造成的: View层-表现为字符串展示 数据在页面上是一个扁平的.不带数据类型的字符串,无论数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来.数据在传递时,任何数据都都被当作字符串或字符串数组来进行. Controller层-表现为java对象 在控制层,数据

独家分析:安卓“Janus”漏洞的产生原理及利用过程

近日,Google在12月发布的安卓系统安全公告中披露了一个名为"Janus"安卓漏洞(漏洞编号:CVE-2017-13156).该漏洞可以让攻击者绕过安卓系统的signature scheme V1签名机制,进而直接对App进行篡改.而且由于安卓系统的其他安全机制也是建立在签名和校验基础之上,该漏洞相当于绕过了安卓系统的整个安全机制. 一旦攻击者将植入恶意代码的仿冒的App投放到安卓商店等第三方应用市场,就可替代原有的App做下载.更新.网友安装这些仿冒App后,不仅会泄露个人账号.

2015306 白皎 《网络攻防》Exp4 恶意代码分析

2015306 白皎 <网络攻防>Exp4 恶意代码分析 netstat [Mac.Linux.Win] sysinteral [MS]:1 2 3 一.系统监控--Windows计划任务schtasks 1.创建计划任务,使系统每5分钟自动检测到哪些有哪些程序在连接我们的网络. 注:任务将创建于当前登录的用户名文件夹下. C:\schtasks /create /TN netstat /sc MINUTE /MO 5 /TR "cmd /c netstat -bn > c:\

经典栈溢出利用详解一例—Notepad++插件CCompletion

标 题: 经典栈溢出利用详解一例-Notepad++插件CCompletion 时 间: 2014-02-23,21:08:51 回顾 上篇文章介绍了Noetpad++程序中的一个插件CCompletion存在的一个因使用不安全的lstrcpyW函数拷贝字符串造成的栈溢出漏洞,并且确定了漏洞的大致利用入口,已经找到了可控EIP数据在整个输入数据中的精确位置,但是如果要写出可以利用的Shell Code还需是需要费一番功夫去调试和修正的.这篇文章就按照前面所说的那个漏洞的利用入口来详细的介绍一个可