only rewrite .dynamic创建时间:2003-09-30 文章属性:原创 文章提交:alert7 (sztcww_at_sina.com) 原贴于unix hacking版让大家讨论的,现尽斑竹义务,整理了一下。 https://www.xfocus.net/bbs/index.php?act=ST&f=19&t=28294 ★★ 前言: watercloud引发的<已初始化数据区溢出覆盖外部函数导入地址表的利用分析 > 讨论还是挺不错的 https://www.xfocus.net/bbs/index.php?act=ST&f=19&t=28101 可能还会有更极端一点情况,假如我们现在能溢出的大小不足以覆盖到.got或者.dtors,只能覆盖到.dynamic, 那么又该如何利用呢?! 注意:当然由于内存分布的不同,以下讨论只在LINUX上有意义。 我们可以先来看下一般情况下的内存布局 [16] .data PROGBITS 080495cc 0005cc 000010 00 WA 0 0 4 [17] .dynamic DYNAMIC 080495dc 0005dc 0000c8 08 WA 5 0 4 [18] .ctors PROGBITS 080496a4 0006a4 000008 00 WA 0 0 4 [19] .dtors PROGBITS 080496ac 0006ac 000008 00 WA 0 0 4 [20] .jcr PROGBITS 080496b4 0006b4 000004 00 WA 0 0 4 已初始化数据区一般位于.data section中,所以假如那里发生溢出的话,会覆盖到后面的SECTION。 ★★ example: /* [root@redhat9 root]# ./vul array addr 0x80495d8! 假设该程序只能覆盖.dynamic 部分,实际用gcc -o vul vul.c编译出来的也只能覆盖到.dynamic vul.c write by alert7 for only rewirte .dynamic gcc -o vul vul.c tested on redhat 9 ,redhat 8 ,redhat 7.3 */ #include<stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> unsigned int array[1]={0}; #define FATAL(X) { perror (X);exit (EXIT_FAILURE); } #define SIZE 204 int main(int argc,char *argv[]) { FILE * fd; printf("array addr %p!\n",&array); if (argc!=2) return 0; fd = fopen (argv[1], "r+"); if (fd == NULL) FATAL ("fopen"); if (fread (&array, SIZE ,1, fd) < 1)/* @@1 */ FATAL ("fread"); fclose(fd); /* @@2 */ return 0; } ★★ 简单分析: @@1 处的fread导致会覆盖到内存中的.dynamic section。覆盖.dynamic section有何作用后面再说,先放放。 @@2 处执行fclose函数。执行fclose函数过程可以参考《ELF动态解析符号过程(修订版)》 http://elfhack.whitecell.org/mydocs/ELF_symbol_resolve_process1.txt 由于fclose函数是该程序中是第一次被调用,所以将会引起一次函数符号的解析过程。 jmp GOT[x]-->_dl_runtime_resolve()-->fixup() 而在这函数符号的解析过程中,就需要用到.dynamic section,而现在.dynamic section已经 被我们覆盖了。基于这个事实,我们来看看如何exploit。 ★★ exploit过程 ★我们来看看最重要的fixup过程 static ElfW(Addr) __attribute_used__ fixup ( struct link_map *__unbounded l, ElfW(Word) reloc_offset) { const ElfW(Sym) *const symtab //symtab可控制 = (const void *) D_PTR (l, l_info[DT_SYMTAB]); const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); //strtab可控制 const PLTREL *const reloc //reloc可控制 = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; //sym可控制 void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); //rel_addr可控制 lookup_t result; ElfW(Addr) value; /* The use of `alloca' here looks ridiculous but it helps. The goal is to prevent the function from being inlined and thus optimized out. There is no official way to do this so we use this trick. gcc never inlines functions which use `alloca'. */ alloca (sizeof (int)); /* Sanity check that we're really looking at a PLT relocation. */ assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); //确保reloc的类型为ELF_MACHINE_JMP_SLOT /* Look up the target symbol. If the normal lookup rules are not used don't look in the global scope. */ //确保ELFW(ST_VISIBILITY) (sym->st_other)不为0 if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) { .....//不需要落入该处理代码中 } else { /* We already found the symbol. The module (and therefore its load address) is also known. */ value = l->l_addr + sym->st_value;//从而value也完全可控制 #ifdef DL_LOOKUP_RETURNS_MAP result = l; #endif } /* And now perhaps the relocation addend. */ value = elf_machine_plt_value (l, reloc, value);//对于x86来说是个空函数 /* Finally, fix up the plt itself. */ if (__builtin_expect (GL(dl_bind_not), 0)) return value; return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);//*reloc_addr = value; //确保rel_addr地址的写操作不会失败,随后返回就会跳到value去执行代码 } ★Dynamic segment 内存内容 -------------------------------------------------------------------------- Dynamic segment at offset 0x5dc contains 20 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x8048300 0x0000000d (FINI) 0x8048584 0x00000004 (HASH) 0x8048128 0x00000005 (STRTAB) 0x8048204 0x00000006 (SYMTAB) 0x8048164 0x0000000a (STRSZ) 117 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x80496b8 0x00000002 (PLTRELSZ) 56 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x80482c8 0x00000011 (REL) 0x80482c0 0x00000012 (RELSZ) 8 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x8048290 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x804827a 0x00000000 (NULL) 0x0 ★ 一些结构定义 typedef struct { Elf32_Sword d_tag; /* Dynamic entry type */ union { Elf32_Word d_val; /* Integer value */ Elf32_Addr d_ptr; /* Address value */ } d_un; } Elf32_Dyn; typedef struct { Elf32_Addr r_offset; /* Address */ Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel; typedef uint16_t Elf32_Section; typedef struct { Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf32_Section st_shndx; /* Section index */ } Elf32_Sym; ★ fake .dynamic sym类型为Elf32_Sym * reloc类型为Elf32_Rel * 低 ----->高 +-----------+-------------+----------+-----------------+---------+-----------+-----------+--------+------------+ |array(4) | NEEDED(8) | INIT(8) | FINI(8) | HASH(8) | STRTAB(8) | SYMTAB(8) | (48) | JMPREL (8) | +-----------+-------------+----------+-----------------+---------+-----------+-----------+--------+------------+ |<-----fake sym -------->|<--fake reloc--> | |--dynamic_addr 我们可以读ELF文件格式,所以dynamic_addr地址可知 最关键的是,我们要控制SYMTAB和JMPREL,一个fake的 Elf32_Sym sym,一个fake的Elf32_Rel reloc reloc_offset值我们很容易得到 1:为了要使fake reloc地址在dynamic_addr+16,我们需要使下列等式成立 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); dynamic_addr+16 = JMPREL + reloc_offset 所以,JMPREL值为dynamic_addr+16 - reloc_offset ok,现在fake reloc已经跑到了我们预想的地方了。 2: const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; #define ELF32_R_SYM(val) ((val) >> 8) 我们可以使ELF32_R_SYM( reloc->r_info)为0,那么sym的地址就等于SYMTAB的地址, 所以,我们可以设置SYMTAB = dynamic_addr; reloc->r_info=0; 3: void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); 对于可执行文件来说,l->l_addr应该为0, 所以,我们只需要设置reloc->r_offset为dynamic_addr-4;就可以了,那么rel_addr也为dynamic_addr-4;,当下面往这个地址写东西的 时候就不会发生什么问题。 4: 现在又需要满足assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); #define ELF32_R_TYPE(val) ((val) & 0xff) 所以第2步我们设置的 reloc->r_info为0应该重新调整一下。 现在把reloc->r_info设置为ELF_MACHINE_JMP_SLOT 5: 需要满足 ELFW(ST_VISIBILITY) (sym->st_other)不为0 #define ELF32_ST_VISIBILITY(o) ((o) & 0x03) 设置sym->st_other为2就可以了 6:设置sym->st_value为SHELLCODE地址,当fixup返回后就跳到SHELLCODE地址执行了。 总结一下: SYMTAB = dynamic_addr; JMPREL = dynamic_addr+16 - reloc_offset reloc->r_offset = dynamic_addr-4; reloc->r_info =ELF_MACHINE_JMP_SLOT; sym->st_other = 2; sym->st_value =SHELLCODE; ok,按照这个分析,写出exploit出来应该不是难事了吧。 dumplogin 提供了一个exploit可供参考。 https://www.xfocus.net/bbs/index.php?act=ST&f=19&t=28294 ★★ 更多 有兴趣的可以试试fake_sym.st_other &3为0的那个分支能否被利用。 欢迎到Unix Hacking讨论技术 https://www.xfocus.net/bbs/index.php?act=SF&f=19 --EOF |