黄东升: 栈缓冲区溢出攻击实例

点击上方蓝色字体,关注我们



插一条作(zhēng)者(hūn)简(guǎng)介(gào):


作者简介

黄东升,

西安邮电在读研究生,

酷爱编码,干净帅气,

不打游戏,声音好听,

笑起来像是在发光~ 关键还是单身。


都是傲娇的孩子,

只卖呆萌的价格,

一套煎饼果子就能带走他



1

栈缓冲区溢出概述

    缓冲区溢出是一种历史悠久的攻击手段,在1988爆发的Morris worm就使用缓冲区溢出作为其中一种攻击手段.简单定义下,缓冲区溢出就是利用编程语言不进行数组下标越界进行检查的漏洞,向缓冲区写入越界的数据,破坏缓冲区相邻内存数据的一种手段。

具体而言,缓冲区溢出包含的变种有栈缓冲区溢出,堆缓冲区溢出等。


2

保护措施

    为了保护系统不受缓冲区溢出的攻击,研究人员提出了一系列的保护措施,包括:

    地址空间随机化(ASLR),在每次编译链接时,将程序地址空间中的函数库,代码段,数据段,堆栈的地址随机化。

    栈不可执行(NX),将栈的页描述符的X位设置为0, 防止CPU执行攻击者向栈中注入的代码。

    金丝雀(canary),在栈底写入一个魔数,当攻击者实施栈溢出时,魔数就会被覆盖,系统就会检测到该程序已经受到了攻击。

3

x86_64函数调用惯例及其栈帧

    为了向栈中注入我们自己的代码,首先需要了解的是函数调用时的栈的变化。需要提前说明的是,x86是小端结构,栈的起点位于高地址。 
    写一段简单的代码:


gcc call.c -o call 
objdump -d call

编译之后,让我们看看可执行文件的情况。 

图3-1 

下图是main函数和maxn函数的栈帧变化示意图 
图3-2

下图是简化后的函数的栈的结构 

4

shellcode的编写

    shellcode就是我们注入到目标栈中的代码,shellcode的功能是使用execve函数得到一个shell

1

x86-64的系统调用


从下图可见,x86_64架构取消了传统的中断形式的系统调用,使用syscall指令实现系统调用。并且存放参数的寄存器也有所变化。execve的系统调用号也从0xb变为了0x3b

图4-1 32bit 和 64bit 系统调用对比图

[object Object]
2

代码实例


为了提高得到shell的成功率,我们使用内联汇编完成,代码并不难理解,需要说明的是有三点:

  1. 标号here和call指令的使用保证程序处于循环之中,不会自动退出

  2. pop %rdi 将execve的参数 "/bin/sh" 的地址传入rdi

  3. 在执行syscall之前需要使用xor rax, rax将rax寄存器清空

  4. call指令将下一条指令的地址入栈保存,这样参数 "/bin/sh" 的地址也就保存在了栈中

运行命令 
gcc rop.c 
编译完成后,得到可执行文件a.out 
运行之后,可以看到shell成功运行 
图4-2 

3

截取shellcode

图4-3

得到的可执行文件包含很多段,我们需要截取出需要的部分 
由上图可以看到main函数的起点是0x4004d6, retq指令的地址为0x4004fe,4fe - 4d6 = 28, 所以我们将截取的大小设置为32个字节(8的整数倍) 
使用xxd工具完成截取操作, 
xxd -s0x4d6 -l32 -p a.out > shellcode 
使用-p参数输出单纯的16进制数据 
图4-4 




5

victim

先看一段有漏洞的代码,缓冲区大小为32个字节。

接下来我们需要逐步关闭一些保护机制

1. 关闭金丝雀机制

gcc -fno-stack-protector -o victim victim.c

2. 关闭栈不可执行机制

execstack -s victim

3. 关闭地址空间随机化

setarch arch -R ./victim

运行多次我们会发现缓冲区的地址并不会改变

6

向栈中注入数据

接下来到了最关键的一步,向目标缓冲区注入数据。有两个问题:

  1. 注入数据的内容

  2. 注入数据的长度

1

注入数据的内容


最终的目的是改变进程的执行路径,让其执行我们构造好的shellcode。由图4-3可得main函数的最后一条指令是retq指令,该指令的行为是将栈顶所保存的返回地址赋给rip, 恢复进程的原执行路径。 
只要我们将返回地址修改为shellcode的地址,当main函数执行retq指令后,就可以让进程执行shellcode。

所以数据的内容应该是 shellcode + 填充字节 + shellcode的地址(从低地址到高地址)

图6-1 


2

注入数据的长度


缓冲区是32字节,保存的栈底指针rbp是8个字节,返回地址是8个字节,也就是32 + 8 + 8.而shellcode 是32个字节,所以为了覆盖整个栈帧,填充字节应该是8(8个字节的rbp),最后是8个字节的shellcode的地址.也就是32 + 8+ 8.

3

开始获取shell

由于x86是小端,所以我们需要颠倒缓冲区地址的次序(使用tac命令) 
得到缓冲区地址 
addr=$(echo | setarch arch -R ./victim) 
a=printf %16x $addr | tac -rs.. 
图6-2


cat shellcode ; printf %016d 0 ;

echo $a | xxd -r -p ;

cat | setarch arch -R ./victim 
注入后的栈结构: 

运行上述命令后,可以看到成功得到了一个shell。

7

总结

其实,由于地址空间随机化和栈不可执行等保护措施,常规的栈缓冲区溢出攻击已经失效.后来为了应对栈不可执行机制,出现了ret2lib 攻击,在ret2lib中我们不再向栈中注入代码,而是利用libc中的系统调用system来获取shell.随后又出现了ROP(面向返回编程). 

附上一个脚本

> https://github.com/DennisWong/Hack.git


参考

http://crypto.stanford.edu/~blynn/rop/   
https://blog.csdn.net/mikayong/article/details/52057818 解释了为什么在shellcode中要使用call指令 
https://en.wikipedia.org/wiki/Buffer_overflow


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值