SEED-Labs 缓冲区溢出
Task1 魔改shellcode
就是把
1 | /bin/ls -l; echo Hello 32; /bin/tail -n 2 /etc/passwd * |
改成带有删除的,如删除tmpfile。
1 | /bin/rm -f tmpfile; echo Hello 32; * |
然后试一下就行了,新建tmpfile,然后删除。
1 | touch tmpfile |
make是把两个file(shellcode32和shellcode64)都make。
1 | gcc -m32 -z execstack -o a32.out call_shellcode.c |
然后直接开始运行出来的a32.out。
1 | a32.out |
发现文件删了,echo也echo了。
1 | Hello 32 |
Task2 干掉第一台机子
首先把反制措施关了
1 | sudo /sbin/sysctl -w kernel.randomize_va_space=0 |
启动docker,nc发个东西过去看看
1 | echo aminoac | nc 10.9.0.5 9090 |
返回了结果,显示8位内容。
1 | server-1-10.9.0.5 | Got a connection from 10.9.0.1 |
发现ebp指针在0xffffd158,Buffer’s address inside bof()为0xffffd0e8(buffer数组开始的地方)
尝试把之前的shellcode用在其提供的程序骨架exploit.py上。然后改一下ret的地址。
1 | #!/usr/bin/python3 |
其中,ret=0xffffd158 + 8,(最小值为ebp + 8,最大值为ebp + 517 - len(code))解释如下:
首先,分配了517byte的空间。
为什么是517呢?因为在server的源代码中,stack.c的代码中为我们留了517大小个char的空间
1 | int main(int argc, char **argv) |
然后这个时候栈大概是这个样子
由于shellcode的长度为136,大于112,所以不能把shellcode放到ebp-xxx的空间中,太大了塞不下。而这个ebp+xxx的空间,其地址为(ebp + 8, ebp + 517),所以我们shellcode起始地址就得是(ebp + 8, ebp + 517 - len(code))。
shellcode的长度可以简单地写个脚本来测试:
1 | shellcode= ( |
然后,将payload填满NOP。NOP的作用是让PC+1,换句话说就是空拍子。
1 | content = bytearray(0x90 for i in range(517)) |
然后,把shellcode放在这个巨大NOP段payload的某处。建议放在比ret高的那一段,不然放不下。
1 | # Put the shellcode somewhere in the payload |
我们这里把shellcode放到尾部,即
1 | start = 517-len(shellcode) # Change this number |
然后,定位这个ret,让返回地址变成我们shellcode的地址。
1 | # Decide the return address value |
为什么是+8呢?因为我们要指到返回地址,其中差了两条指令
为什么offset是ebp地址减去buffer地址加4?因为ebp地址减去buffer地址就是buffer长度,我们需要定位返回地址,就需要首先在517个长度大小的payload中填充满buffer大小的NOP,此时,再下一个填充的地址即为ebp所指的地址。我们再+4(再向前指一个),那么下一个填充的地址就是ret。
事实上,offset指payload中,和ret重合的位置,这里储存着跳转到shellcode的地址,这样函数在结束调用时,就会跳转到shellcode。
然后,把ret地址塞到content里。这步代码是在payload中插入ret。
1 | # Use 4 for 32-bit address and 8 for 64-bit address |
nc开启本地监听
1 | nc -lvnp 9999 |
直接运行python文件生成payload,然后用nc提交上去,然后本地监听就收到shell啦
1 | python3 exploit.py |
whoami一下,居然是root权限,真实厉害。
Task3 干掉第二台机子
netcat传点东西过去,发现只返回buffer地址了。
1 | server-2-10.9.0.6 | Got a connection from 10.9.0.1 |
翻看实验手册发现buffer大小在[100,300],而且不让用爆破(既把buffer大小全试一遍),那么就直接把所有的返回地址全部刷成shellcode地址,那它的返回无论是多少都能是shellcode(跳转地址覆盖了ebp和ebp+4)
1 | ret = 0xffffd208 + 308 # Change this number |
因为buffer大小在100到300,那么ebp+4就位于104到308之间。可以直接把这块全换成跳转的地址。
生成payload后发送到服务器,本地监听收到了shell。
whoami一下,居然直接是root权限,真实厉害。
Task4 干掉第三台机子
netcat传点东西过去,发现变成了64位。
1 | server-3-10.9.0.7 | Got a connection from 10.9.0.1 |
那之前用于32位脚本的东西就用不了了。直接把shellcode_64.py里的shellcode移到exploit.py里,然后安上反弹shell。
1 | shellcode = ( |
脚本跑一下,这个shellcode长度165。
手册提到,strcpy()遇到0x00会停止复制(因其也为字符串结束标志),那么就要解决64位机子的地址前面有0x00的问题。那么就可以将返回地址放到最前面。
为什么可以放到最前面?因为shellcode长度165,而缓冲区长度为0x00007fffffffe200-0x00007fffffffe130,长度为208,已经超过了shellcode的长度,所以我们可以把它放到最前面,放到缓冲区中。
那么我们就可以改代码了。改成这样就行了。
1 | # Put the shellcode somewhere in the payload |
为什么ret的地址为缓冲区地址?因为payload的一开始就是shellcode(content[start:start+len(shellcode)] = shellcode),其地址就为缓冲区刚开始的地址,我们需要把跳转的地址跳转到这里。
为什么offset变成了+8?因为是64位的机子,同理offset变为了+8、ret地址也为8byte。(0x00|00|7f|ff|ff|ff|e1|30)
生成payload后发送到服务器,本地监听收到了shell。
whoami一下,居然是root权限,真实厉害。
Task5 干掉第四台机子
netcat传点东西过去,发现不仅是64位,长度居然还只有0x00007fffffffe320-0x00007fffffffe2c0=96byte。
1 | server-4-10.9.0.8 | Got a connection from 10.9.0.1 |
将 ret 的值设为rbp+n,在这个案例里,n应该可以被设为[1184, 1424]?其目的是越过缓冲区,写入主函数栈和bof()函数栈之间的区域
为什么可以这样设置?看stack.c,源码中存在这样一个“dummy function”。其在主函数栈和bof()栈之间插入了1000个char的空间。
那么,我们的shellcode就可以放在这段空间中,所以要求返回地址大于缓冲区。
但是按照栈大小和形参实参的储存来计算,可能不是1184。也许是gdb调试的。
1 | // This function is used to insert a stack frame of size |
将 ret 的值设为rbp+1200,并且把payload放到shellcode最后面。
1 | # Put the shellcode somewhere in the payload |
生成payload后发送到服务器,本地监听收到了shell。
whoami一下,居然是root权限,真实厉害。
Task6 干掉地址随机化机子
打开地址随机化
1 | sudo /sbin/sysctl -w kernel.randomize_va_space=2 |
现在第一台机子地址开始随机了,每次都不一样。生成一个lab1的payload直接用实验报告给的脚本爆破即可,总能随机到payload的地址。
1 | !/bin/bash |
读一下这个脚本,就是无限循环提交payload碰运气。
爆破几万次,就拿到shell了,而且是root权限。
1 | 1 minutes and 48 seconds elapsed. |
Task7 干掉有其他防御措施的机子
7.1 StackGuard Protection
重新编译一下stack.c,直接改makefile,去掉–fno-stack-protector的flag就行了。
1 | ./stack-L1 < badfile |
重新编译完了,检测到stack smashing,直接退出了。
1 | Input size: 517 |
7.1 Non-executable Stack Protection
在 Ubuntu 操作系统中,二进制程序(和共享库)的二进制映像必须声明它们是否需要可执行堆栈。也就是说,它们需要在程序头中标记一个字段。这种标记在默认情况下是由gcc自动完成的,而一般标注堆栈是不可执行的。
重新编译call_shellcode.c,这个时候去除 -z noexecstack 的 flag。然后尝试编译输出。
1 | Segmentation fault |
果然就不行了。