堆栈溢出初体验

环境配置

  • Kali Linux 4.8.0 (基于Debian 4.8.5),64位
  • GCC 6.3.0 20170425
  • GDB 7.11.1
  • GDBGUI 网页前端 用于逃离gdb可怕的键盘操作

准备工作

源码

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

int hijack() //待注入的函数
{
    printf("hijack!\n");
    return 0;
}

int main(int argc, char* argv[])
{
    char buf[256];

    printf("buf at %p\n", buf);
    strcpy(buf, argv[1]); //通过这里的溢出修改返回地址
    printf("Input: %s\n", buf);

    return 0;
}

编译选项

由于现在的OS和编译器对缓冲区溢出有了一定的防范,所以为了实验最基本的缓冲区溢出,必须关掉一些选项,包括:
关闭OS的虚拟地址随机化,默认值是2
#echo 0 > /proc/sys/kernel/randomize_va_space 调整编译选项
gcc -g -fno-stack-protector -z execstack -o book book.c

搭GDBGUI环境

听闻gdb的大名许久,但限于对纯命令行调试的恐惧,一直不敢有所亵玩。为了实验缓冲区溢出,看着各方大神都是在Linux用gdb找线索,不得不操起gdb大法,尝试着走两步。
首先用gdb book,打开要调试的二进制文件book,黑糊糊的一大坨。

GDB默认使用AT&T的汇编方式展示,如果不喜欢的话可以改成Intel模式
(gdb) set disassembly-flavor intel 对于只有一片屏幕感觉读起来很孤独的话,可以用

(gdb) layout asm
(gdb) layout regs

开启更多分屏,但空间随之被压缩。由于刷新的问题屏幕可能乱七八糟,此时用Ctrl + L重新刷新即可。
要调试起来的话一般是用b(reak) main在main中设置一个断点,然后用r(un)运行程序,到了断点的地方用c(ontinue)s(tep)in(ext)i来单步调试。注意sini对应汇编代码的下一行,如果是直接调C程序则用sn即可。
要在某一行加断点使用b *0xfffffff7c,而删除断点使用d(elete) n,n对应断点编号。
所以GDB高手调起程序来是这个状态:


然而这样调有一个致命的缺点是,无法和C程序对照,需要切窗口,而且还得记命令(不知道上面的命令我google了多久才用起来...),于是有人站出来造福人类。
#pip3 install gdbgui --upgrade
用gdbgui之后,至少ni这个需要不断敲的指令可以用鼠标点击替代了。

溢出实验

reference

其实讲基本溢出的攻击很多,但第一个属于手把手,讲的比较细,第二个是少数几个讲64位OS溢出的基础实验。由于64位OS的通用寄存器与32位的不同,因此作为上来一不小心选了64位作为平台的小白,歪果仁讲的更明白(相对而言)。

原理

在本文中,最基础的溢出原理就是使用strcpy(dst,src)`时,由于不限定复制的长度,如果使用一个长度大于dst的src来运行此操作时,会出现dst后面的数据被复写。而如果dst后面的数据是堆栈中的重要寄存器值,比如返回地址(strcpy函数总是要返回给上级函数的嘛),那么经过巧妙构建的溢出数据,就可以修改程序的走向,引导到自己设定的邪恶的圈套中。

实现

从源代码中可以看到设计的溢出数据是通过argv[1]从外部传进来的。那么为了制造一个缓冲区溢出,首先用r $(python -c 'print "A"*300')来构造一个看看效果(python脚本惊为天人)。


当运行到0x5555555547cb ret main+116时,即leave完成后,可以看到寄存器恰好被改写


而运行0x5555555547cb ret main+116时,会出现segmentation fault,说明返回地址错了。
为了精确的了解返回时的堆栈位置,重新运行一边,停在0x5555555547ca leave main+115会看到rsp指向的位置是0x7fffffffe0b0,而“A”所对应的地址是0x7fffffffe0c0,也就是说此时传入参数的位置在0x7fffffffe0c0

ni一下,当程序停在0x5555555547cb ret main+116时,rsp指向0x7fffffffe1c8

二者地址相减,e1c8-e0c0 = 0x108 = 264,于是获知,合法的缓冲区长度为264(长于256的数组长度,多的8字节按照<典型的基于堆栈的缓冲区溢出>的图,或许是argc的长度),于是在264字节后补充需要跳转的地址。在本例中,通过disassemble hijack获得hijack函数的入口地址是0x0000555555554740,所以修改函数输入为r $(python -c 'print "A"*264+"\x55\x55\x55\x55\x47\x40"[::-1]')([::-1]意为反序,由于小端存储的原因),输出结果令人满意:

并未正常调用的函数所打印的“hijack”如约出现。


更多的暗黑操作,是通过构建/bin/bash的字符串,由程序中自带的system()触发。之后还要研究如果开启了ASLR或其他缓解措施(比如va random或编译选项)是,如何溢出堆栈。路还很长哪~

Comments
Write a Comment
'