37

缓冲区溢出之Strcpy和Memcpy

 4 years ago
source link: https://www.freebuf.com/articles/others-articles/230731.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

问题:定义三个函数fu’n1,fun2,fun3,不使用嵌入式汇编调用和函数调用,仅仅字符串的操作按顺序调用他们。

这个是今天老师抛出来的一个问题,似乎有着似曾相识的感觉。想到之前老师用strcpy()溢出实现过三个函数的调用,折回去看了一下之前的思路,然后按照题意进行分析。

方法一:strcpy()函数:易发生\x00截断

strcpy()的文章请查看: Strcpy()函数之缓冲区溢出

1、strcpy溢出原理简述

以下为strcpy()函数溢出的示意图:

即如果将长度较大的值 b 赋值给 较短的值 a(b>a),那么strcpy之后,b多出来将一部分将会覆盖原来的内存单元。当溢出的值刚好覆盖了 函数结束后 ret 返回的地址时,那么函数 ret后将会执行 溢出的值。

拓展:ret函数原理如下:即将栈顶的元素赋给eip,当作下一步执行的地址。

pop eip

zQzuMb7.jpg!web

由于我们需要知道函数结束后ret执行的位置。即要知道当执行ret时,程序esp的值,然后在之前将需要的地址准确的覆盖到这个 esp处。

2、代码示例及分析

原理讲了这么多,接下来我们用代码来演示一下吧。

代码作用:执行fun1()函数

char *shellcode1="\x64\x65\x66\x67\x68\x69\x70\x71\x0F\x10\x40\x00";

void fun1()
{
printf("fun1 run!\n");
}


int main(int argc, char* argv[])//
{
    printf("begin\n");
char a[4]={0};
    strcpy(a,shellcode1);
return 0;
}

在开始执行main()函数时,如下图,我们发现 esp = 0x0012ff84 ;因此在main()函数正常执行结束后,执行最后一步 ret指令时 esp也是等于 0x12ff84(因为需要堆栈平衡)。因次我们需要执行的程序应该通过溢出而到达 0x12ff84 的位置。

fq6vu2i.jpg!web

以下是main函数执行结束后,执行ret指令时 esp的值。与我们才猜想的一样。

vu67vq6.jpg!web

接下来我们应该将shellcode的地址通过溢出而放在 0x12ff84 的位置。

通过对shellcode的不断尝试,(可令shellcdoe=”abcdefghij……”的16进制代码),然后观察内存单元中的16进制码,从而确定 在 0x12ff84的字符,然后将字符换成 shellcode的地址,便可在 ret 时进行准确的跳转了。在上图中,我么可以发现  在字符 “rstu”  的位置在 0x12ff84,由于我们需要执行的是fun()函数,因此可以将fun()函数跳转地址存放在  “rstu” 字符的位置。

NvyAziJ.jpg!web

如下我们可以发现,在程序中又一个地址专门存放着 jmp 跳转到相应 fun()函数语句的地方。因此,我们可以将相关函数的  jmp 跳转语句的地址放在 上面的字符处。

BjyMJn6.jpg!web

3、shellcode的构造

如下,是我们构造的shellcode:

char *shellcode1 = "\x64\x65\x66\x67\x68\x69\x70\x71“
                  ”\x0F\x10\x40\x00";//jmp fun1的地址。

以上是将fun1()函数的jmp语句放入栈中,那么其他函数呢?

错误示例:将其余的地址放在shellcode1后面。如下,为构造好的shellcode。

char *shellcode1 =  "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x0F\x10\x40\x00"  //jmp fun1的地址
                    ”\x05\x10\x40\x00"  //jmp fun2的地址
                    ”\x0A\x10\x40\x00";//jmp fun3的地址

这个是错误的,因为 strcpy()函数在遇到 \x00 字符时将会截断,因此后面的 jmp fun2和 fun3的 跳转语句将无法入栈。(不过在此可以通过xor异或加密,然后解密执行,可以绕过 00截断哦!)

4、最终代码

以下是正确的代码:

可以发现,其实这个方法就是将 strcpy()函数重复使用,从而达到 执行 fun()函数的目的。

#include "stdafx.h"
#include "string.h"
//char *shellcode="abcd"; //不断地尝试abc...可以用来测试定位,哈哈,不过这个是一种笨方法啦
char *shellcode1 = "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x0F\x10\x40\x00";//jmp fun1()
char *shellcode2 = "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x05\x10\x40\x00";//jmp fun2()
char *shellcode3 = "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x0A\x10\x40\x00";//jmp func3()
char *shellcode4 = "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x49\x14\x40\x00"; //防止报错
/***
\x49\x14\x40\x00地址:这个是在函数正常执行时 ret 跳转的地址。欺骗程序正常执行完成,然后终止程序。
***/
 
void fun1()
{
    char a1[4]={0}; 
    printf("fun1 run!\n");
    strcpy(a1,shellcode2); 
}
void fun2()
{
    char a2[4]={0}; 
    printf("fun2 run!\n");
    strcpy(a2,shellcode3);
}
void fun3()
{
    char a3[4]={0};
    printf("fun3 run!\n");
    strcpy(a3,shellcode4);
}
int main(int argc, char* argv[])
{
    printf("begin\n");
    char a[4]={0};
    strcpy(a,shellcode1);
    return 0;
}

方法二:memcpy函数溢出

1、memcpy函数分析

首先介绍一下memcpy()函数。

参考链接: https://blog.csdn.net/qq_28351609/article/details/84704531

extern void *memcpy(void *dest, void *src, unsigned int count);

用法:#include <string.h>

功能:由src所指内存区域复制count个字节到dest所指内存区域。

说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针

注意:

 1.source和destin所指内存区域不能重叠,函数返回指向destin的指针
 2.与strcpy相比,memcpy并不是遇到’\0’就结束,而是一定会拷贝完n个字节。

memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;

memcpy(a,b,n):将b中的n个字符拷贝到a处。但是如果  n>a将会发生溢出。相较于 strcpy() 函数,memcpy函数遇到  \x00 将会继续复制,不发生 00 截断。

如下,memcpy()执行后将会发生溢出。

char a[100], b[50];

memcpy(b, a,sizeof(b)); //注意如用sizeof(a),会造成b的内存地址溢出

2、构造payload 

memcpy()函数在这里的使用实质上就是上面 strcpy()的错误示例,但是由于 strcpy有 \x00 截断,而这个没有,所以便可以构造如下payload:

char shellcode[] =  "\x64\x65\x66\x67\x68\x69\x70\x71“
                    ”\x05\x10\x40\x00“  //jmp fun1
                    ”\x14\x10\x40\x00“  //jmp fun2
                    ”\x0F\x10\x40\x00“  //jmp fun3
                    ”\x49\x14\x40\x00"; //防止报错

3、最终代码

#include "stdafx.h"
#include "string.h"

char shellcode[] =  "\x64\x65\x66\x67\x68\x69\x70\x71“
		    ”\x05\x10\x40\x00“
		    ”\x14\x10\x40\x00“
		    ”\x0F\x10\x40\x00“
		    ”\x49\x14\x40\x00";
 
void fun1()
{
   printf("fun1 run!\n");
}
void fun2()
{
   printf("fun2 run!\n");
}
void fun3()
{
  printf("fun3 run!\n");
}

//主函数
int main(int argc, char* argv[])
{
    printf("begin\n");
	char a[4]={0};
	//溢出原理:  shellcode长度 > a长度
	memcpy(a, shellcode,sizeof(shellcode));

    return 0;
}

*本文作者:Plutol,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK