20145236《网络对抗》进阶实验——Return-to-libc攻击
基础知识
-
Return-into-libc
攻击方式不具有同时写和执行的行为模式,因为其不需要注入新的恶意代码,取而代之的是重用漏洞程序中已有的函数完成攻击,让漏洞程序跳转到已有的代码序列(比如库函数的代码序列)。攻击者在实施攻击时仍然可以用恶意代码的地址(比如libc
库中的system()
函数等)来覆盖程序函数调用的返回地址,并传递重新设定好的参数使其能够按攻击者的期望运行。这就是为什么攻击者会采用return-into-libc
的方式,并使用程序提供的库函数。这种攻击方式在实现攻击的同时,也避开了数据执行保护策略中对攻击代码的注入和执行进行的防护。 -
攻击者可以利用栈中的内容实施
return-into-libc
攻击。这是因为攻击者能够通过缓冲区溢出改写返回地址为一个库函数的地址,并且将此库函数执行时的参数也重新写入栈中。这样当函数调用时获取的是攻击者设定好的参数值,并且结束后返回时就会返回到库函数而不是main()
。而此库函数实际上就帮助攻击者执行了其恶意行为。更复杂的攻击还可以通过return-into-libc
的调用链(一系列库函数的连续调用)来完成。
实验步骤
一、配置实验环境
- 在自己的虚拟机上输入命令安装一些用于编译32位C程序的东西,输入命令
linux32
进入32位linux环境,输入/bin/bash
使用bash
。
sudo apt-get update
sudo apt-get install lib32z1 libc6-dev-i386
sudo apt-get install lib32readline-gplv2-devsudo apt-get install lib32readline-gplv2-dev
- 这个攻击最终的目标是要获取root权限,因此在做之前,我先另外添加了一个用户:
- 进入32位linux环境,将地址随机化关闭
sudo sysctl -w kernel.randomize_va_space=0
,并且把/bin/sh指向zsh:
二、漏洞程序
-
将漏洞程序保存在/tmp目录下,编译该程序:
-
编译该代码,使用
–fno-stack-protector
来关闭阻止缓冲区溢出的栈保护机制,并设置给该程序的所有者以suid权限,可以像root用户一样操作:
三、在/tmp文件夹下编写getenvaddr和exploit
getenvaddr
用来读取环境变量:
- 将攻击程序"exploit.c"保存在/tmp目录下:
- 在root用户下编译:
- 用刚才的
getenvaddr
程序获得BIN_SH
地址:
- 利用gdb获得
system
和exit
地址:
- 将上述三个地址修改入"exploit.c"文件:
- 删除刚才调试编译的
exploit
程序和badfile
文件,重新编译修改后的"exploit.c"文件:(由于exploit
文件受保护无法用命令删除,所以在图形化界面tmp文件夹下直接手动删除)
四、攻击
- 先运行攻击程序
exploit
,再运行漏洞程序retlib
,攻击成功,获得了root权限:
五、深入思考
将/bin/sh 重新指向/bin/bash(或/bin/dash),观察能否攻击成功,能否获得 root 权限。
-
首先,我们将/bin/sh重新指向/bin/bash,运行漏洞程序:
-
发现无法获取root权限,因为bash内置了权限降低的机制,虽然我们可以使得bof返回时执行system(“/bin/sh”),但是我们也依旧获取不到root权限。
-
如果我们要想在/bin/sh指向/bin/bash时获取root权限,那我们就要想办法在调用/bin/bash之前提升正在运行的进程的Set-UID至root权限,那么我们就可以绕过对bash的权限限制。
-
之后在查资料时发现了Linux下一个setuid()函数,查看其帮助文档:
-
从帮助文档中可以得知setuid()用来重新设置执行目前进程的用户识别码,不过,要让此函数有作用,其有效的用户识别码必须为0(root)。在Linux 下,当root 使用setuid()来变换成其他用户识别码时,root 权限会被抛弃,完全转换成该用户身份,也就是说,该进程往后将不再具有可setuid()的权利。
-
因此我们可以利用函数setuid(0)来实现我们的目标,在调用系统函数system(“/bin/sh”)之前,先调用系统函数setuid(0)来提升权限。
-
我们先要对攻击程序进行修改,在bof的返回地址处(&buf[24])写入setuid()的地址,setuid的地址同样可以通过gdb来获得:
-
setuid的参数0写在与其入口地址相隔一个字节的位置(即buf[32])处(因为setuid()执行完毕之后,会转向存放setuid入口地址的下一个位置,所以这个位置应该放入system函数的入口地址),同理system的参数放入&buf[36]处。
-
修改完成后,编译运行,发现攻击成功!