环境准备
安装好提供的环境,然后执行如下操作:
sudo sysctl -w kernel.randomize_va_space=0 # 关闭地址随机化 |
StackGuard and Non-Executable Stack
Getting Familiar with Shellcode
C 语言版的 shellcode
|
但是我们不能够拿这个代码编译生成的二进制就当作了 shellcode
。
32 位的汇编版
; Store the command on stack |
64 位的汇编版
xor rdx, rdx ; rdx = 0: execve()’s 3rd argument |
Task: Invoking the Shellcode
|
我们得到两个版本的 shellcode
(shellcode
如何编写不是这个实验的重点),这个程序就是将这段 shellcode
当作代码段运行。
all: |
-m32
表示编译生成 32 位程序,-z execstack
表示运行代码在栈上运行。执行 make setuid
命令后我们可以得到两个可执行程序。分别执行之:
可以看到,我们拿到了 root
权限的 shell
进程。
Task 2: Understanding the Vulnerable Program
我们要攻击的代码总体如下:
int bof(char *str) |
main 函数在调用 bof
还调用了 dummy_function
,我认为最主要的作用就是为 bof
和 main
之间留出足够的可插入 shellcode
的空间。
我们将代码编译生成 L1
到 L4
这 4 个版本,前两个版本是 32 位,后两个版本是 64 位,并且每个版本都有普通版和 debug
版。再将普通版设为 Setuid
程序。其中 L1
的 BUF_SIZE
是 100,L2
的 BUF_SIZE
是 100 到 200 之间,L3
的 BUF_SIZE
是 200,L4
的 BUF_SIZE
是 10。
Launching Attack on 32-bit Program (Level 1)
我们首先调试 stack-L1-dbg
得到一些数据:
gdb-peda$ p &buffer |
我们可以看到 $ebp=0xffffcb58
,这也就说明返回地址存储在 0xffffcb5c
这个位置,相对于 &buffer 偏移了 108 + 4 也就是 112 这个位置。我们需要将这个位置修改,改成我们希望跳转执行的位置。
# Replace the content with the actual shellcode |
我们把上面 32 位的 shellcode
复制过来,然后 ret
就是要返回跳转的位置,我们设为 0xffffcaec + start
也就是 &buffer
偏移 start
的位置,我们希望代码从 &buffer
开始执行。并且把 shellcode
插入到 &buffer
偏移 start
的位置(content
会被复制到 buffer
)。
下面依次是 start
= 0,200,300,400 的结果:
等于 0 表示我们把 shellcode
放在 bof
函数栈内,可能是在函数退出之后,栈空间会被回收,因此导致了错误。等于 200 出现问题我感觉到匪夷所思。我调试了一下,在 debug
模式下可以工作,因此可能是普通模式没有 gdb
信息时这个地址在两个栈中间,影响到了重要的硬件指令。后面两个已经到 dummy_function
栈内了,因此可以正常工作。
Task 4: Launching Attack without Knowing Buffer Size (Level 2)
在这个任务下,我们不知道 buffer
的大小,只知道是 100 到 200。但是,我们还是可以使用 gdb
得到 &buffer
的地址,只是不能得到 $ebp
的地址罢了。我们先把 ret
的值修改为 &buffer
,然后把函数每个可能的 ret
位置都修改为我们的 ret
,并且将 start
设为 400 或者 517 - len(shellcode)
。
for offset in range(0, 288, L): |
根据我的测试,第二个数最大就是 288 了,292 就会抛出错误,可能是把某个重要指令被无意义数据给覆盖了。
Task 5: Launching Attack on 64-bit Program (Level 3)
64 位地址长这个样,0x00007FFFFFFFFFFF
也就是地址中有 0,在执行 strcpy(buffer, str);
的时候遇到 0 就停止了, 0 后面的内容不会被拷贝。这也就意味着 shellcode
不能放在 ret
的后面,只能放在 ret
的前面。并且地址还是小端序,ret
的低位非 0 会先拷贝,高位 0 不拷贝,但是之前的地址高位也是 0,所以没关系。
使用 GDB 调试得到数据如下:
gdb-peda$ p $rbp |
我们需要在修改 exploit.py
如下,将 shellcode
修改为 64 位的,然后将 start
设为 0(把 shellcode
放在 ret 前面),然后把 ret
的值设为 &buffer
:
#!/usr/bin/python3 |
执行结果如下,折磨一天了,我是没什么办法了:
今天看到一个博主的文章,发现他填的参数很奇怪,又试了一次,诶嘿,居然通过了(使用的参数为 start = 96, ret = 0x7fffffffd850 + 160):
我将 attack.c 添加了一条 printf,输出 buffer 的地址,并且我还在 gdb 调试 stack-L3-dbg 时打印了 buffer 的地址:
$ ./stack-L3 |
可以看到地址是各不相同的,0x7fffffffd850 + 160 就是 0x7fffffffd8f0,也就是 buffer 在 stack-L3 的地址首位。其实 32 位也会出现这种情况,所以有些我以为能够运行的结果无法运行,就是因为实际运行和调试出来的地址有点区别。我一直想着不能动 stack.c 函数,其实改一下代码就会发现问题所在了,白白浪费了很多时间。
就是实际运行和调试之间会有一定的地址偏移,因此我们需要调整一下我们的数据。
Task 6: Launching Attack on 64-bit Program (Level 4)
这里我们的缓冲区很小,存不下 shellcode,但是我们还有一个源头啊,就是 main 函数中的 str 有 shellcode,我们可以使得函数执行 main 中的 shellcode。
gdb-peda$ p $rbp |
然后按照这个思路修改 exploit.py
如下:
start = 517 - len(shellcode) # 放最后面,容错率高一点 |
这样就可以了,这个适当偏移很有玄学的概念,我们可以使用一个脚本来穷举:
ret = 0x7fffffffdd80 + int(sys.argv[1]) # &input 的地址并适当偏移 |
脚本如下:
!/bin/bash |
结果还是比较奇妙的(不懂为什么 200 可以,中间的一些数又不可以,然后后面又可以):
Tasks 7: Defeating dash’s Countermeasure
在 ubuntu 中, dash shell 检测到有效的 UID 和真实 UID 不相等就会放弃特权。前面我们将 sh 链接到 zsh 来解决的这个问题,现在我们来尝试新的对策。首先链接回原来的:
sudo ln -sf /bin/dash /bin/sh |
在调用 execve() 之前将真实 ID 修改为 0 即可,也就是调用 setuid(0)。我们把 call_shellcode.c 中注释掉的二进制代码加入到 shellcode 的开头就行了。
重复 Level1,可以看到,在 sh 链接到 dash 的情况下我们拿到了 root 权限:
Task 8: Defeating Address Randomization
在 32 位系统下,栈只有 19 位熵,我们可以穷举破解它,首先,打开地址随机化:
sudo /sbin/sysctl -w kernel.randomize_va_space=2 |
然后编写一个脚本,不断的执行攻击,然后就是碰运气了:
|
Tasks 9: Experimenting with Other Countermeasures
Turn on the StackGuard Protection
和 Turn on the Non-executable Stack Protection
,打开这两个之后,攻击肯定是成功不了的。
- 栈保护机制(StackGuard Protection),
gcc
编译器实现的安全机制,阻止缓冲区溢出漏洞。你可以关闭这种机制,通过在编译时使用-fno-stack-protector
选项。 - 栈不可执行(Non-Executable Stack),
ubuntu
默认栈不可执行,可以通过在编译的时候使用-z execstack
选项来使堆栈可执行。
在只需要在栈上执行我们的数据的情况下,可以不用关闭 StackGuard Protection
,比如 call_shellcode.c
在编译的时候就没有 -fno-stack-protector
,因为不需要溢出。但是栈上可执行都是要的吧。